diff --git a/composer.json b/composer.json index ada51578..cb31a0e5 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "codeception/module-cli": "^1.0", "codeception/util-universalframework": "^1.0", "rector/rector": "^0.17.0", - "wp-cli/wp-cli-bundle": "2.8.1", + "wp-cli/wp-cli-bundle": "^2.9", "phpstan/phpstan": "^1.10.0", "szepeviktor/phpstan-wordpress": "dev-master", "phpstan/extension-installer": "^1.3.1", diff --git a/composer.lock b/composer.lock index 561a46a8..09ef5070 100644 --- a/composer.lock +++ b/composer.lock @@ -4,21 +4,21 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b95bc88a23567874b9f29e94eecf7a64", + "content-hash": "4c357ca873ffffff2a63276a6d1a1839", "packages": [], "packages-dev": [ { "name": "antecedent/patchwork", - "version": "2.1.26", + "version": "2.1.27", "source": { "type": "git", "url": "https://github.com/antecedent/patchwork.git", - "reference": "f2dae0851b2eae4c51969af740fdd0356d7f8f55" + "reference": "16a1ab81559aabf14acb616141e801b32777f085" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/antecedent/patchwork/zipball/f2dae0851b2eae4c51969af740fdd0356d7f8f55", - "reference": "f2dae0851b2eae4c51969af740fdd0356d7f8f55", + "url": "https://api.github.com/repos/antecedent/patchwork/zipball/16a1ab81559aabf14acb616141e801b32777f085", + "reference": "16a1ab81559aabf14acb616141e801b32777f085", "shasum": "" }, "require": { @@ -39,7 +39,7 @@ } ], "description": "Method redefinition (monkey-patching) functionality for PHP.", - "homepage": "http://patchwork2.org/", + "homepage": "https://antecedent.github.io/patchwork/", "keywords": [ "aop", "aspect", @@ -51,9 +51,9 @@ ], "support": { "issues": "https://github.com/antecedent/patchwork/issues", - "source": "https://github.com/antecedent/patchwork/tree/2.1.26" + "source": "https://github.com/antecedent/patchwork/tree/2.1.27" }, - "time": "2023-09-18T08:18:37+00:00" + "time": "2023-12-03T18:46:49+00:00" }, { "name": "behat/gherkin", @@ -174,6 +174,75 @@ }, "time": "2012-08-31T00:00:00+00:00" }, + { + "name": "carbonphp/carbon-doctrine-types", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git", + "reference": "99f76ffa36cce3b70a4a6abce41dba15ca2e84cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/99f76ffa36cce3b70a4a6abce41dba15ca2e84cb", + "reference": "99f76ffa36cce3b70a4a6abce41dba15ca2e84cb", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "doctrine/dbal": "<3.7.0 || >=4.0.0" + }, + "require-dev": { + "doctrine/dbal": "^3.7.0", + "nesbot/carbon": "^2.71.0 || ^3.0.0", + "phpunit/phpunit": "^10.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Carbon\\Doctrine\\": "src/Carbon/Doctrine/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "KyleKatarn", + "email": "kylekatarnls@gmail.com" + } + ], + "description": "Types to use Carbon in Doctrine", + "keywords": [ + "carbon", + "date", + "datetime", + "doctrine", + "time" + ], + "support": { + "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/2.1.0" + }, + "funding": [ + { + "url": "https://github.com/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", + "type": "tidelift" + } + ], + "time": "2023-12-11T17:09:12+00:00" + }, { "name": "codeception/codeception", "version": "4.2.2", @@ -836,16 +905,16 @@ }, { "name": "composer/ca-bundle", - "version": "1.3.7", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "76e46335014860eec1aa5a724799a00a2e47cc85" + "reference": "b66d11b7479109ab547f9405b97205640b17d385" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/76e46335014860eec1aa5a724799a00a2e47cc85", - "reference": "76e46335014860eec1aa5a724799a00a2e47cc85", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/b66d11b7479109ab547f9405b97205640b17d385", + "reference": "b66d11b7479109ab547f9405b97205640b17d385", "shasum": "" }, "require": { @@ -857,7 +926,7 @@ "phpstan/phpstan": "^0.12.55", "psr/log": "^1.0", "symfony/phpunit-bridge": "^4.2 || ^5", - "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0" + "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0" }, "type": "library", "extra": { @@ -892,7 +961,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.3.7" + "source": "https://github.com/composer/ca-bundle/tree/1.4.0" }, "funding": [ { @@ -908,7 +977,7 @@ "type": "tidelift" } ], - "time": "2023-08-30T09:31:38+00:00" + "time": "2023-12-18T12:05:55+00:00" }, { "name": "composer/composer", @@ -1232,16 +1301,16 @@ }, { "name": "composer/spdx-licenses", - "version": "1.5.7", + "version": "1.5.8", "source": { "type": "git", "url": "https://github.com/composer/spdx-licenses.git", - "reference": "c848241796da2abf65837d51dce1fae55a960149" + "reference": "560bdcf8deb88ae5d611c80a2de8ea9d0358cc0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/c848241796da2abf65837d51dce1fae55a960149", - "reference": "c848241796da2abf65837d51dce1fae55a960149", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/560bdcf8deb88ae5d611c80a2de8ea9d0358cc0a", + "reference": "560bdcf8deb88ae5d611c80a2de8ea9d0358cc0a", "shasum": "" }, "require": { @@ -1290,9 +1359,9 @@ "validator" ], "support": { - "irc": "irc://irc.freenode.org/composer", + "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/spdx-licenses/issues", - "source": "https://github.com/composer/spdx-licenses/tree/1.5.7" + "source": "https://github.com/composer/spdx-licenses/tree/1.5.8" }, "funding": [ { @@ -1308,7 +1377,7 @@ "type": "tidelift" } ], - "time": "2022-05-23T07:37:50+00:00" + "time": "2023-11-20T07:44:33+00:00" }, { "name": "composer/xdebug-handler", @@ -1873,16 +1942,16 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.8.0", + "version": "7.8.1", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "1110f66a6530a40fe7aea0378fe608ee2b2248f9" + "reference": "41042bc7ab002487b876a0683fc8dce04ddce104" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/1110f66a6530a40fe7aea0378fe608ee2b2248f9", - "reference": "1110f66a6530a40fe7aea0378fe608ee2b2248f9", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104", + "reference": "41042bc7ab002487b876a0683fc8dce04ddce104", "shasum": "" }, "require": { @@ -1897,11 +1966,11 @@ "psr/http-client-implementation": "1.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", + "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", "php-http/message-factory": "^1.1", - "phpunit/phpunit": "^8.5.29 || ^9.5.23", + "phpunit/phpunit": "^8.5.36 || ^9.6.15", "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { @@ -1979,7 +2048,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.8.0" + "source": "https://github.com/guzzle/guzzle/tree/7.8.1" }, "funding": [ { @@ -1995,28 +2064,28 @@ "type": "tidelift" } ], - "time": "2023-08-27T10:20:53+00:00" + "time": "2023-12-03T20:35:24+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.0.1", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "111166291a0f8130081195ac4556a5587d7f1b5d" + "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/111166291a0f8130081195ac4556a5587d7f1b5d", - "reference": "111166291a0f8130081195ac4556a5587d7f1b5d", + "url": "https://api.github.com/repos/guzzle/promises/zipball/bbff78d96034045e58e13dedd6ad91b5d1253223", + "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", - "phpunit/phpunit": "^8.5.29 || ^9.5.23" + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.36 || ^9.6.15" }, "type": "library", "extra": { @@ -2062,7 +2131,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.0.1" + "source": "https://github.com/guzzle/promises/tree/2.0.2" }, "funding": [ { @@ -2078,20 +2147,20 @@ "type": "tidelift" } ], - "time": "2023-08-03T15:11:55+00:00" + "time": "2023-12-03T20:19:20+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.6.1", + "version": "2.6.2", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "be45764272e8873c72dbe3d2edcfdfcc3bc9f727" + "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/be45764272e8873c72dbe3d2edcfdfcc3bc9f727", - "reference": "be45764272e8873c72dbe3d2edcfdfcc3bc9f727", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/45b30f99ac27b5ca93cb4831afe16285f57b8221", + "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221", "shasum": "" }, "require": { @@ -2105,9 +2174,9 @@ "psr/http-message-implementation": "1.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", + "bamarni/composer-bin-plugin": "^1.8.2", "http-interop/http-factory-tests": "^0.9", - "phpunit/phpunit": "^8.5.29 || ^9.5.23" + "phpunit/phpunit": "^8.5.36 || ^9.6.15" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -2178,7 +2247,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.6.1" + "source": "https://github.com/guzzle/psr7/tree/2.6.2" }, "funding": [ { @@ -2194,7 +2263,7 @@ "type": "tidelift" } ], - "time": "2023-08-27T10:13:57+00:00" + "time": "2023-12-03T20:05:35+00:00" }, { "name": "illuminate/collections", @@ -2484,16 +2553,16 @@ }, { "name": "lucatume/wp-browser", - "version": "3.2.1", + "version": "3.2.2", "source": { "type": "git", "url": "https://github.com/lucatume/wp-browser.git", - "reference": "95a189fda634fd52cb44eebbbda3fabe0fdabdb2" + "reference": "88456c4d73ded85132c6bfa594f685a90952630c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lucatume/wp-browser/zipball/95a189fda634fd52cb44eebbbda3fabe0fdabdb2", - "reference": "95a189fda634fd52cb44eebbbda3fabe0fdabdb2", + "url": "https://api.github.com/repos/lucatume/wp-browser/zipball/88456c4d73ded85132c6bfa594f685a90952630c", + "reference": "88456c4d73ded85132c6bfa594f685a90952630c", "shasum": "" }, "require": { @@ -2583,7 +2652,7 @@ ], "support": { "issues": "https://github.com/lucatume/wp-browser/issues", - "source": "https://github.com/lucatume/wp-browser/tree/3.2.1" + "source": "https://github.com/lucatume/wp-browser/tree/3.2.2" }, "funding": [ { @@ -2591,7 +2660,7 @@ "type": "github" } ], - "time": "2023-09-21T06:36:09+00:00" + "time": "2023-11-20T15:05:37+00:00" }, { "name": "mck89/peast", @@ -2912,19 +2981,20 @@ }, { "name": "nesbot/carbon", - "version": "2.71.0", + "version": "2.72.1", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "98276233188583f2ff845a0f992a235472d9466a" + "reference": "2b3b3db0a2d0556a177392ff1a3bf5608fa09f78" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/98276233188583f2ff845a0f992a235472d9466a", - "reference": "98276233188583f2ff845a0f992a235472d9466a", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/2b3b3db0a2d0556a177392ff1a3bf5608fa09f78", + "reference": "2b3b3db0a2d0556a177392ff1a3bf5608fa09f78", "shasum": "" }, "require": { + "carbonphp/carbon-doctrine-types": "*", "ext-json": "*", "php": "^7.1.8 || ^8.0", "psr/clock": "^1.0", @@ -2936,8 +3006,8 @@ "psr/clock-implementation": "1.0" }, "require-dev": { - "doctrine/dbal": "^2.0 || ^3.1.4", - "doctrine/orm": "^2.7", + "doctrine/dbal": "^2.0 || ^3.1.4 || ^4.0", + "doctrine/orm": "^2.7 || ^3.0", "friendsofphp/php-cs-fixer": "^3.0", "kylekatarnls/multi-tester": "^2.0", "ondrejmirtes/better-reflection": "*", @@ -3014,20 +3084,20 @@ "type": "tidelift" } ], - "time": "2023-09-25T11:31:05+00:00" + "time": "2023-12-08T23:47:49+00:00" }, { "name": "nikic/php-parser", - "version": "v4.17.1", + "version": "v4.18.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d" + "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", - "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bcbb2179f97633e98bbbc87044ee2611c7d7999", + "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999", "shasum": "" }, "require": { @@ -3068,9 +3138,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.17.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.18.0" }, - "time": "2023-08-13T19:53:39+00:00" + "time": "2023-12-10T21:03:43+00:00" }, { "name": "phar-io/manifest", @@ -3185,25 +3255,27 @@ }, { "name": "php-stubs/wordpress-stubs", - "version": "v6.3.2", + "version": "v6.4.1", "source": { "type": "git", "url": "https://github.com/php-stubs/wordpress-stubs.git", - "reference": "f22b00cacd3b9addc2b07ff48290084503c48574" + "reference": "6d6063cf9464a306ca2a0529705d41312b08500b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/f22b00cacd3b9addc2b07ff48290084503c48574", - "reference": "f22b00cacd3b9addc2b07ff48290084503c48574", + "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/6d6063cf9464a306ca2a0529705d41312b08500b", + "reference": "6d6063cf9464a306ca2a0529705d41312b08500b", "shasum": "" }, "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", "nikic/php-parser": "^4.13", "php": "^7.4 || ~8.0.0", "php-stubs/generator": "^0.8.3", "phpdocumentor/reflection-docblock": "^5.3", "phpstan/phpstan": "^1.10.12", - "phpunit/phpunit": "^9.5" + "phpunit/phpunit": "^9.5", + "szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^0.8" }, "suggest": { "paragonie/sodium_compat": "Pure PHP implementation of libsodium", @@ -3224,9 +3296,9 @@ ], "support": { "issues": "https://github.com/php-stubs/wordpress-stubs/issues", - "source": "https://github.com/php-stubs/wordpress-stubs/tree/v6.3.2" + "source": "https://github.com/php-stubs/wordpress-stubs/tree/v6.4.1" }, - "time": "2023-10-14T10:08:05+00:00" + "time": "2023-11-10T00:33:47+00:00" }, { "name": "php-stubs/wp-cli-stubs", @@ -3515,29 +3587,29 @@ }, { "name": "phpcsstandards/phpcsextra", - "version": "1.1.2", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHPCSExtra.git", - "reference": "746c3190ba8eb2f212087c947ba75f4f5b9a58d5" + "reference": "11d387c6642b6e4acaf0bd9bf5203b8cca1ec489" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/746c3190ba8eb2f212087c947ba75f4f5b9a58d5", - "reference": "746c3190ba8eb2f212087c947ba75f4f5b9a58d5", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/11d387c6642b6e4acaf0bd9bf5203b8cca1ec489", + "reference": "11d387c6642b6e4acaf0bd9bf5203b8cca1ec489", "shasum": "" }, "require": { "php": ">=5.4", - "phpcsstandards/phpcsutils": "^1.0.8", - "squizlabs/php_codesniffer": "^3.7.1" + "phpcsstandards/phpcsutils": "^1.0.9", + "squizlabs/php_codesniffer": "^3.8.0" }, "require-dev": { "php-parallel-lint/php-console-highlighter": "^1.0", "php-parallel-lint/php-parallel-lint": "^1.3.2", "phpcsstandards/phpcsdevcs": "^1.1.6", "phpcsstandards/phpcsdevtools": "^1.2.1", - "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0" + "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" }, "type": "phpcodesniffer-standard", "extra": { @@ -3572,35 +3644,50 @@ ], "support": { "issues": "https://github.com/PHPCSStandards/PHPCSExtra/issues", + "security": "https://github.com/PHPCSStandards/PHPCSExtra/security/policy", "source": "https://github.com/PHPCSStandards/PHPCSExtra" }, - "time": "2023-09-20T22:06:18+00:00" + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2023-12-08T16:49:07+00:00" }, { "name": "phpcsstandards/phpcsutils", - "version": "1.0.8", + "version": "1.0.9", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHPCSUtils.git", - "reference": "69465cab9d12454e5e7767b9041af0cd8cd13be7" + "reference": "908247bc65010c7b7541a9551e002db12e9dae70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/69465cab9d12454e5e7767b9041af0cd8cd13be7", - "reference": "69465cab9d12454e5e7767b9041af0cd8cd13be7", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/908247bc65010c7b7541a9551e002db12e9dae70", + "reference": "908247bc65010c7b7541a9551e002db12e9dae70", "shasum": "" }, "require": { "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7 || ^1.0", "php": ">=5.4", - "squizlabs/php_codesniffer": "^3.7.1 || 4.0.x-dev@dev" + "squizlabs/php_codesniffer": "^3.8.0 || 4.0.x-dev@dev" }, "require-dev": { "ext-filter": "*", "php-parallel-lint/php-console-highlighter": "^1.0", "php-parallel-lint/php-parallel-lint": "^1.3.2", "phpcsstandards/phpcsdevcs": "^1.1.6", - "yoast/phpunit-polyfills": "^1.0.5 || ^2.0.0" + "yoast/phpunit-polyfills": "^1.1.0 || ^2.0.0" }, "type": "phpcodesniffer-standard", "extra": { @@ -3645,9 +3732,24 @@ "support": { "docs": "https://phpcsutils.com/", "issues": "https://github.com/PHPCSStandards/PHPCSUtils/issues", + "security": "https://github.com/PHPCSStandards/PHPCSUtils/security/policy", "source": "https://github.com/PHPCSStandards/PHPCSUtils" }, - "time": "2023-07-16T21:39:41+00:00" + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2023-12-08T14:50:00+00:00" }, { "name": "phpstan/extension-installer", @@ -3695,16 +3797,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.40", + "version": "1.10.54", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "93c84b5bf7669920d823631e39904d69b9c7dc5d" + "reference": "3e25f279dada0adc14ffd7bad09af2e2fc3523bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/93c84b5bf7669920d823631e39904d69b9c7dc5d", - "reference": "93c84b5bf7669920d823631e39904d69b9c7dc5d", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/3e25f279dada0adc14ffd7bad09af2e2fc3523bb", + "reference": "3e25f279dada0adc14ffd7bad09af2e2fc3523bb", "shasum": "" }, "require": { @@ -3753,27 +3855,27 @@ "type": "tidelift" } ], - "time": "2023-10-30T14:48:31+00:00" + "time": "2024-01-05T15:50:47+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.29", + "version": "9.2.30", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76" + "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/6a3a87ac2bbe33b25042753df8195ba4aa534c76", - "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca2bd87d2f9215904682a9cb9bb37dda98e76089", + "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.15", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -3823,7 +3925,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.29" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.30" }, "funding": [ { @@ -3831,7 +3933,7 @@ "type": "github" } ], - "time": "2023-09-19T04:57:46+00:00" + "time": "2023-12-22T06:47:57+00:00" }, { "name": "phpunit/php-file-iterator", @@ -4076,16 +4178,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.13", + "version": "9.6.15", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "f3d767f7f9e191eab4189abe41ab37797e30b1be" + "reference": "05017b80304e0eb3f31d90194a563fd53a6021f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f3d767f7f9e191eab4189abe41ab37797e30b1be", - "reference": "f3d767f7f9e191eab4189abe41ab37797e30b1be", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/05017b80304e0eb3f31d90194a563fd53a6021f1", + "reference": "05017b80304e0eb3f31d90194a563fd53a6021f1", "shasum": "" }, "require": { @@ -4159,7 +4261,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.13" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.15" }, "funding": [ { @@ -4175,7 +4277,7 @@ "type": "tidelift" } ], - "time": "2023-09-19T05:39:22+00:00" + "time": "2023-12-01T16:55:19+00:00" }, { "name": "psr/clock", @@ -4630,23 +4732,23 @@ }, { "name": "react/promise", - "version": "v2.10.0", + "version": "v2.11.0", "source": { "type": "git", "url": "https://github.com/reactphp/promise.git", - "reference": "f913fb8cceba1e6644b7b90c4bfb678ed8a3ef38" + "reference": "1a8460931ea36dc5c76838fec5734d55c88c6831" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/f913fb8cceba1e6644b7b90c4bfb678ed8a3ef38", - "reference": "f913fb8cceba1e6644b7b90c4bfb678ed8a3ef38", + "url": "https://api.github.com/repos/reactphp/promise/zipball/1a8460931ea36dc5c76838fec5734d55c88c6831", + "reference": "1a8460931ea36dc5c76838fec5734d55c88c6831", "shasum": "" }, "require": { "php": ">=5.4.0" }, "require-dev": { - "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.36" + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" }, "type": "library", "autoload": { @@ -4690,7 +4792,7 @@ ], "support": { "issues": "https://github.com/reactphp/promise/issues", - "source": "https://github.com/reactphp/promise/tree/v2.10.0" + "source": "https://github.com/reactphp/promise/tree/v2.11.0" }, "funding": [ { @@ -4698,7 +4800,7 @@ "type": "open_collective" } ], - "time": "2023-05-02T15:15:43+00:00" + "time": "2023-11-16T16:16:50+00:00" }, { "name": "rector/rector", @@ -4999,20 +5101,20 @@ }, { "name": "sebastian/complexity", - "version": "2.0.2", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", "shasum": "" }, "require": { - "nikic/php-parser": "^4.7", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3" }, "require-dev": { @@ -5044,7 +5146,7 @@ "homepage": "https://github.com/sebastianbergmann/complexity", "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" }, "funding": [ { @@ -5052,7 +5154,7 @@ "type": "github" } ], - "time": "2020-10-26T15:52:27+00:00" + "time": "2023-12-22T06:19:30+00:00" }, { "name": "sebastian/diff", @@ -5326,20 +5428,20 @@ }, { "name": "sebastian/lines-of-code", - "version": "1.0.3", + "version": "1.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", "shasum": "" }, "require": { - "nikic/php-parser": "^4.6", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3" }, "require-dev": { @@ -5371,7 +5473,7 @@ "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" }, "funding": [ { @@ -5379,7 +5481,7 @@ "type": "github" } ], - "time": "2020-11-28T06:42:11+00:00" + "time": "2023-12-22T06:20:34+00:00" }, { "name": "sebastian/object-enumerator", @@ -5722,16 +5824,16 @@ }, { "name": "seld/jsonlint", - "version": "1.10.0", + "version": "1.10.1", "source": { "type": "git", "url": "https://github.com/Seldaek/jsonlint.git", - "reference": "594fd6462aad8ecee0b45ca5045acea4776667f1" + "reference": "76d449a358ece77d6f1d6331c68453e657172202" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/594fd6462aad8ecee0b45ca5045acea4776667f1", - "reference": "594fd6462aad8ecee0b45ca5045acea4776667f1", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/76d449a358ece77d6f1d6331c68453e657172202", + "reference": "76d449a358ece77d6f1d6331c68453e657172202", "shasum": "" }, "require": { @@ -5758,7 +5860,7 @@ { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" + "homepage": "https://seld.be" } ], "description": "JSON Linter", @@ -5770,7 +5872,7 @@ ], "support": { "issues": "https://github.com/Seldaek/jsonlint/issues", - "source": "https://github.com/Seldaek/jsonlint/tree/1.10.0" + "source": "https://github.com/Seldaek/jsonlint/tree/1.10.1" }, "funding": [ { @@ -5782,7 +5884,7 @@ "type": "tidelift" } ], - "time": "2023-05-11T13:16:46+00:00" + "time": "2023-12-18T13:03:25+00:00" }, { "name": "seld/phar-utils", @@ -5834,16 +5936,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.7.2", + "version": "3.8.0", "source": { "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879" + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "5805f7a4e4958dbb5e944ef1e6edae0a303765e7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ed8e00df0a83aa96acf703f8c2979ff33341f879", - "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/5805f7a4e4958dbb5e944ef1e6edae0a303765e7", + "reference": "5805f7a4e4958dbb5e944ef1e6edae0a303765e7", "shasum": "" }, "require": { @@ -5853,7 +5955,7 @@ "php": ">=5.4.0" }, "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" }, "bin": [ "bin/phpcs", @@ -5872,35 +5974,58 @@ "authors": [ { "name": "Greg Sherwood", - "role": "lead" + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" } ], "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", "keywords": [ "phpcs", "standards", "static analysis" ], "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" }, - "time": "2023-02-22T23:07:41+00:00" + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2023-12-08T12:32:31+00:00" }, { "name": "symfony/browser-kit", - "version": "v5.4.21", + "version": "v5.4.31", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "a866ca7e396f15d7efb6d74a8a7d364d4e05b704" + "reference": "0ed1f634a36606f2065eec221b3975e05016cbbe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/a866ca7e396f15d7efb6d74a8a7d364d4e05b704", - "reference": "a866ca7e396f15d7efb6d74a8a7d364d4e05b704", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/0ed1f634a36606f2065eec221b3975e05016cbbe", + "reference": "0ed1f634a36606f2065eec221b3975e05016cbbe", "shasum": "" }, "require": { @@ -5943,7 +6068,7 @@ "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/browser-kit/tree/v5.4.21" + "source": "https://github.com/symfony/browser-kit/tree/v5.4.31" }, "funding": [ { @@ -5959,20 +6084,20 @@ "type": "tidelift" } ], - "time": "2023-02-14T08:03:56+00:00" + "time": "2023-10-31T07:58:33+00:00" }, { "name": "symfony/console", - "version": "v5.4.28", + "version": "v5.4.34", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "f4f71842f24c2023b91237c72a365306f3c58827" + "reference": "4b4d8cd118484aa604ec519062113dd87abde18c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/f4f71842f24c2023b91237c72a365306f3c58827", - "reference": "f4f71842f24c2023b91237c72a365306f3c58827", + "url": "https://api.github.com/repos/symfony/console/zipball/4b4d8cd118484aa604ec519062113dd87abde18c", + "reference": "4b4d8cd118484aa604ec519062113dd87abde18c", "shasum": "" }, "require": { @@ -6042,7 +6167,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.28" + "source": "https://github.com/symfony/console/tree/v5.4.34" }, "funding": [ { @@ -6058,7 +6183,7 @@ "type": "tidelift" } ], - "time": "2023-08-07T06:12:30+00:00" + "time": "2023-12-08T13:33:03+00:00" }, { "name": "symfony/css-selector", @@ -6195,16 +6320,16 @@ }, { "name": "symfony/dom-crawler", - "version": "v5.4.25", + "version": "v5.4.32", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "d2aefa5a7acc5511422792931d14d1be96fe9fea" + "reference": "728f1fc136252a626ba5a69c02bd66a3697ff201" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/d2aefa5a7acc5511422792931d14d1be96fe9fea", - "reference": "d2aefa5a7acc5511422792931d14d1be96fe9fea", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/728f1fc136252a626ba5a69c02bd66a3697ff201", + "reference": "728f1fc136252a626ba5a69c02bd66a3697ff201", "shasum": "" }, "require": { @@ -6250,7 +6375,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v5.4.25" + "source": "https://github.com/symfony/dom-crawler/tree/v5.4.32" }, "funding": [ { @@ -6266,20 +6391,20 @@ "type": "tidelift" } ], - "time": "2023-06-05T08:05:41+00:00" + "time": "2023-11-17T20:43:48+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v5.4.26", + "version": "v5.4.34", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "5dcc00e03413f05c1e7900090927bb7247cb0aac" + "reference": "e3bca343efeb613f843c254e7718ef17c9bdf7a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/5dcc00e03413f05c1e7900090927bb7247cb0aac", - "reference": "5dcc00e03413f05c1e7900090927bb7247cb0aac", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/e3bca343efeb613f843c254e7718ef17c9bdf7a3", + "reference": "e3bca343efeb613f843c254e7718ef17c9bdf7a3", "shasum": "" }, "require": { @@ -6335,7 +6460,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.26" + "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.34" }, "funding": [ { @@ -6351,7 +6476,7 @@ "type": "tidelift" } ], - "time": "2023-07-06T06:34:20+00:00" + "time": "2023-12-27T21:12:56+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -7053,16 +7178,16 @@ }, { "name": "symfony/process", - "version": "v5.4.28", + "version": "v5.4.34", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "45261e1fccad1b5447a8d7a8e67aa7b4a9798b7b" + "reference": "8fa22178dfc368911dbd513b431cd9b06f9afe7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/45261e1fccad1b5447a8d7a8e67aa7b4a9798b7b", - "reference": "45261e1fccad1b5447a8d7a8e67aa7b4a9798b7b", + "url": "https://api.github.com/repos/symfony/process/zipball/8fa22178dfc368911dbd513b431cd9b06f9afe7a", + "reference": "8fa22178dfc368911dbd513b431cd9b06f9afe7a", "shasum": "" }, "require": { @@ -7095,7 +7220,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.4.28" + "source": "https://github.com/symfony/process/tree/v5.4.34" }, "funding": [ { @@ -7111,7 +7236,7 @@ "type": "tidelift" } ], - "time": "2023-08-07T10:36:04+00:00" + "time": "2023-12-02T08:41:43+00:00" }, { "name": "symfony/service-contracts", @@ -7198,16 +7323,16 @@ }, { "name": "symfony/string", - "version": "v5.4.29", + "version": "v5.4.34", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "e41bdc93def20eaf3bfc1537c4e0a2b0680a152d" + "reference": "e3f98bfc7885c957488f443df82d97814a3ce061" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/e41bdc93def20eaf3bfc1537c4e0a2b0680a152d", - "reference": "e41bdc93def20eaf3bfc1537c4e0a2b0680a152d", + "url": "https://api.github.com/repos/symfony/string/zipball/e3f98bfc7885c957488f443df82d97814a3ce061", + "reference": "e3f98bfc7885c957488f443df82d97814a3ce061", "shasum": "" }, "require": { @@ -7264,7 +7389,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.29" + "source": "https://github.com/symfony/string/tree/v5.4.34" }, "funding": [ { @@ -7280,20 +7405,20 @@ "type": "tidelift" } ], - "time": "2023-09-13T11:47:41+00:00" + "time": "2023-12-09T13:20:28+00:00" }, { "name": "symfony/translation", - "version": "v5.4.30", + "version": "v5.4.31", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "8560dc532e4e48d331937532a7cbfd2a9f9f53ce" + "reference": "ba72f72fceddf36f00bd495966b5873f2d17ad8f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/8560dc532e4e48d331937532a7cbfd2a9f9f53ce", - "reference": "8560dc532e4e48d331937532a7cbfd2a9f9f53ce", + "url": "https://api.github.com/repos/symfony/translation/zipball/ba72f72fceddf36f00bd495966b5873f2d17ad8f", + "reference": "ba72f72fceddf36f00bd495966b5873f2d17ad8f", "shasum": "" }, "require": { @@ -7361,7 +7486,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v5.4.30" + "source": "https://github.com/symfony/translation/tree/v5.4.31" }, "funding": [ { @@ -7377,7 +7502,7 @@ "type": "tidelift" } ], - "time": "2023-10-28T09:19:54+00:00" + "time": "2023-11-03T16:16:43+00:00" }, { "name": "symfony/translation-contracts", @@ -7459,16 +7584,16 @@ }, { "name": "symfony/yaml", - "version": "v5.4.30", + "version": "v5.4.31", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "c6980e82a6656f6ebfabfd82f7585794cb122554" + "reference": "f387675d7f5fc4231f7554baa70681f222f73563" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/c6980e82a6656f6ebfabfd82f7585794cb122554", - "reference": "c6980e82a6656f6ebfabfd82f7585794cb122554", + "url": "https://api.github.com/repos/symfony/yaml/zipball/f387675d7f5fc4231f7554baa70681f222f73563", + "reference": "f387675d7f5fc4231f7554baa70681f222f73563", "shasum": "" }, "require": { @@ -7514,7 +7639,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v5.4.30" + "source": "https://github.com/symfony/yaml/tree/v5.4.31" }, "funding": [ { @@ -7530,7 +7655,7 @@ "type": "tidelift" } ], - "time": "2023-10-27T18:36:14+00:00" + "time": "2023-11-03T14:41:28+00:00" }, { "name": "szepeviktor/phpstan-wordpress", @@ -7538,12 +7663,12 @@ "source": { "type": "git", "url": "https://github.com/szepeviktor/phpstan-wordpress.git", - "reference": "28802acf3747e588981f6a4190a9c89a10a23340" + "reference": "6fbfbdfc822e70a669cff31d87545fad043f5a55" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/szepeviktor/phpstan-wordpress/zipball/28802acf3747e588981f6a4190a9c89a10a23340", - "reference": "28802acf3747e588981f6a4190a9c89a10a23340", + "url": "https://api.github.com/repos/szepeviktor/phpstan-wordpress/zipball/6fbfbdfc822e70a669cff31d87545fad043f5a55", + "reference": "6fbfbdfc822e70a669cff31d87545fad043f5a55", "shasum": "" }, "require": { @@ -7593,20 +7718,20 @@ "issues": "https://github.com/szepeviktor/phpstan-wordpress/issues", "source": "https://github.com/szepeviktor/phpstan-wordpress/tree/master" }, - "time": "2023-10-16T17:31:07+00:00" + "time": "2023-11-09T00:15:27+00:00" }, { "name": "theseer/tokenizer", - "version": "1.2.1", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", "shasum": "" }, "require": { @@ -7635,7 +7760,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + "source": "https://github.com/theseer/tokenizer/tree/1.2.2" }, "funding": [ { @@ -7643,7 +7768,7 @@ "type": "github" } ], - "time": "2021-07-28T10:34:58+00:00" + "time": "2023-11-20T00:12:19+00:00" }, { "name": "voku/portable-ascii", @@ -7849,16 +7974,16 @@ }, { "name": "wp-cli/checksum-command", - "version": "v2.2.4", + "version": "v2.2.5", "source": { "type": "git", "url": "https://github.com/wp-cli/checksum-command.git", - "reference": "7ae020192bc6ee9042be0bf664bd998b1861e994" + "reference": "f6911998734018da08f75464a168feb0d07b4475" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/checksum-command/zipball/7ae020192bc6ee9042be0bf664bd998b1861e994", - "reference": "7ae020192bc6ee9042be0bf664bd998b1861e994", + "url": "https://api.github.com/repos/wp-cli/checksum-command/zipball/f6911998734018da08f75464a168feb0d07b4475", + "reference": "f6911998734018da08f75464a168feb0d07b4475", "shasum": "" }, "require": { @@ -7902,22 +8027,22 @@ "homepage": "https://github.com/wp-cli/checksum-command", "support": { "issues": "https://github.com/wp-cli/checksum-command/issues", - "source": "https://github.com/wp-cli/checksum-command/tree/v2.2.4" + "source": "https://github.com/wp-cli/checksum-command/tree/v2.2.5" }, - "time": "2023-08-30T13:34:47+00:00" + "time": "2023-11-10T21:54:15+00:00" }, { "name": "wp-cli/config-command", - "version": "v2.3.2", + "version": "v2.3.3", "source": { "type": "git", "url": "https://github.com/wp-cli/config-command.git", - "reference": "9de6ce3536a2db56ae5264c61b6f1a35e9132e01" + "reference": "890b6e3c8fd945dcad2bff4bf565ba6dfb33e35d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/config-command/zipball/9de6ce3536a2db56ae5264c61b6f1a35e9132e01", - "reference": "9de6ce3536a2db56ae5264c61b6f1a35e9132e01", + "url": "https://api.github.com/repos/wp-cli/config-command/zipball/890b6e3c8fd945dcad2bff4bf565ba6dfb33e35d", + "reference": "890b6e3c8fd945dcad2bff4bf565ba6dfb33e35d", "shasum": "" }, "require": { @@ -7926,7 +8051,7 @@ }, "require-dev": { "wp-cli/db-command": "^1.3 || ^2", - "wp-cli/wp-cli-tests": "^4" + "wp-cli/wp-cli-tests": "^4.2.8" }, "type": "wp-cli-package", "extra": { @@ -7976,22 +8101,22 @@ "homepage": "https://github.com/wp-cli/config-command", "support": { "issues": "https://github.com/wp-cli/config-command/issues", - "source": "https://github.com/wp-cli/config-command/tree/v2.3.2" + "source": "https://github.com/wp-cli/config-command/tree/v2.3.3" }, - "time": "2023-10-20T10:15:46+00:00" + "time": "2023-12-21T10:01:16+00:00" }, { "name": "wp-cli/core-command", - "version": "v2.1.15", + "version": "v2.1.16", "source": { "type": "git", "url": "https://github.com/wp-cli/core-command.git", - "reference": "7a81a8658620078bf5f2785836cb33aa382e8bb4" + "reference": "9d6ebb4545df0b8bc7e688a49910ddcdd86dbe93" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/core-command/zipball/7a81a8658620078bf5f2785836cb33aa382e8bb4", - "reference": "7a81a8658620078bf5f2785836cb33aa382e8bb4", + "url": "https://api.github.com/repos/wp-cli/core-command/zipball/9d6ebb4545df0b8bc7e688a49910ddcdd86dbe93", + "reference": "9d6ebb4545df0b8bc7e688a49910ddcdd86dbe93", "shasum": "" }, "require": { @@ -8047,9 +8172,9 @@ "homepage": "https://github.com/wp-cli/core-command", "support": { "issues": "https://github.com/wp-cli/core-command/issues", - "source": "https://github.com/wp-cli/core-command/tree/v2.1.15" + "source": "https://github.com/wp-cli/core-command/tree/v2.1.16" }, - "time": "2023-08-30T15:54:16+00:00" + "time": "2023-11-10T23:54:33+00:00" }, { "name": "wp-cli/cron-command", @@ -8122,16 +8247,16 @@ }, { "name": "wp-cli/db-command", - "version": "v2.0.26", + "version": "v2.0.27", "source": { "type": "git", "url": "https://github.com/wp-cli/db-command.git", - "reference": "0908bf5182b830c302199037070292e20d9f4ea6" + "reference": "eea28dd115fb381c82641a2a3060856d3a67242d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/db-command/zipball/0908bf5182b830c302199037070292e20d9f4ea6", - "reference": "0908bf5182b830c302199037070292e20d9f4ea6", + "url": "https://api.github.com/repos/wp-cli/db-command/zipball/eea28dd115fb381c82641a2a3060856d3a67242d", + "reference": "eea28dd115fb381c82641a2a3060856d3a67242d", "shasum": "" }, "require": { @@ -8190,9 +8315,9 @@ "homepage": "https://github.com/wp-cli/db-command", "support": { "issues": "https://github.com/wp-cli/db-command/issues", - "source": "https://github.com/wp-cli/db-command/tree/v2.0.26" + "source": "https://github.com/wp-cli/db-command/tree/v2.0.27" }, - "time": "2023-08-30T15:50:59+00:00" + "time": "2023-11-13T12:34:44+00:00" }, { "name": "wp-cli/embed-command", @@ -8595,16 +8720,16 @@ }, { "name": "wp-cli/extension-command", - "version": "v2.1.15", + "version": "v2.1.16", "source": { "type": "git", "url": "https://github.com/wp-cli/extension-command.git", - "reference": "1fe271c5ebb1815732a8cf6bb6979c9261ee6375" + "reference": "71183254b1e403bc0f9b4a97fab48e284cfe1367" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/extension-command/zipball/1fe271c5ebb1815732a8cf6bb6979c9261ee6375", - "reference": "1fe271c5ebb1815732a8cf6bb6979c9261ee6375", + "url": "https://api.github.com/repos/wp-cli/extension-command/zipball/71183254b1e403bc0f9b4a97fab48e284cfe1367", + "reference": "71183254b1e403bc0f9b4a97fab48e284cfe1367", "shasum": "" }, "require": { @@ -8687,22 +8812,22 @@ "homepage": "https://github.com/wp-cli/extension-command", "support": { "issues": "https://github.com/wp-cli/extension-command/issues", - "source": "https://github.com/wp-cli/extension-command/tree/v2.1.15" + "source": "https://github.com/wp-cli/extension-command/tree/v2.1.16" }, - "time": "2023-10-11T14:55:49+00:00" + "time": "2023-11-10T12:24:35+00:00" }, { "name": "wp-cli/i18n-command", - "version": "v2.4.4", + "version": "v2.5.0", "source": { "type": "git", "url": "https://github.com/wp-cli/i18n-command.git", - "reference": "7d82e675f271359b1af614e6325d8eeaeb7d7474" + "reference": "9cf9b40f6bad64ade8660cc26bf1f28f2d223268" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/i18n-command/zipball/7d82e675f271359b1af614e6325d8eeaeb7d7474", - "reference": "7d82e675f271359b1af614e6325d8eeaeb7d7474", + "url": "https://api.github.com/repos/wp-cli/i18n-command/zipball/9cf9b40f6bad64ade8660cc26bf1f28f2d223268", + "reference": "9cf9b40f6bad64ade8660cc26bf1f28f2d223268", "shasum": "" }, "require": { @@ -8755,9 +8880,9 @@ "homepage": "https://github.com/wp-cli/i18n-command", "support": { "issues": "https://github.com/wp-cli/i18n-command/issues", - "source": "https://github.com/wp-cli/i18n-command/tree/v2.4.4" + "source": "https://github.com/wp-cli/i18n-command/tree/v2.5.0" }, - "time": "2023-08-30T18:00:10+00:00" + "time": "2023-11-16T17:09:37+00:00" }, { "name": "wp-cli/import-command", @@ -8821,16 +8946,16 @@ }, { "name": "wp-cli/language-command", - "version": "v2.0.16", + "version": "v2.0.18", "source": { "type": "git", "url": "https://github.com/wp-cli/language-command.git", - "reference": "829cdf985fc1fdbe0572ef9a281b8a590aa4ee29" + "reference": "24f76e35f477887d09f1fd40a60d9011a6da18bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/language-command/zipball/829cdf985fc1fdbe0572ef9a281b8a590aa4ee29", - "reference": "829cdf985fc1fdbe0572ef9a281b8a590aa4ee29", + "url": "https://api.github.com/repos/wp-cli/language-command/zipball/24f76e35f477887d09f1fd40a60d9011a6da18bf", + "reference": "24f76e35f477887d09f1fd40a60d9011a6da18bf", "shasum": "" }, "require": { @@ -8894,22 +9019,22 @@ "homepage": "https://github.com/wp-cli/language-command", "support": { "issues": "https://github.com/wp-cli/language-command/issues", - "source": "https://github.com/wp-cli/language-command/tree/v2.0.16" + "source": "https://github.com/wp-cli/language-command/tree/v2.0.18" }, - "time": "2023-10-18T21:57:04+00:00" + "time": "2023-11-10T13:27:16+00:00" }, { "name": "wp-cli/maintenance-mode-command", - "version": "v2.0.10", + "version": "v2.1.0", "source": { "type": "git", "url": "https://github.com/wp-cli/maintenance-mode-command.git", - "reference": "599f8f08045ed2ef26a53d1432a4845b39d54f7d" + "reference": "2e9845a1cd1678b960dd8d0dd0c936648113788c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/maintenance-mode-command/zipball/599f8f08045ed2ef26a53d1432a4845b39d54f7d", - "reference": "599f8f08045ed2ef26a53d1432a4845b39d54f7d", + "url": "https://api.github.com/repos/wp-cli/maintenance-mode-command/zipball/2e9845a1cd1678b960dd8d0dd0c936648113788c", + "reference": "2e9845a1cd1678b960dd8d0dd0c936648113788c", "shasum": "" }, "require": { @@ -8955,22 +9080,22 @@ "homepage": "https://github.com/wp-cli/maintenance-mode-command", "support": { "issues": "https://github.com/wp-cli/maintenance-mode-command/issues", - "source": "https://github.com/wp-cli/maintenance-mode-command/tree/v2.0.10" + "source": "https://github.com/wp-cli/maintenance-mode-command/tree/v2.1.0" }, - "time": "2023-08-30T14:54:15+00:00" + "time": "2023-11-06T14:04:13+00:00" }, { "name": "wp-cli/media-command", - "version": "v2.0.20", + "version": "v2.0.21", "source": { "type": "git", "url": "https://github.com/wp-cli/media-command.git", - "reference": "49ef52c657de7ac1e50010ce3f6b0cdc40c02dc7" + "reference": "4950ed4ded35c52068d30fec080d545a33baa85c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/media-command/zipball/49ef52c657de7ac1e50010ce3f6b0cdc40c02dc7", - "reference": "49ef52c657de7ac1e50010ce3f6b0cdc40c02dc7", + "url": "https://api.github.com/repos/wp-cli/media-command/zipball/4950ed4ded35c52068d30fec080d545a33baa85c", + "reference": "4950ed4ded35c52068d30fec080d545a33baa85c", "shasum": "" }, "require": { @@ -9017,9 +9142,9 @@ "homepage": "https://github.com/wp-cli/media-command", "support": { "issues": "https://github.com/wp-cli/media-command/issues", - "source": "https://github.com/wp-cli/media-command/tree/v2.0.20" + "source": "https://github.com/wp-cli/media-command/tree/v2.0.21" }, - "time": "2023-09-01T13:08:38+00:00" + "time": "2023-11-10T21:56:52+00:00" }, { "name": "wp-cli/mustangostang-spyc", @@ -9074,20 +9199,20 @@ }, { "name": "wp-cli/package-command", - "version": "v2.4.0", + "version": "v2.5.0", "source": { "type": "git", "url": "https://github.com/wp-cli/package-command.git", - "reference": "f295538382b970cca506172b892f5f1c8adbedb2" + "reference": "71683195f8c27ad97009628e2a72d2a4155503b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/package-command/zipball/f295538382b970cca506172b892f5f1c8adbedb2", - "reference": "f295538382b970cca506172b892f5f1c8adbedb2", + "url": "https://api.github.com/repos/wp-cli/package-command/zipball/71683195f8c27ad97009628e2a72d2a4155503b2", + "reference": "71683195f8c27ad97009628e2a72d2a4155503b2", "shasum": "" }, "require": { - "composer/composer": "^1.10.23 || ~2.2.17", + "composer/composer": "^1.10.23 || ^2.2.17", "ext-json": "*", "wp-cli/wp-cli": "^2.8" }, @@ -9133,22 +9258,22 @@ "homepage": "https://github.com/wp-cli/package-command", "support": { "issues": "https://github.com/wp-cli/package-command/issues", - "source": "https://github.com/wp-cli/package-command/tree/v2.4.0" + "source": "https://github.com/wp-cli/package-command/tree/v2.5.0" }, - "time": "2023-09-06T21:10:16+00:00" + "time": "2023-12-08T10:38:16+00:00" }, { "name": "wp-cli/php-cli-tools", - "version": "v0.11.21", + "version": "v0.11.22", "source": { "type": "git", "url": "https://github.com/wp-cli/php-cli-tools.git", - "reference": "b3457a8d60cd0b1c48cab76ad95df136d266f0b6" + "reference": "a6bb94664ca36d0962f9c2ff25591c315a550c51" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/php-cli-tools/zipball/b3457a8d60cd0b1c48cab76ad95df136d266f0b6", - "reference": "b3457a8d60cd0b1c48cab76ad95df136d266f0b6", + "url": "https://api.github.com/repos/wp-cli/php-cli-tools/zipball/a6bb94664ca36d0962f9c2ff25591c315a550c51", + "reference": "a6bb94664ca36d0962f9c2ff25591c315a550c51", "shasum": "" }, "require": { @@ -9196,9 +9321,9 @@ ], "support": { "issues": "https://github.com/wp-cli/php-cli-tools/issues", - "source": "https://github.com/wp-cli/php-cli-tools/tree/v0.11.21" + "source": "https://github.com/wp-cli/php-cli-tools/tree/v0.11.22" }, - "time": "2023-09-29T15:28:10+00:00" + "time": "2023-12-03T19:25:05+00:00" }, { "name": "wp-cli/rewrite-command", @@ -9329,16 +9454,16 @@ }, { "name": "wp-cli/scaffold-command", - "version": "v2.1.3", + "version": "v2.2.0", "source": { "type": "git", "url": "https://github.com/wp-cli/scaffold-command.git", - "reference": "4125a31134e1bad3d28cff71c178dcee1393f605" + "reference": "8aa906c3ec6ae7d95f38c962fc2561714f9c7145" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/scaffold-command/zipball/4125a31134e1bad3d28cff71c178dcee1393f605", - "reference": "4125a31134e1bad3d28cff71c178dcee1393f605", + "url": "https://api.github.com/repos/wp-cli/scaffold-command/zipball/8aa906c3ec6ae7d95f38c962fc2561714f9c7145", + "reference": "8aa906c3ec6ae7d95f38c962fc2561714f9c7145", "shasum": "" }, "require": { @@ -9389,22 +9514,22 @@ "homepage": "https://github.com/wp-cli/scaffold-command", "support": { "issues": "https://github.com/wp-cli/scaffold-command/issues", - "source": "https://github.com/wp-cli/scaffold-command/tree/v2.1.3" + "source": "https://github.com/wp-cli/scaffold-command/tree/v2.2.0" }, - "time": "2023-08-30T14:29:02+00:00" + "time": "2023-11-16T15:25:33+00:00" }, { "name": "wp-cli/search-replace-command", - "version": "v2.1.3", + "version": "v2.1.4", "source": { "type": "git", "url": "https://github.com/wp-cli/search-replace-command.git", - "reference": "0b662a372483224fe93f113c2a4fa2089e951d34" + "reference": "88c9f23eda4eff4803a3eeeabd46d1a99cd17340" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/search-replace-command/zipball/0b662a372483224fe93f113c2a4fa2089e951d34", - "reference": "0b662a372483224fe93f113c2a4fa2089e951d34", + "url": "https://api.github.com/repos/wp-cli/search-replace-command/zipball/88c9f23eda4eff4803a3eeeabd46d1a99cd17340", + "reference": "88c9f23eda4eff4803a3eeeabd46d1a99cd17340", "shasum": "" }, "require": { @@ -9449,9 +9574,9 @@ "homepage": "https://github.com/wp-cli/search-replace-command", "support": { "issues": "https://github.com/wp-cli/search-replace-command/issues", - "source": "https://github.com/wp-cli/search-replace-command/tree/v2.1.3" + "source": "https://github.com/wp-cli/search-replace-command/tree/v2.1.4" }, - "time": "2023-09-01T12:41:32+00:00" + "time": "2023-12-19T12:41:53+00:00" }, { "name": "wp-cli/server-command", @@ -9768,16 +9893,16 @@ }, { "name": "wp-cli/wp-cli-bundle", - "version": "v2.8.1", + "version": "v2.9.0", "source": { "type": "git", "url": "https://github.com/wp-cli/wp-cli-bundle.git", - "reference": "42e9cb9c16831c31ff720ed9dd1604e0f9871695" + "reference": "3512737e69e55507172e8dce400e09231ba95919" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/wp-cli-bundle/zipball/42e9cb9c16831c31ff720ed9dd1604e0f9871695", - "reference": "42e9cb9c16831c31ff720ed9dd1604e0f9871695", + "url": "https://api.github.com/repos/wp-cli/wp-cli-bundle/zipball/3512737e69e55507172e8dce400e09231ba95919", + "reference": "3512737e69e55507172e8dce400e09231ba95919", "shasum": "" }, "require": { @@ -9808,11 +9933,11 @@ "wp-cli/shell-command": "^2", "wp-cli/super-admin-command": "^2", "wp-cli/widget-command": "^2", - "wp-cli/wp-cli": "^2.8.1" + "wp-cli/wp-cli": "^2.9.0" }, "require-dev": { "roave/security-advisories": "dev-latest", - "wp-cli/wp-cli-tests": "^3.0.7" + "wp-cli/wp-cli-tests": "^4" }, "suggest": { "psy/psysh": "Enhanced `wp shell` functionality" @@ -9820,7 +9945,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.8.x-dev" + "dev-main": "2.9.x-dev" } }, "notification-url": "https://packagist.org/downloads/", @@ -9838,20 +9963,20 @@ "issues": "https://github.com/wp-cli/wp-cli-bundle/issues", "source": "https://github.com/wp-cli/wp-cli-bundle" }, - "time": "2023-06-05T07:33:43+00:00" + "time": "2023-10-25T09:28:58+00:00" }, { "name": "wp-cli/wp-config-transformer", - "version": "v1.3.4", + "version": "v1.3.5", "source": { "type": "git", "url": "https://github.com/wp-cli/wp-config-transformer.git", - "reference": "1f80df413c0d779a813223d9dd5dd58358eee60c" + "reference": "202aa80528939159d52bc4026cee5453aec382db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/wp-config-transformer/zipball/1f80df413c0d779a813223d9dd5dd58358eee60c", - "reference": "1f80df413c0d779a813223d9dd5dd58358eee60c", + "url": "https://api.github.com/repos/wp-cli/wp-config-transformer/zipball/202aa80528939159d52bc4026cee5453aec382db", + "reference": "202aa80528939159d52bc4026cee5453aec382db", "shasum": "" }, "require": { @@ -9880,9 +10005,9 @@ "homepage": "https://github.com/wp-cli/wp-config-transformer", "support": { "issues": "https://github.com/wp-cli/wp-config-transformer/issues", - "source": "https://github.com/wp-cli/wp-config-transformer/tree/v1.3.4" + "source": "https://github.com/wp-cli/wp-config-transformer/tree/v1.3.5" }, - "time": "2023-08-31T10:11:36+00:00" + "time": "2023-11-10T14:28:03+00:00" }, { "name": "wp-coding-standards/wpcs", diff --git a/css/styles.css b/css/styles.css index df80fb5e..f725804a 100644 --- a/css/styles.css +++ b/css/styles.css @@ -277,7 +277,12 @@ Style different log levels. color: rgb(137, 143, 156); margin-top: 0.4em; line-height: 1; - max-height: 1em; + /* max-height: 1em; */ +} + +.SimpleHistoryLogitem__occasionsAddOnsText { + margin: 0.4em 0 0 0; + line-height: 1; } .SimpleHistoryLogitem__details { diff --git a/docker-compose.yml b/docker-compose.yml index 06373894..dbd7fdd3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -32,8 +32,6 @@ services: db: image: "${DB_IMAGE:-mariadb:10.5}" - #image: "${DB_IMAGE:-biarms/mysql:5.5}" - #image: "${DB_IMAGE:-biarms/mysql:5.7}" container_name: simple-history-database restart: "${DOCKER_RESTART_POLICY:-unless-stopped}" environment: @@ -46,8 +44,6 @@ services: - "${DB_EXPOSE_PORT:-127.0.0.1:}:3306" volumes: - "${DB_DATA_DIR:-./data/mysql}:/var/lib/mysql" - #- "${DB_DATA_DIR:-./data/mysql-5.5}:/var/lib/mysql" - #- "${DB_DATA_DIR:-./data/mysql-5.7}:/var/lib/mysql" chrome: image: seleniarm/standalone-chromium:117.0 diff --git a/dropins/class-new-rows-notifier-dropin.php b/dropins/class-new-rows-notifier-dropin.php index ac65c7c5..5e8e91cb 100644 --- a/dropins/class-new-rows-notifier-dropin.php +++ b/dropins/class-new-rows-notifier-dropin.php @@ -90,7 +90,6 @@ public function ajax() { ); } - // $since_id = isset( $_GET["since_id"] ) ? absint($_GET["since_id"]) : null; $logQueryArgs = $apiArgs; $logQuery = new Log_Query(); diff --git a/dropins/class-quick-stats.php b/dropins/class-quick-stats.php index 8b9744b4..958ab131 100644 --- a/dropins/class-quick-stats.php +++ b/dropins/class-quick-stats.php @@ -57,7 +57,7 @@ public function output_quick_stats() { ); $cache_key = 'quick_stats_users_today_' . md5( serialize( $sql_loggers_in ) ); - $cache_group = 'simple-history-' . Helpers::get_cache_incrementor(); + $cache_group = Helpers::get_cache_group(); $results_users_today = wp_cache_get( $cache_key, $cache_group ); if ( false === $results_users_today ) { diff --git a/dropins/class-sidebar-add-ons-dropin.php b/dropins/class-sidebar-add-ons-dropin.php new file mode 100644 index 00000000..cf2dcd1c --- /dev/null +++ b/dropins/class-sidebar-add-ons-dropin.php @@ -0,0 +1,65 @@ + +
+ +

+ + New +

+ +
+

+ +

+ + +
  • + + +
  • + +
  • + + +
  • + +
  • + + +
  • + + */ + ?> + +

    + + + +

    +
    +
    + query( $sql ); - self::get_cache_incrementor( true ); + self::clear_cache(); return $num_rows; } @@ -1127,31 +1146,55 @@ public static function get_num_events_last_n_days( $period_days = 28 ) { */ public static function get_num_events_per_day_last_n_days( $period_days = 28 ) { $simple_history = Simple_History::get_instance(); - $transient_key = 'sh_' . md5( __METHOD__ . $period_days . '_2' ); + $transient_key = 'sh_' . md5( __METHOD__ . $period_days . '_3' ); $dates = get_transient( $transient_key ); if ( false === $dates ) { + /** @var \wpdb $wpdb */ global $wpdb; $sqlStringLoggersUserCanRead = $simple_history->get_loggers_that_user_can_read( null, 'sql' ); - $sql = sprintf( - ' - SELECT - date_format(date, "%%Y-%%m-%%d") AS yearDate, - count(date) AS count - FROM - %1$s - WHERE - UNIX_TIMESTAMP(date) >= %2$d - AND logger IN (%3$d) - GROUP BY yearDate - ORDER BY yearDate ASC - ', - $simple_history->get_events_table_name(), - strtotime( "-$period_days days" ), - $sqlStringLoggersUserCanRead - ); + $db_engine = Log_Query::get_db_engine(); + + if ( $db_engine === 'mysql' ) { + $sql = sprintf( + ' + SELECT + date_format(date, "%%Y-%%m-%%d") AS yearDate, + count(date) AS count + FROM + %1$s + WHERE + UNIX_TIMESTAMP(date) >= %2$d + AND logger IN %3$s + GROUP BY yearDate + ORDER BY yearDate ASC + ', + $simple_history->get_events_table_name(), + strtotime( "-$period_days days" ), + $sqlStringLoggersUserCanRead + ); + } elseif ( $db_engine === 'sqlite' ) { + // SQLite does not support date_format() or UNIX_TIMESTAMP so we need to use strftime(). + $sql = sprintf( + ' + SELECT + strftime("%%Y-%%m-%%d", date) AS yearDate, + count(date) AS count + FROM + %1$s + WHERE + unixepoch(date) >= %2$d + AND logger IN %3$s + GROUP BY yearDate + ORDER BY yearDate ASC + ', + $simple_history->get_events_table_name(), + strtotime( "-$period_days days" ), + $sqlStringLoggersUserCanRead + ); + } $dates = $wpdb->get_results( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared diff --git a/inc/class-log-query.php b/inc/class-log-query.php index 121174dd..e8719c35 100644 --- a/inc/class-log-query.php +++ b/inc/class-log-query.php @@ -6,19 +6,36 @@ /** * Queries the Simple History Log. + * + * Todo + * - Occasions should check user permissions, or otherwise it can add any id and the user will have access to it. + * - Test if fix for full group is working. + * - [x] Finish query_overview_full_group_by() to return same data as query_overview(), and then compare and verify that it returns same data. + * - Also print SQL query and do some EXPLAIN on it in a regular editor. If this works it would be nice to blog about the findings, + * and print benchmarks etc. + * - [x] Get num rows using second query with count(*) + * - [x] Add limit. + * - [x] Test in MySQL 5.5, 5.7, MariaDB 10.4. + * - [x] Add support for SQLite. + * - [x] Use get_cache_group + * - [x] Use clear_cache instead of (true) + * - [x] Date filtering is broken (sql where clause missing/not added) + * - [x] Add caching to SQLite + * - [x] Add tests for single event occasions. + * - [x] Add tests for log row notifier. + * - [ ] Run PHPStan and Rector. + * - [ ] Merge together all git commits to one commit with close-##-messages. */ class Log_Query { - /** * Query the log. * * @param string|array|object $args { * Optional. Array or string of arguments for querying the log. * @type string $type Type of query. Accepts 'overview', 'occasions', or 'single'. Default 'overview'. - * @type int $posts_per_page Number of posts to show per page. 0 to show all. Default 0. + * @type int $posts_per_page Number of posts to show per page. Default is 10. * @type int $paged Page to show. 1 = first page. Default 1. * @type array $post__in Array. Only get posts that are in array. Default null. - * @type string $format Array or html. Default 'array'. * @type int $max_id_first_page If max_id_first_page is set then only get rows that have id equal or lower than this, to make * sure that the first page of results is not too large. Default null. * @type int $since_id If since_id is set the rows returned will only be rows with an ID greater than (i.e. more recent than) since_id. Default null. @@ -28,277 +45,1012 @@ class Log_Query { * @type array|string $dates Dates in format "month:2015-06" for june 2015 or "lastdays:7" for the last 7 days. Default null. * @type string $search Text to search for. Message, logger and level are searched for in main table. Values are searched for in context table. Default null. * @type string $loglevels Log levels to include. Comma separated or as array. Defaults to all. Default null. - * @type string $loggers Loggers to include. Comma separated. Defaults to all the user can read. Default null. - * @type string $messages Messages to include. Comma separated. Defaults to all. Default null. - * @type int $user User ID as number. Default null. - * @type string $users User IDs, comma separated. Default null. + * @type string $loggers Loggers to include. Comma separated or array. Default null = all the user can read. + * @type string $messages Messages to include. Array or string with commaa separated in format "LoggerSlug:Message", e.g. "SimplePluginLogger:plugin_activated,SimplePluginLogger:plugin_deactivated". Default null = show all messages. + * @type int $user Single user ID as number. Default null. + * @type string $users User IDs, comma separated or array. Default null. * } * @return array + * @throws \InvalidArgumentException If invalid query type. */ public function query( $args ) { - $users_in = null; - $sql_user = null; - $sql_tmpl = null; - $defaults = array( + $args = wp_parse_args( $args ); - // overview | occasions. - 'type' => 'overview', + // Determine kind of query. + $type = $args['type'] ?? 'overview'; - // Number of posts to show per page. 0 to show all. - 'posts_per_page' => 0, + if ( $type === 'overview' || $type === 'single' ) { + return $this->query_overview( $args ); + } elseif ( $type === 'occasions' ) { + return $this->query_occasions( $args ); + } else { + throw new \InvalidArgumentException( 'Invalid query type' ); + } + } - // Page to show. 1 = first page. - 'paged' => 1, + /** + * Query history using a query that uses full group by, + * making it compatible with both MySQL 5.5, 5.7 and MariaDB. + * + * Subequent occasions query thanks to the answer Stack Overflow thread: + * http://stackoverflow.com/questions/13566303/how-to-group-subsequent-rows-based-on-a-criteria-and-then-count-them-mysql/13567320#13567320 + * + * @param string|array|object $args Arguments. + * @return array Log rows. + * @throws \ErrorException If invalid DB engine. + */ + public function query_overview( $args ) { + $db_engine = $this->get_db_engine(); + + if ( $db_engine === 'mysql' ) { + // Call usual method. + return $this->query_overview_mysql( $args ); + } else if ( $db_engine === 'sqlite' ) { + // Call sqlite method. + return $this->query_overview_sqlite( $args ); + } else { + throw new \ErrorException( 'Invalid DB engine' ); + } + } - // Array. Only get posts that are in array. - 'post__in' => null, + /** + * SQLite compatible version of query_overview_mysql(). + * Main difference is that the SQL query is simpler, + * because it does not support occasions. + * + * @param string|array|object $args Arguments. + * @return array Log rows. + */ + protected function query_overview_sqlite( $args ) { + $args = $this->prepare_args( $args ); - // array or html. - 'format' => 'array', + // Create cache key based on args and current user. + $cache_key = md5( __METHOD__ . serialize( $args ) ) . '_userid_' . get_current_user_id(); + $cache_group = Helpers::get_cache_group(); - // If max_id_first_page is set then only get rows - // that have id equal or lower than this, to make. - 'max_id_first_page' => null, + /** @var array|false Return value. */ + $arr_return = wp_cache_get( $cache_key, $cache_group ); - // if since_id is set the rows returned will only be rows with an ID greater than (i.e. more recent than) since_id. - 'since_id' => null, + // Return cached value if it exists. + if ( false !== $arr_return ) { + $arr_return['cached_result'] = true; + return $arr_return; + } - /** - * From date, as unix timestamp integer or as a format compatible with strtotime, for example 'Y-m-d H:i:s'. - * - * @var int|string - */ - 'date_from' => null, + global $wpdb; - /** - * To date, as unix timestamp integer or as a format compatible with strtotime, for example 'Y-m-d H:i:s'. - * - * @var int|string - */ - 'date_to' => null, + $Simple_History = Simple_History::get_instance(); - // months in format "Y-m" - // array or comma separated. - 'months' => null, + /** + * @var string SQL template used to get all events from the ones + * found in statement sql_statement_max_ids_and_count_template. + * This final statement gets all columns we finally need. + */ + $sql_statement_log_rows = ' + SELECT + simple_history_1.id, + simple_history_1.logger, + simple_history_1.level, + simple_history_1.date, + simple_history_1.message, + simple_history_1.initiator, + simple_history_1.occasionsID, + 1 AS repeatCount, + 1 AS subsequentOccasions + FROM %1$s AS simple_history_1 + %2$s + ORDER BY simple_history_1.id DESC + %3$s + '; + + $inner_where_array = $this->get_inner_where( $args ); + $inner_where_string = empty( $inner_where_array ) ? '' : "\nWHERE " . implode( "\nAND ", $inner_where_array ); + + /** @var int $limit_offset */ + $limit_offset = ( $args['paged'] - 1 ) * $args['posts_per_page']; + + /** @var string Limit clause. */ + $limit_clause = sprintf( 'LIMIT %1$d, %2$d', $limit_offset, $args['posts_per_page'] ); + + $sql_query_log_rows = sprintf( + $sql_statement_log_rows, + $Simple_History->get_events_table_name(), // 1 + $inner_where_string, // 2 + $limit_clause // 3 + ); - // dates in format - // "month:2015-06" for june 2015 - // "lastdays:7" for the last 7 days. - 'dates' => null, + $result_log_rows = $wpdb->get_results( $sql_query_log_rows, OBJECT_K ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared - /** - * Text to search for. - * Message, logger and level are searched for in main table. - * Values are searched for in context table. - * - * @var string - */ - 'search' => null, + if ( ! empty( $wpdb->last_error ) ) { + exit; + } - // log levels to include. comma separated or as array. defaults to all. - 'loglevels' => null, + // Append context to log rows. + $result_log_rows = $this->add_contexts_to_log_rows( $result_log_rows ); - // loggers to include. comma separated. defaults to all the user can read. - 'loggers' => null, + // Re-index array. + $result_log_rows = array_values( $result_log_rows ); - 'messages' => null, + // Like $sql_statement_log_rows but all columns is replaced by a single COUNT(*). + $sql_statement_log_rows_count = ' + SELECT count(*) as count + FROM %1$s AS simple_history_1 + %2$s + ORDER BY simple_history_1.id DESC + '; - // userID as number. - 'user' => null, + $sql_query_log_rows_count = sprintf( + $sql_statement_log_rows_count, + $Simple_History->get_events_table_name(), // 1 + $inner_where_string, // 2 + ); - // user ids, comma separated. - 'users' => null, + $total_found_rows = $wpdb->get_var( $sql_query_log_rows_count ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared - // Can also contain: - // occasionsCount - // occasionsCountMaxReturn - // occasionsID. + // Calc pages. + $pages_count = Ceil( $total_found_rows / $args['posts_per_page'] ); + + // Calc pagination info. + $log_rows_count = count( $result_log_rows ); + $page_rows_from = ( $args['paged'] * $args['posts_per_page'] ) - $args['posts_per_page'] + 1; + $page_rows_to = $page_rows_from + $log_rows_count - 1; + + // Get maxId and minId. + // MaxId is the id of the first row in the result (i.e. the latest entry). + // MinId is the id of the last row in the result (i.e. the oldest entry). + $min_id = null; + $max_id = null; + if ( sizeof( $result_log_rows ) > 0 ) { + $max_id = $result_log_rows[0]->id; + $min_id = $result_log_rows[ count( $result_log_rows ) - 1 ]->id; + } + + // Create array to return. + // Add log rows to sub key 'log_rows' because meta info is also added. + $arr_return = [ + 'total_row_count' => (int) $total_found_rows, + 'pages_count' => $pages_count, + 'page_current' => $args['paged'], + 'page_rows_from' => $page_rows_from, + 'page_rows_to' => $page_rows_to, + 'max_id' => (int) $max_id, + 'min_id' => (int) $min_id, + 'log_rows_count' => $log_rows_count, + 'log_rows' => $result_log_rows, + ]; + + wp_cache_set( $cache_key, $arr_return, $cache_group ); + + return $arr_return; + } + + /** + * @param string|array|object $args Arguments. + * @return array Log rows. + */ + protected function query_overview_mysql( $args ) { + // Parse and prepare args. + $args = $this->prepare_args( $args ); + + // Create cache key based on args and current user. + $cache_key = md5( __METHOD__ . serialize( $args ) ) . '_userid_' . get_current_user_id(); + $cache_group = Helpers::get_cache_group(); + + /** @var array|false Return value. */ + $arr_return = wp_cache_get( $cache_key, $cache_group ); + + // Return cached value if it exists. + if ( false !== $arr_return ) { + $arr_return['cached_result'] = true; + return $arr_return; + } + + global $wpdb; + + $Simple_History = Simple_History::get_instance(); + + $wpdb->query( 'SET @a:=NULL, @counter:=1, @groupby:=0' ); + + /** + * @var string SQL statement that will be used for inner join. + * + * Template uses number argument to sprintf to insert values. + * Arguments: + * 1 = table name for events. + * 2 = table name for contexts. + * 2 = where clause. + * + * TODO: Add where for messages. Check that both logger and key are correct. + */ + $inner_sql_statement_template = ' + + + ## START INNER_SQL_QUERY_STATEMENT + SELECT + id, + #message, + IF(@a=occasionsID,@counter:=@counter+1,@counter:=1) AS repeatCount, + IF(@counter=1,@groupby:=@groupby+1,@groupby) AS repeated, + @a:=occasionsId, + contexts.value as context_message_key + FROM %1$s AS h2 + + # Join column with message key so its searchable/filterable. + LEFT OUTER JOIN %2$s AS contexts ON (contexts.history_id = h2.id AND contexts.key = "_message_key") + + # Where statement. + %3$s + + ORDER BY id DESC + ## END INNER_SQL_QUERY_STATEMENT + + + '; + + $inner_where_array = $this->get_inner_where( $args ); + $inner_where_string = empty( $inner_where_array ) ? '' : "\nWHERE " . implode( "\nAND ", $inner_where_array ); + + $inner_sql_query_statement = sprintf( + $inner_sql_statement_template, + $Simple_History->get_events_table_name(), // 1 + $Simple_History->get_contexts_table_name(), // 2 + $inner_where_string // 3 ); - $args = wp_parse_args( $args, $defaults ); + /** + * @var string SQL statement template used to get IDs of all events. + * + * Template uses number argument to sprintf to insert values. + * Arguments: + * 1 = table name for events. + * 2 = table name for contexts. + * 3 = Inner join SQL query. + * 4 = where clause for outer query. + * 5 = limit clause. + */ + $sql_statement_max_ids_and_count_template = ' + + + ## START SQL_STATEMENT_MAX_IDS_AND_COUNT_TEMPLATE + SELECT + max(h.id) as maxId, + min(h.id) as minId, + max(historyWithRepeated.repeatCount) as repeatCount + FROM %1$s AS h + + INNER JOIN ( + %3$s + ) as historyWithRepeated ON historyWithRepeated.id = h.id + + # Outer where + %4$s + + GROUP BY historyWithRepeated.repeated + ORDER by maxId DESC + + # Limit + %5$s + + + ## END SQL_STATEMENT_MAX_IDS_AND_COUNT_TEMPLATE + '; + + /** @var string Inner where clause, including "where" if has values. */ + $inner_where_string = ''; + + $inner_where_array = $this->get_inner_where( $args ); + if ( ! empty( $inner_where_array ) ) { + $inner_where_string = "\nWHERE\n" . implode( "\nAND ", $inner_where_array ); + } + + /** @var string Outer where clause, including "where" if has values. */ + $outer_where_string = ''; - // Create cache key based on args and request and current user. - $cache_key = 'SimpleHistoryLogQuery_' . md5( serialize( $args ) ) . '_get_' . md5( serialize( $_GET ) ) . '_userid_' . get_current_user_id(); - $cache_group = 'simple-history-' . Helpers::get_cache_incrementor(); + $outer_where_array = $this->get_outer_where( $args ); + if ( ! empty( $outer_where_array ) ) { + $outer_where_string = "\nWHERE " . implode( "\nAND ", $outer_where_array ); + } + + /** @var int $limit_offset */ + $limit_offset = ( $args['paged'] - 1 ) * $args['posts_per_page']; + + /** @var string Limit clause. */ + $limit_clause = sprintf( 'LIMIT %1$d, %2$d', $limit_offset, $args['posts_per_page'] ); + + $max_ids_and_count_sql_statement = sprintf( + $sql_statement_max_ids_and_count_template, + $Simple_History->get_events_table_name(), // 1 + $Simple_History->get_contexts_table_name(), // 2 + $inner_sql_query_statement, // 3 + $outer_where_string, // 4 + $limit_clause // 5 Limit clause. + ); + + /** + * @var string SQL template used to get all events from the ones + * found in statement sql_statement_max_ids_and_count_template. + * This final statement gets all columns we finally need. + */ + $sql_statement_log_rows = ' + + + ## START SQL_STATEMENT_LOG_ROWS + SELECT + simple_history_1.id, + maxId, + minId, + simple_history_1.logger, + simple_history_1.level, + simple_history_1.date, + simple_history_1.message, + simple_history_1.initiator, + simple_history_1.occasionsID, + repeatCount, + repeatCount AS subsequentOccasions + + FROM %1$s AS simple_history_1 + + INNER JOIN ( + %2$s + ) AS max_ids_and_count ON simple_history_1.id = max_ids_and_count.maxId + + ORDER BY simple_history_1.id DESC + ## END SQL_STATEMENT_LOG_ROWS + + + '; + + $sql_query_log_rows = sprintf( + $sql_statement_log_rows, + $Simple_History->get_events_table_name(), // 1 + $max_ids_and_count_sql_statement // 2 + ); + + $result_log_rows = $wpdb->get_results( $sql_query_log_rows, OBJECT_K ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + + if ( ! empty( $wpdb->last_error ) ) { + exit; + } + + // Append context to log rows. + $result_log_rows = $this->add_contexts_to_log_rows( $result_log_rows ); + + // Re-index array. + $result_log_rows = array_values( $result_log_rows ); + + // Get max id and min id. + // Max id is the id of the first row in the result (i.e. the latest entry). + // Min id is the minId value of the last row in the result (i.e. the oldest entry). + $min_id = null; + $max_id = null; + if ( sizeof( $result_log_rows ) > 0 ) { + $max_id = $result_log_rows[0]->id; + $min_id = $result_log_rows[ count( $result_log_rows ) - 1 ]->minId; + } + + // Like $sql_statement_log_rows but all columns is replaced by a single COUNT(*). + $sql_statement_log_rows_count = ' + ## START SQL_STATEMENT_LOG_ROWS + SELECT + count(*) as count + + FROM %1$s AS simple_history_1 + + INNER JOIN ( + %2$s + ) AS max_ids_and_count ON simple_history_1.id = max_ids_and_count.maxId + + ORDER BY simple_history_1.id DESC + ## END SQL_STATEMENT_LOG_ROWS + '; + + // Create $max_ids_and_count_sql_statement without limit, + // to get count(*). + $max_ids_and_count_without_limit_sql_statement = sprintf( + $sql_statement_max_ids_and_count_template, + $Simple_History->get_events_table_name(), // 1 + $Simple_History->get_contexts_table_name(), // 2 + $inner_sql_query_statement, // 3 + $outer_where_string, // 4 + '', // 5 Limit clause. + ); + + $sql_query_log_rows_count = sprintf( + $sql_statement_log_rows_count, + $Simple_History->get_events_table_name(), // 1 + $max_ids_and_count_without_limit_sql_statement // 2 + ); + + $total_found_rows = $wpdb->get_var( $sql_query_log_rows_count ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + + // Calc pages. + $pages_count = Ceil( $total_found_rows / $args['posts_per_page'] ); + + // Calc pagination info. + $log_rows_count = count( $result_log_rows ); + $page_rows_from = ( $args['paged'] * $args['posts_per_page'] ) - $args['posts_per_page'] + 1; + $page_rows_to = $page_rows_from + $log_rows_count - 1; + + // Create array to return. + // Add log rows to sub key 'log_rows' because meta info is also added. + $arr_return = [ + 'total_row_count' => (int) $total_found_rows, + 'pages_count' => $pages_count, + 'page_current' => $args['paged'], + 'page_rows_from' => $page_rows_from, + 'page_rows_to' => $page_rows_to, + 'max_id' => (int) $max_id, + 'min_id' => (int) $min_id, + 'log_rows_count' => $log_rows_count, + // Remove id from keys, because they are cumbersome when working with JSON. + 'log_rows' => $result_log_rows, + ]; + + wp_cache_set( $cache_key, $arr_return, $cache_group ); + + return $arr_return; + } + + /** + * Get occasions for a single event. + * + * Required args are: + * - occasionsID: The id to get occasions for. + * - occasionsCount: The number of occasions to get. + * - occasionsCountMaxReturn: The max number of occasions to return. + * + * Does not take filters/where into consideration. + * + * @param string|array|object $args Arguments. + * @return array + */ + protected function query_occasions( $args ) { + // Create cache key based on args and current user. + $cache_key = 'SimpleHistoryLogQuery_' . md5( serialize( $args ) ) . '_userid_' . get_current_user_id(); + $cache_group = Helpers::get_cache_group(); + + /** @var array Return value. */ $arr_return = wp_cache_get( $cache_key, $cache_group ); + // Return cached value if it exists. if ( false !== $arr_return ) { $arr_return['cached_result'] = true; return $arr_return; } - /* - Subequent occasions query thanks to this Stack Overflow thread: - http://stackoverflow.com/questions/13566303/how-to-group-subsequent-rows-based-on-a-criteria-and-then-count-them-mysql/13567320#13567320 - Similar questions that I didn't manage to understand, work, or did try: - - http://stackoverflow.com/questions/23651176/mysql-query-if-dates-are-subsequent - - http://stackoverflow.com/questions/17651868/mysql-group-by-subsequent - - http://stackoverflow.com/questions/4495242/mysql-number-of-subsequent-occurrences - - http://stackoverflow.com/questions/20446242/postgresql-group-subsequent-rows - - http://stackoverflow.com/questions/17061156/mysql-group-by-range - - http://stackoverflow.com/questions/6602006/complicated-query-with-group-by-and-range-of-prices-in-mysql - */ + $simpe_history = Simple_History::get_instance(); + $events_table_name = $simpe_history->get_events_table_name(); + $contexts_table_name = $simpe_history->get_contexts_table_name(); + + $args = wp_parse_args( + $args, + [ + 'type' => 'occasions', + 'logRowID' => null, + 'occasionsID' => null, + 'occasionsCount' => null, + 'occasionsCountMaxReturn' => null, + ] + ); + + $args = $this->prepare_args( $args ); + + // Get occasions for a single event. + // Args must contain: + // - occasionsID: The id to get occasions for + // - occasionsCount: The number of occasions to get. + // - occasionsCountMaxReturn: The max number of occasions to return, + // if occasionsCount is very large and we do not want to get all occasions. + + /** + * @var string $sql_statement_template SQL template for occasions query. + * Template uses number argument to sprintf to insert values. + * Arguments: + * 1 = where clause. + * 2 = limit clause. + * 3 = table name for events. + */ + $sql_statement_template = ' + SELECT + h.id, + h.logger, + h.level, + h.date, + h.message, + h.initiator, + h.occasionsID, + c1.value AS context_message_key, + # Hard code subsequentOccasions column that exist in overview query + 1 as subsequentOccasions + FROM %3$s AS h + + # Add context message key + LEFT OUTER JOIN %4$s AS c1 ON (c1.history_id = h.id AND c1.key = "_message_key") + + # Where + %1$s + + ORDER BY id DESC + %2$s + '; + + /** @var array Where clauses for outer query. */ + $outer_where = []; + + // Get rows with id lower than logRowID, i.e. previous rows. + $outer_where[] = 'h.id < ' . (int) $args['logRowID']; + + // Get rows with occasionsID equal to occasionsID. + $outer_where[] = "h.occasionsID = '" . esc_sql( $args['occasionsID'] ) . "'"; + + if ( isset( $args['occasionsCountMaxReturn'] ) && $args['occasionsCountMaxReturn'] < $args['occasionsCount'] ) { + // Limit to max nn events if occasionsCountMaxReturn is set. + // Used for example in GUI to prevent to many events returned, that can stall the browser. + $limit = 'LIMIT ' . $args['occasionsCountMaxReturn']; + } else { + // Regular limit that gets all occasions. + $limit = 'LIMIT ' . $args['occasionsCount']; + } + + // Create where string. + $outer_where = implode( "\nAND ", $outer_where ); + + // Append where to sql template. + if ( ! empty( $outer_where ) ) { + $outer_where = "\nWHERE {$outer_where}"; + } + + /** @var string SQL generated from template. */ + $sql_query = sprintf( + $sql_statement_template, // sprintf template. + $outer_where, // 1 + $limit, // 2 + $events_table_name, // 3 + $contexts_table_name // 4 + ); + + global $wpdb; + + /** @var array Log rows matching where queries. */ + $log_rows = $wpdb->get_results( $sql_query, OBJECT_K ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + + $log_rows = $this->add_contexts_to_log_rows( $log_rows ); + + return [ + // Remove id from keys, because they are cumbersome when working with JSON. + 'log_rows' => array_values( $log_rows ), + 'sql' => $sql_query, + ]; + } + + /** + * Prepare arguments, i.e. checking that they are valid, + * of the correct type, etc. + * + * @param array $args Argument. + * @return array + * @throws \InvalidArgumentException If invalid type. + */ + protected function prepare_args( $args ) { + /** @var array Query arguments. */ + $args = wp_parse_args( + $args, + [ + // overview | occasions | single. + // When type is occasions then logRowID, occasionsID, occasionsCount, occasionsCountMaxReturn are required. + 'type' => 'overview', + + // Number of posts to show per page. 0 to show all. + 'posts_per_page' => 10, + + // Page to show. 1 = first page. + 'paged' => 1, + + // Array. Only get posts that are in array. + 'post__in' => [], + + // If max_id_first_page is set then only get rows + // that have id equal or lower than this, to make. + 'max_id_first_page' => null, + + // if since_id is set the rows returned will only be rows with an ID greater than (i.e. more recent than) since_id. + 'since_id' => null, + + /** + * From date, as unix timestamp integer or as a format compatible with strtotime, for example 'Y-m-d H:i:s'. + * + * @var int|string + */ + 'date_from' => null, + + /** + * To date, as unix timestamp integer or as a format compatible with strtotime, for example 'Y-m-d H:i:s'. + * + * @var int|string + */ + 'date_to' => null, + + // months in format "Y-m" + // array or comma separated. + 'months' => null, + + // dates in format + // "month:2015-06" for june 2015 + // "lastdays:7" for the last 7 days. + 'dates' => null, + + /** + * Text to search for. + * Message, logger and level are searched for in main table. + * Values are searched for in context table. + * + * @var string + */ + 'search' => null, + + // log levels to include. comma separated or as array. defaults to all. + 'loglevels' => null, + + // loggers to include. comma separated. defaults to all the user can read. + 'loggers' => null, + + 'messages' => null, + + // userID as number. + 'user' => null, + + // User ids, comma separated or array. + 'users' => null, + + // Can also contain: + // logRowID + // occasionsCount + // occasionsCountMaxReturn + // occasionsID. + ] + ); + + // Type must be string and any of "overview", "occasions", "single". + if ( ! is_string( $args['type'] ) && ! in_array( $args['type'], [ 'overview', 'occasions', 'single' ], true ) ) { + throw new \InvalidArgumentException( 'Invalid type' ); + } + + // If occasionsCountMaxReturn is set then it must be an integer. + if ( isset( $args['occasionsCountMaxReturn'] ) && ! is_numeric( $args['occasionsCountMaxReturn'] ) ) { + throw new \InvalidArgumentException( 'Invalid occasionsCountMaxReturn' ); + } elseif ( isset( $args['occasionsCountMaxReturn'] ) ) { + $args['occasionsCountMaxReturn'] = (int) $args['occasionsCountMaxReturn']; + } + + // If occasionsCount is set then it must be an integer. + if ( isset( $args['occasionsCount'] ) && ! is_numeric( $args['occasionsCount'] ) ) { + throw new \InvalidArgumentException( 'Invalid occasionsCount' ); + } elseif ( isset( $args['occasionsCount'] ) ) { + $args['occasionsCount'] = (int) $args['occasionsCount']; + } + + // If posts_per_page is set then it must be a positive integer. + if ( isset( $args['posts_per_page'] ) && ( ! is_numeric( $args['posts_per_page'] ) || $args['posts_per_page'] < 1 ) ) { + throw new \InvalidArgumentException( 'Invalid posts_per_page' ); + } elseif ( isset( $args['posts_per_page'] ) ) { + $args['posts_per_page'] = (int) $args['posts_per_page']; + } + + // paged must be must be a positive integer. + if ( isset( $args['paged'] ) && ( ! is_numeric( $args['paged'] ) || $args['paged'] < 1 ) ) { + throw new \InvalidArgumentException( 'Invalid paged' ); + } elseif ( isset( $args['paged'] ) ) { + $args['paged'] = (int) $args['paged']; + } + + // "post__in" must be array and must only contain integers. + if ( isset( $args['post__in'] ) && ! is_array( $args['post__in'] ) ) { + throw new \InvalidArgumentException( 'Invalid post__in' ); + } elseif ( isset( $args['post__in'] ) ) { + $args['post__in'] = array_map( 'intval', $args['post__in'] ); + $args['post__in'] = array_filter( $args['post__in'] ); + } + + // "max_id_first_page" must be integer. + if ( isset( $args['max_id_first_page'] ) && ! is_numeric( $args['max_id_first_page'] ) ) { + throw new \InvalidArgumentException( 'Invalid max_id_first_page' ); + } elseif ( isset( $args['max_id_first_page'] ) ) { + $args['max_id_first_page'] = (int) $args['max_id_first_page']; + } + + // "since_id" must be integer. + if ( isset( $args['since_id'] ) && ! is_numeric( $args['since_id'] ) ) { + throw new \InvalidArgumentException( 'Invalid since_id' ); + } elseif ( isset( $args['since_id'] ) ) { + $args['since_id'] = (int) $args['since_id']; + } + + // "date_from" must be timestamp or string. If string then convert to timestamp. + if ( isset( $args['date_from'] ) && ! is_numeric( $args['date_from'] ) ) { + $args['date_from'] = strtotime( $args['date_from'] ); + } elseif ( isset( $args['date_from'] ) && is_numeric( $args['date_from'] ) ) { + $args['date_from'] = (int) $args['date_from']; + } elseif ( isset( $args['date_from'] ) && is_string( $args['date_from'] ) ) { + $args['date_from'] = (int) $args['date_from']; + } elseif ( isset( $args['date_from'] ) ) { + throw new \InvalidArgumentException( 'Invalid date_from' ); + } + + // "date_to" must be timestamp or string. If string then convert to timestamp. + if ( isset( $args['date_to'] ) && ! is_numeric( $args['date_to'] ) ) { + $args['date_to'] = strtotime( $args['date_to'] ); + } elseif ( isset( $args['date_to'] ) && is_string( $args['date_to'] ) ) { + $args['date_to'] = (int) $args['date_to']; + } elseif ( isset( $args['date_to'] ) ) { + throw new \InvalidArgumentException( 'Invalid date_to' ); + } + + // "search" must be string. + if ( isset( $args['search'] ) && ! is_string( $args['search'] ) ) { + throw new \InvalidArgumentException( 'Invalid search' ); + } + + // "loglevels" must be comma separeated string "info,debug" + // or array of log level strings. + if ( isset( $args['loglevels'] ) && ! is_string( $args['loglevels'] ) && ! is_array( $args['loglevels'] ) ) { + throw new \InvalidArgumentException( 'Invalid loglevels' ); + } elseif ( isset( $args['loglevels'] ) && is_string( $args['loglevels'] ) ) { + $args['loglevels'] = explode( ',', $args['loglevels'] ); + } + + // Make sure loglevels are trimmed, strings, and empty vals removed. + if ( isset( $args['loglevels'] ) ) { + $args['loglevels'] = array_map( 'trim', $args['loglevels'] ); + $args['loglevels'] = array_map( 'strval', $args['loglevels'] ); + $args['loglevels'] = array_filter( $args['loglevels'] ); + } + + // "messages" is string with comma separated loggers and messages, + // or array with comma separated loggers and messages. + // Array example: + // Array + // ( + // [0] => SimpleCommentsLogger:anon_comment_added,SimpleCommentsLogger:user_comment_added,SimpleCommentsLogger:anon_trackback_added,SimpleCommentsLogger:user_trackback_added,SimpleCommentsLogger:anon_pingback_added,SimpleCommentsLogger:user_pingback_added,SimpleCommentsLogger:comment_edited,SimpleCommentsLogger:trackback_edited,SimpleCommentsLogger:pingback_edited,SimpleCommentsLogger:comment_status_approve,SimpleCommentsLogger:trackback_status_approve,SimpleCommentsLogger:pingback_status_approve,SimpleCommentsLogger:comment_status_hold,SimpleCommentsLogger:trackback_status_hold,SimpleCommentsLogger:pingback_status_hold,SimpleCommentsLogger:comment_status_spam,SimpleCommentsLogger:trackback_status_spam,SimpleCommentsLogger:pingback_status_spam,SimpleCommentsLogger:comment_status_trash,SimpleCommentsLogger:trackback_status_trash,SimpleCommentsLogger:pingback_status_trash,SimpleCommentsLogger:comment_untrashed,SimpleCommentsLogger:trackback_untrashed,SimpleCommentsLogger:pingback_untrashed,SimpleCommentsLogger:comment_deleted,SimpleCommentsLogger:trackback_deleted,SimpleCommentsLogger:pingback_deleted + // [1] => SimpleCommentsLogger:SimpleCommentsLogger:comment_status_spam,SimpleCommentsLogger:trackback_status_spam,SimpleCommentsLogger:pingback_status_spam + // ) + if ( isset( $args['messages'] ) && ! is_string( $args['messages'] ) && ! is_array( $args['messages'] ) ) { + throw new \InvalidArgumentException( 'Invalid messages' ); + } elseif ( isset( $args['messages'] ) && is_string( $args['messages'] ) ) { + $args['messages'] = explode( ',', $args['messages'] ); + } elseif ( isset( $args['messages'] ) && is_array( $args['messages'] ) ) { + // Turn multi dimensional array into single array with strings. + $arr_messages = []; + foreach ( $args['messages'] as $one_arr_messages_row ) { + $arr_messages = array_merge( $arr_messages, explode( ',', $one_arr_messages_row ) ); + } + + $args['messages'] = $arr_messages; + } + + // Make sure messages are trimmed, strings, and empty vals removed. + if ( isset( $args['messages'] ) ) { + $args['messages'] = array_map( 'trim', $args['messages'] ); + $args['messages'] = array_map( 'strval', $args['messages'] ); + $args['messages'] = array_filter( $args['messages'] ); + + $arr_loggers_and_messages = []; + + // Transform to format where + // - key = logger slug. + // - value = array of logger messages.. + foreach ( $args['messages'] as $one_row_logger_and_message ) { + $arr_one_logger_and_message = explode( ':', $one_row_logger_and_message ); + + if ( ! isset( $arr_loggers_and_messages[ $arr_one_logger_and_message[0] ] ) ) { + $arr_loggers_and_messages[ $arr_one_logger_and_message[0] ] = array(); + } + + $arr_loggers_and_messages[ $arr_one_logger_and_message[0] ][] = $arr_one_logger_and_message[1]; + } + + $args['messages'] = $arr_loggers_and_messages; + } + + // "loggers", comma separated string or array with strings. + // Example format: "AvailableUpdatesLogger,SimpleuserLogger". + if ( isset( $args['loggers'] ) && ! is_string( $args['loggers'] ) && ! is_array( $args['loggers'] ) ) { + throw new \InvalidArgumentException( 'Invalid loggers' ); + } elseif ( isset( $args['loggers'] ) && is_string( $args['loggers'] ) ) { + $args['loggers'] = explode( ',', $args['loggers'] ); + } + + // "user" must be integer. + if ( isset( $args['user'] ) && ! is_numeric( $args['user'] ) ) { + throw new \InvalidArgumentException( 'Invalid user' ); + } elseif ( isset( $args['user'] ) ) { + $args['user'] = (int) $args['user']; + } + + // "users" must be comma separated string or array with integers. + if ( isset( $args['users'] ) && ! is_string( $args['users'] ) && ! is_array( $args['users'] ) ) { + throw new \InvalidArgumentException( 'Invalid users' ); + } elseif ( isset( $args['users'] ) && is_string( $args['users'] ) ) { + $args['users'] = explode( ',', $args['users'] ); + } + + // Make sure users are integers and remove empty vals. + if ( isset( $args['users'] ) ) { + $args['users'] = array_map( 'intval', $args['users'] ); + $args['users'] = array_filter( $args['users'] ); + } + + return $args; + } + + /** + * Add context to log rows. + * Gets all ids from log rows and then fetches context for those ids + * in a single query. + * + * @param array $log_rows Log rows to append context to. + * @return array Log rows with context added. + */ + protected function add_contexts_to_log_rows( $log_rows ) { + // Bail if no log rows. + if ( sizeof( $log_rows ) === 0 ) { + return $log_rows; + } global $wpdb; + $simple_history = Simple_History::get_instance(); - $table_name = $simple_history->get_events_table_name(); - $table_name_contexts = $simple_history->get_contexts_table_name(); - - $where = '1 = 1'; - $limit = ''; - $inner_where = '1 = 1'; - - if ( 'overview' === $args['type'] || 'single' === $args['type'] ) { - // Set variables used by query. - $sql_set_var = 'SET @a:=NULL, @counter:=1, @groupby:=0'; - // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared - $wpdb->query( $sql_set_var ); - - // Main query - // 1 = where. - // 2 = limit. - // 3 = db name. - // 4 = where for inner calc sql query thingie. - // 5 = db name contexts. - $sql_tmpl = ' - /*NO_SELECT_FOUND_ROWS*/ - SELECT - SQL_CALC_FOUND_ROWS - h.id, - h.logger, - h.level, - h.date, - h.message, - h.initiator, - h.occasionsID, - count(t.repeated) AS subsequentOccasions, - t.rep, - t.repeated, - t.occasionsIDType, - c1.value AS context_message_key - - FROM %3$s AS h - - LEFT OUTER JOIN %5$s AS c1 ON (c1.history_id = h.id AND c1.key = "_message_key") - - INNER JOIN ( - SELECT - id, - IF(@a=occasionsID,@counter:=@counter+1,@counter:=1) AS rep, - IF(@counter=1,@groupby:=@groupby+1,@groupby) AS repeated, - @a:=occasionsID occasionsIDType - FROM %3$s AS h2 - - # First/inner where - WHERE - %4$s - - ORDER BY id DESC, date DESC - ) AS t ON t.id = h.id - - WHERE - # Outer/Second where - %1$s - - GROUP BY repeated - ORDER BY id DESC, date DESC - %2$s - '; + $table_contexts = $simple_history->get_contexts_table_name(); - $sh = Simple_History::get_instance(); - - // Only include loggers that the current user can view - // @TODO: this causes error if user has no access to any logger at all. - $sql_loggers_user_can_view = $sh->get_loggers_that_user_can_read( get_current_user_id(), 'sql' ); - $inner_where .= " AND logger IN {$sql_loggers_user_can_view}"; - } elseif ( 'occasions' === $args['type'] ) { - // Query template - // 1 = where. - // 2 = limit. - // 3 = db name. - $sql_tmpl = ' - SELECT h.*, - # fake columns that exist in overview query - 1 as subsequentOccasions - FROM %3$s AS h - WHERE %1$s - ORDER BY id DESC - %2$s - '; + $post_ids = wp_list_pluck( $log_rows, 'id' ); - $where .= ' AND h.id < ' . (int) $args['logRowID']; - $where .= " AND h.occasionsID = '" . esc_sql( $args['occasionsID'] ) . "'"; + $sql_context_query = sprintf( + 'SELECT history_id, `key`, value FROM %2$s WHERE history_id IN (%1$s)', + join( ',', $post_ids ), + $table_contexts + ); - if ( isset( $args['occasionsCountMaxReturn'] ) && (int) $args['occasionsCountMaxReturn'] < (int) $args['occasionsCount'] ) { - // Limit to max nn events if occasionsCountMaxReturn is set. - // Used in gui to prevent to many events returned, that can stall the browser. - $limit = 'LIMIT ' . (int) $args['occasionsCountMaxReturn']; - } else { - // Regular limit that gets all occasions. - $limit = 'LIMIT ' . (int) $args['occasionsCount']; + $context_results = $wpdb->get_results( $sql_context_query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + + foreach ( $context_results as $context_row ) { + if ( ! isset( $log_rows[ $context_row->history_id ]->context ) ) { + $log_rows[ $context_row->history_id ]->context = []; } - }// End if(). - // Determine limit - // Both posts_per_page and paged must be set. - $is_limit_query = ( is_numeric( $args['posts_per_page'] ) && $args['posts_per_page'] > 0 ); - $is_limit_query = $is_limit_query && ( is_numeric( $args['paged'] ) && $args['paged'] > 0 ); - if ( $is_limit_query ) { - $limit_offset = ( $args['paged'] - 1 ) * $args['posts_per_page']; - $limit .= sprintf( 'LIMIT %1$d, %2$d', $limit_offset, $args['posts_per_page'] ); + $log_rows[ $context_row->history_id ]->context[ $context_row->key ] = $context_row->value; } - // Determine where. - if ( $args['post__in'] && is_array( $args['post__in'] ) ) { - // make sure all vals are integers. - $args['post__in'] = array_map( 'intval', $args['post__in'] ); + // Move up _message_key from context row to main row as context_message_key. + // This is because that's the way it was before SQL was rewritten + // to support FULL_GROUP_BY in December 2023. + foreach ( $log_rows as $log_row ) { + if ( isset( $log_row->context['_message_key'] ) ) { + $log_row->context_message_key = $log_row->context['_message_key']; + } + } + + return $log_rows; + } + + /** + * Get max and min ids for a set of log rows. + * + * @param array $log_rows Log rows. + * @return array Array with max and min id. + */ + protected function get_max_min_ids( $log_rows ) { + /** @var null|int $min_id */ + $min_id = null; + + /** @var null|int $max_id */ + $max_id = null; + + // Bail of no log rows. + if ( sizeof( $log_rows ) === 0 ) { + return [ + $max_id, + $min_id, + ]; + } + + global $wpdb; + + $events_table_name = Simple_History::get_instance()->get_events_table_name(); + + // Max id is simply the id of the first/most recent row. + $max_id = reset( $log_rows )->id; + + // Min id = to find the lowest id we must take occasions into consideration. + $last_row = end( $log_rows ); + + // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + $last_row_occasions_count = (int) $last_row->subsequentOccasions - 1; + + if ( $last_row_occasions_count === 0 ) { + // Last row did not have any more occasions, so get min_id directly from the row. + $min_id = (int) $last_row->id; + } else { + // Last row did have occasions, so fetch all occasions, and find id of last one. + $sql = sprintf( + ' + SELECT id, date, occasionsID + FROM %1$s + WHERE id <= %2$d + ORDER BY id DESC + LIMIT %3$d + ', + $events_table_name, + $last_row->id, + $last_row_occasions_count + 1 + ); + + $results = $wpdb->get_results( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + + // the last occasion has the id we consider last in this paged result. + $min_id = (int) end( $results )->id; + } + + return [ + $max_id, + $min_id, + ]; + } + + /** + * Get inner where clause. + * + * @param array $args Arguments. + * @return array Where clauses. + */ + protected function get_inner_where( $args ) { + global $wpdb; + + $simple_history = Simple_History::get_instance(); + $contexts_table_name = $simple_history->get_contexts_table_name(); + $db_engine = $this->get_db_engine(); + + $inner_where = []; - $inner_where .= sprintf( ' AND id IN (%1$s)', implode( ',', $args['post__in'] ) ); + // Only include loggers that the current user can view + // @TODO: this causes error if user has no access to any logger at all. + $sql_loggers_user_can_view = $simple_history->get_loggers_that_user_can_read( get_current_user_id(), 'sql' ); + $inner_where[] = "logger IN {$sql_loggers_user_can_view}"; + + // Add post__in where. + if ( isset( $args['post__in'] ) && sizeof( $args['post__in'] ) > 0 ) { + $inner_where[] = sprintf( 'id IN (%1$s)', implode( ',', $args['post__in'] ) ); } // If max_id_first_page is then then only include rows - // with id equal to or earlier. - if ( isset( $args['max_id_first_page'] ) && is_numeric( $args['max_id_first_page'] ) ) { - $max_id_first_page = (int) $args['max_id_first_page']; - $inner_where .= sprintf( - ' AND id <= %1$d', - $max_id_first_page + // with id equal to or earlier than this, i.e. older than this. + if ( isset( $args['max_id_first_page'] ) ) { + $inner_where[] = sprintf( + 'id <= %1$d', + $args['max_id_first_page'] ); } - if ( isset( $args['since_id'] ) && is_numeric( $args['since_id'] ) ) { - $since_id = (int) $args['since_id']; - // Add where to inner because that's faster. - $inner_where .= sprintf( - ' AND id > %1$d', - $since_id + // Add where clause for since_id, + // to include rows with id greater than since_id, i.e. more recent than since_id. + if ( isset( $args['since_id'] ) ) { + $inner_where[] = sprintf( + 'id > %1$d', + (int) $args['since_id'], ); } - // Append date where. + // Append date where clause. + // If date_from is set it is a timestamp. if ( ! empty( $args['date_from'] ) ) { - // date_from=2014-08-01 - // if date is not numeric assume Y-m-d H:i-format. - $date_from = $args['date_from']; - if ( ! is_numeric( $date_from ) ) { - $date_from = strtotime( $date_from ); - } - - $inner_where .= "\n" . sprintf( ' AND date >= "%1$s"', gmdate( 'Y-m-d H:i:s', $date_from ) ); + $inner_where[] = sprintf( 'date >= "%1$s"', gmdate( 'Y-m-d H:i:s', $args['date_from'] ) ); } + // Date to. + // If date_to is set it is a timestamp. if ( ! empty( $args['date_to'] ) ) { - // date_to=2014-08-01 - // if date is not numeric assume Y-m-d H:i-format. - $date_to = $args['date_to']; - if ( ! is_numeric( $date_to ) ) { - $date_to = strtotime( $date_to ); - } - - $inner_where .= "\n" . sprintf( ' AND date <= "%1$s"', gmdate( 'Y-m-d H:i:s', $date_to ) ); + $inner_where[] = sprintf( 'date <= "%1$s"', gmdate( 'Y-m-d H:i:s', $args['date_to'] ) ); } - // If months they translate to $args["months"] because we already have support for that + // If "months" they translate to $args["months"] because we already have support for that // can't use months and dates and the same time. if ( ! empty( $args['dates'] ) ) { if ( is_array( $args['dates'] ) ) { @@ -328,7 +1080,7 @@ public function query( $args ) { ) */ - $args['months'] = array(); + $args['months'] = []; $args['lastdays'] = 0; foreach ( $arr_dates as $one_date ) { @@ -343,15 +1095,19 @@ public function query( $args ) { } } - // lastdays, as int. + // Add where clause for "lastdays", as int. if ( ! empty( $args['lastdays'] ) ) { - $inner_where .= "\n" . sprintf( - ' - # lastdays - AND date >= DATE(NOW()) - INTERVAL %d DAY - ', - $args['lastdays'] - ); + if ( $db_engine === 'mysql' ) { + $inner_where[] = sprintf( + 'date >= DATE(NOW() - INTERVAL %d DAY)', + $args['lastdays'] + ); + } elseif ( $db_engine === 'sqlite' ) { + $inner_where[] = sprintf( + 'date >= datetime("now", "-%d days")', + $args['lastdays'] + ); + } } // months, in format "Y-m". @@ -363,8 +1119,7 @@ public function query( $args ) { } $sql_months = "\n" . ' - # sql_months - AND ( + ( '; foreach ( $arr_months as $one_month ) { @@ -396,22 +1151,20 @@ public function query( $args ) { $sql_months = rtrim( $sql_months, ' OR ' ); $sql_months .= ' - # end sql_months and wrap ) '; - $inner_where .= $sql_months; + $inner_where[] = $sql_months; } // End if(). // Search. - if ( ! empty( $args['search'] ) ) { - $search_words = $args['search']; + if ( isset( $args['search'] ) ) { $str_search_conditions = ''; - $arr_search_words = preg_split( '/[\s,]+/', $search_words ); + $arr_search_words = preg_split( '/[\s,]+/', $args['search'] ); // create array of all searched words // split both spaces and commas and such. - $arr_sql_like_cols = array( 'message', 'logger', 'level' ); + $arr_sql_like_cols = [ 'message', 'logger', 'level' ]; foreach ( $arr_sql_like_cols as $one_col ) { $str_sql_search_words = ''; @@ -429,7 +1182,7 @@ public function query( $args ) { $str_sql_search_words = ltrim( $str_sql_search_words, ' AND ' ); $str_search_conditions .= "\n" . sprintf( - ' OR ( %1$s ) ', + ' OR ( %1$s ) ', $str_sql_search_words ); } @@ -442,8 +1195,8 @@ public function query( $args ) { $str_like = esc_sql( $wpdb->esc_like( $one_search_word ) ); $str_search_conditions .= "\n" . sprintf( - ' id IN ( SELECT history_id FROM %1$s AS c WHERE c.value LIKE "%2$s" ) AND ', - $table_name_contexts, // 1 + ' id IN ( SELECT history_id FROM %1$s AS c WHERE c.value LIKE "%2$s" ) AND ', + $contexts_table_name, // 1 '%' . $str_like . '%' // 2 ); } @@ -451,77 +1204,83 @@ public function query( $args ) { $str_search_conditions .= "\n ) "; // end OR for contexts. - $inner_where .= "\n AND \n(\n {$str_search_conditions} \n ) "; + $inner_where[] = "\n(\n {$str_search_conditions} \n ) "; }// End if(). - // log levels - // comma separated - // http://playground-root.ep/wp-admin/admin-ajax.php?action=simple_history_api&type=overview&format=&posts_per_page=10&paged=1&max_id_first_page=27273&SimpleHistoryLogQuery-showDebug=0&loglevel=error,warn. + // "loglevels", array with loglevels. + // e.g. info, debug, and so on. if ( ! empty( $args['loglevels'] ) ) { $sql_loglevels = ''; - if ( is_array( $args['loglevels'] ) ) { - $arr_loglevels = $args['loglevels']; - } else { - $arr_loglevels = explode( ',', $args['loglevels'] ); - } - - foreach ( $arr_loglevels as $one_loglevel ) { + foreach ( $args['loglevels'] as $one_loglevel ) { $sql_loglevels .= sprintf( ' "%s", ', esc_sql( $one_loglevel ) ); } + // Remove last comma. $sql_loglevels = rtrim( $sql_loglevels, ' ,' ); - $sql_loglevels = "\n AND level IN ({$sql_loglevels}) "; - $inner_where .= $sql_loglevels; + // Add to where in clause. + $inner_where[] = "level IN ({$sql_loglevels})"; } - // messages. - if ( ! empty( $args['messages'] ) ) { - /* - $args['messages']: - Array - ( - [0] => SimpleCommentsLogger:anon_comment_added,SimpleCommentsLogger:user_comment_added,SimpleCommentsLogger:anon_trackback_added,SimpleCommentsLogger:user_trackback_added,SimpleCommentsLogger:anon_pingback_added,SimpleCommentsLogger:user_pingback_added,SimpleCommentsLogger:comment_edited,SimpleCommentsLogger:trackback_edited,SimpleCommentsLogger:pingback_edited,SimpleCommentsLogger:comment_status_approve,SimpleCommentsLogger:trackback_status_approve,SimpleCommentsLogger:pingback_status_approve,SimpleCommentsLogger:comment_status_hold,SimpleCommentsLogger:trackback_status_hold,SimpleCommentsLogger:pingback_status_hold,SimpleCommentsLogger:comment_status_spam,SimpleCommentsLogger:trackback_status_spam,SimpleCommentsLogger:pingback_status_spam,SimpleCommentsLogger:comment_status_trash,SimpleCommentsLogger:trackback_status_trash,SimpleCommentsLogger:pingback_status_trash,SimpleCommentsLogger:comment_untrashed,SimpleCommentsLogger:trackback_untrashed,SimpleCommentsLogger:pingback_untrashed,SimpleCommentsLogger:comment_deleted,SimpleCommentsLogger:trackback_deleted,SimpleCommentsLogger:pingback_deleted - [1] => SimpleCommentsLogger:SimpleCommentsLogger:comment_status_spam,SimpleCommentsLogger:trackback_status_spam,SimpleCommentsLogger:pingback_status_spam - ) - */ + // loggers, comma separated or array. + // http://playground-root.ep/wp-admin/admin-ajax.php?action=simple_history_api&type=overview&format=&posts_per_page=10&paged=1&max_id_first_page=27273&SimpleHistoryLogQuery-showDebug=0&loggers=SimpleCommentsLogger,SimpleCoreUpdatesLogger. + if ( ! empty( $args['loggers'] ) ) { + $sql_loggers = ''; - // Array with loggers and messages. - $arr_loggers_and_messages = array(); + foreach ( $args['loggers'] as $one_logger ) { + $sql_loggers .= sprintf( ' "%s", ', esc_sql( $one_logger ) ); + } - // Transform from received format to our own internal format. - foreach ( (array) $args['messages'] as $one_arr_messages_row ) { - $arr_row_messages = explode( ',', $one_arr_messages_row ); + // Remove last comma. + $sql_loggers = rtrim( $sql_loggers, ' ,' ); - /* - $one_arr_messages_row: - Array - ( - [0] => SimpleCommentsLogger:anon_comment_added - [1] => SimpleCommentsLogger:user_comment_added - [2] => SimpleCommentsLogger:anon_trackback_added - */ - foreach ( $arr_row_messages as $one_row_logger_and_message ) { - $arr_one_logger_and_message = explode( ':', $one_row_logger_and_message ); + // Add to where in clause. + $inner_where[] = "logger IN ({$sql_loggers}) "; + } - if ( ! isset( $arr_loggers_and_messages[ $arr_one_logger_and_message[0] ] ) ) { - $arr_loggers_and_messages[ $arr_one_logger_and_message[0] ] = array(); - } + // Add where for a single user ID. + if ( isset( $args['user'] ) ) { + $inner_where[] = sprintf( + 'id IN ( SELECT history_id FROM %1$s AS c WHERE c.key = "_user_id" AND c.value = %2$s )', + $contexts_table_name, // 1 + $args['user'], // 2 + ); + } - $arr_loggers_and_messages[ $arr_one_logger_and_message[0] ][] = $arr_one_logger_and_message[1]; - } - } + // Users, array with user ids. + if ( isset( $args['users'] ) ) { + $inner_where[] = sprintf( + 'id IN ( SELECT history_id FROM %1$s AS c WHERE c.key = "_user_id" AND c.value IN (%2$s) )', + $contexts_table_name, // 1 + implode( ',', $args['users'] ), // 2 + ); + } - // Create sql where based on loggers and messages. - $sql_messages_where = ' AND ('; + return $inner_where; + } - foreach ( $arr_loggers_and_messages as $logger_slug => $logger_messages ) { + /** + * Get outer where clause. + * + * @param array $args Arguments. + * @return array Where clauses. + */ + protected function get_outer_where( $args ) { + $outer_where = []; + // messages. + if ( ! empty( $args['messages'] ) ) { + // Create sql where based on loggers and messages. + $sql_messages_where = '('; + + foreach ( $args['messages'] as $logger_slug => $logger_messages ) { $sql_logger_messages_in = ''; + foreach ( $logger_messages as $one_logger_message ) { $sql_logger_messages_in .= sprintf( '"%s",', esc_sql( $one_logger_message ) ); } + $sql_logger_messages_in = rtrim( $sql_logger_messages_in, ' ,' ); $sql_logger_messages_in = "\n AND c1.value IN ({$sql_logger_messages_in}) "; @@ -536,216 +1295,28 @@ public function query( $args ) { $sql_logger_messages_in ); } - // remove last or. + + // Remove last 'OR '. $sql_messages_where = preg_replace( '/OR $/', '', $sql_messages_where ); $sql_messages_where .= "\n )"; - $where .= $sql_messages_where; + $outer_where[] = $sql_messages_where; } // End if(). - // loggers - // comma separated - // http://playground-root.ep/wp-admin/admin-ajax.php?action=simple_history_api&type=overview&format=&posts_per_page=10&paged=1&max_id_first_page=27273&SimpleHistoryLogQuery-showDebug=0&loggers=SimpleCommentsLogger,SimpleCoreUpdatesLogger. - if ( ! empty( $args['loggers'] ) ) { - $sql_loggers = ''; - if ( is_array( $args['loggers'] ) ) { - $arr_loggers = $args['loggers']; - } else { - $arr_loggers = explode( ',', $args['loggers'] ); - } - - foreach ( $arr_loggers as $one_logger ) { - $sql_loggers .= sprintf( ' "%s", ', esc_sql( $one_logger ) ); - } - $sql_loggers = rtrim( $sql_loggers, ' ,' ); - $sql_loggers = "\n AND logger IN ({$sql_loggers}) "; - - $inner_where .= $sql_loggers; - } - - // user, a single userID. - if ( ! empty( $args['user'] ) && is_numeric( $args['user'] ) ) { - $userID = (int) $args['user']; - $sql_user = sprintf( - ' - AND id IN ( SELECT history_id FROM %1$s AS c WHERE c.key = "_user_id" AND c.value = %2$s ) - ', - $table_name_contexts, // 1 - $userID // 2 - ); - - $inner_where .= $sql_user; - } - - // If users is array, make it comma separated. - if ( isset( $args['users'] ) && is_array( $args['users'] ) ) { - $args['users'] = implode( ',', $args['users'] ); - } - - // Users, comma separated. - if ( ! empty( $args['users'] ) && is_string( $args['users'] ) ) { - $users = explode( ',', $args['users'] ); - $users = array_map( 'intval', $users ); - $users_in = implode( ',', $users ); - $sql_user = sprintf( - ' - AND id IN ( SELECT history_id FROM %1$s AS c WHERE c.key = "_user_id" AND c.value IN (%2$s) ) - ', - $table_name_contexts, // 1 - $users_in // 2 - ); - $inner_where .= $sql_user; - } - - /** - * Filter the sql template - * - * @since 2.0 - * - * @param string $sql_tmpl - */ - $sql_tmpl = apply_filters( 'simple_history/log_query_sql_template', $sql_tmpl ); - - /** - * Filter the sql template where clause - * - * @since 2.0 - * - * @param string $where - */ - $where = apply_filters( 'simple_history/log_query_sql_where', $where ); - - /** - * Filter the sql template limit - * - * @since 2.0 - * - * @param string $limit - */ - $limit = apply_filters( 'simple_history/log_query_limit', $limit ); - - /** - * Filter the sql template limit - * - * @since 2.0 - * - * @param string $limit - */ - $inner_where = apply_filters( 'simple_history/log_query_inner_where', $inner_where ); - - $sql = sprintf( - $sql_tmpl, // sprintf template. - $where, // 1 - $limit, // 2 - $table_name, // 3 - $inner_where, // 4 - $table_name_contexts // 5 - ); - - /** - * Filter the final sql query - * - * @since 2.0 - * - * @param string $sql - */ - $sql = apply_filters( 'simple_history/log_query_sql', $sql ); - - /** @var array */ - $log_rows = $wpdb->get_results( $sql, OBJECT_K ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared - - // Find total number of rows that we would have gotten without pagination - // This is the number of rows with occasions taken into consideration. - $sql_found_rows = 'SELECT FOUND_ROWS()'; - $total_found_rows = (int) $wpdb->get_var( $sql_found_rows ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared - - // Add context. - $post_ids = wp_list_pluck( $log_rows, 'id' ); - - if ( empty( $post_ids ) ) { - $context_results = array(); - } else { - $sql_context = sprintf( 'SELECT * FROM %2$s WHERE history_id IN (%1$s)', join( ',', $post_ids ), $table_name_contexts ); - $context_results = $wpdb->get_results( $sql_context ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared - } - - foreach ( $context_results as $context_row ) { - if ( ! isset( $log_rows[ $context_row->history_id ]->context ) ) { - $log_rows[ $context_row->history_id ]->context = array(); - } - - $log_rows[ $context_row->history_id ]->context[ $context_row->key ] = $context_row->value; - } - - // Remove id from keys, because they are cumbersome when working with JSON. - $log_rows = array_values( $log_rows ); - $min_id = null; - $max_id = null; - - if ( count( $log_rows ) ) { - // Max id is simply the id of the first row. - $max_id = reset( $log_rows )->id; - - // Min id = to find the lowest id we must take occasions into consideration. - $last_row = end( $log_rows ); - - // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase - $last_row_occasions_count = (int) $last_row->subsequentOccasions - 1; - - if ( $last_row_occasions_count === 0 ) { - // Last row did not have any more occasions, so get min_id directly from the row. - $min_id = $last_row->id; - } else { - // Last row did have occasions, so fetch all occasions, and find id of last one. - $db_table = $simple_history->get_events_table_name(); - $sql = sprintf( - ' - SELECT id, date, occasionsID - FROM %1$s - WHERE id <= %2$s - ORDER BY id DESC - LIMIT %3$s - ', - $db_table, - $last_row->id, - $last_row_occasions_count + 1 - ); - - $results = $wpdb->get_results( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared - - // the last occasion has the id we consider last in this paged result. - $min_id = end( $results )->id; - } - } // End if(). - - // Calc pages. - if ( $args['posts_per_page'] ) { - $pages_count = Ceil( $total_found_rows / (int) $args['posts_per_page'] ); - } else { - $pages_count = 1; - } - - // Create array to return. - // Make all rows a sub key because we want to add some meta info too. - $log_rows_count = count( $log_rows ); - $page_rows_from = ( (int) $args['paged'] * (int) $args['posts_per_page'] ) - (int) $args['posts_per_page'] + 1; - $page_rows_to = $page_rows_from + $log_rows_count - 1; - $arr_return = array( - 'total_row_count' => $total_found_rows, - 'pages_count' => $pages_count, - 'page_current' => (int) $args['paged'], - 'page_rows_from' => $page_rows_from, - 'page_rows_to' => $page_rows_to, - 'max_id' => (int) $max_id, - 'min_id' => (int) $min_id, - 'log_rows_count' => $log_rows_count, - 'log_rows' => $log_rows, - // Add sql query to debug. - // 'sql' => $sql, - ); - - wp_cache_set( $cache_key, $arr_return, $cache_group ); + return $outer_where; + } - return $arr_return; + /** + * Get db engine in use. + * Default is "mysql", which supports both MySQL and MariaDB. + * Can also return "sqlite", which means that plugin + * https://wordpress.org/plugins/sqlite-database-integration/ is in use, + * and we need to use SQLite specific SQL at some places. + * + * @return string "mysql" or "sqlite" + */ + public static function get_db_engine() { + $db_engine = defined( 'DB_ENGINE' ) && constant( 'DB_ENGINE' ) === 'sqlite' ? 'sqlite' : 'mysql'; + return $db_engine; } } diff --git a/inc/class-simple-history.php b/inc/class-simple-history.php index 6192a4a3..76db12ca 100644 --- a/inc/class-simple-history.php +++ b/inc/class-simple-history.php @@ -438,6 +438,7 @@ public function get_core_dropins() { Dropins\WP_CLI_Dropin::class, Dropins\Event_Details_Dev_Dropin::class, Dropins\Quick_Stats::class, + Dropins\Sidebar_Add_Ons_Dropin::class, ); /** @@ -802,6 +803,7 @@ public function get_log_row_html_output( $one_log_row, $args ) { $occasions_count = $one_log_row->subsequentOccasions - 1; $occasions_html = ''; + // Add markup for occasions. if ( $occasions_count > 0 ) { $occasions_html = '
    '; @@ -825,6 +827,22 @@ public function get_log_row_html_output( $one_log_row, $args ) { ); $occasions_html .= ''; + // Div with information about add-ons. + // TODO: Finalize copy. Make it shorter. Remember that it will be shown many times. + // Also only show for failed logins. + // $occasions_html .= '
    '; + // $occasions_html .= '

    '; + // $occasions_html .= sprintf( + // /* translators: 1 is link to add-on page */ + // __( + // 'Set number of login attempts to store using the Extended Settings add-on.', + // 'simple-history' + // ), + // 'https://simple-history.com/add-ons/extended-settings/?utm_source=wpadmin' + // ); + // $occasions_html .= '

    '; + // $occasions_html .= '
    '; + $occasions_html .= '
    '; } @@ -934,9 +952,6 @@ public function get_log_row_html_output( $one_log_row, $args ) { unset( $logRowKeysToShow['occasionsID'], $logRowKeysToShow['subsequentOccasions'], - $logRowKeysToShow['rep'], - $logRowKeysToShow['repeated'], - $logRowKeysToShow['occasionsIDType'], $logRowKeysToShow['context'], $logRowKeysToShow['type'] ); diff --git a/inc/global-helpers.php b/inc/global-helpers.php index 0cc859e9..6efe0a7f 100644 --- a/inc/global-helpers.php +++ b/inc/global-helpers.php @@ -62,12 +62,12 @@ function sh_error_log() { // phpcs:ignore WordPress.NamingConventions.PrefixAllG * sh_d('$_POST', $_POST); * sh_d('My vars', $varOne, $varTwo, $varXYZ); * - * @mixed Vars Variables to output. + * @param mixed[] ...$args Variables to output. */ - function sh_d() { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound + function sh_d( ...$args ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound $output = ''; - foreach ( func_get_args() as $var ) { + foreach ( $args as $var ) { $loopOutput = ''; if ( is_bool( $var ) ) { $bool_string = $var ? 'true' : 'false'; diff --git a/inc/services/class-api.php b/inc/services/class-api.php index eff9c738..68cf5c0f 100644 --- a/inc/services/class-api.php +++ b/inc/services/class-api.php @@ -63,24 +63,39 @@ public function api() { $logQuery = new Log_Query(); $data = $logQuery->query( $args ); + // $data_full_group_by = $logQuery->query_overview_full_group_by( $args ); + + // sh_d('$data', $data, '$data_full_group_by', $data_full_group_by);exit; $data['api_args'] = $args; + // $data['log_rows_full_group_by'] = array_values( $data_full_group_by ); + // Output can be array or HTML. if ( isset( $args['format'] ) && 'html' === $args['format'] ) { - $data['log_rows_raw'] = []; - foreach ( $data['log_rows'] as $key => $oneLogRow ) { - $args = []; + $format_args = []; if ( $type == 'single' ) { - $args['type'] = 'single'; + $format_args['type'] = 'single'; } - $data['log_rows'][ $key ] = $this->simple_history->get_log_row_html_output( $oneLogRow, $args ); + $data['log_rows'][ $key ] = $this->simple_history->get_log_row_html_output( $oneLogRow, $format_args ); } $data['num_queries'] = get_num_queries(); $data['cached_result'] = $data['cached_result'] ?? false; + + // Add full group by result. + #sh_d( 'data_full_group_by', $data_full_group_by ); + #exit; + // foreach ( $data_full_group_by['log_rows'] as $key => $one_log_row ) { + // $format_args = []; + // if ( $type == 'single' ) { + // $format_args['type'] = 'single'; + // } + + // $data['log_rows_full'][ $key ] = $this->simple_history->get_log_row_html_output( $one_log_row, $format_args ); + // } } break; diff --git a/inc/services/class-setup-purge-db-cron.php b/inc/services/class-setup-purge-db-cron.php index 26eee102..dd180dcf 100644 --- a/inc/services/class-setup-purge-db-cron.php +++ b/inc/services/class-setup-purge-db-cron.php @@ -40,6 +40,7 @@ public function setup_cron() { public function maybe_purge_db() { /** * Day of week today. + * * @int $current_day_of_week */ $current_day_of_week = (int) gmdate( 'N' ); @@ -67,6 +68,9 @@ public function maybe_purge_db() { /** * Removes old entries from the db. + * + * Removes in batches of 100 000 rows. + * */ public function purge_db() { $do_purge_history = true; @@ -132,7 +136,7 @@ public function purge_db() { */ do_action( 'simple_history/db/events_purged', $days, $num_rows_purged ); - Helpers::get_cache_incrementor( true ); + Helpers::clear_cache(); } } } diff --git a/inc/services/class-setup-settings-page.php b/inc/services/class-setup-settings-page.php index 414630a5..4c8ad1d3 100644 --- a/inc/services/class-setup-settings-page.php +++ b/inc/services/class-setup-settings-page.php @@ -341,6 +341,25 @@ public function settings_field_clear_log() { esc_html__( 'Items in the database are automatically removed after %1$s days.', 'simple-history' ), esc_html( $clear_days ) ); + echo '
    '; + + $message = sprintf( + /* translators: 1 is a link to webpage with info about how to modify number of days to keep the log */ + __( 'The number of days can be changed using a filter or with an add-on. More info.', 'simple-history' ), + esc_url( 'https://simple-history.com/support/change-number-of-days-to-keep-log/?utm_source=wpadmin' ) + ); + + echo '

    ' . wp_kses( + $message, + [ + 'a' => [ + 'href' => [], + 'target' => [], + 'class' => [], + ], + ] + ) . '

    '; + } else { esc_html_e( 'Items in the database are kept forever.', 'simple-history' ); } diff --git a/js/scripts.js b/js/scripts.js index b73156d4..ee8fdd23 100644 --- a/js/scripts.js +++ b/js/scripts.js @@ -399,7 +399,7 @@ var simple_history = (function ($) { }, events: { - 'click .SimpleHistoryLogitem__occasions a': 'showOccasions', + 'click .SimpleHistoryLogitem__occasionsLink': 'showOccasions', 'click .SimpleHistoryLogitem__permalink': 'permalink' }, diff --git a/loggers/class-logger.php b/loggers/class-logger.php index cc760ed2..f291f2a9 100644 --- a/loggers/class-logger.php +++ b/loggers/class-logger.php @@ -1306,7 +1306,7 @@ public function log( $level = 'info', $message = '', $context = array() ) { $this->last_insert_context = $context; $this->last_insert_data = $data; - Helpers::get_cache_incrementor( true ); + Helpers::clear_cache(); /** * Fired after an event has been logged. diff --git a/loggers/class-simple-history-logger.php b/loggers/class-simple-history-logger.php index ef9b05be..d7e91a4b 100644 --- a/loggers/class-simple-history-logger.php +++ b/loggers/class-simple-history-logger.php @@ -160,10 +160,34 @@ public function on_updated_option( $option, $old_value, $new_value ) { /** * Get the log row details for this logger. * - * @param array $row Log row. + * @param object $row Log row. * @return Event_Details_Group */ public function get_log_row_details_output( $row ) { + + $message_key = $row->context_message_key; + + if ( $message_key === 'purged_events' ) { + // For message "Removed 24318 events that were older than 60 days" + // add a text with a link with information on how to modify this. + $message = sprintf( + /* translators: 1 is a link to webpage with info about how to modify number of days to keep the log */ + __( 'The number of days the log is kept can be changed using a filter or an add-on. More info.', 'simple-history' ), + esc_url( 'https://simple-history.com/support/change-number-of-days-to-keep-log/?utm_source=wpadmin' ) + ); + + return '

    ' . wp_kses( + $message, + [ + 'a' => [ + 'href' => [], + 'target' => [], + 'class' => [], + ], + ] + ) . '

    '; + } + $event_details_group = ( new Event_Details_Group() ) ->add_items( [ diff --git a/package.json b/package.json index 6a3fdd6b..8f13bf86 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,11 @@ "test:functional": "docker-compose run --rm php-cli vendor/bin/codecept run functional", "test:acceptance": "docker-compose run --rm php-cli vendor/bin/codecept run acceptance", "spell-check": "typos", - "spell-check:write-changes": "typos --write-changes" + "spell-check:write-changes": "typos --write-changes", + "test:log-query-mysq55": "PHP_CLI_VERSION=81 PHP_VERSION=8.1 DB_IMAGE=biarms/mysql:5.5 DB_DATA_DIR=./data/mysql-5.5 npm run test:wpunit-logquery", + "test:log-query-mysq57": "PHP_CLI_VERSION=81 PHP_VERSION=8.1 DB_IMAGE=biarms/mysql:5.7 DB_DATA_DIR=./data/mysql-5.7 npm run test:wpunit-logquery", + "test:log-query-mariadb105": "PHP_CLI_VERSION=81 PHP_VERSION=8.1 DB_IMAGE=mariadb:10.5 DB_DATA_DIR=./data/mysql npm run test:wpunit-logquery", + "test:wpunit-logquery": "docker-compose run --rm php-cli vendor/bin/codecept run wpunit:LogQueryTest" }, "lint-staged": { "*.php": [ diff --git a/phpstan.neon b/phpstan.neon index 6596b40c..daa26dab 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -13,3 +13,5 @@ parameters: bootstrapFiles: - tests/phpstan/bootstrap.php level: 2 + ignoreErrors: + - '#Access to an undefined property object::\$context_message_key.#' diff --git a/readme.txt b/readme.txt index 5424af92..e53558da 100644 --- a/readme.txt +++ b/readme.txt @@ -14,7 +14,7 @@ Simple History shows recent changes made within WordPress, directly on your dash The plugin works as a log/history/audit log/version history of the most important events that occur in WordPress. -It’s a plugin that is good to have on websites where several people are involved in editing the content. +It's a plugin that is good to have on websites where several people are involved in editing the content. Out of the box Simple History has support for: @@ -164,12 +164,14 @@ If you want to translate Simple History to your language then read about how thi Development of this plugin takes place at GitHub. Please join in with feature requests, bug reports, or even pull requests! https://github.com/bonny/WordPress-Simple-History -### Donation +### Sponsor this project If you like this plugin please consider donating to support the development. You can [donate using PayPal](https://www.paypal.me/eskapism) or you can [become a GitHub sponsor](https://github.com/sponsors/bonny). +There is also some [add-ons](https://simple-history.com/add-ons/) that you can buy to support the development of this plugin and get some extra features. + ## Frequently Asked Questions = Can I add my own events to the log? = @@ -209,6 +211,22 @@ This can be modified using the filter [`simple_history/db_purge_days_interval`]( ## Changelog +4.9.0 Unreleased + +This release comes with improvements to the SQL queries that the plugin use to fetch events. These optimizations enhance query performance and reliability on both MySQL and MariaDB. + +Additionally, the plugin now provides support for SQLite databases. + +- Added: support for SQLite Database. Tested with the WordPress [SQLite Database Integration](https://wordpress.org/plugins/sqlite-database-integration/) feature plugin. See [Let's make WordPress officially support SQLite](https://make.wordpress.org/core/2022/09/12/lets-make-wordpress-officially-support-sqlite/) and [Help us test the SQLite implementation](https://make.wordpress.org/core/2022/12/20/help-us-test-the-sqlite-implementation/) for more information about the SQLite integration in WordPress and the current status. Fixes [#394](https://github.com/bonny/WordPress-Simple-History/issues/394) and [#411](https://github.com/bonny/WordPress-Simple-History/issues/411). +- Added: Support for plugin preview button that soon will be available in the WordPress.org plugin directory. This is a very nice way to quickly test plugins in your web browser. Read more in blog post ["Plugin Directory: Preview button revisited"](https://make.wordpress.org/meta/2023/11/22/plugin-directory-preview-button-revisited/) and follow progress in [trac ticket "Add a Preview in Playground button to the plugin directory"](https://meta.trac.wordpress.org/ticket/7251). You can however already test the functionality using this link: [Preview Simple History plugin](https://playground.wordpress.net/?plugin=simple-history&blueprint-url=https://wordpress.org/plugins/wp-json/plugins/v1/plugin/simple-history/blueprint.json). +- Added: IP addresses are now shown on occasions. +- Added: Helper functions `get_cache_group()`, `clear_cache()`. +- Changed: Better support for MariaDB and MySQL 8 by using full group by in the query. Fixes multiple database related errors. Fixes [#397](https://github.com/bonny/WordPress-Simple-History/issues/397), [#409](https://github.com/bonny/WordPress-Simple-History/issues/409), and [#405](https://github.com/bonny/WordPress-Simple-History/issues/405). +- Changed: Misc code cleanup and improvements and GUI changes. +- Removed: Usage of `SQL_CALC_FOUND_ROWS` since it's deprecated in MySQL 8.0.17. Also [this should make the query faster](https://stackoverflow.com/a/188682). Fixes [#312](https://github.com/bonny/WordPress-Simple-History/issues/312). +- Removed: Columns "rep", "repeated" and "occasionsIDType" are removed from return value in `Log_Query()`. +- Fixed: Stats widget counting could be wrong due to incorrect loggers included in stats query. + ### 4.8.0 (December 2023) 🧩 This release contains minor fixes, some code cleanup, and adds [support for add-ons](https://simple-history.com/2023/simple-history-4-8-0-introducing-add-ons/)! diff --git a/templates/template-settings-tab-debug.php b/templates/template-settings-tab-debug.php index fece243a..ca399001 100644 --- a/templates/template-settings-tab-debug.php +++ b/templates/template-settings-tab-debug.php @@ -97,7 +97,11 @@ echo ''; +/** + * Number of rows in database + */ $logQuery = new Log_Query(); + $rows = $logQuery->query( array( 'posts_per_page' => 1, diff --git a/tests/wpunit/APHPVersionTest.php b/tests/wpunit/APHPVersionTest.php index 22af4385..51ba1e49 100644 --- a/tests/wpunit/APHPVersionTest.php +++ b/tests/wpunit/APHPVersionTest.php @@ -11,7 +11,7 @@ public function test_a_php_version() { // Output MySQL/MariaDB version. global $wpdb; if ( empty( $wpdb->use_mysqli ) ) { - $mysqlVersion = mysql_get_server_info(); + // $mysqlVersion = mysql_get_server_info(); } else { $mysqlVersion = mysqli_get_server_info( $wpdb->dbh ); } diff --git a/tests/wpunit/LogQueryTest.php b/tests/wpunit/LogQueryTest.php index a3174b89..dfb6f81e 100644 --- a/tests/wpunit/LogQueryTest.php +++ b/tests/wpunit/LogQueryTest.php @@ -1,35 +1,27 @@ markTestIncomplete('This test will fail in Mysql >5.5 and MariaDB until SQL bug is fixed.'); - // Add and set current user to admin user, so user can read all logs. $user_id = $this->factory->user->create( array( @@ -46,7 +38,7 @@ function test_query() { "num_new_rows": 1, "num_mysql_queries": 50, (what? why so many??) */ - $added_rows_id = []; + $added_rows_ids = []; $num_rows_to_add = 10; for ($i = 0; $i < $num_rows_to_add; $i++) { $logger = SimpleLogger()->info( @@ -56,31 +48,214 @@ function test_query() { 'message_num' => $i, ] ); - // sh_d('$logger', $logger->last_insert_context, $logger->last_insert_data, $logger->last_insert_id); - $added_rows_id[] = $logger->last_insert_id; + $added_rows_ids[] = $logger->last_insert_id; } // Now query the log and see what id we get as the latest. - $log_query_args = array( - 'posts_per_page' => 1, + $query_results = (new Log_Query())->query( ['posts_per_page' => 1] ); + $first_log_row_from_query = $query_results['log_rows'][0]; + + $this->assertEquals( + $added_rows_ids[$num_rows_to_add-1], + $first_log_row_from_query->id, + 'The id of the first row in query result should be the same as the id of the last added row.' ); + + // Add more. + for ($i = 0; $i < 4; $i++) { + $logger = SimpleLogger()->info( + 'Another test info message ' . $i, + [ + '_occasionsID' => 'my_occasion_id_2', + 'message_num' => $i, + ] + ); + } + + $logger = SimpleLogger()->info( + 'Single message ' . 0, + [ + '_occasionsID' => 'my_occasion_id_3', + 'message_num' => 0, + ] + ); + - $log_query = new Log_Query(); - $query_results = $log_query->query( $log_query_args ); - $first_log_row = $query_results['log_rows'][0]; + $hello_some_messages_message_count = 7; + for ($i = 0; $i < $hello_some_messages_message_count; $i++) { + $logger = SimpleLogger()->info( + 'Hello some messages ' . $i, + [ + '_occasionsID' => 'my_occasion_id_5', + 'message_num' => $i, + ] + ); + } + + $oh_such_logging_rows_num_to_add = 3; + for ($i = 0; $i < $oh_such_logging_rows_num_to_add; $i++) { + $logger = SimpleLogger()->info( + 'Oh such logging things ' . $i, + [ + '_occasionsID' => 'my_occasion_id_6', + 'message_num' => $i, + ] + ); + } + + // Get first result and check that it has 3 subsequentOccasions + // and that the message is + // "Oh such logging things {$i-1}" + // and that context contains message_num = {$i-1}. + $results = (new Log_Query())->query([ + 'posts_per_page' => 3 + ]); + + $first_log_row_from_query = $results['log_rows'][0]; + $second_log_row_from_query = $results['log_rows'][1]; + $third_log_row_from_query = $results['log_rows'][2]; + + $this->assertEquals( + 3, + $first_log_row_from_query->subsequentOccasions, + 'The first log row should have 3 subsequentOccasions.' + ); + + $this->assertIsNumeric($first_log_row_from_query->subsequentOccasions); + + $this->assertEquals( + 'Oh such logging things ' . ($i-1), + $first_log_row_from_query->message, + 'The first log row should have the message "Oh such logging things" ' . $i-1 + ); + + $this->assertEquals( + $i-1, + $first_log_row_from_query->context['message_num'], + 'The first log row should have the context message_num = ' . ($i-1) + ); + + // Test second message. + $this->assertEquals( + $hello_some_messages_message_count, + $second_log_row_from_query->subsequentOccasions, + "The second log row should have $hello_some_messages_message_count subsequentOccasions." + ); + + $this->assertIsNumeric($second_log_row_from_query->subsequentOccasions); - // On MariaDB $first_log_row->id is the same as the value in $added_rows_id[0] (the first added row) - // but it should be the id from the last added row, i.e. the value in $added_rows_id[9]. - sh_d('$first_log_row id', $first_log_row->id); - sh_d('$added_rows_id[0]', $added_rows_id[0]); - sh_d('$added_rows_id[max]', $added_rows_id[$num_rows_to_add-1]); + $this->assertEquals( + 'Hello some messages 6', + $second_log_row_from_query->message, + 'The first log row should have the message "Hello some messages 6"' + ); + + // Test third message. + $this->assertEquals( + 1, + $third_log_row_from_query->subsequentOccasions, + 'The third log row should have 1 subsequentOccasions.' + ); + + $this->assertIsNumeric($third_log_row_from_query->subsequentOccasions); - // $this->markTestIncomplete('This test will fail in MariaDB until bug is fixed.'); $this->assertEquals( - $added_rows_id[$num_rows_to_add-1], - $first_log_row->id, - 'The id of the first row should be the same as the id of the last added row.' + 'Single message 0', + $third_log_row_from_query->message, + 'The third log row should have the message "Single message 0"' + ); + + + // Test occasions query arg. for first returned row. + $query_results = (new Log_Query())->query([ + 'type' => 'occasions', + // Get history rows with id:s less than this, i.e. get earlier/previous rows. + 'logRowID' => $first_log_row_from_query->id, + 'occasionsID' => $first_log_row_from_query->occasionsID, // The occasions id is md5:ed so we need to use log query to get the last row, and then get occasions id.. + 'occasionsCount' => $first_log_row_from_query->subsequentOccasions - 1, + ]); + + $this->assertCount( + $oh_such_logging_rows_num_to_add - 1, + $query_results['log_rows'], + 'The number of rows returned when getting occasions should be ' . ($oh_such_logging_rows_num_to_add - 1) + ); + + // Test occasions query arg. for second returned row. + $query_results = (new Log_Query())->query([ + 'type' => 'occasions', + 'logRowID' => $second_log_row_from_query->id, + 'occasionsID' => $second_log_row_from_query->occasionsID, // The occasions id is md5:ed so we need to use log query to get the last row, and then get occasions id.. + 'occasionsCount' => $second_log_row_from_query->subsequentOccasions - 1, + ]); + + $this->assertCount( + $hello_some_messages_message_count - 1, + $query_results['log_rows'], + 'The number of rows returned when getting occasions should be ' . ($hello_some_messages_message_count - 1) + ); + + // Test occasions query arg. for third returned row. + $query_results = (new Log_Query())->query([ + 'type' => 'occasions', + 'logRowID' => $third_log_row_from_query->id, + 'occasionsID' => $third_log_row_from_query->occasionsID, // The occasions id is md5:ed so we need to use log query to get the last row, and then get occasions id. + 'occasionsCount' => $third_log_row_from_query->subsequentOccasions - 1, + ]); + + // No further occasions for this row. + $this->assertSame( + "1", + $third_log_row_from_query->subsequentOccasions, + 'The number of rows returned when getting occasions should be 0' + ); + + $this->assertCount( + 0, + $query_results['log_rows'], + 'The number of rows returned when getting occasions should be 0' + ); + } + + function test_since_id() { + // Add and set current user to admin user, so user can read all logs. + $user_id = $this->factory->user->create( + array( + 'role' => 'administrator', + ) + ); + wp_set_current_user( $user_id ); + + $logger = SimpleLogger()->info( + 'Test info message 1', + [ + '_occasionsID' => 'my_occasion_id', + 'message_num' => 1, + ] ); + + $last_insert_id = $logger->last_insert_id; + + // Query log again and use $last_insert_id as since_id. + // It should not have any new rows yet. + $query_results = (new Log_Query())->query( ['since_id' => $last_insert_id] ); + $this->assertEmpty($query_results['log_rows'], 'There should be no new rows yet.'); + + // Add two new events. + for ($i=0; $i<2; $i++) { + echo "log $i"; + $logger = SimpleLogger()->info( + 'Test info message ' . $i, + [ + '_occasionsID' => 'my_occasion_id_in_loop_' . $i, + 'message_num' => $i, + ] + ); + } + + // Test that we get two new rows. + $query_results = (new Log_Query())->query( ['since_id' => $last_insert_id] ); + $this->assertEquals(2, $query_results['total_row_count'], 'There should be two new rows now.'); } /** @@ -130,9 +305,6 @@ function test_log_query() { $this->assertTrue( property_exists( $first_log_row, 'initiator' ) ); $this->assertTrue( property_exists( $first_log_row, 'occasionsID' ) ); $this->assertTrue( property_exists( $first_log_row, 'subsequentOccasions' ) ); - $this->assertTrue( property_exists( $first_log_row, 'rep' ) ); - $this->assertTrue( property_exists( $first_log_row, 'repeated' ) ); - $this->assertTrue( property_exists( $first_log_row, 'occasionsIDType' ) ); $this->assertTrue( property_exists( $first_log_row, 'context' ) ); } } diff --git a/tests/wpunit/OldLoggerTest.php b/tests/wpunit/OldLoggerTest.php index 267b472c..b131c216 100644 --- a/tests/wpunit/OldLoggerTest.php +++ b/tests/wpunit/OldLoggerTest.php @@ -65,7 +65,7 @@ public function test_old_log_query_class() { $expected_object->initiator = 'wp_user'; $actual = $query_results['log_rows'][0]; - unset($actual->id, $actual->date, $actual->occasionsID, $actual->subsequentOccasions, $actual->rep, $actual->repeated, $actual->occasionsIDType, $actual->context); + unset($actual->id, $actual->date, $actual->occasionsID, $actual->subsequentOccasions, $actual->rep, $actual->repeated, $actual->context); $this->assertEquals($expected_object, $actual); }