diff --git a/composer.json b/composer.json index fdb96a5899..ddc216d636 100644 --- a/composer.json +++ b/composer.json @@ -1,219 +1,220 @@ { - "name": "wp-media/wp-rocket", - "description": "Performance optimization plugin for WordPress", - "keywords": [ - "wordpress", - "cache", - "minification", - "lazyload" - ], - "homepage": "https://wp-rocket.me", - "license": "GPL-2.0-or-later", - "authors": [ - { - "name": "WP Media", - "email": "contact@wp-media.me", - "homepage": "https://wp-media.me" - } - ], - "type": "wordpress-plugin", - "config": { - "sort-packages": true, - "preferred-install": { - "wp-media/phpunit": "source" - }, - "process-timeout": 0, - "allow-plugins": { - "composer/installers": true, - "mnsami/composer-custom-directory-installer": true, - "dealerdirect/phpcodesniffer-composer-installer": true - } - }, - "support": { - "issues": "https://github.com/wp-media/wp-rocket/issues", - "source": "https://github.com/wp-media/wp-rocket" - }, - "repositories": [ - { - "type": "composer", - "url": "https://wpackagist.org" - } - ], - "require": { - "php": ">=7.3", - "cloudflare/cf-ip-rewrite": "^1.0", - "composer/installers": "^1.0 || ^2.0", - "monolog/monolog": "^1.0 || ^2.0" - }, - "require-dev": { - "php": "^7 || ^8", - "brain/monkey": "^2.0", - "coenjacobs/mozart": "^0.7", - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", - "league/container": "^3.3", - "mikey179/vfsstream": "1.6.11", - "mnsami/composer-custom-directory-installer": "^2.0", - "mobiledetect/mobiledetectlib": "^2.8", - "phpcompatibility/phpcompatibility-wp": "^2.0", - "phpstan/phpstan": "^1.8", - "phpunit/phpunit": "^7.5 || ^8 || ^9", - "psr/container": "1.0.0", - "roave/security-advisories": "dev-master", - "szepeviktor/phpstan-wordpress": "^1.3", - "woocommerce/action-scheduler": "^3.4", - "wp-coding-standards/wpcs": "^2", - "wp-media/background-processing": "^1.3", - "wp-media/phpunit": "^3", - "wp-media/rocket-lazyload-common": "^3.0.11", - "wp-media/wp-imagify-partner": "^1.0", - "wpackagist-plugin/amp": "^1.1.4", - "wpackagist-plugin/hummingbird-performance": "2.0.1", - "wpackagist-plugin/jetpack": "9.3.2", - "wpackagist-plugin/pdf-embedder": "^4.6", - "wpackagist-plugin/simple-custom-css": "^4.0.3", - "wpackagist-plugin/spinupwp": "^1.1", - "wpackagist-plugin/woocommerce": "^7", - "wpackagist-plugin/wp-smushit": "^3" - }, - "autoload": { - "classmap": [ - "inc/classes", - "inc/vendors/classes", - "inc/deprecated" - ], - "exclude-from-classmap": [ - "inc/vendors/classes/class-rocket-mobile-detect.php", - "inc/classes/class-wp-rocket-requirements-check.php" - ], - "psr-4": { - "WP_Rocket\\": "inc/", - "WPMedia\\Cloudflare\\": "inc/Addon/Cloudflare/" - } - }, - "autoload-dev": { - "psr-4": { - "WP_Rocket\\Tests\\": "tests/" - } - }, - "extra": { - "installer-paths": { - "./inc/Dependencies/ActionScheduler/": ["woocommerce/action-scheduler"], - "vendor/{$vendor}/{$name}/": ["type:wordpress-plugin"] - }, - "mozart": { - "dep_namespace": "WP_Rocket\\Dependencies\\", - "dep_directory": "/inc/Dependencies/", - "classmap_directory": "/inc/classes/dependencies/", - "classmap_prefix": "WP_Rocket_", - "packages": [ - "mobiledetect/mobiledetectlib", - "wp-media/background-processing", - "wp-media/rocket-lazyload-common", - "wp-media/wp-imagify-partner", - "league/container" - ] - } - }, - "scripts": { - "test-unit": "\"vendor/bin/phpunit\" --testsuite unit --colors=always --configuration tests/Unit/phpunit.xml.dist", - "test-integration": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --exclude-group AdminOnly,BeaverBuilder,Elementor,Hummingbird,WithSmush,WithWoo,WithAmp,WithAmpAndCloudflare,WithSCCSS,Cloudways,Dreampress,Cloudflare,CloudflareAdmin,Multisite,WPEngine,SpinUpWP,WordPressCom,O2Switch,PDFEmbedder,PDFEmbedderPremium,PDFEmbedderSecure,Godaddy,LiteSpeed,RevolutionSlider,WordFence,ConvertPlug,Kinsta,Jetpack,RankMathSEO,AllInOneSeoPack,SEOPress,TheSEOFramework,OneCom,RocketLazyLoad,WPXCloud,TheEventsCalendar,Perfmatters,RapidLoad,ProIsp,TranslatePress,WPGeotargeting", - "test-integration-adminonly": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group AdminOnly", - "test-integration-bb": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group BeaverBuilder", - "test-integration-cloudflare": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group Cloudflare", - "test-integration-cloudflareadmin": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group CloudflareAdmin", - "test-integration-cloudways": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group Cloudways", - "test-integration-elementor": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group Elementor", - "test-integration-hummingbird": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group Hummingbird", - "test-integration-multisite": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group Multisite", - "test-integration-withsmush": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group WithSmush", - "test-integration-withamp": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group WithAmp", - "test-integration-withampcloudflare": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group WithAmpAndCloudflare", - "test-integration-withsccss": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group WithSCCSS", - "test-integration-withwoo": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group WithWoo", - "test-integration-wpengine": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group WPEngine", - "test-integration-spinupwp": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group SpinUpWP", - "test-integration-wpcom": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group WordPressCom", - "test-integration-pdfembedder": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group PDFEmbedder", - "test-integration-pdfembedderpremium": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group PDFEmbedderPremium", - "test-integration-pdfembeddersecure": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group PDFEmbedderSecure", - "test-integration-o2switch": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group O2Switch", - "test-integration-dreampress": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group Dreampress", - "test-integration-godaddy": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group Godaddy", - "test-integration-revolutionslider": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group RevolutionSlider", - "test-integration-litespeed": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group LiteSpeed", - "test-integration-wordfence": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group WordFence", - "test-integration-kinsta": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group Kinsta", - "test-integration-convertplug": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group ConvertPlug", - "test-integration-onecom": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group OneCom", - "test-integration-jetpack": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group Jetpack", - "test-integration-rank-math-seo": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group RankMathSEO", - "test-integration-all-in-seo-pack": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group AllInOneSeoPack", - "test-integration-seopress": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group SEOPress", - "test-integration-the-seo-framework": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group TheSEOFramework", - "test-integration-rocket-lazy-load": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group RocketLazyLoad", - "test-integration-the-events-calendar": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group TheEventsCalendar", - "test-integration-wpxcloud": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group WPXCloud", - "test-integration-perfmatters": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group Perfmatters", - "test-integration-rapidload": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group RapidLoad", - "test-integration-proisp": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group ProIsp", - "test-integration-wp-geotargeting": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group WPGeotargeting", - "test-integration-translatepress": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group TranslatePress", - "run-tests": [ - "@test-unit", - "@test-integration", - "@test-integration-adminonly", - "@test-integration-cloudflare", - "@test-integration-cloudflareadmin", - "@test-integration-bb", - "@test-integration-elementor", - "@test-integration-hummingbird", - "@test-integration-withamp", - "@test-integration-withampcloudflare", - "@test-integration-withsccss", - "@test-integration-withsmush", - "@test-integration-withwoo", - "@test-integration-pdfembedder", - "@test-integration-pdfembedderpremium", - "@test-integration-pdfembeddersecure", - "@test-integration-multisite", - "@test-integration-cloudways", - "@test-integration-wpengine", - "@test-integration-spinupwp", - "@test-integration-wpcom", - "@test-integration-o2switch", - "@test-integration-dreampress", - "@test-integration-godaddy", - "@test-integration-revolutionslider", - "@test-integration-litespeed", - "@test-integration-wordfence", - "@test-integration-kinsta", - "@test-integration-convertplug", - "@test-integration-onecom", - "@test-integration-jetpack", - "@test-integration-rank-math-seo", - "@test-integration-all-in-seo-pack", - "@test-integration-seopress", - "@test-integration-the-events-calendar", - "@test-integration-wpxcloud", - "@test-integration-perfmatters", - "@test-integration-rapidload", - "@test-integration-proisp", - "@test-integration-wp-geotargeting", - "@test-integration-translatepress" - ], - "run-stan": "vendor/bin/phpstan analyze --memory-limit=2G --no-progress", - "install-codestandards": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin::run", - "phpcs": "phpcs --basepath=.", - "phpcs-changed": "./bin/phpcs-changed.sh", - "phpcs:fix": "phpcbf", - "post-install-cmd": [ - "\"vendor/bin/mozart\" compose", - "composer dump-autoload" - ], - "post-update-cmd": [ - "\"vendor/bin/mozart\" compose", - "composer dump-autoload" - ], - "code-coverage": "\"vendor/bin/phpunit\" --testsuite unit --colors=always --configuration tests/Unit/phpunit.xml.dist --coverage-clover=tests/report/coverage.clover" - } + "name": "wp-media/wp-rocket", + "description": "Performance optimization plugin for WordPress", + "keywords": [ + "wordpress", + "cache", + "minification", + "lazyload" + ], + "homepage": "https://wp-rocket.me", + "license": "GPL-2.0-or-later", + "authors": [ + { + "name": "WP Media", + "email": "contact@wp-media.me", + "homepage": "https://wp-media.me" + } + ], + "type": "wordpress-plugin", + "config": { + "sort-packages": true, + "preferred-install": { + "wp-media/phpunit": "source" + }, + "process-timeout": 0, + "allow-plugins": { + "composer/installers": true, + "mnsami/composer-custom-directory-installer": true, + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "support": { + "issues": "https://github.com/wp-media/wp-rocket/issues", + "source": "https://github.com/wp-media/wp-rocket" + }, + "repositories": [ + { + "type": "composer", + "url": "https://wpackagist.org" + } + ], + "require": { + "php": ">=7.3", + "cloudflare/cf-ip-rewrite": "^1.0", + "composer/installers": "^1.0 || ^2.0" + }, + "require-dev": { + "php": "^7 || ^8", + "brain/monkey": "^2.0", + "coenjacobs/mozart": "^0.7", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "league/container": "^3.3", + "mikey179/vfsstream": "1.6.11", + "mnsami/composer-custom-directory-installer": "^2.0", + "mobiledetect/mobiledetectlib": "^2.8", + "phpcompatibility/phpcompatibility-wp": "^2.0", + "phpstan/phpstan": "^1.8", + "phpunit/phpunit": "^7.5 || ^8 || ^9", + "psr/container": "1.0.0", + "roave/security-advisories": "dev-master", + "szepeviktor/phpstan-wordpress": "^1.3", + "woocommerce/action-scheduler": "^3.4", + "wp-coding-standards/wpcs": "^2", + "wp-media/background-processing": "^1.3", + "wp-media/phpunit": "^3", + "wp-media/rocket-lazyload-common": "^3.0.11", + "wp-media/wp-imagify-partner": "^1.0", + "wpackagist-plugin/amp": "^1.1.4", + "wpackagist-plugin/hummingbird-performance": "2.0.1", + "wpackagist-plugin/jetpack": "9.3.2", + "wpackagist-plugin/pdf-embedder": "^4.6", + "wpackagist-plugin/simple-custom-css": "^4.0.3", + "wpackagist-plugin/spinupwp": "^1.1", + "wpackagist-plugin/woocommerce": "^7", + "wpackagist-plugin/wp-smushit": "^3", + "wp-media/monolog": "^0.0" + }, + "autoload": { + "classmap": [ + "inc/classes", + "inc/vendors/classes", + "inc/deprecated" + ], + "exclude-from-classmap": [ + "inc/vendors/classes/class-rocket-mobile-detect.php", + "inc/classes/class-wp-rocket-requirements-check.php" + ], + "psr-4": { + "WP_Rocket\\": "inc/", + "WPMedia\\Cloudflare\\": "inc/Addon/Cloudflare/" + } + }, + "autoload-dev": { + "psr-4": { + "WP_Rocket\\Tests\\": "tests/" + } + }, + "extra": { + "installer-paths": { + "./inc/Dependencies/ActionScheduler/": ["woocommerce/action-scheduler"], + "vendor/{$vendor}/{$name}/": ["type:wordpress-plugin"] + }, + "mozart": { + "dep_namespace": "WP_Rocket\\Dependencies\\", + "dep_directory": "/inc/Dependencies/", + "classmap_directory": "/inc/classes/dependencies/", + "classmap_prefix": "WP_Rocket_", + "packages": [ + "mobiledetect/mobiledetectlib", + "wp-media/background-processing", + "wp-media/rocket-lazyload-common", + "wp-media/wp-imagify-partner", + "wp-media/monolog", + "league/container" + ] + } + }, + "scripts": { + "test-unit": "\"vendor/bin/phpunit\" --testsuite unit --colors=always --configuration tests/Unit/phpunit.xml.dist", + "test-integration": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --exclude-group AdminOnly,BeaverBuilder,Elementor,Hummingbird,WithSmush,WithWoo,WithAmp,WithAmpAndCloudflare,WithSCCSS,Cloudways,Dreampress,Cloudflare,CloudflareAdmin,Multisite,WPEngine,SpinUpWP,WordPressCom,O2Switch,PDFEmbedder,PDFEmbedderPremium,PDFEmbedderSecure,Godaddy,LiteSpeed,RevolutionSlider,WordFence,ConvertPlug,Kinsta,Jetpack,RankMathSEO,AllInOneSeoPack,SEOPress,TheSEOFramework,OneCom,RocketLazyLoad,WPXCloud,TheEventsCalendar,Perfmatters,RapidLoad,ProIsp,TranslatePress,WPGeotargeting", + "test-integration-adminonly": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group AdminOnly", + "test-integration-bb": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group BeaverBuilder", + "test-integration-cloudflare": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group Cloudflare", + "test-integration-cloudflareadmin": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group CloudflareAdmin", + "test-integration-cloudways": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group Cloudways", + "test-integration-elementor": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group Elementor", + "test-integration-hummingbird": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group Hummingbird", + "test-integration-multisite": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group Multisite", + "test-integration-withsmush": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group WithSmush", + "test-integration-withamp": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group WithAmp", + "test-integration-withampcloudflare": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group WithAmpAndCloudflare", + "test-integration-withsccss": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group WithSCCSS", + "test-integration-withwoo": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group WithWoo", + "test-integration-wpengine": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group WPEngine", + "test-integration-spinupwp": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group SpinUpWP", + "test-integration-wpcom": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group WordPressCom", + "test-integration-pdfembedder": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group PDFEmbedder", + "test-integration-pdfembedderpremium": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group PDFEmbedderPremium", + "test-integration-pdfembeddersecure": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group PDFEmbedderSecure", + "test-integration-o2switch": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group O2Switch", + "test-integration-dreampress": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group Dreampress", + "test-integration-godaddy": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group Godaddy", + "test-integration-revolutionslider": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group RevolutionSlider", + "test-integration-litespeed": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group LiteSpeed", + "test-integration-wordfence": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group WordFence", + "test-integration-kinsta": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group Kinsta", + "test-integration-convertplug": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group ConvertPlug", + "test-integration-onecom": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group OneCom", + "test-integration-jetpack": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group Jetpack", + "test-integration-rank-math-seo": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group RankMathSEO", + "test-integration-all-in-seo-pack": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group AllInOneSeoPack", + "test-integration-seopress": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group SEOPress", + "test-integration-the-seo-framework": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group TheSEOFramework", + "test-integration-rocket-lazy-load": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group RocketLazyLoad", + "test-integration-the-events-calendar": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group TheEventsCalendar", + "test-integration-wpxcloud": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group WPXCloud", + "test-integration-perfmatters": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group Perfmatters", + "test-integration-rapidload": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group RapidLoad", + "test-integration-proisp": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group ProIsp", + "test-integration-wp-geotargeting": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group WPGeotargeting", + "test-integration-translatepress": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group TranslatePress", + "run-tests": [ + "@test-unit", + "@test-integration", + "@test-integration-adminonly", + "@test-integration-cloudflare", + "@test-integration-cloudflareadmin", + "@test-integration-bb", + "@test-integration-elementor", + "@test-integration-hummingbird", + "@test-integration-withamp", + "@test-integration-withampcloudflare", + "@test-integration-withsccss", + "@test-integration-withsmush", + "@test-integration-withwoo", + "@test-integration-pdfembedder", + "@test-integration-pdfembedderpremium", + "@test-integration-pdfembeddersecure", + "@test-integration-multisite", + "@test-integration-cloudways", + "@test-integration-wpengine", + "@test-integration-spinupwp", + "@test-integration-wpcom", + "@test-integration-o2switch", + "@test-integration-dreampress", + "@test-integration-godaddy", + "@test-integration-revolutionslider", + "@test-integration-litespeed", + "@test-integration-wordfence", + "@test-integration-kinsta", + "@test-integration-convertplug", + "@test-integration-onecom", + "@test-integration-jetpack", + "@test-integration-rank-math-seo", + "@test-integration-all-in-seo-pack", + "@test-integration-seopress", + "@test-integration-the-events-calendar", + "@test-integration-wpxcloud", + "@test-integration-perfmatters", + "@test-integration-rapidload", + "@test-integration-proisp", + "@test-integration-wp-geotargeting", + "@test-integration-translatepress" + ], + "run-stan": "vendor/bin/phpstan analyze --memory-limit=2G --no-progress", + "install-codestandards": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin::run", + "phpcs": "phpcs --basepath=.", + "phpcs-changed": "./bin/phpcs-changed.sh", + "phpcs:fix": "phpcbf", + "post-install-cmd": [ + "\"vendor/bin/mozart\" compose", + "composer dump-autoload" + ], + "post-update-cmd": [ + "\"vendor/bin/mozart\" compose", + "composer dump-autoload" + ], + "code-coverage": "\"vendor/bin/phpunit\" --testsuite unit --colors=always --configuration tests/Unit/phpunit.xml.dist --coverage-clover=tests/report/coverage.clover" + } } diff --git a/inc/Dependencies/Monolog/ErrorHandler.php b/inc/Dependencies/Monolog/ErrorHandler.php new file mode 100644 index 0000000000..faa26b5fbe --- /dev/null +++ b/inc/Dependencies/Monolog/ErrorHandler.php @@ -0,0 +1,239 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace WP_Rocket\Dependencies\Monolog; + +use WP_Rocket\Dependencies\Psr\Log\LoggerInterface; +use WP_Rocket\Dependencies\Psr\Log\LogLevel; +use WP_Rocket\Dependencies\Monolog\Handler\AbstractHandler; + +/** + * WP_Rocket\Dependencies\Monolog error handler + * + * A facility to enable logging of runtime errors, exceptions and fatal errors. + * + * Quick setup: ErrorHandler::register($logger); + * + * @author Jordi Boggiano + */ +class ErrorHandler +{ + private $logger; + + private $previousExceptionHandler; + private $uncaughtExceptionLevel; + + private $previousErrorHandler; + private $errorLevelMap; + private $handleOnlyReportedErrors; + + private $hasFatalErrorHandler; + private $fatalLevel; + private $reservedMemory; + private $lastFatalTrace; + private static $fatalErrors = array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR); + + public function __construct(LoggerInterface $logger) + { + $this->logger = $logger; + } + + /** + * Registers a new ErrorHandler for a given Logger + * + * By default it will handle errors, exceptions and fatal errors + * + * @param LoggerInterface $logger + * @param array|false $errorLevelMap an array of E_* constant to LogLevel::* constant mapping, or false to disable error handling + * @param int|false $exceptionLevel a LogLevel::* constant, or false to disable exception handling + * @param int|false $fatalLevel a LogLevel::* constant, or false to disable fatal error handling + * @return ErrorHandler + */ + public static function register(LoggerInterface $logger, $errorLevelMap = array(), $exceptionLevel = null, $fatalLevel = null) + { + //Forces the autoloader to run for LogLevel. Fixes an autoload issue at compile-time on PHP5.3. See https://github.com/Seldaek/monolog/pull/929 + class_exists('\\Psr\\Log\\LogLevel', true); + + /** @phpstan-ignore-next-line */ + $handler = new static($logger); + if ($errorLevelMap !== false) { + $handler->registerErrorHandler($errorLevelMap); + } + if ($exceptionLevel !== false) { + $handler->registerExceptionHandler($exceptionLevel); + } + if ($fatalLevel !== false) { + $handler->registerFatalHandler($fatalLevel); + } + + return $handler; + } + + public function registerExceptionHandler($level = null, $callPrevious = true) + { + $prev = set_exception_handler(array($this, 'handleException')); + $this->uncaughtExceptionLevel = $level; + if ($callPrevious && $prev) { + $this->previousExceptionHandler = $prev; + } + } + + public function registerErrorHandler(array $levelMap = array(), $callPrevious = true, $errorTypes = -1, $handleOnlyReportedErrors = true) + { + $prev = set_error_handler(array($this, 'handleError'), $errorTypes); + $this->errorLevelMap = array_replace($this->defaultErrorLevelMap(), $levelMap); + if ($callPrevious) { + $this->previousErrorHandler = $prev ?: true; + } + + $this->handleOnlyReportedErrors = $handleOnlyReportedErrors; + } + + public function registerFatalHandler($level = null, $reservedMemorySize = 20) + { + register_shutdown_function(array($this, 'handleFatalError')); + + $this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize); + $this->fatalLevel = $level; + $this->hasFatalErrorHandler = true; + } + + protected function defaultErrorLevelMap() + { + return array( + E_ERROR => LogLevel::CRITICAL, + E_WARNING => LogLevel::WARNING, + E_PARSE => LogLevel::ALERT, + E_NOTICE => LogLevel::NOTICE, + E_CORE_ERROR => LogLevel::CRITICAL, + E_CORE_WARNING => LogLevel::WARNING, + E_COMPILE_ERROR => LogLevel::ALERT, + E_COMPILE_WARNING => LogLevel::WARNING, + E_USER_ERROR => LogLevel::ERROR, + E_USER_WARNING => LogLevel::WARNING, + E_USER_NOTICE => LogLevel::NOTICE, + E_STRICT => LogLevel::NOTICE, + E_RECOVERABLE_ERROR => LogLevel::ERROR, + E_DEPRECATED => LogLevel::NOTICE, + E_USER_DEPRECATED => LogLevel::NOTICE, + ); + } + + /** + * @private + */ + public function handleException($e) + { + $this->logger->log( + $this->uncaughtExceptionLevel === null ? LogLevel::ERROR : $this->uncaughtExceptionLevel, + sprintf('Uncaught Exception %s: "%s" at %s line %s', Utils::getClass($e), $e->getMessage(), $e->getFile(), $e->getLine()), + array('exception' => $e) + ); + + if ($this->previousExceptionHandler) { + call_user_func($this->previousExceptionHandler, $e); + } + + exit(255); + } + + /** + * @private + */ + public function handleError($code, $message, $file = '', $line = 0, $context = array()) + { + if ($this->handleOnlyReportedErrors && !(error_reporting() & $code)) { + return; + } + + // fatal error codes are ignored if a fatal error handler is present as well to avoid duplicate log entries + if (!$this->hasFatalErrorHandler || !in_array($code, self::$fatalErrors, true)) { + $level = isset($this->errorLevelMap[$code]) ? $this->errorLevelMap[$code] : LogLevel::CRITICAL; + $this->logger->log($level, self::codeToString($code).': '.$message, array('code' => $code, 'message' => $message, 'file' => $file, 'line' => $line)); + } else { + // http://php.net/manual/en/function.debug-backtrace.php + // As of 5.3.6, DEBUG_BACKTRACE_IGNORE_ARGS option was added. + // Any version less than 5.3.6 must use the DEBUG_BACKTRACE_IGNORE_ARGS constant value '2'. + $trace = debug_backtrace((PHP_VERSION_ID < 50306) ? 2 : DEBUG_BACKTRACE_IGNORE_ARGS); + array_shift($trace); // Exclude handleError from trace + $this->lastFatalTrace = $trace; + } + + if ($this->previousErrorHandler === true) { + return false; + } elseif ($this->previousErrorHandler) { + return call_user_func($this->previousErrorHandler, $code, $message, $file, $line, $context); + } + } + + /** + * @private + */ + public function handleFatalError() + { + $this->reservedMemory = null; + + $lastError = error_get_last(); + if ($lastError && in_array($lastError['type'], self::$fatalErrors, true)) { + $this->logger->log( + $this->fatalLevel === null ? LogLevel::ALERT : $this->fatalLevel, + 'Fatal Error ('.self::codeToString($lastError['type']).'): '.$lastError['message'], + array('code' => $lastError['type'], 'message' => $lastError['message'], 'file' => $lastError['file'], 'line' => $lastError['line'], 'trace' => $this->lastFatalTrace) + ); + + if ($this->logger instanceof Logger) { + foreach ($this->logger->getHandlers() as $handler) { + if ($handler instanceof AbstractHandler) { + $handler->close(); + } + } + } + } + } + + private static function codeToString($code) + { + switch ($code) { + case E_ERROR: + return 'E_ERROR'; + case E_WARNING: + return 'E_WARNING'; + case E_PARSE: + return 'E_PARSE'; + case E_NOTICE: + return 'E_NOTICE'; + case E_CORE_ERROR: + return 'E_CORE_ERROR'; + case E_CORE_WARNING: + return 'E_CORE_WARNING'; + case E_COMPILE_ERROR: + return 'E_COMPILE_ERROR'; + case E_COMPILE_WARNING: + return 'E_COMPILE_WARNING'; + case E_USER_ERROR: + return 'E_USER_ERROR'; + case E_USER_WARNING: + return 'E_USER_WARNING'; + case E_USER_NOTICE: + return 'E_USER_NOTICE'; + case E_STRICT: + return 'E_STRICT'; + case E_RECOVERABLE_ERROR: + return 'E_RECOVERABLE_ERROR'; + case E_DEPRECATED: + return 'E_DEPRECATED'; + case E_USER_DEPRECATED: + return 'E_USER_DEPRECATED'; + } + + return 'Unknown PHP error'; + } +} diff --git a/inc/Dependencies/Monolog/Formatter/FormatterInterface.php b/inc/Dependencies/Monolog/Formatter/FormatterInterface.php new file mode 100644 index 0000000000..e4a3176394 --- /dev/null +++ b/inc/Dependencies/Monolog/Formatter/FormatterInterface.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace WP_Rocket\Dependencies\Monolog\Formatter; + +/** + * Interface for formatters + * + * @author Jordi Boggiano + */ +interface FormatterInterface +{ + /** + * Formats a log record. + * + * @param array $record A record to format + * @return mixed The formatted record + */ + public function format(array $record); + + /** + * Formats a set of log records. + * + * @param array $records A set of records to format + * @return mixed The formatted set of records + */ + public function formatBatch(array $records); +} diff --git a/inc/Dependencies/Monolog/Formatter/HtmlFormatter.php b/inc/Dependencies/Monolog/Formatter/HtmlFormatter.php new file mode 100644 index 0000000000..b8c2158435 --- /dev/null +++ b/inc/Dependencies/Monolog/Formatter/HtmlFormatter.php @@ -0,0 +1,142 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace WP_Rocket\Dependencies\Monolog\Formatter; + +use WP_Rocket\Dependencies\Monolog\Logger; +use WP_Rocket\Dependencies\Monolog\Utils; + +/** + * Formats incoming records into an HTML table + * + * This is especially useful for html email logging + * + * @author Tiago Brito + */ +class HtmlFormatter extends NormalizerFormatter +{ + /** + * Translates WP_Rocket\Dependencies\Monolog log levels to html color priorities. + */ + protected $logLevels = array( + Logger::DEBUG => '#cccccc', + Logger::INFO => '#468847', + Logger::NOTICE => '#3a87ad', + Logger::WARNING => '#c09853', + Logger::ERROR => '#f0ad4e', + Logger::CRITICAL => '#FF7708', + Logger::ALERT => '#C12A19', + Logger::EMERGENCY => '#000000', + ); + + /** + * @param string $dateFormat The format of the timestamp: one supported by DateTime::format + */ + public function __construct($dateFormat = null) + { + parent::__construct($dateFormat); + } + + /** + * Creates an HTML table row + * + * @param string $th Row header content + * @param string $td Row standard cell content + * @param bool $escapeTd false if td content must not be html escaped + * @return string + */ + protected function addRow($th, $td = ' ', $escapeTd = true) + { + $th = htmlspecialchars($th, ENT_NOQUOTES, 'UTF-8'); + if ($escapeTd) { + $td = '
'.htmlspecialchars($td, ENT_NOQUOTES, 'UTF-8').'
'; + } + + return "\n$th:\n".$td."\n"; + } + + /** + * Create a HTML h1 tag + * + * @param string $title Text to be in the h1 + * @param int $level Error level + * @return string + */ + protected function addTitle($title, $level) + { + $title = htmlspecialchars($title, ENT_NOQUOTES, 'UTF-8'); + + return '

'.$title.'

'; + } + + /** + * Formats a log record. + * + * @param array $record A record to format + * @return mixed The formatted record + */ + public function format(array $record) + { + $output = $this->addTitle($record['level_name'], $record['level']); + $output .= ''; + + $output .= $this->addRow('Message', (string) $record['message']); + $output .= $this->addRow('Time', $record['datetime']->format($this->dateFormat)); + $output .= $this->addRow('Channel', $record['channel']); + if ($record['context']) { + $embeddedTable = '
'; + foreach ($record['context'] as $key => $value) { + $embeddedTable .= $this->addRow($key, $this->convertToString($value)); + } + $embeddedTable .= '
'; + $output .= $this->addRow('Context', $embeddedTable, false); + } + if ($record['extra']) { + $embeddedTable = ''; + foreach ($record['extra'] as $key => $value) { + $embeddedTable .= $this->addRow($key, $this->convertToString($value)); + } + $embeddedTable .= '
'; + $output .= $this->addRow('Extra', $embeddedTable, false); + } + + return $output.''; + } + + /** + * Formats a set of log records. + * + * @param array $records A set of records to format + * @return mixed The formatted set of records + */ + public function formatBatch(array $records) + { + $message = ''; + foreach ($records as $record) { + $message .= $this->format($record); + } + + return $message; + } + + protected function convertToString($data) + { + if (null === $data || is_scalar($data)) { + return (string) $data; + } + + $data = $this->normalize($data); + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + return Utils::jsonEncode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE, true); + } + + return str_replace('\\/', '/', Utils::jsonEncode($data, null, true)); + } +} diff --git a/inc/Dependencies/Monolog/Formatter/LineFormatter.php b/inc/Dependencies/Monolog/Formatter/LineFormatter.php new file mode 100644 index 0000000000..60b8a72c18 --- /dev/null +++ b/inc/Dependencies/Monolog/Formatter/LineFormatter.php @@ -0,0 +1,181 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace WP_Rocket\Dependencies\Monolog\Formatter; + +use WP_Rocket\Dependencies\Monolog\Utils; + +/** + * Formats incoming records into a one-line string + * + * This is especially useful for logging to files + * + * @author Jordi Boggiano + * @author Christophe Coevoet + */ +class LineFormatter extends NormalizerFormatter +{ + const SIMPLE_FORMAT = "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n"; + + protected $format; + protected $allowInlineLineBreaks; + protected $ignoreEmptyContextAndExtra; + protected $includeStacktraces; + + /** + * @param string $format The format of the message + * @param string $dateFormat The format of the timestamp: one supported by DateTime::format + * @param bool $allowInlineLineBreaks Whether to allow inline line breaks in log entries + * @param bool $ignoreEmptyContextAndExtra + */ + public function __construct($format = null, $dateFormat = null, $allowInlineLineBreaks = false, $ignoreEmptyContextAndExtra = false) + { + $this->format = $format ?: static::SIMPLE_FORMAT; + $this->allowInlineLineBreaks = $allowInlineLineBreaks; + $this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra; + parent::__construct($dateFormat); + } + + public function includeStacktraces($include = true) + { + $this->includeStacktraces = $include; + if ($this->includeStacktraces) { + $this->allowInlineLineBreaks = true; + } + } + + public function allowInlineLineBreaks($allow = true) + { + $this->allowInlineLineBreaks = $allow; + } + + public function ignoreEmptyContextAndExtra($ignore = true) + { + $this->ignoreEmptyContextAndExtra = $ignore; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + $vars = parent::format($record); + + $output = $this->format; + + foreach ($vars['extra'] as $var => $val) { + if (false !== strpos($output, '%extra.'.$var.'%')) { + $output = str_replace('%extra.'.$var.'%', $this->stringify($val), $output); + unset($vars['extra'][$var]); + } + } + + + foreach ($vars['context'] as $var => $val) { + if (false !== strpos($output, '%context.'.$var.'%')) { + $output = str_replace('%context.'.$var.'%', $this->stringify($val), $output); + unset($vars['context'][$var]); + } + } + + if ($this->ignoreEmptyContextAndExtra) { + if (empty($vars['context'])) { + unset($vars['context']); + $output = str_replace('%context%', '', $output); + } + + if (empty($vars['extra'])) { + unset($vars['extra']); + $output = str_replace('%extra%', '', $output); + } + } + + foreach ($vars as $var => $val) { + if (false !== strpos($output, '%'.$var.'%')) { + $output = str_replace('%'.$var.'%', $this->stringify($val), $output); + } + } + + // remove leftover %extra.xxx% and %context.xxx% if any + if (false !== strpos($output, '%')) { + $output = preg_replace('/%(?:extra|context)\..+?%/', '', $output); + } + + return $output; + } + + public function formatBatch(array $records) + { + $message = ''; + foreach ($records as $record) { + $message .= $this->format($record); + } + + return $message; + } + + public function stringify($value) + { + return $this->replaceNewlines($this->convertToString($value)); + } + + protected function normalizeException($e) + { + // TODO 2.0 only check for Throwable + if (!$e instanceof \Exception && !$e instanceof \Throwable) { + throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.Utils::getClass($e)); + } + + $previousText = ''; + if ($previous = $e->getPrevious()) { + do { + $previousText .= ', '.Utils::getClass($previous).'(code: '.$previous->getCode().'): '.$previous->getMessage().' at '.$previous->getFile().':'.$previous->getLine(); + } while ($previous = $previous->getPrevious()); + } + + $str = '[object] ('.Utils::getClass($e).'(code: '.$e->getCode().'): '.$e->getMessage().' at '.$e->getFile().':'.$e->getLine().$previousText.')'; + if ($this->includeStacktraces) { + $str .= "\n[stacktrace]\n".$e->getTraceAsString()."\n"; + } + + return $str; + } + + protected function convertToString($data) + { + if (null === $data || is_bool($data)) { + return var_export($data, true); + } + + if (is_scalar($data)) { + return (string) $data; + } + + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + return $this->toJson($data, true); + } + + return str_replace('\\/', '/', $this->toJson($data, true)); + } + + protected function replaceNewlines($str) + { + if ($this->allowInlineLineBreaks) { + if (0 === strpos($str, '{')) { + return str_replace(array('\r', '\n'), array("\r", "\n"), $str); + } + + return $str; + } + + return str_replace(array("\r\n", "\r", "\n"), ' ', $str); + } +} diff --git a/inc/Dependencies/Monolog/Formatter/NormalizerFormatter.php b/inc/Dependencies/Monolog/Formatter/NormalizerFormatter.php new file mode 100644 index 0000000000..df64b2a174 --- /dev/null +++ b/inc/Dependencies/Monolog/Formatter/NormalizerFormatter.php @@ -0,0 +1,199 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace WP_Rocket\Dependencies\Monolog\Formatter; + +use Exception; +use WP_Rocket\Dependencies\Monolog\Utils; + +/** + * Normalizes incoming records to remove objects/resources so it's easier to dump to various targets + * + * @author Jordi Boggiano + */ +class NormalizerFormatter implements FormatterInterface +{ + const SIMPLE_DATE = "Y-m-d H:i:s"; + + protected $dateFormat; + protected $maxDepth; + + /** + * @param string $dateFormat The format of the timestamp: one supported by DateTime::format + * @param int $maxDepth + */ + public function __construct($dateFormat = null, $maxDepth = 9) + { + $this->dateFormat = $dateFormat ?: static::SIMPLE_DATE; + $this->maxDepth = $maxDepth; + if (!function_exists('json_encode')) { + throw new \RuntimeException('PHP\'s json extension is required to use WP_Rocket\Dependencies\Monolog\'s NormalizerFormatter'); + } + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + return $this->normalize($record); + } + + /** + * {@inheritdoc} + */ + public function formatBatch(array $records) + { + foreach ($records as $key => $record) { + $records[$key] = $this->format($record); + } + + return $records; + } + + /** + * @return int + */ + public function getMaxDepth() + { + return $this->maxDepth; + } + + /** + * @param int $maxDepth + */ + public function setMaxDepth($maxDepth) + { + $this->maxDepth = $maxDepth; + } + + protected function normalize($data, $depth = 0) + { + if ($depth > $this->maxDepth) { + return 'Over '.$this->maxDepth.' levels deep, aborting normalization'; + } + + if (null === $data || is_scalar($data)) { + if (is_float($data)) { + if (is_infinite($data)) { + return ($data > 0 ? '' : '-') . 'INF'; + } + if (is_nan($data)) { + return 'NaN'; + } + } + + return $data; + } + + if (is_array($data)) { + $normalized = array(); + + $count = 1; + foreach ($data as $key => $value) { + if ($count++ > 1000) { + $normalized['...'] = 'Over 1000 items ('.count($data).' total), aborting normalization'; + break; + } + + $normalized[$key] = $this->normalize($value, $depth+1); + } + + return $normalized; + } + + if ($data instanceof \DateTime) { + return $data->format($this->dateFormat); + } + + if (is_object($data)) { + // TODO 2.0 only check for Throwable + if ($data instanceof Exception || (PHP_VERSION_ID > 70000 && $data instanceof \Throwable)) { + return $this->normalizeException($data); + } + + // non-serializable objects that implement __toString stringified + if (method_exists($data, '__toString') && !$data instanceof \JsonSerializable) { + $value = $data->__toString(); + } else { + // the rest is json-serialized in some way + $value = $this->toJson($data, true); + } + + return sprintf("[object] (%s: %s)", Utils::getClass($data), $value); + } + + if (is_resource($data)) { + return sprintf('[resource] (%s)', get_resource_type($data)); + } + + return '[unknown('.gettype($data).')]'; + } + + protected function normalizeException($e) + { + // TODO 2.0 only check for Throwable + if (!$e instanceof Exception && !$e instanceof \Throwable) { + throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.Utils::getClass($e)); + } + + $data = array( + 'class' => Utils::getClass($e), + 'message' => $e->getMessage(), + 'code' => (int) $e->getCode(), + 'file' => $e->getFile().':'.$e->getLine(), + ); + + if ($e instanceof \SoapFault) { + if (isset($e->faultcode)) { + $data['faultcode'] = $e->faultcode; + } + + if (isset($e->faultactor)) { + $data['faultactor'] = $e->faultactor; + } + + if (isset($e->detail)) { + if (is_string($e->detail)) { + $data['detail'] = $e->detail; + } elseif (is_object($e->detail) || is_array($e->detail)) { + $data['detail'] = $this->toJson($e->detail, true); + } + } + } + + $trace = $e->getTrace(); + foreach ($trace as $frame) { + if (isset($frame['file'])) { + $data['trace'][] = $frame['file'].':'.$frame['line']; + } + } + + if ($previous = $e->getPrevious()) { + $data['previous'] = $this->normalizeException($previous); + } + + return $data; + } + + /** + * Return the JSON representation of a value + * + * @param mixed $data + * @param bool $ignoreErrors + * @throws \RuntimeException if encoding fails and errors are not ignored + * @return string + */ + protected function toJson($data, $ignoreErrors = false) + { + return Utils::jsonEncode($data, null, $ignoreErrors); + } +} \ No newline at end of file diff --git a/inc/Dependencies/Monolog/Handler/AbstractHandler.php b/inc/Dependencies/Monolog/Handler/AbstractHandler.php new file mode 100644 index 0000000000..5e089b68ca --- /dev/null +++ b/inc/Dependencies/Monolog/Handler/AbstractHandler.php @@ -0,0 +1,196 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace WP_Rocket\Dependencies\Monolog\Handler; + +use WP_Rocket\Dependencies\Monolog\Formatter\FormatterInterface; +use WP_Rocket\Dependencies\Monolog\Formatter\LineFormatter; +use WP_Rocket\Dependencies\Monolog\Logger; +use WP_Rocket\Dependencies\Monolog\ResettableInterface; + +/** + * Base Handler class providing the Handler structure + * + * @author Jordi Boggiano + */ +abstract class AbstractHandler implements HandlerInterface, ResettableInterface +{ + protected $level = Logger::DEBUG; + protected $bubble = true; + + /** + * @var FormatterInterface + */ + protected $formatter; + protected $processors = array(); + + /** + * @param int|string $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($level = Logger::DEBUG, $bubble = true) + { + $this->setLevel($level); + $this->bubble = $bubble; + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + return $record['level'] >= $this->level; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + foreach ($records as $record) { + $this->handle($record); + } + } + + /** + * Closes the handler. + * + * This will be called automatically when the object is destroyed + */ + public function close() + { + } + + /** + * {@inheritdoc} + */ + public function pushProcessor($callback) + { + if (!is_callable($callback)) { + throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given'); + } + array_unshift($this->processors, $callback); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function popProcessor() + { + if (!$this->processors) { + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + + return array_shift($this->processors); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter) + { + $this->formatter = $formatter; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + if (!$this->formatter) { + $this->formatter = $this->getDefaultFormatter(); + } + + return $this->formatter; + } + + /** + * Sets minimum logging level at which this handler will be triggered. + * + * @param int|string $level Level or level name + * @return self + */ + public function setLevel($level) + { + $this->level = Logger::toMonologLevel($level); + + return $this; + } + + /** + * Gets minimum logging level at which this handler will be triggered. + * + * @return int + */ + public function getLevel() + { + return $this->level; + } + + /** + * Sets the bubbling behavior. + * + * @param bool $bubble true means that this handler allows bubbling. + * false means that bubbling is not permitted. + * @return self + */ + public function setBubble($bubble) + { + $this->bubble = $bubble; + + return $this; + } + + /** + * Gets the bubbling behavior. + * + * @return bool true means that this handler allows bubbling. + * false means that bubbling is not permitted. + */ + public function getBubble() + { + return $this->bubble; + } + + public function __destruct() + { + try { + $this->close(); + } catch (\Exception $e) { + // do nothing + } catch (\Throwable $e) { + // do nothing + } + } + + public function reset() + { + foreach ($this->processors as $processor) { + if ($processor instanceof ResettableInterface) { + $processor->reset(); + } + } + } + + /** + * Gets the default formatter. + * + * @return FormatterInterface + */ + protected function getDefaultFormatter() + { + return new LineFormatter(); + } +} diff --git a/inc/Dependencies/Monolog/Handler/AbstractProcessingHandler.php b/inc/Dependencies/Monolog/Handler/AbstractProcessingHandler.php new file mode 100644 index 0000000000..e852d27033 --- /dev/null +++ b/inc/Dependencies/Monolog/Handler/AbstractProcessingHandler.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace WP_Rocket\Dependencies\Monolog\Handler; + +use WP_Rocket\Dependencies\Monolog\ResettableInterface; + +/** + * Base Handler class providing the Handler structure + * + * Classes extending it should (in most cases) only implement write($record) + * + * @author Jordi Boggiano + * @author Christophe Coevoet + */ +abstract class AbstractProcessingHandler extends AbstractHandler +{ + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if (!$this->isHandling($record)) { + return false; + } + + $record = $this->processRecord($record); + + $record['formatted'] = $this->getFormatter()->format($record); + + $this->write($record); + + return false === $this->bubble; + } + + /** + * Writes the record down to the log of the implementing handler + * + * @param array $record + * @return void + */ + abstract protected function write(array $record); + + /** + * Processes a record. + * + * @param array $record + * @return array + */ + protected function processRecord(array $record) + { + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + return $record; + } +} diff --git a/inc/Dependencies/Monolog/Handler/FormattableHandlerInterface.php b/inc/Dependencies/Monolog/Handler/FormattableHandlerInterface.php new file mode 100644 index 0000000000..3d4cbec619 --- /dev/null +++ b/inc/Dependencies/Monolog/Handler/FormattableHandlerInterface.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace WP_Rocket\Dependencies\Monolog\Handler; + +use WP_Rocket\Dependencies\Monolog\Formatter\FormatterInterface; + +/** + * Interface to describe loggers that have a formatter + * + * This interface is present in monolog 1.x to ease forward compatibility. + * + * @author Jordi Boggiano + */ +interface FormattableHandlerInterface +{ + /** + * Sets the formatter. + * + * @param FormatterInterface $formatter + * @return HandlerInterface self + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface; + + /** + * Gets the formatter. + * + * @return FormatterInterface + */ + public function getFormatter(): FormatterInterface; +} diff --git a/inc/Dependencies/Monolog/Handler/FormattableHandlerTrait.php b/inc/Dependencies/Monolog/Handler/FormattableHandlerTrait.php new file mode 100644 index 0000000000..9f40d25908 --- /dev/null +++ b/inc/Dependencies/Monolog/Handler/FormattableHandlerTrait.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace WP_Rocket\Dependencies\Monolog\Handler; + +use WP_Rocket\Dependencies\Monolog\Formatter\FormatterInterface; +use WP_Rocket\Dependencies\Monolog\Formatter\LineFormatter; + +/** + * Helper trait for implementing FormattableInterface + * + * This trait is present in monolog 1.x to ease forward compatibility. + * + * @author Jordi Boggiano + */ +trait FormattableHandlerTrait +{ + /** + * @var FormatterInterface + */ + protected $formatter; + + /** + * {@inheritdoc} + * @suppress PhanTypeMismatchReturn + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + $this->formatter = $formatter; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getFormatter(): FormatterInterface + { + if (!$this->formatter) { + $this->formatter = $this->getDefaultFormatter(); + } + + return $this->formatter; + } + + /** + * Gets the default formatter. + * + * Overwrite this if the LineFormatter is not a good default for your handler. + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter(); + } +} diff --git a/inc/Dependencies/Monolog/Handler/HandlerInterface.php b/inc/Dependencies/Monolog/Handler/HandlerInterface.php new file mode 100644 index 0000000000..20527d76e8 --- /dev/null +++ b/inc/Dependencies/Monolog/Handler/HandlerInterface.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace WP_Rocket\Dependencies\Monolog\Handler; + +use WP_Rocket\Dependencies\Monolog\Formatter\FormatterInterface; + +/** + * Interface that all WP_Rocket\Dependencies\Monolog Handlers must implement + * + * @author Jordi Boggiano + */ +interface HandlerInterface +{ + /** + * Checks whether the given record will be handled by this handler. + * + * This is mostly done for performance reasons, to avoid calling processors for nothing. + * + * Handlers should still check the record levels within handle(), returning false in isHandling() + * is no guarantee that handle() will not be called, and isHandling() might not be called + * for a given record. + * + * @param array $record Partial log record containing only a level key + * + * @return bool + */ + public function isHandling(array $record); + + /** + * Handles a record. + * + * All records may be passed to this method, and the handler should discard + * those that it does not want to handle. + * + * The return value of this function controls the bubbling process of the handler stack. + * Unless the bubbling is interrupted (by returning true), the Logger class will keep on + * calling further handlers in the stack with a given log record. + * + * @param array $record The record to handle + * @return bool true means that this handler handled the record, and that bubbling is not permitted. + * false means the record was either not processed or that this handler allows bubbling. + */ + public function handle(array $record); + + /** + * Handles a set of records at once. + * + * @param array $records The records to handle (an array of record arrays) + */ + public function handleBatch(array $records); + + /** + * Adds a processor in the stack. + * + * @param callable $callback + * @return self + */ + public function pushProcessor($callback); + + /** + * Removes the processor on top of the stack and returns it. + * + * @return callable + */ + public function popProcessor(); + + /** + * Sets the formatter. + * + * @param FormatterInterface $formatter + * @return self + */ + public function setFormatter(FormatterInterface $formatter); + + /** + * Gets the formatter. + * + * @return FormatterInterface + */ + public function getFormatter(); +} diff --git a/inc/Dependencies/Monolog/Handler/ProcessableHandlerInterface.php b/inc/Dependencies/Monolog/Handler/ProcessableHandlerInterface.php new file mode 100644 index 0000000000..12630d9ebb --- /dev/null +++ b/inc/Dependencies/Monolog/Handler/ProcessableHandlerInterface.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace WP_Rocket\Dependencies\Monolog\Handler; + +use WP_Rocket\Dependencies\Monolog\Processor\ProcessorInterface; + +/** + * Interface to describe loggers that have processors + * + * This interface is present in monolog 1.x to ease forward compatibility. + * + * @author Jordi Boggiano + */ +interface ProcessableHandlerInterface +{ + /** + * Adds a processor in the stack. + * + * @param ProcessorInterface|callable $callback + * @return HandlerInterface self + */ + public function pushProcessor($callback): HandlerInterface; + + /** + * Removes the processor on top of the stack and returns it. + * + * @throws \LogicException In case the processor stack is empty + * @return callable + */ + public function popProcessor(): callable; +} diff --git a/inc/Dependencies/Monolog/Handler/ProcessableHandlerTrait.php b/inc/Dependencies/Monolog/Handler/ProcessableHandlerTrait.php new file mode 100644 index 0000000000..b5223cb6a7 --- /dev/null +++ b/inc/Dependencies/Monolog/Handler/ProcessableHandlerTrait.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace WP_Rocket\Dependencies\Monolog\Handler; + +use WP_Rocket\Dependencies\Monolog\ResettableInterface; + +/** + * Helper trait for implementing ProcessableInterface + * + * This trait is present in monolog 1.x to ease forward compatibility. + * + * @author Jordi Boggiano + */ +trait ProcessableHandlerTrait +{ + /** + * @var callable[] + */ + protected $processors = []; + + /** + * {@inheritdoc} + * @suppress PhanTypeMismatchReturn + */ + public function pushProcessor($callback): HandlerInterface + { + array_unshift($this->processors, $callback); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function popProcessor(): callable + { + if (!$this->processors) { + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + + return array_shift($this->processors); + } + + /** + * Processes a record. + */ + protected function processRecord(array $record): array + { + foreach ($this->processors as $processor) { + $record = $processor($record); + } + + return $record; + } + + protected function resetProcessors(): void + { + foreach ($this->processors as $processor) { + if ($processor instanceof ResettableInterface) { + $processor->reset(); + } + } + } +} diff --git a/inc/Dependencies/Monolog/Handler/StreamHandler.php b/inc/Dependencies/Monolog/Handler/StreamHandler.php new file mode 100644 index 0000000000..1b1ff9c07f --- /dev/null +++ b/inc/Dependencies/Monolog/Handler/StreamHandler.php @@ -0,0 +1,194 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace WP_Rocket\Dependencies\Monolog\Handler; + +use WP_Rocket\Dependencies\Monolog\Logger; +use WP_Rocket\Dependencies\Monolog\Utils; + +/** + * Stores to any stream resource + * + * Can be used to store into php://stderr, remote and local files, etc. + * + * @author Jordi Boggiano + */ +class StreamHandler extends AbstractProcessingHandler +{ + /** @private 512KB */ + const CHUNK_SIZE = 524288; + + /** @var resource|null */ + protected $stream; + protected $url; + private $errorMessage; + protected $filePermission; + protected $useLocking; + private $dirCreated; + + /** + * @param resource|string $stream + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write) + * @param bool $useLocking Try to lock log file before doing any writes + * + * @throws \Exception If a missing directory is not buildable + * @throws \InvalidArgumentException If stream is not a resource or string + */ + public function __construct($stream, $level = Logger::DEBUG, $bubble = true, $filePermission = null, $useLocking = false) + { + parent::__construct($level, $bubble); + if (is_resource($stream)) { + $this->stream = $stream; + $this->streamSetChunkSize(); + } elseif (is_string($stream)) { + $this->url = Utils::canonicalizePath($stream); + } else { + throw new \InvalidArgumentException('A stream must either be a resource or a string.'); + } + + $this->filePermission = $filePermission; + $this->useLocking = $useLocking; + } + + /** + * {@inheritdoc} + */ + public function close() + { + if ($this->url && is_resource($this->stream)) { + fclose($this->stream); + } + $this->stream = null; + $this->dirCreated = null; + } + + /** + * Return the currently active stream if it is open + * + * @return resource|null + */ + public function getStream() + { + return $this->stream; + } + + /** + * Return the stream URL if it was configured with a URL and not an active resource + * + * @return string|null + */ + public function getUrl() + { + return $this->url; + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + if (!is_resource($this->stream)) { + if (null === $this->url || '' === $this->url) { + throw new \LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().'); + } + $this->createDir(); + $this->errorMessage = null; + set_error_handler(array($this, 'customErrorHandler')); + $this->stream = fopen($this->url, 'a'); + if ($this->filePermission !== null) { + @chmod($this->url, $this->filePermission); + } + restore_error_handler(); + if (!is_resource($this->stream)) { + $this->stream = null; + + throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened in append mode: '.$this->errorMessage, $this->url)); + } + $this->streamSetChunkSize(); + } + + if ($this->useLocking) { + // ignoring errors here, there's not much we can do about them + flock($this->stream, LOCK_EX); + } + + $this->streamWrite($this->stream, $record); + + if ($this->useLocking) { + flock($this->stream, LOCK_UN); + } + } + + /** + * Write to stream + * @param resource $stream + * @param array $record + */ + protected function streamWrite($stream, array $record) + { + fwrite($stream, (string) $record['formatted']); + } + + protected function streamSetChunkSize() + { + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + return stream_set_chunk_size($this->stream, self::CHUNK_SIZE); + } + + return false; + } + + private function customErrorHandler($code, $msg) + { + $this->errorMessage = preg_replace('{^(fopen|mkdir)\(.*?\): }', '', $msg); + } + + /** + * @param string $stream + * + * @return null|string + */ + private function getDirFromStream($stream) + { + $pos = strpos($stream, '://'); + if ($pos === false) { + return dirname($stream); + } + + if ('file://' === substr($stream, 0, 7)) { + return dirname(substr($stream, 7)); + } + + return null; + } + + private function createDir() + { + // Do not try to create dir if it has already been tried. + if ($this->dirCreated) { + return; + } + + $dir = $this->getDirFromStream($this->url); + if (null !== $dir && !is_dir($dir)) { + $this->errorMessage = null; + set_error_handler(array($this, 'customErrorHandler')); + $status = mkdir($dir, 0777, true); + restore_error_handler(); + if (false === $status && !is_dir($dir)) { + throw new \UnexpectedValueException(sprintf('There is no existing directory at "%s" and its not buildable: '.$this->errorMessage, $dir)); + } + } + $this->dirCreated = true; + } +} diff --git a/inc/Dependencies/Monolog/Logger.php b/inc/Dependencies/Monolog/Logger.php new file mode 100644 index 0000000000..2903bc73f7 --- /dev/null +++ b/inc/Dependencies/Monolog/Logger.php @@ -0,0 +1,796 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace WP_Rocket\Dependencies\Monolog; + +use WP_Rocket\Dependencies\Monolog\Handler\HandlerInterface; +use WP_Rocket\Dependencies\Monolog\Handler\StreamHandler; +use WP_Rocket\Dependencies\Psr\Log\LoggerInterface; +use WP_Rocket\Dependencies\Psr\Log\InvalidArgumentException; +use Exception; + +/** + * WP_Rocket\Dependencies\Monolog log channel + * + * It contains a stack of Handlers and a stack of Processors, + * and uses them to store records that are added to it. + * + * @author Jordi Boggiano + */ +class Logger implements LoggerInterface, ResettableInterface +{ + /** + * Detailed debug information + */ + const DEBUG = 100; + + /** + * Interesting events + * + * Examples: User logs in, SQL logs. + */ + const INFO = 200; + + /** + * Uncommon events + */ + const NOTICE = 250; + + /** + * Exceptional occurrences that are not errors + * + * Examples: Use of deprecated APIs, poor use of an API, + * undesirable things that are not necessarily wrong. + */ + const WARNING = 300; + + /** + * Runtime errors + */ + const ERROR = 400; + + /** + * Critical conditions + * + * Example: Application component unavailable, unexpected exception. + */ + const CRITICAL = 500; + + /** + * Action must be taken immediately + * + * Example: Entire website down, database unavailable, etc. + * This should trigger the SMS alerts and wake you up. + */ + const ALERT = 550; + + /** + * Urgent alert. + */ + const EMERGENCY = 600; + + /** + * WP_Rocket\Dependencies\Monolog API version + * + * This is only bumped when API breaks are done and should + * follow the major version of the library + * + * @var int + */ + const API = 1; + + /** + * Logging levels from syslog protocol defined in RFC 5424 + * + * @var array $levels Logging levels + */ + protected static $levels = array( + self::DEBUG => 'DEBUG', + self::INFO => 'INFO', + self::NOTICE => 'NOTICE', + self::WARNING => 'WARNING', + self::ERROR => 'ERROR', + self::CRITICAL => 'CRITICAL', + self::ALERT => 'ALERT', + self::EMERGENCY => 'EMERGENCY', + ); + + /** + * @var \DateTimeZone + */ + protected static $timezone; + + /** + * @var string + */ + protected $name; + + /** + * The handler stack + * + * @var HandlerInterface[] + */ + protected $handlers; + + /** + * Processors that will process all log records + * + * To process records of a single handler instead, add the processor on that specific handler + * + * @var callable[] + */ + protected $processors; + + /** + * @var bool + */ + protected $microsecondTimestamps = true; + + /** + * @var callable + */ + protected $exceptionHandler; + + /** + * @param string $name The logging channel + * @param HandlerInterface[] $handlers Optional stack of handlers, the first one in the array is called first, etc. + * @param callable[] $processors Optional array of processors + */ + public function __construct($name, array $handlers = array(), array $processors = array()) + { + $this->name = $name; + $this->setHandlers($handlers); + $this->processors = $processors; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Return a new cloned instance with the name changed + * + * @return static + */ + public function withName($name) + { + $new = clone $this; + $new->name = $name; + + return $new; + } + + /** + * Pushes a handler on to the stack. + * + * @param HandlerInterface $handler + * @return $this + */ + public function pushHandler(HandlerInterface $handler) + { + array_unshift($this->handlers, $handler); + + return $this; + } + + /** + * Pops a handler from the stack + * + * @return HandlerInterface + */ + public function popHandler() + { + if (!$this->handlers) { + throw new \LogicException('You tried to pop from an empty handler stack.'); + } + + return array_shift($this->handlers); + } + + /** + * Set handlers, replacing all existing ones. + * + * If a map is passed, keys will be ignored. + * + * @param HandlerInterface[] $handlers + * @return $this + */ + public function setHandlers(array $handlers) + { + $this->handlers = array(); + foreach (array_reverse($handlers) as $handler) { + $this->pushHandler($handler); + } + + return $this; + } + + /** + * @return HandlerInterface[] + */ + public function getHandlers() + { + return $this->handlers; + } + + /** + * Adds a processor on to the stack. + * + * @param callable $callback + * @return $this + */ + public function pushProcessor($callback) + { + if (!is_callable($callback)) { + throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given'); + } + array_unshift($this->processors, $callback); + + return $this; + } + + /** + * Removes the processor on top of the stack and returns it. + * + * @return callable + */ + public function popProcessor() + { + if (!$this->processors) { + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + + return array_shift($this->processors); + } + + /** + * @return callable[] + */ + public function getProcessors() + { + return $this->processors; + } + + /** + * Control the use of microsecond resolution timestamps in the 'datetime' + * member of new records. + * + * Generating microsecond resolution timestamps by calling + * microtime(true), formatting the result via sprintf() and then parsing + * the resulting string via \DateTime::createFromFormat() can incur + * a measurable runtime overhead vs simple usage of DateTime to capture + * a second resolution timestamp in systems which generate a large number + * of log events. + * + * @param bool $micro True to use microtime() to create timestamps + */ + public function useMicrosecondTimestamps($micro) + { + $this->microsecondTimestamps = (bool) $micro; + } + + /** + * Adds a log record. + * + * @param int $level The logging level + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function addRecord($level, $message, array $context = array()) + { + if (!$this->handlers) { + $this->pushHandler(new StreamHandler('php://stderr', static::DEBUG)); + } + + $levelName = static::getLevelName($level); + + // check if any handler will handle this message so we can return early and save cycles + $handlerKey = null; + reset($this->handlers); + while ($handler = current($this->handlers)) { + if ($handler->isHandling(array('level' => $level))) { + $handlerKey = key($this->handlers); + break; + } + + next($this->handlers); + } + + if (null === $handlerKey) { + return false; + } + + if (!static::$timezone) { + static::$timezone = new \DateTimeZone(date_default_timezone_get() ?: 'UTC'); + } + + // php7.1+ always has microseconds enabled, so we do not need this hack + if ($this->microsecondTimestamps && PHP_VERSION_ID < 70100) { + $ts = \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true)), static::$timezone); + } else { + $ts = new \DateTime('now', static::$timezone); + } + $ts->setTimezone(static::$timezone); + + $record = array( + 'message' => (string) $message, + 'context' => $context, + 'level' => $level, + 'level_name' => $levelName, + 'channel' => $this->name, + 'datetime' => $ts, + 'extra' => array(), + ); + + try { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + + while ($handler = current($this->handlers)) { + if (true === $handler->handle($record)) { + break; + } + + next($this->handlers); + } + } catch (Exception $e) { + $this->handleException($e, $record); + } + + return true; + } + + /** + * Ends a log cycle and frees all resources used by handlers. + * + * Closing a Handler means flushing all buffers and freeing any open resources/handles. + * Handlers that have been closed should be able to accept log records again and re-open + * themselves on demand, but this may not always be possible depending on implementation. + * + * This is useful at the end of a request and will be called automatically on every handler + * when they get destructed. + */ + public function close() + { + foreach ($this->handlers as $handler) { + if (method_exists($handler, 'close')) { + $handler->close(); + } + } + } + + /** + * Ends a log cycle and resets all handlers and processors to their initial state. + * + * Resetting a Handler or a Processor means flushing/cleaning all buffers, resetting internal + * state, and getting it back to a state in which it can receive log records again. + * + * This is useful in case you want to avoid logs leaking between two requests or jobs when you + * have a long running process like a worker or an application server serving multiple requests + * in one process. + */ + public function reset() + { + foreach ($this->handlers as $handler) { + if ($handler instanceof ResettableInterface) { + $handler->reset(); + } + } + + foreach ($this->processors as $processor) { + if ($processor instanceof ResettableInterface) { + $processor->reset(); + } + } + } + + /** + * Adds a log record at the DEBUG level. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function addDebug($message, array $context = array()) + { + return $this->addRecord(static::DEBUG, $message, $context); + } + + /** + * Adds a log record at the INFO level. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function addInfo($message, array $context = array()) + { + return $this->addRecord(static::INFO, $message, $context); + } + + /** + * Adds a log record at the NOTICE level. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function addNotice($message, array $context = array()) + { + return $this->addRecord(static::NOTICE, $message, $context); + } + + /** + * Adds a log record at the WARNING level. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function addWarning($message, array $context = array()) + { + return $this->addRecord(static::WARNING, $message, $context); + } + + /** + * Adds a log record at the ERROR level. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function addError($message, array $context = array()) + { + return $this->addRecord(static::ERROR, $message, $context); + } + + /** + * Adds a log record at the CRITICAL level. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function addCritical($message, array $context = array()) + { + return $this->addRecord(static::CRITICAL, $message, $context); + } + + /** + * Adds a log record at the ALERT level. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function addAlert($message, array $context = array()) + { + return $this->addRecord(static::ALERT, $message, $context); + } + + /** + * Adds a log record at the EMERGENCY level. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function addEmergency($message, array $context = array()) + { + return $this->addRecord(static::EMERGENCY, $message, $context); + } + + /** + * Gets all supported logging levels. + * + * @return array Assoc array with human-readable level names => level codes. + */ + public static function getLevels() + { + return array_flip(static::$levels); + } + + /** + * Gets the name of the logging level. + * + * @param int $level + * @return string + */ + public static function getLevelName($level) + { + if (!isset(static::$levels[$level])) { + throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', array_keys(static::$levels))); + } + + return static::$levels[$level]; + } + + /** + * Converts PSR-3 levels to WP_Rocket\Dependencies\Monolog ones if necessary + * + * @param string|int $level Level number (monolog) or name (PSR-3) + * @return int + */ + public static function toMonologLevel($level) + { + if (is_string($level)) { + // Contains chars of all log levels and avoids using strtoupper() which may have + // strange results depending on locale (for example, "i" will become "İ") + $upper = strtr($level, 'abcdefgilmnortuwy', 'ABCDEFGILMNORTUWY'); + if (defined(__CLASS__.'::'.$upper)) { + return constant(__CLASS__ . '::' . $upper); + } + } + + return $level; + } + + /** + * Checks whether the Logger has a handler that listens on the given level + * + * @param int $level + * @return bool + */ + public function isHandling($level) + { + $record = array( + 'level' => $level, + ); + + foreach ($this->handlers as $handler) { + if ($handler->isHandling($record)) { + return true; + } + } + + return false; + } + + /** + * Set a custom exception handler + * + * @param callable $callback + * @return $this + */ + public function setExceptionHandler($callback) + { + if (!is_callable($callback)) { + throw new \InvalidArgumentException('Exception handler must be valid callable (callback or object with an __invoke method), '.var_export($callback, true).' given'); + } + $this->exceptionHandler = $callback; + + return $this; + } + + /** + * @return callable + */ + public function getExceptionHandler() + { + return $this->exceptionHandler; + } + + /** + * Delegates exception management to the custom exception handler, + * or throws the exception if no custom handler is set. + */ + protected function handleException(Exception $e, array $record) + { + if (!$this->exceptionHandler) { + throw $e; + } + + call_user_func($this->exceptionHandler, $e, $record); + } + + /** + * Adds a log record at an arbitrary level. + * + * This method allows for compatibility with common interfaces. + * + * @param mixed $level The log level + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function log($level, $message, array $context = array()) + { + $level = static::toMonologLevel($level); + + return $this->addRecord($level, $message, $context); + } + + /** + * Adds a log record at the DEBUG level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function debug($message, array $context = array()) + { + return $this->addRecord(static::DEBUG, $message, $context); + } + + /** + * Adds a log record at the INFO level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function info($message, array $context = array()) + { + return $this->addRecord(static::INFO, $message, $context); + } + + /** + * Adds a log record at the NOTICE level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function notice($message, array $context = array()) + { + return $this->addRecord(static::NOTICE, $message, $context); + } + + /** + * Adds a log record at the WARNING level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function warn($message, array $context = array()) + { + return $this->addRecord(static::WARNING, $message, $context); + } + + /** + * Adds a log record at the WARNING level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function warning($message, array $context = array()) + { + return $this->addRecord(static::WARNING, $message, $context); + } + + /** + * Adds a log record at the ERROR level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function err($message, array $context = array()) + { + return $this->addRecord(static::ERROR, $message, $context); + } + + /** + * Adds a log record at the ERROR level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function error($message, array $context = array()) + { + return $this->addRecord(static::ERROR, $message, $context); + } + + /** + * Adds a log record at the CRITICAL level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function crit($message, array $context = array()) + { + return $this->addRecord(static::CRITICAL, $message, $context); + } + + /** + * Adds a log record at the CRITICAL level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function critical($message, array $context = array()) + { + return $this->addRecord(static::CRITICAL, $message, $context); + } + + /** + * Adds a log record at the ALERT level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function alert($message, array $context = array()) + { + return $this->addRecord(static::ALERT, $message, $context); + } + + /** + * Adds a log record at the EMERGENCY level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function emerg($message, array $context = array()) + { + return $this->addRecord(static::EMERGENCY, $message, $context); + } + + /** + * Adds a log record at the EMERGENCY level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return bool Whether the record has been processed + */ + public function emergency($message, array $context = array()) + { + return $this->addRecord(static::EMERGENCY, $message, $context); + } + + /** + * Set the timezone to be used for the timestamp of log records. + * + * This is stored globally for all Logger instances + * + * @param \DateTimeZone $tz Timezone object + */ + public static function setTimezone(\DateTimeZone $tz) + { + self::$timezone = $tz; + } +} diff --git a/inc/Dependencies/Monolog/Processor/IntrospectionProcessor.php b/inc/Dependencies/Monolog/Processor/IntrospectionProcessor.php new file mode 100644 index 0000000000..d91d462182 --- /dev/null +++ b/inc/Dependencies/Monolog/Processor/IntrospectionProcessor.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace WP_Rocket\Dependencies\Monolog\Processor; + +use WP_Rocket\Dependencies\Monolog\Logger; + +/** + * Injects line/file:class/function where the log message came from + * + * Warning: This only works if the handler processes the logs directly. + * If you put the processor on a handler that is behind a FingersCrossedHandler + * for example, the processor will only be called once the trigger level is reached, + * and all the log records will have the same file/line/.. data from the call that + * triggered the FingersCrossedHandler. + * + * @author Jordi Boggiano + */ +class IntrospectionProcessor implements ProcessorInterface +{ + private $level; + + private $skipClassesPartials; + + private $skipStackFramesCount; + + private $skipFunctions = array( + 'call_user_func', + 'call_user_func_array', + ); + + public function __construct($level = Logger::DEBUG, array $skipClassesPartials = array(), $skipStackFramesCount = 0) + { + $this->level = Logger::toMonologLevel($level); + $this->skipClassesPartials = array_merge(array('WP_Rocket\Dependencies\Monolog\\'), $skipClassesPartials); + $this->skipStackFramesCount = $skipStackFramesCount; + } + + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + // return if the level is not high enough + if ($record['level'] < $this->level) { + return $record; + } + + /* + * http://php.net/manual/en/function.debug-backtrace.php + * As of 5.3.6, DEBUG_BACKTRACE_IGNORE_ARGS option was added. + * Any version less than 5.3.6 must use the DEBUG_BACKTRACE_IGNORE_ARGS constant value '2'. + */ + $trace = debug_backtrace((PHP_VERSION_ID < 50306) ? 2 : DEBUG_BACKTRACE_IGNORE_ARGS); + + // skip first since it's always the current method + array_shift($trace); + // the call_user_func call is also skipped + array_shift($trace); + + $i = 0; + + while ($this->isTraceClassOrSkippedFunction($trace, $i)) { + if (isset($trace[$i]['class'])) { + foreach ($this->skipClassesPartials as $part) { + if (strpos($trace[$i]['class'], $part) !== false) { + $i++; + continue 2; + } + } + } elseif (in_array($trace[$i]['function'], $this->skipFunctions)) { + $i++; + continue; + } + + break; + } + + $i += $this->skipStackFramesCount; + + // we should have the call source now + $record['extra'] = array_merge( + $record['extra'], + array( + 'file' => isset($trace[$i - 1]['file']) ? $trace[$i - 1]['file'] : null, + 'line' => isset($trace[$i - 1]['line']) ? $trace[$i - 1]['line'] : null, + 'class' => isset($trace[$i]['class']) ? $trace[$i]['class'] : null, + 'function' => isset($trace[$i]['function']) ? $trace[$i]['function'] : null, + ) + ); + + return $record; + } + + private function isTraceClassOrSkippedFunction(array $trace, $index) + { + if (!isset($trace[$index])) { + return false; + } + + return isset($trace[$index]['class']) || in_array($trace[$index]['function'], $this->skipFunctions); + } +} diff --git a/inc/Dependencies/Monolog/Processor/ProcessorInterface.php b/inc/Dependencies/Monolog/Processor/ProcessorInterface.php new file mode 100644 index 0000000000..597900c88a --- /dev/null +++ b/inc/Dependencies/Monolog/Processor/ProcessorInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace WP_Rocket\Dependencies\Monolog\Processor; + +/** + * An optional interface to allow labelling WP_Rocket\Dependencies\Monolog processors. + * + * @author Nicolas Grekas + */ +interface ProcessorInterface +{ + /** + * @return array The processed records + */ + public function __invoke(array $records); +} diff --git a/inc/Dependencies/Monolog/Registry.php b/inc/Dependencies/Monolog/Registry.php new file mode 100644 index 0000000000..806ee114ac --- /dev/null +++ b/inc/Dependencies/Monolog/Registry.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace WP_Rocket\Dependencies\Monolog; + +use InvalidArgumentException; + +/** + * WP_Rocket\Dependencies\Monolog log registry + * + * Allows to get `Logger` instances in the global scope + * via static method calls on this class. + * + * + * $application = new WP_Rocket\Dependencies\Monolog\Logger('application'); + * $api = new WP_Rocket\Dependencies\Monolog\Logger('api'); + * + * WP_Rocket\Dependencies\Monolog\Registry::addLogger($application); + * WP_Rocket\Dependencies\Monolog\Registry::addLogger($api); + * + * function testLogger() + * { + * WP_Rocket\Dependencies\Monolog\Registry::api()->addError('Sent to $api Logger instance'); + * WP_Rocket\Dependencies\Monolog\Registry::application()->addError('Sent to $application Logger instance'); + * } + * + * + * @author Tomas Tatarko + */ +class Registry +{ + /** + * List of all loggers in the registry (by named indexes) + * + * @var Logger[] + */ + private static $loggers = array(); + + /** + * Adds new logging channel to the registry + * + * @param Logger $logger Instance of the logging channel + * @param string|null $name Name of the logging channel ($logger->getName() by default) + * @param bool $overwrite Overwrite instance in the registry if the given name already exists? + * @throws \InvalidArgumentException If $overwrite set to false and named Logger instance already exists + */ + public static function addLogger(Logger $logger, $name = null, $overwrite = false) + { + $name = $name ?: $logger->getName(); + + if (isset(self::$loggers[$name]) && !$overwrite) { + throw new InvalidArgumentException('Logger with the given name already exists'); + } + + self::$loggers[$name] = $logger; + } + + /** + * Checks if such logging channel exists by name or instance + * + * @param string|Logger $logger Name or logger instance + */ + public static function hasLogger($logger) + { + if ($logger instanceof Logger) { + $index = array_search($logger, self::$loggers, true); + + return false !== $index; + } else { + return isset(self::$loggers[$logger]); + } + } + + /** + * Removes instance from registry by name or instance + * + * @param string|Logger $logger Name or logger instance + */ + public static function removeLogger($logger) + { + if ($logger instanceof Logger) { + if (false !== ($idx = array_search($logger, self::$loggers, true))) { + unset(self::$loggers[$idx]); + } + } else { + unset(self::$loggers[$logger]); + } + } + + /** + * Clears the registry + */ + public static function clear() + { + self::$loggers = array(); + } + + /** + * Gets Logger instance from the registry + * + * @param string $name Name of the requested Logger instance + * @throws \InvalidArgumentException If named Logger instance is not in the registry + * @return Logger Requested instance of Logger + */ + public static function getInstance($name) + { + if (!isset(self::$loggers[$name])) { + throw new InvalidArgumentException(sprintf('Requested "%s" logger instance is not in the registry', $name)); + } + + return self::$loggers[$name]; + } + + /** + * Gets Logger instance from the registry via static method call + * + * @param string $name Name of the requested Logger instance + * @param array $arguments Arguments passed to static method call + * @throws \InvalidArgumentException If named Logger instance is not in the registry + * @return Logger Requested instance of Logger + */ + public static function __callStatic($name, $arguments) + { + return self::getInstance($name); + } +} diff --git a/inc/Dependencies/Monolog/ResettableInterface.php b/inc/Dependencies/Monolog/ResettableInterface.php new file mode 100644 index 0000000000..f91ee124b2 --- /dev/null +++ b/inc/Dependencies/Monolog/ResettableInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace WP_Rocket\Dependencies\Monolog; + +/** + * Handler or Processor implementing this interface will be reset when Logger::reset() is called. + * + * Resetting ends a log cycle gets them back to their initial state. + * + * Resetting a Handler or a Processor means flushing/cleaning all buffers, resetting internal + * state, and getting it back to a state in which it can receive log records again. + * + * This is useful in case you want to avoid logs leaking between two requests or jobs when you + * have a long running process like a worker or an application server serving multiple requests + * in one process. + * + * @author Grégoire Pineau + */ +interface ResettableInterface +{ + public function reset(); +} diff --git a/inc/Dependencies/Monolog/SignalHandler.php b/inc/Dependencies/Monolog/SignalHandler.php new file mode 100644 index 0000000000..8f62c7e990 --- /dev/null +++ b/inc/Dependencies/Monolog/SignalHandler.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 WP_Rocket\Dependencies\Monolog; + +use WP_Rocket\Dependencies\Psr\Log\LoggerInterface; +use WP_Rocket\Dependencies\Psr\Log\LogLevel; +use ReflectionExtension; + +/** + * WP_Rocket\Dependencies\Monolog POSIX signal handler + * + * @author Robert Gust-Bardon + */ +class SignalHandler +{ + private $logger; + + private $previousSignalHandler = array(); + private $signalLevelMap = array(); + private $signalRestartSyscalls = array(); + + public function __construct(LoggerInterface $logger) + { + $this->logger = $logger; + } + + public function registerSignalHandler($signo, $level = LogLevel::CRITICAL, $callPrevious = true, $restartSyscalls = true, $async = true) + { + if (!extension_loaded('pcntl') || !function_exists('pcntl_signal')) { + return $this; + } + + if ($callPrevious) { + if (function_exists('pcntl_signal_get_handler')) { + $handler = pcntl_signal_get_handler($signo); + if ($handler === false) { + return $this; + } + $this->previousSignalHandler[$signo] = $handler; + } else { + $this->previousSignalHandler[$signo] = true; + } + } else { + unset($this->previousSignalHandler[$signo]); + } + $this->signalLevelMap[$signo] = $level; + $this->signalRestartSyscalls[$signo] = $restartSyscalls; + + if (function_exists('pcntl_async_signals') && $async !== null) { + pcntl_async_signals($async); + } + + pcntl_signal($signo, array($this, 'handleSignal'), $restartSyscalls); + + return $this; + } + + public function handleSignal($signo, array $siginfo = null) + { + static $signals = array(); + + if (!$signals && extension_loaded('pcntl')) { + $pcntl = new ReflectionExtension('pcntl'); + $constants = $pcntl->getConstants(); + if (!$constants) { + // HHVM 3.24.2 returns an empty array. + $constants = get_defined_constants(true); + $constants = $constants['Core']; + } + foreach ($constants as $name => $value) { + if (substr($name, 0, 3) === 'SIG' && $name[3] !== '_' && is_int($value)) { + $signals[$value] = $name; + } + } + unset($constants); + } + + $level = isset($this->signalLevelMap[$signo]) ? $this->signalLevelMap[$signo] : LogLevel::CRITICAL; + $signal = isset($signals[$signo]) ? $signals[$signo] : $signo; + $context = isset($siginfo) ? $siginfo : array(); + $this->logger->log($level, sprintf('Program received signal %s', $signal), $context); + + if (!isset($this->previousSignalHandler[$signo])) { + return; + } + + if ($this->previousSignalHandler[$signo] === true || $this->previousSignalHandler[$signo] === SIG_DFL) { + if (extension_loaded('pcntl') && function_exists('pcntl_signal') && function_exists('pcntl_sigprocmask') && function_exists('pcntl_signal_dispatch') + && extension_loaded('posix') && function_exists('posix_getpid') && function_exists('posix_kill')) { + $restartSyscalls = isset($this->signalRestartSyscalls[$signo]) ? $this->signalRestartSyscalls[$signo] : true; + pcntl_signal($signo, SIG_DFL, $restartSyscalls); + pcntl_sigprocmask(SIG_UNBLOCK, array($signo), $oldset); + posix_kill(posix_getpid(), $signo); + pcntl_signal_dispatch(); + pcntl_sigprocmask(SIG_SETMASK, $oldset); + pcntl_signal($signo, array($this, 'handleSignal'), $restartSyscalls); + } + } elseif (is_callable($this->previousSignalHandler[$signo])) { + if (PHP_VERSION_ID >= 70100) { + $this->previousSignalHandler[$signo]($signo, $siginfo); + } else { + $this->previousSignalHandler[$signo]($signo); + } + } + } +} diff --git a/inc/Dependencies/Monolog/Utils.php b/inc/Dependencies/Monolog/Utils.php new file mode 100644 index 0000000000..2542e2ed19 --- /dev/null +++ b/inc/Dependencies/Monolog/Utils.php @@ -0,0 +1,189 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace WP_Rocket\Dependencies\Monolog; + +class Utils +{ + /** + * @internal + */ + public static function getClass($object) + { + $class = \get_class($object); + + return 'c' === $class[0] && 0 === strpos($class, "class@anonymous\0") ? get_parent_class($class).'@anonymous' : $class; + } + + /** + * Makes sure if a relative path is passed in it is turned into an absolute path + * + * @param string $streamUrl stream URL or path without protocol + * + * @return string + */ + public static function canonicalizePath($streamUrl) + { + $prefix = ''; + if ('file://' === substr($streamUrl, 0, 7)) { + $streamUrl = substr($streamUrl, 7); + $prefix = 'file://'; + } + + // other type of stream, not supported + if (false !== strpos($streamUrl, '://')) { + return $streamUrl; + } + + // already absolute + if (substr($streamUrl, 0, 1) === '/' || substr($streamUrl, 1, 1) === ':' || substr($streamUrl, 0, 2) === '\\\\') { + return $prefix.$streamUrl; + } + + $streamUrl = getcwd() . '/' . $streamUrl; + + return $prefix.$streamUrl; + } + + /** + * Return the JSON representation of a value + * + * @param mixed $data + * @param int $encodeFlags flags to pass to json encode, defaults to JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE + * @param bool $ignoreErrors whether to ignore encoding errors or to throw on error, when ignored and the encoding fails, "null" is returned which is valid json for null + * @throws \RuntimeException if encoding fails and errors are not ignored + * @return string + */ + public static function jsonEncode($data, $encodeFlags = null, $ignoreErrors = false) + { + if (null === $encodeFlags && version_compare(PHP_VERSION, '5.4.0', '>=')) { + $encodeFlags = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE; + } + + if ($ignoreErrors) { + $json = @json_encode($data, $encodeFlags); + if (false === $json) { + return 'null'; + } + + return $json; + } + + $json = json_encode($data, $encodeFlags); + if (false === $json) { + $json = self::handleJsonError(json_last_error(), $data); + } + + return $json; + } + + /** + * Handle a json_encode failure. + * + * If the failure is due to invalid string encoding, try to clean the + * input and encode again. If the second encoding attempt fails, the + * inital error is not encoding related or the input can't be cleaned then + * raise a descriptive exception. + * + * @param int $code return code of json_last_error function + * @param mixed $data data that was meant to be encoded + * @param int $encodeFlags flags to pass to json encode, defaults to JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE + * @throws \RuntimeException if failure can't be corrected + * @return string JSON encoded data after error correction + */ + public static function handleJsonError($code, $data, $encodeFlags = null) + { + if ($code !== JSON_ERROR_UTF8) { + self::throwEncodeError($code, $data); + } + + if (is_string($data)) { + self::detectAndCleanUtf8($data); + } elseif (is_array($data)) { + array_walk_recursive($data, array('WP_Rocket\Dependencies\Monolog\Utils', 'detectAndCleanUtf8')); + } else { + self::throwEncodeError($code, $data); + } + + if (null === $encodeFlags && version_compare(PHP_VERSION, '5.4.0', '>=')) { + $encodeFlags = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE; + } + + $json = json_encode($data, $encodeFlags); + + if ($json === false) { + self::throwEncodeError(json_last_error(), $data); + } + + return $json; + } + + /** + * Throws an exception according to a given code with a customized message + * + * @param int $code return code of json_last_error function + * @param mixed $data data that was meant to be encoded + * @throws \RuntimeException + */ + private static function throwEncodeError($code, $data) + { + switch ($code) { + case JSON_ERROR_DEPTH: + $msg = 'Maximum stack depth exceeded'; + break; + case JSON_ERROR_STATE_MISMATCH: + $msg = 'Underflow or the modes mismatch'; + break; + case JSON_ERROR_CTRL_CHAR: + $msg = 'Unexpected control character found'; + break; + case JSON_ERROR_UTF8: + $msg = 'Malformed UTF-8 characters, possibly incorrectly encoded'; + break; + default: + $msg = 'Unknown error'; + } + + throw new \RuntimeException('JSON encoding failed: '.$msg.'. Encoding: '.var_export($data, true)); + } + + /** + * Detect invalid UTF-8 string characters and convert to valid UTF-8. + * + * Valid UTF-8 input will be left unmodified, but strings containing + * invalid UTF-8 codepoints will be reencoded as UTF-8 with an assumed + * original encoding of ISO-8859-15. This conversion may result in + * incorrect output if the actual encoding was not ISO-8859-15, but it + * will be clean UTF-8 output and will not rely on expensive and fragile + * detection algorithms. + * + * Function converts the input in place in the passed variable so that it + * can be used as a callback for array_walk_recursive. + * + * @param mixed $data Input to check and convert if needed, passed by ref + * @private + */ + public static function detectAndCleanUtf8(&$data) + { + if (is_string($data) && !preg_match('//u', $data)) { + $data = preg_replace_callback( + '/[\x80-\xFF]+/', + function ($m) { return utf8_encode($m[0]); }, + $data + ); + $data = str_replace( + array('¤', '¦', '¨', '´', '¸', '¼', '½', '¾'), + array('€', 'Š', 'š', 'Ž', 'ž', 'Œ', 'œ', 'Ÿ'), + $data + ); + } + } +} diff --git a/inc/Dependencies/Psr/Log/AbstractLogger.php b/inc/Dependencies/Psr/Log/AbstractLogger.php new file mode 100644 index 0000000000..ae46968633 --- /dev/null +++ b/inc/Dependencies/Psr/Log/AbstractLogger.php @@ -0,0 +1,128 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } +} diff --git a/inc/Dependencies/Psr/Log/InvalidArgumentException.php b/inc/Dependencies/Psr/Log/InvalidArgumentException.php new file mode 100644 index 0000000000..559e424ca1 --- /dev/null +++ b/inc/Dependencies/Psr/Log/InvalidArgumentException.php @@ -0,0 +1,7 @@ +logger = $logger; + } +} diff --git a/inc/Dependencies/Psr/Log/LoggerInterface.php b/inc/Dependencies/Psr/Log/LoggerInterface.php new file mode 100644 index 0000000000..f150312453 --- /dev/null +++ b/inc/Dependencies/Psr/Log/LoggerInterface.php @@ -0,0 +1,125 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } + + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + * + * @throws \WP_Rocket\Dependencies\Psr\Log\InvalidArgumentException + */ + abstract public function log($level, $message, array $context = array()); +} diff --git a/inc/Dependencies/Psr/Log/NullLogger.php b/inc/Dependencies/Psr/Log/NullLogger.php new file mode 100644 index 0000000000..bdea7eb158 --- /dev/null +++ b/inc/Dependencies/Psr/Log/NullLogger.php @@ -0,0 +1,30 @@ +logger) { }` + * blocks. + */ +class NullLogger extends AbstractLogger +{ + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + * + * @throws \WP_Rocket\Dependencies\Psr\Log\InvalidArgumentException + */ + public function log($level, $message, array $context = array()) + { + // noop + } +} diff --git a/inc/Engine/Cache/AdminSubscriber.php b/inc/Engine/Cache/AdminSubscriber.php index ff7368e1ce..16feec5874 100644 --- a/inc/Engine/Cache/AdminSubscriber.php +++ b/inc/Engine/Cache/AdminSubscriber.php @@ -72,6 +72,7 @@ public static function get_subscribed_events() { ], "update_option_{$slug}" => [ 'maybe_set_wp_cache', 12 ], 'site_status_tests' => 'add_wp_cache_status_test', + 'wp_rocket_upgrade' => [ 'on_update', 10, 2 ], 'rocket_domain_changed' => [ [ 'regenerate_configs' ], [ 'delete_old_configs' ], @@ -214,9 +215,9 @@ public function delete_old_configs() { foreach ( $contents as $content ) { $content = WP_ROCKET_CONFIG_PATH . $content['name']; if ( ! preg_match( '#\.php$#', $content ) || ! $this->filesystem->is_file( $content ) || in_array( - $content, + $content, $configs, - true + true ) ) { continue; } @@ -251,4 +252,19 @@ protected function generate_config_path() { public function clear_cache( string $current_url, string $old_url ) { rocket_clean_files( [ $old_url, $current_url ], null, false ); } + + /** + * Regenerate the advanced cache file on update + * + * @param string $new_version New plugin version. + * @param string $old_version Previous plugin version. + * + * @return void + */ + public function on_update( $new_version, $old_version ) { + if ( version_compare( $old_version, '3.15', '>=' ) ) { + return; + } + rocket_generate_advanced_cache_file(); + } } diff --git a/inc/Logger/HTMLFormatter.php b/inc/Logger/HTMLFormatter.php new file mode 100644 index 0000000000..22ef7f9287 --- /dev/null +++ b/inc/Logger/HTMLFormatter.php @@ -0,0 +1,58 @@ +addTitle( $record['level_name'], $record['level'] ); + $output .= ''; + + $output .= $this->addRow( 'Message', (string) $record['message'] ); + $output .= $this->addRow( 'Time', $record['datetime']->format( $this->dateFormat ) ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + + if ( $record['context'] ) { + $embedded_table = '
'; + + foreach ( $record['context'] as $key => $value ) { + $embedded_table .= $this->addRow( $key, $this->convertToString( $value ) ); + } + + $embedded_table .= '
'; + $output .= $this->addRow( 'Context', $embedded_table, false ); + } + + if ( $record['extra'] ) { + $embedded_table = ''; + + foreach ( $record['extra'] as $key => $value ) { + $embedded_table .= $this->addRow( $key, $this->convertToString( $value ) ); + } + + $embedded_table .= '
'; + $output .= $this->addRow( 'Extra', $embedded_table, false ); + } + + return $output . ''; + } +} diff --git a/inc/Logger/Logger.php b/inc/Logger/Logger.php new file mode 100644 index 0000000000..ce3eef6848 --- /dev/null +++ b/inc/Logger/Logger.php @@ -0,0 +1,563 @@ +debug( $message, $context ) : null; + } + + /** + * Adds a log record at the INFO level. + * + * @since 3.1.4 + * @access public + * @author Grégory Viguier + * + * @param string $message The log message. + * @param array $context The log context. + * @return bool|null Whether the record has been processed. + */ + public static function info( $message, array $context = [] ) { + return static::debug_enabled() ? static::get_logger()->info( $message, $context ) : null; + } + + /** + * Adds a log record at the NOTICE level. + * + * @since 3.1.4 + * @access public + * @author Grégory Viguier + * + * @param string $message The log message. + * @param array $context The log context. + * @return bool|null Whether the record has been processed. + */ + public static function notice( $message, array $context = [] ) { + return static::debug_enabled() ? static::get_logger()->notice( $message, $context ) : null; + } + + /** + * Adds a log record at the WARNING level. + * + * @since 3.1.4 + * @access public + * @author Grégory Viguier + * + * @param string $message The log message. + * @param array $context The log context. + * @return bool|null Whether the record has been processed. + */ + public static function warning( $message, array $context = [] ) { + return static::debug_enabled() ? static::get_logger()->warning( $message, $context ) : null; + } + + /** + * Adds a log record at the ERROR level. + * + * @since 3.1.4 + * @access public + * @author Grégory Viguier + * + * @param string $message The log message. + * @param array $context The log context. + * @return bool|null Whether the record has been processed. + */ + public static function error( $message, array $context = [] ) { + return static::debug_enabled() ? static::get_logger()->error( $message, $context ) : null; + } + + /** + * Adds a log record at the CRITICAL level. + * + * @since 3.1.4 + * @access public + * @author Grégory Viguier + * + * @param string $message The log message. + * @param array $context The log context. + * @return bool|null Whether the record has been processed. + */ + public static function critical( $message, array $context = [] ) { + return static::debug_enabled() ? static::get_logger()->critical( $message, $context ) : null; + } + + /** + * Adds a log record at the ALERT level. + * + * @since 3.1.4 + * @access public + * @author Grégory Viguier + * + * @param string $message The log message. + * @param array $context The log context. + * @return bool|null Whether the record has been processed. + */ + public static function alert( $message, array $context = [] ) { + return static::debug_enabled() ? static::get_logger()->alert( $message, $context ) : null; + } + + /** + * Adds a log record at the EMERGENCY level. + * + * @since 3.1.4 + * @access public + * @author Grégory Viguier + * + * @param string $message The log message. + * @param array $context The log context. + * @return bool|null Whether the record has been processed. + */ + public static function emergency( $message, array $context = [] ) { + return static::debug_enabled() ? static::get_logger()->emergency( $message, $context ) : null; + } + + /** + * Get the logger instance. + * + * @since 3.1.4 + * @access public + * @author Grégory Viguier + * + * @return Logger A Logger instance. + */ + public static function get_logger() { + $logger_name = static::LOGGER_NAME; + $log_level = Monologger::DEBUG; + + if ( Registry::hasLogger( $logger_name ) ) { + return Registry::$logger_name(); + } + + /** + * File handler. + * HTML formatter is used. + */ + $handler = new StreamHandler( static::get_log_file_path(), $log_level ); + $formatter = new HtmlFormatter(); + + $handler->setFormatter( $formatter ); + + /** + * Thanks to the processors, add data to each log: + * - `debug_backtrace()` (exclude this class and Abstract_Buffer). + */ + $trace_processor = new IntrospectionProcessor( $log_level, [ get_called_class(), 'Abstract_Buffer' ] ); + + // Create the logger. + $logger = new Monologger( $logger_name, [ $handler ], [ $trace_processor ] ); + + // Store the logger. + Registry::addLogger( $logger ); + + return $logger; + } + + + /** ----------------------------------------------------------------------------------------- */ + /** LOG FILE ================================================================================ */ + /** ----------------------------------------------------------------------------------------- */ + + /** + * Get the path to the log file. + * + * @since 3.1.4 + * @access public + * @author Grégory Viguier + * + * @return string + */ + public static function get_log_file_path() { + if ( defined( 'WP_ROCKET_DEBUG_LOG_FILE' ) && WP_ROCKET_DEBUG_LOG_FILE && is_string( WP_ROCKET_DEBUG_LOG_FILE ) ) { + // Make sure the file uses a ".log" extension. + return preg_replace( '/\.[^.]*$/', '', WP_ROCKET_DEBUG_LOG_FILE ) . '.log'; + } + + if ( defined( 'WP_ROCKET_DEBUG_INTERVAL' ) ) { + // Adds an optional logs rotator depending on a constant value - WP_ROCKET_DEBUG_INTERVAL (interval by minutes). + $rotator = str_pad( round( ( strtotime( 'now' ) - strtotime( 'today midnight' ) ) / 60 / WP_ROCKET_DEBUG_INTERVAL ), 4, '0', STR_PAD_LEFT ); + return WP_CONTENT_DIR . '/wp-rocket-config/' . $rotator . '-' . static::LOG_FILE_NAME; + } else { + return WP_CONTENT_DIR . '/wp-rocket-config/' . static::LOG_FILE_NAME; + } + } + + /** + * Get the log file contents. + * + * @since 3.1.4 + * @access public + * @author Grégory Viguier + * + * @return string|object The file contents on success. A WP_Error object on failure. + */ + public static function get_log_file_contents() { + $filesystem = \rocket_direct_filesystem(); + $file_path = static::get_log_file_path(); + + if ( ! $filesystem->exists( $file_path ) ) { + return new \WP_Error( 'no_file', __( 'The log file does not exist.', 'rocket' ) ); + } + + $contents = $filesystem->get_contents( $file_path ); + + if ( false === $contents ) { + return new \WP_Error( 'file_not_read', __( 'The log file could not be read.', 'rocket' ) ); + } + + return $contents; + } + + /** + * Get the log file size and number of entries. + * + * @since 3.1.4 + * @access public + * @author Grégory Viguier + * + * @return array|object An array of statistics on success. A WP_Error object on failure. + */ + public static function get_log_file_stats() { + $formatter = static::get_stream_formatter(); + + if ( ! $formatter ) { + return new \WP_Error( 'no_stream_formatter', __( 'The logs are not saved into a file.', 'rocket' ) ); + } + + $filesystem = \rocket_direct_filesystem(); + $file_path = static::get_log_file_path(); + + if ( ! $filesystem->exists( $file_path ) ) { + return new \WP_Error( 'no_file', __( 'The log file does not exist.', 'rocket' ) ); + } + + $contents = $filesystem->get_contents( $file_path ); + + if ( false === $contents ) { + return new \WP_Error( 'file_not_read', __( 'The log file could not be read.', 'rocket' ) ); + } + + if ( $formatter instanceof HtmlFormatter ) { + $entries = preg_split( '@

size( $file_path ); + $decimals = $bytes > pow( 1024, 3 ) ? 1 : 0; + $bytes = @size_format( $bytes, $decimals ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged + $bytes = str_replace( ' ', ' ', $bytes ); // Non-breaking space character. + + return compact( 'entries', 'bytes' ); + } + + /** + * Get the log file extension related to the formatter in use. This can be used when the file is downloaded. + * + * @since 3.1.4 + * @access public + * @author Grégory Viguier + * + * @return string The corresponding file extension with the heading dot. + */ + public static function get_log_file_extension() { + $formatter = static::get_stream_formatter(); + + if ( ! $formatter ) { + return '.log'; + } + + if ( $formatter instanceof HtmlFormatter ) { + return '.html'; + } + + if ( $formatter instanceof LineFormatter ) { + return '.txt'; + } + + return '.log'; + } + + /** + * Delete the log file. + * + * @since 3.1.4 + * @access public + * @author Grégory Viguier + * + * @return bool True on success. False on failure. + */ + public static function delete_log_file() { + $filesystem = \rocket_direct_filesystem(); + $file_path = static::get_log_file_path(); + + if ( ! $filesystem->exists( $file_path ) ) { + return true; + } + + $filesystem->put_contents( $file_path, '' ); + $filesystem->delete( $file_path, false, 'f' ); + + return ! $filesystem->exists( $file_path ); + } + + /** + * Get the handler used for the log file. + * + * @since 3.2 + * @access public + * @author Grégory Viguier + * + * @return object|bool The formatter object on success. False on failure. + */ + public static function get_stream_handler() { + $handlers = static::get_logger()->getHandlers(); + + if ( ! $handlers ) { + return false; + } + + foreach ( $handlers as $_handler ) { + if ( $_handler instanceof MonoStreamHandler ) { + $handler = $_handler; + break; + } + } + + if ( empty( $handler ) ) { + return false; + } + + return $handler; + } + + /** + * Get the formatter used for the log file. + * + * @since 3.1.4 + * @access public + * @author Grégory Viguier + * + * @return object|bool The formatter object on success. False on failure. + */ + public static function get_stream_formatter() { + $handler = static::get_stream_handler(); + + if ( empty( $handler ) ) { + return false; + } + + return $handler->getFormatter(); + } + + + /** ----------------------------------------------------------------------------------------- */ + /** CONSTANT ================================================================================ */ + /** ----------------------------------------------------------------------------------------- */ + + /** + * Tell if debug is enabled. + * + * @since 3.1.4 + * @access public + * @author Grégory Viguier + * + * @return bool + */ + public static function debug_enabled() { + return defined( 'WP_ROCKET_DEBUG' ) && WP_ROCKET_DEBUG; + } + + /** + * Enable debug mode by adding a constant in the `wp-config.php` file. + * + * @since 3.1.4 + * @access public + * @author Grégory Viguier + */ + public static function enable_debug() { + static::define_debug( true ); + } + + /** + * Disable debug mode by removing the constant in the `wp-config.php` file. + * + * @since 3.1.4 + * @access public + * @author Grégory Viguier + */ + public static function disable_debug() { + static::define_debug( false ); + } + + /** + * Enable or disable debug mode by adding or removing a constant in the `wp-config.php` file. + * + * @since 3.1.4 + * @access public + * @author Grégory Viguier + * + * @param bool $enable True to enable debug, false to disable. + */ + public static function define_debug( $enable ) { + if ( $enable && static::debug_enabled() ) { + // Debug is already enabled. + return; + } + + if ( ! $enable && ! static::debug_enabled() ) { + // Debug is already disabled. + return; + } + + // Get the path to the file. + $file_path = \rocket_find_wpconfig_path(); + + if ( ! $file_path ) { + // Couldn't get the path to the file. + return; + } + + // Get the content of the file. + $filesystem = \rocket_direct_filesystem(); + $content = $filesystem->get_contents( $file_path ); + + if ( false === $content ) { + // Cound't get the content of the file. + return; + } + + // Remove previous value. + $placeholder = '## WP_ROCKET_DEBUG placeholder ##'; + $content = preg_replace( '@^[\t ]*define\s*\(\s*["\']WP_ROCKET_DEBUG["\'].*$@miU', $placeholder, $content ); + $content = preg_replace( "@\n$placeholder@", '', $content ); + + if ( $enable ) { + // Add the constant. + $define = "define( 'WP_ROCKET_DEBUG', true ); // Added by WP Rocket.\r\n"; + $content = preg_replace( '@<\?php\s*@i', "put_contents( $file_path, $content, $chmod ); + } + + + /** ----------------------------------------------------------------------------------------- */ + /** TOOLS =================================================================================== */ + /** ----------------------------------------------------------------------------------------- */ + + /** + * Get the thread identifier. + * + * @since 3.3 + * @access public + * @author Grégory Viguier + * + * @return string + */ + public static function get_thread_id() { + if ( ! isset( self::$thread_id ) ) { + self::$thread_id = uniqid( '', true ); + } + + return self::$thread_id; + } + + /** + * Remove cookies related to WP auth. + * + * @since 3.1.4 + * @access public + * @author Grégory Viguier + * + * @param array $cookies An array of cookies. + * @return array + */ + public static function remove_auth_cookies( $cookies = [] ) { + if ( ! $cookies || ! is_array( $cookies ) ) { + $cookies = $_COOKIE; + } + + unset( $cookies['wordpress_test_cookie'] ); + + if ( ! $cookies ) { + return []; + } + + $pattern = strtolower( '@^WordPress(?:user|pass|_sec|_logged_in)?_@' ); // Trolling PHPCS. + + foreach ( $cookies as $cookie_name => $value ) { + if ( preg_match( $pattern, $cookie_name ) ) { + $cookies[ $cookie_name ] = 'Value removed by WP Rocket.'; + } + } + + return $cookies; + } +} diff --git a/inc/Logger/StreamHandler.php b/inc/Logger/StreamHandler.php new file mode 100644 index 0000000000..1359a53ae8 --- /dev/null +++ b/inc/Logger/StreamHandler.php @@ -0,0 +1,147 @@ +create_htaccess_file(); + } + + /** + * Create a .htaccess file in the log folder, to prevent direct access and directory listing. + * + * @since 3.2 + * + * @throws \UnexpectedValueException When the .htaccess file could not be created. + * + * @return bool True if the file exists or has been created. False on failure. + */ + public function create_htaccess_file() { + if ( $this->htaccess_exists ) { + return true; + } + + if ( $this->has_error ) { + return false; + } + + $dir = $this->get_dir_from_stream( $this->url ); + + if ( ! $dir || ! is_dir( $dir ) ) { + $this->has_error = true; + return false; + } + + $file_path = $dir . '/.htaccess'; + + if ( file_exists( $file_path ) ) { + $this->htaccess_exists = true; + return true; + } + + $this->error_message = null; + + set_error_handler( [ $this, 'custom_error_handler' ] ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler + + $file_resource = fopen( $file_path, 'a' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fopen + + restore_error_handler(); + + if ( ! is_resource( $file_resource ) ) { + $this->has_error = true; + throw new UnexpectedValueException( sprintf( 'The file "%s" could not be opened: ' . $this->error_message, $file_path ) ); + } + + $new_content = "\nOrder allow,deny\nDeny from all\n\nOptions -Indexes"; + + fwrite( $file_resource, $new_content ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fwrite + fclose( $file_resource ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose + @chmod( $file_path, 0644 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged + + $this->htaccess_exists = true; + + return true; + } + + /** + * Temporary error handler that "cleans" the error messages. + * + * @since 3.2 + * + * @see parent::customErrorHandler() + * + * @param int $code Error code. + * @param string $msg Error message. + */ + private function custom_error_handler( int $code, string $msg ) { + $this->error_message = preg_replace( '{^(fopen|mkdir)\(.*?\): }', '', $msg ); + } + + /** + * A dirname() that also works for streams, by removing the protocol. + * + * @since 3.2 + * + * @see parent::getDirFromStream() + * + * @param string $stream Path to a file. + * + * @return null|string + */ + private function get_dir_from_stream( string $stream ) { + $pos = strpos( $stream, '://' ); + + if ( false === $pos ) { + return dirname( $stream ); + } + + if ( 'file://' === substr( $stream, 0, 7 ) ) { + return dirname( substr( $stream, 7 ) ); + } + } +} diff --git a/inc/classes/logger/class-html-formatter.php b/inc/classes/logger/class-html-formatter.php index 8652c9cc40..250bd4c04d 100644 --- a/inc/classes/logger/class-html-formatter.php +++ b/inc/classes/logger/class-html-formatter.php @@ -1,58 +1,4 @@ addTitle( $record['level_name'], $record['level'] ); - $output .= ''; - - $output .= $this->addRow( 'Message', (string) $record['message'] ); - $output .= $this->addRow( 'Time', $record['datetime']->format( $this->dateFormat ) ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase - - if ( $record['context'] ) { - $embedded_table = '
'; - - foreach ( $record['context'] as $key => $value ) { - $embedded_table .= $this->addRow( $key, $this->convertToString( $value ) ); - } - - $embedded_table .= '
'; - $output .= $this->addRow( 'Context', $embedded_table, false ); - } - - if ( $record['extra'] ) { - $embedded_table = ''; - - foreach ( $record['extra'] as $key => $value ) { - $embedded_table .= $this->addRow( $key, $this->convertToString( $value ) ); - } - - $embedded_table .= '
'; - $output .= $this->addRow( 'Extra', $embedded_table, false ); - } - - return $output . ''; - } +if ( isset( $rocket_path ) ) { + require_once $rocket_path . 'inc/Logger/HTMLFormatter.php'; } diff --git a/inc/classes/logger/class-logger.php b/inc/classes/logger/class-logger.php index 3fc1f178d3..25cbd426fc 100644 --- a/inc/classes/logger/class-logger.php +++ b/inc/classes/logger/class-logger.php @@ -1,563 +1,4 @@ debug( $message, $context ) : null; - } - - /** - * Adds a log record at the INFO level. - * - * @since 3.1.4 - * @access public - * @author Grégory Viguier - * - * @param string $message The log message. - * @param array $context The log context. - * @return bool|null Whether the record has been processed. - */ - public static function info( $message, array $context = [] ) { - return static::debug_enabled() ? static::get_logger()->info( $message, $context ) : null; - } - - /** - * Adds a log record at the NOTICE level. - * - * @since 3.1.4 - * @access public - * @author Grégory Viguier - * - * @param string $message The log message. - * @param array $context The log context. - * @return bool|null Whether the record has been processed. - */ - public static function notice( $message, array $context = [] ) { - return static::debug_enabled() ? static::get_logger()->notice( $message, $context ) : null; - } - - /** - * Adds a log record at the WARNING level. - * - * @since 3.1.4 - * @access public - * @author Grégory Viguier - * - * @param string $message The log message. - * @param array $context The log context. - * @return bool|null Whether the record has been processed. - */ - public static function warning( $message, array $context = [] ) { - return static::debug_enabled() ? static::get_logger()->warning( $message, $context ) : null; - } - - /** - * Adds a log record at the ERROR level. - * - * @since 3.1.4 - * @access public - * @author Grégory Viguier - * - * @param string $message The log message. - * @param array $context The log context. - * @return bool|null Whether the record has been processed. - */ - public static function error( $message, array $context = [] ) { - return static::debug_enabled() ? static::get_logger()->error( $message, $context ) : null; - } - - /** - * Adds a log record at the CRITICAL level. - * - * @since 3.1.4 - * @access public - * @author Grégory Viguier - * - * @param string $message The log message. - * @param array $context The log context. - * @return bool|null Whether the record has been processed. - */ - public static function critical( $message, array $context = [] ) { - return static::debug_enabled() ? static::get_logger()->critical( $message, $context ) : null; - } - - /** - * Adds a log record at the ALERT level. - * - * @since 3.1.4 - * @access public - * @author Grégory Viguier - * - * @param string $message The log message. - * @param array $context The log context. - * @return bool|null Whether the record has been processed. - */ - public static function alert( $message, array $context = [] ) { - return static::debug_enabled() ? static::get_logger()->alert( $message, $context ) : null; - } - - /** - * Adds a log record at the EMERGENCY level. - * - * @since 3.1.4 - * @access public - * @author Grégory Viguier - * - * @param string $message The log message. - * @param array $context The log context. - * @return bool|null Whether the record has been processed. - */ - public static function emergency( $message, array $context = [] ) { - return static::debug_enabled() ? static::get_logger()->emergency( $message, $context ) : null; - } - - /** - * Get the logger instance. - * - * @since 3.1.4 - * @access public - * @author Grégory Viguier - * - * @return Logger A Logger instance. - */ - public static function get_logger() { - $logger_name = static::LOGGER_NAME; - $log_level = Monologger::DEBUG; - - if ( Registry::hasLogger( $logger_name ) ) { - return Registry::$logger_name(); - } - - /** - * File handler. - * HTML formatter is used. - */ - $handler = new StreamHandler( static::get_log_file_path(), $log_level ); - $formatter = new HtmlFormatter(); - - $handler->setFormatter( $formatter ); - - /** - * Thanks to the processors, add data to each log: - * - `debug_backtrace()` (exclude this class and Abstract_Buffer). - */ - $trace_processor = new IntrospectionProcessor( $log_level, [ get_called_class(), 'Abstract_Buffer' ] ); - - // Create the logger. - $logger = new Monologger( $logger_name, [ $handler ], [ $trace_processor ] ); - - // Store the logger. - Registry::addLogger( $logger ); - - return $logger; - } - - - /** ----------------------------------------------------------------------------------------- */ - /** LOG FILE ================================================================================ */ - /** ----------------------------------------------------------------------------------------- */ - - /** - * Get the path to the log file. - * - * @since 3.1.4 - * @access public - * @author Grégory Viguier - * - * @return string - */ - public static function get_log_file_path() { - if ( defined( 'WP_ROCKET_DEBUG_LOG_FILE' ) && WP_ROCKET_DEBUG_LOG_FILE && is_string( WP_ROCKET_DEBUG_LOG_FILE ) ) { - // Make sure the file uses a ".log" extension. - return preg_replace( '/\.[^.]*$/', '', WP_ROCKET_DEBUG_LOG_FILE ) . '.log'; - } - - if ( defined( 'WP_ROCKET_DEBUG_INTERVAL' ) ) { - // Adds an optional logs rotator depending on a constant value - WP_ROCKET_DEBUG_INTERVAL (interval by minutes). - $rotator = str_pad( round( ( strtotime( 'now' ) - strtotime( 'today midnight' ) ) / 60 / WP_ROCKET_DEBUG_INTERVAL ), 4, '0', STR_PAD_LEFT ); - return WP_CONTENT_DIR . '/wp-rocket-config/' . $rotator . '-' . static::LOG_FILE_NAME; - } else { - return WP_CONTENT_DIR . '/wp-rocket-config/' . static::LOG_FILE_NAME; - } - } - - /** - * Get the log file contents. - * - * @since 3.1.4 - * @access public - * @author Grégory Viguier - * - * @return string|object The file contents on success. A WP_Error object on failure. - */ - public static function get_log_file_contents() { - $filesystem = \rocket_direct_filesystem(); - $file_path = static::get_log_file_path(); - - if ( ! $filesystem->exists( $file_path ) ) { - return new \WP_Error( 'no_file', __( 'The log file does not exist.', 'rocket' ) ); - } - - $contents = $filesystem->get_contents( $file_path ); - - if ( false === $contents ) { - return new \WP_Error( 'file_not_read', __( 'The log file could not be read.', 'rocket' ) ); - } - - return $contents; - } - - /** - * Get the log file size and number of entries. - * - * @since 3.1.4 - * @access public - * @author Grégory Viguier - * - * @return array|object An array of statistics on success. A WP_Error object on failure. - */ - public static function get_log_file_stats() { - $formatter = static::get_stream_formatter(); - - if ( ! $formatter ) { - return new \WP_Error( 'no_stream_formatter', __( 'The logs are not saved into a file.', 'rocket' ) ); - } - - $filesystem = \rocket_direct_filesystem(); - $file_path = static::get_log_file_path(); - - if ( ! $filesystem->exists( $file_path ) ) { - return new \WP_Error( 'no_file', __( 'The log file does not exist.', 'rocket' ) ); - } - - $contents = $filesystem->get_contents( $file_path ); - - if ( false === $contents ) { - return new \WP_Error( 'file_not_read', __( 'The log file could not be read.', 'rocket' ) ); - } - - if ( $formatter instanceof HtmlFormatter ) { - $entries = preg_split( '@

size( $file_path ); - $decimals = $bytes > pow( 1024, 3 ) ? 1 : 0; - $bytes = @size_format( $bytes, $decimals ); - $bytes = str_replace( ' ', ' ', $bytes ); // Non-breaking space character. - - return compact( 'entries', 'bytes' ); - } - - /** - * Get the log file extension related to the formatter in use. This can be used when the file is downloaded. - * - * @since 3.1.4 - * @access public - * @author Grégory Viguier - * - * @return string The corresponding file extension with the heading dot. - */ - public static function get_log_file_extension() { - $formatter = static::get_stream_formatter(); - - if ( ! $formatter ) { - return '.log'; - } - - if ( $formatter instanceof HtmlFormatter ) { - return '.html'; - } - - if ( $formatter instanceof LineFormatter ) { - return '.txt'; - } - - return '.log'; - } - - /** - * Delete the log file. - * - * @since 3.1.4 - * @access public - * @author Grégory Viguier - * - * @return bool True on success. False on failure. - */ - public static function delete_log_file() { - $filesystem = \rocket_direct_filesystem(); - $file_path = static::get_log_file_path(); - - if ( ! $filesystem->exists( $file_path ) ) { - return true; - } - - $filesystem->put_contents( $file_path, '' ); - $filesystem->delete( $file_path, false, 'f' ); - - return ! $filesystem->exists( $file_path ); - } - - /** - * Get the handler used for the log file. - * - * @since 3.2 - * @access public - * @author Grégory Viguier - * - * @return object|bool The formatter object on success. False on failure. - */ - public static function get_stream_handler() { - $handlers = static::get_logger()->getHandlers(); - - if ( ! $handlers ) { - return false; - } - - foreach ( $handlers as $_handler ) { - if ( $_handler instanceof MonoStreamHandler ) { - $handler = $_handler; - break; - } - } - - if ( empty( $handler ) ) { - return false; - } - - return $handler; - } - - /** - * Get the formatter used for the log file. - * - * @since 3.1.4 - * @access public - * @author Grégory Viguier - * - * @return object|bool The formatter object on success. False on failure. - */ - public static function get_stream_formatter() { - $handler = static::get_stream_handler(); - - if ( empty( $handler ) ) { - return false; - } - - return $handler->getFormatter(); - } - - - /** ----------------------------------------------------------------------------------------- */ - /** CONSTANT ================================================================================ */ - /** ----------------------------------------------------------------------------------------- */ - - /** - * Tell if debug is enabled. - * - * @since 3.1.4 - * @access public - * @author Grégory Viguier - * - * @return bool - */ - public static function debug_enabled() { - return defined( 'WP_ROCKET_DEBUG' ) && WP_ROCKET_DEBUG; - } - - /** - * Enable debug mode by adding a constant in the `wp-config.php` file. - * - * @since 3.1.4 - * @access public - * @author Grégory Viguier - */ - public static function enable_debug() { - static::define_debug( true ); - } - - /** - * Disable debug mode by removing the constant in the `wp-config.php` file. - * - * @since 3.1.4 - * @access public - * @author Grégory Viguier - */ - public static function disable_debug() { - static::define_debug( false ); - } - - /** - * Enable or disable debug mode by adding or removing a constant in the `wp-config.php` file. - * - * @since 3.1.4 - * @access public - * @author Grégory Viguier - * - * @param bool $enable True to enable debug, false to disable. - */ - public static function define_debug( $enable ) { - if ( $enable && static::debug_enabled() ) { - // Debug is already enabled. - return; - } - - if ( ! $enable && ! static::debug_enabled() ) { - // Debug is already disabled. - return; - } - - // Get the path to the file. - $file_path = \rocket_find_wpconfig_path(); - - if ( ! $file_path ) { - // Couldn't get the path to the file. - return; - } - - // Get the content of the file. - $filesystem = \rocket_direct_filesystem(); - $content = $filesystem->get_contents( $file_path ); - - if ( false === $content ) { - // Cound't get the content of the file. - return; - } - - // Remove previous value. - $placeholder = '## WP_ROCKET_DEBUG placeholder ##'; - $content = preg_replace( '@^[\t ]*define\s*\(\s*["\']WP_ROCKET_DEBUG["\'].*$@miU', $placeholder, $content ); - $content = preg_replace( "@\n$placeholder@", '', $content ); - - if ( $enable ) { - // Add the constant. - $define = "define( 'WP_ROCKET_DEBUG', true ); // Added by WP Rocket.\r\n"; - $content = preg_replace( '@<\?php\s*@i', "put_contents( $file_path, $content, $chmod ); - } - - - /** ----------------------------------------------------------------------------------------- */ - /** TOOLS =================================================================================== */ - /** ----------------------------------------------------------------------------------------- */ - - /** - * Get the thread identifier. - * - * @since 3.3 - * @access public - * @author Grégory Viguier - * - * @return string - */ - public static function get_thread_id() { - if ( ! isset( self::$thread_id ) ) { - self::$thread_id = uniqid( '', true ); - } - - return self::$thread_id; - } - - /** - * Remove cookies related to WP auth. - * - * @since 3.1.4 - * @access public - * @author Grégory Viguier - * - * @param array $cookies An array of cookies. - * @return array - */ - public static function remove_auth_cookies( $cookies = [] ) { - if ( ! $cookies || ! is_array( $cookies ) ) { - $cookies = $_COOKIE; - } - - unset( $cookies['wordpress_test_cookie'] ); - - if ( ! $cookies ) { - return []; - } - - $pattern = strtolower( '@^WordPress(?:user|pass|_sec|_logged_in)?_@' ); // Trolling PHPCS. - - foreach ( $cookies as $cookie_name => $value ) { - if ( preg_match( $pattern, $cookie_name ) ) { - $cookies[ $cookie_name ] = 'Value removed by WP Rocket.'; - } - } - - return $cookies; - } +if ( isset( $rocket_path ) ) { + require_once $rocket_path . 'inc/Logger/Logger.php'; } diff --git a/inc/classes/logger/class-stream-handler.php b/inc/classes/logger/class-stream-handler.php index 7e80bd74e9..179267ed3c 100644 --- a/inc/classes/logger/class-stream-handler.php +++ b/inc/classes/logger/class-stream-handler.php @@ -1,147 +1,4 @@ create_htaccess_file(); - } - - /** - * Create a .htaccess file in the log folder, to prevent direct access and directory listing. - * - * @since 3.2 - * - * @throws \UnexpectedValueException When the .htaccess file could not be created. - * - * @return bool True if the file exists or has been created. False on failure. - */ - public function create_htaccess_file() { - if ( $this->htaccess_exists ) { - return true; - } - - if ( $this->has_error ) { - return false; - } - - $dir = $this->get_dir_from_stream( $this->url ); - - if ( ! $dir || ! is_dir( $dir ) ) { - $this->has_error = true; - return false; - } - - $file_path = $dir . '/.htaccess'; - - if ( file_exists( $file_path ) ) { - $this->htaccess_exists = true; - return true; - } - - $this->error_message = null; - - set_error_handler( [ $this, 'custom_error_handler' ] ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler - - $file_resource = fopen( $file_path, 'a' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fopen - - restore_error_handler(); - - if ( ! is_resource( $file_resource ) ) { - $this->has_error = true; - throw new UnexpectedValueException( sprintf( 'The file "%s" could not be opened: ' . $this->error_message, $file_path ) ); - } - - $new_content = "\nOrder allow,deny\nDeny from all\n\nOptions -Indexes"; - - fwrite( $file_resource, $new_content ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fwrite - fclose( $file_resource ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose - @chmod( $file_path, 0644 ); - - $this->htaccess_exists = true; - - return true; - } - - /** - * Temporary error handler that "cleans" the error messages. - * - * @since 3.2 - * - * @see parent::customErrorHandler() - * - * @param int $code Error code. - * @param string $msg Error message. - */ - private function custom_error_handler( int $code, string $msg ) { - $this->error_message = preg_replace( '{^(fopen|mkdir)\(.*?\): }', '', $msg ); - } - - /** - * A dirname() that also works for streams, by removing the protocol. - * - * @since 3.2 - * - * @see parent::getDirFromStream() - * - * @param string $stream Path to a file. - * - * @return null|string - */ - private function get_dir_from_stream( string $stream ) { - $pos = strpos( $stream, '://' ); - - if ( false === $pos ) { - return dirname( $stream ); - } - - if ( 'file://' === substr( $stream, 0, 7 ) ) { - return dirname( substr( $stream, 7 ) ); - } - } +if ( isset( $rocket_path ) ) { + require_once $rocket_path . 'inc/Logger/StreamHandler.php'; } diff --git a/tests/Fixtures/content/advancedCacheContent.php b/tests/Fixtures/content/advancedCacheContent.php index 5e2e142de5..684db81f81 100644 --- a/tests/Fixtures/content/advancedCacheContent.php +++ b/tests/Fixtures/content/advancedCacheContent.php @@ -36,62 +36,7 @@ MOBILE_CONTENTS; -$end = << \$rocket_path . 'inc/classes/Buffer/class-abstract-buffer.php', - 'WP_Rocket\\\Buffer\\\Cache' => \$rocket_path . 'inc/classes/Buffer/class-cache.php', - 'WP_Rocket\\\Buffer\\\Tests' => \$rocket_path . 'inc/classes/Buffer/class-tests.php', - 'WP_Rocket\\\Buffer\\\Config' => \$rocket_path . 'inc/classes/Buffer/class-config.php', - 'WP_Rocket\\\Logger\\\HTML_Formatter' => \$rocket_path . 'inc/classes/logger/class-html-formatter.php', - 'WP_Rocket\\\Logger\\\Logger' => \$rocket_path . 'inc/classes/logger/class-logger.php', - 'WP_Rocket\\\Logger\\\Stream_Handler' => \$rocket_path . 'inc/classes/logger/class-stream-handler.php', - 'WP_Rocket\\\Traits\\\Memoize' => \$rocket_path . 'inc/classes/traits/trait-memoize.php', - ]; - - if ( isset( \$rocket_classes[ \$class ] ) ) { - \$file = \$rocket_classes[ \$class ]; - } elseif ( strpos( \$class, 'Monolog\\\' ) === 0 ) { - \$file = \$rocket_path . 'vendor/monolog/monolog/src/' . str_replace( '\\\', '/', \$class ) . '.php'; - } elseif ( strpos( \$class, 'Psr\\\Log\\\' ) === 0 ) { - \$file = \$rocket_path . 'vendor/psr/log/' . str_replace( '\\\', '/', \$class ) . '.php'; - } else { - return; - } - - if ( file_exists( \$file ) ) { - require \$file; - } - } -); - -if ( ! class_exists( '\WP_Rocket\Buffer\Cache' ) ) { - if ( ! defined( 'DONOTROCKETOPTIMIZE' ) ) { - define( 'DONOTROCKETOPTIMIZE', true ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound - } - return; -} - -\$rocket_config_class = new Config( - [ - 'config_dir_path' => \$rocket_config_path, - ] -); - -( new Cache( - new Tests( - \$rocket_config_class - ), - \$rocket_config_class, - [ - 'cache_dir_path' => \$rocket_cache_path, - ] -) )->maybe_init_process(); - -ENDING_CONTENTS; +$end = file_get_contents(__DIR__ . '/endingContent.php'); return [ 'non_mobile' => "{$start}{$end}", diff --git a/tests/Fixtures/content/endingContent.php b/tests/Fixtures/content/endingContent.php new file mode 100644 index 0000000000..682abb1203 --- /dev/null +++ b/tests/Fixtures/content/endingContent.php @@ -0,0 +1,76 @@ + + +spl_autoload_register( + function( $class ) use ( $rocket_path ) { + $rocket_classes = [ + 'WP_Rocket\\Buffer\\Abstract_Buffer' => [ $rocket_path . 'inc/classes/Buffer/class-abstract-buffer.php' ], + 'WP_Rocket\\Buffer\\Cache' => [ $rocket_path . 'inc/classes/Buffer/class-cache.php' ], + 'WP_Rocket\\Buffer\\Tests' => [ $rocket_path . 'inc/classes/Buffer/class-tests.php' ], + 'WP_Rocket\\Buffer\\Config' => [ $rocket_path . 'inc/classes/Buffer/class-config.php' ], + 'WP_Rocket\\Logger\\HTMLFormatter' => [ + $rocket_path . 'inc/Logger/HTMLFormatter.php', + $rocket_path . 'inc/classes/logger/class-html-formatter.php', + ], + 'WP_Rocket\\Logger\\Logger' => [ + $rocket_path . 'inc/Logger/Logger.php', + $rocket_path . 'inc/classes/logger/class-logger.php', + ], + 'WP_Rocket\\Logger\\StreamHandler' => [ + $rocket_path . 'inc/Logger/StreamHandler.php', + $rocket_path . 'inc/classes/logger/class-stream-handler.php', + ], + 'WP_Rocket\\Traits\\Memoize' => [ $rocket_path . 'inc/classes/traits/trait-memoize.php' ], + ]; + + if ( isset( $rocket_classes[ $class ] ) ) { + $file_options = $rocket_classes[ $class ]; + $file = ''; + + foreach ( $file_options as $file_option ) { + if ( file_exists( $file_option ) ) { + $file = $file_option; + break; + } + } + } elseif ( strpos( $class, 'WP_Rocket\\Dependencies\\Monolog\\' ) === 0 ) { + $file = $rocket_path . 'inc/Dependencies/Monolog/' . str_replace( '\\', '/', $class ) . '.php'; + if ( ! file_exists( $file ) ) { + $file = $rocket_path . 'vendor/monolog/monolog/src/' . str_replace( '\\', '/', $class ) . '.php'; + } + } elseif ( strpos( $class, 'WP_Rocket\\Dependencies\\Psr\\Log\\' ) === 0 ) { + $file = $rocket_path . 'inc/Dependencies/Psr/Log/' . str_replace( '\\', '/', $class ) . '.php'; + if ( ! file_exists( $file ) ) { + $file = $rocket_path . 'vendor/psr/log/' . str_replace( '\\', '/', $class ) . '.php'; + } + } else { + return; + } + + if ( file_exists( $file ) ) { + require $file; + } + } +); + +if ( ! class_exists( '\WP_Rocket\Buffer\Cache' ) ) { + if ( ! defined( 'DONOTROCKETOPTIMIZE' ) ) { + define( 'DONOTROCKETOPTIMIZE', true ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound + } + return; +} + +$rocket_config_class = new Config( + [ + 'config_dir_path' => $rocket_config_path, + ] +); + +( new Cache( + new Tests( + $rocket_config_class + ), + $rocket_config_class, + [ + 'cache_dir_path' => $rocket_cache_path, + ] +) )->maybe_init_process(); diff --git a/tests/Fixtures/inc/Engine/Cache/AdminSubscriber/onUpdate.php b/tests/Fixtures/inc/Engine/Cache/AdminSubscriber/onUpdate.php new file mode 100644 index 0000000000..9705c42cc7 --- /dev/null +++ b/tests/Fixtures/inc/Engine/Cache/AdminSubscriber/onUpdate.php @@ -0,0 +1,17 @@ + [ + 'config' => [ + 'new_version' => '3.15', + 'old_version' => '3.14', + 'is_superior' => false + ], + ], + 'SuperiorShouldNotGenerate' => [ + 'config' => [ + 'new_version' => '3.17', + 'old_version' => '3.16', + 'is_superior' => true + ], + ], +]; diff --git a/tests/Unit/inc/Engine/Cache/AdminSubscriber/onUpdate.php b/tests/Unit/inc/Engine/Cache/AdminSubscriber/onUpdate.php new file mode 100644 index 0000000000..eec16920a3 --- /dev/null +++ b/tests/Unit/inc/Engine/Cache/AdminSubscriber/onUpdate.php @@ -0,0 +1,44 @@ +subscriber = new AdminSubscriber( + Mockery::mock( AdvancedCache::class ), + Mockery::mock( WPCache::class ) + ); + } + + /** + * @dataProvider configTestData + */ + public function testShouldDoAsExpected( $config ) + { + if( $config['is_superior'] ) { + Functions\expect('rocket_generate_advanced_cache_file')->never(); + } else { + Functions\expect('rocket_generate_advanced_cache_file'); + } + $this->subscriber->on_update( $config['new_version'], $config['old_version'] ); + } +} diff --git a/views/cache/advanced-cache.php b/views/cache/advanced-cache.php index 018f69fd81..afdb524237 100644 --- a/views/cache/advanced-cache.php +++ b/views/cache/advanced-cache.php @@ -31,22 +31,45 @@ spl_autoload_register( function( $class ) use ( $rocket_path ) { $rocket_classes = [ - 'WP_Rocket\\Buffer\\Abstract_Buffer' => $rocket_path . 'inc/classes/Buffer/class-abstract-buffer.php', - 'WP_Rocket\\Buffer\\Cache' => $rocket_path . 'inc/classes/Buffer/class-cache.php', - 'WP_Rocket\\Buffer\\Tests' => $rocket_path . 'inc/classes/Buffer/class-tests.php', - 'WP_Rocket\\Buffer\\Config' => $rocket_path . 'inc/classes/Buffer/class-config.php', - 'WP_Rocket\\Logger\\HTML_Formatter' => $rocket_path . 'inc/classes/logger/class-html-formatter.php', - 'WP_Rocket\\Logger\\Logger' => $rocket_path . 'inc/classes/logger/class-logger.php', - 'WP_Rocket\\Logger\\Stream_Handler' => $rocket_path . 'inc/classes/logger/class-stream-handler.php', - 'WP_Rocket\\Traits\\Memoize' => $rocket_path . 'inc/classes/traits/trait-memoize.php', + 'WP_Rocket\\Buffer\\Abstract_Buffer' => [ $rocket_path . 'inc/classes/Buffer/class-abstract-buffer.php' ], + 'WP_Rocket\\Buffer\\Cache' => [ $rocket_path . 'inc/classes/Buffer/class-cache.php' ], + 'WP_Rocket\\Buffer\\Tests' => [ $rocket_path . 'inc/classes/Buffer/class-tests.php' ], + 'WP_Rocket\\Buffer\\Config' => [ $rocket_path . 'inc/classes/Buffer/class-config.php' ], + 'WP_Rocket\\Logger\\HTMLFormatter' => [ + $rocket_path . 'inc/Logger/HTMLFormatter.php', + $rocket_path . 'inc/classes/logger/class-html-formatter.php', + ], + 'WP_Rocket\\Logger\\Logger' => [ + $rocket_path . 'inc/Logger/Logger.php', + $rocket_path . 'inc/classes/logger/class-logger.php', + ], + 'WP_Rocket\\Logger\\StreamHandler' => [ + $rocket_path . 'inc/Logger/StreamHandler.php', + $rocket_path . 'inc/classes/logger/class-stream-handler.php', + ], + 'WP_Rocket\\Traits\\Memoize' => [ $rocket_path . 'inc/classes/traits/trait-memoize.php' ], ]; if ( isset( $rocket_classes[ $class ] ) ) { - $file = $rocket_classes[ $class ]; - } elseif ( strpos( $class, 'Monolog\\' ) === 0 ) { - $file = $rocket_path . 'vendor/monolog/monolog/src/' . str_replace( '\\', '/', $class ) . '.php'; - } elseif ( strpos( $class, 'Psr\\Log\\' ) === 0 ) { - $file = $rocket_path . 'vendor/psr/log/' . str_replace( '\\', '/', $class ) . '.php'; + $file_options = $rocket_classes[ $class ]; + $file = ''; + + foreach ( $file_options as $file_option ) { + if ( file_exists( $file_option ) ) { + $file = $file_option; + break; + } + } + } elseif ( strpos( $class, 'WP_Rocket\\Dependencies\\Monolog\\' ) === 0 ) { + $file = $rocket_path . 'inc/Dependencies/Monolog/' . str_replace( '\\', '/', $class ) . '.php'; + if ( ! file_exists( $file ) ) { + $file = $rocket_path . 'vendor/monolog/monolog/src/' . str_replace( '\\', '/', $class ) . '.php'; + } + } elseif ( strpos( $class, 'WP_Rocket\\Dependencies\\Psr\\Log\\' ) === 0 ) { + $file = $rocket_path . 'inc/Dependencies/Psr/Log/' . str_replace( '\\', '/', $class ) . '.php'; + if ( ! file_exists( $file ) ) { + $file = $rocket_path . 'vendor/psr/log/' . str_replace( '\\', '/', $class ) . '.php'; + } } else { return; }