From 38aa589e37de1d910cd5f09b39a5c29bf6c7b91a Mon Sep 17 00:00:00 2001 From: onli Date: Wed, 3 Apr 2024 23:34:53 +0200 Subject: [PATCH] Add image optimizer to thumbnail creation flow --- bundled-libs/composer/autoload_classmap.php | 37 + bundled-libs/composer/autoload_files.php | 1 + bundled-libs/composer/autoload_psr4.php | 3 + bundled-libs/composer/autoload_real.php | 2 +- bundled-libs/composer/autoload_static.php | 56 + bundled-libs/composer/installed.json | 206 ++ bundled-libs/composer/installed.php | 31 +- bundled-libs/composer/platform_check.php | 4 +- .../spatie/image-optimizer/CHANGELOG.md | 188 ++ bundled-libs/spatie/image-optimizer/LICENSE | 21 + bundled-libs/spatie/image-optimizer/README.md | 400 ++++ .../spatie/image-optimizer/composer.json | 51 + .../image-optimizer/src/DummyLogger.php | 44 + .../spatie/image-optimizer/src/Image.php | 36 + .../spatie/image-optimizer/src/Optimizer.php | 54 + .../image-optimizer/src/OptimizerChain.php | 123 ++ .../src/OptimizerChainFactory.php | 72 + .../src/Optimizers/Avifenc.php | 51 + .../src/Optimizers/BaseOptimizer.php | 63 + .../image-optimizer/src/Optimizers/Cwebp.php | 24 + .../src/Optimizers/Gifsicle.php | 24 + .../src/Optimizers/Jpegoptim.php | 15 + .../src/Optimizers/Optipng.php | 15 + .../src/Optimizers/Pngquant.php | 24 + .../image-optimizer/src/Optimizers/Svgo.php | 33 + .../spatie/image-optimizer/svgo.config.js | 13 + bundled-libs/symfony/polyfill-php80/LICENSE | 19 + bundled-libs/symfony/polyfill-php80/Php80.php | 115 ++ .../symfony/polyfill-php80/PhpToken.php | 103 + bundled-libs/symfony/polyfill-php80/README.md | 25 + .../Resources/stubs/Attribute.php | 31 + .../Resources/stubs/PhpToken.php | 16 + .../Resources/stubs/Stringable.php | 20 + .../Resources/stubs/UnhandledMatchError.php | 16 + .../Resources/stubs/ValueError.php | 16 + .../symfony/polyfill-php80/bootstrap.php | 42 + .../symfony/polyfill-php80/composer.json | 37 + bundled-libs/symfony/process/CHANGELOG.md | 116 ++ .../process/Exception/ExceptionInterface.php | 21 + .../Exception/InvalidArgumentException.php | 21 + .../process/Exception/LogicException.php | 21 + .../Exception/ProcessFailedException.php | 54 + .../Exception/ProcessSignaledException.php | 41 + .../Exception/ProcessTimedOutException.php | 69 + .../process/Exception/RuntimeException.php | 21 + .../symfony/process/ExecutableFinder.php | 86 + bundled-libs/symfony/process/InputStream.php | 96 + bundled-libs/symfony/process/LICENSE | 19 + .../symfony/process/PhpExecutableFinder.php | 103 + bundled-libs/symfony/process/PhpProcess.php | 72 + .../symfony/process/Pipes/AbstractPipes.php | 180 ++ .../symfony/process/Pipes/PipesInterface.php | 61 + .../symfony/process/Pipes/UnixPipes.php | 163 ++ .../symfony/process/Pipes/WindowsPipes.php | 204 ++ bundled-libs/symfony/process/Process.php | 1666 +++++++++++++++++ bundled-libs/symfony/process/ProcessUtils.php | 69 + bundled-libs/symfony/process/README.md | 28 + bundled-libs/symfony/process/composer.json | 29 + composer.json | 1 + composer.lock | 199 +- include/functions_images.inc.php | 13 + 61 files changed, 5378 insertions(+), 6 deletions(-) create mode 100644 bundled-libs/spatie/image-optimizer/CHANGELOG.md create mode 100644 bundled-libs/spatie/image-optimizer/LICENSE create mode 100644 bundled-libs/spatie/image-optimizer/README.md create mode 100644 bundled-libs/spatie/image-optimizer/composer.json create mode 100644 bundled-libs/spatie/image-optimizer/src/DummyLogger.php create mode 100644 bundled-libs/spatie/image-optimizer/src/Image.php create mode 100644 bundled-libs/spatie/image-optimizer/src/Optimizer.php create mode 100644 bundled-libs/spatie/image-optimizer/src/OptimizerChain.php create mode 100644 bundled-libs/spatie/image-optimizer/src/OptimizerChainFactory.php create mode 100644 bundled-libs/spatie/image-optimizer/src/Optimizers/Avifenc.php create mode 100644 bundled-libs/spatie/image-optimizer/src/Optimizers/BaseOptimizer.php create mode 100644 bundled-libs/spatie/image-optimizer/src/Optimizers/Cwebp.php create mode 100644 bundled-libs/spatie/image-optimizer/src/Optimizers/Gifsicle.php create mode 100644 bundled-libs/spatie/image-optimizer/src/Optimizers/Jpegoptim.php create mode 100644 bundled-libs/spatie/image-optimizer/src/Optimizers/Optipng.php create mode 100644 bundled-libs/spatie/image-optimizer/src/Optimizers/Pngquant.php create mode 100644 bundled-libs/spatie/image-optimizer/src/Optimizers/Svgo.php create mode 100644 bundled-libs/spatie/image-optimizer/svgo.config.js create mode 100644 bundled-libs/symfony/polyfill-php80/LICENSE create mode 100644 bundled-libs/symfony/polyfill-php80/Php80.php create mode 100644 bundled-libs/symfony/polyfill-php80/PhpToken.php create mode 100644 bundled-libs/symfony/polyfill-php80/README.md create mode 100644 bundled-libs/symfony/polyfill-php80/Resources/stubs/Attribute.php create mode 100644 bundled-libs/symfony/polyfill-php80/Resources/stubs/PhpToken.php create mode 100644 bundled-libs/symfony/polyfill-php80/Resources/stubs/Stringable.php create mode 100644 bundled-libs/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php create mode 100644 bundled-libs/symfony/polyfill-php80/Resources/stubs/ValueError.php create mode 100644 bundled-libs/symfony/polyfill-php80/bootstrap.php create mode 100644 bundled-libs/symfony/polyfill-php80/composer.json create mode 100644 bundled-libs/symfony/process/CHANGELOG.md create mode 100644 bundled-libs/symfony/process/Exception/ExceptionInterface.php create mode 100644 bundled-libs/symfony/process/Exception/InvalidArgumentException.php create mode 100644 bundled-libs/symfony/process/Exception/LogicException.php create mode 100644 bundled-libs/symfony/process/Exception/ProcessFailedException.php create mode 100644 bundled-libs/symfony/process/Exception/ProcessSignaledException.php create mode 100644 bundled-libs/symfony/process/Exception/ProcessTimedOutException.php create mode 100644 bundled-libs/symfony/process/Exception/RuntimeException.php create mode 100644 bundled-libs/symfony/process/ExecutableFinder.php create mode 100644 bundled-libs/symfony/process/InputStream.php create mode 100644 bundled-libs/symfony/process/LICENSE create mode 100644 bundled-libs/symfony/process/PhpExecutableFinder.php create mode 100644 bundled-libs/symfony/process/PhpProcess.php create mode 100644 bundled-libs/symfony/process/Pipes/AbstractPipes.php create mode 100644 bundled-libs/symfony/process/Pipes/PipesInterface.php create mode 100644 bundled-libs/symfony/process/Pipes/UnixPipes.php create mode 100644 bundled-libs/symfony/process/Pipes/WindowsPipes.php create mode 100644 bundled-libs/symfony/process/Process.php create mode 100644 bundled-libs/symfony/process/ProcessUtils.php create mode 100644 bundled-libs/symfony/process/README.md create mode 100644 bundled-libs/symfony/process/composer.json diff --git a/bundled-libs/composer/autoload_classmap.php b/bundled-libs/composer/autoload_classmap.php index 69064d536..56e0154a5 100644 --- a/bundled-libs/composer/autoload_classmap.php +++ b/bundled-libs/composer/autoload_classmap.php @@ -6,6 +6,7 @@ $baseDir = dirname($vendorDir); return array( + 'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', 'HTTP_Request2' => $vendorDir . '/pear/http_request2/HTTP/Request2.php', 'HTTP_Request2_Adapter' => $vendorDir . '/pear/http_request2/HTTP/Request2/Adapter.php', @@ -111,6 +112,7 @@ 'Net_DNS2_Updater' => $vendorDir . '/pear/net_dns2/Net/DNS2/Updater.php', 'Net_URL2' => $vendorDir . '/pear/net_url2/Net/URL2.php', 'PEAR_Exception' => $vendorDir . '/pear/pear_exception/PEAR/Exception.php', + 'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', 'Psr\\Log\\AbstractLogger' => $vendorDir . '/psr/log/Psr/Log/AbstractLogger.php', 'Psr\\Log\\InvalidArgumentException' => $vendorDir . '/psr/log/Psr/Log/InvalidArgumentException.php', 'Psr\\Log\\LogLevel' => $vendorDir . '/psr/log/Psr/Log/LogLevel.php', @@ -290,8 +292,43 @@ 'Smarty_Template_Source' => $vendorDir . '/smarty/smarty/libs/sysplugins/smarty_template_source.php', 'Smarty_Undefined_Variable' => $vendorDir . '/smarty/smarty/libs/sysplugins/smarty_undefined_variable.php', 'Smarty_Variable' => $vendorDir . '/smarty/smarty/libs/sysplugins/smarty_variable.php', + 'Spatie\\ImageOptimizer\\DummyLogger' => $vendorDir . '/spatie/image-optimizer/src/DummyLogger.php', + 'Spatie\\ImageOptimizer\\Image' => $vendorDir . '/spatie/image-optimizer/src/Image.php', + 'Spatie\\ImageOptimizer\\Optimizer' => $vendorDir . '/spatie/image-optimizer/src/Optimizer.php', + 'Spatie\\ImageOptimizer\\OptimizerChain' => $vendorDir . '/spatie/image-optimizer/src/OptimizerChain.php', + 'Spatie\\ImageOptimizer\\OptimizerChainFactory' => $vendorDir . '/spatie/image-optimizer/src/OptimizerChainFactory.php', + 'Spatie\\ImageOptimizer\\Optimizers\\Avifenc' => $vendorDir . '/spatie/image-optimizer/src/Optimizers/Avifenc.php', + 'Spatie\\ImageOptimizer\\Optimizers\\BaseOptimizer' => $vendorDir . '/spatie/image-optimizer/src/Optimizers/BaseOptimizer.php', + 'Spatie\\ImageOptimizer\\Optimizers\\Cwebp' => $vendorDir . '/spatie/image-optimizer/src/Optimizers/Cwebp.php', + 'Spatie\\ImageOptimizer\\Optimizers\\Gifsicle' => $vendorDir . '/spatie/image-optimizer/src/Optimizers/Gifsicle.php', + 'Spatie\\ImageOptimizer\\Optimizers\\Jpegoptim' => $vendorDir . '/spatie/image-optimizer/src/Optimizers/Jpegoptim.php', + 'Spatie\\ImageOptimizer\\Optimizers\\Optipng' => $vendorDir . '/spatie/image-optimizer/src/Optimizers/Optipng.php', + 'Spatie\\ImageOptimizer\\Optimizers\\Pngquant' => $vendorDir . '/spatie/image-optimizer/src/Optimizers/Pngquant.php', + 'Spatie\\ImageOptimizer\\Optimizers\\Svgo' => $vendorDir . '/spatie/image-optimizer/src/Optimizers/Svgo.php', + 'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'Symfony\\Component\\Process\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/process/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Process\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/process/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\Process\\Exception\\LogicException' => $vendorDir . '/symfony/process/Exception/LogicException.php', + 'Symfony\\Component\\Process\\Exception\\ProcessFailedException' => $vendorDir . '/symfony/process/Exception/ProcessFailedException.php', + 'Symfony\\Component\\Process\\Exception\\ProcessSignaledException' => $vendorDir . '/symfony/process/Exception/ProcessSignaledException.php', + 'Symfony\\Component\\Process\\Exception\\ProcessTimedOutException' => $vendorDir . '/symfony/process/Exception/ProcessTimedOutException.php', + 'Symfony\\Component\\Process\\Exception\\RuntimeException' => $vendorDir . '/symfony/process/Exception/RuntimeException.php', + 'Symfony\\Component\\Process\\ExecutableFinder' => $vendorDir . '/symfony/process/ExecutableFinder.php', + 'Symfony\\Component\\Process\\InputStream' => $vendorDir . '/symfony/process/InputStream.php', + 'Symfony\\Component\\Process\\PhpExecutableFinder' => $vendorDir . '/symfony/process/PhpExecutableFinder.php', + 'Symfony\\Component\\Process\\PhpProcess' => $vendorDir . '/symfony/process/PhpProcess.php', + 'Symfony\\Component\\Process\\Pipes\\AbstractPipes' => $vendorDir . '/symfony/process/Pipes/AbstractPipes.php', + 'Symfony\\Component\\Process\\Pipes\\PipesInterface' => $vendorDir . '/symfony/process/Pipes/PipesInterface.php', + 'Symfony\\Component\\Process\\Pipes\\UnixPipes' => $vendorDir . '/symfony/process/Pipes/UnixPipes.php', + 'Symfony\\Component\\Process\\Pipes\\WindowsPipes' => $vendorDir . '/symfony/process/Pipes/WindowsPipes.php', + 'Symfony\\Component\\Process\\Process' => $vendorDir . '/symfony/process/Process.php', + 'Symfony\\Component\\Process\\ProcessUtils' => $vendorDir . '/symfony/process/ProcessUtils.php', + 'Symfony\\Polyfill\\Php80\\Php80' => $vendorDir . '/symfony/polyfill-php80/Php80.php', + 'Symfony\\Polyfill\\Php80\\PhpToken' => $vendorDir . '/symfony/polyfill-php80/PhpToken.php', 'TPC_yyStackEntry' => $vendorDir . '/smarty/smarty/libs/sysplugins/smarty_internal_configfileparser.php', 'TP_yyStackEntry' => $vendorDir . '/smarty/smarty/libs/sysplugins/smarty_internal_templateparser.php', + 'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', 'voku\\cache\\AdapterApc' => $vendorDir . '/voku/simple-cache/src/voku/cache/AdapterApc.php', 'voku\\cache\\AdapterApcu' => $vendorDir . '/voku/simple-cache/src/voku/cache/AdapterApcu.php', 'voku\\cache\\AdapterArray' => $vendorDir . '/voku/simple-cache/src/voku/cache/AdapterArray.php', diff --git a/bundled-libs/composer/autoload_files.php b/bundled-libs/composer/autoload_files.php index 724287ce2..1192cbbf6 100644 --- a/bundled-libs/composer/autoload_files.php +++ b/bundled-libs/composer/autoload_files.php @@ -6,5 +6,6 @@ $baseDir = dirname($vendorDir); return array( + 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', '9e71c1459ef1226520e4b26dac3a180d' => $vendorDir . '/php81_bc/strftime/src/php-8.1-strftime.php', ); diff --git a/bundled-libs/composer/autoload_psr4.php b/bundled-libs/composer/autoload_psr4.php index db07f74c4..e5fbf24a5 100644 --- a/bundled-libs/composer/autoload_psr4.php +++ b/bundled-libs/composer/autoload_psr4.php @@ -7,6 +7,9 @@ return array( 'voku\\cache\\' => array($vendorDir . '/voku/simple-cache/src/voku/cache'), + 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'), + 'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'), + 'Spatie\\ImageOptimizer\\' => array($vendorDir . '/spatie/image-optimizer/src'), 'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'), 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), 'Katzgrau\\KLogger\\' => array($vendorDir . '/katzgrau/klogger/src'), diff --git a/bundled-libs/composer/autoload_real.php b/bundled-libs/composer/autoload_real.php index a929208f0..d35c9267b 100644 --- a/bundled-libs/composer/autoload_real.php +++ b/bundled-libs/composer/autoload_real.php @@ -35,7 +35,7 @@ public static function getLoader() require __DIR__ . '/autoload_static.php'; call_user_func(\Composer\Autoload\ComposerStaticInitcbda25b16bb8365467298ce193f0f30c::getInitializer($loader)); - $loader->setApcuPrefix('pZXHgfsNRpI3/XO56IexP'); + $loader->setApcuPrefix('O2/3unHby5sUj7rulNu/K'); $loader->register(true); $filesToLoad = \Composer\Autoload\ComposerStaticInitcbda25b16bb8365467298ce193f0f30c::$files; diff --git a/bundled-libs/composer/autoload_static.php b/bundled-libs/composer/autoload_static.php index e47a0cdcf..e0d64e1cd 100644 --- a/bundled-libs/composer/autoload_static.php +++ b/bundled-libs/composer/autoload_static.php @@ -7,6 +7,7 @@ class ComposerStaticInitcbda25b16bb8365467298ce193f0f30c { public static $files = array ( + 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', '9e71c1459ef1226520e4b26dac3a180d' => __DIR__ . '/..' . '/php81_bc/strftime/src/php-8.1-strftime.php', ); @@ -15,6 +16,12 @@ class ComposerStaticInitcbda25b16bb8365467298ce193f0f30c array ( 'voku\\cache\\' => 11, ), + 'S' => + array ( + 'Symfony\\Polyfill\\Php80\\' => 23, + 'Symfony\\Component\\Process\\' => 26, + 'Spatie\\ImageOptimizer\\' => 22, + ), 'P' => array ( 'Psr\\SimpleCache\\' => 16, @@ -31,6 +38,18 @@ class ComposerStaticInitcbda25b16bb8365467298ce193f0f30c array ( 0 => __DIR__ . '/..' . '/voku/simple-cache/src/voku/cache', ), + 'Symfony\\Polyfill\\Php80\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php80', + ), + 'Symfony\\Component\\Process\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/process', + ), + 'Spatie\\ImageOptimizer\\' => + array ( + 0 => __DIR__ . '/..' . '/spatie/image-optimizer/src', + ), 'Psr\\SimpleCache\\' => array ( 0 => __DIR__ . '/..' . '/psr/simple-cache/src', @@ -63,6 +82,7 @@ class ComposerStaticInitcbda25b16bb8365467298ce193f0f30c ); public static $classMap = array ( + 'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', 'HTTP_Request2' => __DIR__ . '/..' . '/pear/http_request2/HTTP/Request2.php', 'HTTP_Request2_Adapter' => __DIR__ . '/..' . '/pear/http_request2/HTTP/Request2/Adapter.php', @@ -168,6 +188,7 @@ class ComposerStaticInitcbda25b16bb8365467298ce193f0f30c 'Net_DNS2_Updater' => __DIR__ . '/..' . '/pear/net_dns2/Net/DNS2/Updater.php', 'Net_URL2' => __DIR__ . '/..' . '/pear/net_url2/Net/URL2.php', 'PEAR_Exception' => __DIR__ . '/..' . '/pear/pear_exception/PEAR/Exception.php', + 'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', 'Psr\\Log\\AbstractLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/AbstractLogger.php', 'Psr\\Log\\InvalidArgumentException' => __DIR__ . '/..' . '/psr/log/Psr/Log/InvalidArgumentException.php', 'Psr\\Log\\LogLevel' => __DIR__ . '/..' . '/psr/log/Psr/Log/LogLevel.php', @@ -347,8 +368,43 @@ class ComposerStaticInitcbda25b16bb8365467298ce193f0f30c 'Smarty_Template_Source' => __DIR__ . '/..' . '/smarty/smarty/libs/sysplugins/smarty_template_source.php', 'Smarty_Undefined_Variable' => __DIR__ . '/..' . '/smarty/smarty/libs/sysplugins/smarty_undefined_variable.php', 'Smarty_Variable' => __DIR__ . '/..' . '/smarty/smarty/libs/sysplugins/smarty_variable.php', + 'Spatie\\ImageOptimizer\\DummyLogger' => __DIR__ . '/..' . '/spatie/image-optimizer/src/DummyLogger.php', + 'Spatie\\ImageOptimizer\\Image' => __DIR__ . '/..' . '/spatie/image-optimizer/src/Image.php', + 'Spatie\\ImageOptimizer\\Optimizer' => __DIR__ . '/..' . '/spatie/image-optimizer/src/Optimizer.php', + 'Spatie\\ImageOptimizer\\OptimizerChain' => __DIR__ . '/..' . '/spatie/image-optimizer/src/OptimizerChain.php', + 'Spatie\\ImageOptimizer\\OptimizerChainFactory' => __DIR__ . '/..' . '/spatie/image-optimizer/src/OptimizerChainFactory.php', + 'Spatie\\ImageOptimizer\\Optimizers\\Avifenc' => __DIR__ . '/..' . '/spatie/image-optimizer/src/Optimizers/Avifenc.php', + 'Spatie\\ImageOptimizer\\Optimizers\\BaseOptimizer' => __DIR__ . '/..' . '/spatie/image-optimizer/src/Optimizers/BaseOptimizer.php', + 'Spatie\\ImageOptimizer\\Optimizers\\Cwebp' => __DIR__ . '/..' . '/spatie/image-optimizer/src/Optimizers/Cwebp.php', + 'Spatie\\ImageOptimizer\\Optimizers\\Gifsicle' => __DIR__ . '/..' . '/spatie/image-optimizer/src/Optimizers/Gifsicle.php', + 'Spatie\\ImageOptimizer\\Optimizers\\Jpegoptim' => __DIR__ . '/..' . '/spatie/image-optimizer/src/Optimizers/Jpegoptim.php', + 'Spatie\\ImageOptimizer\\Optimizers\\Optipng' => __DIR__ . '/..' . '/spatie/image-optimizer/src/Optimizers/Optipng.php', + 'Spatie\\ImageOptimizer\\Optimizers\\Pngquant' => __DIR__ . '/..' . '/spatie/image-optimizer/src/Optimizers/Pngquant.php', + 'Spatie\\ImageOptimizer\\Optimizers\\Svgo' => __DIR__ . '/..' . '/spatie/image-optimizer/src/Optimizers/Svgo.php', + 'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'Symfony\\Component\\Process\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/process/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Process\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/process/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\Process\\Exception\\LogicException' => __DIR__ . '/..' . '/symfony/process/Exception/LogicException.php', + 'Symfony\\Component\\Process\\Exception\\ProcessFailedException' => __DIR__ . '/..' . '/symfony/process/Exception/ProcessFailedException.php', + 'Symfony\\Component\\Process\\Exception\\ProcessSignaledException' => __DIR__ . '/..' . '/symfony/process/Exception/ProcessSignaledException.php', + 'Symfony\\Component\\Process\\Exception\\ProcessTimedOutException' => __DIR__ . '/..' . '/symfony/process/Exception/ProcessTimedOutException.php', + 'Symfony\\Component\\Process\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/process/Exception/RuntimeException.php', + 'Symfony\\Component\\Process\\ExecutableFinder' => __DIR__ . '/..' . '/symfony/process/ExecutableFinder.php', + 'Symfony\\Component\\Process\\InputStream' => __DIR__ . '/..' . '/symfony/process/InputStream.php', + 'Symfony\\Component\\Process\\PhpExecutableFinder' => __DIR__ . '/..' . '/symfony/process/PhpExecutableFinder.php', + 'Symfony\\Component\\Process\\PhpProcess' => __DIR__ . '/..' . '/symfony/process/PhpProcess.php', + 'Symfony\\Component\\Process\\Pipes\\AbstractPipes' => __DIR__ . '/..' . '/symfony/process/Pipes/AbstractPipes.php', + 'Symfony\\Component\\Process\\Pipes\\PipesInterface' => __DIR__ . '/..' . '/symfony/process/Pipes/PipesInterface.php', + 'Symfony\\Component\\Process\\Pipes\\UnixPipes' => __DIR__ . '/..' . '/symfony/process/Pipes/UnixPipes.php', + 'Symfony\\Component\\Process\\Pipes\\WindowsPipes' => __DIR__ . '/..' . '/symfony/process/Pipes/WindowsPipes.php', + 'Symfony\\Component\\Process\\Process' => __DIR__ . '/..' . '/symfony/process/Process.php', + 'Symfony\\Component\\Process\\ProcessUtils' => __DIR__ . '/..' . '/symfony/process/ProcessUtils.php', + 'Symfony\\Polyfill\\Php80\\Php80' => __DIR__ . '/..' . '/symfony/polyfill-php80/Php80.php', + 'Symfony\\Polyfill\\Php80\\PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/PhpToken.php', 'TPC_yyStackEntry' => __DIR__ . '/..' . '/smarty/smarty/libs/sysplugins/smarty_internal_configfileparser.php', 'TP_yyStackEntry' => __DIR__ . '/..' . '/smarty/smarty/libs/sysplugins/smarty_internal_templateparser.php', + 'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', 'voku\\cache\\AdapterApc' => __DIR__ . '/..' . '/voku/simple-cache/src/voku/cache/AdapterApc.php', 'voku\\cache\\AdapterApcu' => __DIR__ . '/..' . '/voku/simple-cache/src/voku/cache/AdapterApcu.php', 'voku\\cache\\AdapterArray' => __DIR__ . '/..' . '/voku/simple-cache/src/voku/cache/AdapterArray.php', diff --git a/bundled-libs/composer/installed.json b/bundled-libs/composer/installed.json index 8dcea3600..82f11c9a3 100644 --- a/bundled-libs/composer/installed.json +++ b/bundled-libs/composer/installed.json @@ -529,6 +529,212 @@ }, "install-path": "../smarty/smarty" }, + { + "name": "spatie/image-optimizer", + "version": "1.7.2", + "version_normalized": "1.7.2.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/image-optimizer.git", + "reference": "62f7463483d1bd975f6f06025d89d42a29608fe1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/image-optimizer/zipball/62f7463483d1bd975f6f06025d89d42a29608fe1", + "reference": "62f7463483d1bd975f6f06025d89d42a29608fe1", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.3|^8.0", + "psr/log": "^1.0 | ^2.0 | ^3.0", + "symfony/process": "^4.2|^5.0|^6.0|^7.0" + }, + "require-dev": { + "pestphp/pest": "^1.21", + "phpunit/phpunit": "^8.5.21|^9.4.4", + "symfony/var-dumper": "^4.2|^5.0|^6.0|^7.0" + }, + "time": "2023-11-03T10:08:02+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Spatie\\ImageOptimizer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Easily optimize images using PHP", + "homepage": "https://github.com/spatie/image-optimizer", + "keywords": [ + "image-optimizer", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/image-optimizer/issues", + "source": "https://github.com/spatie/image-optimizer/tree/1.7.2" + }, + "install-path": "../spatie/image-optimizer" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.29.0", + "version_normalized": "1.29.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "time": "2024-01-29T20:11:03+00:00", + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-php80" + }, + { + "name": "symfony/process", + "version": "v5.4.36", + "version_normalized": "5.4.36.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "4fdf34004f149cc20b2f51d7d119aa500caad975" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/4fdf34004f149cc20b2f51d7d119aa500caad975", + "reference": "4fdf34004f149cc20b2f51d7d119aa500caad975", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" + }, + "time": "2024-02-12T15:49:53+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v5.4.36" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/process" + }, { "name": "voku/simple-cache", "version": "4.1.0", diff --git a/bundled-libs/composer/installed.php b/bundled-libs/composer/installed.php index 41ab50020..31a8cbe7d 100644 --- a/bundled-libs/composer/installed.php +++ b/bundled-libs/composer/installed.php @@ -3,7 +3,7 @@ 'name' => 's9y/serendipity', 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => '10975a6127469bfdcb209dfae278f36e129c36e2', + 'reference' => 'f62d3ba8c81b64838bbe45c8313493e8b9ca3dba', 'type' => 's9y-core', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -91,7 +91,7 @@ 's9y/serendipity' => array( 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => '10975a6127469bfdcb209dfae278f36e129c36e2', + 'reference' => 'f62d3ba8c81b64838bbe45c8313493e8b9ca3dba', 'type' => 's9y-core', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -106,6 +106,33 @@ 'aliases' => array(), 'dev_requirement' => false, ), + 'spatie/image-optimizer' => array( + 'pretty_version' => '1.7.2', + 'version' => '1.7.2.0', + 'reference' => '62f7463483d1bd975f6f06025d89d42a29608fe1', + 'type' => 'library', + 'install_path' => __DIR__ . '/../spatie/image-optimizer', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-php80' => array( + 'pretty_version' => 'v1.29.0', + 'version' => '1.29.0.0', + 'reference' => '87b68208d5c1188808dd7839ee1e6c8ec3b02f1b', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-php80', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/process' => array( + 'pretty_version' => 'v5.4.36', + 'version' => '5.4.36.0', + 'reference' => '4fdf34004f149cc20b2f51d7d119aa500caad975', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/process', + 'aliases' => array(), + 'dev_requirement' => false, + ), 'voku/simple-cache' => array( 'pretty_version' => '4.1.0', 'version' => '4.1.0.0', diff --git a/bundled-libs/composer/platform_check.php b/bundled-libs/composer/platform_check.php index 6d3407dbb..92370c5a0 100644 --- a/bundled-libs/composer/platform_check.php +++ b/bundled-libs/composer/platform_check.php @@ -4,8 +4,8 @@ $issues = array(); -if (!(PHP_VERSION_ID >= 70100)) { - $issues[] = 'Your Composer dependencies require a PHP version ">= 7.1.0". You are running ' . PHP_VERSION . '.'; +if (!(PHP_VERSION_ID >= 70300)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 7.3.0". You are running ' . PHP_VERSION . '.'; } if ($issues) { diff --git a/bundled-libs/spatie/image-optimizer/CHANGELOG.md b/bundled-libs/spatie/image-optimizer/CHANGELOG.md new file mode 100644 index 000000000..b93f2f044 --- /dev/null +++ b/bundled-libs/spatie/image-optimizer/CHANGELOG.md @@ -0,0 +1,188 @@ +# Changelog + +All notable changes to `image-optimizer` will be documented in this file + +## 1.7.1 - 2023-07-27 + +### What's Changed + +- libavif version note by @0xb4lint in https://github.com/spatie/image-optimizer/pull/199 + +**Full Changelog**: https://github.com/spatie/image-optimizer/compare/1.7.0...1.7.1 + +## 1.7.0 - 2023-07-22 + +### What's Changed + +- README.md file size fixes, DSSIM score, optimized webp replaced by @0xb4lint in https://github.com/spatie/image-optimizer/pull/197 +- added AVIF support by @0xb4lint in https://github.com/spatie/image-optimizer/pull/198 + +**Full Changelog**: https://github.com/spatie/image-optimizer/compare/1.6.4...1.7.0 + +## 1.6.4 - 2023-03-10 + +### What's Changed + +- SVGO 3 Support by @l-alexandrov in https://github.com/spatie/image-optimizer/pull/186 + +### New Contributors + +- @l-alexandrov made their first contribution in https://github.com/spatie/image-optimizer/pull/186 + +**Full Changelog**: https://github.com/spatie/image-optimizer/compare/1.6.3...1.6.4 + +## 1.6.3 - 2023-02-28 + +### What's Changed + +- Update .gitattributes by @PaolaRuby in https://github.com/spatie/image-optimizer/pull/161 +- Feature: Convert PHPUnit tests to Pest by @mansoorkhan96 in https://github.com/spatie/image-optimizer/pull/167 +- Add dependabot automation by @patinthehat in https://github.com/spatie/image-optimizer/pull/173 +- Allow Pest Composer Plugin (fix failing tests) by @patinthehat in https://github.com/spatie/image-optimizer/pull/176 +- Update Dependabot Automation by @patinthehat in https://github.com/spatie/image-optimizer/pull/175 +- DOC: adding SilverStripe link by @sunnysideup in https://github.com/spatie/image-optimizer/pull/177 +- Bump dependabot/fetch-metadata from 1.3.5 to 1.3.6 by @dependabot in https://github.com/spatie/image-optimizer/pull/183 +- WebP Quality Option by @jan-tricks in https://github.com/spatie/image-optimizer/pull/185 +- Bump actions/checkout from 2 to 3 by @dependabot in https://github.com/spatie/image-optimizer/pull/174 + +### New Contributors + +- @PaolaRuby made their first contribution in https://github.com/spatie/image-optimizer/pull/161 +- @mansoorkhan96 made their first contribution in https://github.com/spatie/image-optimizer/pull/167 +- @patinthehat made their first contribution in https://github.com/spatie/image-optimizer/pull/173 +- @sunnysideup made their first contribution in https://github.com/spatie/image-optimizer/pull/177 +- @dependabot made their first contribution in https://github.com/spatie/image-optimizer/pull/183 +- @jan-tricks made their first contribution in https://github.com/spatie/image-optimizer/pull/185 + +**Full Changelog**: https://github.com/spatie/image-optimizer/compare/1.6.2...1.6.3 + +## 1.6.2 - 2021-12-21 + +## What's Changed + +- add support for Symfony 6 by @Nielsvanpach in https://github.com/spatie/image-optimizer/pull/155 + +**Full Changelog**: https://github.com/spatie/image-optimizer/compare/1.6.1...1.6.2 + +## 1.6.1 - 2021-11-17 + +## What's Changed + +- Add PHP 8.1 support by @freekmurze in https://github.com/spatie/image-optimizer/pull/154 + +**Full Changelog**: https://github.com/spatie/image-optimizer/compare/1.5.0...1.6.1 + +## 1.5.0 - 2021-10-18 + +- Support new releases of psr/log (#150) + +## 1.4.0 - 2021-04-22 + +- use `--skip-if-larger` pngquant option by default (#140) + +## 1.3.2 - 2020-11-28 + +- improve gifsicle (#131) + +## 1.3.1 - 2020-10-20 + +- fix empty string setBinaryPath() (#129) + +## 1.3.0 - 2020-10-10 + +- add support for php 8.0 + +## 1.2.1 - 2019-11-23 + +- allow symfony 5 components + +## 1.2.0 - 2019-08-28 + +- add support for webp + +## 1.1.6 - 2019-08-26 + +- do not export docs directory + +## 1.1.5 - 2019-01-15 + +- fix for svg's +- make compatible with PHPUnit 8 + +## 1.1.4 - 2019-01-14 + +- fix deprecation warning for passing strings to processes + +## 1.1.3 - 2018-11-19 + +- require the fileinfo extension + +## 1.1.2 - 2018-10-10 + +- make sure all optimizers use `binaryPath` + +## 1.1.1 - 2018-09-10 + +- fix logger output + +## 1.1.0 - 2018-06-05 + +- add `setBinaryPath` + +## 1.0.14 - 2018-03-07 + +- support more symfony versions + +## 1.0.13 - 2018-02-26 + +- added `text/plain` to the list of valid svg mime types + +## 1.0.12. - 2018-02-21 + +- added `image/svg+xml` mime type + +## 1.0.11 - 2018-02-08 + +- SVG mime type detection in PHP 7.2 + +## 1.0.10 - 2018-02-08 + +- Support symfony ^4.0 +- Support phpunit ^7.0 + +## 1.0.9 - 2017-11-03 + +- fix shell command quotes + +## 1.0.8 - 2017-09-14 + +- allow Symfony 2 components +- make Google Pagespeed tests pass + +## 1.0.7 - 2017-07-29 + +- lower requirements of dependencies + +## 1.0.6 - 2017-07-10 + +- fix `jpegoptim` parameters + +## 1.0.4 - 2017-07-07 + +- make `setTimeout` chainable + +## 1.0.3 - 2017-07-06 + +- fix `composer.json` + +## 1.0.2 - 2017-07-06 + +- fix for Laravel 5.5 users + +## 1.0.1 - 2017-07-06 + +- improve security + +## 1.0.0 - 2017-07-05 + +- initial release diff --git a/bundled-libs/spatie/image-optimizer/LICENSE b/bundled-libs/spatie/image-optimizer/LICENSE new file mode 100644 index 000000000..779764adc --- /dev/null +++ b/bundled-libs/spatie/image-optimizer/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Spatie + +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: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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. diff --git a/bundled-libs/spatie/image-optimizer/README.md b/bundled-libs/spatie/image-optimizer/README.md new file mode 100644 index 000000000..ee72ac9b2 --- /dev/null +++ b/bundled-libs/spatie/image-optimizer/README.md @@ -0,0 +1,400 @@ +# Easily optimize images using PHP + +[![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/image-optimizer.svg?style=flat-square)](https://packagist.org/packages/spatie/image-optimizer) +![Tests](https://github.com/spatie/image-optimizer/workflows/Tests/badge.svg) +[![Total Downloads](https://img.shields.io/packagist/dt/spatie/image-optimizer.svg?style=flat-square)](https://packagist.org/packages/spatie/image-optimizer) + +This package can optimize PNGs, JPGs, WEBPs, AVIFs, SVGs and GIFs by running them through a chain of various [image optimization tools](#optimization-tools). Here's how you can use it: + +```php +use Spatie\ImageOptimizer\OptimizerChainFactory; + +$optimizerChain = OptimizerChainFactory::create(); + +$optimizerChain->optimize($pathToImage); +``` + +The image at `$pathToImage` will be overwritten by an optimized version which should be smaller. The package will automatically detect which optimization binaries are installed on your system and use them. + +Here are some [example conversions](#example-conversions) that have been done by this package. + +Loving Laravel? Then head over to [the Laravel specific integration](https://github.com/spatie/laravel-image-optimizer). + +Using WordPress? Then try out [the WP CLI command](https://github.com/TypistTech/image-optimize-command). + +SilverStripe enthusiast? Don't waste time, go to [the SilverStripe module](https://github.com/axllent/silverstripe-image-optimiser). + +## Support us + +[](https://spatie.be/github-ad-click/image-optimizer) + +We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us). + +We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards). + +## Installation + +You can install the package via composer: + +```bash +composer require spatie/image-optimizer +``` + +### Optimization tools + +The package will use these optimizers if they are present on your system: + +- [JpegOptim](https://github.com/tjko/jpegoptim) +- [Optipng](http://optipng.sourceforge.net/) +- [Pngquant 2](https://pngquant.org/) +- [SVGO 1](https://github.com/svg/svgo) +- [Gifsicle](http://www.lcdf.org/gifsicle/) +- [cwebp](https://developers.google.com/speed/webp/docs/precompiled) +- [avifenc](https://github.com/AOMediaCodec/libavif/blob/main/doc/avifenc.1.md) + +Here's how to install all the optimizers on Ubuntu/Debian: + +```bash +sudo apt-get install jpegoptim +sudo apt-get install optipng +sudo apt-get install pngquant +sudo npm install -g svgo +sudo apt-get install gifsicle +sudo apt-get install webp +sudo apt-get install libavif-bin # minimum 0.9.3 +``` + +And here's how to install the binaries on MacOS (using [Homebrew](https://brew.sh/)): + +```bash +brew install jpegoptim +brew install optipng +brew install pngquant +npm install -g svgo +brew install gifsicle +brew install webp +brew install libavif +``` + +And here's how to install the binaries on Fedora/RHEL/CentOS: + +```bash +sudo dnf install epel-release +sudo dnf install jpegoptim +sudo dnf install optipng +sudo dnf install pngquant +sudo npm install -g svgo +sudo dnf install gifsicle +sudo dnf install libwebp-tools +sudo dnf install libavif-tools +``` + +## Which tools will do what? + +The package will automatically decide which tools to use on a particular image. + +### JPGs + +JPGs will be made smaller by running them through [JpegOptim](http://freecode.com/projects/jpegoptim). These options are used: +- `-m85`: this will store the image with 85% quality. This setting [seems to satisfy Google's Pagespeed compression rules](https://webmasters.stackexchange.com/questions/102094/google-pagespeed-how-to-satisfy-the-new-image-compression-rules) +- `--strip-all`: this strips out all text information such as comments and EXIF data +- `--all-progressive`: this will make sure the resulting image is a progressive one, meaning it can be downloaded using multiple passes of progressively higher details. + +### PNGs + +PNGs will be made smaller by running them through two tools. The first one is [Pngquant 2](https://pngquant.org/), a lossy PNG compressor. We set no extra options, their defaults are used. After that we run the image through a second one: [Optipng](http://optipng.sourceforge.net/). These options are used: +- `-i0`: this will result in a non-interlaced, progressive scanned image +- `-o2`: this set the optimization level to two (multiple IDAT compression trials) + +### SVGs + +SVGs will be minified by [SVGO](https://github.com/svg/svgo). SVGO's default configuration will be used, with the omission of the `cleanupIDs` and `removeViewBox` plugins because these are known to cause troubles when displaying multiple optimized SVGs on one page. + +Please be aware that SVGO can break your svg. You'll find more info on that in this [excellent blogpost](https://www.sarasoueidan.com/blog/svgo-tools/) by [Sara Soueidan](https://twitter.com/SaraSoueidan). + +### GIFs + +GIFs will be optimized by [Gifsicle](http://www.lcdf.org/gifsicle/). These options will be used: +- `-O3`: this sets the optimization level to Gifsicle's maximum, which produces the slowest but best results + +### WEBPs + +WEBPs will be optimized by [Cwebp](https://developers.google.com/speed/webp/docs/cwebp). These options will be used: +- `-m 6` for the slowest compression method in order to get the best compression. +- `-pass 10` for maximizing the amount of analysis pass. +- `-mt` multithreading for some speed improvements. +- `-q 90` Quality factor that brings the least noticeable changes. + +(Settings are original taken from [here](https://medium.com/@vinhlh/how-i-apply-webp-for-optimizing-images-9b11068db349)) + +### AVIFs + +AVIFs will be optimized by [avifenc](https://github.com/AOMediaCodec/libavif/blob/main/doc/avifenc.1.md). These options will be used: +- `-a cq-level=23`: Constant Quality level. Lower values mean better quality and greater file size (0-63). +- `-j all`: Number of jobs (worker threads, `all` uses all available cores). +- `--min 0`: Min quantizer for color (0-63). +- `--max 63`: Max quantizer for color (0-63). +- `--minalpha 0`: Min quantizer for alpha (0-63). +- `--maxalpha 63`: Max quantizer for alpha (0-63). +- `-a end-usage=q` Rate control mode set to Constant Quality mode. +- `-a tune=ssim`: SSIM as tune the encoder for distortion metric. + +(Settings are original taken from [here](https://web.dev/compress-images-avif/#create-an-avif-image-with-default-settings) and [here](https://github.com/feat-agency/avif)) + +## Usage + +This is the default way to use the package: + +``` php +use Spatie\ImageOptimizer\OptimizerChainFactory; + +$optimizerChain = OptimizerChainFactory::create(); + +$optimizerChain->optimize($pathToImage); +``` + +The image at `$pathToImage` will be overwritten by an optimized version which should be smaller. + +The package will automatically detect which optimization binaries are installed on your system and use them. + +To keep the original image, you can pass through a second argument`optimize`: +```php +use Spatie\ImageOptimizer\OptimizerChainFactory; + +$optimizerChain = OptimizerChainFactory::create(); + +$optimizerChain->optimize($pathToImage, $pathToOutput); +``` + +In that example the package won't touch `$pathToImage` and write an optimized version to `$pathToOutput`. + +### Setting a timeout + +You can set the maximum of time in seconds that each individual optimizer in a chain can use by calling `setTimeout`: + +```php +$optimizerChain + ->setTimeout(10) + ->optimize($pathToImage); +``` + +In this example each optimizer in the chain will get a maximum 10 seconds to do it's job. + +### Creating your own optimization chains + +If you want to customize the chain of optimizers you can do so by adding `Optimizer`s manually to an `OptimizerChain`. + +Here's an example where we only want `optipng` and `jpegoptim` to be used: + +```php +use Spatie\ImageOptimizer\OptimizerChain; +use Spatie\ImageOptimizer\Optimizers\Jpegoptim; +use Spatie\ImageOptimizer\Optimizers\Pngquant; + +$optimizerChain = (new OptimizerChain) + ->addOptimizer(new Jpegoptim([ + '--strip-all', + '--all-progressive', + ])) + + ->addOptimizer(new Pngquant([ + '--force', + ])) +``` + +Notice that you can pass the options an `Optimizer` should use to its constructor. + +### Writing a custom optimizers + +Want to use another command line utility to optimize your images? No problem. Just write your own optimizer. An optimizer is any class that implements the `Spatie\ImageOptimizer\Optimizers\Optimizer` interface: + +```php +namespace Spatie\ImageOptimizer\Optimizers; + +use Spatie\ImageOptimizer\Image; + +interface Optimizer +{ + /** + * Returns the name of the binary to be executed. + * + * @return string + */ + public function binaryName(): string; + + /** + * Determines if the given image can be handled by the optimizer. + * + * @param \Spatie\ImageOptimizer\Image $image + * + * @return bool + */ + public function canHandle(Image $image): bool; + + /** + * Set the path to the image that should be optimized. + * + * @param string $imagePath + * + * @return $this + */ + public function setImagePath(string $imagePath); + + /** + * Set the options the optimizer should use. + * + * @param array $options + * + * @return $this + */ + public function setOptions(array $options = []); + + /** + * Get the command that should be executed. + * + * @return string + */ + public function getCommand(): string; +} +``` + +If you want to view an example implementation take a look at [the existing optimizers](https://github.com/spatie/image-optimizer/tree/master/src/Optimizers) shipped with this package. + +You can easily add your optimizer by using the `addOptimizer` method on an `OptimizerChain`. + +``` php +use Spatie\ImageOptimizer\ImageOptimizerFactory; + +$optimizerChain = OptimizerChainFactory::create(); + +$optimizerChain + ->addOptimizer(new YourCustomOptimizer()) + ->optimize($pathToImage); +``` + +## Logging the optimization process + +By default the package will not throw any errors and just operate silently. To verify what the package is doing you can set a logger: + +```php +use Spatie\ImageOptimizer\OptimizerChainFactory; + +$optimizerChain = OptimizerChainFactory::create(); + +$optimizerChain + ->useLogger(new MyLogger()) + ->optimize($pathToImage); +``` + +A logger is a class that implements `Psr\Log\LoggerInterface`. A good logging library that's fully compliant is [Monolog](https://github.com/Seldaek/monolog). The package will write to log which `Optimizers` are used, which commands are executed and their output. + +## Example conversions + +Here are some real life example conversions done by this package. + +Methodology for JPG, WEBP, AVIF images: the [original image](https://unsplash.com/photos/jTeQavJjBDs) has been fed to [spatie/image](https://github.com/spatie/image) (using the default GD driver) and resized to 2048px width: + +```php +Spatie\Image\Image::load('original.jpg') + ->width(2048) + ->save('image.jpg'); // image.png, image.webp, image.avif +``` + +### jpg + +![Original](https://spatie.github.io/image-optimizer/examples/image.jpg) +Original
+771 KB + +![Optimized](https://spatie.github.io/image-optimizer/examples/image-optimized.jpg) +Optimized
+511 KB (-33.7%, DSSIM: 0.00052061) + +credits: Jeff Sheldon, via [Unsplash](https://unsplash.com) + +### webp + +![Original](https://spatie.github.io/image-optimizer/examples/image.webp) +Original
+461 KB + +![Optimized](https://spatie.github.io/image-optimizer/examples/image-optimized.webp) +Optimized
+184 KB (-60.0%, DSSIM: 0.00166036) + +credits: Jeff Sheldon, via [Unsplash](https://unsplash.com) + +### avif + +![Original](https://spatie.github.io/image-optimizer/examples/image.avif) +Original
+725 KB + +![Optimized](https://spatie.github.io/image-optimizer/examples/image-optimized.avif) +Optimized
+194 KB (-73.2%, DSSIM: 0.00163751) + +credits: Jeff Sheldon, via [Unsplash](https://unsplash.com) + +### png + +Original: Photoshop 'Save for web' | PNG-24 with transparency
+39 KB + +![Original](https://spatie.github.io/image-optimizer/examples/logo.png) + +Optimized
+16 KB (-59%, DSSIM: 0.00000251) + +![Optimized](https://spatie.github.io/image-optimizer/examples/logo-optimized.png) + +### svg + +Original: Illustrator | Web optimized SVG export
+25 KB + +![Original](https://spatie.github.io/image-optimizer/examples/graph.svg) + +Optimized
+20 KB (-21.5%) + +![Optimized](https://spatie.github.io/image-optimizer/examples/graph-optimized.svg) + +## Changelog + +Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. + +## Testing + +``` bash +composer test +``` + +## Contributing + +Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details. + +## Security + +If you've found a bug regarding security please mail [security@spatie.be](mailto:security@spatie.be) instead of using the issue tracker. + +## Postcardware + +You're free to use this package (it's [MIT-licensed](.github/LICENSE.md)), but if it makes it to your production environment we highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. + +Our address is: Spatie, Kruikstraat 22, 2018 Antwerp, Belgium. + +We publish all received postcards [on our company website](https://spatie.be/en/opensource/postcards). + +## Credits + +- [Freek Van der Herten](https://github.com/freekmurze) +- [All Contributors](../../contributors) + +This package has been inspired by [psliwa/image-optimizer](https://github.com/psliwa/image-optimizer) + +Emotional support provided by [Joke Forment](https://twitter.com/pronneur) + +## License + +The MIT License (MIT). Please see [License File](.github/LICENSE.md) for more information. diff --git a/bundled-libs/spatie/image-optimizer/composer.json b/bundled-libs/spatie/image-optimizer/composer.json new file mode 100644 index 000000000..f7dc94cdd --- /dev/null +++ b/bundled-libs/spatie/image-optimizer/composer.json @@ -0,0 +1,51 @@ +{ + "name": "spatie/image-optimizer", + "description": "Easily optimize images using PHP", + "keywords": [ + "spatie", + "image-optimizer" + ], + "homepage": "https://github.com/spatie/image-optimizer", + "license": "MIT", + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "require": { + "php": "^7.3|^8.0", + "ext-fileinfo": "*", + "psr/log": "^1.0 | ^2.0 | ^3.0", + "symfony/process": "^4.2|^5.0|^6.0|^7.0" + }, + "require-dev": { + "pestphp/pest": "^1.21", + "phpunit/phpunit": "^8.5.21|^9.4.4", + "symfony/var-dumper": "^4.2|^5.0|^6.0|^7.0" + }, + "autoload": { + "psr-4": { + "Spatie\\ImageOptimizer\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Spatie\\ImageOptimizer\\Test\\": "tests" + }, + "files": [ + "tests/helpers.php" + ] + }, + "scripts": { + "test": "vendor/bin/phpunit" + }, + "config": { + "sort-packages": true, + "allow-plugins": { + "pestphp/pest-plugin": true + } + } +} diff --git a/bundled-libs/spatie/image-optimizer/src/DummyLogger.php b/bundled-libs/spatie/image-optimizer/src/DummyLogger.php new file mode 100644 index 000000000..d6092001f --- /dev/null +++ b/bundled-libs/spatie/image-optimizer/src/DummyLogger.php @@ -0,0 +1,44 @@ +pathToImage = $pathToImage; + } + + public function mime(): string + { + return mime_content_type($this->pathToImage); + } + + public function path(): string + { + return $this->pathToImage; + } + + public function extension(): string + { + $extension = pathinfo($this->pathToImage, PATHINFO_EXTENSION); + + return strtolower($extension); + } +} diff --git a/bundled-libs/spatie/image-optimizer/src/Optimizer.php b/bundled-libs/spatie/image-optimizer/src/Optimizer.php new file mode 100644 index 000000000..1b5ff771e --- /dev/null +++ b/bundled-libs/spatie/image-optimizer/src/Optimizer.php @@ -0,0 +1,54 @@ +useLogger(new DummyLogger()); + } + + public function getOptimizers(): array + { + return $this->optimizers; + } + + public function addOptimizer(Optimizer $optimizer) + { + $this->optimizers[] = $optimizer; + + return $this; + } + + public function setOptimizers(array $optimizers) + { + $this->optimizers = []; + + foreach ($optimizers as $optimizer) { + $this->addOptimizer($optimizer); + } + + return $this; + } + + /* + * Set the amount of seconds each separate optimizer may use. + */ + public function setTimeout(int $timeoutInSeconds) + { + $this->timeout = $timeoutInSeconds; + + return $this; + } + + public function useLogger(LoggerInterface $log) + { + $this->logger = $log; + + return $this; + } + + public function optimize(string $pathToImage, string $pathToOutput = null) + { + if ($pathToOutput) { + copy($pathToImage, $pathToOutput); + + $pathToImage = $pathToOutput; + } + + $image = new Image($pathToImage); + + $this->logger->info("Start optimizing {$pathToImage}"); + + foreach ($this->optimizers as $optimizer) { + $this->applyOptimizer($optimizer, $image); + } + } + + protected function applyOptimizer(Optimizer $optimizer, Image $image) + { + if (! $optimizer->canHandle($image)) { + return; + } + + $optimizerClass = get_class($optimizer); + + $this->logger->info("Using optimizer: `{$optimizerClass}`"); + + $optimizer->setImagePath($image->path()); + + $command = $optimizer->getCommand(); + + $this->logger->info("Executing `{$command}`"); + + $process = Process::fromShellCommandline($command); + + $process + ->setTimeout($this->timeout) + ->run(); + + if ( + ($tmpPath = $optimizer->getTmpPath()) && + file_exists($tmpPath) + ) { + unlink($tmpPath); + } + + $this->logResult($process); + } + + protected function logResult(Process $process) + { + if (! $process->isSuccessful()) { + $this->logger->error("Process errored with `{$process->getErrorOutput()}`"); + + return; + } + + $this->logger->info("Process successfully ended with output `{$process->getOutput()}`"); + } +} diff --git a/bundled-libs/spatie/image-optimizer/src/OptimizerChainFactory.php b/bundled-libs/spatie/image-optimizer/src/OptimizerChainFactory.php new file mode 100644 index 000000000..5c724f94a --- /dev/null +++ b/bundled-libs/spatie/image-optimizer/src/OptimizerChainFactory.php @@ -0,0 +1,72 @@ +addOptimizer(new Jpegoptim([ + $jpegQuality, + '--strip-all', + '--all-progressive', + ])) + + ->addOptimizer(new Pngquant([ + $pngQuality, + '--force', + '--skip-if-larger', + ])) + + ->addOptimizer(new Optipng([ + '-i0', + '-o2', + '-quiet', + ])) + + ->addOptimizer(new Svgo([ + '--config=svgo.config.js', + ])) + + ->addOptimizer(new Gifsicle([ + '-b', + '-O3', + ])) + ->addOptimizer(new Cwebp([ + $webpQuality, + '-m 6', + '-pass 10', + '-mt', + ])) + ->addOptimizer(new Avifenc([ + $avifQuality, + '-j all', + '--min 0', + '--max 63', + '--minalpha 0', + '--maxalpha 63', + '-a end-usage=q', + '-a tune=ssim', + ])); + } +} diff --git a/bundled-libs/spatie/image-optimizer/src/Optimizers/Avifenc.php b/bundled-libs/spatie/image-optimizer/src/Optimizers/Avifenc.php new file mode 100644 index 000000000..0aca78187 --- /dev/null +++ b/bundled-libs/spatie/image-optimizer/src/Optimizers/Avifenc.php @@ -0,0 +1,51 @@ +extension() === 'avif'; + } + + return $image->mime() === 'image/avif'; + } + + public function getCommand(): string + { + return $this->getDecodeCommand().' && ' + .$this->getEncodeCommand(); + } + + protected function getDecodeCommand() + { + $this->tmpPath = tempnam(sys_get_temp_dir(), 'avifdec').'.png'; + + $optionString = implode(' ', [ + '-j all', + '--ignore-icc', + '--no-strict', + '--png-compress 0', + ]); + + return "\"{$this->binaryPath}{$this->decodeBinaryName}\" {$optionString}" + .' '.escapeshellarg($this->imagePath) + .' '.escapeshellarg($this->tmpPath); + } + + protected function getEncodeCommand() + { + $optionString = implode(' ', $this->options); + + return "\"{$this->binaryPath}{$this->binaryName}\" {$optionString}" + .' '.escapeshellarg($this->tmpPath) + .' '.escapeshellarg($this->imagePath); + } +} diff --git a/bundled-libs/spatie/image-optimizer/src/Optimizers/BaseOptimizer.php b/bundled-libs/spatie/image-optimizer/src/Optimizers/BaseOptimizer.php new file mode 100644 index 000000000..eb4f57b35 --- /dev/null +++ b/bundled-libs/spatie/image-optimizer/src/Optimizers/BaseOptimizer.php @@ -0,0 +1,63 @@ +setOptions($options); + } + + public function binaryName(): string + { + return $this->binaryName; + } + + public function setBinaryPath(string $binaryPath) + { + if (strlen($binaryPath) > 0 && substr($binaryPath, -1) !== DIRECTORY_SEPARATOR) { + $binaryPath = $binaryPath.DIRECTORY_SEPARATOR; + } + + $this->binaryPath = $binaryPath; + + return $this; + } + + public function setImagePath(string $imagePath) + { + $this->imagePath = $imagePath; + + return $this; + } + + public function setOptions(array $options = []) + { + $this->options = $options; + + return $this; + } + + public function getCommand(): string + { + $optionString = implode(' ', $this->options); + + return "\"{$this->binaryPath}{$this->binaryName}\" {$optionString} ".escapeshellarg($this->imagePath); + } + + public function getTmpPath(): ?string + { + return $this->tmpPath; + } +} diff --git a/bundled-libs/spatie/image-optimizer/src/Optimizers/Cwebp.php b/bundled-libs/spatie/image-optimizer/src/Optimizers/Cwebp.php new file mode 100644 index 000000000..b6b944206 --- /dev/null +++ b/bundled-libs/spatie/image-optimizer/src/Optimizers/Cwebp.php @@ -0,0 +1,24 @@ +mime() === 'image/webp'; + } + + public function getCommand(): string + { + $optionString = implode(' ', $this->options); + + return "\"{$this->binaryPath}{$this->binaryName}\" {$optionString}" + .' '.escapeshellarg($this->imagePath) + .' -o '.escapeshellarg($this->imagePath); + } +} diff --git a/bundled-libs/spatie/image-optimizer/src/Optimizers/Gifsicle.php b/bundled-libs/spatie/image-optimizer/src/Optimizers/Gifsicle.php new file mode 100644 index 000000000..fdc5bf9d6 --- /dev/null +++ b/bundled-libs/spatie/image-optimizer/src/Optimizers/Gifsicle.php @@ -0,0 +1,24 @@ +mime() === 'image/gif'; + } + + public function getCommand(): string + { + $optionString = implode(' ', $this->options); + + return "\"{$this->binaryPath}{$this->binaryName}\" {$optionString}" + .' -i '.escapeshellarg($this->imagePath) + .' -o '.escapeshellarg($this->imagePath); + } +} diff --git a/bundled-libs/spatie/image-optimizer/src/Optimizers/Jpegoptim.php b/bundled-libs/spatie/image-optimizer/src/Optimizers/Jpegoptim.php new file mode 100644 index 000000000..7f94e158e --- /dev/null +++ b/bundled-libs/spatie/image-optimizer/src/Optimizers/Jpegoptim.php @@ -0,0 +1,15 @@ +mime() === 'image/jpeg'; + } +} diff --git a/bundled-libs/spatie/image-optimizer/src/Optimizers/Optipng.php b/bundled-libs/spatie/image-optimizer/src/Optimizers/Optipng.php new file mode 100644 index 000000000..4b6a65542 --- /dev/null +++ b/bundled-libs/spatie/image-optimizer/src/Optimizers/Optipng.php @@ -0,0 +1,15 @@ +mime() === 'image/png'; + } +} diff --git a/bundled-libs/spatie/image-optimizer/src/Optimizers/Pngquant.php b/bundled-libs/spatie/image-optimizer/src/Optimizers/Pngquant.php new file mode 100644 index 000000000..e14bba9c8 --- /dev/null +++ b/bundled-libs/spatie/image-optimizer/src/Optimizers/Pngquant.php @@ -0,0 +1,24 @@ +mime() === 'image/png'; + } + + public function getCommand(): string + { + $optionString = implode(' ', $this->options); + + return "\"{$this->binaryPath}{$this->binaryName}\" {$optionString}" + .' '.escapeshellarg($this->imagePath) + .' --output='.escapeshellarg($this->imagePath); + } +} diff --git a/bundled-libs/spatie/image-optimizer/src/Optimizers/Svgo.php b/bundled-libs/spatie/image-optimizer/src/Optimizers/Svgo.php new file mode 100644 index 000000000..0389a64ce --- /dev/null +++ b/bundled-libs/spatie/image-optimizer/src/Optimizers/Svgo.php @@ -0,0 +1,33 @@ +extension() !== 'svg') { + return false; + } + + return in_array($image->mime(), [ + 'text/html', + 'image/svg', + 'image/svg+xml', + 'text/plain', + ]); + } + + public function getCommand(): string + { + $optionString = implode(' ', $this->options); + + return "\"{$this->binaryPath}{$this->binaryName}\" {$optionString}" + .' --input='.escapeshellarg($this->imagePath) + .' --output='.escapeshellarg($this->imagePath); + } +} diff --git a/bundled-libs/spatie/image-optimizer/svgo.config.js b/bundled-libs/spatie/image-optimizer/svgo.config.js new file mode 100644 index 000000000..aba77c50f --- /dev/null +++ b/bundled-libs/spatie/image-optimizer/svgo.config.js @@ -0,0 +1,13 @@ +module.exports = { + plugins: [ + { + name: 'preset-default', + params: { + overrides: { + cleanupIDs: false, + removeViewBox: false, + }, + }, + }, + ], +}; \ No newline at end of file diff --git a/bundled-libs/symfony/polyfill-php80/LICENSE b/bundled-libs/symfony/polyfill-php80/LICENSE new file mode 100644 index 000000000..0ed3a2465 --- /dev/null +++ b/bundled-libs/symfony/polyfill-php80/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020-present Fabien Potencier + +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: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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. diff --git a/bundled-libs/symfony/polyfill-php80/Php80.php b/bundled-libs/symfony/polyfill-php80/Php80.php new file mode 100644 index 000000000..362dd1a95 --- /dev/null +++ b/bundled-libs/symfony/polyfill-php80/Php80.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php80; + +/** + * @author Ion Bazan + * @author Nico Oelgart + * @author Nicolas Grekas + * + * @internal + */ +final class Php80 +{ + public static function fdiv(float $dividend, float $divisor): float + { + return @($dividend / $divisor); + } + + public static function get_debug_type($value): string + { + switch (true) { + case null === $value: return 'null'; + case \is_bool($value): return 'bool'; + case \is_string($value): return 'string'; + case \is_array($value): return 'array'; + case \is_int($value): return 'int'; + case \is_float($value): return 'float'; + case \is_object($value): break; + case $value instanceof \__PHP_Incomplete_Class: return '__PHP_Incomplete_Class'; + default: + if (null === $type = @get_resource_type($value)) { + return 'unknown'; + } + + if ('Unknown' === $type) { + $type = 'closed'; + } + + return "resource ($type)"; + } + + $class = \get_class($value); + + if (false === strpos($class, '@')) { + return $class; + } + + return (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous'; + } + + public static function get_resource_id($res): int + { + if (!\is_resource($res) && null === @get_resource_type($res)) { + throw new \TypeError(sprintf('Argument 1 passed to get_resource_id() must be of the type resource, %s given', get_debug_type($res))); + } + + return (int) $res; + } + + public static function preg_last_error_msg(): string + { + switch (preg_last_error()) { + case \PREG_INTERNAL_ERROR: + return 'Internal error'; + case \PREG_BAD_UTF8_ERROR: + return 'Malformed UTF-8 characters, possibly incorrectly encoded'; + case \PREG_BAD_UTF8_OFFSET_ERROR: + return 'The offset did not correspond to the beginning of a valid UTF-8 code point'; + case \PREG_BACKTRACK_LIMIT_ERROR: + return 'Backtrack limit exhausted'; + case \PREG_RECURSION_LIMIT_ERROR: + return 'Recursion limit exhausted'; + case \PREG_JIT_STACKLIMIT_ERROR: + return 'JIT stack limit exhausted'; + case \PREG_NO_ERROR: + return 'No error'; + default: + return 'Unknown error'; + } + } + + public static function str_contains(string $haystack, string $needle): bool + { + return '' === $needle || false !== strpos($haystack, $needle); + } + + public static function str_starts_with(string $haystack, string $needle): bool + { + return 0 === strncmp($haystack, $needle, \strlen($needle)); + } + + public static function str_ends_with(string $haystack, string $needle): bool + { + if ('' === $needle || $needle === $haystack) { + return true; + } + + if ('' === $haystack) { + return false; + } + + $needleLength = \strlen($needle); + + return $needleLength <= \strlen($haystack) && 0 === substr_compare($haystack, $needle, -$needleLength); + } +} diff --git a/bundled-libs/symfony/polyfill-php80/PhpToken.php b/bundled-libs/symfony/polyfill-php80/PhpToken.php new file mode 100644 index 000000000..fe6e69105 --- /dev/null +++ b/bundled-libs/symfony/polyfill-php80/PhpToken.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php80; + +/** + * @author Fedonyuk Anton + * + * @internal + */ +class PhpToken implements \Stringable +{ + /** + * @var int + */ + public $id; + + /** + * @var string + */ + public $text; + + /** + * @var int + */ + public $line; + + /** + * @var int + */ + public $pos; + + public function __construct(int $id, string $text, int $line = -1, int $position = -1) + { + $this->id = $id; + $this->text = $text; + $this->line = $line; + $this->pos = $position; + } + + public function getTokenName(): ?string + { + if ('UNKNOWN' === $name = token_name($this->id)) { + $name = \strlen($this->text) > 1 || \ord($this->text) < 32 ? null : $this->text; + } + + return $name; + } + + /** + * @param int|string|array $kind + */ + public function is($kind): bool + { + foreach ((array) $kind as $value) { + if (\in_array($value, [$this->id, $this->text], true)) { + return true; + } + } + + return false; + } + + public function isIgnorable(): bool + { + return \in_array($this->id, [\T_WHITESPACE, \T_COMMENT, \T_DOC_COMMENT, \T_OPEN_TAG], true); + } + + public function __toString(): string + { + return (string) $this->text; + } + + /** + * @return static[] + */ + public static function tokenize(string $code, int $flags = 0): array + { + $line = 1; + $position = 0; + $tokens = token_get_all($code, $flags); + foreach ($tokens as $index => $token) { + if (\is_string($token)) { + $id = \ord($token); + $text = $token; + } else { + [$id, $text, $line] = $token; + } + $tokens[$index] = new static($id, $text, $line, $position); + $position += \strlen($text); + } + + return $tokens; + } +} diff --git a/bundled-libs/symfony/polyfill-php80/README.md b/bundled-libs/symfony/polyfill-php80/README.md new file mode 100644 index 000000000..3816c559d --- /dev/null +++ b/bundled-libs/symfony/polyfill-php80/README.md @@ -0,0 +1,25 @@ +Symfony Polyfill / Php80 +======================== + +This component provides features added to PHP 8.0 core: + +- [`Stringable`](https://php.net/stringable) interface +- [`fdiv`](https://php.net/fdiv) +- [`ValueError`](https://php.net/valueerror) class +- [`UnhandledMatchError`](https://php.net/unhandledmatcherror) class +- `FILTER_VALIDATE_BOOL` constant +- [`get_debug_type`](https://php.net/get_debug_type) +- [`PhpToken`](https://php.net/phptoken) class +- [`preg_last_error_msg`](https://php.net/preg_last_error_msg) +- [`str_contains`](https://php.net/str_contains) +- [`str_starts_with`](https://php.net/str_starts_with) +- [`str_ends_with`](https://php.net/str_ends_with) +- [`get_resource_id`](https://php.net/get_resource_id) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/bundled-libs/symfony/polyfill-php80/Resources/stubs/Attribute.php b/bundled-libs/symfony/polyfill-php80/Resources/stubs/Attribute.php new file mode 100644 index 000000000..2b955423f --- /dev/null +++ b/bundled-libs/symfony/polyfill-php80/Resources/stubs/Attribute.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#[Attribute(Attribute::TARGET_CLASS)] +final class Attribute +{ + public const TARGET_CLASS = 1; + public const TARGET_FUNCTION = 2; + public const TARGET_METHOD = 4; + public const TARGET_PROPERTY = 8; + public const TARGET_CLASS_CONSTANT = 16; + public const TARGET_PARAMETER = 32; + public const TARGET_ALL = 63; + public const IS_REPEATABLE = 64; + + /** @var int */ + public $flags; + + public function __construct(int $flags = self::TARGET_ALL) + { + $this->flags = $flags; + } +} diff --git a/bundled-libs/symfony/polyfill-php80/Resources/stubs/PhpToken.php b/bundled-libs/symfony/polyfill-php80/Resources/stubs/PhpToken.php new file mode 100644 index 000000000..bd1212f6e --- /dev/null +++ b/bundled-libs/symfony/polyfill-php80/Resources/stubs/PhpToken.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000 && extension_loaded('tokenizer')) { + class PhpToken extends Symfony\Polyfill\Php80\PhpToken + { + } +} diff --git a/bundled-libs/symfony/polyfill-php80/Resources/stubs/Stringable.php b/bundled-libs/symfony/polyfill-php80/Resources/stubs/Stringable.php new file mode 100644 index 000000000..7c62d7508 --- /dev/null +++ b/bundled-libs/symfony/polyfill-php80/Resources/stubs/Stringable.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000) { + interface Stringable + { + /** + * @return string + */ + public function __toString(); + } +} diff --git a/bundled-libs/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php b/bundled-libs/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php new file mode 100644 index 000000000..01c6c6c8a --- /dev/null +++ b/bundled-libs/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000) { + class UnhandledMatchError extends Error + { + } +} diff --git a/bundled-libs/symfony/polyfill-php80/Resources/stubs/ValueError.php b/bundled-libs/symfony/polyfill-php80/Resources/stubs/ValueError.php new file mode 100644 index 000000000..783dbc28c --- /dev/null +++ b/bundled-libs/symfony/polyfill-php80/Resources/stubs/ValueError.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000) { + class ValueError extends Error + { + } +} diff --git a/bundled-libs/symfony/polyfill-php80/bootstrap.php b/bundled-libs/symfony/polyfill-php80/bootstrap.php new file mode 100644 index 000000000..e5f7dbc1a --- /dev/null +++ b/bundled-libs/symfony/polyfill-php80/bootstrap.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php80 as p; + +if (\PHP_VERSION_ID >= 80000) { + return; +} + +if (!defined('FILTER_VALIDATE_BOOL') && defined('FILTER_VALIDATE_BOOLEAN')) { + define('FILTER_VALIDATE_BOOL', \FILTER_VALIDATE_BOOLEAN); +} + +if (!function_exists('fdiv')) { + function fdiv(float $num1, float $num2): float { return p\Php80::fdiv($num1, $num2); } +} +if (!function_exists('preg_last_error_msg')) { + function preg_last_error_msg(): string { return p\Php80::preg_last_error_msg(); } +} +if (!function_exists('str_contains')) { + function str_contains(?string $haystack, ?string $needle): bool { return p\Php80::str_contains($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('str_starts_with')) { + function str_starts_with(?string $haystack, ?string $needle): bool { return p\Php80::str_starts_with($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('str_ends_with')) { + function str_ends_with(?string $haystack, ?string $needle): bool { return p\Php80::str_ends_with($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('get_debug_type')) { + function get_debug_type($value): string { return p\Php80::get_debug_type($value); } +} +if (!function_exists('get_resource_id')) { + function get_resource_id($resource): int { return p\Php80::get_resource_id($resource); } +} diff --git a/bundled-libs/symfony/polyfill-php80/composer.json b/bundled-libs/symfony/polyfill-php80/composer.json new file mode 100644 index 000000000..46ccde203 --- /dev/null +++ b/bundled-libs/symfony/polyfill-php80/composer.json @@ -0,0 +1,37 @@ +{ + "name": "symfony/polyfill-php80", + "type": "library", + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "keywords": ["polyfill", "shim", "compatibility", "portable"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php80\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/bundled-libs/symfony/process/CHANGELOG.md b/bundled-libs/symfony/process/CHANGELOG.md new file mode 100644 index 000000000..31b9ee6a2 --- /dev/null +++ b/bundled-libs/symfony/process/CHANGELOG.md @@ -0,0 +1,116 @@ +CHANGELOG +========= + +5.2.0 +----- + + * added `Process::setOptions()` to set `Process` specific options + * added option `create_new_console` to allow a subprocess to continue + to run after the main script exited, both on Linux and on Windows + +5.1.0 +----- + + * added `Process::getStartTime()` to retrieve the start time of the process as float + +5.0.0 +----- + + * removed `Process::inheritEnvironmentVariables()` + * removed `PhpProcess::setPhpBinary()` + * `Process` must be instantiated with a command array, use `Process::fromShellCommandline()` when the command should be parsed by the shell + * removed `Process::setCommandLine()` + +4.4.0 +----- + + * deprecated `Process::inheritEnvironmentVariables()`: env variables are always inherited. + * added `Process::getLastOutputTime()` method + +4.2.0 +----- + + * added the `Process::fromShellCommandline()` to run commands in a shell wrapper + * deprecated passing a command as string when creating a `Process` instance + * deprecated the `Process::setCommandline()` and the `PhpProcess::setPhpBinary()` methods + * added the `Process::waitUntil()` method to wait for the process only for a + specific output, then continue the normal execution of your application + +4.1.0 +----- + + * added the `Process::isTtySupported()` method that allows to check for TTY support + * made `PhpExecutableFinder` look for the `PHP_BINARY` env var when searching the php binary + * added the `ProcessSignaledException` class to properly catch signaled process errors + +4.0.0 +----- + + * environment variables will always be inherited + * added a second `array $env = []` argument to the `start()`, `run()`, + `mustRun()`, and `restart()` methods of the `Process` class + * added a second `array $env = []` argument to the `start()` method of the + `PhpProcess` class + * the `ProcessUtils::escapeArgument()` method has been removed + * the `areEnvironmentVariablesInherited()`, `getOptions()`, and `setOptions()` + methods of the `Process` class have been removed + * support for passing `proc_open()` options has been removed + * removed the `ProcessBuilder` class, use the `Process` class instead + * removed the `getEnhanceWindowsCompatibility()` and `setEnhanceWindowsCompatibility()` methods of the `Process` class + * passing a not existing working directory to the constructor of the `Symfony\Component\Process\Process` class is not + supported anymore + +3.4.0 +----- + + * deprecated the ProcessBuilder class + * deprecated calling `Process::start()` without setting a valid working directory beforehand (via `setWorkingDirectory()` or constructor) + +3.3.0 +----- + + * added command line arrays in the `Process` class + * added `$env` argument to `Process::start()`, `run()`, `mustRun()` and `restart()` methods + * deprecated the `ProcessUtils::escapeArgument()` method + * deprecated not inheriting environment variables + * deprecated configuring `proc_open()` options + * deprecated configuring enhanced Windows compatibility + * deprecated configuring enhanced sigchild compatibility + +2.5.0 +----- + + * added support for PTY mode + * added the convenience method "mustRun" + * deprecation: Process::setStdin() is deprecated in favor of Process::setInput() + * deprecation: Process::getStdin() is deprecated in favor of Process::getInput() + * deprecation: Process::setInput() and ProcessBuilder::setInput() do not accept non-scalar types + +2.4.0 +----- + + * added the ability to define an idle timeout + +2.3.0 +----- + + * added ProcessUtils::escapeArgument() to fix the bug in escapeshellarg() function on Windows + * added Process::signal() + * added Process::getPid() + * added support for a TTY mode + +2.2.0 +----- + + * added ProcessBuilder::setArguments() to reset the arguments on a builder + * added a way to retrieve the standard and error output incrementally + * added Process:restart() + +2.1.0 +----- + + * added support for non-blocking processes (start(), wait(), isRunning(), stop()) + * enhanced Windows compatibility + * added Process::getExitCodeText() that returns a string representation for + the exit code returned by the process + * added ProcessBuilder diff --git a/bundled-libs/symfony/process/Exception/ExceptionInterface.php b/bundled-libs/symfony/process/Exception/ExceptionInterface.php new file mode 100644 index 000000000..bd4a60403 --- /dev/null +++ b/bundled-libs/symfony/process/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +/** + * Marker Interface for the Process Component. + * + * @author Johannes M. Schmitt + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/bundled-libs/symfony/process/Exception/InvalidArgumentException.php b/bundled-libs/symfony/process/Exception/InvalidArgumentException.php new file mode 100644 index 000000000..926ee2118 --- /dev/null +++ b/bundled-libs/symfony/process/Exception/InvalidArgumentException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +/** + * InvalidArgumentException for the Process Component. + * + * @author Romain Neutron + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/bundled-libs/symfony/process/Exception/LogicException.php b/bundled-libs/symfony/process/Exception/LogicException.php new file mode 100644 index 000000000..be3d490dd --- /dev/null +++ b/bundled-libs/symfony/process/Exception/LogicException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +/** + * LogicException for the Process Component. + * + * @author Romain Neutron + */ +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/bundled-libs/symfony/process/Exception/ProcessFailedException.php b/bundled-libs/symfony/process/Exception/ProcessFailedException.php new file mode 100644 index 000000000..328acfde5 --- /dev/null +++ b/bundled-libs/symfony/process/Exception/ProcessFailedException.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +use Symfony\Component\Process\Process; + +/** + * Exception for failed processes. + * + * @author Johannes M. Schmitt + */ +class ProcessFailedException extends RuntimeException +{ + private $process; + + public function __construct(Process $process) + { + if ($process->isSuccessful()) { + throw new InvalidArgumentException('Expected a failed process, but the given process was successful.'); + } + + $error = sprintf('The command "%s" failed.'."\n\nExit Code: %s(%s)\n\nWorking directory: %s", + $process->getCommandLine(), + $process->getExitCode(), + $process->getExitCodeText(), + $process->getWorkingDirectory() + ); + + if (!$process->isOutputDisabled()) { + $error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s", + $process->getOutput(), + $process->getErrorOutput() + ); + } + + parent::__construct($error); + + $this->process = $process; + } + + public function getProcess() + { + return $this->process; + } +} diff --git a/bundled-libs/symfony/process/Exception/ProcessSignaledException.php b/bundled-libs/symfony/process/Exception/ProcessSignaledException.php new file mode 100644 index 000000000..d4d322756 --- /dev/null +++ b/bundled-libs/symfony/process/Exception/ProcessSignaledException.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +use Symfony\Component\Process\Process; + +/** + * Exception that is thrown when a process has been signaled. + * + * @author Sullivan Senechal + */ +final class ProcessSignaledException extends RuntimeException +{ + private $process; + + public function __construct(Process $process) + { + $this->process = $process; + + parent::__construct(sprintf('The process has been signaled with signal "%s".', $process->getTermSignal())); + } + + public function getProcess(): Process + { + return $this->process; + } + + public function getSignal(): int + { + return $this->getProcess()->getTermSignal(); + } +} diff --git a/bundled-libs/symfony/process/Exception/ProcessTimedOutException.php b/bundled-libs/symfony/process/Exception/ProcessTimedOutException.php new file mode 100644 index 000000000..94391a459 --- /dev/null +++ b/bundled-libs/symfony/process/Exception/ProcessTimedOutException.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +use Symfony\Component\Process\Process; + +/** + * Exception that is thrown when a process times out. + * + * @author Johannes M. Schmitt + */ +class ProcessTimedOutException extends RuntimeException +{ + public const TYPE_GENERAL = 1; + public const TYPE_IDLE = 2; + + private $process; + private $timeoutType; + + public function __construct(Process $process, int $timeoutType) + { + $this->process = $process; + $this->timeoutType = $timeoutType; + + parent::__construct(sprintf( + 'The process "%s" exceeded the timeout of %s seconds.', + $process->getCommandLine(), + $this->getExceededTimeout() + )); + } + + public function getProcess() + { + return $this->process; + } + + public function isGeneralTimeout() + { + return self::TYPE_GENERAL === $this->timeoutType; + } + + public function isIdleTimeout() + { + return self::TYPE_IDLE === $this->timeoutType; + } + + public function getExceededTimeout() + { + switch ($this->timeoutType) { + case self::TYPE_GENERAL: + return $this->process->getTimeout(); + + case self::TYPE_IDLE: + return $this->process->getIdleTimeout(); + + default: + throw new \LogicException(sprintf('Unknown timeout type "%d".', $this->timeoutType)); + } + } +} diff --git a/bundled-libs/symfony/process/Exception/RuntimeException.php b/bundled-libs/symfony/process/Exception/RuntimeException.php new file mode 100644 index 000000000..adead2536 --- /dev/null +++ b/bundled-libs/symfony/process/Exception/RuntimeException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +/** + * RuntimeException for the Process Component. + * + * @author Johannes M. Schmitt + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/bundled-libs/symfony/process/ExecutableFinder.php b/bundled-libs/symfony/process/ExecutableFinder.php new file mode 100644 index 000000000..f392c962e --- /dev/null +++ b/bundled-libs/symfony/process/ExecutableFinder.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +/** + * Generic executable finder. + * + * @author Fabien Potencier + * @author Johannes M. Schmitt + */ +class ExecutableFinder +{ + private $suffixes = ['.exe', '.bat', '.cmd', '.com']; + + /** + * Replaces default suffixes of executable. + */ + public function setSuffixes(array $suffixes) + { + $this->suffixes = $suffixes; + } + + /** + * Adds new possible suffix to check for executable. + */ + public function addSuffix(string $suffix) + { + $this->suffixes[] = $suffix; + } + + /** + * Finds an executable by name. + * + * @param string $name The executable name (without the extension) + * @param string|null $default The default to return if no executable is found + * @param array $extraDirs Additional dirs to check into + * + * @return string|null + */ + public function find(string $name, ?string $default = null, array $extraDirs = []) + { + if (\ini_get('open_basedir')) { + $searchPath = array_merge(explode(\PATH_SEPARATOR, \ini_get('open_basedir')), $extraDirs); + $dirs = []; + foreach ($searchPath as $path) { + // Silencing against https://bugs.php.net/69240 + if (@is_dir($path)) { + $dirs[] = $path; + } else { + if (basename($path) == $name && @is_executable($path)) { + return $path; + } + } + } + } else { + $dirs = array_merge( + explode(\PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')), + $extraDirs + ); + } + + $suffixes = ['']; + if ('\\' === \DIRECTORY_SEPARATOR) { + $pathExt = getenv('PATHEXT'); + $suffixes = array_merge($pathExt ? explode(\PATH_SEPARATOR, $pathExt) : $this->suffixes, $suffixes); + } + foreach ($suffixes as $suffix) { + foreach ($dirs as $dir) { + if (@is_file($file = $dir.\DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === \DIRECTORY_SEPARATOR || @is_executable($file))) { + return $file; + } + } + } + + return $default; + } +} diff --git a/bundled-libs/symfony/process/InputStream.php b/bundled-libs/symfony/process/InputStream.php new file mode 100644 index 000000000..0c45b5245 --- /dev/null +++ b/bundled-libs/symfony/process/InputStream.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +use Symfony\Component\Process\Exception\RuntimeException; + +/** + * Provides a way to continuously write to the input of a Process until the InputStream is closed. + * + * @author Nicolas Grekas + * + * @implements \IteratorAggregate + */ +class InputStream implements \IteratorAggregate +{ + /** @var callable|null */ + private $onEmpty = null; + private $input = []; + private $open = true; + + /** + * Sets a callback that is called when the write buffer becomes empty. + */ + public function onEmpty(?callable $onEmpty = null) + { + $this->onEmpty = $onEmpty; + } + + /** + * Appends an input to the write buffer. + * + * @param resource|string|int|float|bool|\Traversable|null $input The input to append as scalar, + * stream resource or \Traversable + */ + public function write($input) + { + if (null === $input) { + return; + } + if ($this->isClosed()) { + throw new RuntimeException(sprintf('"%s" is closed.', static::class)); + } + $this->input[] = ProcessUtils::validateInput(__METHOD__, $input); + } + + /** + * Closes the write buffer. + */ + public function close() + { + $this->open = false; + } + + /** + * Tells whether the write buffer is closed or not. + */ + public function isClosed() + { + return !$this->open; + } + + /** + * @return \Traversable + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + $this->open = true; + + while ($this->open || $this->input) { + if (!$this->input) { + yield ''; + continue; + } + $current = array_shift($this->input); + + if ($current instanceof \Iterator) { + yield from $current; + } else { + yield $current; + } + if (!$this->input && $this->open && null !== $onEmpty = $this->onEmpty) { + $this->write($onEmpty($this)); + } + } + } +} diff --git a/bundled-libs/symfony/process/LICENSE b/bundled-libs/symfony/process/LICENSE new file mode 100644 index 000000000..0138f8f07 --- /dev/null +++ b/bundled-libs/symfony/process/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +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: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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. diff --git a/bundled-libs/symfony/process/PhpExecutableFinder.php b/bundled-libs/symfony/process/PhpExecutableFinder.php new file mode 100644 index 000000000..45dbcca43 --- /dev/null +++ b/bundled-libs/symfony/process/PhpExecutableFinder.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +/** + * An executable finder specifically designed for the PHP executable. + * + * @author Fabien Potencier + * @author Johannes M. Schmitt + */ +class PhpExecutableFinder +{ + private $executableFinder; + + public function __construct() + { + $this->executableFinder = new ExecutableFinder(); + } + + /** + * Finds The PHP executable. + * + * @return string|false + */ + public function find(bool $includeArgs = true) + { + if ($php = getenv('PHP_BINARY')) { + if (!is_executable($php)) { + $command = '\\' === \DIRECTORY_SEPARATOR ? 'where' : 'command -v --'; + if ($php = strtok(exec($command.' '.escapeshellarg($php)), \PHP_EOL)) { + if (!is_executable($php)) { + return false; + } + } else { + return false; + } + } + + if (@is_dir($php)) { + return false; + } + + return $php; + } + + $args = $this->findArguments(); + $args = $includeArgs && $args ? ' '.implode(' ', $args) : ''; + + // PHP_BINARY return the current sapi executable + if (\PHP_BINARY && \in_array(\PHP_SAPI, ['cli', 'cli-server', 'phpdbg'], true)) { + return \PHP_BINARY.$args; + } + + if ($php = getenv('PHP_PATH')) { + if (!@is_executable($php) || @is_dir($php)) { + return false; + } + + return $php; + } + + if ($php = getenv('PHP_PEAR_PHP_BIN')) { + if (@is_executable($php) && !@is_dir($php)) { + return $php; + } + } + + if (@is_executable($php = \PHP_BINDIR.('\\' === \DIRECTORY_SEPARATOR ? '\\php.exe' : '/php')) && !@is_dir($php)) { + return $php; + } + + $dirs = [\PHP_BINDIR]; + if ('\\' === \DIRECTORY_SEPARATOR) { + $dirs[] = 'C:\xampp\php\\'; + } + + return $this->executableFinder->find('php', false, $dirs); + } + + /** + * Finds the PHP executable arguments. + * + * @return array + */ + public function findArguments() + { + $arguments = []; + if ('phpdbg' === \PHP_SAPI) { + $arguments[] = '-qrr'; + } + + return $arguments; + } +} diff --git a/bundled-libs/symfony/process/PhpProcess.php b/bundled-libs/symfony/process/PhpProcess.php new file mode 100644 index 000000000..3a1d147c8 --- /dev/null +++ b/bundled-libs/symfony/process/PhpProcess.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +use Symfony\Component\Process\Exception\LogicException; +use Symfony\Component\Process\Exception\RuntimeException; + +/** + * PhpProcess runs a PHP script in an independent process. + * + * $p = new PhpProcess(''); + * $p->run(); + * print $p->getOutput()."\n"; + * + * @author Fabien Potencier + */ +class PhpProcess extends Process +{ + /** + * @param string $script The PHP script to run (as a string) + * @param string|null $cwd The working directory or null to use the working dir of the current PHP process + * @param array|null $env The environment variables or null to use the same environment as the current PHP process + * @param int $timeout The timeout in seconds + * @param array|null $php Path to the PHP binary to use with any additional arguments + */ + public function __construct(string $script, ?string $cwd = null, ?array $env = null, int $timeout = 60, ?array $php = null) + { + if (null === $php) { + $executableFinder = new PhpExecutableFinder(); + $php = $executableFinder->find(false); + $php = false === $php ? null : array_merge([$php], $executableFinder->findArguments()); + } + if ('phpdbg' === \PHP_SAPI) { + $file = tempnam(sys_get_temp_dir(), 'dbg'); + file_put_contents($file, $script); + register_shutdown_function('unlink', $file); + $php[] = $file; + $script = null; + } + + parent::__construct($php, $cwd, $env, $script, $timeout); + } + + /** + * {@inheritdoc} + */ + public static function fromShellCommandline(string $command, ?string $cwd = null, ?array $env = null, $input = null, ?float $timeout = 60) + { + throw new LogicException(sprintf('The "%s()" method cannot be called when using "%s".', __METHOD__, self::class)); + } + + /** + * {@inheritdoc} + */ + public function start(?callable $callback = null, array $env = []) + { + if (null === $this->getCommandLine()) { + throw new RuntimeException('Unable to find the PHP executable.'); + } + + parent::start($callback, $env); + } +} diff --git a/bundled-libs/symfony/process/Pipes/AbstractPipes.php b/bundled-libs/symfony/process/Pipes/AbstractPipes.php new file mode 100644 index 000000000..656dc0328 --- /dev/null +++ b/bundled-libs/symfony/process/Pipes/AbstractPipes.php @@ -0,0 +1,180 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Pipes; + +use Symfony\Component\Process\Exception\InvalidArgumentException; + +/** + * @author Romain Neutron + * + * @internal + */ +abstract class AbstractPipes implements PipesInterface +{ + public $pipes = []; + + private $inputBuffer = ''; + private $input; + private $blocked = true; + private $lastError; + + /** + * @param resource|string|int|float|bool|\Iterator|null $input + */ + public function __construct($input) + { + if (\is_resource($input) || $input instanceof \Iterator) { + $this->input = $input; + } elseif (\is_string($input)) { + $this->inputBuffer = $input; + } else { + $this->inputBuffer = (string) $input; + } + } + + /** + * {@inheritdoc} + */ + public function close() + { + foreach ($this->pipes as $pipe) { + if (\is_resource($pipe)) { + fclose($pipe); + } + } + $this->pipes = []; + } + + /** + * Returns true if a system call has been interrupted. + */ + protected function hasSystemCallBeenInterrupted(): bool + { + $lastError = $this->lastError; + $this->lastError = null; + + // stream_select returns false when the `select` system call is interrupted by an incoming signal + return null !== $lastError && false !== stripos($lastError, 'interrupted system call'); + } + + /** + * Unblocks streams. + */ + protected function unblock() + { + if (!$this->blocked) { + return; + } + + foreach ($this->pipes as $pipe) { + stream_set_blocking($pipe, 0); + } + if (\is_resource($this->input)) { + stream_set_blocking($this->input, 0); + } + + $this->blocked = false; + } + + /** + * Writes input to stdin. + * + * @throws InvalidArgumentException When an input iterator yields a non supported value + */ + protected function write(): ?array + { + if (!isset($this->pipes[0])) { + return null; + } + $input = $this->input; + + if ($input instanceof \Iterator) { + if (!$input->valid()) { + $input = null; + } elseif (\is_resource($input = $input->current())) { + stream_set_blocking($input, 0); + } elseif (!isset($this->inputBuffer[0])) { + if (!\is_string($input)) { + if (!\is_scalar($input)) { + throw new InvalidArgumentException(sprintf('"%s" yielded a value of type "%s", but only scalars and stream resources are supported.', get_debug_type($this->input), get_debug_type($input))); + } + $input = (string) $input; + } + $this->inputBuffer = $input; + $this->input->next(); + $input = null; + } else { + $input = null; + } + } + + $r = $e = []; + $w = [$this->pipes[0]]; + + // let's have a look if something changed in streams + if (false === @stream_select($r, $w, $e, 0, 0)) { + return null; + } + + foreach ($w as $stdin) { + if (isset($this->inputBuffer[0])) { + $written = fwrite($stdin, $this->inputBuffer); + $this->inputBuffer = substr($this->inputBuffer, $written); + if (isset($this->inputBuffer[0])) { + return [$this->pipes[0]]; + } + } + + if ($input) { + while (true) { + $data = fread($input, self::CHUNK_SIZE); + if (!isset($data[0])) { + break; + } + $written = fwrite($stdin, $data); + $data = substr($data, $written); + if (isset($data[0])) { + $this->inputBuffer = $data; + + return [$this->pipes[0]]; + } + } + if (feof($input)) { + if ($this->input instanceof \Iterator) { + $this->input->next(); + } else { + $this->input = null; + } + } + } + } + + // no input to read on resource, buffer is empty + if (!isset($this->inputBuffer[0]) && !($this->input instanceof \Iterator ? $this->input->valid() : $this->input)) { + $this->input = null; + fclose($this->pipes[0]); + unset($this->pipes[0]); + } elseif (!$w) { + return [$this->pipes[0]]; + } + + return null; + } + + /** + * @internal + */ + public function handleError(int $type, string $msg) + { + $this->lastError = $msg; + } +} diff --git a/bundled-libs/symfony/process/Pipes/PipesInterface.php b/bundled-libs/symfony/process/Pipes/PipesInterface.php new file mode 100644 index 000000000..50eb5c47e --- /dev/null +++ b/bundled-libs/symfony/process/Pipes/PipesInterface.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Pipes; + +/** + * PipesInterface manages descriptors and pipes for the use of proc_open. + * + * @author Romain Neutron + * + * @internal + */ +interface PipesInterface +{ + public const CHUNK_SIZE = 16384; + + /** + * Returns an array of descriptors for the use of proc_open. + */ + public function getDescriptors(): array; + + /** + * Returns an array of filenames indexed by their related stream in case these pipes use temporary files. + * + * @return string[] + */ + public function getFiles(): array; + + /** + * Reads data in file handles and pipes. + * + * @param bool $blocking Whether to use blocking calls or not + * @param bool $close Whether to close pipes if they've reached EOF + * + * @return string[] An array of read data indexed by their fd + */ + public function readAndWrite(bool $blocking, bool $close = false): array; + + /** + * Returns if the current state has open file handles or pipes. + */ + public function areOpen(): bool; + + /** + * Returns if pipes are able to read output. + */ + public function haveReadSupport(): bool; + + /** + * Closes file handles and pipes. + */ + public function close(); +} diff --git a/bundled-libs/symfony/process/Pipes/UnixPipes.php b/bundled-libs/symfony/process/Pipes/UnixPipes.php new file mode 100644 index 000000000..5a0e9d47f --- /dev/null +++ b/bundled-libs/symfony/process/Pipes/UnixPipes.php @@ -0,0 +1,163 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Pipes; + +use Symfony\Component\Process\Process; + +/** + * UnixPipes implementation uses unix pipes as handles. + * + * @author Romain Neutron + * + * @internal + */ +class UnixPipes extends AbstractPipes +{ + private $ttyMode; + private $ptyMode; + private $haveReadSupport; + + public function __construct(?bool $ttyMode, bool $ptyMode, $input, bool $haveReadSupport) + { + $this->ttyMode = $ttyMode; + $this->ptyMode = $ptyMode; + $this->haveReadSupport = $haveReadSupport; + + parent::__construct($input); + } + + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + $this->close(); + } + + /** + * {@inheritdoc} + */ + public function getDescriptors(): array + { + if (!$this->haveReadSupport) { + $nullstream = fopen('/dev/null', 'c'); + + return [ + ['pipe', 'r'], + $nullstream, + $nullstream, + ]; + } + + if ($this->ttyMode) { + return [ + ['file', '/dev/tty', 'r'], + ['file', '/dev/tty', 'w'], + ['file', '/dev/tty', 'w'], + ]; + } + + if ($this->ptyMode && Process::isPtySupported()) { + return [ + ['pty'], + ['pty'], + ['pty'], + ]; + } + + return [ + ['pipe', 'r'], + ['pipe', 'w'], // stdout + ['pipe', 'w'], // stderr + ]; + } + + /** + * {@inheritdoc} + */ + public function getFiles(): array + { + return []; + } + + /** + * {@inheritdoc} + */ + public function readAndWrite(bool $blocking, bool $close = false): array + { + $this->unblock(); + $w = $this->write(); + + $read = $e = []; + $r = $this->pipes; + unset($r[0]); + + // let's have a look if something changed in streams + set_error_handler([$this, 'handleError']); + if (($r || $w) && false === stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) { + restore_error_handler(); + // if a system call has been interrupted, forget about it, let's try again + // otherwise, an error occurred, let's reset pipes + if (!$this->hasSystemCallBeenInterrupted()) { + $this->pipes = []; + } + + return $read; + } + restore_error_handler(); + + foreach ($r as $pipe) { + // prior PHP 5.4 the array passed to stream_select is modified and + // lose key association, we have to find back the key + $read[$type = array_search($pipe, $this->pipes, true)] = ''; + + do { + $data = @fread($pipe, self::CHUNK_SIZE); + $read[$type] .= $data; + } while (isset($data[0]) && ($close || isset($data[self::CHUNK_SIZE - 1]))); + + if (!isset($read[$type][0])) { + unset($read[$type]); + } + + if ($close && feof($pipe)) { + fclose($pipe); + unset($this->pipes[$type]); + } + } + + return $read; + } + + /** + * {@inheritdoc} + */ + public function haveReadSupport(): bool + { + return $this->haveReadSupport; + } + + /** + * {@inheritdoc} + */ + public function areOpen(): bool + { + return (bool) $this->pipes; + } +} diff --git a/bundled-libs/symfony/process/Pipes/WindowsPipes.php b/bundled-libs/symfony/process/Pipes/WindowsPipes.php new file mode 100644 index 000000000..968dd0262 --- /dev/null +++ b/bundled-libs/symfony/process/Pipes/WindowsPipes.php @@ -0,0 +1,204 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Pipes; + +use Symfony\Component\Process\Exception\RuntimeException; +use Symfony\Component\Process\Process; + +/** + * WindowsPipes implementation uses temporary files as handles. + * + * @see https://bugs.php.net/51800 + * @see https://bugs.php.net/65650 + * + * @author Romain Neutron + * + * @internal + */ +class WindowsPipes extends AbstractPipes +{ + private $files = []; + private $fileHandles = []; + private $lockHandles = []; + private $readBytes = [ + Process::STDOUT => 0, + Process::STDERR => 0, + ]; + private $haveReadSupport; + + public function __construct($input, bool $haveReadSupport) + { + $this->haveReadSupport = $haveReadSupport; + + if ($this->haveReadSupport) { + // Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big. + // Workaround for this problem is to use temporary files instead of pipes on Windows platform. + // + // @see https://bugs.php.net/51800 + $pipes = [ + Process::STDOUT => Process::OUT, + Process::STDERR => Process::ERR, + ]; + $tmpDir = sys_get_temp_dir(); + $lastError = 'unknown reason'; + set_error_handler(function ($type, $msg) use (&$lastError) { $lastError = $msg; }); + for ($i = 0;; ++$i) { + foreach ($pipes as $pipe => $name) { + $file = sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name); + + if (!$h = fopen($file.'.lock', 'w')) { + if (file_exists($file.'.lock')) { + continue 2; + } + restore_error_handler(); + throw new RuntimeException('A temporary file could not be opened to write the process output: '.$lastError); + } + if (!flock($h, \LOCK_EX | \LOCK_NB)) { + continue 2; + } + if (isset($this->lockHandles[$pipe])) { + flock($this->lockHandles[$pipe], \LOCK_UN); + fclose($this->lockHandles[$pipe]); + } + $this->lockHandles[$pipe] = $h; + + if (!($h = fopen($file, 'w')) || !fclose($h) || !$h = fopen($file, 'r')) { + flock($this->lockHandles[$pipe], \LOCK_UN); + fclose($this->lockHandles[$pipe]); + unset($this->lockHandles[$pipe]); + continue 2; + } + $this->fileHandles[$pipe] = $h; + $this->files[$pipe] = $file; + } + break; + } + restore_error_handler(); + } + + parent::__construct($input); + } + + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + $this->close(); + } + + /** + * {@inheritdoc} + */ + public function getDescriptors(): array + { + if (!$this->haveReadSupport) { + $nullstream = fopen('NUL', 'c'); + + return [ + ['pipe', 'r'], + $nullstream, + $nullstream, + ]; + } + + // We're not using pipe on Windows platform as it hangs (https://bugs.php.net/51800) + // We're not using file handles as it can produce corrupted output https://bugs.php.net/65650 + // So we redirect output within the commandline and pass the nul device to the process + return [ + ['pipe', 'r'], + ['file', 'NUL', 'w'], + ['file', 'NUL', 'w'], + ]; + } + + /** + * {@inheritdoc} + */ + public function getFiles(): array + { + return $this->files; + } + + /** + * {@inheritdoc} + */ + public function readAndWrite(bool $blocking, bool $close = false): array + { + $this->unblock(); + $w = $this->write(); + $read = $r = $e = []; + + if ($blocking) { + if ($w) { + @stream_select($r, $w, $e, 0, Process::TIMEOUT_PRECISION * 1E6); + } elseif ($this->fileHandles) { + usleep((int) (Process::TIMEOUT_PRECISION * 1E6)); + } + } + foreach ($this->fileHandles as $type => $fileHandle) { + $data = stream_get_contents($fileHandle, -1, $this->readBytes[$type]); + + if (isset($data[0])) { + $this->readBytes[$type] += \strlen($data); + $read[$type] = $data; + } + if ($close) { + ftruncate($fileHandle, 0); + fclose($fileHandle); + flock($this->lockHandles[$type], \LOCK_UN); + fclose($this->lockHandles[$type]); + unset($this->fileHandles[$type], $this->lockHandles[$type]); + } + } + + return $read; + } + + /** + * {@inheritdoc} + */ + public function haveReadSupport(): bool + { + return $this->haveReadSupport; + } + + /** + * {@inheritdoc} + */ + public function areOpen(): bool + { + return $this->pipes && $this->fileHandles; + } + + /** + * {@inheritdoc} + */ + public function close() + { + parent::close(); + foreach ($this->fileHandles as $type => $handle) { + ftruncate($handle, 0); + fclose($handle); + flock($this->lockHandles[$type], \LOCK_UN); + fclose($this->lockHandles[$type]); + } + $this->fileHandles = $this->lockHandles = []; + } +} diff --git a/bundled-libs/symfony/process/Process.php b/bundled-libs/symfony/process/Process.php new file mode 100644 index 000000000..a4b0a784c --- /dev/null +++ b/bundled-libs/symfony/process/Process.php @@ -0,0 +1,1666 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +use Symfony\Component\Process\Exception\InvalidArgumentException; +use Symfony\Component\Process\Exception\LogicException; +use Symfony\Component\Process\Exception\ProcessFailedException; +use Symfony\Component\Process\Exception\ProcessSignaledException; +use Symfony\Component\Process\Exception\ProcessTimedOutException; +use Symfony\Component\Process\Exception\RuntimeException; +use Symfony\Component\Process\Pipes\PipesInterface; +use Symfony\Component\Process\Pipes\UnixPipes; +use Symfony\Component\Process\Pipes\WindowsPipes; + +/** + * Process is a thin wrapper around proc_* functions to easily + * start independent PHP processes. + * + * @author Fabien Potencier + * @author Romain Neutron + * + * @implements \IteratorAggregate + */ +class Process implements \IteratorAggregate +{ + public const ERR = 'err'; + public const OUT = 'out'; + + public const STATUS_READY = 'ready'; + public const STATUS_STARTED = 'started'; + public const STATUS_TERMINATED = 'terminated'; + + public const STDIN = 0; + public const STDOUT = 1; + public const STDERR = 2; + + // Timeout Precision in seconds. + public const TIMEOUT_PRECISION = 0.2; + + public const ITER_NON_BLOCKING = 1; // By default, iterating over outputs is a blocking call, use this flag to make it non-blocking + public const ITER_KEEP_OUTPUT = 2; // By default, outputs are cleared while iterating, use this flag to keep them in memory + public const ITER_SKIP_OUT = 4; // Use this flag to skip STDOUT while iterating + public const ITER_SKIP_ERR = 8; // Use this flag to skip STDERR while iterating + + private $callback; + private $hasCallback = false; + private $commandline; + private $cwd; + private $env = []; + private $input; + private $starttime; + private $lastOutputTime; + private $timeout; + private $idleTimeout; + private $exitcode; + private $fallbackStatus = []; + private $processInformation; + private $outputDisabled = false; + private $stdout; + private $stderr; + private $process; + private $status = self::STATUS_READY; + private $incrementalOutputOffset = 0; + private $incrementalErrorOutputOffset = 0; + private $tty = false; + private $pty; + private $options = ['suppress_errors' => true, 'bypass_shell' => true]; + + private $useFileHandles = false; + /** @var PipesInterface */ + private $processPipes; + + private $latestSignal; + private $cachedExitCode; + + private static $sigchild; + + /** + * Exit codes translation table. + * + * User-defined errors must use exit codes in the 64-113 range. + */ + public static $exitCodes = [ + 0 => 'OK', + 1 => 'General error', + 2 => 'Misuse of shell builtins', + + 126 => 'Invoked command cannot execute', + 127 => 'Command not found', + 128 => 'Invalid exit argument', + + // signals + 129 => 'Hangup', + 130 => 'Interrupt', + 131 => 'Quit and dump core', + 132 => 'Illegal instruction', + 133 => 'Trace/breakpoint trap', + 134 => 'Process aborted', + 135 => 'Bus error: "access to undefined portion of memory object"', + 136 => 'Floating point exception: "erroneous arithmetic operation"', + 137 => 'Kill (terminate immediately)', + 138 => 'User-defined 1', + 139 => 'Segmentation violation', + 140 => 'User-defined 2', + 141 => 'Write to pipe with no one reading', + 142 => 'Signal raised by alarm', + 143 => 'Termination (request to terminate)', + // 144 - not defined + 145 => 'Child process terminated, stopped (or continued*)', + 146 => 'Continue if stopped', + 147 => 'Stop executing temporarily', + 148 => 'Terminal stop signal', + 149 => 'Background process attempting to read from tty ("in")', + 150 => 'Background process attempting to write to tty ("out")', + 151 => 'Urgent data available on socket', + 152 => 'CPU time limit exceeded', + 153 => 'File size limit exceeded', + 154 => 'Signal raised by timer counting virtual time: "virtual timer expired"', + 155 => 'Profiling timer expired', + // 156 - not defined + 157 => 'Pollable event', + // 158 - not defined + 159 => 'Bad syscall', + ]; + + /** + * @param array $command The command to run and its arguments listed as separate entries + * @param string|null $cwd The working directory or null to use the working dir of the current PHP process + * @param array|null $env The environment variables or null to use the same environment as the current PHP process + * @param mixed $input The input as stream resource, scalar or \Traversable, or null for no input + * @param int|float|null $timeout The timeout in seconds or null to disable + * + * @throws LogicException When proc_open is not installed + */ + public function __construct(array $command, ?string $cwd = null, ?array $env = null, $input = null, ?float $timeout = 60) + { + if (!\function_exists('proc_open')) { + throw new LogicException('The Process class relies on proc_open, which is not available on your PHP installation.'); + } + + $this->commandline = $command; + $this->cwd = $cwd; + + // on Windows, if the cwd changed via chdir(), proc_open defaults to the dir where PHP was started + // on Gnu/Linux, PHP builds with --enable-maintainer-zts are also affected + // @see : https://bugs.php.net/51800 + // @see : https://bugs.php.net/50524 + if (null === $this->cwd && (\defined('ZEND_THREAD_SAFE') || '\\' === \DIRECTORY_SEPARATOR)) { + $this->cwd = getcwd(); + } + if (null !== $env) { + $this->setEnv($env); + } + + $this->setInput($input); + $this->setTimeout($timeout); + $this->useFileHandles = '\\' === \DIRECTORY_SEPARATOR; + $this->pty = false; + } + + /** + * Creates a Process instance as a command-line to be run in a shell wrapper. + * + * Command-lines are parsed by the shell of your OS (/bin/sh on Unix-like, cmd.exe on Windows.) + * This allows using e.g. pipes or conditional execution. In this mode, signals are sent to the + * shell wrapper and not to your commands. + * + * In order to inject dynamic values into command-lines, we strongly recommend using placeholders. + * This will save escaping values, which is not portable nor secure anyway: + * + * $process = Process::fromShellCommandline('my_command "${:MY_VAR}"'); + * $process->run(null, ['MY_VAR' => $theValue]); + * + * @param string $command The command line to pass to the shell of the OS + * @param string|null $cwd The working directory or null to use the working dir of the current PHP process + * @param array|null $env The environment variables or null to use the same environment as the current PHP process + * @param mixed $input The input as stream resource, scalar or \Traversable, or null for no input + * @param int|float|null $timeout The timeout in seconds or null to disable + * + * @return static + * + * @throws LogicException When proc_open is not installed + */ + public static function fromShellCommandline(string $command, ?string $cwd = null, ?array $env = null, $input = null, ?float $timeout = 60) + { + $process = new static([], $cwd, $env, $input, $timeout); + $process->commandline = $command; + + return $process; + } + + /** + * @return array + */ + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + if ($this->options['create_new_console'] ?? false) { + $this->processPipes->close(); + } else { + $this->stop(0); + } + } + + public function __clone() + { + $this->resetProcessData(); + } + + /** + * Runs the process. + * + * The callback receives the type of output (out or err) and + * some bytes from the output in real-time. It allows to have feedback + * from the independent process during execution. + * + * The STDOUT and STDERR are also available after the process is finished + * via the getOutput() and getErrorOutput() methods. + * + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * + * @return int The exit status code + * + * @throws RuntimeException When process can't be launched + * @throws RuntimeException When process is already running + * @throws ProcessTimedOutException When process timed out + * @throws ProcessSignaledException When process stopped after receiving signal + * @throws LogicException In case a callback is provided and output has been disabled + * + * @final + */ + public function run(?callable $callback = null, array $env = []): int + { + $this->start($callback, $env); + + return $this->wait(); + } + + /** + * Runs the process. + * + * This is identical to run() except that an exception is thrown if the process + * exits with a non-zero exit code. + * + * @return $this + * + * @throws ProcessFailedException if the process didn't terminate successfully + * + * @final + */ + public function mustRun(?callable $callback = null, array $env = []): self + { + if (0 !== $this->run($callback, $env)) { + throw new ProcessFailedException($this); + } + + return $this; + } + + /** + * Starts the process and returns after writing the input to STDIN. + * + * This method blocks until all STDIN data is sent to the process then it + * returns while the process runs in the background. + * + * The termination of the process can be awaited with wait(). + * + * The callback receives the type of output (out or err) and some bytes from + * the output in real-time while writing the standard input to the process. + * It allows to have feedback from the independent process during execution. + * + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * + * @throws RuntimeException When process can't be launched + * @throws RuntimeException When process is already running + * @throws LogicException In case a callback is provided and output has been disabled + */ + public function start(?callable $callback = null, array $env = []) + { + if ($this->isRunning()) { + throw new RuntimeException('Process is already running.'); + } + + $this->resetProcessData(); + $this->starttime = $this->lastOutputTime = microtime(true); + $this->callback = $this->buildCallback($callback); + $this->hasCallback = null !== $callback; + $descriptors = $this->getDescriptors(); + + if ($this->env) { + $env += '\\' === \DIRECTORY_SEPARATOR ? array_diff_ukey($this->env, $env, 'strcasecmp') : $this->env; + } + + $env += '\\' === \DIRECTORY_SEPARATOR ? array_diff_ukey($this->getDefaultEnv(), $env, 'strcasecmp') : $this->getDefaultEnv(); + + if (\is_array($commandline = $this->commandline)) { + $commandline = implode(' ', array_map([$this, 'escapeArgument'], $commandline)); + + if ('\\' !== \DIRECTORY_SEPARATOR) { + // exec is mandatory to deal with sending a signal to the process + $commandline = 'exec '.$commandline; + } + } else { + $commandline = $this->replacePlaceholders($commandline, $env); + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + $commandline = $this->prepareWindowsCommandLine($commandline, $env); + } elseif (!$this->useFileHandles && $this->isSigchildEnabled()) { + // last exit code is output on the fourth pipe and caught to work around --enable-sigchild + $descriptors[3] = ['pipe', 'w']; + + // See https://unix.stackexchange.com/questions/71205/background-process-pipe-input + $commandline = '{ ('.$commandline.') <&3 3<&- 3>/dev/null & } 3<&0;'; + $commandline .= 'pid=$!; echo $pid >&3; wait $pid 2>/dev/null; code=$?; echo $code >&3; exit $code'; + + // Workaround for the bug, when PTS functionality is enabled. + // @see : https://bugs.php.net/69442 + $ptsWorkaround = fopen(__FILE__, 'r'); + } + + $envPairs = []; + foreach ($env as $k => $v) { + if (false !== $v && false === \in_array($k, ['argc', 'argv', 'ARGC', 'ARGV'], true)) { + $envPairs[] = $k.'='.$v; + } + } + + if (!is_dir($this->cwd)) { + throw new RuntimeException(sprintf('The provided cwd "%s" does not exist.', $this->cwd)); + } + + $this->process = @proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $this->options); + + if (!\is_resource($this->process)) { + throw new RuntimeException('Unable to launch a new process.'); + } + $this->status = self::STATUS_STARTED; + + if (isset($descriptors[3])) { + $this->fallbackStatus['pid'] = (int) fgets($this->processPipes->pipes[3]); + } + + if ($this->tty) { + return; + } + + $this->updateStatus(false); + $this->checkTimeout(); + } + + /** + * Restarts the process. + * + * Be warned that the process is cloned before being started. + * + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * + * @return static + * + * @throws RuntimeException When process can't be launched + * @throws RuntimeException When process is already running + * + * @see start() + * + * @final + */ + public function restart(?callable $callback = null, array $env = []): self + { + if ($this->isRunning()) { + throw new RuntimeException('Process is already running.'); + } + + $process = clone $this; + $process->start($callback, $env); + + return $process; + } + + /** + * Waits for the process to terminate. + * + * The callback receives the type of output (out or err) and some bytes + * from the output in real-time while writing the standard input to the process. + * It allows to have feedback from the independent process during execution. + * + * @param callable|null $callback A valid PHP callback + * + * @return int The exitcode of the process + * + * @throws ProcessTimedOutException When process timed out + * @throws ProcessSignaledException When process stopped after receiving signal + * @throws LogicException When process is not yet started + */ + public function wait(?callable $callback = null) + { + $this->requireProcessIsStarted(__FUNCTION__); + + $this->updateStatus(false); + + if (null !== $callback) { + if (!$this->processPipes->haveReadSupport()) { + $this->stop(0); + throw new LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::wait".'); + } + $this->callback = $this->buildCallback($callback); + } + + do { + $this->checkTimeout(); + $running = $this->isRunning() && ('\\' === \DIRECTORY_SEPARATOR || $this->processPipes->areOpen()); + $this->readPipes($running, '\\' !== \DIRECTORY_SEPARATOR || !$running); + } while ($running); + + while ($this->isRunning()) { + $this->checkTimeout(); + usleep(1000); + } + + if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) { + throw new ProcessSignaledException($this); + } + + return $this->exitcode; + } + + /** + * Waits until the callback returns true. + * + * The callback receives the type of output (out or err) and some bytes + * from the output in real-time while writing the standard input to the process. + * It allows to have feedback from the independent process during execution. + * + * @throws RuntimeException When process timed out + * @throws LogicException When process is not yet started + * @throws ProcessTimedOutException In case the timeout was reached + */ + public function waitUntil(callable $callback): bool + { + $this->requireProcessIsStarted(__FUNCTION__); + $this->updateStatus(false); + + if (!$this->processPipes->haveReadSupport()) { + $this->stop(0); + throw new LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::waitUntil".'); + } + $callback = $this->buildCallback($callback); + + $ready = false; + while (true) { + $this->checkTimeout(); + $running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen(); + $output = $this->processPipes->readAndWrite($running, '\\' !== \DIRECTORY_SEPARATOR || !$running); + + foreach ($output as $type => $data) { + if (3 !== $type) { + $ready = $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data) || $ready; + } elseif (!isset($this->fallbackStatus['signaled'])) { + $this->fallbackStatus['exitcode'] = (int) $data; + } + } + if ($ready) { + return true; + } + if (!$running) { + return false; + } + + usleep(1000); + } + } + + /** + * Returns the Pid (process identifier), if applicable. + * + * @return int|null The process id if running, null otherwise + */ + public function getPid() + { + return $this->isRunning() ? $this->processInformation['pid'] : null; + } + + /** + * Sends a POSIX signal to the process. + * + * @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants) + * + * @return $this + * + * @throws LogicException In case the process is not running + * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed + * @throws RuntimeException In case of failure + */ + public function signal(int $signal) + { + $this->doSignal($signal, true); + + return $this; + } + + /** + * Disables fetching output and error output from the underlying process. + * + * @return $this + * + * @throws RuntimeException In case the process is already running + * @throws LogicException if an idle timeout is set + */ + public function disableOutput() + { + if ($this->isRunning()) { + throw new RuntimeException('Disabling output while the process is running is not possible.'); + } + if (null !== $this->idleTimeout) { + throw new LogicException('Output cannot be disabled while an idle timeout is set.'); + } + + $this->outputDisabled = true; + + return $this; + } + + /** + * Enables fetching output and error output from the underlying process. + * + * @return $this + * + * @throws RuntimeException In case the process is already running + */ + public function enableOutput() + { + if ($this->isRunning()) { + throw new RuntimeException('Enabling output while the process is running is not possible.'); + } + + $this->outputDisabled = false; + + return $this; + } + + /** + * Returns true in case the output is disabled, false otherwise. + * + * @return bool + */ + public function isOutputDisabled() + { + return $this->outputDisabled; + } + + /** + * Returns the current output of the process (STDOUT). + * + * @return string + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + */ + public function getOutput() + { + $this->readPipesForOutput(__FUNCTION__); + + if (false === $ret = stream_get_contents($this->stdout, -1, 0)) { + return ''; + } + + return $ret; + } + + /** + * Returns the output incrementally. + * + * In comparison with the getOutput method which always return the whole + * output, this one returns the new output since the last call. + * + * @return string + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + */ + public function getIncrementalOutput() + { + $this->readPipesForOutput(__FUNCTION__); + + $latest = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset); + $this->incrementalOutputOffset = ftell($this->stdout); + + if (false === $latest) { + return ''; + } + + return $latest; + } + + /** + * Returns an iterator to the output of the process, with the output type as keys (Process::OUT/ERR). + * + * @param int $flags A bit field of Process::ITER_* flags + * + * @return \Generator + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + */ + #[\ReturnTypeWillChange] + public function getIterator(int $flags = 0) + { + $this->readPipesForOutput(__FUNCTION__, false); + + $clearOutput = !(self::ITER_KEEP_OUTPUT & $flags); + $blocking = !(self::ITER_NON_BLOCKING & $flags); + $yieldOut = !(self::ITER_SKIP_OUT & $flags); + $yieldErr = !(self::ITER_SKIP_ERR & $flags); + + while (null !== $this->callback || ($yieldOut && !feof($this->stdout)) || ($yieldErr && !feof($this->stderr))) { + if ($yieldOut) { + $out = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset); + + if (isset($out[0])) { + if ($clearOutput) { + $this->clearOutput(); + } else { + $this->incrementalOutputOffset = ftell($this->stdout); + } + + yield self::OUT => $out; + } + } + + if ($yieldErr) { + $err = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset); + + if (isset($err[0])) { + if ($clearOutput) { + $this->clearErrorOutput(); + } else { + $this->incrementalErrorOutputOffset = ftell($this->stderr); + } + + yield self::ERR => $err; + } + } + + if (!$blocking && !isset($out[0]) && !isset($err[0])) { + yield self::OUT => ''; + } + + $this->checkTimeout(); + $this->readPipesForOutput(__FUNCTION__, $blocking); + } + } + + /** + * Clears the process output. + * + * @return $this + */ + public function clearOutput() + { + ftruncate($this->stdout, 0); + fseek($this->stdout, 0); + $this->incrementalOutputOffset = 0; + + return $this; + } + + /** + * Returns the current error output of the process (STDERR). + * + * @return string + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + */ + public function getErrorOutput() + { + $this->readPipesForOutput(__FUNCTION__); + + if (false === $ret = stream_get_contents($this->stderr, -1, 0)) { + return ''; + } + + return $ret; + } + + /** + * Returns the errorOutput incrementally. + * + * In comparison with the getErrorOutput method which always return the + * whole error output, this one returns the new error output since the last + * call. + * + * @return string + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + */ + public function getIncrementalErrorOutput() + { + $this->readPipesForOutput(__FUNCTION__); + + $latest = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset); + $this->incrementalErrorOutputOffset = ftell($this->stderr); + + if (false === $latest) { + return ''; + } + + return $latest; + } + + /** + * Clears the process output. + * + * @return $this + */ + public function clearErrorOutput() + { + ftruncate($this->stderr, 0); + fseek($this->stderr, 0); + $this->incrementalErrorOutputOffset = 0; + + return $this; + } + + /** + * Returns the exit code returned by the process. + * + * @return int|null The exit status code, null if the Process is not terminated + */ + public function getExitCode() + { + $this->updateStatus(false); + + return $this->exitcode; + } + + /** + * Returns a string representation for the exit code returned by the process. + * + * This method relies on the Unix exit code status standardization + * and might not be relevant for other operating systems. + * + * @return string|null A string representation for the exit status code, null if the Process is not terminated + * + * @see http://tldp.org/LDP/abs/html/exitcodes.html + * @see http://en.wikipedia.org/wiki/Unix_signal + */ + public function getExitCodeText() + { + if (null === $exitcode = $this->getExitCode()) { + return null; + } + + return self::$exitCodes[$exitcode] ?? 'Unknown error'; + } + + /** + * Checks if the process ended successfully. + * + * @return bool + */ + public function isSuccessful() + { + return 0 === $this->getExitCode(); + } + + /** + * Returns true if the child process has been terminated by an uncaught signal. + * + * It always returns false on Windows. + * + * @return bool + * + * @throws LogicException In case the process is not terminated + */ + public function hasBeenSignaled() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + return $this->processInformation['signaled']; + } + + /** + * Returns the number of the signal that caused the child process to terminate its execution. + * + * It is only meaningful if hasBeenSignaled() returns true. + * + * @return int + * + * @throws RuntimeException In case --enable-sigchild is activated + * @throws LogicException In case the process is not terminated + */ + public function getTermSignal() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + if ($this->isSigchildEnabled() && -1 === $this->processInformation['termsig']) { + throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal cannot be retrieved.'); + } + + return $this->processInformation['termsig']; + } + + /** + * Returns true if the child process has been stopped by a signal. + * + * It always returns false on Windows. + * + * @return bool + * + * @throws LogicException In case the process is not terminated + */ + public function hasBeenStopped() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + return $this->processInformation['stopped']; + } + + /** + * Returns the number of the signal that caused the child process to stop its execution. + * + * It is only meaningful if hasBeenStopped() returns true. + * + * @return int + * + * @throws LogicException In case the process is not terminated + */ + public function getStopSignal() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + return $this->processInformation['stopsig']; + } + + /** + * Checks if the process is currently running. + * + * @return bool + */ + public function isRunning() + { + if (self::STATUS_STARTED !== $this->status) { + return false; + } + + $this->updateStatus(false); + + return $this->processInformation['running']; + } + + /** + * Checks if the process has been started with no regard to the current state. + * + * @return bool + */ + public function isStarted() + { + return self::STATUS_READY != $this->status; + } + + /** + * Checks if the process is terminated. + * + * @return bool + */ + public function isTerminated() + { + $this->updateStatus(false); + + return self::STATUS_TERMINATED == $this->status; + } + + /** + * Gets the process status. + * + * The status is one of: ready, started, terminated. + * + * @return string + */ + public function getStatus() + { + $this->updateStatus(false); + + return $this->status; + } + + /** + * Stops the process. + * + * @param int|float $timeout The timeout in seconds + * @param int|null $signal A POSIX signal to send in case the process has not stop at timeout, default is SIGKILL (9) + * + * @return int|null The exit-code of the process or null if it's not running + */ + public function stop(float $timeout = 10, ?int $signal = null) + { + $timeoutMicro = microtime(true) + $timeout; + if ($this->isRunning()) { + // given SIGTERM may not be defined and that "proc_terminate" uses the constant value and not the constant itself, we use the same here + $this->doSignal(15, false); + do { + usleep(1000); + } while ($this->isRunning() && microtime(true) < $timeoutMicro); + + if ($this->isRunning()) { + // Avoid exception here: process is supposed to be running, but it might have stopped just + // after this line. In any case, let's silently discard the error, we cannot do anything. + $this->doSignal($signal ?: 9, false); + } + } + + if ($this->isRunning()) { + if (isset($this->fallbackStatus['pid'])) { + unset($this->fallbackStatus['pid']); + + return $this->stop(0, $signal); + } + $this->close(); + } + + return $this->exitcode; + } + + /** + * Adds a line to the STDOUT stream. + * + * @internal + */ + public function addOutput(string $line) + { + $this->lastOutputTime = microtime(true); + + fseek($this->stdout, 0, \SEEK_END); + fwrite($this->stdout, $line); + fseek($this->stdout, $this->incrementalOutputOffset); + } + + /** + * Adds a line to the STDERR stream. + * + * @internal + */ + public function addErrorOutput(string $line) + { + $this->lastOutputTime = microtime(true); + + fseek($this->stderr, 0, \SEEK_END); + fwrite($this->stderr, $line); + fseek($this->stderr, $this->incrementalErrorOutputOffset); + } + + /** + * Gets the last output time in seconds. + */ + public function getLastOutputTime(): ?float + { + return $this->lastOutputTime; + } + + /** + * Gets the command line to be executed. + * + * @return string + */ + public function getCommandLine() + { + return \is_array($this->commandline) ? implode(' ', array_map([$this, 'escapeArgument'], $this->commandline)) : $this->commandline; + } + + /** + * Gets the process timeout in seconds (max. runtime). + * + * @return float|null + */ + public function getTimeout() + { + return $this->timeout; + } + + /** + * Gets the process idle timeout in seconds (max. time since last output). + * + * @return float|null + */ + public function getIdleTimeout() + { + return $this->idleTimeout; + } + + /** + * Sets the process timeout (max. runtime) in seconds. + * + * To disable the timeout, set this value to null. + * + * @return $this + * + * @throws InvalidArgumentException if the timeout is negative + */ + public function setTimeout(?float $timeout) + { + $this->timeout = $this->validateTimeout($timeout); + + return $this; + } + + /** + * Sets the process idle timeout (max. time since last output) in seconds. + * + * To disable the timeout, set this value to null. + * + * @return $this + * + * @throws LogicException if the output is disabled + * @throws InvalidArgumentException if the timeout is negative + */ + public function setIdleTimeout(?float $timeout) + { + if (null !== $timeout && $this->outputDisabled) { + throw new LogicException('Idle timeout cannot be set while the output is disabled.'); + } + + $this->idleTimeout = $this->validateTimeout($timeout); + + return $this; + } + + /** + * Enables or disables the TTY mode. + * + * @return $this + * + * @throws RuntimeException In case the TTY mode is not supported + */ + public function setTty(bool $tty) + { + if ('\\' === \DIRECTORY_SEPARATOR && $tty) { + throw new RuntimeException('TTY mode is not supported on Windows platform.'); + } + + if ($tty && !self::isTtySupported()) { + throw new RuntimeException('TTY mode requires /dev/tty to be read/writable.'); + } + + $this->tty = $tty; + + return $this; + } + + /** + * Checks if the TTY mode is enabled. + * + * @return bool + */ + public function isTty() + { + return $this->tty; + } + + /** + * Sets PTY mode. + * + * @return $this + */ + public function setPty(bool $bool) + { + $this->pty = $bool; + + return $this; + } + + /** + * Returns PTY state. + * + * @return bool + */ + public function isPty() + { + return $this->pty; + } + + /** + * Gets the working directory. + * + * @return string|null + */ + public function getWorkingDirectory() + { + if (null === $this->cwd) { + // getcwd() will return false if any one of the parent directories does not have + // the readable or search mode set, even if the current directory does + return getcwd() ?: null; + } + + return $this->cwd; + } + + /** + * Sets the current working directory. + * + * @return $this + */ + public function setWorkingDirectory(string $cwd) + { + $this->cwd = $cwd; + + return $this; + } + + /** + * Gets the environment variables. + * + * @return array + */ + public function getEnv() + { + return $this->env; + } + + /** + * Sets the environment variables. + * + * @param array $env The new environment variables + * + * @return $this + */ + public function setEnv(array $env) + { + $this->env = $env; + + return $this; + } + + /** + * Gets the Process input. + * + * @return resource|string|\Iterator|null + */ + public function getInput() + { + return $this->input; + } + + /** + * Sets the input. + * + * This content will be passed to the underlying process standard input. + * + * @param string|int|float|bool|resource|\Traversable|null $input The content + * + * @return $this + * + * @throws LogicException In case the process is running + */ + public function setInput($input) + { + if ($this->isRunning()) { + throw new LogicException('Input cannot be set while the process is running.'); + } + + $this->input = ProcessUtils::validateInput(__METHOD__, $input); + + return $this; + } + + /** + * Performs a check between the timeout definition and the time the process started. + * + * In case you run a background process (with the start method), you should + * trigger this method regularly to ensure the process timeout + * + * @throws ProcessTimedOutException In case the timeout was reached + */ + public function checkTimeout() + { + if (self::STATUS_STARTED !== $this->status) { + return; + } + + if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) { + $this->stop(0); + + throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_GENERAL); + } + + if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) { + $this->stop(0); + + throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_IDLE); + } + } + + /** + * @throws LogicException in case process is not started + */ + public function getStartTime(): float + { + if (!$this->isStarted()) { + throw new LogicException('Start time is only available after process start.'); + } + + return $this->starttime; + } + + /** + * Defines options to pass to the underlying proc_open(). + * + * @see https://php.net/proc_open for the options supported by PHP. + * + * Enabling the "create_new_console" option allows a subprocess to continue + * to run after the main process exited, on both Windows and *nix + */ + public function setOptions(array $options) + { + if ($this->isRunning()) { + throw new RuntimeException('Setting options while the process is running is not possible.'); + } + + $defaultOptions = $this->options; + $existingOptions = ['blocking_pipes', 'create_process_group', 'create_new_console']; + + foreach ($options as $key => $value) { + if (!\in_array($key, $existingOptions)) { + $this->options = $defaultOptions; + throw new LogicException(sprintf('Invalid option "%s" passed to "%s()". Supported options are "%s".', $key, __METHOD__, implode('", "', $existingOptions))); + } + $this->options[$key] = $value; + } + } + + /** + * Returns whether TTY is supported on the current operating system. + */ + public static function isTtySupported(): bool + { + static $isTtySupported; + + if (null === $isTtySupported) { + $isTtySupported = (bool) @proc_open('echo 1 >/dev/null', [['file', '/dev/tty', 'r'], ['file', '/dev/tty', 'w'], ['file', '/dev/tty', 'w']], $pipes); + } + + return $isTtySupported; + } + + /** + * Returns whether PTY is supported on the current operating system. + * + * @return bool + */ + public static function isPtySupported() + { + static $result; + + if (null !== $result) { + return $result; + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + return $result = false; + } + + return $result = (bool) @proc_open('echo 1 >/dev/null', [['pty'], ['pty'], ['pty']], $pipes); + } + + /** + * Creates the descriptors needed by the proc_open. + */ + private function getDescriptors(): array + { + if ($this->input instanceof \Iterator) { + $this->input->rewind(); + } + if ('\\' === \DIRECTORY_SEPARATOR) { + $this->processPipes = new WindowsPipes($this->input, !$this->outputDisabled || $this->hasCallback); + } else { + $this->processPipes = new UnixPipes($this->isTty(), $this->isPty(), $this->input, !$this->outputDisabled || $this->hasCallback); + } + + return $this->processPipes->getDescriptors(); + } + + /** + * Builds up the callback used by wait(). + * + * The callbacks adds all occurred output to the specific buffer and calls + * the user callback (if present) with the received output. + * + * @param callable|null $callback The user defined PHP callback + * + * @return \Closure + */ + protected function buildCallback(?callable $callback = null) + { + if ($this->outputDisabled) { + return function ($type, $data) use ($callback): bool { + return null !== $callback && $callback($type, $data); + }; + } + + $out = self::OUT; + + return function ($type, $data) use ($callback, $out): bool { + if ($out == $type) { + $this->addOutput($data); + } else { + $this->addErrorOutput($data); + } + + return null !== $callback && $callback($type, $data); + }; + } + + /** + * Updates the status of the process, reads pipes. + * + * @param bool $blocking Whether to use a blocking read call + */ + protected function updateStatus(bool $blocking) + { + if (self::STATUS_STARTED !== $this->status) { + return; + } + + $this->processInformation = proc_get_status($this->process); + $running = $this->processInformation['running']; + + // In PHP < 8.3, "proc_get_status" only returns the correct exit status on the first call. + // Subsequent calls return -1 as the process is discarded. This workaround caches the first + // retrieved exit status for consistent results in later calls, mimicking PHP 8.3 behavior. + if (\PHP_VERSION_ID < 80300) { + if (!isset($this->cachedExitCode) && !$running && -1 !== $this->processInformation['exitcode']) { + $this->cachedExitCode = $this->processInformation['exitcode']; + } + + if (isset($this->cachedExitCode) && !$running && -1 === $this->processInformation['exitcode']) { + $this->processInformation['exitcode'] = $this->cachedExitCode; + } + } + + $this->readPipes($running && $blocking, '\\' !== \DIRECTORY_SEPARATOR || !$running); + + if ($this->fallbackStatus && $this->isSigchildEnabled()) { + $this->processInformation = $this->fallbackStatus + $this->processInformation; + } + + if (!$running) { + $this->close(); + } + } + + /** + * Returns whether PHP has been compiled with the '--enable-sigchild' option or not. + * + * @return bool + */ + protected function isSigchildEnabled() + { + if (null !== self::$sigchild) { + return self::$sigchild; + } + + if (!\function_exists('phpinfo')) { + return self::$sigchild = false; + } + + ob_start(); + phpinfo(\INFO_GENERAL); + + return self::$sigchild = str_contains(ob_get_clean(), '--enable-sigchild'); + } + + /** + * Reads pipes for the freshest output. + * + * @param string $caller The name of the method that needs fresh outputs + * @param bool $blocking Whether to use blocking calls or not + * + * @throws LogicException in case output has been disabled or process is not started + */ + private function readPipesForOutput(string $caller, bool $blocking = false) + { + if ($this->outputDisabled) { + throw new LogicException('Output has been disabled.'); + } + + $this->requireProcessIsStarted($caller); + + $this->updateStatus($blocking); + } + + /** + * Validates and returns the filtered timeout. + * + * @throws InvalidArgumentException if the given timeout is a negative number + */ + private function validateTimeout(?float $timeout): ?float + { + $timeout = (float) $timeout; + + if (0.0 === $timeout) { + $timeout = null; + } elseif ($timeout < 0) { + throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.'); + } + + return $timeout; + } + + /** + * Reads pipes, executes callback. + * + * @param bool $blocking Whether to use blocking calls or not + * @param bool $close Whether to close file handles or not + */ + private function readPipes(bool $blocking, bool $close) + { + $result = $this->processPipes->readAndWrite($blocking, $close); + + $callback = $this->callback; + foreach ($result as $type => $data) { + if (3 !== $type) { + $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data); + } elseif (!isset($this->fallbackStatus['signaled'])) { + $this->fallbackStatus['exitcode'] = (int) $data; + } + } + } + + /** + * Closes process resource, closes file handles, sets the exitcode. + * + * @return int The exitcode + */ + private function close(): int + { + $this->processPipes->close(); + if (\is_resource($this->process)) { + proc_close($this->process); + } + $this->exitcode = $this->processInformation['exitcode']; + $this->status = self::STATUS_TERMINATED; + + if (-1 === $this->exitcode) { + if ($this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) { + // if process has been signaled, no exitcode but a valid termsig, apply Unix convention + $this->exitcode = 128 + $this->processInformation['termsig']; + } elseif ($this->isSigchildEnabled()) { + $this->processInformation['signaled'] = true; + $this->processInformation['termsig'] = -1; + } + } + + // Free memory from self-reference callback created by buildCallback + // Doing so in other contexts like __destruct or by garbage collector is ineffective + // Now pipes are closed, so the callback is no longer necessary + $this->callback = null; + + return $this->exitcode; + } + + /** + * Resets data related to the latest run of the process. + */ + private function resetProcessData() + { + $this->starttime = null; + $this->callback = null; + $this->exitcode = null; + $this->fallbackStatus = []; + $this->processInformation = null; + $this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+'); + $this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+'); + $this->process = null; + $this->latestSignal = null; + $this->status = self::STATUS_READY; + $this->incrementalOutputOffset = 0; + $this->incrementalErrorOutputOffset = 0; + } + + /** + * Sends a POSIX signal to the process. + * + * @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants) + * @param bool $throwException Whether to throw exception in case signal failed + * + * @throws LogicException In case the process is not running + * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed + * @throws RuntimeException In case of failure + */ + private function doSignal(int $signal, bool $throwException): bool + { + if (null === $pid = $this->getPid()) { + if ($throwException) { + throw new LogicException('Cannot send signal on a non running process.'); + } + + return false; + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + exec(sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode); + if ($exitCode && $this->isRunning()) { + if ($throwException) { + throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output))); + } + + return false; + } + } else { + if (!$this->isSigchildEnabled()) { + $ok = @proc_terminate($this->process, $signal); + } elseif (\function_exists('posix_kill')) { + $ok = @posix_kill($pid, $signal); + } elseif ($ok = proc_open(sprintf('kill -%d %d', $signal, $pid), [2 => ['pipe', 'w']], $pipes)) { + $ok = false === fgets($pipes[2]); + } + if (!$ok) { + if ($throwException) { + throw new RuntimeException(sprintf('Error while sending signal "%s".', $signal)); + } + + return false; + } + } + + $this->latestSignal = $signal; + $this->fallbackStatus['signaled'] = true; + $this->fallbackStatus['exitcode'] = -1; + $this->fallbackStatus['termsig'] = $this->latestSignal; + + return true; + } + + private function prepareWindowsCommandLine(string $cmd, array &$env): string + { + $uid = uniqid('', true); + $varCount = 0; + $varCache = []; + $cmd = preg_replace_callback( + '/"(?:( + [^"%!^]*+ + (?: + (?: !LF! | "(?:\^[%!^])?+" ) + [^"%!^]*+ + )++ + ) | [^"]*+ )"/x', + function ($m) use (&$env, &$varCache, &$varCount, $uid) { + if (!isset($m[1])) { + return $m[0]; + } + if (isset($varCache[$m[0]])) { + return $varCache[$m[0]]; + } + if (str_contains($value = $m[1], "\0")) { + $value = str_replace("\0", '?', $value); + } + if (false === strpbrk($value, "\"%!\n")) { + return '"'.$value.'"'; + } + + $value = str_replace(['!LF!', '"^!"', '"^%"', '"^^"', '""'], ["\n", '!', '%', '^', '"'], $value); + $value = '"'.preg_replace('/(\\\\*)"/', '$1$1\\"', $value).'"'; + $var = $uid.++$varCount; + + $env[$var] = $value; + + return $varCache[$m[0]] = '!'.$var.'!'; + }, + $cmd + ); + + $cmd = 'cmd /V:ON /E:ON /D /C ('.str_replace("\n", ' ', $cmd).')'; + foreach ($this->processPipes->getFiles() as $offset => $filename) { + $cmd .= ' '.$offset.'>"'.$filename.'"'; + } + + return $cmd; + } + + /** + * Ensures the process is running or terminated, throws a LogicException if the process has a not started. + * + * @throws LogicException if the process has not run + */ + private function requireProcessIsStarted(string $functionName) + { + if (!$this->isStarted()) { + throw new LogicException(sprintf('Process must be started before calling "%s()".', $functionName)); + } + } + + /** + * Ensures the process is terminated, throws a LogicException if the process has a status different than "terminated". + * + * @throws LogicException if the process is not yet terminated + */ + private function requireProcessIsTerminated(string $functionName) + { + if (!$this->isTerminated()) { + throw new LogicException(sprintf('Process must be terminated before calling "%s()".', $functionName)); + } + } + + /** + * Escapes a string to be used as a shell argument. + */ + private function escapeArgument(?string $argument): string + { + if ('' === $argument || null === $argument) { + return '""'; + } + if ('\\' !== \DIRECTORY_SEPARATOR) { + return "'".str_replace("'", "'\\''", $argument)."'"; + } + if (str_contains($argument, "\0")) { + $argument = str_replace("\0", '?', $argument); + } + if (!preg_match('/[\/()%!^"<>&|\s]/', $argument)) { + return $argument; + } + $argument = preg_replace('/(\\\\+)$/', '$1$1', $argument); + + return '"'.str_replace(['"', '^', '%', '!', "\n"], ['""', '"^^"', '"^%"', '"^!"', '!LF!'], $argument).'"'; + } + + private function replacePlaceholders(string $commandline, array $env) + { + return preg_replace_callback('/"\$\{:([_a-zA-Z]++[_a-zA-Z0-9]*+)\}"/', function ($matches) use ($commandline, $env) { + if (!isset($env[$matches[1]]) || false === $env[$matches[1]]) { + throw new InvalidArgumentException(sprintf('Command line is missing a value for parameter "%s": ', $matches[1]).$commandline); + } + + return $this->escapeArgument($env[$matches[1]]); + }, $commandline); + } + + private function getDefaultEnv(): array + { + $env = getenv(); + $env = ('\\' === \DIRECTORY_SEPARATOR ? array_intersect_ukey($env, $_SERVER, 'strcasecmp') : array_intersect_key($env, $_SERVER)) ?: $env; + + return $_ENV + ('\\' === \DIRECTORY_SEPARATOR ? array_diff_ukey($env, $_ENV, 'strcasecmp') : $env); + } +} diff --git a/bundled-libs/symfony/process/ProcessUtils.php b/bundled-libs/symfony/process/ProcessUtils.php new file mode 100644 index 000000000..2a7aff71b --- /dev/null +++ b/bundled-libs/symfony/process/ProcessUtils.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +use Symfony\Component\Process\Exception\InvalidArgumentException; + +/** + * ProcessUtils is a bunch of utility methods. + * + * This class contains static methods only and is not meant to be instantiated. + * + * @author Martin HasoĊˆ + */ +class ProcessUtils +{ + /** + * This class should not be instantiated. + */ + private function __construct() + { + } + + /** + * Validates and normalizes a Process input. + * + * @param string $caller The name of method call that validates the input + * @param mixed $input The input to validate + * + * @return mixed + * + * @throws InvalidArgumentException In case the input is not valid + */ + public static function validateInput(string $caller, $input) + { + if (null !== $input) { + if (\is_resource($input)) { + return $input; + } + if (\is_string($input)) { + return $input; + } + if (\is_scalar($input)) { + return (string) $input; + } + if ($input instanceof Process) { + return $input->getIterator($input::ITER_SKIP_ERR); + } + if ($input instanceof \Iterator) { + return $input; + } + if ($input instanceof \Traversable) { + return new \IteratorIterator($input); + } + + throw new InvalidArgumentException(sprintf('"%s" only accepts strings, Traversable objects or stream resources.', $caller)); + } + + return $input; + } +} diff --git a/bundled-libs/symfony/process/README.md b/bundled-libs/symfony/process/README.md new file mode 100644 index 000000000..8777de4a6 --- /dev/null +++ b/bundled-libs/symfony/process/README.md @@ -0,0 +1,28 @@ +Process Component +================= + +The Process component executes commands in sub-processes. + +Sponsor +------- + +The Process component for Symfony 5.4/6.0 is [backed][1] by [SensioLabs][2]. + +As the creator of Symfony, SensioLabs supports companies using Symfony, with an +offering encompassing consultancy, expertise, services, training, and technical +assistance to ensure the success of web application development projects. + +Help Symfony by [sponsoring][3] its development! + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/process.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) + +[1]: https://symfony.com/backers +[2]: https://sensiolabs.com +[3]: https://symfony.com/sponsor diff --git a/bundled-libs/symfony/process/composer.json b/bundled-libs/symfony/process/composer.json new file mode 100644 index 000000000..1669eba57 --- /dev/null +++ b/bundled-libs/symfony/process/composer.json @@ -0,0 +1,29 @@ +{ + "name": "symfony/process", + "type": "library", + "description": "Executes commands in sub-processes", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Process\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/composer.json b/composer.json index f21d5df3a..0a18dda2d 100644 --- a/composer.json +++ b/composer.json @@ -36,6 +36,7 @@ "pear/net_dns2": "^1.5", "php81_bc/strftime": "^0.5.0", "smarty/smarty": "^4.3", + "spatie/image-optimizer": "^1.7", "voku/simple-cache": "^4.1" }, "minimum-stability": "dev", diff --git a/composer.lock b/composer.lock index 20ee9328c..b5f35b008 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ed393f6ea172968b5795b80a35b344ec", + "content-hash": "5748df29ad76fc7a8610b1c981c33b38", "packages": [ { "name": "katzgrau/klogger", @@ -508,6 +508,203 @@ }, "time": "2024-01-23T10:47:54+00:00" }, + { + "name": "spatie/image-optimizer", + "version": "1.7.2", + "source": { + "type": "git", + "url": "https://github.com/spatie/image-optimizer.git", + "reference": "62f7463483d1bd975f6f06025d89d42a29608fe1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/image-optimizer/zipball/62f7463483d1bd975f6f06025d89d42a29608fe1", + "reference": "62f7463483d1bd975f6f06025d89d42a29608fe1", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.3|^8.0", + "psr/log": "^1.0 | ^2.0 | ^3.0", + "symfony/process": "^4.2|^5.0|^6.0|^7.0" + }, + "require-dev": { + "pestphp/pest": "^1.21", + "phpunit/phpunit": "^8.5.21|^9.4.4", + "symfony/var-dumper": "^4.2|^5.0|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\ImageOptimizer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Easily optimize images using PHP", + "homepage": "https://github.com/spatie/image-optimizer", + "keywords": [ + "image-optimizer", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/image-optimizer/issues", + "source": "https://github.com/spatie/image-optimizer/tree/1.7.2" + }, + "time": "2023-11-03T10:08:02+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/process", + "version": "v5.4.36", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "4fdf34004f149cc20b2f51d7d119aa500caad975" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/4fdf34004f149cc20b2f51d7d119aa500caad975", + "reference": "4fdf34004f149cc20b2f51d7d119aa500caad975", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v5.4.36" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-02-12T15:49:53+00:00" + }, { "name": "voku/simple-cache", "version": "4.1.0", diff --git a/include/functions_images.inc.php b/include/functions_images.inc.php index 996ec117c..e3682353f 100644 --- a/include/functions_images.inc.php +++ b/include/functions_images.inc.php @@ -6,6 +6,8 @@ die ("Don't hack!"); } +use Spatie\ImageOptimizer\OptimizerChainFactory; + /** * Check if an uploaded file is "evil" * @@ -747,9 +749,20 @@ function serendipity_makeThumbnail($file, $directory = '', $size = false, $thumb unset($output, $result); } } + if ($r != false) { + serendipity_optimizeImage($outfile); + } return $r; } +/** + * Optimize thee given image by running it through image optimizers, to further reduze image size + */ +function serendipity_optimizeImage($imagePath) { + $optimizerChain = OptimizerChainFactory::create(); + $optimizerChain->optimize($imagePath); +} + /** * Scale an image *