diff --git a/composer.json b/composer.json
index e2945f1..ddbb30b 100644
--- a/composer.json
+++ b/composer.json
@@ -1,55 +1,52 @@
{
- "name": "a8cteam51/simple-events",
- "type": "wordpress-plugin",
+ "name": "a8cteam51/simple-events",
+ "type": "wordpress-plugin",
+ "description": "A simple Gutenberg-first event management plugin that integrates with WooCommerce Box Office.",
+ "homepage": "https://github.com/a8cteam51/simple-events",
+ "license": "GPL-2.0-or-later",
+ "authors": [{
+ "name": "WordPress.com Special Projects Team",
+ "homepage": "https://wpspecialprojects.wordpress.com"
+ },
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/a8cteam51/simple-events/graphs/contributors"
+ }
+ ],
- "description": "A simple Gutenberg-first event management plugin that integrates with WooCommerce Box Office.",
- "homepage": "https://github.com/a8cteam51/simple-events",
- "license": "GPL-2.0-or-later",
- "authors": [
- {
- "name": "WordPress.com Special Projects Team",
- "homepage": "https://wpspecialprojects.wordpress.com"
+ "repositories": [{
+ "type": "vcs",
+ "url": "https://github.com/a8cteam51/team51-configs"
+ }],
+ "require": {
+ "eluceo/ical": "^2.14.0"
},
- {
- "name": "Contributors",
- "homepage": "https://github.com/a8cteam51/simple-events/graphs/contributors"
- }
- ],
-
- "repositories": [
- {
- "type": "vcs",
- "url": "https://github.com/a8cteam51/team51-configs"
- }
- ],
- "require": {
- "eluceo/ical": "^0.16.0"
- },
- "require-dev": {
- "a8cteam51/team51-configs": "dev-trunk",
+ "require-dev": {
+ "a8cteam51/team51-configs": "dev-trunk",
- "wp-coding-standards/wpcs": "^3.0",
- "phpcompatibility/phpcompatibility-wp": "*",
+ "wp-coding-standards/wpcs": "^3.0",
+ "phpcompatibility/phpcompatibility-wp": "*",
- "roave/security-advisories": "dev-latest"
- },
+ "roave/security-advisories": "dev-latest"
+ },
- "scripts": {
- "generate-autoloader": "@composer dump-autoload -o",
+ "scripts": {
+ "generate-autoloader": "@composer dump-autoload -o",
- "format:php": "phpcbf --basepath=. . -v",
- "lint:php": "phpcs --basepath=. . -v",
+ "format:php": "phpcbf --basepath=. . -v",
+ "lint:php": "phpcs --basepath=. . -v",
- "packages-install": "@composer install --ignore-platform-reqs --no-interaction",
- "packages-update": [
- "@composer clear-cache",
- "@composer update --prefer-stable --no-interaction"
- ]
- },
- "config": {
- "allow-plugins": {
- "composer/*": true,
- "dealerdirect/phpcodesniffer-composer-installer": true
+ "packages-install": "@composer install --ignore-platform-reqs --no-interaction",
+ "packages-update": [
+ "@composer clear-cache",
+ "@composer update --prefer-stable --no-interaction"
+ ]
+ },
+ "config": {
+ "allow-plugins": {
+ "composer/*": true,
+ "dealerdirect/phpcodesniffer-composer-installer": true,
+ "phpstan/extension-installer": true
+ }
}
- }
-}
+}
\ No newline at end of file
diff --git a/composer.lock b/composer.lock
index 3e68f59..e840cf1 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,30 +4,37 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "dc9939c4ef3864569f02d4d374da0639",
+ "content-hash": "c58913b7b5a16b9d665efe3497cf7b30",
"packages": [
{
"name": "eluceo/ical",
- "version": "0.16.1",
+ "version": "2.14.0",
"source": {
"type": "git",
"url": "https://github.com/markuspoerschke/iCal.git",
- "reference": "7043337feaeacbc016844e7e52ef41bba504ad8f"
+ "reference": "3123533f7ff0af015da1d788476204f936d18135"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/markuspoerschke/iCal/zipball/7043337feaeacbc016844e7e52ef41bba504ad8f",
- "reference": "7043337feaeacbc016844e7e52ef41bba504ad8f",
+ "url": "https://api.github.com/repos/markuspoerschke/iCal/zipball/3123533f7ff0af015da1d788476204f936d18135",
+ "reference": "3123533f7ff0af015da1d788476204f936d18135",
"shasum": ""
},
"require": {
- "php": ">=7.1 || ~8.0.0"
+ "ext-mbstring": "*",
+ "php": ">=7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0",
+ "symfony/deprecation-contracts": "^2.1 || ^3.0"
},
- "require-dev": {
- "phpunit/phpunit": "^7.0"
+ "conflict": {
+ "php": "7.4.6"
},
- "suggest": {
- "ext-mbstring": "Massive performance enhancement of line folding"
+ "require-dev": {
+ "ergebnis/composer-normalize": "^2.23.1",
+ "friendsofphp/php-cs-fixer": "^3.4",
+ "infection/infection": "^0.23 || ^0.26 || ^0.27",
+ "phpmd/phpmd": "^2.13",
+ "phpunit/phpunit": "^9.5",
+ "vimeo/psalm": "^4.8 || ^5.0"
},
"type": "library",
"autoload": {
@@ -42,11 +49,11 @@
"authors": [
{
"name": "Markus Poerschke",
- "email": "markus@eluceo.de",
+ "email": "markus@poerschke.nrw",
"role": "Developer"
}
],
- "description": "The eluceo/iCal package offers a abstraction layer for creating iCalendars. You can easily create iCal files by using PHP object instead of typing your *.ics file by hand. The output will follow RFC 5545 as best as possible.",
+ "description": "The eluceo/iCal package offers an abstraction layer for creating iCalendars. You can easily create iCal files by using PHP objects instead of typing your *.ics file by hand. The output will follow RFC 5545 as best as possible.",
"homepage": "https://github.com/markuspoerschke/iCal",
"keywords": [
"calendar",
@@ -56,10 +63,79 @@
"php calendar"
],
"support": {
+ "docs": "https://ical.poerschke.nrw",
+ "forum": "https://github.com/markuspoerschke/iCal/discussions",
"issues": "https://github.com/markuspoerschke/iCal/issues",
"source": "https://github.com/markuspoerschke/iCal"
},
- "time": "2020-10-04T17:41:11+00:00"
+ "time": "2024-07-11T22:33:13+00:00"
+ },
+ {
+ "name": "symfony/deprecation-contracts",
+ "version": "v3.6.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/deprecation-contracts.git",
+ "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62",
+ "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "3.6-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "function.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "A generic function and convention to trigger deprecation notices",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-25T14:21:43+00:00"
}
],
"packages-dev": [
@@ -69,23 +145,31 @@
"source": {
"type": "git",
"url": "https://github.com/a8cteam51/team51-configs.git",
- "reference": "e4737f2cba9a57d72a32cbf46bca54b69555e3a5"
+ "reference": "1f025c367e0287886d1f7490618950131fa8d491"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/a8cteam51/team51-configs/zipball/e4737f2cba9a57d72a32cbf46bca54b69555e3a5",
- "reference": "e4737f2cba9a57d72a32cbf46bca54b69555e3a5",
+ "url": "https://api.github.com/repos/a8cteam51/team51-configs/zipball/1f025c367e0287886d1f7490618950131fa8d491",
+ "reference": "1f025c367e0287886d1f7490618950131fa8d491",
"shasum": ""
},
"require": {
"ext-json": "*",
- "php": ">=7.4"
- },
- "require-dev": {
- "composer/composer": "^2.6",
+ "johnbillion/wp-compat": "^1",
+ "php": ">=8.3",
"phpcompatibility/phpcompatibility-wp": "*",
+ "phpmd/phpmd": "^2",
+ "phpstan/extension-installer": "^1",
+ "phpstan/phpstan": "^2",
+ "phpstan/phpstan-deprecation-rules": "^2",
+ "phpstan/phpstan-strict-rules": "^2",
+ "swissspidy/phpstan-no-private": "^1",
+ "szepeviktor/phpstan-wordpress": "^2",
"wp-coding-standards/wpcs": "^3"
},
+ "require-dev": {
+ "composer/composer": "^2"
+ },
"default-branch": true,
"type": "library",
"autoload": {
@@ -94,12 +178,15 @@
]
},
"scripts": {
- "composer:install": [
+ "generate-autoloader": [
+ "@composer dump-autoload --ignore-platform-reqs -o"
+ ],
+ "packages-install": [
"@composer install --ignore-platform-reqs --no-interaction"
],
- "composer:update": [
+ "packages-update": [
"@composer clear-cache",
- "@composer update --prefer-stable --no-interaction"
+ "@composer update --prefer-stable --ignore-platform-reqs --no-interaction"
]
},
"license": [
@@ -116,32 +203,177 @@
"source": "https://github.com/a8cteam51/team51-configs/tree/trunk",
"issues": "https://github.com/a8cteam51/team51-configs/issues"
},
- "time": "2023-10-10T15:57:04+00:00"
+ "time": "2025-05-25T15:59:20+00:00"
+ },
+ {
+ "name": "composer/pcre",
+ "version": "3.3.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/composer/pcre.git",
+ "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
+ "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.4 || ^8.0"
+ },
+ "conflict": {
+ "phpstan/phpstan": "<1.11.10"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^1.12 || ^2",
+ "phpstan/phpstan-strict-rules": "^1 || ^2",
+ "phpunit/phpunit": "^8 || ^9"
+ },
+ "type": "library",
+ "extra": {
+ "phpstan": {
+ "includes": [
+ "extension.neon"
+ ]
+ },
+ "branch-alias": {
+ "dev-main": "3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Composer\\Pcre\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jordi Boggiano",
+ "email": "j.boggiano@seld.be",
+ "homepage": "http://seld.be"
+ }
+ ],
+ "description": "PCRE wrapping library that offers type-safe preg_* replacements.",
+ "keywords": [
+ "PCRE",
+ "preg",
+ "regex",
+ "regular expression"
+ ],
+ "support": {
+ "issues": "https://github.com/composer/pcre/issues",
+ "source": "https://github.com/composer/pcre/tree/3.3.2"
+ },
+ "funding": [
+ {
+ "url": "https://packagist.com",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/composer",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/composer/composer",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-11-12T16:29:46+00:00"
+ },
+ {
+ "name": "composer/xdebug-handler",
+ "version": "3.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/composer/xdebug-handler.git",
+ "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef",
+ "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef",
+ "shasum": ""
+ },
+ "require": {
+ "composer/pcre": "^1 || ^2 || ^3",
+ "php": "^7.2.5 || ^8.0",
+ "psr/log": "^1 || ^2 || ^3"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^1.0",
+ "phpstan/phpstan-strict-rules": "^1.1",
+ "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Composer\\XdebugHandler\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "John Stevenson",
+ "email": "john-stevenson@blueyonder.co.uk"
+ }
+ ],
+ "description": "Restarts a process without Xdebug.",
+ "keywords": [
+ "Xdebug",
+ "performance"
+ ],
+ "support": {
+ "irc": "ircs://irc.libera.chat:6697/composer",
+ "issues": "https://github.com/composer/xdebug-handler/issues",
+ "source": "https://github.com/composer/xdebug-handler/tree/3.0.5"
+ },
+ "funding": [
+ {
+ "url": "https://packagist.com",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/composer",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/composer/composer",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-05-06T16:37:16+00:00"
},
{
"name": "dealerdirect/phpcodesniffer-composer-installer",
- "version": "v1.0.0",
+ "version": "v1.1.1",
"source": {
"type": "git",
"url": "https://github.com/PHPCSStandards/composer-installer.git",
- "reference": "4be43904336affa5c2f70744a348312336afd0da"
+ "reference": "6e0fa428497bf560152ee73ffbb8af5c6a56b0dd"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/4be43904336affa5c2f70744a348312336afd0da",
- "reference": "4be43904336affa5c2f70744a348312336afd0da",
+ "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/6e0fa428497bf560152ee73ffbb8af5c6a56b0dd",
+ "reference": "6e0fa428497bf560152ee73ffbb8af5c6a56b0dd",
"shasum": ""
},
"require": {
- "composer-plugin-api": "^1.0 || ^2.0",
+ "composer-plugin-api": "^2.2",
"php": ">=5.4",
"squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0"
},
"require-dev": {
- "composer/composer": "*",
+ "composer/composer": "^2.2",
"ext-json": "*",
"ext-zip": "*",
- "php-parallel-lint/php-parallel-lint": "^1.3.1",
+ "php-parallel-lint/php-parallel-lint": "^1.4.0",
"phpcompatibility/php-compatibility": "^9.0",
"yoast/phpunit-polyfills": "^1.0"
},
@@ -161,9 +393,9 @@
"authors": [
{
"name": "Franck Nijhof",
- "email": "franck.nijhof@dealerdirect.com",
- "homepage": "http://www.frenck.nl",
- "role": "Developer / IT Manager"
+ "email": "opensource@frenck.dev",
+ "homepage": "https://frenck.dev",
+ "role": "Open source developer"
},
{
"name": "Contributors",
@@ -171,7 +403,6 @@
}
],
"description": "PHP_CodeSniffer Standards Composer Installer Plugin",
- "homepage": "http://www.dealerdirect.com",
"keywords": [
"PHPCodeSniffer",
"PHP_CodeSniffer",
@@ -192,152 +423,379 @@
],
"support": {
"issues": "https://github.com/PHPCSStandards/composer-installer/issues",
+ "security": "https://github.com/PHPCSStandards/composer-installer/security/policy",
"source": "https://github.com/PHPCSStandards/composer-installer"
},
- "time": "2023-01-05T11:28:13+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"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/phpcsstandards",
+ "type": "thanks_dev"
+ }
+ ],
+ "time": "2025-06-27T17:24:01+00:00"
},
{
- "name": "phpcompatibility/php-compatibility",
- "version": "9.3.5",
+ "name": "johnbillion/wp-compat",
+ "version": "1.3.0",
"source": {
"type": "git",
- "url": "https://github.com/PHPCompatibility/PHPCompatibility.git",
- "reference": "9fb324479acf6f39452e0655d2429cc0d3914243"
+ "url": "https://github.com/johnbillion/wp-compat.git",
+ "reference": "ea44e487191b39b2beea0e8e4ee4e1f28376e0eb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243",
- "reference": "9fb324479acf6f39452e0655d2429cc0d3914243",
+ "url": "https://api.github.com/repos/johnbillion/wp-compat/zipball/ea44e487191b39b2beea0e8e4ee4e1f28376e0eb",
+ "reference": "ea44e487191b39b2beea0e8e4ee4e1f28376e0eb",
"shasum": ""
},
"require": {
- "php": ">=5.3",
- "squizlabs/php_codesniffer": "^2.3 || ^3.0.2"
- },
- "conflict": {
- "squizlabs/php_codesniffer": "2.6.2"
+ "php": ">= 7.4",
+ "phpstan/phpstan": "^2.0",
+ "wp-hooks/wordpress-core": "^1.10"
},
"require-dev": {
- "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0"
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
+ "johnbillion/plugin-infrastructure": "dev-trunk",
+ "nikic/php-parser": "^5.1",
+ "php-stubs/wordpress-stubs": "^6.6",
+ "phpstan/phpstan-deprecation-rules": "2.0.0",
+ "phpstan/phpstan-phpunit": "2.0.1",
+ "phpstan/phpstan-strict-rules": "2.0.0",
+ "phpunit/phpunit": "^9.0",
+ "roots/wordpress-core-installer": "1.100.0",
+ "roots/wordpress-full": "*",
+ "wp-coding-standards/wpcs": "3.1.0"
},
"suggest": {
- "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.",
- "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
+ "phpstan/phpstan-deprecation-rules": "PHPStan rules for detecting usage of deprecated symbols",
+ "swissspidy/phpstan-no-private": "PHPStan rules for detecting usage of pseudo-private functions, classes, and methods",
+ "szepeviktor/phpstan-wordpress": "WordPress extensions for PHPStan"
+ },
+ "type": "phpstan-extension",
+ "extra": {
+ "phpstan": {
+ "includes": [
+ "extension.neon"
+ ]
+ },
+ "wordpress-install-dir": "vendor/wordpress/wordpress"
+ },
+ "autoload": {
+ "psr-4": {
+ "WPCompat\\PHPStan\\": "src/"
+ }
},
- "type": "phpcodesniffer-standard",
"notification-url": "https://packagist.org/downloads/",
"license": [
- "LGPL-3.0-or-later"
+ "MIT"
],
"authors": [
{
- "name": "Wim Godden",
- "homepage": "https://github.com/wimg",
- "role": "lead"
- },
- {
- "name": "Juliette Reinders Folmer",
- "homepage": "https://github.com/jrfnl",
- "role": "lead"
- },
- {
- "name": "Contributors",
- "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors"
+ "name": "John Blackbourn",
+ "homepage": "https://johnblackbourn.com/"
}
],
- "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.",
- "homepage": "http://techblog.wimgodden.be/tag/codesniffer/",
+ "description": "PHPStan extension to help verify that your PHP code is compatible with a given version of WordPress",
"keywords": [
- "compatibility",
- "phpcs",
- "standards"
+ "PHPStan",
+ "wordpress"
],
"support": {
- "issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues",
- "source": "https://github.com/PHPCompatibility/PHPCompatibility"
+ "issues": "https://github.com/johnbillion/wp-compat/issues",
+ "source": "https://github.com/johnbillion/wp-compat"
},
- "time": "2019-12-27T09:44:58+00:00"
+ "funding": [
+ {
+ "url": "https://github.com/sponsors/johnbillion",
+ "type": "github"
+ }
+ ],
+ "time": "2025-07-03T15:08:02+00:00"
},
{
- "name": "phpcompatibility/phpcompatibility-paragonie",
- "version": "1.3.2",
+ "name": "pdepend/pdepend",
+ "version": "2.16.2",
"source": {
"type": "git",
- "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git",
- "reference": "bba5a9dfec7fcfbd679cfaf611d86b4d3759da26"
+ "url": "https://github.com/pdepend/pdepend.git",
+ "reference": "f942b208dc2a0868454d01b29f0c75bbcfc6ed58"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/bba5a9dfec7fcfbd679cfaf611d86b4d3759da26",
- "reference": "bba5a9dfec7fcfbd679cfaf611d86b4d3759da26",
+ "url": "https://api.github.com/repos/pdepend/pdepend/zipball/f942b208dc2a0868454d01b29f0c75bbcfc6ed58",
+ "reference": "f942b208dc2a0868454d01b29f0c75bbcfc6ed58",
"shasum": ""
},
"require": {
- "phpcompatibility/php-compatibility": "^9.0"
+ "php": ">=5.3.7",
+ "symfony/config": "^2.3.0|^3|^4|^5|^6.0|^7.0",
+ "symfony/dependency-injection": "^2.3.0|^3|^4|^5|^6.0|^7.0",
+ "symfony/filesystem": "^2.3.0|^3|^4|^5|^6.0|^7.0",
+ "symfony/polyfill-mbstring": "^1.19"
},
"require-dev": {
- "dealerdirect/phpcodesniffer-composer-installer": "^0.7",
- "paragonie/random_compat": "dev-master",
- "paragonie/sodium_compat": "dev-master"
+ "easy-doc/easy-doc": "0.0.0|^1.2.3",
+ "gregwar/rst": "^1.0",
+ "squizlabs/php_codesniffer": "^2.0.0"
},
- "suggest": {
- "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.",
- "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
+ "bin": [
+ "src/bin/pdepend"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PDepend\\": "src/main/php/PDepend"
+ }
},
- "type": "phpcodesniffer-standard",
"notification-url": "https://packagist.org/downloads/",
"license": [
- "LGPL-3.0-or-later"
- ],
- "authors": [
- {
- "name": "Wim Godden",
- "role": "lead"
- },
- {
- "name": "Juliette Reinders Folmer",
- "role": "lead"
- }
+ "BSD-3-Clause"
],
- "description": "A set of rulesets for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by the Paragonie polyfill libraries.",
- "homepage": "http://phpcompatibility.com/",
+ "description": "Official version of pdepend to be handled with Composer",
"keywords": [
- "compatibility",
- "paragonie",
- "phpcs",
- "polyfill",
- "standards",
- "static analysis"
+ "PHP Depend",
+ "PHP_Depend",
+ "dev",
+ "pdepend"
],
"support": {
- "issues": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/issues",
- "source": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie"
+ "issues": "https://github.com/pdepend/pdepend/issues",
+ "source": "https://github.com/pdepend/pdepend/tree/2.16.2"
},
- "time": "2022-10-25T01:46:02+00:00"
+ "funding": [
+ {
+ "url": "https://tidelift.com/funding/github/packagist/pdepend/pdepend",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2023-12-17T18:09:59+00:00"
},
{
- "name": "phpcompatibility/phpcompatibility-wp",
- "version": "2.1.4",
+ "name": "php-stubs/wordpress-stubs",
+ "version": "v6.8.1",
"source": {
"type": "git",
- "url": "https://github.com/PHPCompatibility/PHPCompatibilityWP.git",
- "reference": "b6c1e3ee1c35de6c41a511d5eb9bd03e447480a5"
+ "url": "https://github.com/php-stubs/wordpress-stubs.git",
+ "reference": "92e444847d94f7c30f88c60004648f507688acd5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/b6c1e3ee1c35de6c41a511d5eb9bd03e447480a5",
- "reference": "b6c1e3ee1c35de6c41a511d5eb9bd03e447480a5",
+ "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/92e444847d94f7c30f88c60004648f507688acd5",
+ "reference": "92e444847d94f7c30f88c60004648f507688acd5",
"shasum": ""
},
- "require": {
+ "conflict": {
+ "phpdocumentor/reflection-docblock": "5.6.1"
+ },
+ "require-dev": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^1.0",
+ "nikic/php-parser": "^5.4",
+ "php": "^7.4 || ^8.0",
+ "php-stubs/generator": "^0.8.3",
+ "phpdocumentor/reflection-docblock": "^5.4.1",
+ "phpstan/phpstan": "^2.1",
+ "phpunit/phpunit": "^9.5",
+ "szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^1.1.1",
+ "wp-coding-standards/wpcs": "3.1.0 as 2.3.0"
+ },
+ "suggest": {
+ "paragonie/sodium_compat": "Pure PHP implementation of libsodium",
+ "symfony/polyfill-php80": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+ "szepeviktor/phpstan-wordpress": "WordPress extensions for PHPStan"
+ },
+ "type": "library",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "WordPress function and class declaration stubs for static analysis.",
+ "homepage": "https://github.com/php-stubs/wordpress-stubs",
+ "keywords": [
+ "PHPStan",
+ "static analysis",
+ "wordpress"
+ ],
+ "support": {
+ "issues": "https://github.com/php-stubs/wordpress-stubs/issues",
+ "source": "https://github.com/php-stubs/wordpress-stubs/tree/v6.8.1"
+ },
+ "time": "2025-05-02T12:33:34+00:00"
+ },
+ {
+ "name": "phpcompatibility/php-compatibility",
+ "version": "9.3.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPCompatibility/PHPCompatibility.git",
+ "reference": "9fb324479acf6f39452e0655d2429cc0d3914243"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243",
+ "reference": "9fb324479acf6f39452e0655d2429cc0d3914243",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3",
+ "squizlabs/php_codesniffer": "^2.3 || ^3.0.2"
+ },
+ "conflict": {
+ "squizlabs/php_codesniffer": "2.6.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0"
+ },
+ "suggest": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.",
+ "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
+ },
+ "type": "phpcodesniffer-standard",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL-3.0-or-later"
+ ],
+ "authors": [
+ {
+ "name": "Wim Godden",
+ "homepage": "https://github.com/wimg",
+ "role": "lead"
+ },
+ {
+ "name": "Juliette Reinders Folmer",
+ "homepage": "https://github.com/jrfnl",
+ "role": "lead"
+ },
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors"
+ }
+ ],
+ "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.",
+ "homepage": "http://techblog.wimgodden.be/tag/codesniffer/",
+ "keywords": [
+ "compatibility",
+ "phpcs",
+ "standards"
+ ],
+ "support": {
+ "issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues",
+ "source": "https://github.com/PHPCompatibility/PHPCompatibility"
+ },
+ "time": "2019-12-27T09:44:58+00:00"
+ },
+ {
+ "name": "phpcompatibility/phpcompatibility-paragonie",
+ "version": "1.3.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git",
+ "reference": "293975b465e0e709b571cbf0c957c6c0a7b9a2ac"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/293975b465e0e709b571cbf0c957c6c0a7b9a2ac",
+ "reference": "293975b465e0e709b571cbf0c957c6c0a7b9a2ac",
+ "shasum": ""
+ },
+ "require": {
+ "phpcompatibility/php-compatibility": "^9.0"
+ },
+ "require-dev": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^1.0",
+ "paragonie/random_compat": "dev-master",
+ "paragonie/sodium_compat": "dev-master"
+ },
+ "suggest": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^1.0 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.",
+ "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
+ },
+ "type": "phpcodesniffer-standard",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL-3.0-or-later"
+ ],
+ "authors": [
+ {
+ "name": "Wim Godden",
+ "role": "lead"
+ },
+ {
+ "name": "Juliette Reinders Folmer",
+ "role": "lead"
+ }
+ ],
+ "description": "A set of rulesets for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by the Paragonie polyfill libraries.",
+ "homepage": "http://phpcompatibility.com/",
+ "keywords": [
+ "compatibility",
+ "paragonie",
+ "phpcs",
+ "polyfill",
+ "standards",
+ "static analysis"
+ ],
+ "support": {
+ "issues": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/issues",
+ "security": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/security/policy",
+ "source": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/PHPCompatibility",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/jrfnl",
+ "type": "github"
+ },
+ {
+ "url": "https://opencollective.com/php_codesniffer",
+ "type": "open_collective"
+ }
+ ],
+ "time": "2024-04-24T21:30:46+00:00"
+ },
+ {
+ "name": "phpcompatibility/phpcompatibility-wp",
+ "version": "2.1.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPCompatibility/PHPCompatibilityWP.git",
+ "reference": "5bfbbfbabb3df2b9a83e601de9153e4a7111962c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/5bfbbfbabb3df2b9a83e601de9153e4a7111962c",
+ "reference": "5bfbbfbabb3df2b9a83e601de9153e4a7111962c",
+ "shasum": ""
+ },
+ "require": {
"phpcompatibility/php-compatibility": "^9.0",
- "phpcompatibility/phpcompatibility-paragonie": "^1.0"
+ "phpcompatibility/phpcompatibility-paragonie": "^1.0",
+ "squizlabs/php_codesniffer": "^3.3"
},
"require-dev": {
- "dealerdirect/phpcodesniffer-composer-installer": "^0.7"
+ "dealerdirect/phpcodesniffer-composer-installer": "^1.0"
},
"suggest": {
- "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.",
+ "dealerdirect/phpcodesniffer-composer-installer": "^1.0 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.",
"roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
},
"type": "phpcodesniffer-standard",
@@ -366,35 +824,54 @@
],
"support": {
"issues": "https://github.com/PHPCompatibility/PHPCompatibilityWP/issues",
+ "security": "https://github.com/PHPCompatibility/PHPCompatibilityWP/security/policy",
"source": "https://github.com/PHPCompatibility/PHPCompatibilityWP"
},
- "time": "2022-10-24T09:00:36+00:00"
+ "funding": [
+ {
+ "url": "https://github.com/PHPCompatibility",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/jrfnl",
+ "type": "github"
+ },
+ {
+ "url": "https://opencollective.com/php_codesniffer",
+ "type": "open_collective"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/phpcompatibility",
+ "type": "thanks_dev"
+ }
+ ],
+ "time": "2025-05-12T16:38:37+00:00"
},
{
"name": "phpcsstandards/phpcsextra",
- "version": "1.2.1",
+ "version": "1.4.0",
"source": {
"type": "git",
"url": "https://github.com/PHPCSStandards/PHPCSExtra.git",
- "reference": "11d387c6642b6e4acaf0bd9bf5203b8cca1ec489"
+ "reference": "fa4b8d051e278072928e32d817456a7fdb57b6ca"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/11d387c6642b6e4acaf0bd9bf5203b8cca1ec489",
- "reference": "11d387c6642b6e4acaf0bd9bf5203b8cca1ec489",
+ "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/fa4b8d051e278072928e32d817456a7fdb57b6ca",
+ "reference": "fa4b8d051e278072928e32d817456a7fdb57b6ca",
"shasum": ""
},
"require": {
"php": ">=5.4",
- "phpcsstandards/phpcsutils": "^1.0.9",
- "squizlabs/php_codesniffer": "^3.8.0"
+ "phpcsstandards/phpcsutils": "^1.1.0",
+ "squizlabs/php_codesniffer": "^3.13.0 || ^4.0"
},
"require-dev": {
"php-parallel-lint/php-console-highlighter": "^1.0",
- "php-parallel-lint/php-parallel-lint": "^1.3.2",
+ "php-parallel-lint/php-parallel-lint": "^1.4.0",
"phpcsstandards/phpcsdevcs": "^1.1.6",
"phpcsstandards/phpcsdevtools": "^1.2.1",
- "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0"
+ "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4"
},
"type": "phpcodesniffer-standard",
"extra": {
@@ -444,35 +921,39 @@
{
"url": "https://opencollective.com/php_codesniffer",
"type": "open_collective"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/phpcsstandards",
+ "type": "thanks_dev"
}
],
- "time": "2023-12-08T16:49:07+00:00"
+ "time": "2025-06-14T07:40:39+00:00"
},
{
"name": "phpcsstandards/phpcsutils",
- "version": "1.0.9",
+ "version": "1.1.0",
"source": {
"type": "git",
"url": "https://github.com/PHPCSStandards/PHPCSUtils.git",
- "reference": "908247bc65010c7b7541a9551e002db12e9dae70"
+ "reference": "65355670ac17c34cd235cf9d3ceae1b9252c4dad"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/908247bc65010c7b7541a9551e002db12e9dae70",
- "reference": "908247bc65010c7b7541a9551e002db12e9dae70",
+ "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/65355670ac17c34cd235cf9d3ceae1b9252c4dad",
+ "reference": "65355670ac17c34cd235cf9d3ceae1b9252c4dad",
"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.8.0 || 4.0.x-dev@dev"
+ "squizlabs/php_codesniffer": "^3.13.0 || ^4.0"
},
"require-dev": {
"ext-filter": "*",
"php-parallel-lint/php-console-highlighter": "^1.0",
- "php-parallel-lint/php-parallel-lint": "^1.3.2",
+ "php-parallel-lint/php-parallel-lint": "^1.4.0",
"phpcsstandards/phpcsdevcs": "^1.1.6",
- "yoast/phpunit-polyfills": "^1.1.0 || ^2.0.0"
+ "yoast/phpunit-polyfills": "^1.1.0 || ^2.0.0 || ^3.0.0"
},
"type": "phpcodesniffer-standard",
"extra": {
@@ -509,6 +990,7 @@
"phpcodesniffer-standard",
"phpcs",
"phpcs3",
+ "phpcs4",
"standards",
"static analysis",
"tokens",
@@ -532,45 +1014,445 @@
{
"url": "https://opencollective.com/php_codesniffer",
"type": "open_collective"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/phpcsstandards",
+ "type": "thanks_dev"
}
],
- "time": "2023-12-08T14:50:00+00:00"
+ "time": "2025-06-12T04:32:33+00:00"
},
{
- "name": "roave/security-advisories",
- "version": "dev-latest",
+ "name": "phpmd/phpmd",
+ "version": "2.15.0",
"source": {
"type": "git",
- "url": "https://github.com/Roave/SecurityAdvisories.git",
- "reference": "e3b3cce1f2454ee4575500e084211b11efdaf64b"
+ "url": "https://github.com/phpmd/phpmd.git",
+ "reference": "74a1f56e33afad4128b886e334093e98e1b5e7c0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpmd/phpmd/zipball/74a1f56e33afad4128b886e334093e98e1b5e7c0",
+ "reference": "74a1f56e33afad4128b886e334093e98e1b5e7c0",
+ "shasum": ""
+ },
+ "require": {
+ "composer/xdebug-handler": "^1.0 || ^2.0 || ^3.0",
+ "ext-xml": "*",
+ "pdepend/pdepend": "^2.16.1",
+ "php": ">=5.3.9"
+ },
+ "require-dev": {
+ "easy-doc/easy-doc": "0.0.0 || ^1.3.2",
+ "ext-json": "*",
+ "ext-simplexml": "*",
+ "gregwar/rst": "^1.0",
+ "mikey179/vfsstream": "^1.6.8",
+ "squizlabs/php_codesniffer": "^2.9.2 || ^3.7.2"
+ },
+ "bin": [
+ "src/bin/phpmd"
+ ],
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "PHPMD\\": "src/main/php"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Manuel Pichler",
+ "email": "github@manuel-pichler.de",
+ "homepage": "https://github.com/manuelpichler",
+ "role": "Project Founder"
+ },
+ {
+ "name": "Marc Würth",
+ "email": "ravage@bluewin.ch",
+ "homepage": "https://github.com/ravage84",
+ "role": "Project Maintainer"
+ },
+ {
+ "name": "Other contributors",
+ "homepage": "https://github.com/phpmd/phpmd/graphs/contributors",
+ "role": "Contributors"
+ }
+ ],
+ "description": "PHPMD is a spin-off project of PHP Depend and aims to be a PHP equivalent of the well known Java tool PMD.",
+ "homepage": "https://phpmd.org/",
+ "keywords": [
+ "dev",
+ "mess detection",
+ "mess detector",
+ "pdepend",
+ "phpmd",
+ "pmd"
+ ],
+ "support": {
+ "irc": "irc://irc.freenode.org/phpmd",
+ "issues": "https://github.com/phpmd/phpmd/issues",
+ "source": "https://github.com/phpmd/phpmd/tree/2.15.0"
+ },
+ "funding": [
+ {
+ "url": "https://tidelift.com/funding/github/packagist/phpmd/phpmd",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2023-12-11T08:22:20+00:00"
+ },
+ {
+ "name": "phpstan/extension-installer",
+ "version": "1.4.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpstan/extension-installer.git",
+ "reference": "85e90b3942d06b2326fba0403ec24fe912372936"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/85e90b3942d06b2326fba0403ec24fe912372936",
+ "reference": "85e90b3942d06b2326fba0403ec24fe912372936",
+ "shasum": ""
+ },
+ "require": {
+ "composer-plugin-api": "^2.0",
+ "php": "^7.2 || ^8.0",
+ "phpstan/phpstan": "^1.9.0 || ^2.0"
+ },
+ "require-dev": {
+ "composer/composer": "^2.0",
+ "php-parallel-lint/php-parallel-lint": "^1.2.0",
+ "phpstan/phpstan-strict-rules": "^0.11 || ^0.12 || ^1.0"
+ },
+ "type": "composer-plugin",
+ "extra": {
+ "class": "PHPStan\\ExtensionInstaller\\Plugin"
+ },
+ "autoload": {
+ "psr-4": {
+ "PHPStan\\ExtensionInstaller\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Composer plugin for automatic installation of PHPStan extensions",
+ "keywords": [
+ "dev",
+ "static analysis"
+ ],
+ "support": {
+ "issues": "https://github.com/phpstan/extension-installer/issues",
+ "source": "https://github.com/phpstan/extension-installer/tree/1.4.3"
+ },
+ "time": "2024-09-04T20:21:43+00:00"
+ },
+ {
+ "name": "phpstan/phpstan",
+ "version": "2.1.17",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpstan/phpstan.git",
+ "reference": "89b5ef665716fa2a52ecd2633f21007a6a349053"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/e3b3cce1f2454ee4575500e084211b11efdaf64b",
- "reference": "e3b3cce1f2454ee4575500e084211b11efdaf64b",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/89b5ef665716fa2a52ecd2633f21007a6a349053",
+ "reference": "89b5ef665716fa2a52ecd2633f21007a6a349053",
"shasum": ""
},
+ "require": {
+ "php": "^7.4|^8.0"
+ },
"conflict": {
- "3f/pygmentize": "<1.2",
- "admidio/admidio": "<4.2.13",
- "adodb/adodb-php": "<=5.20.20|>=5.21,<=5.21.3",
- "aheinze/cockpit": "<2.2",
- "aimeos/aimeos-typo3": "<19.10.12|>=20,<20.10.5",
- "airesvsg/acf-to-rest-api": "<=3.1",
- "akaunting/akaunting": "<2.1.13",
+ "phpstan/phpstan-shim": "*"
+ },
+ "bin": [
+ "phpstan",
+ "phpstan.phar"
+ ],
+ "type": "library",
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "PHPStan - PHP Static Analysis Tool",
+ "keywords": [
+ "dev",
+ "static analysis"
+ ],
+ "support": {
+ "docs": "https://phpstan.org/user-guide/getting-started",
+ "forum": "https://github.com/phpstan/phpstan/discussions",
+ "issues": "https://github.com/phpstan/phpstan/issues",
+ "security": "https://github.com/phpstan/phpstan/security/policy",
+ "source": "https://github.com/phpstan/phpstan-src"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/ondrejmirtes",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/phpstan",
+ "type": "github"
+ }
+ ],
+ "time": "2025-05-21T20:55:28+00:00"
+ },
+ {
+ "name": "phpstan/phpstan-deprecation-rules",
+ "version": "2.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpstan/phpstan-deprecation-rules.git",
+ "reference": "468e02c9176891cc901143da118f09dc9505fc2f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/468e02c9176891cc901143da118f09dc9505fc2f",
+ "reference": "468e02c9176891cc901143da118f09dc9505fc2f",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.4 || ^8.0",
+ "phpstan/phpstan": "^2.1.15"
+ },
+ "require-dev": {
+ "php-parallel-lint/php-parallel-lint": "^1.2",
+ "phpstan/phpstan-phpunit": "^2.0",
+ "phpunit/phpunit": "^9.6"
+ },
+ "type": "phpstan-extension",
+ "extra": {
+ "phpstan": {
+ "includes": [
+ "rules.neon"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PHPStan\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "PHPStan rules for detecting usage of deprecated classes, methods, properties, constants and traits.",
+ "support": {
+ "issues": "https://github.com/phpstan/phpstan-deprecation-rules/issues",
+ "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/2.0.3"
+ },
+ "time": "2025-05-14T10:56:57+00:00"
+ },
+ {
+ "name": "phpstan/phpstan-strict-rules",
+ "version": "2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpstan/phpstan-strict-rules.git",
+ "reference": "3e139cbe67fafa3588e1dbe27ca50f31fdb6236a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/3e139cbe67fafa3588e1dbe27ca50f31fdb6236a",
+ "reference": "3e139cbe67fafa3588e1dbe27ca50f31fdb6236a",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.4 || ^8.0",
+ "phpstan/phpstan": "^2.0.4"
+ },
+ "require-dev": {
+ "php-parallel-lint/php-parallel-lint": "^1.2",
+ "phpstan/phpstan-deprecation-rules": "^2.0",
+ "phpstan/phpstan-phpunit": "^2.0",
+ "phpunit/phpunit": "^9.6"
+ },
+ "type": "phpstan-extension",
+ "extra": {
+ "phpstan": {
+ "includes": [
+ "rules.neon"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PHPStan\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Extra strict and opinionated rules for PHPStan",
+ "support": {
+ "issues": "https://github.com/phpstan/phpstan-strict-rules/issues",
+ "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.4"
+ },
+ "time": "2025-03-18T11:42:40+00:00"
+ },
+ {
+ "name": "psr/container",
+ "version": "2.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/container.git",
+ "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+ "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Container\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common Container Interface (PHP FIG PSR-11)",
+ "homepage": "https://github.com/php-fig/container",
+ "keywords": [
+ "PSR-11",
+ "container",
+ "container-interface",
+ "container-interop",
+ "psr"
+ ],
+ "support": {
+ "issues": "https://github.com/php-fig/container/issues",
+ "source": "https://github.com/php-fig/container/tree/2.0.2"
+ },
+ "time": "2021-11-05T16:47:00+00:00"
+ },
+ {
+ "name": "psr/log",
+ "version": "3.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/log.git",
+ "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
+ "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.0.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Log\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for logging libraries",
+ "homepage": "https://github.com/php-fig/log",
+ "keywords": [
+ "log",
+ "psr",
+ "psr-3"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/log/tree/3.0.2"
+ },
+ "time": "2024-09-11T13:17:53+00:00"
+ },
+ {
+ "name": "roave/security-advisories",
+ "version": "dev-latest",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Roave/SecurityAdvisories.git",
+ "reference": "a76f62e135c8b583602bd99df737b5c20f4d7200"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/a76f62e135c8b583602bd99df737b5c20f4d7200",
+ "reference": "a76f62e135c8b583602bd99df737b5c20f4d7200",
+ "shasum": ""
+ },
+ "conflict": {
+ "3f/pygmentize": "<1.2",
+ "adaptcms/adaptcms": "<=1.3",
+ "admidio/admidio": "<4.3.12",
+ "adodb/adodb-php": "<=5.22.8",
+ "aheinze/cockpit": "<2.2",
+ "aimeos/ai-admin-graphql": ">=2022.04.1,<2022.10.10|>=2023.04.1,<2023.10.6|>=2024.04.1,<2024.07.2",
+ "aimeos/ai-admin-jsonadm": "<2020.10.13|>=2021.04.1,<2021.10.6|>=2022.04.1,<2022.10.3|>=2023.04.1,<2023.10.4|==2024.04.1",
+ "aimeos/ai-client-html": ">=2020.04.1,<2020.10.27|>=2021.04.1,<2021.10.22|>=2022.04.1,<2022.10.13|>=2023.04.1,<2023.10.15|>=2024.04.1,<2024.04.7",
+ "aimeos/ai-controller-frontend": "<2020.10.15|>=2021.04.1,<2021.10.8|>=2022.04.1,<2022.10.8|>=2023.04.1,<2023.10.9|==2024.04.1",
+ "aimeos/aimeos-core": ">=2022.04.1,<2022.10.17|>=2023.04.1,<2023.10.17|>=2024.04.1,<2024.04.7",
+ "aimeos/aimeos-typo3": "<19.10.12|>=20,<20.10.5",
+ "airesvsg/acf-to-rest-api": "<=3.1",
+ "akaunting/akaunting": "<2.1.13",
"akeneo/pim-community-dev": "<5.0.119|>=6,<6.0.53",
- "alextselegidis/easyappointments": "<1.5",
+ "alextselegidis/easyappointments": "<=1.5.1",
"alterphp/easyadmin-extension-bundle": ">=1.2,<1.2.11|>=1.3,<1.3.1",
"amazing/media2click": ">=1,<1.3.3",
+ "ameos/ameos_tarteaucitron": "<1.2.23",
"amphp/artax": "<1.0.6|>=2,<2.0.6",
- "amphp/http": "<1.0.1",
+ "amphp/http": "<=1.7.2|>=2,<=2.1",
"amphp/http-client": ">=4,<4.4",
"anchorcms/anchor-cms": "<=0.12.7",
"andreapollastri/cipi": "<=3.1.15",
"andrewhaine/silverstripe-form-capture": ">=0.2,<=0.2.3|>=1,<1.0.2|>=2,<2.2.5",
+ "aoe/restler": "<1.7.1",
"apache-solr-for-typo3/solr": "<2.8.3",
"apereo/phpcas": "<1.6",
- "api-platform/core": ">=2.2,<2.2.10|>=2.3,<2.3.6|>=2.6,<2.7.10|>=3,<3.0.12|>=3.1,<3.1.3",
+ "api-platform/core": "<3.4.17|>=4.0.0.0-alpha1,<4.0.22",
+ "api-platform/graphql": "<3.4.17|>=4.0.0.0-alpha1,<4.0.22",
"appwrite/server-ce": "<=1.2.1",
"arc/web": "<3",
"area17/twill": "<1.2.5|>=2,<2.5.3",
@@ -578,35 +1460,52 @@
"asymmetricrypt/asymmetricrypt": "<9.9.99",
"athlon1600/php-proxy": "<=5.1",
"athlon1600/php-proxy-app": "<=3",
+ "athlon1600/youtube-downloader": "<=4",
"austintoddj/canvas": "<=3.4.2",
- "automad/automad": "<=1.10.9",
+ "auth0/auth0-php": ">=8.0.0.0-beta1,<8.14",
+ "auth0/login": "<7.17",
+ "auth0/symfony": "<5.4",
+ "auth0/wordpress": "<5.3",
+ "automad/automad": "<2.0.0.0-alpha5",
+ "automattic/jetpack": "<9.8",
"awesome-support/awesome-support": "<=6.0.7",
"aws/aws-sdk-php": "<3.288.1",
"azuracast/azuracast": "<0.18.3",
- "backdrop/backdrop": "<1.24.2",
+ "b13/seo_basics": "<0.8.2",
+ "backdrop/backdrop": "<1.27.3|>=1.28,<1.28.2",
"backpack/crud": "<3.4.9",
+ "backpack/filemanager": "<2.0.2|>=3,<3.0.9",
"bacula-web/bacula-web": "<8.0.0.0-RC2-dev",
"badaso/core": "<2.7",
- "bagisto/bagisto": "<1.3.2",
+ "bagisto/bagisto": "<2.1",
"barrelstrength/sprout-base-email": "<1.2.7",
"barrelstrength/sprout-forms": "<3.9",
- "barryvdh/laravel-translation-manager": "<0.6.2",
+ "barryvdh/laravel-translation-manager": "<0.6.8",
"barzahlen/barzahlen-php": "<2.0.1",
- "baserproject/basercms": "<4.8",
+ "baserproject/basercms": "<=5.1.1",
"bassjobsen/bootstrap-3-typeahead": ">4.0.2",
+ "bbpress/bbpress": "<2.6.5",
+ "bcit-ci/codeigniter": "<3.1.3",
+ "bcosca/fatfree": "<3.7.2",
+ "bedita/bedita": "<4",
+ "bednee/cooluri": "<1.0.30",
"bigfork/silverstripe-form-capture": ">=3,<3.1.1",
- "billz/raspap-webgui": "<2.9.5",
+ "billz/raspap-webgui": "<3.3.6",
"bk2k/bootstrap-package": ">=7.1,<7.1.2|>=8,<8.0.8|>=9,<9.0.4|>=9.1,<9.1.3|>=10,<10.0.10|>=11,<11.0.3",
+ "blueimp/jquery-file-upload": "==6.4.4",
"bmarshall511/wordpress_zero_spam": "<5.2.13",
"bolt/bolt": "<3.7.2",
"bolt/core": "<=4.2",
+ "born05/craft-twofactorauthentication": "<3.3.4",
"bottelet/flarepoint": "<2.2.1",
+ "bref/bref": "<2.1.17",
"brightlocal/phpwhois": "<=4.2.5",
"brotkrueml/codehighlight": "<2.7",
"brotkrueml/schema": "<1.13.1|>=2,<2.5.1",
"brotkrueml/typo3-matomo-integration": "<1.3.2",
"buddypress/buddypress": "<7.2.1",
- "bugsnag/bugsnag-laravel": "<2.0.2",
+ "bugsnag/bugsnag-laravel": ">=2,<2.0.2",
+ "bvbmedia/multishop": "<2.0.39",
"bytefury/crater": "<6.0.2",
"cachethq/cachet": "<2.5.1",
"cakephp/cakephp": "<3.10.3|>=4,<4.0.10|>=4.1,<4.1.4|>=4.2,<4.2.12|>=4.3,<4.3.11|>=4.4,<4.4.10",
@@ -614,69 +1513,113 @@
"cardgate/magento2": "<2.0.33",
"cardgate/woocommerce": "<=3.1.15",
"cart2quote/module-quotation": ">=4.1.6,<=4.4.5|>=5,<5.4.4",
+ "cart2quote/module-quotation-encoded": ">=4.1.6,<=4.4.5|>=5,<5.4.4",
"cartalyst/sentry": "<=2.1.6",
"catfan/medoo": "<1.7.5",
+ "causal/oidc": "<4",
"cecil/cecil": "<7.47.1",
- "centreon/centreon": "<22.10.0.0-beta1",
+ "centreon/centreon": "<22.10.15",
"cesnet/simplesamlphp-module-proxystatistics": "<3.1",
"chriskacerguis/codeigniter-restserver": "<=2.7.1",
+ "chrome-php/chrome": "<1.14",
"civicrm/civicrm-core": ">=4.2,<4.2.9|>=4.3,<4.3.3",
- "ckeditor/ckeditor": "<4.17",
- "cockpit-hq/cockpit": "<=2.6.3",
+ "ckeditor/ckeditor": "<4.25",
+ "clickstorm/cs-seo": ">=6,<6.8|>=7,<7.5|>=8,<8.4|>=9,<9.3",
+ "co-stack/fal_sftp": "<0.2.6",
+ "cockpit-hq/cockpit": "<2.11.4",
"codeception/codeception": "<3.1.3|>=4,<4.1.22",
- "codeigniter/framework": "<3.1.9",
- "codeigniter4/framework": "<=4.4.2",
+ "codeigniter/framework": "<3.1.10",
+ "codeigniter4/framework": "<4.5.8",
"codeigniter4/shield": "<1.0.0.0-beta8",
"codiad/codiad": "<=2.8.4",
- "composer/composer": "<1.10.27|>=2,<2.2.22|>=2.3,<2.6.4",
- "concrete5/concrete5": "<9.2.3",
+ "codingms/additional-tca": ">=1.7,<1.15.17|>=1.16,<1.16.9",
+ "commerceteam/commerce": ">=0.9.6,<0.9.9",
+ "components/jquery": ">=1.0.3,<3.5",
+ "composer/composer": "<1.10.27|>=2,<2.2.24|>=2.3,<2.7.7",
+ "concrete5/concrete5": "<9.4.0.0-RC2-dev",
"concrete5/core": "<8.5.8|>=9,<9.1",
"contao-components/mediaelement": ">=2.14.2,<2.21.1",
- "contao/contao": ">=4,<4.4.56|>=4.5,<4.9.40|>=4.10,<4.11.7|>=4.13,<4.13.21|>=5.1,<5.1.4",
- "contao/core": ">=2,<3.5.39",
- "contao/core-bundle": "<4.9.42|>=4.10,<4.13.28|>=5,<5.1.10",
- "contao/listing-bundle": ">=4,<4.4.8",
+ "contao/comments-bundle": ">=2,<4.13.40|>=5.0.0.0-RC1-dev,<5.3.4",
+ "contao/contao": ">=3,<3.5.37|>=4,<4.4.56|>=4.5,<4.9.40|>=4.10,<4.11.7|>=4.13,<4.13.21|>=5.1,<5.1.4",
+ "contao/core": "<3.5.39",
+ "contao/core-bundle": "<4.13.54|>=5,<5.3.30|>=5.4,<5.5.6",
+ "contao/listing-bundle": ">=3,<=3.5.30|>=4,<4.4.8",
"contao/managed-edition": "<=1.5",
"corveda/phpsandbox": "<1.3.5",
"cosenary/instagram": "<=2.3",
- "craftcms/cms": "<4.6.2",
+ "couleurcitron/tarteaucitron-wp": "<0.3",
+ "craftcms/cms": "<4.15.3|>=5,<5.7.5",
"croogo/croogo": "<4",
"cuyz/valinor": "<0.12",
+ "czim/file-handling": "<1.5|>=2,<2.3",
"czproject/git-php": "<4.0.3",
+ "damienharper/auditor-bundle": "<5.2.6",
+ "dapphp/securimage": "<3.6.6",
"darylldoyle/safe-svg": "<1.9.10",
"datadog/dd-trace": ">=0.30,<0.30.2",
"datatables/datatables": "<1.10.10",
"david-garcia/phpwhois": "<=4.3.1",
"dbrisinajumi/d2files": "<1",
- "dcat/laravel-admin": "<=2.1.3.0-beta",
+ "dcat/laravel-admin": "<=2.1.3|==2.2.0.0-beta|==2.2.2.0-beta",
"derhansen/fe_change_pwd": "<2.0.5|>=3,<3.0.3",
- "derhansen/sf_event_mgt": "<4.3.1|>=5,<5.1.1",
+ "derhansen/sf_event_mgt": "<4.3.1|>=5,<5.1.1|>=7,<7.4",
"desperado/xml-bundle": "<=0.1.7",
+ "dev-lancer/minecraft-motd-parser": "<=1.0.5",
+ "devgroup/dotplant": "<2020.09.14-dev",
+ "digimix/wp-svg-upload": "<=1",
"directmailteam/direct-mail": "<6.0.3|>=7,<7.0.3|>=8,<9.5.2",
+ "dl/yag": "<3.0.1",
+ "dmk/webkitpdf": "<1.1.4",
+ "dnadesign/silverstripe-elemental": "<5.3.12",
"doctrine/annotations": "<1.2.7",
- "doctrine/cache": "<1.3.2|>=1.4,<1.4.2",
+ "doctrine/cache": ">=1,<1.3.2|>=1.4,<1.4.2",
"doctrine/common": "<2.4.3|>=2.5,<2.5.1",
"doctrine/dbal": ">=2,<2.0.8|>=2.1,<2.1.2|>=3,<3.1.4",
"doctrine/doctrine-bundle": "<1.5.2",
- "doctrine/doctrine-module": "<=0.7.1",
+ "doctrine/doctrine-module": "<0.7.2",
"doctrine/mongodb-odm": "<1.0.2",
"doctrine/mongodb-odm-bundle": "<3.0.1",
- "doctrine/orm": ">=2,<2.4.8|>=2.5,<2.5.1|>=2.8.3,<2.8.4",
- "dolibarr/dolibarr": "<18.0.2",
+ "doctrine/orm": ">=1,<1.2.4|>=2,<2.4.8|>=2.5,<2.5.1|>=2.8.3,<2.8.4",
+ "dolibarr/dolibarr": "<19.0.2|==21.0.0.0-beta",
"dompdf/dompdf": "<2.0.4",
"doublethreedigital/guest-entries": "<3.1.2",
- "drupal/core": "<9.5.11|>=10,<10.0.11|>=10.1,<10.1.4",
- "drupal/drupal": ">=6,<6.38|>=7,<7.80|>=8,<8.9.16|>=9,<9.1.12|>=9.2,<9.2.4",
+ "drupal/admin_audit_trail": "<1.0.5",
+ "drupal/ai": "<1.0.5",
+ "drupal/alogin": "<2.0.6",
+ "drupal/cache_utility": "<1.2.1",
+ "drupal/commerce_alphabank_redirect": "<1.0.3",
+ "drupal/commerce_eurobank_redirect": "<2.1.1",
+ "drupal/config_split": "<1.10|>=2,<2.0.2",
+ "drupal/core": ">=6,<6.38|>=7,<7.102|>=8,<10.3.14|>=10.4,<10.4.5|>=11,<11.0.13|>=11.1,<11.1.5",
+ "drupal/core-recommended": ">=7,<7.102|>=8,<10.2.11|>=10.3,<10.3.9|>=11,<11.0.8",
+ "drupal/drupal": ">=5,<5.11|>=6,<6.38|>=7,<7.102|>=8,<10.2.11|>=10.3,<10.3.9|>=11,<11.0.8",
+ "drupal/formatter_suite": "<2.1",
+ "drupal/gdpr": "<3.0.1|>=3.1,<3.1.2",
+ "drupal/google_tag": "<1.8|>=2,<2.0.8",
+ "drupal/ignition": "<1.0.4",
+ "drupal/lightgallery": "<1.6",
+ "drupal/link_field_display_mode_formatter": "<1.6",
+ "drupal/matomo": "<1.24",
+ "drupal/oauth2_client": "<4.1.3",
+ "drupal/oauth2_server": "<2.1",
+ "drupal/obfuscate": "<2.0.1",
+ "drupal/quick_node_block": "<2",
+ "drupal/rapidoc_elements_field_formatter": "<1.0.1",
+ "drupal/spamspan": "<3.2.1",
+ "drupal/tfa": "<1.10",
"duncanmcclean/guest-entries": "<3.1.2",
"dweeves/magmi": "<=0.7.24",
- "ec-cube/ec-cube": "<2.4.4",
+ "ec-cube/ec-cube": "<2.4.4|>=2.11,<=2.17.1|>=3,<=3.0.18.0-patch4|>=4,<=4.1.2",
"ecodev/newsletter": "<=4",
"ectouch/ectouch": "<=2.7.2",
+ "egroupware/egroupware": "<23.1.20240624",
"elefant/cms": "<2.0.7",
"elgg/elgg": "<3.3.24|>=4,<4.0.5",
"elijaa/phpmemcacheadmin": "<=1.3",
+ "elmsln/haxcms": "<11",
"encore/laravel-admin": "<=1.8.19",
"endroid/qr-code-bundle": "<3.4.2",
+ "enhavo/enhavo-app": "<=0.13.1",
"enshrined/svg-sanitize": "<0.15",
"erusev/parsedown": "<1.7.2",
"ether/logs": "<3.0.4",
@@ -688,30 +1631,39 @@
"ezsystems/ezdemo-ls-extension": ">=5.4,<5.4.2.1-dev",
"ezsystems/ezfind-ls": ">=5.3,<5.3.6.1-dev|>=5.4,<5.4.11.1-dev|>=2017.12,<2017.12.0.1-dev",
"ezsystems/ezplatform": "<=1.13.6|>=2,<=2.5.24",
- "ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.6|>=1.5,<1.5.29|>=2.3,<2.3.26",
- "ezsystems/ezplatform-admin-ui-assets": ">=4,<4.2.1|>=5,<5.0.1|>=5.1,<5.1.1",
+ "ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.6|>=1.5,<1.5.29|>=2.3,<2.3.38|>=3.3,<3.3.39",
+ "ezsystems/ezplatform-admin-ui-assets": ">=4,<4.2.1|>=5,<5.0.1|>=5.1,<5.1.1|>=5.3.0.0-beta1,<5.3.5",
"ezsystems/ezplatform-graphql": ">=1.0.0.0-RC1-dev,<1.0.13|>=2.0.0.0-beta1,<2.3.12",
- "ezsystems/ezplatform-kernel": "<1.2.5.1-dev|>=1.3,<1.3.34",
+ "ezsystems/ezplatform-http-cache": "<2.3.16",
+ "ezsystems/ezplatform-kernel": "<1.2.5.1-dev|>=1.3,<1.3.35",
"ezsystems/ezplatform-rest": ">=1.2,<=1.2.2|>=1.3,<1.3.8",
- "ezsystems/ezplatform-richtext": ">=2.3,<2.3.7.1-dev",
+ "ezsystems/ezplatform-richtext": ">=2.3,<2.3.26|>=3.3,<3.3.40",
"ezsystems/ezplatform-solr-search-engine": ">=1.7,<1.7.12|>=2,<2.0.2|>=3.3,<3.3.15",
"ezsystems/ezplatform-user": ">=1,<1.0.1",
"ezsystems/ezpublish-kernel": "<6.13.8.2-dev|>=7,<7.5.31",
"ezsystems/ezpublish-legacy": "<=2017.12.7.3|>=2018.6,<=2019.03.5.1",
"ezsystems/platform-ui-assets-bundle": ">=4.2,<4.2.3",
"ezsystems/repository-forms": ">=2.3,<2.3.2.1-dev|>=2.5,<2.5.15",
- "ezyang/htmlpurifier": "<4.1.1",
+ "ezyang/htmlpurifier": "<=4.2",
"facade/ignition": "<1.16.15|>=2,<2.4.2|>=2.5,<2.5.2",
"facturascripts/facturascripts": "<=2022.08",
+ "fastly/magento2": "<1.2.26",
"feehi/cms": "<=2.1.1",
"feehi/feehicms": "<=2.1.1",
"fenom/fenom": "<=2.12.1",
+ "filament/actions": ">=3.2,<3.2.123",
+ "filament/infolists": ">=3,<3.2.115",
+ "filament/tables": ">=3,<3.2.115",
"filegator/filegator": "<7.8",
+ "filp/whoops": "<2.1.13",
+ "fineuploader/php-traditional-server": "<=1.2.2",
"firebase/php-jwt": "<6",
+ "fisharebest/webtrees": "<=2.1.18",
"fixpunkt/fp-masterquiz": "<2.2.1|>=3,<3.5.2",
- "fixpunkt/fp-newsletter": "<1.1.1|>=2,<2.1.2|>=2.2,<3.2.6",
- "flarum/core": "<1.8.5",
- "flarum/framework": "<1.8.5",
+ "fixpunkt/fp-newsletter": "<1.1.1|>=1.2,<2.1.2|>=2.2,<3.2.6",
+ "flarum/core": "<1.8.10",
+ "flarum/flarum": "<0.1.0.0-beta8",
+ "flarum/framework": "<1.8.10",
"flarum/mentions": "<1.6.3",
"flarum/sticky": ">=0.1.0.0-beta14,<=0.1.0.0-beta15",
"flarum/tags": "<=0.1.0.0-beta13",
@@ -723,37 +1675,46 @@
"fooman/tcpdf": "<6.2.22",
"forkcms/forkcms": "<5.11.1",
"fossar/tcpdf-parser": "<6.2.22",
- "francoisjacquet/rosariosis": "<11",
+ "francoisjacquet/rosariosis": "<=11.5.1",
"frappant/frp-form-answers": "<3.1.2|>=4,<4.0.2",
"friendsofsymfony/oauth2-php": "<1.3",
"friendsofsymfony/rest-bundle": ">=1.2,<1.2.2",
- "friendsofsymfony/user-bundle": ">=1.2,<1.3.5",
+ "friendsofsymfony/user-bundle": ">=1,<1.3.5",
+ "friendsofsymfony1/swiftmailer": ">=4,<5.4.13|>=6,<6.2.5",
+ "friendsofsymfony1/symfony1": ">=1.1,<1.5.19",
"friendsoftypo3/mediace": ">=7.6.2,<7.6.5",
"friendsoftypo3/openid": ">=4.5,<4.5.31|>=4.7,<4.7.16|>=6,<6.0.11|>=6.1,<6.1.6",
- "froala/wysiwyg-editor": "<3.2.7|>=4.0.1,<=4.1.1",
- "froxlor/froxlor": "<=2.1.1",
+ "froala/wysiwyg-editor": "<=4.3",
+ "froxlor/froxlor": "<=2.2.5",
+ "frozennode/administrator": "<=5.0.12",
"fuel/core": "<1.8.1",
- "funadmin/funadmin": "<=3.2|>=3.3.2,<=3.3.3",
+ "funadmin/funadmin": "<=5.0.2",
"gaoming13/wechat-php-sdk": "<=1.10.2",
"genix/cms": "<=1.1.11",
- "getgrav/grav": "<=1.7.42.1",
- "getkirby/cms": "<3.5.8.3-dev|>=3.6,<3.6.6.3-dev|>=3.7,<3.7.5.2-dev|>=3.8,<3.8.4.1-dev|>=3.9,<3.9.6",
- "getkirby/kirby": "<=2.5.12",
+ "georgringer/news": "<1.3.3",
+ "geshi/geshi": "<=1.0.9.1",
+ "getformwork/formwork": "<1.13.1|>=2.0.0.0-beta1,<2.0.0.0-beta4",
+ "getgrav/grav": "<1.7.46",
+ "getkirby/cms": "<3.9.8.3-dev|>=3.10,<3.10.1.2-dev|>=4,<4.7.1",
+ "getkirby/kirby": "<3.9.8.3-dev|>=3.10,<3.10.1.2-dev|>=4,<4.7.1",
"getkirby/panel": "<2.5.14",
"getkirby/starterkit": "<=3.7.0.2",
"gilacms/gila": "<=1.15.4",
- "gleez/cms": "<=1.2|==2",
+ "gleez/cms": "<=1.3|==2",
"globalpayments/php-sdk": "<2",
+ "goalgorilla/open_social": "<12.3.11|>=12.4,<12.4.10|>=13.0.0.0-alpha1,<13.0.0.0-alpha11",
"gogentooss/samlbase": "<1.2.7",
"google/protobuf": "<3.15",
"gos/web-socket-bundle": "<1.10.4|>=2,<2.6.1|>=3,<3.3",
"gree/jose": "<2.2.1",
"gregwar/rst": "<1.0.3",
- "grumpydictator/firefly-iii": "<6.1.7",
+ "grumpydictator/firefly-iii": "<6.1.17",
"gugoan/economizzer": "<=0.9.0.0-beta1",
"guzzlehttp/guzzle": "<6.5.8|>=7,<7.4.5",
+ "guzzlehttp/oauth-subscriber": "<0.8.1",
"guzzlehttp/psr7": "<1.9.1|>=2,<2.4.5",
"haffner/jh_captcha": "<=2.1.3|>=3,<=3.0.2",
+ "handcraftedinthealps/goodby-csv": "<1.4.3",
"harvesthq/chosen": "<1.8.7",
"helloxz/imgurl": "<=2.31",
"hhxsv5/laravel-s": "<3.7.36",
@@ -763,200 +1724,269 @@
"hov/jobfair": "<1.0.13|>=2,<2.0.2",
"httpsoft/http-message": "<1.0.12",
"hyn/multi-tenant": ">=5.6,<5.7.2",
- "ibexa/admin-ui": ">=4.2,<4.2.3",
- "ibexa/core": ">=4,<4.0.7|>=4.1,<4.1.4|>=4.2,<4.2.3|>=4.5,<4.5.4",
+ "ibexa/admin-ui": ">=4.2,<4.2.3|>=4.6,<4.6.21",
+ "ibexa/admin-ui-assets": ">=4.6.0.0-alpha1,<4.6.21",
+ "ibexa/core": ">=4,<4.0.7|>=4.1,<4.1.4|>=4.2,<4.2.3|>=4.5,<4.5.6|>=4.6,<4.6.2",
+ "ibexa/fieldtype-richtext": ">=4.6,<4.6.21",
"ibexa/graphql": ">=2.5,<2.5.31|>=3.3,<3.3.28|>=4.2,<4.2.3",
- "ibexa/post-install": "<=1.0.4",
+ "ibexa/http-cache": ">=4.6,<4.6.14",
+ "ibexa/post-install": "<1.0.16|>=4.6,<4.6.14",
"ibexa/solr": ">=4.5,<4.5.4",
"ibexa/user": ">=4,<4.4.3",
"icecoder/icecoder": "<=8.1",
"idno/known": "<=1.3.1",
- "illuminate/auth": ">=4,<4.0.99|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.10",
- "illuminate/cookie": ">=4,<=4.0.11|>=4.1,<=4.1.99999|>=4.2,<=4.2.99999|>=5,<=5.0.99999|>=5.1,<=5.1.99999|>=5.2,<=5.2.99999|>=5.3,<=5.3.99999|>=5.4,<=5.4.99999|>=5.5,<=5.5.49|>=5.6,<=5.6.99999|>=5.7,<=5.7.99999|>=5.8,<=5.8.99999|>=6,<6.18.31|>=7,<7.22.4",
+ "ilicmiljan/secure-props": ">=1.2,<1.2.2",
+ "illuminate/auth": "<5.5.10",
+ "illuminate/cookie": ">=4,<=4.0.11|>=4.1,<6.18.31|>=7,<7.22.4",
"illuminate/database": "<6.20.26|>=7,<7.30.5|>=8,<8.40",
"illuminate/encryption": ">=4,<=4.0.11|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.40|>=5.6,<5.6.15",
"illuminate/view": "<6.20.42|>=7,<7.30.6|>=8,<8.75",
+ "imdbphp/imdbphp": "<=5.1.1",
"impresscms/impresscms": "<=1.4.5",
- "impresspages/impresspages": "<=1.0.12",
- "in2code/femanager": "<5.5.3|>=6,<6.3.4|>=7,<7.2.3",
+ "impresspages/impresspages": "<1.0.13",
+ "in2code/femanager": "<5.5.5|>=6,<6.4.1|>=7,<7.4.2|>=8,<8.2.2",
"in2code/ipandlanguageredirect": "<5.1.2",
"in2code/lux": "<17.6.1|>=18,<24.0.2",
+ "in2code/powermail": "<7.5.1|>=8,<8.5.1|>=9,<10.9.1|>=11,<12.4.1",
"innologi/typo3-appointments": "<2.0.6",
"intelliants/subrion": "<4.2.2",
+ "inter-mediator/inter-mediator": "==5.5",
+ "ipl/web": "<0.10.1",
+ "islandora/crayfish": "<4.1",
"islandora/islandora": ">=2,<2.4.1",
"ivankristianto/phpwhois": "<=4.3",
"jackalope/jackalope-doctrine-dbal": "<1.7.4",
+ "jambagecom/div2007": "<0.10.2",
"james-heinrich/getid3": "<1.9.21",
"james-heinrich/phpthumb": "<1.7.12",
"jasig/phpcas": "<1.3.3",
+ "jbartels/wec-map": "<3.0.3",
"jcbrand/converse.js": "<3.3.3",
+ "joelbutcher/socialstream": "<5.6|>=6,<6.2",
+ "johnbillion/wp-crontrol": "<1.16.2",
"joomla/application": "<1.0.13",
"joomla/archive": "<1.1.12|>=2,<2.0.1",
+ "joomla/database": ">=1,<2.2|>=3,<3.4",
"joomla/filesystem": "<1.6.2|>=2,<2.0.1",
"joomla/filter": "<1.4.4|>=2,<2.0.1",
- "joomla/framework": ">=2.5.4,<=3.8.12",
+ "joomla/framework": "<1.5.7|>=2.5.4,<=3.8.12",
"joomla/input": ">=2,<2.0.2",
- "joomla/joomla-cms": ">=2.5,<3.9.12",
+ "joomla/joomla-cms": "<3.9.12|>=4,<4.4.13|>=5,<5.2.6",
+ "joomla/joomla-platform": "<1.5.4",
"joomla/session": "<1.3.1",
"joyqi/hyper-down": "<=2.4.27",
"jsdecena/laracom": "<2.0.9",
"jsmitty12/phpwhois": "<5.1",
- "juzaweb/cms": "<=3.4",
+ "juzaweb/cms": "<=3.4.2",
+ "jweiland/events2": "<8.3.8|>=9,<9.0.6",
+ "jweiland/kk-downloader": "<1.2.2",
"kazist/phpwhois": "<=4.2.6",
"kelvinmo/simplexrd": "<3.1.1",
"kevinpapst/kimai2": "<1.16.7",
"khodakhah/nodcms": "<=3",
- "kimai/kimai": "<2.1",
+ "kimai/kimai": "<=2.20.1",
"kitodo/presentation": "<3.2.3|>=3.3,<3.3.4",
"klaviyo/magento2-extension": ">=1,<3",
"knplabs/knp-snappy": "<=1.4.2",
"kohana/core": "<3.3.3",
- "krayin/laravel-crm": "<1.2.2",
+ "koillection/koillection": "<1.6.12",
+ "krayin/laravel-crm": "<=1.3",
"kreait/firebase-php": ">=3.2,<3.8.1",
+ "kumbiaphp/kumbiapp": "<=1.1.1",
"la-haute-societe/tcpdf": "<6.2.22",
"laminas/laminas-diactoros": "<2.18.1|==2.19|==2.20|==2.21|==2.22|==2.23|>=2.24,<2.24.2|>=2.25,<2.25.2",
"laminas/laminas-form": "<2.17.1|>=3,<3.0.2|>=3.1,<3.1.1",
"laminas/laminas-http": "<2.14.2",
+ "lara-zeus/artemis": ">=1,<=1.0.6",
+ "lara-zeus/dynamic-dashboard": ">=3,<=3.0.1",
"laravel/fortify": "<1.11.1",
- "laravel/framework": "<6.20.44|>=7,<7.30.6|>=8,<8.75",
- "laravel/socialite": ">=1,<1.0.99|>=2,<2.0.10",
+ "laravel/framework": "<10.48.29|>=11,<11.44.1|>=12,<12.1.1",
+ "laravel/laravel": ">=5.4,<5.4.22",
+ "laravel/pulse": "<1.3.1",
+ "laravel/reverb": "<1.4",
+ "laravel/socialite": ">=1,<2.0.10",
"latte/latte": "<2.10.8",
- "lavalite/cms": "<=9",
+ "lavalite/cms": "<=9|==10.1",
"lcobucci/jwt": ">=3.4,<3.4.6|>=4,<4.0.4|>=4.1,<4.1.5",
- "league/commonmark": "<0.18.3",
+ "league/commonmark": "<2.7",
"league/flysystem": "<1.1.4|>=2,<2.1.1",
"league/oauth2-server": ">=8.3.2,<8.4.2|>=8.5,<8.5.3",
+ "leantime/leantime": "<3.3",
"lexik/jwt-authentication-bundle": "<2.10.7|>=2.11,<2.11.3",
+ "libreform/libreform": ">=2,<=2.0.8",
"librenms/librenms": "<2017.08.18",
"liftkit/database": "<2.13.2",
- "limesurvey/limesurvey": "<3.27.19",
+ "lightsaml/lightsaml": "<1.3.5",
+ "limesurvey/limesurvey": "<6.5.12",
"livehelperchat/livehelperchat": "<=3.91",
- "livewire/livewire": ">2.2.4,<2.2.6",
+ "livewire/livewire": "<2.12.7|>=3.0.0.0-beta1,<3.5.2",
+ "livewire/volt": "<1.7",
"lms/routes": "<2.1.1",
"localizationteam/l10nmgr": "<7.4|>=8,<8.7|>=9,<9.2",
+ "lomkit/laravel-rest-api": "<2.13",
+ "luracast/restler": "<3.1",
"luyadev/yii-helpers": "<1.2.1",
- "magento/community-edition": "<2.4.3.0-patch3|>=2.4.4,<2.4.5",
+ "macropay-solutions/laravel-crud-wizard-free": "<3.4.17",
+ "maestroerror/php-heic-to-jpg": "<1.0.5",
+ "magento/community-edition": "<2.4.5.0-patch13|==2.4.6|>=2.4.6.0-patch1,<2.4.6.0-patch11|>=2.4.7.0-beta1,<2.4.7.0-patch6|>=2.4.8.0-beta1,<2.4.8.0-patch1",
"magento/core": "<=1.9.4.5",
"magento/magento1ce": "<1.9.4.3-dev",
"magento/magento1ee": ">=1,<1.14.4.3-dev",
- "magento/product-community-edition": ">=2,<2.2.10|>=2.3,<2.3.2.0-patch2",
+ "magento/product-community-edition": "<2.4.4.0-patch9|>=2.4.5,<2.4.5.0-patch8|>=2.4.6,<2.4.6.0-patch6|>=2.4.7,<2.4.7.0-patch1",
+ "magento/project-community-edition": "<=2.0.2",
"magneto/core": "<1.9.4.4-dev",
"maikuolan/phpmussel": ">=1,<1.6",
"mainwp/mainwp": "<=4.4.3.3",
- "mantisbt/mantisbt": "<=2.25.7",
+ "mantisbt/mantisbt": "<=2.26.3",
"marcwillmann/turn": "<0.3.3",
+ "matomo/matomo": "<1.11",
"matyhtf/framework": "<3.0.6",
- "mautic/core": "<4.3",
- "mediawiki/core": ">=1.27,<1.27.6|>=1.29,<1.29.3|>=1.30,<1.30.2|>=1.31,<1.31.9|>=1.32,<1.32.6|>=1.32.99,<1.33.3|>=1.33.99,<1.34.3|>=1.34.99,<1.35",
+ "mautic/core": "<5.2.6|>=6.0.0.0-alpha,<6.0.2",
+ "mautic/core-lib": ">=1.0.0.0-beta,<4.4.13|>=5.0.0.0-alpha,<5.1.1",
+ "maximebf/debugbar": "<1.19",
+ "mdanter/ecc": "<2",
+ "mediawiki/abuse-filter": "<1.39.9|>=1.40,<1.41.3|>=1.42,<1.42.2",
+ "mediawiki/cargo": "<3.6.1",
+ "mediawiki/core": "<1.39.5|==1.40",
+ "mediawiki/data-transfer": ">=1.39,<1.39.11|>=1.41,<1.41.3|>=1.42,<1.42.2",
"mediawiki/matomo": "<2.4.3",
"mediawiki/semantic-media-wiki": "<4.0.2",
+ "mehrwert/phpmyadmin": "<3.2",
"melisplatform/melis-asset-manager": "<5.0.1",
"melisplatform/melis-cms": "<5.0.1",
"melisplatform/melis-front": "<5.0.1",
"mezzio/mezzio-swoole": "<3.7|>=4,<4.3",
"mgallegos/laravel-jqgrid": "<=1.3",
- "microsoft/microsoft-graph": ">=1.16,<1.109.1|>=2.0.0.0-RC1-dev,<2.0.1",
+ "microsoft/microsoft-graph": ">=1.16,<1.109.1|>=2,<2.0.1",
"microsoft/microsoft-graph-beta": "<2.0.1",
"microsoft/microsoft-graph-core": "<2.0.2",
- "microweber/microweber": "<=2.0.4",
+ "microweber/microweber": "<=2.0.16",
+ "mikehaertl/php-shellcommand": "<1.6.1",
"miniorange/miniorange-saml": "<1.4.3",
"mittwald/typo3_forum": "<1.2.1",
"mobiledetect/mobiledetectlib": "<2.8.32",
- "modx/revolution": "<=2.8.3.0-patch",
+ "modx/revolution": "<=3.1",
"mojo42/jirafeau": "<4.4",
"mongodb/mongodb": ">=1,<1.9.2",
"monolog/monolog": ">=1.8,<1.12",
- "moodle/moodle": "<4.3.0.0-RC2-dev",
+ "moodle/moodle": "<4.3.12|>=4.4,<4.4.8|>=4.5.0.0-beta,<4.5.4",
"mos/cimage": "<0.7.19",
"movim/moxl": ">=0.8,<=0.10",
+ "movingbytes/social-network": "<=1.2.1",
"mpdf/mpdf": "<=7.1.7",
"munkireport/comment": "<4.1",
"munkireport/managedinstalls": "<2.6",
+ "munkireport/munki_facts": "<1.5",
"munkireport/munkireport": ">=2.5.3,<5.6.3",
+ "munkireport/reportdata": "<3.5",
+ "munkireport/softwareupdate": "<1.6",
"mustache/mustache": ">=2,<2.14.1",
+ "mwdelaney/wp-enable-svg": "<=0.2",
"namshi/jose": "<2.2",
+ "nasirkhan/laravel-starter": "<11.11",
+ "nategood/httpful": "<1",
"neoan3-apps/template": "<1.1.1",
"neorazorx/facturascripts": "<2022.04",
"neos/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.12|>=3.1,<3.1.10|>=3.2,<3.2.13|>=3.3,<3.3.13|>=4,<4.0.6",
"neos/form": ">=1.2,<4.3.3|>=5,<5.0.9|>=5.1,<5.1.3",
"neos/media-browser": "<7.3.19|>=8,<8.0.16|>=8.1,<8.1.11|>=8.2,<8.2.11|>=8.3,<8.3.9",
- "neos/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<2.9.99|>=3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<5.3.10|>=7,<7.0.9|>=7.1,<7.1.7|>=7.2,<7.2.6|>=7.3,<7.3.4|>=8,<8.0.2",
- "neos/swiftmailer": ">=4.1,<4.1.99|>=5.4,<5.4.5",
+ "neos/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<5.3.10|>=7,<7.0.9|>=7.1,<7.1.7|>=7.2,<7.2.6|>=7.3,<7.3.4|>=8,<8.0.2",
+ "neos/swiftmailer": "<5.4.5",
+ "nesbot/carbon": "<2.72.6|>=3,<3.8.4",
+ "netcarver/textile": "<=4.1.2",
"netgen/tagsbundle": ">=3.4,<3.4.11|>=4,<4.0.15",
"nette/application": ">=2,<2.0.19|>=2.1,<2.1.13|>=2.2,<2.2.10|>=2.3,<2.3.14|>=2.4,<2.4.16|>=3,<3.0.6",
"nette/nette": ">=2,<2.0.19|>=2.1,<2.1.13",
- "nilsteampassnet/teampass": "<3.0.10",
+ "nilsteampassnet/teampass": "<3.1.3.1-dev",
+ "nitsan/ns-backup": "<13.0.1",
"nonfiction/nterchange": "<4.1.1",
"notrinos/notrinos-erp": "<=0.7",
"noumo/easyii": "<=0.9",
+ "novaksolutions/infusionsoft-php-sdk": "<1",
"nukeviet/nukeviet": "<4.5.02",
"nyholm/psr7": "<1.6.1",
"nystudio107/craft-seomatic": "<3.4.12",
+ "nzedb/nzedb": "<0.8",
"nzo/url-encryptor-bundle": ">=4,<4.3.2|>=5,<5.0.1",
"october/backend": "<1.1.2",
"october/cms": "<1.0.469|==1.0.469|==1.0.471|==1.1.1",
- "october/october": "<=3.4.4",
+ "october/october": "<3.7.5",
"october/rain": "<1.0.472|>=1.1,<1.1.2",
- "october/system": "<1.0.476|>=1.1,<1.1.12|>=2,<2.2.34|>=3,<3.5.2",
+ "october/system": "<3.7.5",
+ "oliverklee/phpunit": "<3.5.15",
"omeka/omeka-s": "<4.0.3",
"onelogin/php-saml": "<2.10.4",
- "oneup/uploader-bundle": "<1.9.3|>=2,<2.1.5",
+ "oneup/uploader-bundle": ">=1,<1.9.3|>=2,<2.1.5",
"open-web-analytics/open-web-analytics": "<1.7.4",
- "opencart/opencart": "<=3.0.3.7|>=4,<4.0.2.3-dev",
+ "opencart/opencart": ">=0",
"openid/php-openid": "<2.3",
- "openmage/magento-lts": "<20.2",
- "opensource-workshop/connect-cms": "<1.7.2|>=2,<2.3.2",
- "orchid/platform": ">=9,<9.4.4|>=14.0.0.0-alpha4,<14.5",
+ "openmage/magento-lts": "<20.12.3",
+ "opensolutions/vimbadmin": "<=3.0.15",
+ "opensource-workshop/connect-cms": "<1.8.7|>=2,<2.4.7",
+ "orchid/platform": ">=8,<14.43",
"oro/calendar-bundle": ">=4.2,<=4.2.6|>=5,<=5.0.6|>=5.1,<5.1.1",
"oro/commerce": ">=4.1,<5.0.11|>=5.1,<5.1.1",
"oro/crm": ">=1.7,<1.7.4|>=3.1,<4.1.17|>=4.2,<4.2.7",
"oro/crm-call-bundle": ">=4.2,<=4.2.5|>=5,<5.0.4|>=5.1,<5.1.1",
- "oro/customer-portal": ">=4.2,<=4.2.8|>=5,<5.0.11|>=5.1,<5.1.1",
- "oro/platform": ">=1.7,<1.7.4|>=3.1,<3.1.29|>=4.1,<4.1.17|>=4.2,<=4.2.10|>=5,<5.0.8",
- "oxid-esales/oxideshop-ce": "<4.5",
+ "oro/customer-portal": ">=4.1,<=4.1.13|>=4.2,<=4.2.10|>=5,<=5.0.11|>=5.1,<=5.1.3",
+ "oro/platform": ">=1.7,<1.7.4|>=3.1,<3.1.29|>=4.1,<4.1.17|>=4.2,<=4.2.10|>=5,<=5.0.12|>=5.1,<=5.1.3",
+ "oveleon/contao-cookiebar": "<1.16.3|>=2,<2.1.3",
+ "oxid-esales/oxideshop-ce": "<=7.0.5",
+ "oxid-esales/paymorrow-module": ">=1,<1.0.2|>=2,<2.0.1",
"packbackbooks/lti-1-3-php-library": "<5",
"padraic/humbug_get_contents": "<1.1.2",
"pagarme/pagarme-php": "<3",
"pagekit/pagekit": "<=1.0.18",
+ "paragonie/ecc": "<2.0.1",
"paragonie/random_compat": "<2",
- "passbolt/passbolt_api": "<2.11",
+ "passbolt/passbolt_api": "<4.6.2",
+ "paypal/adaptivepayments-sdk-php": "<=3.9.2",
+ "paypal/invoice-sdk-php": "<=3.9",
"paypal/merchant-sdk-php": "<3.12",
+ "paypal/permissions-sdk-php": "<=3.9.1",
"pear/archive_tar": "<1.4.14",
+ "pear/auth": "<1.2.4",
"pear/crypt_gpg": "<1.6.7",
+ "pear/http_request2": "<2.7",
"pear/pear": "<=1.10.1",
"pegasus/google-for-jobs": "<1.5.1|>=2,<2.1.1",
"personnummer/personnummer": "<3.0.2",
"phanan/koel": "<5.1.4",
- "phenx/php-svg-lib": "<0.5.1",
+ "phenx/php-svg-lib": "<0.5.2",
+ "php-censor/php-censor": "<2.0.13|>=2.1,<2.1.5",
"php-mod/curl": "<2.3.2",
- "phpbb/phpbb": "<3.2.10|>=3.3,<3.3.1",
+ "phpbb/phpbb": "<3.3.11",
"phpems/phpems": ">=6,<=6.1.3",
"phpfastcache/phpfastcache": "<6.1.5|>=7,<7.1.2|>=8,<8.0.7",
"phpmailer/phpmailer": "<6.5",
"phpmussel/phpmussel": ">=1,<1.6",
- "phpmyadmin/phpmyadmin": "<5.2.1",
- "phpmyfaq/phpmyfaq": "<=3.1.7",
- "phpoffice/phpexcel": "<1.8",
- "phpoffice/phpspreadsheet": "<1.16",
- "phpseclib/phpseclib": "<2.0.31|>=3,<3.0.34",
+ "phpmyadmin/phpmyadmin": "<5.2.2",
+ "phpmyfaq/phpmyfaq": "<3.2.5|==3.2.5|>=3.2.10,<=4.0.1",
+ "phpoffice/common": "<0.2.9",
+ "phpoffice/math": "<=0.2",
+ "phpoffice/phpexcel": "<=1.8.2",
+ "phpoffice/phpspreadsheet": "<1.29.9|>=2,<2.1.8|>=2.2,<2.3.7|>=3,<3.9",
+ "phpseclib/phpseclib": "<2.0.47|>=3,<3.0.36",
"phpservermon/phpservermon": "<3.6",
"phpsysinfo/phpsysinfo": "<3.4.3",
- "phpunit/phpunit": ">=4.8.19,<4.8.28|>=5,<5.6.3",
+ "phpunit/phpunit": ">=4.8.19,<4.8.28|>=5.0.10,<5.6.3",
"phpwhois/phpwhois": "<=4.2.5",
"phpxmlrpc/extras": "<0.6.1",
"phpxmlrpc/phpxmlrpc": "<4.9.2",
"pi/pi": "<=2.5",
- "pimcore/admin-ui-classic-bundle": "<1.3.2",
- "pimcore/customer-management-framework-bundle": "<4.0.6",
+ "pimcore/admin-ui-classic-bundle": "<1.7.6",
+ "pimcore/customer-management-framework-bundle": "<4.2.1",
"pimcore/data-hub": "<1.2.4",
+ "pimcore/data-importer": "<1.8.9|>=1.9,<1.9.3",
"pimcore/demo": "<10.3",
"pimcore/ecommerce-framework-bundle": "<1.0.10",
"pimcore/perspective-editor": "<1.5.1",
- "pimcore/pimcore": "<11.1.1",
- "pixelfed/pixelfed": "<=0.11.4",
+ "pimcore/pimcore": "<11.5.4",
+ "piwik/piwik": "<1.11",
+ "pixelfed/pixelfed": "<0.12.5",
"plotly/plotly.js": "<2.25.2",
"pocketmine/bedrock-protocol": "<8.0.2",
- "pocketmine/pocketmine-mp": "<=4.23|>=5,<5.3.1",
+ "pocketmine/pocketmine-mp": "<5.25.2",
"pocketmine/raklib": ">=0.14,<0.14.6|>=0.15,<0.15.1",
"pressbooks/pressbooks": "<5.18",
"prestashop/autoupgrade": ">=4,<4.10.1",
@@ -964,22 +1994,27 @@
"prestashop/blockwishlist": ">=2,<2.1.1",
"prestashop/contactform": ">=1.0.1,<4.3",
"prestashop/gamification": "<2.3.2",
- "prestashop/prestashop": "<8.1.3",
+ "prestashop/prestashop": "<8.1.6",
"prestashop/productcomments": "<5.0.2",
+ "prestashop/ps_contactinfo": "<=3.3.2",
"prestashop/ps_emailsubscription": "<2.6.1",
"prestashop/ps_facetedsearch": "<3.4.1",
"prestashop/ps_linklist": "<3.1",
- "privatebin/privatebin": "<1.4",
- "processwire/processwire": "<=3.0.210",
+ "privatebin/privatebin": "<1.4|>=1.5,<1.7.4",
+ "processwire/processwire": "<=3.0.229",
"propel/propel": ">=2.0.0.0-alpha1,<=2.0.0.0-alpha7",
"propel/propel1": ">=1,<=1.7.1",
- "pterodactyl/panel": "<1.7",
+ "pterodactyl/panel": "<=1.11.10",
"ptheofan/yii2-statemachine": ">=2.0.0.0-RC1-dev,<=2",
"ptrofimov/beanstalk_console": "<1.7.14",
"pubnub/pubnub": "<6.1",
+ "punktde/pt_extbase": "<1.5.1",
"pusher/pusher-php-server": "<2.2.1",
"pwweb/laravel-core": "<=0.3.6.0-beta",
+ "pxlrbt/filament-excel": "<1.1.14|>=2.0.0.0-alpha,<2.3.3",
"pyrocms/pyrocms": "<=3.9.1",
+ "qcubed/qcubed": "<=3.1.1",
+ "quickapps/cms": "<=2.0.0.0-beta2",
"rainlab/blog-plugin": "<1.4.1",
"rainlab/debugbar-plugin": "<3.1",
"rainlab/user-plugin": "<=1.4.5",
@@ -987,87 +2022,106 @@
"rap2hpoutre/laravel-log-viewer": "<0.13",
"react/http": ">=0.7,<1.9",
"really-simple-plugins/complianz-gdpr": "<6.4.2",
- "remdex/livehelperchat": "<3.99",
- "reportico-web/reportico": "<=7.1.21",
+ "redaxo/source": "<5.18.3",
+ "remdex/livehelperchat": "<4.29",
+ "renolit/reint-downloadmanager": "<4.0.2|>=5,<5.0.1",
+ "reportico-web/reportico": "<=8.1",
"rhukster/dom-sanitizer": "<1.0.7",
"rmccue/requests": ">=1.6,<1.8",
- "robrichards/xmlseclibs": "<3.0.4",
+ "robrichards/xmlseclibs": ">=1,<3.0.4",
"roots/soil": "<4.1",
+ "roundcube/roundcubemail": "<1.5.10|>=1.6,<1.6.11",
"rudloff/alltube": "<3.0.3",
+ "rudloff/rtmpdump-bin": "<=2.3.1",
"s-cart/core": "<6.9",
"s-cart/s-cart": "<6.9",
"sabberworm/php-css-parser": ">=1,<1.0.1|>=2,<2.0.1|>=3,<3.0.1|>=4,<4.0.1|>=5,<5.0.9|>=5.1,<5.1.3|>=5.2,<5.2.1|>=6,<6.0.2|>=7,<7.0.4|>=8,<8.0.1|>=8.1,<8.1.1|>=8.2,<8.2.1|>=8.3,<8.3.1",
- "sabre/dav": "<1.7.11|>=1.8,<1.8.9",
+ "sabre/dav": ">=1.6,<1.7.11|>=1.8,<1.8.9",
+ "samwilson/unlinked-wikibase": "<1.42",
"scheb/two-factor-bundle": "<3.26|>=4,<4.11",
"sensiolabs/connect": "<4.2.3",
"serluck/phpwhois": "<=4.2.6",
"sfroemken/url_redirect": "<=1.2.1",
- "sheng/yiicms": "<=1.2",
- "shopware/core": "<=6.5.7.3",
- "shopware/platform": "<=6.5.7.3",
+ "sheng/yiicms": "<1.2.1",
+ "shopware/core": "<6.5.8.18-dev|>=6.6,<6.6.10.3-dev|>=6.7.0.0-RC1-dev,<6.7.0.0-RC2-dev",
+ "shopware/platform": "<6.5.8.18-dev|>=6.6,<6.6.10.3-dev|>=6.7.0.0-RC1-dev,<6.7.0.0-RC2-dev",
"shopware/production": "<=6.3.5.2",
"shopware/shopware": "<=5.7.17",
- "shopware/storefront": "<=6.4.8.1",
- "shopxo/shopxo": "<2.2.6",
+ "shopware/storefront": "<=6.4.8.1|>=6.5.8,<6.5.8.7-dev",
+ "shopxo/shopxo": "<=6.4",
"showdoc/showdoc": "<2.10.4",
+ "shuchkin/simplexlsx": ">=1.0.12,<1.1.13",
"silverstripe-australia/advancedreports": ">=1,<=2",
"silverstripe/admin": "<1.13.19|>=2,<2.1.8",
"silverstripe/assets": ">=1,<1.11.1",
"silverstripe/cms": "<4.11.3",
- "silverstripe/comments": ">=1.3,<1.9.99|>=2,<2.9.99|>=3,<3.1.1",
+ "silverstripe/comments": ">=1.3,<3.1.1",
"silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3",
- "silverstripe/framework": "<4.13.39|>=5,<5.1.11",
- "silverstripe/graphql": "<3.8.2|>=4,<4.3.7|>=5,<5.1.3",
+ "silverstripe/framework": "<5.3.23",
+ "silverstripe/graphql": ">=2,<2.0.5|>=3,<3.8.2|>=4,<4.3.7|>=5,<5.1.3",
"silverstripe/hybridsessions": ">=1,<2.4.1|>=2.5,<2.5.1",
"silverstripe/recipe-cms": ">=4.5,<4.5.3",
"silverstripe/registry": ">=2.1,<2.1.2|>=2.2,<2.2.1",
- "silverstripe/restfulserver": ">=1,<1.0.9|>=2,<2.0.4",
+ "silverstripe/reports": "<5.2.3",
+ "silverstripe/restfulserver": ">=1,<1.0.9|>=2,<2.0.4|>=2.1,<2.1.2",
"silverstripe/silverstripe-omnipay": "<2.5.2|>=3,<3.0.2|>=3.1,<3.1.4|>=3.2,<3.2.1",
"silverstripe/subsites": ">=2,<2.6.1",
"silverstripe/taxonomy": ">=1.3,<1.3.1|>=2,<2.0.1",
- "silverstripe/userforms": "<3",
+ "silverstripe/userforms": "<3|>=5,<5.4.2",
"silverstripe/versioned-admin": ">=1,<1.11.1",
"simple-updates/phpwhois": "<=1",
- "simplesamlphp/saml2": "<1.15.4|>=2,<2.3.8|>=3,<3.1.4|==5.0.0.0-alpha12",
+ "simplesamlphp/saml2": "<=4.16.15|>=5.0.0.0-alpha1,<=5.0.0.0-alpha19",
+ "simplesamlphp/saml2-legacy": "<=4.16.15",
"simplesamlphp/simplesamlphp": "<1.18.6",
"simplesamlphp/simplesamlphp-module-infocard": "<1.0.1",
"simplesamlphp/simplesamlphp-module-openid": "<1",
"simplesamlphp/simplesamlphp-module-openidprovider": "<0.9",
+ "simplesamlphp/xml-common": "<1.20",
"simplesamlphp/xml-security": "==1.6.11",
"simplito/elliptic-php": "<1.0.6",
"sitegeist/fluid-components": "<3.5",
+ "sjbr/sr-feuser-register": "<2.6.2|>=5.1,<12.5",
"sjbr/sr-freecap": "<2.4.6|>=2.5,<2.5.3",
+ "sjbr/static-info-tables": "<2.3.1",
"slim/psr7": "<1.4.1|>=1.5,<1.5.1|>=1.6,<1.6.1",
"slim/slim": "<2.6",
"slub/slub-events": "<3.0.3",
- "smarty/smarty": "<3.1.48|>=4,<4.3.1",
- "snipe/snipe-it": "<=6.2.2",
+ "smarty/smarty": "<4.5.3|>=5,<5.1.1",
+ "snipe/snipe-it": "<8.1",
"socalnick/scn-social-auth": "<1.15.2",
"socialiteproviders/steam": "<1.1",
- "spatie/browsershot": "<3.57.4",
+ "spatie/browsershot": "<5.0.5",
+ "spatie/image-optimizer": "<1.7.3",
+ "spencer14420/sp-php-email-handler": "<1",
"spipu/html2pdf": "<5.2.8",
"spoon/library": "<1.4.1",
"spoonity/tcpdf": "<6.2.22",
"squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1",
- "ssddanbrown/bookstack": "<22.02.3",
- "statamic/cms": "<4.36",
+ "ssddanbrown/bookstack": "<24.05.1",
+ "starcitizentools/citizen-skin": ">=1.9.4,<3.4",
+ "starcitizentools/short-description": ">=4,<4.0.1",
+ "starcitizentools/tabber-neue": ">=1.9.1,<2.7.2|>=3,<3.1.1",
+ "statamic/cms": "<=5.16",
"stormpath/sdk": "<9.9.99",
- "studio-42/elfinder": "<2.1.62",
+ "studio-42/elfinder": "<=2.1.64",
+ "studiomitte/friendlycaptcha": "<0.1.4",
"subhh/libconnect": "<7.0.8|>=8,<8.1",
"sukohi/surpass": "<1",
- "sulu/sulu": "<1.6.44|>=2,<2.2.18|>=2.3,<2.3.8|==2.4.0.0-RC1|>=2.5,<2.5.10",
+ "sulu/form-bundle": ">=2,<2.5.3",
+ "sulu/sulu": "<1.6.44|>=2,<2.5.25|>=2.6,<2.6.9|>=3.0.0.0-alpha1,<3.0.0.0-alpha3",
"sumocoders/framework-user-bundle": "<1.4",
"superbig/craft-audit": "<3.0.2",
+ "svewap/a21glossary": "<=0.4.10",
"swag/paypal": "<5.4.4",
- "swiftmailer/swiftmailer": ">=4,<5.4.5",
+ "swiftmailer/swiftmailer": "<6.2.5",
"swiftyedit/swiftyedit": "<1.2",
"sylius/admin-bundle": ">=1,<1.0.17|>=1.1,<1.1.9|>=1.2,<1.2.2",
"sylius/grid": ">=1,<1.1.19|>=1.2,<1.2.18|>=1.3,<1.3.13|>=1.4,<1.4.5|>=1.5,<1.5.1",
"sylius/grid-bundle": "<1.10.1",
- "sylius/paypal-plugin": ">=1,<1.2.4|>=1.3,<1.3.1",
- "sylius/resource-bundle": "<1.3.14|>=1.4,<1.4.7|>=1.5,<1.5.2|>=1.6,<1.6.4",
- "sylius/sylius": "<1.9.10|>=1.10,<1.10.11|>=1.11,<1.11.2",
- "symbiote/silverstripe-multivaluefield": ">=3,<3.0.99",
+ "sylius/paypal-plugin": "<1.6.2|>=1.7,<1.7.2|>=2,<2.0.2",
+ "sylius/resource-bundle": ">=1,<1.3.14|>=1.4,<1.4.7|>=1.5,<1.5.2|>=1.6,<1.6.4",
+ "sylius/sylius": "<1.12.19|>=1.13.0.0-alpha1,<1.13.4",
+ "symbiote/silverstripe-multivaluefield": ">=3,<3.1",
"symbiote/silverstripe-queuedjobs": ">=3,<3.0.2|>=3.1,<3.1.4|>=4,<4.0.7|>=4.1,<4.1.2|>=4.2,<4.2.4|>=4.3,<4.3.3|>=4.4,<4.4.3|>=4.5,<4.5.1|>=4.6,<4.6.4",
"symbiote/silverstripe-seed": "<6.0.3",
"symbiote/silverstripe-versionedfiles": "<=2.0.3",
@@ -1076,8 +2130,9 @@
"symfony/dependency-injection": ">=2,<2.0.17|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7",
"symfony/error-handler": ">=4.4,<4.4.4|>=5,<5.0.4",
"symfony/form": ">=2.3,<2.3.35|>=2.4,<2.6.12|>=2.7,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.20|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1",
- "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7|>=5.3.14,<=5.3.14|>=5.4.3,<=5.4.3|>=6.0.3,<=6.0.3",
- "symfony/http-foundation": ">=2,<2.8.52|>=3,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7",
+ "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7|>=5.3.14,<5.3.15|>=5.4.3,<5.4.4|>=6.0.3,<6.0.4",
+ "symfony/http-client": ">=4.3,<5.4.47|>=6,<6.4.15|>=7,<7.1.8",
+ "symfony/http-foundation": "<5.4.46|>=6,<6.4.14|>=7,<7.1.7",
"symfony/http-kernel": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6",
"symfony/intl": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13",
"symfony/maker-bundle": ">=1.27,<1.29.2|>=1.30,<1.31.1",
@@ -1085,57 +2140,78 @@
"symfony/phpunit-bridge": ">=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7",
"symfony/polyfill": ">=1,<1.10",
"symfony/polyfill-php55": ">=1,<1.10",
+ "symfony/process": "<5.4.46|>=6,<6.4.14|>=7,<7.1.7",
"symfony/proxy-manager-bridge": ">=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7",
"symfony/routing": ">=2,<2.0.19",
+ "symfony/runtime": ">=5.3,<5.4.46|>=6,<6.4.14|>=7,<7.1.7",
"symfony/security": ">=2,<2.7.51|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.8",
- "symfony/security-bundle": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6",
+ "symfony/security-bundle": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.4.10|>=7,<7.0.10|>=7.1,<7.1.3",
"symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.9",
"symfony/security-csrf": ">=2.4,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11",
"symfony/security-guard": ">=2.8,<3.4.48|>=4,<4.4.23|>=5,<5.2.8",
- "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7|>=5.1,<5.2.8|>=5.3,<5.3.2|>=5.4,<5.4.31|>=6,<6.3.8",
+ "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7|>=5.1,<5.2.8|>=5.3,<5.4.47|>=6,<6.4.15|>=7,<7.1.8",
"symfony/serializer": ">=2,<2.0.11|>=4.1,<4.4.35|>=5,<5.3.12",
- "symfony/symfony": "<4.4.51|>=5,<5.4.31|>=6,<6.3.8",
+ "symfony/symfony": "<5.4.47|>=6,<6.4.15|>=7,<7.1.8",
"symfony/translation": ">=2,<2.0.17",
"symfony/twig-bridge": ">=2,<4.4.51|>=5,<5.4.31|>=6,<6.3.8",
"symfony/ux-autocomplete": "<2.11.2",
- "symfony/validator": ">=2,<2.0.24|>=2.1,<2.1.12|>=2.2,<2.2.5|>=2.3,<2.3.3",
+ "symfony/ux-live-component": "<2.25.1",
+ "symfony/ux-twig-component": "<2.25.1",
+ "symfony/validator": "<5.4.43|>=6,<6.4.11|>=7,<7.1.4",
"symfony/var-exporter": ">=4.2,<4.2.12|>=4.3,<4.3.8",
"symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4",
"symfony/webhook": ">=6.3,<6.3.8",
- "symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7",
+ "symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7|>=2.2.0.0-beta1,<2.2.0.0-beta2",
"symphonycms/symphony-2": "<2.6.4",
"t3/dce": "<0.11.5|>=2.2,<2.6.2",
"t3g/svg-sanitizer": "<1.0.3",
"t3s/content-consent": "<1.0.3|>=2,<2.0.2",
- "tastyigniter/tastyigniter": "<3.3",
- "tcg/voyager": "<=1.4",
- "tecnickcom/tcpdf": "<6.2.22",
+ "tastyigniter/tastyigniter": "<4",
+ "tcg/voyager": "<=1.8",
+ "tecnickcom/tc-lib-pdf-font": "<2.6.4",
+ "tecnickcom/tcpdf": "<6.8",
"terminal42/contao-tablelookupwizard": "<3.3.5",
"thelia/backoffice-default-template": ">=2.1,<2.1.2",
"thelia/thelia": ">=2.1,<2.1.3",
"theonedemon/phpwhois": "<=4.2.5",
- "thinkcmf/thinkcmf": "<=5.1.7",
- "thorsten/phpmyfaq": "<3.2.2",
+ "thinkcmf/thinkcmf": "<6.0.8",
+ "thorsten/phpmyfaq": "<=4.0.1",
"tikiwiki/tiki-manager": "<=17.1",
- "tinymce/tinymce": "<5.10.9|>=6,<6.7.3",
+ "timber/timber": ">=0.16.6,<1.23.1|>=1.24,<1.24.1|>=2,<2.1",
+ "tinymce/tinymce": "<7.2",
"tinymighty/wiki-seo": "<1.2.2",
"titon/framework": "<9.9.99",
+ "tltneon/lgsl": "<7",
"tobiasbg/tablepress": "<=2.0.0.0-RC1",
- "topthink/framework": "<6.0.14",
+ "topthink/framework": "<6.0.17|>=6.1,<=8.0.4",
"topthink/think": "<=6.1.1",
- "topthink/thinkphp": "<=3.2.3",
+ "topthink/thinkphp": "<=3.2.3|>=6.1.3,<=8.0.4",
+ "torrentpier/torrentpier": "<=2.4.3",
"tpwd/ke_search": "<4.0.3|>=4.1,<4.6.6|>=5,<5.0.2",
- "tribalsystems/zenario": "<=9.4.59197",
+ "tribalsystems/zenario": "<=9.7.61188",
"truckersmp/phpwhois": "<=4.3.1",
"ttskch/pagination-service-provider": "<1",
- "twig/twig": "<1.44.7|>=2,<2.15.3|>=3,<3.4.3",
+ "twbs/bootstrap": "<=3.4.1|>=4,<=4.6.2",
+ "twig/twig": "<3.11.2|>=3.12,<3.14.1|>=3.16,<3.19",
"typo3/cms": "<9.5.29|>=10,<10.4.35|>=11,<11.5.23|>=12,<12.2",
- "typo3/cms-backend": ">=7,<=7.6.50|>=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.13|>=11,<=11.1",
- "typo3/cms-core": "<8.7.55|>=9,<9.5.44|>=10,<10.4.41|>=11,<11.5.33|>=12,<12.4.8",
+ "typo3/cms-backend": "<4.1.14|>=4.2,<4.2.15|>=4.3,<4.3.7|>=4.4,<4.4.4|>=7,<=7.6.50|>=8,<=8.7.39|>=9,<=9.5.24|>=10,<10.4.46|>=11,<11.5.40|>=12,<=12.4.30|>=13,<=13.4.11",
+ "typo3/cms-belog": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2",
+ "typo3/cms-beuser": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2",
+ "typo3/cms-core": "<=8.7.56|>=9,<=9.5.50|>=10,<=10.4.49|>=11,<=11.5.43|>=12,<=12.4.30|>=13,<=13.4.11",
+ "typo3/cms-dashboard": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2",
"typo3/cms-extbase": "<6.2.24|>=7,<7.6.8|==8.1.1",
- "typo3/cms-form": ">=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.13|>=11,<=11.1",
- "typo3/cms-install": ">=12.2,<12.4.8",
+ "typo3/cms-extensionmanager": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2",
+ "typo3/cms-felogin": ">=4.2,<4.2.3",
+ "typo3/cms-fluid": "<4.3.4|>=4.4,<4.4.1",
+ "typo3/cms-form": ">=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2",
+ "typo3/cms-frontend": "<4.3.9|>=4.4,<4.4.5",
+ "typo3/cms-indexed-search": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2",
+ "typo3/cms-install": "<4.1.14|>=4.2,<4.2.16|>=4.3,<4.3.9|>=4.4,<4.4.5|>=12.2,<12.4.8|==13.4.2",
+ "typo3/cms-lowlevel": ">=11,<=11.5.41",
"typo3/cms-rte-ckeditor": ">=9.5,<9.5.42|>=10,<10.4.39|>=11,<11.5.30",
+ "typo3/cms-scheduler": ">=11,<=11.5.41",
+ "typo3/cms-setup": ">=9,<=9.5.50|>=10,<=10.4.49|>=11,<=11.5.43|>=12,<=12.4.30|>=13,<=13.4.11",
+ "typo3/cms-webhooks": ">=12,<=12.4.30|>=13,<=13.4.11",
"typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.12|>=3.1,<3.1.10|>=3.2,<3.2.13|>=3.3,<3.3.13|>=4,<4.0.6",
"typo3/html-sanitizer": ">=1,<=1.5.2|>=2,<=2.1.3",
"typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<2.3.99|>=3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<3.3.23|>=4,<4.0.17|>=4.1,<4.1.16|>=4.2,<4.2.12|>=4.3,<4.3.3",
@@ -1144,19 +2220,32 @@
"typo3fluid/fluid": ">=2,<2.0.8|>=2.1,<2.1.7|>=2.2,<2.2.4|>=2.3,<2.3.7|>=2.4,<2.4.4|>=2.5,<2.5.11|>=2.6,<2.6.10",
"ua-parser/uap-php": "<3.8",
"uasoft-indonesia/badaso": "<=2.9.7",
- "unisharp/laravel-filemanager": "<2.6.4",
+ "unisharp/laravel-filemanager": "<2.9.1",
+ "unopim/unopim": "<0.1.5",
"userfrosting/userfrosting": ">=0.3.1,<4.6.3",
"usmanhalalit/pixie": "<1.0.3|>=2,<2.0.2",
"uvdesk/community-skeleton": "<=1.1.1",
+ "uvdesk/core-framework": "<=1.1.1",
"vanilla/safecurl": "<0.9.2",
+ "verbb/comments": "<1.5.5",
+ "verbb/formie": "<=2.1.43",
+ "verbb/image-resizer": "<2.0.9",
+ "verbb/knock-knock": "<1.2.8",
"verot/class.upload.php": "<=2.1.6",
+ "vertexvaar/falsftp": "<0.2.6",
+ "villagedefrance/opencart-overclocked": "<=1.11.1",
"vova07/yii2-fileapi-widget": "<0.1.9",
"vrana/adminer": "<4.8.1",
+ "vufind/vufind": ">=2,<9.1.1",
"waldhacker/hcaptcha": "<2.1.2",
"wallabag/tcpdf": "<6.2.22",
- "wallabag/wallabag": "<2.6.7",
+ "wallabag/wallabag": "<2.6.11",
"wanglelecc/laracms": "<=1.0.3",
- "web-auth/webauthn-framework": ">=3.3,<3.3.4",
+ "wapplersystems/a21glossary": "<=0.4.10",
+ "web-auth/webauthn-framework": ">=3.3,<3.3.4|>=4.5,<4.9",
+ "web-auth/webauthn-lib": ">=4.5,<4.9",
+ "web-feet/coastercms": "==5.5",
+ "web-tp3/wec_map": "<3.0.3",
"webbuilders-group/silverstripe-kapost-bridge": "<0.4",
"webcoast/deferred-image-processing": "<1.0.2",
"webklex/laravel-imap": "<5.3",
@@ -1166,38 +2255,45 @@
"wikimedia/parsoid": "<0.12.2",
"willdurand/js-translation-bundle": "<2.1.1",
"winter/wn-backend-module": "<1.2.4",
+ "winter/wn-cms-module": "<1.0.476|>=1.1,<1.1.11|>=1.2,<1.2.7",
+ "winter/wn-dusk-plugin": "<2.1",
"winter/wn-system-module": "<1.2.4",
- "wintercms/winter": "<1.2.3",
- "woocommerce/woocommerce": "<6.6",
- "wp-cli/wp-cli": "<2.5",
+ "wintercms/winter": "<=1.2.3",
+ "wireui/wireui": "<1.19.3|>=2,<2.1.3",
+ "woocommerce/woocommerce": "<6.6|>=8.8,<8.8.5|>=8.9,<8.9.3",
+ "wp-cli/wp-cli": ">=0.12,<2.5",
"wp-graphql/wp-graphql": "<=1.14.5",
+ "wp-premium/gravityforms": "<2.4.21",
"wpanel/wpanel4-cms": "<=4.3.1",
"wpcloud/wp-stateless": "<3.2",
- "wwbn/avideo": "<=12.4",
+ "wpglobus/wpglobus": "<=1.9.6",
+ "wwbn/avideo": "<14.3",
"xataface/xataface": "<3",
"xpressengine/xpressengine": "<3.0.15",
- "yeswiki/yeswiki": "<4.1",
- "yetiforce/yetiforce-crm": "<=6.4",
+ "yab/quarx": "<2.4.5",
+ "yeswiki/yeswiki": "<4.5.4",
+ "yetiforce/yetiforce-crm": "<6.5",
"yidashi/yii2cmf": "<=2",
"yii2mod/yii2-cms": "<1.9.2",
- "yiisoft/yii": "<1.1.29",
- "yiisoft/yii2": "<2.0.38",
+ "yiisoft/yii": "<1.1.31",
+ "yiisoft/yii2": "<2.0.52",
"yiisoft/yii2-authclient": "<2.2.15",
"yiisoft/yii2-bootstrap": "<2.0.4",
- "yiisoft/yii2-dev": "<2.0.43",
+ "yiisoft/yii2-dev": "<=2.0.45",
"yiisoft/yii2-elasticsearch": "<2.0.5",
"yiisoft/yii2-gii": "<=2.2.4",
"yiisoft/yii2-jui": "<2.0.4",
- "yiisoft/yii2-redis": "<2.0.8",
+ "yiisoft/yii2-redis": "<2.0.20",
"yikesinc/yikes-inc-easy-mailchimp-extender": "<6.8.6",
"yoast-seo-for-typo3/yoast_seo": "<7.2.3",
"yourls/yourls": "<=1.8.2",
+ "yuan1994/tpadmin": "<=1.3.12",
"zencart/zencart": "<=1.5.7.0-beta",
"zendesk/zendesk_api_client_php": "<2.2.11",
- "zendframework/zend-cache": "<2.4.8|>=2.5,<2.5.3",
+ "zendframework/zend-cache": ">=2.4,<2.4.8|>=2.5,<2.5.3",
"zendframework/zend-captcha": ">=2,<2.4.9|>=2.5,<2.5.2",
"zendframework/zend-crypt": ">=2,<2.4.9|>=2.5,<2.5.2",
- "zendframework/zend-db": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.10|>=2.3,<2.3.5",
+ "zendframework/zend-db": "<2.2.10|>=2.3,<2.3.5",
"zendframework/zend-developer-tools": ">=1.2.2,<1.2.3",
"zendframework/zend-diactoros": "<1.8.4",
"zendframework/zend-feed": "<2.10.3",
@@ -1205,9 +2301,9 @@
"zendframework/zend-http": "<2.8.1",
"zendframework/zend-json": ">=2.1,<2.1.6|>=2.2,<2.2.6",
"zendframework/zend-ldap": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.8|>=2.3,<2.3.3",
- "zendframework/zend-mail": ">=2,<2.4.11|>=2.5,<2.7.2",
+ "zendframework/zend-mail": "<2.4.11|>=2.5,<2.7.2",
"zendframework/zend-navigation": ">=2,<2.2.7|>=2.3,<2.3.1",
- "zendframework/zend-session": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.9|>=2.3,<2.3.4",
+ "zendframework/zend-session": ">=2,<2.2.9|>=2.3,<2.3.4",
"zendframework/zend-validator": ">=2.3,<2.3.6",
"zendframework/zend-view": ">=2,<2.2.7|>=2.3,<2.3.1",
"zendframework/zend-xmlrpc": ">=2.1,<2.1.6|>=2.2,<2.2.6",
@@ -1222,11 +2318,11 @@
"zendframework/zendservice-slideshare": "<2.0.2",
"zendframework/zendservice-technorati": "<2.0.2",
"zendframework/zendservice-windowsazure": "<2.0.2",
- "zendframework/zendxml": "<1.0.1",
+ "zendframework/zendxml": ">=1,<1.0.1",
"zenstruck/collection": "<0.2.1",
"zetacomponents/mail": "<1.8.2",
"zf-commons/zfc-user": "<1.2.2",
- "zfcampus/zf-apigility-doctrine": "<1.0.3",
+ "zfcampus/zf-apigility-doctrine": ">=1,<1.0.3",
"zfr/zfr-oauth2-server-module": "<0.1.2",
"zoujingli/thinkadmin": "<=6.1.53"
},
@@ -1266,20 +2362,20 @@
"type": "tidelift"
}
],
- "time": "2024-01-31T19:04:19+00:00"
+ "time": "2025-07-04T13:13:44+00:00"
},
{
"name": "squizlabs/php_codesniffer",
- "version": "3.8.1",
+ "version": "3.13.2",
"source": {
"type": "git",
"url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git",
- "reference": "14f5fff1e64118595db5408e946f3a22c75807f7"
+ "reference": "5b5e3821314f947dd040c70f7992a64eac89025c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/14f5fff1e64118595db5408e946f3a22c75807f7",
- "reference": "14f5fff1e64118595db5408e946f3a22c75807f7",
+ "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/5b5e3821314f947dd040c70f7992a64eac89025c",
+ "reference": "5b5e3821314f947dd040c70f7992a64eac89025c",
"shasum": ""
},
"require": {
@@ -1344,75 +2440,816 @@
{
"url": "https://opencollective.com/php_codesniffer",
"type": "open_collective"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/phpcsstandards",
+ "type": "thanks_dev"
}
],
- "time": "2024-01-11T20:47:48+00:00"
+ "time": "2025-06-17T22:17:01+00:00"
},
{
- "name": "wp-coding-standards/wpcs",
- "version": "3.0.1",
+ "name": "swissspidy/phpstan-no-private",
+ "version": "v1.0.0",
"source": {
"type": "git",
- "url": "https://github.com/WordPress/WordPress-Coding-Standards.git",
- "reference": "b4caf9689f1a0e4a4c632679a44e638c1c67aff1"
+ "url": "https://github.com/swissspidy/phpstan-no-private.git",
+ "reference": "559cb0e8d092df7314ed4254db83db0427440af2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/b4caf9689f1a0e4a4c632679a44e638c1c67aff1",
- "reference": "b4caf9689f1a0e4a4c632679a44e638c1c67aff1",
+ "url": "https://api.github.com/repos/swissspidy/phpstan-no-private/zipball/559cb0e8d092df7314ed4254db83db0427440af2",
+ "reference": "559cb0e8d092df7314ed4254db83db0427440af2",
"shasum": ""
},
"require": {
- "ext-filter": "*",
- "ext-libxml": "*",
- "ext-tokenizer": "*",
- "ext-xmlreader": "*",
- "php": ">=5.4",
- "phpcsstandards/phpcsextra": "^1.1.0",
- "phpcsstandards/phpcsutils": "^1.0.8",
- "squizlabs/php_codesniffer": "^3.7.2"
+ "php": "^7.4 || ^8.0",
+ "phpstan/phpstan": "^2.0"
},
"require-dev": {
- "php-parallel-lint/php-console-highlighter": "^1.0.0",
- "php-parallel-lint/php-parallel-lint": "^1.3.2",
- "phpcompatibility/php-compatibility": "^9.0",
- "phpcsstandards/phpcsdevtools": "^1.2.0",
- "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
+ "dealerdirect/phpcodesniffer-composer-installer": "^1.0.0",
+ "nikic/php-parser": "^v5.3.1",
+ "php-parallel-lint/php-parallel-lint": "^1.4",
+ "phpstan/phpstan-phpunit": "^2.0",
+ "phpstan/phpstan-strict-rules": "^2.0",
+ "phpunit/phpunit": "^9.5",
+ "slevomat/coding-standard": "^8.8.0",
+ "squizlabs/php_codesniffer": "^3.5.3"
+ },
+ "type": "phpstan-extension",
+ "extra": {
+ "phpstan": {
+ "includes": [
+ "rules.neon"
+ ]
+ }
},
- "suggest": {
- "ext-iconv": "For improved results",
- "ext-mbstring": "For improved results"
+ "autoload": {
+ "psr-4": {
+ "Swissspidy\\PHPStan\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "PHPStan rules for detecting usage of pseudo-private functions, classes, and methods.",
+ "support": {
+ "issues": "https://github.com/swissspidy/phpstan-no-private/issues",
+ "source": "https://github.com/swissspidy/phpstan-no-private/tree/v1.0.0"
+ },
+ "time": "2024-11-11T11:04:45+00:00"
+ },
+ {
+ "name": "symfony/config",
+ "version": "v7.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/config.git",
+ "reference": "ba62ae565f1327c2f6366726312ed828c85853bc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/config/zipball/ba62ae565f1327c2f6366726312ed828c85853bc",
+ "reference": "ba62ae565f1327c2f6366726312ed828c85853bc",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/deprecation-contracts": "^2.5|^3",
+ "symfony/filesystem": "^7.1",
+ "symfony/polyfill-ctype": "~1.8"
+ },
+ "conflict": {
+ "symfony/finder": "<6.4",
+ "symfony/service-contracts": "<2.5"
+ },
+ "require-dev": {
+ "symfony/event-dispatcher": "^6.4|^7.0",
+ "symfony/finder": "^6.4|^7.0",
+ "symfony/messenger": "^6.4|^7.0",
+ "symfony/service-contracts": "^2.5|^3",
+ "symfony/yaml": "^6.4|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Config\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
},
- "type": "phpcodesniffer-standard",
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
- "name": "Contributors",
- "homepage": "https://github.com/WordPress/WordPress-Coding-Standards/graphs/contributors"
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
}
],
- "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions",
- "keywords": [
- "phpcs",
- "standards",
- "static analysis",
- "wordpress"
- ],
+ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind",
+ "homepage": "https://symfony.com",
"support": {
- "issues": "https://github.com/WordPress/WordPress-Coding-Standards/issues",
- "source": "https://github.com/WordPress/WordPress-Coding-Standards",
- "wiki": "https://github.com/WordPress/WordPress-Coding-Standards/wiki"
+ "source": "https://github.com/symfony/config/tree/v7.3.0"
},
"funding": [
{
- "url": "https://opencollective.com/thewpcc/contribute/wp-php-63406",
+ "url": "https://symfony.com/sponsor",
"type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-05-15T09:04:05+00:00"
+ },
+ {
+ "name": "symfony/dependency-injection",
+ "version": "v7.3.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/dependency-injection.git",
+ "reference": "8656c4848b48784c4bb8c4ae50d2b43f832cead8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/8656c4848b48784c4bb8c4ae50d2b43f832cead8",
+ "reference": "8656c4848b48784c4bb8c4ae50d2b43f832cead8",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "psr/container": "^1.1|^2.0",
+ "symfony/deprecation-contracts": "^2.5|^3",
+ "symfony/service-contracts": "^3.5",
+ "symfony/var-exporter": "^6.4.20|^7.2.5"
+ },
+ "conflict": {
+ "ext-psr": "<1.1|>=2",
+ "symfony/config": "<6.4",
+ "symfony/finder": "<6.4",
+ "symfony/yaml": "<6.4"
+ },
+ "provide": {
+ "psr/container-implementation": "1.1|2.0",
+ "symfony/service-implementation": "1.1|2.0|3.0"
+ },
+ "require-dev": {
+ "symfony/config": "^6.4|^7.0",
+ "symfony/expression-language": "^6.4|^7.0",
+ "symfony/yaml": "^6.4|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\DependencyInjection\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Allows you to standardize and centralize the way objects are constructed in your application",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/dependency-injection/tree/v7.3.1"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-06-24T04:04:43+00:00"
+ },
+ {
+ "name": "symfony/filesystem",
+ "version": "v7.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/filesystem.git",
+ "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/filesystem/zipball/b8dce482de9d7c9fe2891155035a7248ab5c7fdb",
+ "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/polyfill-ctype": "~1.8",
+ "symfony/polyfill-mbstring": "~1.8"
+ },
+ "require-dev": {
+ "symfony/process": "^6.4|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Filesystem\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides basic utilities for the filesystem",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/filesystem/tree/v7.3.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-10-25T15:15:23+00:00"
+ },
+ {
+ "name": "symfony/polyfill-ctype",
+ "version": "v1.32.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-ctype.git",
+ "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
+ "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "provide": {
+ "ext-ctype": "*"
+ },
+ "suggest": {
+ "ext-ctype": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Ctype\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Gert de Pagter",
+ "email": "BackEndTea@gmail.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for ctype functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "ctype",
+ "polyfill",
+ "portable"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-09T11:45:10+00:00"
+ },
+ {
+ "name": "symfony/polyfill-mbstring",
+ "version": "v1.32.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-mbstring.git",
+ "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493",
+ "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493",
+ "shasum": ""
+ },
+ "require": {
+ "ext-iconv": "*",
+ "php": ">=7.2"
+ },
+ "provide": {
+ "ext-mbstring": "*"
+ },
+ "suggest": {
+ "ext-mbstring": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Mbstring\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for the Mbstring extension",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "mbstring",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-12-23T08:48:59+00:00"
+ },
+ {
+ "name": "symfony/service-contracts",
+ "version": "v3.6.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/service-contracts.git",
+ "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4",
+ "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "psr/container": "^1.1|^2.0",
+ "symfony/deprecation-contracts": "^2.5|^3"
+ },
+ "conflict": {
+ "ext-psr": "<1.1|>=2"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "3.6-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\Service\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Test/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Generic abstractions related to writing services",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/service-contracts/tree/v3.6.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-04-25T09:37:31+00:00"
+ },
+ {
+ "name": "symfony/var-exporter",
+ "version": "v7.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/var-exporter.git",
+ "reference": "c9a1168891b5aaadfd6332ef44393330b3498c4c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/var-exporter/zipball/c9a1168891b5aaadfd6332ef44393330b3498c4c",
+ "reference": "c9a1168891b5aaadfd6332ef44393330b3498c4c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/deprecation-contracts": "^2.5|^3"
+ },
+ "require-dev": {
+ "symfony/property-access": "^6.4|^7.0",
+ "symfony/serializer": "^6.4|^7.0",
+ "symfony/var-dumper": "^6.4|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\VarExporter\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Allows exporting any serializable PHP data structure to plain PHP code",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "clone",
+ "construct",
+ "export",
+ "hydrate",
+ "instantiate",
+ "lazy-loading",
+ "proxy",
+ "serialize"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/var-exporter/tree/v7.3.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-05-15T09:04:05+00:00"
+ },
+ {
+ "name": "szepeviktor/phpstan-wordpress",
+ "version": "v2.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/szepeviktor/phpstan-wordpress.git",
+ "reference": "963887b04c21fe7ac78e61c1351f8b00fff9f8f8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/szepeviktor/phpstan-wordpress/zipball/963887b04c21fe7ac78e61c1351f8b00fff9f8f8",
+ "reference": "963887b04c21fe7ac78e61c1351f8b00fff9f8f8",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.4 || ^8.0",
+ "php-stubs/wordpress-stubs": "^6.6.2",
+ "phpstan/phpstan": "^2.0"
+ },
+ "require-dev": {
+ "composer/composer": "^2.1.14",
+ "dealerdirect/phpcodesniffer-composer-installer": "^1.0",
+ "php-parallel-lint/php-parallel-lint": "^1.1",
+ "phpstan/phpstan-strict-rules": "^2.0",
+ "phpunit/phpunit": "^9.0",
+ "szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^1.0",
+ "wp-coding-standards/wpcs": "3.1.0 as 2.3.0"
+ },
+ "suggest": {
+ "swissspidy/phpstan-no-private": "Detect usage of internal core functions, classes and methods"
+ },
+ "type": "phpstan-extension",
+ "extra": {
+ "phpstan": {
+ "includes": [
+ "extension.neon"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "SzepeViktor\\PHPStan\\WordPress\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "WordPress extensions for PHPStan",
+ "keywords": [
+ "PHPStan",
+ "code analyse",
+ "code analysis",
+ "static analysis",
+ "wordpress"
+ ],
+ "support": {
+ "issues": "https://github.com/szepeviktor/phpstan-wordpress/issues",
+ "source": "https://github.com/szepeviktor/phpstan-wordpress/tree/v2.0.2"
+ },
+ "time": "2025-02-12T18:43:37+00:00"
+ },
+ {
+ "name": "wp-coding-standards/wpcs",
+ "version": "3.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/WordPress/WordPress-Coding-Standards.git",
+ "reference": "9333efcbff231f10dfd9c56bb7b65818b4733ca7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/9333efcbff231f10dfd9c56bb7b65818b4733ca7",
+ "reference": "9333efcbff231f10dfd9c56bb7b65818b4733ca7",
+ "shasum": ""
+ },
+ "require": {
+ "ext-filter": "*",
+ "ext-libxml": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlreader": "*",
+ "php": ">=5.4",
+ "phpcsstandards/phpcsextra": "^1.2.1",
+ "phpcsstandards/phpcsutils": "^1.0.10",
+ "squizlabs/php_codesniffer": "^3.9.0"
+ },
+ "require-dev": {
+ "php-parallel-lint/php-console-highlighter": "^1.0.0",
+ "php-parallel-lint/php-parallel-lint": "^1.3.2",
+ "phpcompatibility/php-compatibility": "^9.0",
+ "phpcsstandards/phpcsdevtools": "^1.2.0",
+ "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0"
+ },
+ "suggest": {
+ "ext-iconv": "For improved results",
+ "ext-mbstring": "For improved results"
+ },
+ "type": "phpcodesniffer-standard",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/WordPress/WordPress-Coding-Standards/graphs/contributors"
+ }
+ ],
+ "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions",
+ "keywords": [
+ "phpcs",
+ "standards",
+ "static analysis",
+ "wordpress"
+ ],
+ "support": {
+ "issues": "https://github.com/WordPress/WordPress-Coding-Standards/issues",
+ "source": "https://github.com/WordPress/WordPress-Coding-Standards",
+ "wiki": "https://github.com/WordPress/WordPress-Coding-Standards/wiki"
+ },
+ "funding": [
+ {
+ "url": "https://opencollective.com/php_codesniffer",
+ "type": "custom"
+ }
+ ],
+ "time": "2024-03-25T16:39:00+00:00"
+ },
+ {
+ "name": "wp-hooks/wordpress-core",
+ "version": "1.10.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/wp-hooks/wordpress-core-hooks.git",
+ "reference": "127af21a918a52bcead7ce9b743b17b5d64eb148"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/wp-hooks/wordpress-core-hooks/zipball/127af21a918a52bcead7ce9b743b17b5d64eb148",
+ "reference": "127af21a918a52bcead7ce9b743b17b5d64eb148",
+ "shasum": ""
+ },
+ "replace": {
+ "johnbillion/wp-hooks": "*"
+ },
+ "require-dev": {
+ "erusev/parsedown": "1.8.0-beta-7",
+ "oomphinc/composer-installers-extender": "^2",
+ "roots/wordpress-core-installer": "^1.0.0",
+ "roots/wordpress-full": "6.8",
+ "wp-hooks/generator": "1.0.0"
+ },
+ "type": "library",
+ "extra": {
+ "wp-hooks": {
+ "ignore-files": [
+ "wp-admin/includes/deprecated.php",
+ "wp-admin/includes/ms-deprecated.php",
+ "wp-content/",
+ "wp-includes/deprecated.php",
+ "wp-includes/ID3/",
+ "wp-includes/ms-deprecated.php",
+ "wp-includes/pomo/",
+ "wp-includes/random_compat/",
+ "wp-includes/Requests/",
+ "wp-includes/SimplePie/",
+ "wp-includes/sodium_compat/",
+ "wp-includes/Text/"
+ ],
+ "ignore-hooks": [
+ "load-categories.php",
+ "load-edit-link-categories.php",
+ "load-edit-tags.php",
+ "load-page-new.php",
+ "load-page.php",
+ "option_enable_xmlrpc",
+ "edit_post_{$field}",
+ "pre_post_{$field}",
+ "post_{$field}",
+ "pre_option_enable_xmlrpc",
+ "$page_hook",
+ "$hook",
+ "$hook_name"
+ ]
+ },
+ "wordpress-install-dir": "vendor/wordpress/wordpress"
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "GPL-3.0-or-later"
+ ],
+ "authors": [
+ {
+ "name": "John Blackbourn",
+ "homepage": "https://johnblackbourn.com/"
+ }
+ ],
+ "description": "All the actions and filters from WordPress core in machine-readable JSON format.",
+ "support": {
+ "issues": "https://github.com/wp-hooks/wordpress-core-hooks/issues",
+ "source": "https://github.com/wp-hooks/wordpress-core-hooks/tree/1.10.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sponsors/johnbillion",
+ "type": "github"
}
],
- "time": "2023-09-14T07:06:09+00:00"
+ "time": "2025-04-16T22:20:41+00:00"
}
],
"aliases": [],
@@ -1421,9 +3258,9 @@
"a8cteam51/team51-configs": 20,
"roave/security-advisories": 20
},
- "prefer-stable": true,
+ "prefer-stable": false,
"prefer-lowest": false,
- "platform": [],
- "platform-dev": [],
+ "platform": {},
+ "platform-dev": {},
"plugin-api-version": "2.6.0"
}
diff --git a/package.json b/package.json
index 91d68ad..049a2f7 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "wpcomsp-simple-events",
- "version": "1.0.55",
+ "version": "2.0.0",
"description": "A simple Gutenberg-first event management plugin that integrates with WooCommerce Box Office.",
"author": {
"name": "WordPress.com Special Projects Team",
diff --git a/plugin.php b/plugin.php
index 7fdb5ee..81df0b0 100644
--- a/plugin.php
+++ b/plugin.php
@@ -3,7 +3,7 @@
* Simple Events Plugin bootstrap file.
*
* @since 1.0.0
- * @version 1.0.55
+ * @version 2.0.0-RC1
* @author WordPress.com Special Projects
* @license GPL-3.0-or-later
*
@@ -13,7 +13,7 @@
* Description: Event management frontend for WooCommerce Box Office.
* Requires at least: 6.2
* Tested up to: 6.4
- * Version: 1.0.55
+ * Version: 2.0.0-RC1
* Requires PHP: 8.0
* Author: WordPress.com Special Projects
* Author URI: https://wpspecialprojects.wordpress.com
@@ -31,13 +31,17 @@
function_exists( 'get_plugin_data' ) || require_once ABSPATH . 'wp-admin/includes/plugin.php';
define( 'SE_METADATA', get_plugin_data( __FILE__, false, false ) );
-define( 'SE_VERSION', '1.0.55' );
+define( 'SE_VERSION', '2.0.0' );
define( 'SE_BASENAME', plugin_basename( __FILE__ ) );
define( 'SE_PLUGIN_DIR', untrailingslashit( plugin_dir_path( __FILE__ ) ) );
define( 'SE_PLUGIN_URL', untrailingslashit( plugin_dir_url( __FILE__ ) ) );
define( 'SE_SRC_PATH', untrailingslashit( SE_PLUGIN_DIR . '/src' ) );
define( 'SE_TEMPLATE_PATH', untrailingslashit( SE_SRC_PATH . '/templates' ) );
+// This should only be updated if there are changes to the way we handle dates and there are migration method to handle.
+// This is used to determine if we need to run migrations.
+define( 'SE_MIGRATION_VERSION', '2.0.0' );
+
// Load the autoloader.
if ( ! is_file( SE_PLUGIN_DIR . '/vendor/autoload.php' ) ) {
add_action(
@@ -52,20 +56,6 @@ static function () {
}
require_once SE_PLUGIN_DIR . '/vendor/autoload.php';
-// Initialize the plugin if system requirements check out.
-$se_requirements = validate_plugin_requirements( SE_BASENAME );
-define( 'SE_REQUIREMENTS', $se_requirements );
-
-if ( $se_requirements instanceof WP_Error ) {
- add_action(
- 'admin_notices',
- static function () use ( $se_requirements ) {
- $html_message = wp_sprintf( '
%s
', $se_requirements->get_error_message() );
- echo wp_kses_post( $html_message );
- }
- );
- return;
-}
require_once SE_SRC_PATH . '/classes/class-se-event-post-type.php';
require_once SE_SRC_PATH . '/classes/class-se-blocks.php';
@@ -76,6 +66,9 @@ static function () use ( $se_requirements ) {
require_once SE_SRC_PATH . '/classes/class-se-calendar-export.php';
require_once SE_SRC_PATH . '/classes/class-se-calendar.php';
require_once SE_SRC_PATH . '/classes/class-se-event-query-dates.php';
+require_once SE_SRC_PATH . '/classes/class-se-event-dates.php';
+require_once SE_SRC_PATH . '/classes/class-date-display-formatter.php';
+require_once SE_SRC_PATH . '/classes/class-se-migrate-events.php';
require_once SE_SRC_PATH . '/calendar-functions.php';
require_once SE_SRC_PATH . '/event-functions.php';
@@ -85,6 +78,7 @@ static function () use ( $se_requirements ) {
require_once SE_SRC_PATH . '/rest-api.php';
require_once SE_SRC_PATH . '/back-compat.php';
+
/**
* Add a flag to leverage for flushing rewrite rules.
*
diff --git a/simple-events.zip b/simple-events.zip
new file mode 100644
index 0000000..a5153f8
Binary files /dev/null and b/simple-events.zip differ
diff --git a/src/assets/js/admin.js b/src/assets/js/admin.js
index 1aa6c57..581a809 100644
--- a/src/assets/js/admin.js
+++ b/src/assets/js/admin.js
@@ -4,55 +4,269 @@
* @package simple-events
*/
-jQuery( document ).ready( function( $ ) {
-
+jQuery(document).ready(function ($) {
+ // Handle Migrate Events.
+ $('#se_migrate_events_btn').on('click', function () {
+ startMigrationProcess();
+ });
+
+ /**
+ * Start the migration process and continue until all events are processed
+ */
+ function startMigrationProcess() {
+ // Show warning notice and disable button
+ showMigrationNotice();
+ $('#se_migrate_events_btn').prop('disabled', true);
+
+ // Start processing batches
+ processMigrationBatch();
+ }
+
+ /**
+ * Show the migration warning notice
+ */
+ function showMigrationNotice() {
+ // Remove existing notice if any
+ $('#se_migration_notice').remove();
+
+ // Create and insert the notice
+ const notice = `
+
+
+ ⚠️
+ MIGRATION IN PROGRESS - DO NOT CLOSE YOUR BROWSER
+
+
+ Please keep this page open while events are being migrated. Closing the browser will interrupt the process.
+
+
+ `;
+
+ $('#se_migrate_events_wrapper').before(notice);
+ }
+
+ /**
+ * Hide the migration warning notice
+ */
+ function hideMigrationNotice() {
+ $('#se_migration_notice').fadeOut(300, function () {
+ $(this).remove();
+ });
+ // Keep the button disabled - no retry functionality
+ $('#se_migrate_events_btn').prop('disabled', true);
+ }
+
+ /**
+ * Hide the migration warning notice but keep button disabled (for completion)
+ */
+ function hideMigrationNoticeCompleted() {
+ $('#se_migration_notice').fadeOut(300, function () {
+ $(this).remove();
+ });
+ // Keep the button disabled when migration is completed
+ $('#se_migrate_events_btn').prop('disabled', true);
+ }
+
+ /**
+ * Process a single batch of migration events
+ */
+ function processMigrationBatch() {
+ // Track events processed.
+ const perBatch = 24;
+
+ // Get the next events that are still pending
+ const nextEvents = $('#se_migrate_events_wrapper .se_migrate_event').filter('[data-status="pending"]').slice(0, perBatch);
+
+ // If no more events to process, we're done
+ if (nextEvents.length === 0) {
+ hideMigrationNoticeCompleted();
+ console.log('Migration completed - all events processed!');
+ return;
+ }
+
+ // Get all the event ids.
+ const eventIds = nextEvents.map(function () {
+ return $(this).data('event-id');
+ });
+
+ // Set events to processing status before making the request
+ nextEvents.each(function () {
+ const eventElement = $(this);
+ eventElement.attr('data-status', 'processing');
+ const statusElement = eventElement.find('.se_migrate_event_status');
+ statusElement.css({
+ 'background': '#007cba',
+ 'color': '#fff'
+ }).text('Processing...');
+ });
+
+ // Make REST API call to migrate events
+ $.ajax({
+ url: window.location.origin + '/wp-json/simple-events/migrate-events',
+ type: 'POST',
+ data: {
+ events: JSON.stringify(eventIds.get())
+ },
+ beforeSend: function (xhr) {
+ xhr.setRequestHeader('X-WP-Nonce', seAdmin.nonce);
+ },
+ xhrFields: {
+ withCredentials: true // SEND cookies!
+ },
+ success: function (response) {
+ // Update the status of processed events
+ if (response.data) {
+ Object.keys(response.data).forEach(function (eventId) {
+ const resultData = response.data[eventId];
+ const eventElement = $(`[data-event-id="${eventId}"]`);
+ const statusElement = eventElement.find('.se_migrate_event_status');
+ const versionElement = eventElement.find('span[style*="monospace"]');
+
+ // Handle the new response format
+ if (typeof resultData === 'object' && resultData !== null) {
+ const success = resultData.success;
+ const version = resultData.version;
+
+ // Update status
+ eventElement.attr('data-status', success ? 'completed' : 'error');
+
+ if (success) {
+ statusElement.css({
+ 'background': '#00a32a',
+ 'color': '#fff'
+ }).text('Completed');
+ } else {
+ statusElement.css({
+ 'background': '#d63638',
+ 'color': '#fff'
+ }).text('Error');
+ }
+
+ // Update version if provided
+ if (version && versionElement.length) {
+ versionElement.text('v' + version);
+ }
+ } else {
+ // Fallback for old response format (boolean)
+ const success = Boolean(resultData);
+ eventElement.attr('data-status', success ? 'completed' : 'error');
+
+ if (success) {
+ statusElement.css({
+ 'background': '#00a32a',
+ 'color': '#fff'
+ }).text('Completed');
+ } else {
+ statusElement.css({
+ 'background': '#d63638',
+ 'color': '#fff'
+ }).text('Error');
+ }
+ }
+ });
+ }
+
+ // Process the next batch after a short delay
+ setTimeout(function () {
+ processMigrationBatch();
+ }, 500);
+ },
+ error: function (xhr, status, error) {
+ console.error('Migration failed:', error);
+ // Reset processing events back to pending on error
+ nextEvents.each(function () {
+ const eventElement = $(this);
+ if (eventElement.attr('data-status') === 'processing') {
+ eventElement.attr('data-status', 'pending');
+ const statusElement = eventElement.find('.se_migrate_event_status');
+ statusElement.css({
+ 'background': '#ffc107',
+ 'color': '#856404'
+ }).text('Pending');
+ }
+ });
+
+ // Hide notice and re-enable button on error
+ hideMigrationNotice();
+ }
+ });
+ }
+
// Handle Ticket Only Order Completion..
- $( '#se_ajax_btn' ).on( 'click', function() {
+ $('#se_ajax_btn').on('click', function () {
// Disable button and extract action.
- $( this ).prop( 'disabled', true );
- const action = $( this ).data( 'action' );
-
+ $(this).prop('disabled', true);
+ const action = $(this).data('action');
+
// Perform AJAX request.
- $.ajax( {
+ $.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: action,
},
- success: function( response ) {
- $( '#se_ajax_response' ).html( `${response?.data}
` );
+ success: function (response) {
+ $('#se_ajax_response').html(`${response?.data}
`);
},
- error: function() {
- $( '#se_ajax_response' ).html( 'Something went wrong!
' );
+ error: function () {
+ $('#se_ajax_response').html('Something went wrong!
');
},
- complete: function() {
- $( '#se_ajax_btn' ).prop( 'disabled', false );
- setTimeout( () => {
- $( '#se_ajax_response' ).html( '' );
- }, 2000 );
+ complete: function () {
+ $('#se_ajax_btn').prop('disabled', false);
+ setTimeout(() => {
+ $('#se_ajax_response').html('');
+ }, 2000);
},
- } );
- } );
+ });
+ });
+
+ // Handle Clear Orphaned Events button
+ $('#se_clear_orphaned_btn').on('click', function () {
+ // Disable button and extract action.
+ $(this).prop('disabled', true);
+ const action = $(this).data('action');
+
+ // Perform AJAX request.
+ $.ajax({
+ url: ajaxurl,
+ type: 'POST',
+ data: {
+ action: action,
+ },
+ success: function (response) {
+ $('#se_clear_orphaned_response').html(`${response?.data}
`);
+ },
+ error: function () {
+ $('#se_clear_orphaned_response').html('Something went wrong!
');
+ },
+ complete: function () {
+ $('#se_clear_orphaned_btn').prop('disabled', false);
+ setTimeout(() => {
+ $('#se_clear_orphaned_response').html('');
+ }, 2000);
+ },
+ });
+ });
// Handle Skip Cart and Empty Cart options.
- const skipCart = $( 'input[name="se_options[skip_cart]"]' );
- const emptyCartBeforeAddingTickets = $( 'input[name="se_options[empty_cart_before_adding_tickets]"]' );
+ const skipCart = $('input[name="se_options[skip_cart]"]');
+ const emptyCartBeforeAddingTickets = $('input[name="se_options[empty_cart_before_adding_tickets]"]');
// If Skip Cart is not enabled, disable Empty Cart Before Adding Tickets.
- $( window ).on( 'load', function () {
- if( skipCart && ! skipCart.is( ':checked' ) ) {
- emptyCartBeforeAddingTickets.prop( 'checked', false );
- emptyCartBeforeAddingTickets.closest( 'tr' ).hide();
+ $(window).on('load', function () {
+ if (skipCart && !skipCart.is(':checked')) {
+ emptyCartBeforeAddingTickets.prop('checked', false);
+ emptyCartBeforeAddingTickets.closest('tr').hide();
}
- } );
-
+ });
+
// Handle Skip Cart option change.
- skipCart.on( 'input', function() {
- if( $( this ).is( ':checked' ) ) {
- emptyCartBeforeAddingTickets.closest( 'tr' ).show();
+ skipCart.on('input', function () {
+ if ($(this).is(':checked')) {
+ emptyCartBeforeAddingTickets.closest('tr').show();
} else {
- emptyCartBeforeAddingTickets.prop( 'checked', false );
- emptyCartBeforeAddingTickets.closest( 'tr' ).hide();
+ emptyCartBeforeAddingTickets.prop('checked', false);
+ emptyCartBeforeAddingTickets.closest('tr').hide();
}
- } );
-} );
+ });
+});
diff --git a/src/back-compat.php b/src/back-compat.php
index 6f50ea2..9b5e06e 100644
--- a/src/back-compat.php
+++ b/src/back-compat.php
@@ -26,3 +26,38 @@ function se_pre_gutenberg_14_3_0_compat( $classes ) {
}
add_filter( 'admin_body_class', 'se_pre_gutenberg_14_3_0_compat' );
+
+/**
+ * Adds an admin notice to say we have events that need to be migrated.
+ *
+ * @return void
+ */
+function se_admin_notice_events_to_migrate() {
+ if ( ! current_user_can( 'manage_options' ) ) {
+ return;
+ }
+
+ // Check if we have events that need to be migrated.
+ if ( SE_Migrate_Events::has_events_to_migrate() ) {
+ // Lets make the error large so it cant be ingnored with a link to the settings page.
+
+ ?>
+
+
+ ⚠️
+
+
+
+
+
+
+
+
+
+
+ {
+ const [tempEventDate, setTempEventDate] = useState(null);
+ const [tempEventTime, setTempEventTime] = useState(null);
+ // Add local state to track the current eventDateTime
+ const [currentEventDateTime, setCurrentEventDateTime] = useState(eventDateTime);
+ // Add state to track if this date has been removed
+ const [isRemoved, setIsRemoved] = useState(false);
+
+ // Update local state when eventDateTime prop changes (e.g., timezone update)
+ useEffect(() => {
+ setCurrentEventDateTime(eventDateTime);
+ }, [eventDateTime]);
+
+
+ const eventStart = getMoment(
+ currentEventDateTime.start_date,
+ true,
+ currentTimezone
+ );
+ const eventEnd = getMoment(currentEventDateTime.end_date, true, currentTimezone);
+ const timeFormat = DATE_SETTINGS.formats.datetime;
+
+ /**
+ * Handle the set date and time button click.
+ *
+ * @param {boolean} isStartChange Whether this is start or end date change.
+ *
+ * @return {void}
+ */
+ const setDateTimeHandler = (isStartChange, onClose) => {
+ // Ensure we have either new date or time in state.
+ if (!tempEventDate && !tempEventTime) {
+ return;
+ }
+
+ const newDate =
+ tempEventDate ||
+ (isStartChange ? eventStart : eventEnd);
+ const newTime =
+ tempEventTime ||
+ (isStartChange ? eventStart : eventEnd);
+
+ // Combine the new date and time and convert to a timestamp.
+ const newDateTime = getTimestamp(
+ combineDateAndTime(newDate, newTime),
+ currentTimezone
+ );
+
+ const newEventDateTime = clone(currentEventDateTime);
+
+ if (isStartChange) {
+ newEventDateTime.start_date = newDateTime;
+
+ // Check if the new start time is after the current end time.
+ if (
+ parseInt(newEventDateTime.start_date) >=
+ parseInt(newEventDateTime.end_date)
+ ) {
+ // Set the new end time to be 1 hour after the start dateTime.
+ newEventDateTime.end_date = String(
+ parseInt(newEventDateTime.start_date) +
+ 3600
+ );
+ }
+ } else {
+ newEventDateTime.end_date = newDateTime;
+
+ // Check if the new end time is before the current start time.
+ if (
+ parseInt(newEventDateTime.start_date) >=
+ parseInt(newEventDateTime.end_date)
+ ) {
+ // Set the new start time to be 1 hour before the end dateTime.
+ newEventDateTime.start_date = String(
+ parseInt(newEventDateTime.end_date) - 3600
+ );
+ }
+ }
+
+ // Reset the temp date and time.
+ setTempEventDate(null);
+ setTempEventTime(null);
+
+ // Use dateManagerInstance to save the changes if available
+ if (dateManagerInstance && dateManagerInstance.upsertDate) {
+ dateManagerInstance.upsertDate(newEventDateTime);
+ }
+
+ // Update the current eventDateTime state
+ setCurrentEventDateTime(newEventDateTime);
+
+ // Close the appropriate dropdown
+ onClose();
+ };
+
+ /**
+ * Handles DateTimePicker changes.
+ *
+ * @param {string} currentDateTime The current dateTime.
+ * @param {string} newDateTime The new selected dateTime.
+ *
+ * @return {void}
+ */
+ const datePickerHandler = (currentDateTime, newDateTime) => {
+ // Compare the date without time to see if the time or date was changed.
+ const isDateChange =
+ moment(currentDateTime).format('YYYY-MM-DD') ===
+ moment(newDateTime).format('YYYY-MM-DD');
+
+ if (isDateChange) {
+ setTempEventTime(newDateTime);
+ } else {
+ setTempEventDate(newDateTime);
+ }
+ };
+
+ return (
+
+ {hasMultipleDates && (
+
+ {
+ setIsRemoved(true);
+ dateManagerInstance.removeDate(currentEventDateTime);
+ }}
+ />
+
+ )}
+
+
+ (
+ {
+ onToggle();
+ }}
+ aria-expanded={isOpen}
+ >
+ {currentEventDateTime.all_day
+ ? wp.date.format(
+ 'F j, Y',
+ eventStart
+ )
+ : wp.date.format(
+ timeFormat,
+ eventStart
+ )}
+
+ )}
+ renderContent={({ onClose }) => (
+
+
+ datePickerHandler(
+ eventStart,
+ newDateTime
+ )
+ }
+ __nextRemoveHelpButton
+ __nextRemoveResetButton
+ />
+
+ setDateTimeHandler(true, onClose)
+ }
+ variant="secondary"
+ >
+ {__(
+ 'Set time',
+ 'simple-events'
+ )}
+
+
+ )}
+ />
+
+
+ (
+ {
+ onToggle();
+ }}
+ aria-expanded={isOpen}
+ disabled={currentEventDateTime.all_day}
+ >
+ {currentEventDateTime.all_day
+ ? '--:--'
+ : wp.date.format(
+ timeFormat,
+ eventEnd
+ )}
+
+ )}
+ renderContent={({ onClose }) => (
+
+
+ datePickerHandler(
+ eventEnd,
+ newDateTime
+ )
+ }
+ __nextRemoveHelpButton
+ __nextRemoveResetButton
+ />
+
+ setDateTimeHandler(false, onClose)
+ }
+ variant="secondary"
+ text={__(
+ 'Set time',
+ 'simple-events'
+ )}
+ />
+
+ )}
+ />
+
+
+ {
+ const newEventDateTime = clone(currentEventDateTime);
+
+ newEventDateTime.all_day = !currentEventDateTime.all_day;
+
+ newEventDateTime.start_date = getMoment(
+ newEventDateTime.start_date
+ );
+ newEventDateTime.end_date = getMoment(
+ newEventDateTime.end_date
+ );
+
+ // If all day event, set time between 00:00 and 23:59.
+ if (newEventDateTime.all_day) {
+ newEventDateTime.start_date.startOf('date');
+ newEventDateTime.end_date.endOf('date');
+ } else {
+ newEventDateTime.start_date
+ .hour(DEFAULT_START_HOUR)
+ .minute(0);
+ newEventDateTime.end_date
+ .hour(DEFAULT_END_HOUR)
+ .minute(0);
+ }
+
+ newEventDateTime.start_date = getTimestamp(
+ newEventDateTime.start_date
+ );
+ newEventDateTime.end_date = getTimestamp(
+ newEventDateTime.end_date
+ );
+
+ // Use dateManagerInstance to save the changes if available
+ if (dateManagerInstance && dateManagerInstance.upsertDate) {
+ dateManagerInstance.upsertDate(newEventDateTime);
+ }
+
+ // Update the current eventDateTime state
+ setCurrentEventDateTime(newEventDateTime);
+ }}
+ />
+
+
+ {
+ const newEventDateTime = clone(currentEventDateTime);
+ newEventDateTime.hide_from_calendar = !currentEventDateTime.hide_from_calendar;
+
+ // Use dateManagerInstance to save the changes if available
+ if (dateManagerInstance && dateManagerInstance.upsertDate) {
+ dateManagerInstance.upsertDate(newEventDateTime);
+ }
+
+ // Update the current eventDateTime state
+ setCurrentEventDateTime(newEventDateTime);
+ }}
+ />
+
+
+ {
+ const newEventDateTime = clone(currentEventDateTime);
+ newEventDateTime.hide_from_feed = !currentEventDateTime.hide_from_feed;
+
+ // Use dateManagerInstance to save the changes if available
+ if (dateManagerInstance && dateManagerInstance.upsertDate) {
+ dateManagerInstance.upsertDate(newEventDateTime);
+ }
+
+ // Update the current eventDateTime state
+ setCurrentEventDateTime(newEventDateTime);
+ }}
+ />
+
+
+
+ );
+};
+
+export default DateTimeGroupNew;
diff --git a/src/blocks/event-info/date-utils.js b/src/blocks/event-info/date-utils.js
new file mode 100644
index 0000000..6ec691e
--- /dev/null
+++ b/src/blocks/event-info/date-utils.js
@@ -0,0 +1,314 @@
+import moment from 'moment';
+import { getSettings } from '@wordpress/date';
+import { __ } from '@wordpress/i18n';
+import { head, last } from 'lodash';
+
+/**
+ * Date and Time Utilities
+ *
+ * Utility functions for handling date/time operations, timezone conversions,
+ * and date formatting within the Simple Events plugin. Provides consistent
+ * date handling across the event management system.
+ *
+ * @package SimpleEvents
+ * @since 1.0.0
+ */
+
+/**
+ * Date/Time Constants
+ */
+export const DEFAULT_START_HOUR = 9;
+export const DEFAULT_END_HOUR = 10;
+export const FORMAT = 'YYYY-MM-DD HH:mm';
+
+const DATE_SETTINGS = getSettings();
+export const OFFSET = Number(DATE_SETTINGS.timezone.offset);
+export const TIMEZONE = DATE_SETTINGS.timezone.string;
+
+export let TIMEZONE_NAME = TIMEZONE;
+if ('' === TIMEZONE) {
+ TIMEZONE_NAME = 'UTC' + (OFFSET >= 0 ? '+' : '') + OFFSET;
+}
+
+export const TIMEZONES = moment.tz
+ .names()
+ .map((tz) => ({ label: tz, value: tz }));
+
+// Add an option to use the site settings
+TIMEZONES.unshift({
+ label: __('Same as site', 'simple-events'),
+ value: '',
+});
+
+/**
+ * Gets the DST offset for a given timestamp and timezone.
+ *
+ * Calculates the daylight saving time offset for a specific timestamp
+ * within a given timezone. Handles timezone conversions and DST transitions.
+ *
+ * @since 1.0.0
+ *
+ * @param {number} timestamp The timestamp to check.
+ * @param {string|null} timezone The timezone to check (defaults to current timezone).
+ * @param {string} currentTimezone The current event timezone.
+ * @return {number} The offset in minutes.
+ */
+export const getDstOffset = (timestamp, timezone = null, currentTimezone = TIMEZONE) => {
+ // Return no offset if the event timezone is the same as the site.
+ if (null === timezone) {
+ timezone = currentTimezone;
+ }
+
+ if ('' === timezone) {
+ return OFFSET;
+ }
+
+ // Get the timezone details.
+ const timezoneDetails = moment.tz.zone(timezone);
+
+ // Get the index of the current timezone offset i.e DST or non-DST. -1 at the end to account for search algorithm.
+ const untilIndex = timezoneDetails.untils.findIndex(function (number) {
+ return number / 1000 > timestamp;
+ });
+
+ return timezoneDetails.offsets[untilIndex] * -1;
+};
+
+/**
+ * Creates a moment object in the site timezone from a unix timestamp.
+ *
+ * Converts a unix timestamp to a moment object using the appropriate
+ * timezone offset. Can optionally return a formatted string instead.
+ *
+ * @since 1.0.0
+ *
+ * @param {string} timestamp Timestamp to convert to a moment.
+ * @param {boolean} formatted Whether to return a human-readable formatted string.
+ * @param {string} currentTimezone The current timezone for the event.
+ * @return {moment.Moment|string} Human readable formatted string if `formatted` is true, moment object otherwise.
+ */
+export const getMoment = (timestamp, formatted = false, currentTimezone = TIMEZONE) => {
+ const dateTime = moment
+ .unix(timestamp)
+ .utcOffset(getDstOffset(timestamp, null, currentTimezone));
+
+ if (!formatted) {
+ return dateTime;
+ }
+
+ return dateTime.format(FORMAT);
+};
+
+/**
+ * Creates a timestamp from a date string.
+ *
+ * Converts a date string to a unix timestamp, applying the appropriate
+ * timezone offset for accurate time representation.
+ *
+ * @since 1.0.0
+ *
+ * @param {string} dateTime Date string to convert to a timestamp.
+ * @param {string} currentTimezone The current timezone for the event.
+ * @return {string} The timestamp, cast as a string.
+ */
+export const getTimestamp = (dateTime, currentTimezone = TIMEZONE) => {
+ return String(
+ moment(dateTime)
+ .utcOffset(
+ getDstOffset(moment(dateTime).unix(), null, currentTimezone),
+ true
+ )
+ .utc()
+ .unix()
+ );
+};
+
+/**
+ * Gets the start and end date from a collection of dates.
+ *
+ * Analyzes a collection of event dates to determine the overall start and end
+ * times, filtering out dates that have already passed. Returns the earliest
+ * start date and latest end date from valid future dates.
+ *
+ * @since 1.0.0
+ *
+ * @param {Array} dates Array of date objects with all_day, start_date, and end_date properties.
+ * @return {Object} Object with start_date and end_date properties (strings or null).
+ */
+export const getStartAndEndDate = (dates) => {
+ // iterate over and remove any that has passed.
+ const now = moment().utcOffset(OFFSET);
+ const filteredDates = dates.filter((date) => {
+ const endDate = moment.unix(date.end_date).utcOffset(OFFSET);
+ return endDate.isAfter(now);
+ });
+
+ /**
+ * Gets the first and last date from the collection.
+ *
+ * Helper closure that extracts the earliest start date and
+ * latest end date from the full date collection.
+ *
+ * @since 1.0.0
+ *
+ * @return {Object} Object with start_date and end_date properties.
+ */
+ const getFirstAndLastDate = () => {
+ return {
+ start_date: moment.unix(head(dates).start_date).utcOffset(OFFSET).unix().toString(),
+ end_date: moment.unix(last(dates).end_date).utcOffset(OFFSET).unix().toString(),
+ };
+ };
+
+ let startDate = null;
+ let endDate = null;
+
+ // Do not trust the order of the dates.
+ if (filteredDates.length === 0) {
+ // Return the earliest start date and the latest end date.
+ return getFirstAndLastDate();
+ }
+
+ // Loop over the dates and set the start date as the earliest and the end as the latest.
+ filteredDates.forEach((date) => {
+ const startDateMoment = moment.unix(date.start_date).utcOffset(OFFSET);
+ const endDateMoment = moment.unix(date.end_date).utcOffset(OFFSET);
+
+ // If the end date has passed, skip it.
+ if (endDateMoment.isBefore(now)) {
+ return;
+ }
+
+ /**
+ * Sets the start or end date based on comparison logic.
+ *
+ * Helper closure for determining and setting the earliest start date
+ * and latest end date from the filtered date collection.
+ *
+ * @since 1.0.0
+ *
+ * @param {moment.Moment} startDateMoment The start date moment to evaluate.
+ * @param {moment.Moment} endDateMoment The end date moment to evaluate.
+ */
+ const setDate = (startDateMoment, endDateMoment) => {
+ // If the start date is before the current start date, set it.
+ if (!startDate || startDateMoment.isBefore(startDate) || (startDate.isAfter(startDateMoment) && startDate.isBefore(now))) {
+ startDate = startDateMoment;
+ }
+
+ // If the end date is after the current end date, set it.
+ if (!endDate || endDateMoment.isAfter(endDate)) {
+ endDate = endDateMoment;
+ }
+ };
+
+ // If the start date if after now
+ if (startDateMoment.isAfter(now) && endDateMoment.isAfter(now)) {
+ setDate(startDateMoment, endDateMoment);
+ } else if (startDateMoment.isBefore(now) && endDateMoment.isAfter(now)) {
+ setDate(startDateMoment, endDateMoment);
+ }
+ });
+
+ // If we have no startDate or endDate, just get the first from dates.
+ if (!startDate) {
+ startDate = moment.unix(head(filteredDates).start_date).utcOffset(OFFSET);
+ }
+ if (!endDate) {
+ endDate = moment.unix(last(filteredDates).end_date).utcOffset(OFFSET);
+ }
+ return {
+ start_date: startDate.unix().toString(),
+ end_date: endDate.unix().toString(),
+ };
+};
+
+/**
+ * Creates a default date object for new events.
+ *
+ * Generates a new event date object with sensible defaults based on
+ * existing dates and timezone. Sets the new date to be one day after
+ * the last existing date, or uses current time with default hours.
+ *
+ * @since 2.0.0
+ *
+ * @param {Array} existingDates Array of existing date objects.
+ * @param {string} currentTimezone The current timezone identifier.
+ * @return {Object} New date object with start_date, end_date, and flag properties.
+ */
+export const createDefaultDate = (existingDates = [], currentTimezone = TIMEZONE) => {
+ // Set default date and time.
+ let eventStart = moment().utcOffset(OFFSET);
+ eventStart.hour(DEFAULT_START_HOUR);
+ eventStart.minute(0);
+ eventStart.second(0);
+
+ let eventEnd = eventStart.clone();
+ eventEnd.hour(DEFAULT_END_HOUR);
+
+ // Override with existing date if there is one.
+ if (existingDates.length) {
+ eventStart = getMoment(last(existingDates).start_date, false, currentTimezone);
+ eventEnd = getMoment(last(existingDates).end_date, false, currentTimezone);
+ }
+
+ // Set default date to be +1 day from the last date.
+ eventStart.add(1, 'days');
+ eventEnd.add(1, 'days');
+
+ return {
+ start_date: wp.date.date('U', eventStart),
+ end_date: wp.date.date('U', eventEnd),
+ all_day: false,
+ hide_from_calendar: false,
+ hide_from_feed: false,
+ };
+};
+
+/**
+ * Combines a date and time into a moment object.
+ *
+ * Takes separate date and time strings and combines them into a single
+ * moment object, preserving the date from the first parameter and the
+ * time from the second parameter.
+ *
+ * @since 1.0.0
+ *
+ * @param {string} date The date string to use.
+ * @param {string} time The time string to use.
+ * @return {moment.Moment} The combined date and time as a moment object.
+ */
+export const combineDateAndTime = (date, time) => {
+ const timeMoment = moment(time);
+ const dateMoment = moment(date);
+
+ // Set the timeMoment's time to the dateMoment.
+ return dateMoment.set({
+ hour: timeMoment.get('hour'),
+ minute: timeMoment.get('minute'),
+ });
+};
+
+/**
+ * Checks if the current time format is 12-hour.
+ *
+ * Analyzes the WordPress date format settings to determine if the
+ * site is configured to use 12-hour time format (with AM/PM indicators).
+ *
+ * @since 2.0.0
+ *
+ * @return {boolean} True if 12-hour format is used, false otherwise.
+ */
+export const is12HourTime = () => {
+ const timeFormat = DATE_SETTINGS.formats.datetime;
+ // To know if the current timezone is a 12 hour time with look for an "a" in the time format.
+ // We also make sure this a is not escaped by a "/".
+ return /a(?!\\)/i.test(
+ timeFormat
+ .toLowerCase() // Test only the lower case a
+ .replace(/\\\\/g, '') // Replace "//" with empty strings
+ .split('')
+ .reverse()
+ .join('') // Reverse the string and test for "a" not followed by a slash
+ );
+};
diff --git a/src/blocks/event-info/editor.scss b/src/blocks/event-info/editor.scss
index 222e043..ce11bbe 100644
--- a/src/blocks/event-info/editor.scss
+++ b/src/blocks/event-info/editor.scss
@@ -6,197 +6,187 @@
*/
.wp-block-simple-events-event-info {
- .components-placeholder__fieldset {
- flex-direction: column;
- }
-
- .components-base-control {
- margin: 0 0 8px;
- text-align: left;
-
- &:last-of-type {
- margin-bottom: 0;
-
- .components-base-control__field {
- white-space: nowrap;
- }
-
- .components-checkbox-control__input-container {
- align-content: center;
- align-items: center;
- display: inline-flex;
- height: 33px;
- position: relative;
-
- .components-checkbox-control__input {
- height: 20px;
- min-width: 20px;
- }
- }
- }
- }
-
- .components-base-control__label {
- display: block;
- }
+ .components-placeholder__fieldset {
+ flex-direction: column;
+ }
+ .components-base-control {
+ margin: 0 0 8px;
+ text-align: left;
+ &:last-of-type {
+ margin-bottom: 0;
+ .components-base-control__field {
+ white-space: nowrap;
+ }
+ .components-checkbox-control__input-container {
+ align-content: center;
+ align-items: center;
+ display: inline-flex;
+ height: 33px;
+ position: relative;
+ .components-checkbox-control__input {
+ height: 20px;
+ min-width: 20px;
+ }
+ }
+ }
+ }
+ .components-base-control__label {
+ display: block;
+ }
}
.se__button-done {
- align-self: flex-start;
- margin-top: 10px;
+ align-self: flex-start;
+ margin-top: 10px;
}
.se-datetime-popover {
- .components-popover__content {
- justify-content: center;
- width: max-content;
- padding: 1em;
- position: relative;
-
- .components-datetime__date {
- margin-top: 50px;
- }
- }
-
- .components-datetime__time {
- padding-bottom: 0;
-
- fieldset {
- margin-bottom: 0;
-
- legend {
- display: none;
- }
- }
-
- .components-datetime__time-wrapper .components-datetime__time-field-time {
- align-items: center;
- display: flex;
- }
-
- .components-datetime__time-field-am-pm {
- white-space: nowrap;
- }
- }
+ .components-popover__content {
+ justify-content: center;
+ width: max-content;
+ padding: 1em;
+ position: relative;
+ .components-datetime__date {
+ margin-top: 50px;
+ }
+ }
+ .components-datetime__time {
+ padding-bottom: 0;
+ fieldset {
+ margin-bottom: 0;
+ legend {
+ display: none;
+ }
+ }
+ .components-datetime__time-wrapper .components-datetime__time-field-time {
+ align-items: center;
+ display: flex;
+ }
+ .components-datetime__time-field-am-pm {
+ white-space: nowrap;
+ }
+ }
}
+
/* Date: remove time */
-.se-datetime-popover__date {
- fieldset:first-child {
- display: none;
- }
- .components-datetime__time-field-month-select {
- height: 100%;
- }
+.se-datetime-popover__date {
+ fieldset:first-child {
+ display: none;
+ }
+ .components-datetime__time-field-month-select {
+ height: 100%;
+ }
}
+
/* Time: remove date */
+
.se-datetime-popover__time {
- .components-datetime__time {
- fieldset + fieldset {
- margin-top: 10px;
- }
- }
-
- .components-datetime__timezone {
- display: none;
- }
+ .components-datetime__time {
+ fieldset+fieldset {
+ margin-top: 10px;
+ }
+ }
+ .components-datetime__timezone {
+ display: none;
+ }
}
+
/* Set date/time button */
+
.se-datetime-popover__set-datetime {
- margin-top: 15px;
- position: absolute;
- top: 100px;
+ margin-top: 15px;
+ position: absolute;
+ top: 100px;
}
.se-datetimegroup-controls-label {
- display: flex;
- font-weight: 700;
-
- + div {
- margin-top: -4px;
- }
+ display: flex;
+ font-weight: 700;
+ +div {
+ margin-top: -4px;
+ }
}
.se-datetimegroup-container {
- border-bottom: 1px solid #e2e4e7;
+ border-bottom: 1px solid #e2e4e7;
+ position: relative;
+ padding-top: 8px;
}
.se-datetimegroup-controls {
- display: grid;
- grid-template-columns: auto auto 70px;
- grid-column-gap: 10px;
- margin-top: 8px;
- max-width: 450px;
-
- .components-dropdown {
- width: 100%;
- }
-
- .components-base-control:nth-of-type(3) {
- flex: none;
- align-self: flex-end;
- margin-bottom: 10px;
- margin-left: auto;
-
- .components-checkbox-control__input-container {
- margin-right: 8px;
- }
-
- label {
- font-weight: normal;
- font-size: 14px;
- }
- }
-
- .se-datetime-control__delete {
- grid-column-start: 4;
- margin-top: -68px;
- margin-right: -50px;
- margin-left: auto;
- align-self: center;
- }
-
- .se-datetime-popover__button {
- width: 100%;
- justify-content: center;
- margin-bottom: 0;
- margin-top: 0;
- }
-
- .is-button.is-default:not(:disabled) {
- border-color: #7e8993;
- background-color: #fff;
- color: #32373c;
- }
+ display: grid;
+ grid-template-columns: auto auto 70px;
+ grid-column-gap: 10px;
+ margin-top: 8px;
+ max-width: 450px;
+ .components-dropdown {
+ width: 100%;
+ }
+ .components-base-control:nth-of-type(3) {
+ flex: none;
+ align-self: flex-end;
+ margin-bottom: 10px;
+ margin-left: auto;
+ .components-checkbox-control__input-container {
+ margin-right: 8px;
+ }
+ label {
+ font-weight: normal;
+ font-size: 14px;
+ }
+ }
+ .se-datetime-control__delete {
+ position: absolute;
+ top: 0;
+ right: 0;
+ z-index: 1;
+ .components-button {
+ min-width: 24px;
+ width: 24px;
+ height: 24px;
+ padding: 0;
+ }
+ }
+ .se-datetime-popover__button {
+ width: 100%;
+ justify-content: center;
+ margin-bottom: 0;
+ margin-top: 0;
+ }
+ .is-button.is-default:not(:disabled) {
+ border-color: #7e8993;
+ background-color: #fff;
+ color: #32373c;
+ }
}
.se-datetime-addmore {
- display: flex;
- margin-top: 10px;
- margin-bottom: 20px;
+ display: flex;
+ margin-top: 10px;
+ margin-bottom: 20px;
}
.se-location-label {
- max-width: 450px;
-
- label {
- font-weight: 700;
- }
+ max-width: 450px;
+ label {
+ font-weight: 700;
+ }
}
.se-site-timezone-label {
- flex-direction: column;
- font-size: 12px;
- color: rgb(117, 117, 117);
+ flex-direction: column;
+ font-size: 12px;
+ color: rgb(117, 117, 117);
}
.se-event-calendar-export {
- margin-top: 20px;
+ margin-top: 20px;
}
.se-all-day-checkbox .components-flex {
- align-items: center;
+ align-items: center;
}
\ No newline at end of file
diff --git a/src/blocks/event-info/event-manager.js b/src/blocks/event-info/event-manager.js
new file mode 100644
index 0000000..31f8d1e
--- /dev/null
+++ b/src/blocks/event-info/event-manager.js
@@ -0,0 +1,339 @@
+import { sortBy, isEqual, clone } from 'lodash';
+import { getStartAndEndDate, createDefaultDate, getDstOffset, TIMEZONE, OFFSET } from './date-utils';
+import moment from 'moment';
+
+/**
+ * Date Manager Service
+ *
+ * Creates a date manager service for handling event dates with change tracking,
+ * timezone management, and state synchronization. Provides a centralized way to
+ * manage event dates with automatic dirty state tracking and meta synchronization.
+ *
+ * @package SimpleEvents
+ * @since 2.0.0
+ */
+
+/**
+ * Creates a hash for a date object based on its start and end times.
+ *
+ * Generates a unique identifier for a date object using start time, end time,
+ * and current timestamp to ensure uniqueness across date operations.
+ *
+ * @since 2.0.0
+ *
+ * @param {string} start The start time of the date.
+ * @param {string} end The end time of the date.
+ * @return {string} A unique hash for the date.
+ */
+const createDateHash = (start, end) => {
+ // Get the current timestamp.
+ const timestamp = Date.now();
+ // Create a hash using the start and end times along with the timestamp.
+ const hash = `${start}-${end}-${timestamp}`;
+ return hash;
+}
+
+/**
+ * Creates a date manager instance for handling event dates.
+ *
+ * Provides a comprehensive date management system with change tracking,
+ * timezone conversion, and state synchronization. Manages both original
+ * and current date states with automatic dirty flag tracking.
+ *
+ * @since 2.0.0
+ *
+ * @param {Array} initialDates Array of initial date objects with dates property.
+ * @param {string} timezone Current timezone for the event.
+ * @param {Object} metaSync Optional meta sync object with meta and setMeta properties.
+ * @return {Object} Date management service with public interface.
+ */
+export const dateManager = (initialDates = [], timezone = '', metaSync = null) => {
+
+ // lOOP through dates and add a hash to each date
+ initialDates.dates.forEach(date => {
+ date.hash = createDateHash(date.start_date, date.end_date);
+ });
+
+ // Internal state
+ let originalDates = clone(initialDates.dates || []);
+ let currentDates = clone(initialDates.dates || []);
+ let originalTimezone = timezone || TIMEZONE;
+ let currentTimezone = timezone || TIMEZONE;
+ let isDirty = false;
+
+ // Meta sync helpers
+ const { meta, setMeta } = metaSync || {};
+
+ /**
+ * Refreshes the date manager with new dates.
+ *
+ * Updates both original and current date states with new data,
+ * adds hashes to dates if missing, and resets the dirty flag.
+ *
+ * @since 2.0.0
+ *
+ * @param {Array} newDates Array of new date objects to set.
+ * @return {Object} Updated date management service state.
+ */
+ const refreshWithNewDates = (newDates) => {
+ // Add hash to each date if not present
+ newDates.forEach(date => {
+ if (!date.hash) {
+ date.hash = createDateHash(date.start_date, date.end_date);
+ }
+ });
+
+ // Update internal state
+ originalDates = clone(newDates);
+ currentDates = clone(newDates);
+ isDirty = false;
+
+ // If orginal timezone is not the same as current timezone, update current timezone
+ if (originalTimezone !== currentTimezone && '' !== currentTimezone) {
+ originalTimezone = currentTimezone;
+ }
+
+
+ return getCurrentDates();
+ };
+
+ /**
+ * Gets the current dates and timezone information.
+ *
+ * Returns the current state including dates, timezone, and dirty flag.
+ * Considers timezone changes when determining dirty state.
+ *
+ * @since 2.0.0
+ *
+ * @return {Object} Current dates object with dates, timezone, and isDirty properties.
+ */
+ const getCurrentDates = () => {
+ const timezoneChanged = currentTimezone !== originalTimezone;
+ return {
+ dates: currentDates,
+ timezone: currentTimezone,
+ isDirty: isDirty || timezoneChanged,
+ };
+ }
+
+ /**
+ * Finds a date by its hash identifier.
+ *
+ * Searches through current dates to find a date object
+ * matching the provided hash.
+ *
+ * @since 2.0.0
+ *
+ * @param {string} hash The hash identifier of the date to find.
+ * @return {Object|undefined} The date object if found, undefined otherwise.
+ */
+ const findDateByHash = (hash) => {
+ return currentDates.find(d => d.hash === hash);
+ }
+
+ /**
+ * Updates the timezone and converts all dates accordingly.
+ *
+ * Changes the event timezone and adjusts all date timestamps to
+ * maintain the same local time in the new timezone. Updates meta
+ * if sync is available.
+ *
+ * @since 2.0.0
+ *
+ * @param {string} newTimezone The new timezone identifier to set.
+ * @return {Object} Updated date management service state.
+ */
+ const updateTimezone = (newTimezone) => {
+ const updatedDates = clone(currentDates);
+
+ // Ensure that the value is a string.
+ newTimezone = !Boolean(newTimezone) ? '' : newTimezone;
+ let targetTimezone = newTimezone;
+
+ if ('' === newTimezone) {
+ targetTimezone = TIMEZONE;
+ }
+
+ updatedDates.forEach((eventDateTime) => {
+ [
+ 'start_date',
+ 'end_date',
+ ].forEach((key) => {
+ // Get the current DST offset
+ const currentOffset = getDstOffset(
+ eventDateTime[key],
+ currentTimezone,
+ currentTimezone
+ );
+
+ // Get the target DST offset
+ const targetOffset = '' !== targetTimezone
+ ? getDstOffset(
+ eventDateTime[key],
+ targetTimezone,
+ targetTimezone
+ )
+ : OFFSET;
+
+ // Apply target timezone offset and keep same local time
+ eventDateTime[key] = String(
+ moment
+ .unix(eventDateTime[key])
+ .utcOffset(currentOffset)
+ .utcOffset(targetOffset, true)
+ .utc()
+ .unix()
+ );
+ });
+ });
+
+ // Update internal state
+ currentDates = updatedDates;
+ currentTimezone = newTimezone;
+ isDirty = true; // Mark as dirty since timezone changed
+
+ // Sync to meta if available
+ if (setMeta && meta) {
+ setMeta({
+ ...meta,
+ se_event_timezone: newTimezone
+ });
+ }
+
+ return getCurrentDates();
+ };
+
+ /**
+ * Upserts a date to the event dates collection.
+ *
+ * Updates an existing date if the hash matches, otherwise adds a new date.
+ * Automatically generates hash if missing and maintains sorted order by start date.
+ *
+ * @since 2.0.0
+ *
+ * @param {Object} date Date object to add or update with properties:
+ * - id: null|int
+ * - hash: string
+ * - start_date: string
+ * - end_date: string
+ * - all_day: boolean
+ * - hide_from_feed: boolean
+ * - hide_from_calendar: boolean
+ * @return {Object} Updated date management service state.
+ */
+ const upsertDate = (date) => {
+ // If the date doesnt contain a hash, generate one
+ if (!date.hash) {
+ date.hash = createDateHash(date.start_date, date.end_date);
+ }
+
+ // Check if the hash exists in the current dates
+ const existingIndex = currentDates.findIndex(d => d.hash === date.hash);
+ if (existingIndex !== -1) {
+ // If it exists, update the date
+ currentDates[existingIndex] = date;
+ } else {
+ // If it doesn't exist, add the new date
+ currentDates.push(date);
+ }
+ // Mark as dirty
+ isDirty = true;
+
+ // Sort the dates by start date
+ currentDates = sortBy(currentDates, 'start_date');
+
+ return getCurrentDates();
+ }
+
+ /**
+ * Removes a date from the event dates collection.
+ *
+ * Finds and removes a date object by its hash identifier,
+ * then maintains sorted order and marks state as dirty.
+ *
+ * @since 2.0.0
+ *
+ * @param {Object} date Date object to remove (must contain hash property).
+ * @return {Object} Updated date management service state.
+ */
+ const removeDate = (date) => {
+ // Find the index of the date
+ const index = currentDates.findIndex(d => d.hash === date.hash);
+ if (index !== -1) {
+ // Remove the date
+ currentDates.splice(index, 1);
+ // Mark as dirty
+ isDirty = true;
+ // Sort the dates by start date
+ currentDates = sortBy(currentDates, 'start_date');
+ }
+ return getCurrentDates();
+ }
+
+ /**
+ * Adds a new default date to the event dates collection.
+ *
+ * Creates a new date object with default values based on existing dates
+ * and current timezone, then adds it to the collection.
+ *
+ * @since 2.0.0
+ *
+ * @return {Object} Updated date management service state.
+ */
+ const addDate = () => {
+ const newDate = createDefaultDate(currentDates, currentTimezone);
+ upsertDate(newDate);
+ return getCurrentDates();
+ }
+
+ /**
+ * Reverts dates and timezone to their original state.
+ *
+ * Restores both dates and timezone to their initial values,
+ * clears dirty flag, and syncs timezone back to meta if available.
+ *
+ * @since 2.0.0
+ *
+ * @return {Object} Reverted date management service state.
+ */
+ const revertDates = () => {
+ currentDates = clone(originalDates);
+ currentTimezone = originalTimezone;
+ isDirty = false;
+
+ // Sync timezone revert to meta if available
+ if (setMeta && meta) {
+ setMeta({
+ ...meta,
+ se_event_timezone: originalTimezone
+ });
+ }
+
+ return getCurrentDates();
+ }
+
+ // Return the public interface
+ return {
+ getCurrentDates,
+ updateTimezone,
+ upsertDate,
+ removeDate,
+ addDate,
+ revertDates,
+ refreshWithNewDates,
+ // Expose internal state getters for external access
+ get originalDates() { return originalDates; },
+ get currentDates() { return currentDates; },
+ get originalTimezone() { return originalTimezone; },
+ get currentTimezone() { return currentTimezone; },
+ get isDirty() { return isDirty; },
+ // Expose internal state setters for external access
+ set originalDates(value) { originalDates = clone(value); },
+ set currentDates(value) { currentDates = clone(value); },
+ set originalTimezone(value) { originalTimezone = value; },
+ set currentTimezone(value) { currentTimezone = value; },
+ set isDirty(value) { isDirty = value; }
+ };
+
+
+};
diff --git a/src/blocks/event-info/index.js b/src/blocks/event-info/index.js
index 391407f..2c7d083 100644
--- a/src/blocks/event-info/index.js
+++ b/src/blocks/event-info/index.js
@@ -1,8 +1,13 @@
/* global lodash, ajaxurl */
/**
- * BLOCK: Events Info
+ * Event Info Block
*
- * Event date and location management.
+ * Gutenberg block for managing event date, time, location, and venue information.
+ * Provides an interface for adding multiple event dates with timezone support,
+ * venue and location details, external links, and calendar integration options.
+ *
+ * @package SimpleEvents
+ * @since 1.0.0
*/
import './editor.scss';
@@ -11,19 +16,17 @@ import moment from 'moment';
import { clone, isEqual, sortBy, head, last, pull } from 'lodash';
import { __ } from '@wordpress/i18n';
import { registerBlockType } from '@wordpress/blocks';
-import { Fragment } from '@wordpress/element';
+import { Fragment, useState, useEffect } from '@wordpress/element';
import {
PanelRow,
Placeholder,
- BaseControl,
- Dropdown,
+
Button,
TextControl,
Toolbar,
Disabled,
CheckboxControl,
ComboboxControl,
- DateTimePicker,
PanelBody,
ToggleControl,
} from '@wordpress/components';
@@ -34,145 +37,265 @@ import {
InspectorControls,
useBlockProps,
} from '@wordpress/block-editor';
-import { withState } from '@wordpress/compose';
import { useEntityProp } from '@wordpress/core-data';
+import { useSelect, useDispatch } from '@wordpress/data';
-/**
- * Constants
- */
-const DEFAULT_START_HOUR = 9;
-const DEFAULT_END_HOUR = 10;
-const DATE_SETTINGS = getSettings(); // eslint-disable-line no-restricted-syntax
-
-const OFFSET = Number(DATE_SETTINGS.timezone.offset);
-const TIMEZONE = DATE_SETTINGS.timezone.string;
-let TIMEZONE_NAME = TIMEZONE;
-if ('' === TIMEZONE) {
- TIMEZONE_NAME = 'UTC' + (OFFSET >= 0 ? '+' : '') + OFFSET;
-}
-const FORMAT = 'YYYY-MM-DD HH:mm';
-const TIMEZONES = moment.tz
- .names()
- .map((tz) => ({ label: tz, value: tz }));
-
-// Add an option to use the site settings.
-// (This label is as helpful as we can be since manual offsets have no string.)
-TIMEZONES.unshift({
- label: __('Same as site', 'simple-events'),
- value: '',
-});
+// Import date utilities
+import {
+
+ TIMEZONE,
+ TIMEZONE_NAME,
+ TIMEZONES,
+
+} from './date-utils';
+
+import apiFetch from '@wordpress/api-fetch';
+
+import { dateManager } from './event-manager';
+
+// Import the new DateTimeGroup component as DateTimeGroupNew
+import DateTimeGroupNew from './components/DateTimeGroup';
+
+const DATE_SETTINGS = getSettings(); // Still needed for timeFormat
/**
- * Get the start and end date from a collection of dates.
- * Will remove any event that has passed.
+ * Fetches event dates from the custom REST API endpoint.
+ *
+ * Retrieves all event dates associated with the current post via the
+ * Simple Events REST API. Returns an empty array if no post ID is found.
*
- * @param {{all_day: boolean, datetime_start: string, datetime_end: string}[]} dates The dates to check.
+ * @since 2.0.0
*
- * returns {{ datetime_start: string, datetime_end: string }}
+ * @return {Promise} Promise that resolves to an array of event date objects.
*/
-const getStartAndEndDate = (dates) => {
- // iterate over and remove any that has passed.
- const now = moment().utcOffset(OFFSET);
- const filteredDates = dates.filter((date) => {
+export const getEventDatePosts = () => {
+ // Get the current post id.
+ const postId = window?.wp?.data?.select('core/editor')?.getCurrentPostId();
+ if (!postId) {
+ // Return an empty array if no post id is found.
+ return Promise.resolve([]);
+ }
- const endDate = moment.unix(date.datetime_end).utcOffset(OFFSET);
- return endDate.isAfter(now);
+ // simple-events/event-dates/{event}
+ return apiFetch({ path: '/simple-events/event-dates/' + postId }).then((posts) => posts
+ ).catch((error) => {
+ console.error('Error fetching event dates:', error);
+ return [];
});
- // If we have no filtered dates, but we had dates, before.
- if (filteredDates.length === 0 && dates.length > 0) {
- // Extract all start dates with the offset.
- const allStartDates = dates.map((date) =>
- moment.unix(date.datetime_start).utcOffset(OFFSET)
- );
- const allEndDates = dates.map((date) =>
- moment.unix(date.datetime_end).utcOffset(OFFSET)
- );
+};
- // Return the latest start date and earliest end date.
- return {
- datetime_start: moment.max(allStartDates).unix().toString(),
- datetime_end: moment.max(allEndDates).unix().toString(),
- }
+/**
+ * Saves event dates to the custom REST API endpoint.
+ *
+ * Sends event dates to the server for persistence via the Simple Events
+ * REST API sync endpoint. Displays success/error notifications and optionally
+ * refreshes the date manager instance with updated data.
+ *
+ * @since 2.0.0
+ *
+ * @param {Array} dates Array of event date objects to save.
+ * @param {Object|null} dateManagerInstance Optional date manager instance to refresh after save.
+ * @return {Promise} Promise that resolves to the API response object.
+ */
+export const saveEventDates = (dates, dateManagerInstance = null) => {
+ // Get the current post id.
+ const postId = window?.wp?.data?.select('core/editor')?.getCurrentPostId();
+ if (!postId) {
+ return Promise.reject(new Error('No post ID found'));
}
- let startDate = null;
- let endDate = null;
-
- // Loop over the dates and set the start date as the earliest and the end as the latest.
- filteredDates.forEach((date) => {
- const startDateMoment = moment.unix(date.datetime_start).utcOffset(OFFSET);
- const endDateMoment = moment.unix(date.datetime_end).utcOffset(OFFSET);
+ // simple-events/event-dates/{event}
+ return apiFetch({
+ path: '/simple-events/event-dates/' + postId + '/sync',
+ method: 'POST',
+ data: {
+ dates: dates,
+ event_id: postId,
+ nonce: seSettings.syncDatesNonce
+ }
+ }).then((response) => {
+ // Show notification message if available
+ if (response.message) {
+ // Show success notification in bottom left
+ window.wp.data.dispatch('core/notices').createSuccessNotice(
+ response.message,
+ {
+ type: 'snackbar',
+ isDismissible: true,
+ id: 'event-dates-saved'
+ }
+ );
+ }
- // If the end date has passed, skip it.
- if (endDateMoment.isBefore(now)) {
- return;
+ // Refresh dateManager with new dates if available and dateManager instance is provided
+ if (response.dates && dateManagerInstance && dateManagerInstance.refreshWithNewDates) {
+ dateManagerInstance.refreshWithNewDates(response.dates);
}
- /**
- * Closure for setting the start or end date.
- * @param {moment.Moment} startDateMoment
- * @param {moment.Moment} endDateMoment
- */
- const setDate = (startDateMoment, endDateMoment) => {
- // If the start date is before the current start date, set it.
- if (!startDate || startDateMoment.isBefore(startDate) || (startDate.isAfter(startDateMoment) && startDate.isBefore(now))) {
- startDate = startDateMoment;
- }
- // If the end date is after the current end date, set it.
- if (!endDate || endDateMoment.isAfter(endDate)) {
- endDate = endDateMoment;
+
+ return response;
+ }).catch((error) => {
+ console.error('Error saving event dates:', error);
+
+ // Show error notification
+ window.wp.data.dispatch('core/notices').createErrorNotice(
+ __('Failed to save event dates. Please try again.', 'simple-events'),
+ {
+ type: 'snackbar',
+ isDismissible: true,
+ id: 'event-dates-error'
}
- };
+ );
- // If the start date if after now
- if (startDateMoment.isAfter(now) && endDateMoment.isAfter(now)) {
- setDate(startDateMoment, endDateMoment);
- } else if (startDateMoment.isBefore(now) && endDateMoment.isAfter(now)) {
- setDate(startDateMoment, endDateMoment);
- }
+ throw error;
});
+};
- // If we have no startDate or endDate, just get the first from dates.
- if (!startDate) {
- startDate = moment.unix(head(filteredDates).datetime_start).utcOffset(OFFSET);
+/**
+ * Auto-saves event dates when they change.
+ *
+ * Wrapper function for saveEventDates that handles automatic saving
+ * of event dates during user interactions.
+ *
+ * @since 2.0.0
+ *
+ * @param {Array} dates Array of event date objects to auto-save.
+ * @param {Object|null} dateManagerInstance Optional date manager instance to refresh.
+ * @return {Promise} Promise that resolves to the saved event dates response.
+ */
+export const autoSaveEventDates = async (dates, dateManagerInstance = null) => {
+ try {
+ // Save to REST API
+ const savedDates = await saveEventDates(dates, dateManagerInstance);
+
+ return savedDates;
+ } catch (error) {
+ console.error('Error auto-saving event dates:', error);
+ throw error;
}
- if (!endDate) {
- endDate = moment.unix(last(filteredDates).datetime_end).utcOffset(OFFSET);
+};
+
+/**
+ * Saves event dates when the post is being saved.
+ *
+ * Handles saving event dates during WordPress post save operations.
+ * Also updates block attributes to ensure the saved dates with IDs
+ * are properly persisted in the block editor.
+ *
+ * @since 2.0.0
+ *
+ * @param {Array} dates Array of event date objects to save.
+ * @param {Object|null} dateManagerInstance Optional date manager instance to refresh.
+ * @return {Promise} Promise that resolves to the saved event dates response.
+ */
+export const saveEventDatesOnPostSave = async (dates, dateManagerInstance = null) => {
+ try {
+ // Save to REST API
+ const savedDates = await saveEventDates(dates, dateManagerInstance);
+
+ // Update the post meta to ensure the updated dates (with IDs) are persisted
+ const postId = window?.wp?.data?.select('core/editor')?.getCurrentPostId();
+ if (postId && savedDates) {
+
+ // Update the block attributes to ensure the updated dates (with IDs) are persisted
+ const blocks = window.wp.data.select('core/block-editor').getBlocks();
+ const eventInfoBlock = blocks.find(block => block.name === 'simple-events/event-info');
+
+ if (eventInfoBlock) {
+ window.wp.data.dispatch('core/block-editor').updateBlockAttributes(
+ eventInfoBlock.clientId,
+ {
+ eventDates: savedDates.dates || savedDates,
+ }
+ );
+ }
+ }
+
+ return savedDates;
+ } catch (error) {
+ console.error('Error saving event dates on post save:', error);
+ throw error;
+ }
+};
+
+
+// Initialize date manager instance outside the component
+let dateManagerInstance = null;
+let gettingDates = false;
+
+/**
+ * Initializes the date manager with resolved event date posts.
+ *
+ * Creates and configures a date manager instance with event dates fetched
+ * from the REST API. Handles post meta synchronization and timezone settings.
+ * Uses singleton pattern to avoid multiple initializations.
+ *
+ * @since 2.0.0
+ *
+ * @return {Promise} Promise that resolves to the date manager instance or null on error.
+ */
+const initializeDateManager = async () => {
+ if (gettingDates || dateManagerInstance) {
+ return dateManagerInstance;
}
- return {
- datetime_start: startDate.unix().toString(),
- datetime_end: endDate.unix().toString(),
- };
-}
+ gettingDates = true;
+ try {
+ const eventDatePosts = await getEventDatePosts();
+
+ // Get current post meta to pass to dateManager for sync
+ const currentPostId = window?.wp?.data?.select('core/editor')?.getCurrentPostId();
+ const currentMeta = currentPostId ? window?.wp?.data?.select('core/editor')?.getEditedPostAttribute('meta') : {};
+ const currentTimezone = currentMeta?.se_event_timezone || '';
+
+ // Create meta sync object
+ const metaSync = {
+ meta: currentMeta,
+ setMeta: (updates) => {
+ window.wp.data.dispatch('core/editor').editPost({
+ meta: updates
+ });
+ }
+ };
+ dateManagerInstance = dateManager(eventDatePosts, currentTimezone, metaSync);
+ return dateManagerInstance;
+ } catch (error) {
+ console.error('Error initializing date manager:', error);
+ return null;
+ } finally {
+ gettingDates = false;
+ }
+};
/**
- * Register: a Gutenberg Block.
+ * Registers the Event Info Gutenberg block.
+ *
+ * Creates a custom block for event information management including dates,
+ * times, locations, venues, and related settings. The block provides both
+ * edit and preview modes with extensive customization options.
*
- * Registers a new block provided a unique name and an object defining its
- * behavior. Once registered, the block is made editor as an option to any
- * editor interface where blocks are implemented.
+ * @since 1.0.0
*
- * @link https://wordpress.org/gutenberg/handbook/block-api/
- * @param {string} name Block name.
- * @param {Object} settings Block settings.
- * @return {?WPBlock} The block, if it has been successfully
- * registered; otherwise `undefined`.
+ * @see https://wordpress.org/gutenberg/handbook/block-api/
*/
registerBlockType('simple-events/event-info', {
/**
- * The edit function describes the structure of your block in the context of the editor.
- * This represents what the editor will render when the block is used.
+ * Block edit function.
*
- * The "edit" property must be a valid function.
+ * Defines the structure and behavior of the Event Info block in the editor.
+ * Manages event dates through a date manager instance, handles post meta
+ * synchronization, and provides interfaces for editing event information
+ * including dates, times, locations, venues, and display options.
*
- * @link https://wordpress.org/gutenberg/handbook/block-api/block-edit-save/
+ * @since 1.0.0
*
- * @param {Object} props Props.
- * @return {JSX.Element} JSX Component.
+ * @param {Object} props Block properties.
+ * @param {Object} props.attributes Block attributes including editMode and showOnFrontEnd.
+ * @param {Function} props.setAttributes Function to update block attributes.
+ * @return {JSX.Element} The block editor interface.
*/
edit: (props) => {
const { attributes, setAttributes } = props;
@@ -184,79 +307,109 @@ registerBlockType('simple-events/event-info', {
'meta'
);
+ // Add state for loading indication
+ const [isGettingDates, setIsGettingDates] = useState(false);
+ const [dateManagerReady, setDateManagerReady] = useState(false);
+ const [dateManagerState, setDateManagerState] = useState(null);
+ // Add refresh counter to force re-renders when dateManager state changes
+ const [refreshCounter, setRefreshCounter] = useState(0);
+
+ // Watch for post save events
+ const { isSavingPost, isAutosavingPost } = useSelect((select) => {
+ const { isSavingPost, isAutosavingPost } = select('core/editor');
+ return {
+ isSavingPost: isSavingPost(),
+ isAutosavingPost: isAutosavingPost(),
+ };
+ }, []);
+
+ // Trigger date save when post is being saved
+ useEffect(() => {
+ const saveDatesOnPostSave = async () => {
+ if (isSavingPost && !isAutosavingPost && dateManagerState?.getCurrentDates()?.dates) {
+ try {
+ await saveEventDatesOnPostSave(dateManagerState.getCurrentDates().dates, dateManagerState);
+ } catch (error) {
+ console.error('Failed to save event dates on post save:', error);
+ }
+ }
+ };
- // Sets the default timezone for calculations.
-
- let currentTimezone = meta?.se_event_timezone;
- if ('' === currentTimezone) {
- currentTimezone = TIMEZONE;
- }
+ saveDatesOnPostSave();
+ }, [isSavingPost, isAutosavingPost, dateManagerState]);
- const getDstOffset = (timestamp, timezone = null) => {
- // Return no offset if the event timezone is the same as the site.
- if (null === timezone) {
- timezone = currentTimezone;
+ // Sync dateManagerState dates to block attributes
+ useEffect(() => {
+ if (dateManagerState?.getCurrentDates()?.dates) {
+ setAttributes({
+ eventDates: dateManagerState.getCurrentDates().dates
+ });
}
+ }, [dateManagerState, refreshCounter, setAttributes]);
- if ('' === timezone) {
- return OFFSET;
- }
+ // Check if we should be in edit mode based on missing data
+ useEffect(() => {
+ // Only check after dateManager is ready to avoid premature decisions
+ if (dateManagerReady && dateManagerState) {
+ const hasDates = dateManagerState?.getCurrentDates()?.dates &&
+ dateManagerState.getCurrentDates().dates.length > 0;
- // Get the timezone details.
- const timezoneDetails = moment.tz.zone(timezone);
+ // Enter edit mode if we don't have either location/venue AND dates
+ const shouldBeInEditMode = !hasDates;
- // Get the index of the current timezone offset i.e DST or non-DST. -1 at the end to account for search algorithm.
- const untilIndex = timezoneDetails.untils.findIndex(function (
- number
- ) {
- return number / 1000 > timestamp;
- });
+ if (shouldBeInEditMode && !editMode) {
+ setAttributes({ editMode: true });
+ }
+ }
+ }, [dateManagerReady, dateManagerState, meta?.se_event_location, meta?.se_event_venue, editMode, setAttributes]);
+
+ // Initialize date manager on component mount
+ useEffect(() => {
+ const initManager = async () => {
+ setIsGettingDates(true);
+ try {
+ const manager = await initializeDateManager();
+ setDateManagerReady(true);
+ setDateManagerState(manager);
+ } catch (error) {
+ console.error('Failed to initialize date manager:', error);
+ } finally {
+ setIsGettingDates(false);
+ }
+ };
- return timezoneDetails.offsets[untilIndex] * -1;
- };
+ if (!dateManagerReady && !isGettingDates) {
+ initManager();
+ }
+ }, [dateManagerReady, isGettingDates]);
/**
- * Creates a moment in the site timezone from the provided unix timestamp.
+ * Handles the Done button click event.
+ *
+ * Exits edit mode by setting the editMode attribute to false.
*
- * @param {string} timestamp Timestamp to convert to a moment.
- * @param {boolean} formatted Whether to return a human-readable formatted string.
- * @return {Mixed} Human readable formatted string if `formatted` is true,
- * moment object otherwise.
+ * @since 1.0.0
*/
- const getMoment = (timestamp, formatted = false) => {
- const dateTime = moment
- .unix(timestamp)
- .utcOffset(getDstOffset(timestamp));
+ const onDone = () => {
+ setAttributes({ editMode: false });
- if (!formatted) {
- return dateTime;
+ // Update the date attriutes from dateManagerState
+ if (dateManagerState?.getCurrentDates()?.dates) {
+ setAttributes({
+ eventDates: dateManagerState.getCurrentDates().dates,
+ });
}
-
- return dateTime.format(FORMAT);
};
/**
- * Creates a timestamp from the provided date string.
+ * Handles event location input changes.
+ *
+ * Updates the se_event_location meta field when the location input changes.
+ *
+ * @since 1.0.0
*
- * @param {string} dateTime Date string to convert to a timestamp.
- * @return {string} The timestamp, cast as a string.
+ * @param {string} value The new location value.
*/
- const getTimestamp = (dateTime) => {
- return String(
- moment(dateTime)
- .utcOffset(
- getDstOffset(moment(dateTime).unix()),
- true
- )
- .utc()
- .unix()
- );
- };
-
- const onDone = () => {
- setAttributes({ editMode: false });
- };
-
const onChangeEventLocation = (value) => {
setMeta({
...meta,
@@ -264,24 +417,16 @@ registerBlockType('simple-events/event-info', {
});
};
- const maybeUpdateEventDateTime = (oldDate, newDate) => {
- if (!isEqual(oldDate, newDate)) {
- const updatedDates = sortBy(
- meta?.se_event_dates.map((item) =>
- item === oldDate ? newDate : item
- ),
- 'datetime_start'
- );
-
- setMeta({
- ...meta,
- se_event_dates: updatedDates,
- se_event_date_start: getStartAndEndDate(updatedDates).datetime_start,
- se_event_date_end: getStartAndEndDate(updatedDates).datetime_end,
- });
- }
- };
-
+ /**
+ * Renders the block toolbar controls.
+ *
+ * Creates the edit and visibility toggle buttons that appear in the
+ * block toolbar when the block is selected.
+ *
+ * @since 1.0.0
+ *
+ * @return {JSX.Element} The BlockControls component with toolbar buttons.
+ */
const getBlockControls = () => (
);
- const DateTimeGroup = withState({
- tempEventDate: null,
- tempEventTime: null,
- })(
- ({
- eventDateTime,
- removeDate,
- multiDay,
- tempEventDate,
- tempEventTime,
- setState,
- }) => {
- const eventStart = getMoment(
- eventDateTime.datetime_start,
- true
- );
- const eventEnd = getMoment(eventDateTime.datetime_end, true);
- const timeFormat = DATE_SETTINGS.formats.datetime;
-
- // To know if the current timezone is a 12 hour time with look for an "a" in the time format.
- // We also make sure this a is not escaped by a "/".
- const is12HourTime = /a(?!\\)/i.test(
- timeFormat
- .toLowerCase() // Test only the lower case a
- .replace(/\\\\/g, '') // Replace "//" with empty strings
- .split('')
- .reverse()
- .join('') // Reverse the string and test for "a" not followed by a slash
- );
-
- /**
- * Combines a given date and time into a moment object.
- *
- * @param {string} date The date to combine.
- * @param {string} time The time to combine.
- *
- * @return {moment} The combined date and time.
- */
- const combineDateAndTime = (date, time) => {
- const timeMoment = moment(time);
- const dateMoment = moment(date);
-
- // Set the timeMoment's time to the dateMoment.
- return dateMoment.set({
- hour: timeMoment.get('hour'),
- minute: timeMoment.get('minute'),
- });
- };
-
- /**
- * Handle the set date and time button click.
- *
- * @param {boolean} isStartChange Whether this is start or end date change.
- *
- * @return {void}
- */
- const setDateTimeHandler = (isStartChange) => {
- // Ensure we have either new date or time in state.
- if (!tempEventDate && !tempEventTime) {
- return;
- }
-
- const newDate =
- tempEventDate ||
- (isStartChange ? eventStart : eventEnd);
- const newTime =
- tempEventTime ||
- (isStartChange ? eventStart : eventEnd);
-
- // Combine the new date and time and convert to a timestamp.
- const newDateTime = getTimestamp(
- combineDateAndTime(newDate, newTime)
- );
-
- const newEventDateTime = clone(eventDateTime);
-
- if (isStartChange) {
- newEventDateTime.datetime_start = newDateTime;
-
- // Check if the new start time is after the cuurent end time.
- if (
- parseInt(newEventDateTime.datetime_start) >=
- parseInt(newEventDateTime.datetime_end)
- ) {
- // Set the new end time to be 1 hour after the start dateTime.
- newEventDateTime.datetime_end = String(
- parseInt(newEventDateTime.datetime_start) +
- 3600
- );
- }
- } else {
- newEventDateTime.datetime_end = newDateTime;
-
- // Check if the new end time is before the current start time.
- if (
- parseInt(newEventDateTime.datetime_start) >=
- parseInt(newEventDateTime.datetime_end)
- ) {
- // Set the new start time to be 1 hour before the end dateTime.
- newEventDateTime.datetime_start = String(
- parseInt(newEventDateTime.datetime_end) - 3600
- );
- }
- }
-
- // Reset the temp date and time.
- setState({
- tempEventDate: null,
- tempEventTime: null,
- });
-
-
- maybeUpdateEventDateTime(eventDateTime, newEventDateTime);
- };
-
- /**
- * Handles DateTimePicker changes.
- *
- * @param {string} currentDateTime The current dateTime.
- * @param {string} newDateTime The new selected dateTime.
- *
- * @return {void}
- */
- const datePickerHandler = (currentDateTime, newDateTime) => {
- // Compare the date without time to see if the time or date was changed.
- const isDateChange =
- moment(currentDateTime).format('YYYY-MM-DD') ===
- moment(newDateTime).format('YYYY-MM-DD');
- const stateUpdate = isDateChange
- ? { tempEventTime: newDateTime }
- : { tempEventDate: newDateTime };
- setState(stateUpdate);
- };
-
- return (
-
-
-
- (
- {
- onToggle();
- }}
- aria-expanded={isOpen}
- >
- {eventDateTime.all_day
- ? wp.date.format(
- 'F j, Y',
- eventStart
- )
- : wp.date.format(
- timeFormat,
- eventStart
- )}
-
- )}
- renderContent={() => (
-
-
- datePickerHandler(
- eventStart,
- newDateTime
- )
- }
- __nextRemoveHelpButton
- __nextRemoveResetButton
- />
-
- setDateTimeHandler(true)
- }
- variant="secondary"
- >
- {__(
- 'Set time',
- 'simple-events'
- )}
-
-
- )}
- />
-
-
- (
- {
- onToggle();
- }}
- aria-expanded={isOpen}
- disabled={eventDateTime.all_day}
- >
- {eventDateTime.all_day
- ? '--:--'
- : wp.date.format(
- timeFormat,
- eventEnd
- )}
-
- )}
- renderContent={() => (
-
-
- datePickerHandler(
- eventEnd,
- newDateTime
- )
- }
- __nextRemoveHelpButton
- __nextRemoveResetButton
- />
-
- setDateTimeHandler(false)
- }
- variant="secondary"
- text={__(
- 'Set time',
- 'simple-events'
- )}
- />
-
- )}
- />
-
-
- {
- const newEventDateTime =
- clone(eventDateTime);
-
- newEventDateTime.all_day =
- !eventDateTime.all_day;
-
- newEventDateTime.datetime_start =
- getMoment(
- newEventDateTime.datetime_start
- );
- newEventDateTime.datetime_end =
- getMoment(
- newEventDateTime.datetime_end
- );
-
- // If all day event, set time between 00:00 and 23:59.
- if (newEventDateTime.all_day) {
- newEventDateTime.datetime_start.startOf(
- 'date'
- );
- newEventDateTime.datetime_end.endOf(
- 'date'
- );
- } else {
- newEventDateTime.datetime_start
- .hour(DEFAULT_START_HOUR)
- .minute(0);
- newEventDateTime.datetime_end
- .hour(DEFAULT_END_HOUR)
- .minute(0);
- }
-
- newEventDateTime.datetime_start =
- getTimestamp(
- newEventDateTime.datetime_start
- );
- newEventDateTime.datetime_end =
- getTimestamp(
- newEventDateTime.datetime_end
- );
-
- const updatedDates = sortBy(
- meta?.se_event_dates.map((item) =>
- item === eventDateTime
- ? newEventDateTime
- : item
- ),
- 'datetime_start'
- );
-
- setMeta({
- ...meta,
- se_event_dates: updatedDates,
- se_event_date_start: getStartAndEndDate(updatedDates).datetime_start,
- se_event_date_end: getStartAndEndDate(updatedDates).datetime_end,
- });
- }}
- />
-
- {multiDay && (
-
-
- removeDate(eventDateTime)
- }
- />
-
- )}
-
-
- );
+ // Wrapper functions to trigger re-renders when dateManager state changes
+ /**
+ * Handles adding a new date to the event.
+ *
+ * Calls the date manager's addDate method and triggers a re-render
+ * by incrementing the refresh counter.
+ *
+ * @since 2.0.0
+ */
+ const handleAddDate = () => {
+ if (dateManagerState?.addDate) {
+ dateManagerState.addDate();
+ setRefreshCounter(prev => prev + 1);
}
- );
-
- const EventDateTime = ({ dates }) => {
- const addNewDate = () => {
- const existingDates =
- !dates || 0 === dates.length ? [] : dates;
-
- // Set default date and time.
- let eventStart = moment().utcOffset(OFFSET);
-
- eventStart.hour(DEFAULT_START_HOUR);
- eventStart.minute(0);
- eventStart.second(0);
-
- let eventEnd = eventStart.clone();
-
- eventEnd.hour(DEFAULT_END_HOUR);
-
- // Override with existing date if there is one.
- if (existingDates.length) {
- eventStart = getMoment(
- last(existingDates).datetime_start
- );
- eventEnd = getMoment(last(existingDates).datetime_end);
- }
-
- // Set default date to be +1 day from the last date.
- eventStart.add(1, 'days');
- eventEnd.add(1, 'days');
-
- const updatedDates = sortBy(
- [
- ...existingDates,
- {
- datetime_start: wp.date.date('U', eventStart),
- datetime_end: wp.date.date('U', eventEnd),
- all_day: false,
- },
- ],
- 'datetime_start'
- );
+ };
- setMeta({
- ...meta,
- se_event_dates: updatedDates,
- se_event_date_start: getStartAndEndDate(updatedDates).datetime_start,
- se_event_date_end: getStartAndEndDate(updatedDates).datetime_end,
- });
- };
+ /**
+ * Handles reverting date changes.
+ *
+ * Calls the date manager's revertDates method and triggers a re-render
+ * by incrementing the refresh counter.
+ *
+ * @since 2.0.0
+ */
+ const handleRevertDates = () => {
+ if (dateManagerState?.revertDates) {
+ dateManagerState.revertDates();
+ setRefreshCounter(prev => prev + 1);
+ }
+ };
- const removeDate = (date) => {
- if (!dates.length) {
- return;
+ // Create enhanced dateManagerInstance that triggers re-renders
+ const enhancedDateManagerInstance = dateManagerState ? {
+ ...dateManagerState,
+ upsertDate: (date) => {
+ const result = dateManagerState.upsertDate(date);
+ setRefreshCounter(prev => prev + 1);
+ return result;
+ },
+ removeDate: (date) => {
+ const result = dateManagerState.removeDate(date);
+ setRefreshCounter(prev => prev + 1);
+ return result;
+ },
+ addDate: () => {
+ const result = dateManagerState.addDate();
+ setRefreshCounter(prev => prev + 1);
+ return result;
+ },
+ revertDates: () => {
+ const result = dateManagerState.revertDates();
+ setRefreshCounter(prev => prev + 1);
+ return result;
+ },
+ refreshWithNewDates: (newDates) => {
+ if (dateManagerState.refreshWithNewDates) {
+ dateManagerState.refreshWithNewDates(newDates);
+ setRefreshCounter(prev => prev + 1);
}
+ },
+ updateTimezone: (newTimezone) => {
+ const result = dateManagerState.updateTimezone(newTimezone);
+ setRefreshCounter(prev => prev + 1);
+ return result;
+ }
+ } : null;
- const updatedDates = pull(dates, date);
+ /**
+ * Renders the unsaved changes warning component.
+ *
+ * Displays a warning message when the date manager has unsaved changes,
+ * informing the user that they need to save the post to persist changes.
+ *
+ * @since 2.0.0
+ *
+ * @return {JSX.Element|null} Warning component or null if no unsaved changes.
+ */
+ const UnsavedChangesWarning = () => {
+ if (!dateManagerState?.getCurrentDates()?.isDirty) {
+ return null;
+ }
- setMeta({
- ...meta,
- se_event_dates: updatedDates,
- se_event_date_start: getStartAndEndDate(updatedDates).datetime_start,
- se_event_date_end: getStartAndEndDate(updatedDates).datetime_end,
- });
- };
+ return (
+
+
+
+ {__('Unsaved Changes', 'simple-events')}
+
+
+ {__('You have unsaved date and timezone changes. Save the post to persist these changes.', 'simple-events')}
+
+
+
+ );
+ };
- // If no dates, add a date.
- if (!dates || 0 === dates.length) {
- addNewDate();
- }
+ /**
+ * Renders the event date/time component.
+ *
+ * Creates a list of DateTimeGroupNew components for each event date,
+ * sorted by start date. Each component allows editing of individual
+ * date and time information.
+ *
+ * @since 2.0.0
+ *
+ * @param {Object} props Component properties.
+ * @param {Array} props.dates Array of event date objects to render.
+ * @param {number} props.refreshCounter Counter to force component re-renders.
+ * @return {JSX.Element} Fragment containing the event dates interface.
+ */
+ const EventDateTime = ({ dates, refreshCounter }) => {
const datesOutput = [];
- sortBy(dates, 'datetime_start').forEach((date, index) => {
+ sortBy(dates, 'start_date').forEach((date, index) => {
datesOutput.push(
- 1}
+ removeDate={null}
+ hasMultipleDates={dates.length > 1}
+ currentTimezone={dateManagerState?.getCurrentDates()?.timezone ?? meta?.se_event_timezone ?? TIMEZONE}
+ dateManagerInstance={enhancedDateManagerInstance}
/>
);
});
@@ -730,46 +600,49 @@ registerBlockType('simple-events/event-info', {
return (
- {__('Date & Time', 'simple-events')}
+ {__('Event Dates (New)', 'simple-events')}
{datesOutput}
-
- addNewDate()}>
- {__('+ Add another date', 'simple-events')}
-
-
+
);
};
+ /**
+ * Renders the block preview mode.
+ *
+ * Displays the front-end representation of the block using ServerSideRender
+ * with the current event data. Includes block controls and unsaved changes warning.
+ *
+ * @since 1.0.0
+ *
+ * @return {JSX.Element} The preview component with ServerSideRender.
+ */
const renderPreview = () => (
{getBlockControls()}
+
);
- // Show editMode if no location or date set.
- if (
- meta?.se_event_location.length === 0 &&
- (!meta?.se_event_dates || !meta?.se_event_dates?.length)
- ) {
- setAttributes({ editMode: true });
- }
-
if (!editMode) {
return renderPreview();
}
@@ -783,7 +656,35 @@ registerBlockType('simple-events/event-info', {
isColumnLayout
className={props.className}
>
-
+
+
+
+ {/* Button container with 50/50 layout */}
+
+
+
+
{
- const updatedDates = clone(
- meta?.se_event_dates ?? []
- );
-
- // Ensure that the value is a string.
- value = !Boolean(value) ? '' : value;
- currentTimezone = value;
-
- if ('' === value) {
- currentTimezone = TIMEZONE;
+ if (enhancedDateManagerInstance?.updateTimezone) {
+ enhancedDateManagerInstance.updateTimezone(value);
+ setRefreshCounter(prev => prev + 1);
}
-
- updatedDates.forEach((eventDateTime) => {
- [
- 'datetime_start',
- 'datetime_end',
- ].forEach((key) => {
- const dateTime = moment
- .unix(eventDateTime[key])
- .utcOffset(
- getDstOffset(
- eventDateTime[key],
- meta?.se_event_timezone
- )
- );
-
- const newOffset =
- '' !== currentTimezone
- ? getDstOffset(
- eventDateTime[key],
- currentTimezone
- )
- : OFFSET;
-
- eventDateTime[key] = String(
- dateTime
- .utcOffset(newOffset, true)
- .utc()
- .unix()
- );
- });
- });
-
- setMeta({
- ...meta,
- se_event_dates: updatedDates,
- se_event_date_start: getStartAndEndDate(updatedDates).datetime_start,
- se_event_date_end: getStartAndEndDate(updatedDates).datetime_end,
- se_event_timezone: value,
- });
}}
/>
{
return null;
diff --git a/src/blocks/event-tickets/style.scss b/src/blocks/event-tickets/style.scss
index c84ff66..bc480f9 100644
--- a/src/blocks/event-tickets/style.scss
+++ b/src/blocks/event-tickets/style.scss
@@ -1,42 +1,52 @@
.wp-block-se-event-tickets {
- &__heading {
- margin: 0 0 32px;
- }
+ &__heading {
+ margin: 0 0 32px;
+ }
+ &__ticket-row {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ align-items: center;
+ width: 100%;
+ margin: 0 0 16px;
+ }
+ &__ticket-column {
+ display: flex;
+ flex-direction: column;
+ flex-basis: 100%;
+ &--title {
+ flex: 3;
+ }
+ &--price {
+ flex: 2;
+ }
+ &--buy {
+ flex: 1;
+ }
+ }
+ &__ticket-stock {
+ display: block;
+ opacity: 0.6;
+ font-size: .75em;
+ }
+ &__button {
+ text-align: center;
+ }
+}
- &__ticket-row {
- display: flex;
- flex-direction: row;
- flex-wrap: wrap;
- align-items: center;
- width: 100%;
- margin: 0 0 16px;
- }
- &__ticket-column {
- display: flex;
- flex-direction: column;
- flex-basis: 100%;
+/* Grouped date list styles */
- &--title {
- flex: 3;
- }
+.se-event-date-list-item__grouped {
+ display: inline-flex;
+ gap: 16px;
+ /* 16px spacing between date and time */
+}
- &--price {
- flex: 2;
- }
+.se-event-date-list-item__grouped-date {
+ /* Date styling can be added here if needed */
+}
- &--buy {
- flex: 1;
- }
- }
-
- &__ticket-stock {
- display: block;
- opacity: 0.6;
- font-size: .75em;
- }
-
- &__button {
- text-align: center;
- }
+.se-event-date-list-item__grouped-time {
+ /* Time styling can be added here if needed */
}
\ No newline at end of file
diff --git a/src/blocks/loop-event-info/block.json b/src/blocks/loop-event-info/block.json
index 7a6d35f..cf16fce 100644
--- a/src/blocks/loop-event-info/block.json
+++ b/src/blocks/loop-event-info/block.json
@@ -1,61 +1,69 @@
{
- "$schema": "https://schemas.wp.org/trunk/block.json",
- "apiVersion": 2,
- "name": "simple-events/loop-event-info",
- "title": "Event Metadata",
- "description": "Display event meta in a custom query loop.",
- "icon": "tag",
- "category": "simple-events",
- "keywords": ["event info", "date", "location", "Simple Events", "meta"],
- "usesContext": ["postId"],
- "attributes": {
- "textAlign": {
- "type": "string",
- "default": "left"
- },
- "thePostId": {
- "type": "integer",
- "default": 0
- },
- "metaName": {
- "enum": ["location", "venue", "dates", "date", "time"],
- "type": "string",
- "default": "dates"
- },
- "metaPrefix": {
- "type": "string",
- "default": ""
- },
- "addCalendarLinks": {
- "type": "boolean"
- }
- },
- "supports": {
- "html": false,
- "align": true,
- "typography": {
- "fontSize": true,
- "lineHeight": true,
- "__experimentalFontFamily": true,
- "__experimentalFontWeight": true,
- "__experimentalFontStyle": true,
- "__experimentalTextTransform": true,
- "__experimentalTextDecoration": true,
- "__experimentalLetterSpacing": true,
- "__experimentalDefaultControls": {
- "fontSize": true
- }
- },
- "spacing": {
- "margin": true,
- "padding": true,
- "blockGap": true
- },
- "color": {
- "text": true
- }
- },
- "editorScript": "file:./index.js",
- "editorStyle": "file:./editor.css",
- "style": "file:./style.css"
-}
+ "$schema": "https://schemas.wp.org/trunk/block.json",
+ "apiVersion": 2,
+ "name": "simple-events/loop-event-info",
+ "title": "Event Metadata",
+ "description": "Display event meta in a custom query loop.",
+ "icon": "tag",
+ "category": "simple-events",
+ "keywords": ["event info", "date", "location", "Simple Events", "meta"],
+ "usesContext": ["postId"],
+ "attributes": {
+ "textAlign": {
+ "type": "string",
+ "default": "left"
+ },
+ "thePostId": {
+ "type": "integer",
+ "default": 0
+ },
+ "metaName": {
+ "enum": ["location", "venue", "dates", "date", "time"],
+ "type": "string",
+ "default": "dates"
+ },
+ "metaPrefix": {
+ "type": "string",
+ "default": ""
+ },
+ "addCalendarLinks": {
+ "type": "boolean"
+ },
+ "feedType": {
+ "type": "string",
+ "default": "default"
+ },
+ "order": {
+ "type": "string",
+ "default": "asc"
+ }
+ },
+ "supports": {
+ "html": false,
+ "align": true,
+ "typography": {
+ "fontSize": true,
+ "lineHeight": true,
+ "__experimentalFontFamily": true,
+ "__experimentalFontWeight": true,
+ "__experimentalFontStyle": true,
+ "__experimentalTextTransform": true,
+ "__experimentalTextDecoration": true,
+ "__experimentalLetterSpacing": true,
+ "__experimentalDefaultControls": {
+ "fontSize": true
+ }
+ },
+ "spacing": {
+ "margin": true,
+ "padding": true,
+ "blockGap": true
+ },
+ "color": {
+ "text": true
+ }
+ },
+ "editorScript": "file:./index.js",
+ "editorStyle": "file:./editor.css",
+ "style": "file:./style.css"
+}
\ No newline at end of file
diff --git a/src/blocks/loop-event-info/index.js b/src/blocks/loop-event-info/index.js
index e0bdfe6..1722c44 100644
--- a/src/blocks/loop-event-info/index.js
+++ b/src/blocks/loop-event-info/index.js
@@ -17,9 +17,40 @@ import {
InspectorControls,
BlockControls,
} from '@wordpress/block-editor';
+import { useSelect } from '@wordpress/data';
+import { useEffect } from '@wordpress/element';
registerBlockType(metadata, {
- edit: ({ attributes: { metaName, metaPrefix, thePostId, textAlign, addCalendarLinks }, setAttributes, context: { postId } }) => {
+ edit: ({ attributes: { metaName, metaPrefix, thePostId, textAlign, addCalendarLinks, feedType, order }, setAttributes, context: { postId }, clientId }) => {
+
+ // Get query loop data from our custom store
+ const queryData = useSelect((select) => {
+ const blockEditor = select('core/block-editor');
+ const parents = blockEditor.getBlockParents(clientId);
+
+ // Find the query block parent
+ for (const parentId of parents) {
+ const parentBlock = blockEditor.getBlock(parentId);
+ if (parentBlock && parentBlock.name === 'core/query') {
+ const storeData = select('se-events/query-data').getQueryData(parentId);
+ return storeData || {};
+ }
+ }
+ return {};
+ }, [clientId]);
+
+ const { feedType: contextFeedType = feedType, order: contextOrder = order } = queryData;
+
+ // Update block attributes when context values change
+ useEffect(() => {
+ if (contextFeedType !== feedType || contextOrder !== order) {
+ setAttributes({
+ feedType: contextFeedType,
+ order: contextOrder,
+ });
+ }
+ }, [contextFeedType, contextOrder, feedType, order, setAttributes]);
+
return (
<>
@@ -75,6 +106,8 @@ registerBlockType(metadata, {
textAlign,
thePostId: postId, // Passes the current post ID to the render callback, even if in a query loop.
addCalendarLinks,
+ feedType, // Use block attribute values
+ order, // Use block attribute values
}}
/>
diff --git a/src/classes/class-date-display-formatter.php b/src/classes/class-date-display-formatter.php
new file mode 100644
index 0000000..5d87ee1
--- /dev/null
+++ b/src/classes/class-date-display-formatter.php
@@ -0,0 +1,671 @@
+ false,
+ 'allow_grouping_dates_different_time' => false,
+ )
+ );
+ $this->event_id = $event_id;
+ $this->treat_each_date_as_own_event = isset( $options['treat_each_date_as_own_event'] ) && 'on' === $options['treat_each_date_as_own_event'];
+ $this->allow_grouping_dates_different_time = isset( $options['allow_grouping_dates_different_time'] ) && 'on' === $options['allow_grouping_dates_different_time'];
+ $this->group_dates = filter_var( get_post_meta( $event_id, 'se_event_display_grouped', true ), FILTER_VALIDATE_BOOLEAN );
+ $this->is_single_view = is_single();
+ $this->event_timezone = get_post_meta( $event_id, 'se_event_timezone', true );
+ $this->event_date_id = se_template_get_event_date_id();
+ $this->display_timezone = filter_var( get_post_meta( $event_id, 'se_event_display_timezone', true ), FILTER_VALIDATE_BOOLEAN );
+ $this->hide_end_time = filter_var( get_post_meta( $event_id, 'se_event_hide_end_time', true ), FILTER_VALIDATE_BOOLEAN );
+ $this->hide_start_time = filter_var( get_post_meta( $event_id, 'se_event_hide_start_time', true ), FILTER_VALIDATE_BOOLEAN );
+ $this->show_add_to_calendar = filter_var( get_post_meta( $event_id, 'se_event_add_calendar_links', true ), FILTER_VALIDATE_BOOLEAN );
+ $this->open_in_new_tab = filter_var( get_post_meta( $event_id, 'se_event_open_in_new_window', true ), FILTER_VALIDATE_BOOLEAN );
+ }
+
+ /**
+ * Set the date only.
+ *
+ * @param boolean $date_only The date only.
+ *
+ * @return void
+ */
+ public function set_date_only( bool $date_only = true ) {
+ $this->date_only = $date_only;
+ }
+
+ /**
+ * Set the time only.
+ *
+ * @param boolean $time_only The time only.
+ *
+ * @return void
+ */
+ public function set_time_only( bool $time_only = true ) {
+ $this->time_only = $time_only;
+ }
+
+ /**
+ * Modify Timezone.
+ *
+ * @param string $timezone The timezone.
+ *
+ * @return void
+ */
+ public function modify_timezone( $timezone ): void {
+ if ( ! empty( $timezone ) ) {
+ $this->event_timezone = $timezone;
+ }
+ }
+
+ /**
+ * Has event date id.
+ *
+ * @return boolean
+ */
+ public function has_event_date_in_url() {
+ return $this->event_date_id > 0;
+ }
+
+ /**
+ * Treat each date as own event for navigation.
+ *
+ * @return boolean
+ */
+ public function is_treating_each_date_as_own_event() {
+ return $this->treat_each_date_as_own_event;
+ }
+
+
+ /**
+ * Get the date range for the event.
+ *
+ * @param array $event_dates Event dates.
+ *
+ * @return array{start_date: string, end_date: string}
+ */
+ public function get_date_range( array $event_dates ) {
+ $start = null;
+ $end = null;
+
+ // Loop over each date.
+ foreach ( $event_dates as $date ) {
+ if ( $start === null || $date['start_date'] < $start ) { // phpcs:ignore
+ $start = $date['start_date'];
+ }
+
+ if ( $end === null || $date['end_date'] > $end ) { // phpcs:ignore
+ $end = $date['end_date'];
+ }
+
+ // If all day and start is after the latest end date, set the end date to the start date.
+ if ( $date['all_day'] && $date['start_date'] > $end ) { // phpcs:ignore
+ $end = $date['start_date'];
+ }
+ }
+
+ return array(
+ 'start_date' => $start,
+ 'end_date' => $end,
+ );
+ }
+
+ /**
+ * Gets the header date for the event.
+ *
+ * @param array $event_dates Event dates.
+ *
+ * @return string
+ */
+ public function get_header_date( array $event_dates ) {
+ // If we are treating each date as it own.
+ if ( $this->treat_each_date_as_own_event && $this->event_date_id ) {
+ $found_date = array_filter(
+ $event_dates,
+ function ( $date ) {
+ return $date['id'] === $this->event_date_id;
+ }
+ );
+
+ if ( $found_date ) {
+ return $this->render_single_date( $found_date[0] );
+ }
+ }
+ // If we are grouping dates, return the first date.
+ $date_range = $this->get_date_range( $event_dates );
+ $cloned = $event_dates[0];
+ $cloned['start_date'] = $date_range['start_date'];
+ $cloned['end_date'] = $date_range['end_date'];
+ $cloned['id'] = $this->event_id;
+ return $this->render_single_date( $cloned );
+ }
+
+ /**
+ * Render active date.
+ *
+ * @param array $event_dates Event dates.
+ *
+ * @return string|null
+ */
+ public function render_active_date( array $event_dates ) {
+ // If we dont have an event date id, return the first date.
+ if ( ! $this->event_date_id ) {
+ return null;
+ }
+
+ // If we are not treating each date as it own, return null.
+ if ( ! $this->treat_each_date_as_own_event ) {
+ return null;
+ }
+
+ // Find the date in the event dates.
+ $found_date = array_filter(
+ $event_dates,
+ function ( $date ) {
+ return isset( $date['id'] ) && $date['id'] === $this->event_date_id;
+ }
+ );
+
+ // If we found the date, return it.
+ if ( $found_date ) {
+ return $this->render_single_date( array_values( $found_date )[0] );
+ }
+
+ return null;
+ }
+
+ /**
+ * Renders a date list.
+ *
+ * @param array $event_dates Event dates.
+ * @param boolean $exclude_current_date Exclude the current date.
+ * @param boolean $exclude_past_dates Exclude dates that are in the past.
+ *
+ * @return string
+ */
+ public function render_date_list( array $event_dates, bool $exclude_current_date = false, bool $exclude_past_dates = false ) {
+ // Filter the event dates.
+ $event_dates = array_filter(
+ $event_dates,
+ function ( $date ) use ( $exclude_current_date, $exclude_past_dates ) {
+ // If the date is the current date, exclude it.
+ if ( $exclude_current_date && $this->event_date_id && $date['id'] === $this->event_date_id ) {
+ return false;
+ }
+
+ // If the date is in the past, exclude it.
+ if ( $exclude_past_dates && $date['start_date'] < SE_Calendar::get_instance()->create_date_time( 'now' )->format( 'U' ) ) {
+ return false;
+ }
+
+ return true;
+ }
+ );
+
+ // Sort by the start date.
+ usort(
+ $event_dates,
+ function ( $a, $b ) {
+ return $a['start_date'] - $b['start_date'];
+ }
+ );
+
+ // Get the date count.
+ $dates_count = count( $event_dates );
+
+ // If there is only one date, return the single date.
+ if ( 1 === $dates_count ) {
+ return sprintf( '', $this->render_single_date( $event_dates[0] ) );
+ }
+
+ // Start building the output.
+ $wrapper_class = array( 'se-event-date-list', $this->group_dates ? 'se-event-date-list__grouped' : '', $this->event_date_id ? 'se-event-date-list__active' : '' );
+ $output = sprintf( '', implode( ' ', $wrapper_class ) );
+ // Base if we are grouped, or not.
+ if ( $this->can_group_dates( $event_dates ) ) {
+ $output .= $this->render_date_list_grouped( $event_dates );
+ } else {
+ $output .= $this->render_date_list_ungrouped( $event_dates );
+ }
+
+ $output .= ' ';
+
+ return $output;
+ }
+
+ /**
+ * Checks if the dates can be grouped.
+ *
+ * @param array $event_dates Event dates.
+ *
+ * @return boolean
+ */
+ private function can_group_dates( array $event_dates ) {
+ // If we are not grouping dates, return false.
+ if ( ! $this->group_dates ) {
+ return false;
+ }
+
+ $times = array();
+
+ foreach ( $event_dates as $date ) {
+ $index = $date['all_day'] ? 'all_day' : $this->format_time( $date['start_date'] ) . ' - ' . $this->format_time( $date['end_date'] );
+
+ // If this index is not in the array, add it.
+ if ( ! in_array( $index, $times, true ) ) {
+ $times[] = $index;
+ }
+
+ // If have more than one time and do not allow_grouping_dates_different_time, return false.
+ if ( count( $times ) > 1 && ! $this->allow_grouping_dates_different_time ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Renders the list of date as single items (not grouped view)
+ *
+ * @param array $event_dates Event dates.
+ * @param string $existing_output Existing output.
+ *
+ * @return string
+ */
+ private function render_date_list_ungrouped( array $event_dates, string $existing_output = '' ) {
+ foreach ( $event_dates as $date ) {
+ // If we dont have an id on date, set as null.
+ if ( ! isset( $date['id'] ) ) {
+ $date['id'] = null;
+ }
+ $item_class = array( 'se-event-date-list-item', $date['id'] === $this->event_date_id ? 'se-event-date-list-item__active' : '' );
+ $existing_output .= sprintf( '%s ', $date['id'], implode( ' ', $item_class ), $this->render_single_date( $date ) );
+ }
+
+ return $existing_output;
+ }
+
+ /**
+ * Renders the list of date as grouped items (grouped view)
+ *
+ * @param array $event_dates Event dates.
+ * @param string $existing_output Existing output.
+ *
+ * @return string
+ */
+ private function render_date_list_grouped( array $event_dates, string $existing_output = '' ) {
+ $groups = array();
+
+ // iterate over the dates and group them by the start and end times.
+ foreach ( $event_dates as $date ) {
+ // If the dates all_day is a string, convert 'true' to true.
+ if ( is_string( $date['all_day'] ) ) {
+ $date['all_day'] = 'true' === $date['all_day'] ? true : false;
+ }
+
+ // If this event is all day.
+ if ( true === (bool) $date['all_day'] ) {
+ $groups['all_day'][] = $date;
+ continue;
+ }
+ // Convert the start and end times,/
+ $start = $this->format_time( $date['start_date'] );
+ $end = $this->format_time( $date['end_date'] );
+
+ // Add the date to the group.
+ $groups[ $start . ' - ' . $end ][] = $date;
+ }
+ // Iterate over each group, and break them down to the starting month.
+ foreach ( $groups as $group ) {
+ // Create the time label.
+ $time_label = $group[0]['all_day'] ? SE_Settings::get_all_day_message() : null;
+ if ( ! $time_label ) {
+ $time_start = ( $this->hide_start_time ) ? '' : $this->format_time( $group[0]['start_date'] );
+ $time_end = ( $this->hide_end_time ) ? '' : $this->format_time( $group[0]['end_date'] );
+ // Join using &ndash if we have a start and end time.
+ if ( ! empty( $time_start ) && ! empty( $time_end ) ) {
+ $time_label = $time_start . ' – ' . $time_end;
+ } elseif ( ! empty( $time_start ) ) {
+ $time_label = $time_start;
+ } elseif ( ! empty( $time_end ) ) {
+ $time_label = $time_end;
+ } else {
+ $time_label = '';
+ }
+ }
+
+ $dates = array();
+ foreach ( $group as $date ) {
+ // Get 2020-12 for the start date.
+ $month_year = wp_date( 'Y-m', $date['start_date'], $this->get_timezone_instance() );
+ $same_day = wp_date( 'Y-m-d', $date['start_date'], $this->get_timezone_instance() ) === wp_date( 'Y-m-d', $date['end_date'], $this->get_timezone_instance() );
+ $dates[ $month_year ][] = array(
+ 'date' => $date,
+ 'same_day' => $same_day,
+ 'display_date' => $same_day ? $this->format_date( $date['start_date'] ) : $this->format_date( $date['start_date'] ) . ' – ' . $this->format_date( $date['end_date'] ),
+ );
+ }
+
+ foreach ( $dates as $month_dates ) {
+ $dates_string = $this->join_string( array_column( $month_dates, 'display_date' ), ', ', ' and ' );
+
+ // Lets start compiling the output.
+ $output = '';
+ // If the date is on the same day, we can just render the date.
+ $output .= '';
+ // Add the date.
+ $output .= '
';
+ $output .= $this->time_only ? '' : $dates_string;
+ $output .= '
';
+
+ // Add the time.
+ $output .= '
';
+ $output .= $this->date_only ? '' : $time_label;
+ $output .= '
';
+
+ $output .= '
';
+ $output .= ' ';
+ $existing_output .= $output;
+ }
+ }
+
+ return $existing_output;
+ }
+
+ /**
+ * Join a string with differing separators a the then
+ *
+ * Example: join_string(['a','b','c'], ',', ' and ') => 'a, b and c'
+ *
+ * @param string[] $items The items to join.
+ * @param string $separator The separator to use.
+ * @param string $separator_end The final separator to use.
+ *
+ * @return string
+ */
+ private function join_string( array $items, string $separator, string $separator_end ) {
+ // If arrray only contains one item, return it.
+ if ( count( $items ) === 1 ) {
+ return $items[0];
+ }
+
+ // If array contains two items, return them joined by the separator.
+ if ( count( $items ) === 2 ) {
+ return implode( $separator_end, $items );
+ }
+
+ // Remove the last item.
+ $last_item = array_pop( $items );
+
+ // Join the items with the separator.
+ $output = implode( $separator, $items );
+
+ // Add the separator_end to the last item.
+ return $output . $separator_end . $last_item;
+ }
+
+ /**
+ * Formats the dates for the event.
+ *
+ * @param array $event_dates Event dates.
+ *
+ * @return string
+ */
+ public function format_dates( array $event_dates ) {
+ // Reset indexes
+ $event_dates = array_values( $event_dates );
+
+ // Sort all dates by start date.
+ usort(
+ $event_dates,
+ function ( $a, $b ) {
+ return $a['start_date'] - $b['start_date'];
+ }
+ );
+
+ // Get the date count.
+ $dates_count = count( $event_dates );
+
+ // If there is only one date, return the single date.
+ if ( 1 === $dates_count ) {
+ return sprintf( '', $this->render_single_date( $event_dates[0] ) );
+ }
+
+ // Start building the output.
+ $wrapper_class = array( 'se-event-date-list', $this->group_dates ? 'se-event-date-list__grouped' : '', $this->event_date_id ? 'se-event-date-list__active' : '' );
+ $output = sprintf( '', implode( ' ', $wrapper_class ) );
+
+ // Loop over each date.
+ foreach ( $event_dates as $date ) {
+ $item_class = array( 'se-event-date-list-item', $date['id'] === $this->event_date_id ? 'se-event-date-list-item__active' : '' );
+ $output .= sprintf( '%s ', $date['id'], implode( ' ', $item_class ), $this->render_single_date( $date ) );
+ }
+
+ $output .= ' ';
+
+ return $output;
+ }
+
+ /**
+ * Get the posts timezone instance.
+ *
+ * @return DateTimeZone
+ */
+ private function get_timezone_instance() {
+ return '' !== $this->event_timezone ? new DateTimeZone( $this->event_timezone ) : wp_timezone();
+ }
+
+ /**
+ * Get the timezone abbreviation.
+ *
+ * @return string
+ */
+ private function get_timezone_abbreviation() {
+ $timezone_date = new DateTime( '', $this->get_timezone_instance() );
+ return $timezone_date->format( 'T' );
+ }
+
+ /**
+ * Formats a date to the sites tiemzone and date format.
+ *
+ * @param integer $date_timestamp The date timestamp.
+ *
+ * @return string
+ */
+ public function format_date( $date_timestamp ) {
+ return wp_date( get_option( 'date_format' ), $date_timestamp, $this->get_timezone_instance() );
+ }
+
+ /**
+ * Formats a time to the sites tiemzone and time format.
+ *
+ * @param integer $time_timestamp The time timestamp.
+ *
+ * @return string
+ */
+ public function format_time( $time_timestamp ) {
+ return wp_date( get_option( 'time_format' ), $time_timestamp, $this->get_timezone_instance() );
+ }
+
+ /**
+ * Renders a single date.
+ *
+ * @param array $event_date The event date.
+ *
+ * @return string
+ */
+ public function render_single_date( $event_date ) {
+ // Check if the event starts and ends on the same day.
+ $same_day = wp_date( 'Y-m-d', $event_date['start_date'], $this->get_timezone_instance() ) === wp_date( 'Y-m-d', $event_date['end_date'], $this->get_timezone_instance() );
+
+ // Get start and end times.
+ $time_start = ( $this->hide_start_time || $this->date_only ) ? '' : $this->format_time( $event_date['start_date'] );
+ $time_end = ( $this->hide_end_time || $this->date_only ) ? '' : $this->format_time( $event_date['end_date'] );
+
+ // Get the start and end date.
+ $start_date = $this->time_only ? '' : $this->format_date( $event_date['start_date'] );
+ $end_date = $this->time_only ? '' : $this->format_date( $event_date['end_date'] );
+
+ // Check if it's an all day event.
+ $is_all_day = array_key_exists( 'all_day', $event_date ) ? filter_var( $event_date['all_day'], FILTER_VALIDATE_BOOLEAN ) : false;
+
+ // Start building the output.
+ $output = $start_date;
+
+ // Handle different cases based on whether it's same day, all day, etc.
+ if ( $is_all_day ) {
+
+ // For all day events, just show the date (or date range if different days).
+ if ( ! $same_day ) {
+ $output .= ' – ' . $end_date . ' ' . SE_Settings::get_all_day_message();
+ } else {
+ $output .= ' ' . SE_Settings::get_all_day_message();
+ }
+ } elseif ( $same_day ) {
+ // Same day event with times.
+ $time_parts = array();
+ if ( ! $this->hide_start_time && ! empty( $time_start ) ) {
+ $time_parts[] = $time_start;
+ }
+ if ( ! $this->hide_end_time && ! empty( $time_end ) && $time_start !== $time_end ) {
+ $time_parts[] = $time_end;
+ }
+
+ if ( ! empty( $time_parts ) ) {
+ $output .= ' ' . implode( ' – ', $time_parts );
+ }
+ } else {
+ // Multi-day event with times.
+ if ( ! $this->hide_start_time && ! empty( $time_start ) ) {
+ $output .= ' ' . $time_start;
+ }
+ $output .= ' – ' . $end_date;
+ if ( ! $this->hide_end_time && ! empty( $time_end ) ) {
+ $output .= ' ' . $time_end;
+ }
+ }
+
+ // Add timezone if the option is set.
+ if ( $this->display_timezone ) {
+ $output .= ' (' . $this->get_timezone_abbreviation() . ')';
+ }
+
+ return $output;
+ }
+}
diff --git a/src/classes/class-se-admin.php b/src/classes/class-se-admin.php
index ad438f5..4449f7c 100644
--- a/src/classes/class-se-admin.php
+++ b/src/classes/class-se-admin.php
@@ -32,7 +32,7 @@ public static function init() {
* @return void
*/
public static function enqueue_admin_scripts() {
- wp_enqueue_script(
+ wp_register_script(
'se-admin',
SE_PLUGIN_URL . '/build/js/admin.js',
array( 'jquery' ),
@@ -42,6 +42,16 @@ public static function enqueue_admin_scripts() {
'strategy' => 'async',
)
);
+
+ wp_localize_script(
+ 'se-admin',
+ 'seAdmin',
+ array(
+ 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
+ 'nonce' => wp_create_nonce( 'wp_rest' ),
+ )
+ );
+ wp_enqueue_script( 'se-admin' );
}
/**
diff --git a/src/classes/class-se-block-variations.php b/src/classes/class-se-block-variations.php
index 3a4ed18..d279fb7 100644
--- a/src/classes/class-se-block-variations.php
+++ b/src/classes/class-se-block-variations.php
@@ -98,9 +98,56 @@ public function build_query( $query ) {
}
}
+ // Change the post type.
+ $query['post_type'] = SE_Event_Post_Type::$event_date_post_type;
+
return $this->set_event_query_args( $query, $feed_type, $feed_order );
}
+ /**
+ * Modify event posts results.
+ *
+ * @param array $posts The array of post objects.
+ * @param WP_Query $query The WP_Query instance.
+ *
+ * @return array
+ */
+ public function modify_event_posts( $posts, $query ) {
+ // Check if this is our events query
+ if ( ! isset( $query->query_vars['sub-type'] ) || self::QUERY_LOOP_EVENTS !== $query->query_vars['sub-type'] ) {
+ return $posts;
+ }
+
+ // Return back the
+ return array_map(
+ function ( $post ) {
+ $parent = get_post( $post->post_parent );
+
+ // Get the start date from the event.
+ $start_date_ts = get_post_meta( $post->ID, 'se_event_date_start', true );
+
+ // Get the event timezone.
+ $timezone = get_post_meta( $parent->ID, 'se_event_timezone', true );
+ // use the timezone or default to the site timezone.
+ $timezone = $timezone ? $timezone : wp_timezone_string();
+
+ // Get the date im this format 2025-07-01 13:14:09
+ $start_date = wp_date( 'Y-m-d H:i:s', $start_date_ts, new \DateTimeZone( $timezone ) );
+ $start_date_gmt = wp_date( 'Y-m-d H:i:s', $start_date_ts, new \DateTimeZone( 'UTC' ) );
+
+ // update the parent posts post date
+ $parent->post_date = $start_date;
+ $parent->post_date_gmt = $start_date_gmt;
+ $parent->post_modified = $start_date;
+ $parent->post_modified_gmt = $start_date_gmt;
+ $parent->event_date_id = $post->ID;
+
+ return $parent;
+ },
+ $posts
+ );
+ }
+
/**
* Set the query args for the event loop query admin.
*
@@ -112,7 +159,7 @@ public function build_query( $query ) {
public function set_admin_query( $args, $request ) {
$feed_type = $request->get_param( 'feedType' );
- $feed_order = $request->get_param( 'order' );
+ $feed_order = $request->get_param( 'order' );#
return $this->set_event_query_args( $args, $feed_type, $feed_order );
}
@@ -128,6 +175,13 @@ public function set_admin_query( $args, $request ) {
*/
private function set_event_query_args( $args, $feed_type, $feed_order = 'ASC' ) {
+ // If we are ordering by desc. we need to sort by end date, else start.
+ $args['meta_key'] = 'desc' === strtolower( $feed_order ) ? 'se_event_date_end' : 'se_event_date_start';
+ $args['orderby'] = 'meta_value';
+ $args['order'] = $feed_order;
+
+ $args['sub-type'] = self::QUERY_LOOP_EVENTS;
+
if ( 'upcoming' === $feed_type ) {
$args['meta_query'] = array(
array(
@@ -156,6 +210,19 @@ private function set_event_query_args( $args, $feed_type, $feed_order = 'ASC' )
$args['order'] = $feed_order;
}
+ // add the arg to denote unique parents.
+ $args['unique_parents'] = true;
+ $args['feed_order'] = $feed_order; // Store feed order for use in the WHERE filter
+
+ // Ensure we only get the correct event date for each parent.
+ add_filter( 'posts_where', array( $this, 'filter_unique_parents_where' ), 10, 2 );
+
+ // Add a filter to modify the posts results.
+ add_filter( 'the_posts', array( $this, 'modify_event_posts' ), 10, 2 );
+
+ // Add a custom order by.
+ add_filter( 'posts_orderby', array( $this, 'fix_editor_sort_order' ), 10, 2 );
+
/**
* A filter to customize the args of the event query loop.
*
@@ -165,6 +232,101 @@ private function set_event_query_args( $args, $feed_type, $feed_order = 'ASC' )
*/
return apply_filters( 'se_pre_set_event_query_loop_args', $args, $feed_type, $feed_order );
}
+
+ /**
+ * Ensure the sort order is correctly set for the unique parents query.
+ *
+ * This fixes a weird bug where the admin/editor order is always ASC.
+ *
+ * @param string $orderby The current orderby clause.
+ * @param WP_Query $query The WP_Query instance.
+ *
+ * @return string
+ */
+ public function fix_editor_sort_order( $orderby, $query ) {
+ if ( isset( $query->query_vars['unique_parents'] ) && $query->query_vars['unique_parents'] ) {
+ if ( str_ends_with( $orderby, '+0 ASC' ) ) {
+ $feed_order = isset( $query->query_vars['feed_order'] ) ? $query->query_vars['feed_order'] : 'ASC';
+ $new_order = sprintf( ' %s', strtoupper( $feed_order ) );
+ $orderby = str_replace( '+0 ASC', $new_order, $orderby );
+ }
+ }
+
+ return $orderby;
+ }
+
+
+ /**
+ * Filter posts to only include the correct event date for each parent.
+ *
+ * @param string $where The WHERE clause of the query.
+ * @param WP_Query $query The WP_Query instance.
+ *
+ * @return string
+ */
+ public function filter_unique_parents_where( $where, $query ) {
+ // Check if this is our events query and unique parents is enabled
+ if ( ! isset( $query->query_vars['unique_parents'] ) || ! isset( $query->query_vars['feed_order'] ) ) {
+ return $where;
+ }
+
+ // Skip if treating each date as own event
+ if ( se_event_treat_each_date_as_own_event() ) {
+ return $where;
+ }
+
+ global $wpdb;
+
+ $feed_order = $query->query_vars['feed_order'];
+ $meta_key = 'desc' === $feed_order ? 'se_event_date_end' : 'se_event_date_start';
+ // Get the current time filtering from the main query's meta_query
+ $time_filter = '';
+ $meta_query = $query->get( 'meta_query' );
+ if ( ! empty( $meta_query ) && is_array( $meta_query ) ) {
+ foreach ( $meta_query as $meta_condition ) {
+ if ( isset( $meta_condition['key'] ) && 'se_event_date_end' === $meta_condition['key'] ) {
+ $compare = $meta_condition['compare'];
+ $value = $meta_condition['value'];
+
+ // Add the same time filtering to the subquery
+ if ( '>=' === $compare ) {
+ // For upcoming events
+ $time_filter = "AND pm3.meta_value >= {$value}";
+ } elseif ( '<' === $compare ) {
+ // For past events
+ $time_filter = "AND pm3.meta_value < {$value}";
+ }
+ break;
+ }
+ }
+ }
+
+ // Subquery to get the correct post ID for each parent based on sort order
+ $subquery = "
+ AND {$wpdb->posts}.ID IN (
+ SELECT p1.ID
+ FROM {$wpdb->posts} p1
+ INNER JOIN {$wpdb->postmeta} pm1 ON p1.ID = pm1.post_id AND pm1.meta_key = '{$meta_key}'
+ WHERE p1.post_type = '" . SE_Event_Post_Type::$event_date_post_type . "'
+ AND p1.post_status = 'publish'
+ AND pm1.meta_value = (
+ SELECT " . ( 'desc' === $feed_order ? 'MAX' : 'MIN' ) . "(pm2.meta_value)
+ FROM {$wpdb->posts} p2
+ INNER JOIN {$wpdb->postmeta} pm2 ON p2.ID = pm2.post_id AND pm2.meta_key = '{$meta_key}'
+ " . ( $time_filter ? "INNER JOIN {$wpdb->postmeta} pm3 ON p2.ID = pm3.post_id AND pm3.meta_key = 'se_event_date_end'" : '' ) . "
+ WHERE p2.post_parent = p1.post_parent
+ AND p2.post_type = '" . SE_Event_Post_Type::$event_date_post_type . "'
+ AND p2.post_status = 'publish'
+ {$time_filter}
+ )
+ GROUP BY p1.post_parent
+ )
+ ";
+
+ $where .= $subquery;
+
+ return $where;
+ }
}
( new SE_Block_Variations() )->init();
diff --git a/src/classes/class-se-blocks.php b/src/classes/class-se-blocks.php
index bdf2f6f..e9d0543 100644
--- a/src/classes/class-se-blocks.php
+++ b/src/classes/class-se-blocks.php
@@ -121,6 +121,11 @@ public static function block_assets() {
$block_settings['pastEventsNotice'] = $value;
$block_settings['postType'] = get_post_type();
+ // Pass through new event data information.
+ $block_settings['eventVersion'] = SE_Event_Post_Type::$current_event_version;
+ $block_settings['eventDatePostType'] = SE_Event_Post_Type::$event_date_post_type;
+ $block_settings['syncDatesNonce'] = wp_create_nonce( 'se_event_nonce' );
+
wp_localize_script(
'wp-blocks',
'seSettings',
@@ -236,10 +241,12 @@ public static function event_info_render( $attributes, $content, $block ) {
$post_ID = isset( $block->context['postId'] ) ? $block->context['postId'] : get_the_ID();
+ $date_display_formatter = new SE_Date_Display_Formatter( $post_ID );
+
$output = '';
// Event time / date.
- $event_dates = get_post_meta( $post_ID, 'se_event_dates', true );
+ $event_dates = se_event_get_event_dates( $post_ID );
// Previewing?
if ( ! empty( $attributes['eventDates'] ) ) {
@@ -252,13 +259,42 @@ public static function event_info_render( $attributes, $content, $block ) {
// Previewing?
if ( isset( $attributes['eventTimezone'] ) ) {
$event_timezone = $attributes['eventTimezone'];
+ $date_display_formatter->modify_timezone( $event_timezone );
+ }
+
+ // If we are hiding past dates.
+ $options = get_option( 'se_options' );
+ $event_options = array( 'hide_events_on_both', 'hide_events_on_feed', 'on' );
+ if ( isset( $options['hide_past_events'] ) && in_array( $options['hide_past_events'], $event_options, true ) ) {
+ try {
+ $ts = '' !== $event_timezone ? new \DateTimeZone( $event_timezone ) : null;
+ } catch ( \Throwable $th ) {
+ $ts = null;
+ }
+
+ $now = se_create_date_time_from_timestamp( time(), $ts )->getTimestamp();
+ // Remove any dates that have passed.
+ $event_dates = array_filter(
+ $event_dates,
+ fn( array $date ): bool => ! isset( $date['end_date'] ) || $date['end_date'] >= $now
+ );
}
$dates_output = '';
if ( ! empty( $event_dates ) ) {
- $dates_count = count( $event_dates );
- $date_heading = '' . _n( 'Date', 'Dates', $dates_count, 'simple-events' ) . ' ';
+ $has_header_date = false;
+ $dates_count = count( $event_dates );
+ $active_date = $date_display_formatter->render_active_date( $event_dates );
+ if ( $active_date ) {
+ $dates_output .= '';
+ $has_header_date = true;
+ if ( $dates_count > 1 ) {
+ $date_heading = '' . _n( 'Additional Date', 'Additional Dates', $dates_count - 1, 'simple-events' ) . ' ';
+ }
+ } else {
+ $date_heading = '' . _n( 'Date', 'Dates', $dates_count, 'simple-events' ) . ' ';
+ }
/**
* Filter the markup used for the date heading.
@@ -267,7 +303,10 @@ public static function event_info_render( $attributes, $content, $block ) {
* @param int $dates_count The number of event dates.
*/
$dates_output .= apply_filters( 'se_event_info_date_heading', $date_heading, $dates_count );
- $dates_output .= se_event_get_formatted_dates( $post_ID, false, false, $event_dates );
+ // If we have a header date and 2 or more dates, we need to exclude the current date from the list.
+ if ( ( $has_header_date && $dates_count > 1 ) || ! $has_header_date ) {
+ $dates_output .= $date_display_formatter->render_date_list( $event_dates, ( $has_header_date && $date_display_formatter->is_treating_each_date_as_own_event() ) );
+ }
}
// Event location.
@@ -455,21 +494,22 @@ private static function render_ticket( $product ) {
/**
* Render upcoming events block.
*
- * @param array $attributes Block attributes.
- * @param string $content Block content.
+ * @param array $attributes The attributes passed to the block renderer.
+ * @param string $content The content of the event.
*
* @return HTML Upcoming events render.
+ *
+ * @todo: This is a mess. We need to refactor this.
*/
public static function upcoming_events_render( $attributes = array(), $content = '' ) {
$events_query_args = array();
$events_query = null;
$output = '';
-
if ( ! empty( $attributes['count'] ) ) {
// By default shows the "mixed" feed type (no meta_query).
$events_query_args = array(
- 'post_type' => SE_Event_Post_Type::$post_type,
+ 'post_type' => SE_Event_Post_Type::$event_date_post_type,
'post_status' => 'publish',
'posts_per_page' => absint( $attributes['count'] ),
);
@@ -521,10 +561,32 @@ public static function upcoming_events_render( $attributes = array(), $content =
$events_query_args['se_event_order'] = $attributes['feedOrder'];
}
+ // Set up sorting and unique parent filtering
+ $feed_order = ! empty( $attributes['feedOrder'] ) ? $attributes['feedOrder'] : 'ASC';
+
+ // Set meta key and order based on feed order
+ $events_query_args['meta_key'] = 'desc' === strtolower( $feed_order ) ? 'se_event_date_end' : 'se_event_date_start';
+ $events_query_args['orderby'] = 'meta_value';
+ $events_query_args['order'] = $feed_order;
+
+ // Add unique parents filtering if not treating each date as own event
+ if ( ! se_event_treat_each_date_as_own_event() ) {
+ $events_query_args['unique_parents'] = true;
+ $events_query_args['feed_order'] = $feed_order;
+
+ // Add filter for unique parents WHERE clause
+ add_filter( 'posts_where', array( __CLASS__, 'filter_unique_parents_where' ), 10, 2 );
+ }
+
$show_year_dividers = ! empty( $attributes['showYearDividers'] );
$events_query = new \WP_Query( $events_query_args );
+ // Add filter to modify posts for event_date_id if not treating each date as own event
+ if ( ! se_event_treat_each_date_as_own_event() ) {
+ add_filter( 'the_posts', array( __CLASS__, 'modify_event_posts_for_blocks' ), 10, 2 );
+ }
+
if ( $events_query->have_posts() ) {
$container_class[] = 'wp-block-se-upcoming-events';
$container_class[] = 'wp-block-se-upcoming-events-view-' . $attributes['layout'];
@@ -554,6 +616,12 @@ public static function upcoming_events_render( $attributes = array(), $content =
$output = $content;
}
+ // Clean up filters
+ if ( ! se_event_treat_each_date_as_own_event() ) {
+ remove_filter( 'posts_where', array( __CLASS__, 'filter_unique_parents_where' ), 10 );
+ remove_filter( 'the_posts', array( __CLASS__, 'modify_event_posts_for_blocks' ), 10 );
+ }
+
wp_reset_postdata();
}
@@ -731,14 +799,29 @@ public static function calendar_render( $attributes = array() ) {
* @return string Returns the filtered post date for the current post wrapped inside "time" tags.
*/
public static function loop_event_info_render( $attributes, $content, $block ): string {
-
- $output = '';
- $prefix = '';
- $post_ID = ( isset( $attributes['thePostId'] ) && $attributes['thePostId'] > 0 ) ? $attributes['thePostId'] : $block->context['postId'];
+ global $post;
+ $output = '';
+ $prefix = '';
+ $post_ID = ( isset( $attributes['thePostId'] ) && $attributes['thePostId'] > 0 ) ? $attributes['thePostId'] : $block->context['postId'];
+ $event_date_id = $post instanceof \WP_Post && property_exists( $post, 'event_date_id' ) && is_numeric( $post->event_date_id ) && se_event_treat_each_date_as_own_event()
+ ? absint( $post->event_date_id )
+ : null;
if ( isset( $attributes['metaPrefix'] ) ) {
$prefix = '' . esc_html( $attributes['metaPrefix'] ) . ' ';
}
+ // Based on the feed type, set the get_date_function
+ switch ( $attributes['feedType'] ?? 'default' ) {
+ case 'upcoming':
+ $get_date_function = 'se_event_get_future_dates';
+ break;
+ case 'past':
+ $get_date_function = 'se_event_get_past_dates';
+ break;
+ default:
+ $get_date_function = 'se_event_get_formatted_dates';
+ break;
+ }
// Generate output based on meta name.
if ( ! empty( $post_ID ) ) {
@@ -750,13 +833,13 @@ public static function loop_event_info_render( $attributes, $content, $block ):
$output = se_event_get_venue( $post_ID );
break;
case 'dates':
- $output = se_event_get_future_dates( $post_ID );
+ $output = $get_date_function( $post_ID, $event_date_id );
break;
case 'date':
- $output = se_event_get_future_dates( $post_ID, true, false );
+ $output = $get_date_function( $post_ID, $event_date_id, true, false );
break;
case 'time':
- $output = se_event_get_future_dates( $post_ID, false, true );
+ $output = $get_date_function( $post_ID, $event_date_id, false, true );
break;
}
}
@@ -893,6 +976,123 @@ public static function add_event_query_vars( $vars ) {
return $vars;
}
+
+ /**
+ * Filter posts to only include the correct event date for each parent.
+ *
+ * @param string $where The WHERE clause of the query.
+ * @param WP_Query $query The WP_Query instance.
+ *
+ * @return string
+ */
+ public static function filter_unique_parents_where( $where, $query ) {
+ // Check if this is our events query and unique parents is enabled
+ if ( ! isset( $query->query_vars['unique_parents'] ) || ! isset( $query->query_vars['feed_order'] ) ) {
+ return $where;
+ }
+
+ // Skip if treating each date as own event
+ if ( se_event_treat_each_date_as_own_event() ) {
+ return $where;
+ }
+
+ global $wpdb;
+
+ $feed_order = $query->query_vars['feed_order'];
+ $meta_key = 'desc' === strtolower( $feed_order ) ? 'se_event_date_end' : 'se_event_date_start';
+
+ // Get the current time filtering from the main query's meta_query
+ $time_filter = '';
+ $meta_query = $query->get( 'meta_query' );
+ if ( ! empty( $meta_query ) && is_array( $meta_query ) ) {
+ foreach ( $meta_query as $meta_condition ) {
+ if ( isset( $meta_condition['key'] ) && 'se_event_date_end' === $meta_condition['key'] ) {
+ $compare = $meta_condition['compare'];
+ $value = $meta_condition['value'];
+
+ // Add the same time filtering to the subquery
+ if ( '>=' === $compare ) {
+ // For upcoming events
+ $time_filter = "AND pm3.meta_value >= {$value}";
+ } elseif ( '<' === $compare ) {
+ // For past events
+ $time_filter = "AND pm3.meta_value < {$value}";
+ }
+ break;
+ }
+ }
+ }
+
+ // Subquery to get the correct post ID for each parent based on sort order
+ $subquery = "
+ AND {$wpdb->posts}.ID IN (
+ SELECT p1.ID
+ FROM {$wpdb->posts} p1
+ INNER JOIN {$wpdb->postmeta} pm1 ON p1.ID = pm1.post_id AND pm1.meta_key = '{$meta_key}'
+ WHERE p1.post_type = '" . SE_Event_Post_Type::$event_date_post_type . "'
+ AND p1.post_status = 'publish'
+ AND pm1.meta_value = (
+ SELECT " . ( 'desc' === strtolower( $feed_order ) ? 'MAX' : 'MIN' ) . "(pm2.meta_value)
+ FROM {$wpdb->posts} p2
+ INNER JOIN {$wpdb->postmeta} pm2 ON p2.ID = pm2.post_id AND pm2.meta_key = '{$meta_key}'
+ " . ( $time_filter ? "INNER JOIN {$wpdb->postmeta} pm3 ON p2.ID = pm3.post_id AND pm3.meta_key = 'se_event_date_end'" : '' ) . "
+ WHERE p2.post_parent = p1.post_parent
+ AND p2.post_type = '" . SE_Event_Post_Type::$event_date_post_type . "'
+ AND p2.post_status = 'publish'
+ {$time_filter}
+ )
+ GROUP BY p1.post_parent
+ )
+ ";
+
+ $where .= $subquery;
+
+ return $where;
+ }
+
+ /**
+ * Modify event posts results for blocks.
+ *
+ * @param array $posts The array of post objects.
+ * @param WP_Query $query The WP_Query instance.
+ *
+ * @return array
+ */
+ public static function modify_event_posts_for_blocks( $posts, $query ) {
+ // Check if this is our events query
+ if ( ! isset( $query->query_vars['unique_parents'] ) || ! $query->query_vars['unique_parents'] ) {
+ return $posts;
+ }
+
+ // Return back the modified posts with parent info and event_date_id
+ return array_map(
+ function ( $post ) {
+ $parent = get_post( $post->post_parent );
+
+ // Get the start date from the event.
+ $start_date_ts = get_post_meta( $post->ID, 'se_event_date_start', true );
+
+ // Get the event timezone.
+ $timezone = get_post_meta( $parent->ID, 'se_event_timezone', true );
+ // use the timezone or default to the site timezone.
+ $timezone = $timezone ? $timezone : get_option( 'timezone_string' );
+
+ // Get the date in this format 2025-07-01 13:14:09
+ $start_date = wp_date( 'Y-m-d H:i:s', $start_date_ts, new \DateTimeZone( $timezone ) );
+ $start_date_gmt = wp_date( 'Y-m-d H:i:s', $start_date_ts, new \DateTimeZone( 'UTC' ) );
+
+ // update the parent posts post date
+ $parent->post_date = $start_date;
+ $parent->post_date_gmt = $start_date_gmt;
+ $parent->post_modified = $start_date;
+ $parent->post_modified_gmt = $start_date_gmt;
+ $parent->event_date_id = $post->ID;
+
+ return $parent;
+ },
+ $posts
+ );
+ }
}
SE_Blocks::init();
diff --git a/src/classes/class-se-calendar-export.php b/src/classes/class-se-calendar-export.php
index 6fc8db7..341eef9 100644
--- a/src/classes/class-se-calendar-export.php
+++ b/src/classes/class-se-calendar-export.php
@@ -72,20 +72,25 @@ public static function icalendar() {
// Get dates.
if ( ! empty( $events ) ) {
foreach ( $events as $event_id ) {
- $event_dates = se_event_get_dates( $event_id );
+ $event_dates = se_event_get_event_dates( $event_id );
foreach ( $event_dates as $event_date ) {
$v_event = new \Eluceo\iCal\Component\Event();
- if ( empty( $event_date['datetime_start'] ) || empty( $event_date['datetime_end'] ) ) {
+ // If the date is hidden from the calendar, skip it.
+ if ( true === (bool) $event_date['hide_from_calendar'] ) {
+ continue;
+ }
+
+ if ( empty( $event_date['start_date'] ) || empty( $event_date['end_date'] ) ) {
continue;
}
$date_start = new \DateTime();
- $date_start->setTimestamp( $event_date['datetime_start'] );
+ $date_start->setTimestamp( $event_date['start_date'] );
$date_end = new \DateTime();
- $date_end->setTimestamp( $event_date['datetime_end'] );
+ $date_end->setTimestamp( $event_date['end_date'] );
$v_event
->setDtStart( $date_start )
diff --git a/src/classes/class-se-calendar.php b/src/classes/class-se-calendar.php
index b13db20..a408109 100644
--- a/src/classes/class-se-calendar.php
+++ b/src/classes/class-se-calendar.php
@@ -124,34 +124,6 @@ public function create_date_time( $date_time, $timezone = null ): DateTime {
}
- /**
- * Create a DateTime object from a timestamp, with an optional timezone.
- *
- * @param mixed $timestamp The Unix timestamp to create the DateTime object from.
- * @param string|null $timezone The timezone to be used, or null to use the site timezone.
- *
- * @return DateTime The created DateTime object.
- */
- public function create_date_time_from_timestamp( $timestamp, $timezone = null ): DateTime {
- /**
- * If no timezone is passed, use the site timezone
- */
- if ( null === $timezone ) {
- $timezone = wp_timezone_string();
- }
-
- try {
- $date_time_object = new DateTime( 'now', new DateTimeZone( $timezone ) );
- $date_time_object->setTimestamp( $timestamp );
- } catch ( Exception $e ) {
- $date_time_object = new DateTime();
- // todo handle exception
- }
-
- return $date_time_object->setTimestamp( $timestamp );
- }
-
-
/**
* Retrieves the days of the month and related event information.
*
@@ -272,19 +244,44 @@ public function get_previous_month_with_events( $current_date ) {
$previous_date_time->modify( 'first day of this month' );
$previous_date_time->settime( 0, 0, 0 );
- $sql_query = $wpdb->prepare(
- "SELECT start_meta.meta_value from {$wpdb->prefix}postmeta as start_meta WHERE start_meta.meta_key = 'se_event_date_start' AND start_meta.meta_value < %s ORDER BY start_meta.meta_value DESC LIMIT 1;",
- $previous_date_time->getTimestamp()
+ $args = array(
+ 'post_type' => SE_Event_Post_Type::$event_date_post_type,
+ 'meta_query' => array(
+ 'relation' => 'AND',
+ array(
+ 'key' => 'se_event_date_start',
+ 'value' => $previous_date_time->getTimestamp(),
+ 'compare' => '<',
+ ),
+ array(
+ 'relation' => 'OR',
+ array(
+ 'key' => 'se_event_hide_from_calendar',
+ 'compare' => 'NOT EXISTS',
+ ),
+ array(
+ 'key' => 'se_event_hide_from_calendar',
+ 'value' => '1',
+ 'compare' => '!=',
+ ),
+ ),
+ ),
+ 'orderby' => 'meta_value',
+ 'order' => 'DESC',
+ 'limit' => 1,
);
- // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
- $previous_event = $wpdb->get_row( $sql_query ); // the query is prepared above
+ $query = new WP_Query( $args );
+
+ if ( $query->have_posts() ) {
+ $previous_event = $query->posts[0];
- if ( empty( $previous_event ) ) {
+ // Get the start date of the previous event.
+ $previous_event_start_date = get_post_meta( $previous_event->ID, 'se_event_date_start', true );
+ return se_create_date_time_from_timestamp( $previous_event_start_date );
+ } else {
return null;
}
-
- return $this->create_date_time_from_timestamp( $previous_event->meta_value );
}
/**
@@ -295,36 +292,68 @@ public function get_previous_month_with_events( $current_date ) {
* @return DateTime|null
*/
public function get_next_month_with_events( $current_date ) {
- global $wpdb;
- $next_date_time = clone $current_date;
- $next_date_time->modify( 'last day of this month' );
- $next_date_time->settime( 23, 23, 59 );
-
- $sql_query = $wpdb->prepare(
- "SELECT start_meta.meta_value from {$wpdb->prefix}postmeta as start_meta WHERE start_meta.meta_key = 'se_event_date_start' AND start_meta.meta_value > %s ORDER BY start_meta.meta_value ASC LIMIT 1;",
- $next_date_time->getTimestamp()
- );
-
- // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
- $next_event = $wpdb->get_row( $sql_query ); // the query is prepared above
-
- // If we dont have a next date, check if we have any end dates.
- if ( empty( $next_event ) ) {
- $sql_query = $wpdb->prepare(
- "SELECT end_meta.meta_value from {$wpdb->prefix}postmeta as end_meta WHERE end_meta.meta_key = 'se_event_date_end' AND end_meta.meta_value > %s ORDER BY end_meta.meta_value ASC LIMIT 1;",
- $next_date_time->getTimestamp()
+ /**
+ * Compile a shared set of query args.
+ *
+ * @var string $meta_key The meta key to use for the query.
+ * @var DateTime $current_date The current date.
+ *
+ * @return array The query arguments.
+ */
+ $query_args = function ( string $meta_key ) use ( $current_date ) {
+ $next_date_time = clone $current_date;
+ $next_date_time->modify( 'last day of this month' );
+ $next_date_time->settime( 23, 59, 59 );
+
+ return array(
+ 'post_type' => SE_Event_Post_Type::$event_date_post_type,
+ 'meta_query' => array(
+ 'relation' => 'AND',
+ array(
+ 'key' => $meta_key,
+ 'value' => $next_date_time->getTimestamp(),
+ 'compare' => '>',
+ ),
+ array(
+ 'relation' => 'OR',
+ array(
+ 'key' => 'se_event_hide_from_calendar',
+ 'compare' => 'NOT EXISTS',
+ ),
+ array(
+ 'key' => 'se_event_hide_from_calendar',
+ 'value' => '1',
+ 'compare' => '!=',
+ ),
+ ),
+ ),
+ 'orderby' => 'meta_value',
+ 'order' => 'ASC',
+ 'limit' => 1,
);
-
- // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
- $next_event = $wpdb->get_row( $sql_query ); // the query is prepared above
+ };
+
+ $next_event = null;
+
+ // At first try to get the next event with the start date.
+ $query = new WP_Query( $query_args( 'se_event_date_start' ) );
+ if ( $query->have_posts() ) {
+ $next_event = $query->posts[0];
+ } else {
+ // If we don't have a next event with the start date, try with the end date.
+ $query = new WP_Query( $query_args( 'se_event_date_end' ) );
+ if ( $query->have_posts() ) {
+ $next_event = $query->posts[0];
+ }
}
if ( empty( $next_event ) ) {
return null;
}
-
- return $this->create_date_time_from_timestamp( $next_event->meta_value );
+ // Get the start date of the next event.
+ $next_event_start_date = get_post_meta( $next_event->ID, 'se_event_date_start', true );
+ return se_create_date_time_from_timestamp( $next_event_start_date );
}
@@ -340,57 +369,35 @@ private function get_events_by_date( $date ): array {
$day_events = array();
- $start_timestamp = $date->setTime( 0, 0, 0 )->getTimeStamp();
- $end_timestamp = $date->setTime( 23, 59, 59 )->getTimestamp();
-
- $sql_query = $wpdb->prepare(
- "
-SELECT * from {$wpdb->prefix}posts
-INNER JOIN {$wpdb->prefix}postmeta AS start_meta ON {$wpdb->prefix}posts.ID = start_meta.post_id AND start_meta.meta_key = 'se_event_date_start'
-INNER JOIN {$wpdb->prefix}postmeta AS end_meta ON {$wpdb->prefix}posts.ID = end_meta.post_id AND end_meta.meta_key = 'se_event_date_end'
-WHERE wp_posts.post_type = %s AND (wp_posts.post_status = 'publish') AND
-((start_meta.meta_value >= %s AND start_meta.meta_value < %s)
-OR
-(start_meta.meta_value < %s AND end_meta.meta_value > %s)
-OR
-(end_meta.meta_value <= %s AND end_meta.meta_value > %s))
-GROUP BY {$wpdb->prefix}posts.ID
-ORDER BY start_meta.meta_value ASC;",
- 'se-event',
- $start_timestamp,
- $end_timestamp,
- $start_timestamp,
- $end_timestamp,
- $end_timestamp,
- $start_timestamp
- );
-
- // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
- $all_events = $wpdb->get_results( $sql_query ); // the query is prepared above
- if ( $all_events ) {
- foreach ( $all_events as $event ) {
- $event_dates = se_event_get_dates( $event->ID );
+ $new_all = SE_Event_Dates::get_event_dates_for_date( $date->format( 'Y-m-d' ), true, false );
+ // If we new dates.
+ if ( ! empty( $new_all ) ) {
+ foreach ( $new_all as $event ) {
- if ( ! $event_dates ) {
+ // Get the parent post.
+ $parent_post = get_post( $event['event_id'] );
+ if ( ! $parent_post ) {
continue;
}
- foreach ( $event_dates as $event_date ) {
- $event->event_start_date = $this->create_date_time_from_timestamp( $event_date['datetime_start'] );
- $event->event_end_date = $this->create_date_time_from_timestamp( $event_date['datetime_end'] );
- $event->hide_start_time = '1' === get_post_meta( $event->ID, 'se_event_hide_start_time', true );
- $event->hide_end_time = '1' === get_post_meta( $event->ID, 'se_event_hide_end_time', true );
- if ( $event_date['datetime_start'] >= $start_timestamp && $event_date['datetime_start'] <= $end_timestamp ) {
- $new_event = clone $event;
- $new_event->event_start_date = $this->create_date_time_from_timestamp( $event_date['datetime_start'] );
- $new_event->event_end_date = $this->create_date_time_from_timestamp( $event_date['datetime_end'] );
- $new_event->open_in_new_window = (bool) get_post_meta( $event->ID, 'se_event_open_in_new_window', true );
-
- $day_events[] = $new_event;
- }
- }
+ $event['ID'] = $parent_post->ID;
+ $event['post_title'] = $parent_post->post_title;
+ $event['post_content'] = $parent_post->post_content;
+ $event['post_excerpt'] = $parent_post->post_excerpt;
+ $event['post_date'] = $parent_post->post_date;
+ $event['post_date_gmt'] = $parent_post->post_date_gmt;
+ $event['post_modified'] = $parent_post->post_modified;
+ // Add the meta.
+ $event['event_start_date'] = se_create_date_time_from_timestamp( $event['event_start_date'] );
+ $event['event_end_date'] = se_create_date_time_from_timestamp( $event['event_end_date'] );
+ $event['hide_start_time'] = '1' === get_post_meta( $parent_post->ID, 'se_event_hide_start_time', true );
+ $event['hide_end_time'] = '1' === get_post_meta( $parent_post->ID, 'se_event_hide_end_time', true );
+ $event['open_in_new_window'] = (bool) get_post_meta( $parent_post->ID, 'se_event_open_in_new_window', true );
+
+ $day_events[] = (object) $event;
}
}
+
return $day_events;
}
diff --git a/src/classes/class-se-event-dates.php b/src/classes/class-se-event-dates.php
new file mode 100644
index 0000000..3de1f63
--- /dev/null
+++ b/src/classes/class-se-event-dates.php
@@ -0,0 +1,436 @@
+namespace,
+ '/' . $this->rest_base_dates . '/(?P[\d]+)',
+ array(
+ 'methods' => 'GET',
+ 'callback' => array( $this, 'get_event_dates' ),
+ 'permission_callback' => '__return_true',
+ )
+ );
+
+ register_rest_route(
+ $this->namespace,
+ '/' . $this->rest_base_dates . '/(?P[\d]+)/sync',
+ array(
+ 'methods' => 'POST',
+ 'callback' => array( $this, 'sync_event_dates' ),
+ 'permission_callback' => function () {
+ return current_user_can( 'edit_posts' );
+ },
+ 'args' => array(
+ 'dates' => array(
+ 'required' => true,
+ 'type' => 'array',
+ 'description' => 'Array of date objects from dateManager',
+ ),
+ ),
+ )
+ );
+ }
+
+ /**
+ * Get event dates.
+ *
+ * @param WP_REST_Request $request The request object.
+ *
+ * @return WP_REST_Response
+ */
+ public function get_event_dates( WP_REST_Request $request ): WP_REST_Response {
+ // If we dont have an event ID, return an error.
+ $event_id = $request->get_param( 'event_id' );
+ if ( empty( $event_id ) || ! is_numeric( $event_id ) ) {
+ return new WP_REST_Response(
+ array(
+ 'code' => 'invalid_event_id',
+ 'message' => __( 'Invalid event ID provided.', 'simple-events' ),
+ ),
+ 400
+ );
+ }
+
+ // Check if we have a valid event.
+ $event = get_post( $event_id );
+ if ( ! $event || 'se-event' !== $event->post_type ) {
+ return new WP_REST_Response(
+ array(
+ 'code' => 'invalid_event',
+ 'message' => __( 'Invalid event provided.', 'simple-events' ),
+ ),
+ 404
+ );
+ }
+
+ try {
+ $dates = se_event_get_event_dates( $event_id );
+ } catch ( \Throwable $th ) {
+ return new WP_REST_Response(
+ array(
+ 'code' => 'server_error',
+ 'message' => __( 'An error occurred while fetching event dates.', 'simple-events' ),
+ ),
+ 500
+ );
+ }
+
+ // Create the return.
+ $data = array(
+ 'event_id' => $event_id,
+ 'dates' => $dates,
+ 'timezone' => get_post_meta( $event_id, 'se_event_timezone', true ) ?: wp_timezone_string(), // phpcs:ignore
+ );
+
+ // Return the response.
+ return new WP_REST_Response(
+ $data,
+ 200
+ );
+ }
+
+ /**
+ * Sync event dates.
+ *
+ * @param WP_REST_Request $request The request object.
+ *
+ * @return WP_REST_Response
+ */
+ public function sync_event_dates( WP_REST_Request $request ): WP_REST_Response {
+ $event_id = $request->get_param( 'event_id' );
+ $dates = $request->get_param( 'dates' );
+ $nonce = $request->get_param( 'nonce' );
+
+ // Check if the nonce is valid.
+ if ( ! wp_verify_nonce( $nonce, 'se_event_nonce' ) ) {
+ return new WP_REST_Response(
+ array(
+ 'code' => 'invalid_nonce',
+ 'message' => __( 'Invalid nonce provided.', 'simple-events' ),
+ ),
+ 403
+ );
+ }
+
+ // Get the existing dates.
+ $existing_date_ids = array_map(
+ function ( $date ) {
+ return $date['id'];
+ },
+ se_event_get_event_dates( $event_id )
+ );
+
+ // Iterate over the existing dates and delete any that are not in the new dates.
+ foreach ( $existing_date_ids as $existing_date_id ) {
+ if ( ! in_array( $existing_date_id, array_column( $dates, 'id' ), true ) ) {
+ wp_delete_post( $existing_date_id, true );
+ }
+ }
+ // Iterate over the dates and update the event dates.
+ foreach ( $dates as $date ) {
+ // If we dont have a date ID, create a new date.
+ if ( ! isset( $date['id'] ) ) {
+ $event_date = se_event_create_event_date( $event_id, $date );
+ // If we dont have a WP_Post object, return an error.
+ if ( ! $event_date ) {
+ return new WP_REST_Response(
+ array(
+ 'code' => 'server_error',
+ 'message' => __( 'An error occurred while creating the event date.', 'simple-events' ),
+ ),
+ 500
+ );
+ }
+ $date['id'] = $event_date->ID;
+ }
+
+ // Update the even dates meta.
+ $event_date_id = absint( $date['id'] );
+ update_post_meta( $event_date_id, 'se_event_date_start', esc_attr( $date['start_date'] ) );
+ update_post_meta( $event_date_id, 'se_event_date_end', esc_attr( $date['end_date'] ) );
+ update_post_meta( $event_date_id, 'se_event_all_day', boolval( $date['all_day'] ) );
+ update_post_meta( $event_date_id, 'se_event_hide_from_calendar', boolval( $date['hide_from_calendar'] ) );
+ update_post_meta( $event_date_id, 'se_event_hide_from_feed', boolval( $date['hide_from_feed'] ) );
+ }
+
+ // Update the event version.
+ update_post_meta( $event_id, 'se_event_version', SE_MIGRATION_VERSION );
+
+ // Re fetch the event dates.
+ try {
+ $dates = se_event_get_event_dates( $event_id );
+ } catch ( \Throwable $th ) {
+ return new WP_REST_Response(
+ array(
+ 'code' => 'server_error',
+ 'message' => __( 'An error occurred while fetching event dates.', 'simple-events' ),
+ ),
+ 500
+ );
+ }
+
+ // Update all legacy meta values.
+ self::update_legacy_meta_values( $event_id, $dates );
+
+ // Return the response.
+ return new WP_REST_Response(
+ array(
+ 'code' => 'success',
+ 'message' => __( 'Event dates synced successfully.', 'simple-events' ),
+ 'dates' => $dates,
+ ),
+ 200
+ );
+ }
+
+ /**
+ * Update all legacy meta values.
+ *
+ * @param integer $event_id The event ID.
+ * @param array $dates The dates.
+ *
+ * @return void
+ */
+ public static function update_legacy_meta_values( $event_id, $dates ): void {
+ // Create the legacy date array.
+ $legacy_dates = array_map(
+ function ( $date ) {
+ return array(
+ 'datetime_start' => $date['start_date'],
+ 'datetime_end' => $date['end_date'],
+ 'all_day' => $date['all_day'],
+ );
+ },
+ $dates
+ );
+
+ // Update the legacy meta values.
+ update_post_meta( $event_id, 'se_event_dates', $legacy_dates );
+
+ se_event_update_event_query_dates( $event_id );
+ }
+
+ /**
+ * Fiind event dates.
+ *
+ * @param string $start_date The start date as a timestamp.
+ * @param string $end_date The end date as a timestamp.
+ * @param boolean $hide_from_calendar Whether the event is hidden from the calendar.
+ * @param boolean $hide_from_feed Whether the event is hidden from the feed.
+ *
+ * @return array
+ */
+ public static function find_event_dates( $start_date, $end_date, $hide_from_calendar, $hide_from_feed ): array {
+ // Create the timestamp for the start and end of the $start_date.
+ $start_date_time = se_create_date_time_from_timestamp( $start_date )->setTimezone( wp_timezone() );
+
+ $start_date_range = array(
+ $start_date_time->setTime( 0, 0, 0 )->getTimestamp(),
+ $start_date_time->setTime( 23, 59, 59 )->getTimestamp(),
+ );
+
+ // Query the event dates.
+ $query = new WP_Query(
+ array(
+ 'post_type' => SE_Event_Post_Type::$event_date_post_type,
+ 'meta_query' => array(
+ 'relation' => 'OR',
+ // Exact match for all conditions
+ array(
+ 'relation' => 'AND',
+ array(
+ 'key' => 'se_event_date_start',
+ 'value' => $start_date,
+ 'compare' => '>=',
+ ),
+ array(
+ 'key' => 'se_event_date_end',
+ 'value' => $end_date,
+ 'compare' => '<=',
+ ),
+ ),
+ // All day events with matching start date
+ array(
+ 'relation' => 'AND',
+ array(
+ 'key' => 'se_event_date_start',
+ 'value' => $start_date_range,
+ 'compare' => 'BETWEEN',
+ ),
+ array(
+ 'key' => 'se_event_all_day',
+ 'value' => '1',
+ 'compare' => '=',
+ ),
+ ),
+ ),
+ 'posts_per_page' => -1,
+ 'orderby' => 'meta_value',
+ 'meta_key' => 'se_event_date_start',
+ 'order' => 'ASC',
+ 'post_status' => 'publish',
+ )
+ );
+
+ $mapped = self::map_events_dates_to_event_dates( $query->posts );
+
+ // Remove the event dates that are hidden from the calendar or feed.
+ return array_filter(
+ $mapped,
+ function ( $event_date ) use ( $hide_from_calendar, $hide_from_feed ) {
+ return ! $event_date['event_hide_from_calendar'] && ! $event_date['event_hide_from_feed'];
+ }
+ );
+ }
+
+ /**
+ * Get the events dates for a given date.
+ *
+ * @param string $date The date to get the events for.
+ * @param boolean $hide_from_calendar Whether the event is hidden from the calendar.
+ * @param boolean $hide_from_feed Whether the event is hidden from the feed.
+ *
+ * @return array
+ */
+ public static function get_event_dates_for_date( $date, $hide_from_calendar = false, $hide_from_feed = false ): array {
+ // Create date explicitly in site timezone
+ $date_time = DateTime::createFromFormat( 'Y-m-d H:i:s', $date . ' 00:00:00', wp_timezone() );
+
+ // Set the time to the start of the day.
+ $date_time->setTime( 0, 0, 0 );
+ // set as a timestamp.
+ $start_date = $date_time->getTimestamp();
+
+ // Get the end of the day for the date.
+ $date_time->setTime( 23, 59, 59 );
+ $end_date = $date_time->getTimestamp();
+
+ // Get the events dates.
+ $events_dates = self::find_event_dates( $start_date, $end_date, $hide_from_calendar, $hide_from_feed );
+
+ // Return the events dates.
+ return $events_dates;
+ }
+
+ /**
+ * Map the events dates to the event dates.
+ *
+ * @param array $events_dates The events dates.
+ *
+ * @return array{event_id: int, event_date_id: int, event_start_date: string, event_end_date: string, event_all_day: bool, event_hide_from_calendar: bool, event_hide_from_feed: bool}
+ */
+ public static function map_events_dates_to_event_dates( $events_dates ): array {
+ $compiled_events = array();
+ foreach ( $events_dates as $event_date ) {
+ // Get the parent event.
+ $event = get_post( $event_date->post_parent );
+ if ( ! $event ) {
+ continue;
+ }
+
+ // Get the event date.
+ $start_date = get_post_meta( $event_date->ID, 'se_event_date_start', true );
+ $end_date = get_post_meta( $event_date->ID, 'se_event_date_end', true );
+ $all_day = get_post_meta( $event_date->ID, 'se_event_all_day', true );
+ $hide_from_calendar = get_post_meta( $event_date->ID, 'se_event_hide_from_calendar', true );
+ $hide_from_feed = get_post_meta( $event_date->ID, 'se_event_hide_from_feed', true );
+
+ // Add the event date to the compiled events.
+ $compiled_events[] = array(
+ 'event_id' => absint( $event->ID ),
+ 'event_date_id' => absint( $event_date->ID ),
+ 'event_start_date' => esc_attr( $start_date ),
+ 'event_end_date' => esc_attr( $end_date ),
+ 'event_all_day' => boolval( $all_day ),
+ 'event_hide_from_calendar' => boolval( $hide_from_calendar ),
+ 'event_hide_from_feed' => boolval( $hide_from_feed ),
+ );
+ }
+
+ // Return the compiled events.
+ return $compiled_events;
+ }
+
+ /**
+ * Delete all event dates for a given event.
+ *
+ * @param integer $event_id The event ID.
+ *
+ * @return void
+ */
+ public static function delete_all_event_dates( $event_id ): void {
+ // Get all the event dates.
+ try {
+ $event_dates = se_event_get_event_dates( $event_id );
+ } catch ( \Exception $e ) {
+ // If we can't get the dates, there's nothing to delete
+ return;
+ }
+
+ // Iterate over the event dates and delete them.
+ foreach ( $event_dates as $event_date ) {
+ wp_delete_post( $event_date['id'], true );
+ }
+ }
+
+ /**
+ * Delete a single event date.
+ *
+ * @param integer $event_date_id The event date ID.
+ *
+ * @return void
+ */
+ public static function delete_event_date( $event_date_id ): void {
+ wp_delete_post( $event_date_id, true );
+ }
+}
+SE_Event_Dates::init();
diff --git a/src/classes/class-se-event-post-type.php b/src/classes/class-se-event-post-type.php
index c7b91b3..a2eb072 100644
--- a/src/classes/class-se-event-post-type.php
+++ b/src/classes/class-se-event-post-type.php
@@ -12,6 +12,15 @@
* Post types Class.
*/
class SE_Event_Post_Type {
+
+ /**
+ * The current event version.
+ *
+ * @var string
+ */
+ public static $current_event_version = '2.0.0';
+
+
/**
* This is the name of this post type.
*
@@ -19,6 +28,14 @@ class SE_Event_Post_Type {
*/
public static $post_type = 'se-event';
+
+ /**
+ * The event date post type.
+ *
+ * @var string
+ */
+ public static $event_date_post_type = 'se-event-date';
+
/**
* This is the slug of this post type.
*
@@ -123,6 +140,64 @@ public static function register_post_type() {
),
)
);
+
+ // Register the event-date post type. This is a child of the above event post type.
+ register_post_type(
+ 'se-event-date',
+ array(
+ 'labels' => array(
+ 'name' => __( 'Event Dates', 'simple-events' ),
+ 'singular_name' => __( 'Event Date', 'simple-events' ),
+ 'all_items' => __( 'All Event Dates', 'simple-events' ),
+ 'archives' => __( 'Event Date Archives', 'simple-events' ),
+ 'attributes' => __( 'Event Date Attributes', 'simple-events' ),
+ 'insert_into_item' => __( 'Insert into Event Date', 'simple-events' ),
+ 'uploaded_to_this_item' => __( 'Uploaded to this Event Date', 'simple-events' ),
+ 'featured_image' => _x( 'Featured Image', 'se-event-date', 'simple-events' ),
+ 'set_featured_image' => _x( 'Set featured image', 'se-event-date', 'simple-events' ),
+ 'remove_featured_image' => _x( 'Remove featured image', 'se-event-date', 'simple-events' ),
+ 'use_featured_image' => _x( 'Use as featured image', 'se-event-date', 'simple-events' ),
+ 'filter_items_list' => __( 'Filter Event Dates list', 'simple-events' ),
+ 'items_list_navigation' => __( 'Event Dates list navigation', 'simple-events' ),
+ 'items_list' => __( 'Event Dates list', 'simple-events' ),
+ 'new_item' => __( 'New Event Date', 'simple-events' ),
+ 'add_new' => __( 'Add New', 'simple-events' ),
+ 'add_new_item' => __( 'Add New Event Date', 'simple-events' ),
+ 'edit_item' => __( 'Edit Event Date', 'simple-events' ),
+ 'view_item' => __( 'View Event Date', 'simple-events' ),
+ 'view_items' => __( 'View Event Dates', 'simple-events' ),
+ 'search_items' => __( 'Search Event Dates', 'simple-events' ),
+ 'not_found' => __( 'No Event Dates found', 'simple-events' ),
+ 'not_found_in_trash' => __( 'No Event Dates found in trash', 'simple-events' ),
+ 'parent_item_colon' => __( 'Parent Event Date:', 'simple-events' ),
+ 'menu_name' => __( 'Event Dates', 'simple-events' ),
+ ),
+ 'public' => false,
+ 'hierarchical' => false,
+ 'show_ui' => false,
+ 'show_in_nav_menus' => false,
+ 'supports' => array(
+ 'title',
+ 'editor',
+ 'thumbnail',
+ 'custom-fields',
+ ),
+ 'rewrite' => array(
+ 'slug' => 'event-date',
+ 'with_front' => false,
+ ),
+ 'has_archive' => false,
+ 'query_var' => false,
+ 'menu_position' => null,
+ 'menu_icon' => 'dashicons-calendar-alt',
+ 'show_in_rest' => true,
+ 'rest_base' => 'se-event-date',
+ 'rest_controller_class' => 'WP_REST_Posts_Controller',
+ 'capabilities' => array(
+ 'create_posts' => 'do_not_allow', // Disable creation of new event dates.
+ ),
+ )
+ );
}
/**
@@ -200,26 +275,15 @@ public static function register_meta() {
'post',
'se_event_dates',
array(
- 'single' => true,
- 'type' => 'array',
- 'show_in_rest' => array(
- 'schema' => array(
- 'items' => array(
- 'type' => 'object',
- 'properties' => array(
- 'datetime_start' => array(
- 'type' => 'string',
- ),
- 'datetime_end' => array(
- 'type' => 'string',
- ),
- 'all_day' => array(
- 'type' => 'boolean',
- ),
- ),
- ),
- ),
- ),
+ 'single' => true,
+ 'type' => 'array',
+ 'default' => array(),
+ 'sanitize_callback' => function ( $value ) {
+ if ( is_null( $value ) || ! is_array( $value ) ) {
+ return array();
+ }
+ return $value;
+ },
)
);
@@ -414,6 +478,44 @@ public static function register_meta() {
'default' => true,
)
);
+
+ // is all day (bool)
+ register_meta(
+ 'post',
+ 'se_event_all_day',
+ array(
+ 'show_in_rest' => true,
+ 'single' => true,
+ 'type' => 'boolean',
+ 'object_subtype' => self::$event_date_post_type,
+ )
+ );
+
+ // hide from calendar (bool)
+ register_meta(
+ 'post',
+ 'se_event_hide_from_calendar',
+ array(
+ 'show_in_rest' => true,
+ 'single' => true,
+ 'type' => 'boolean',
+ 'object_subtype' => self::$event_date_post_type,
+ 'default' => false,
+ )
+ );
+
+ // hide from feed (bool)
+ register_meta(
+ 'post',
+ 'se_event_hide_from_feed',
+ array(
+ 'show_in_rest' => true,
+ 'single' => true,
+ 'type' => 'boolean',
+ 'object_subtype' => self::$event_date_post_type,
+ 'default' => false,
+ )
+ );
}
/**
@@ -452,9 +554,60 @@ public static function pre_get_posts( $query ) {
if (
( $query->is_main_query() && ( is_post_type_archive( self::$post_type )
|| is_tax( self::$post_type . '-category' ) ) )
- || ( ! $query->is_main_query() && self::$post_type === $query->get( 'post_type' ) && ! $query->get( 'se_countdown' ) && $query->get( 'sub-type' ) !== SE_Block_Variations::QUERY_LOOP_EVENTS )
+ || ( ! $query->is_main_query() && self::$post_type === $query->get( 'post_type' ) && ! $query->get( 'se_countdown' ) && $query->get( 'sub-type' ) === SE_Block_Variations::QUERY_LOOP_EVENTS )
) {
- $query->set( 'orderby', 'meta_value' );
+ // Handle taxonomy filtering by getting parent event IDs first
+ $parent_event_ids = null;
+ $tax_query = $query->get( 'tax_query' );
+
+ // Check if we have taxonomy queries for event categories
+ if ( ! empty( $tax_query ) || is_tax( self::$post_type . '-category' ) ) {
+ // Create a separate query to get parent events that match taxonomy criteria
+ $parent_query_args = array(
+ 'post_type' => self::$post_type,
+ 'posts_per_page' => -1,
+ 'fields' => 'ids',
+ 'post_status' => 'publish',
+ );
+
+ // Add taxonomy query from original query
+ if ( ! empty( $tax_query ) ) {
+ $parent_query_args['tax_query'] = $tax_query;
+ }
+
+ // Handle category archive pages
+ if ( is_tax( self::$post_type . '-category' ) ) {
+ $term = get_queried_object();
+ $parent_query_args['tax_query'] = array(
+ array(
+ 'taxonomy' => self::$post_type . '-category',
+ 'field' => 'term_id',
+ 'terms' => $term->term_id,
+ ),
+ );
+ }
+
+ $parent_events = new WP_Query( $parent_query_args );
+ $parent_event_ids = $parent_events->posts;
+
+ // If no parent events match, set to empty array to return no results
+ if ( empty( $parent_event_ids ) ) {
+ $parent_event_ids = array( 0 );
+ }
+ }
+
+ // Change query to target event dates instead of events
+ $query->set( 'post_type', self::$event_date_post_type );
+
+ // If we have taxonomy filtering, limit to dates of matching parent events
+ if ( null !== $parent_event_ids ) {
+ $query->set( 'post_parent__in', $parent_event_ids );
+ // Remove tax_query since we're now querying date posts
+ $query->set( 'tax_query', array() );
+ }
+
+ // Order by event date start timestamp
+ $query->set( 'orderby', 'meta_value_num' );
$query->set( 'meta_key', 'se_event_date_start' );
$query->set( 'order', apply_filters( 'se_pre_get_posts_order', $sort_order, $query ) );
@@ -462,16 +615,19 @@ public static function pre_get_posts( $query ) {
$event_options = array( 'hide_events_on_both', 'hide_events_on_feed', 'on' );
if ( isset( $options['hide_past_events'] ) && in_array( $options['hide_past_events'], $event_options, true ) ) {
- $query->set(
- 'meta_query',
- array(
- array(
- 'key' => 'se_event_date_end',
- 'value' => wp_date( 'U' ),
- 'compare' => '>=',
- ),
- )
+ $existing_meta_query = $query->get( 'meta_query' );
+ if ( ! is_array( $existing_meta_query ) ) {
+ $existing_meta_query = array();
+ }
+
+ $existing_meta_query[] = array(
+ 'key' => 'se_event_date_end',
+ 'value' => time(),
+ 'compare' => '>=', // what is this?
+ 'type' => 'NUMERIC',
);
+
+ $query->set( 'meta_query', $existing_meta_query );
}
}
}
@@ -553,7 +709,8 @@ public static function delete_event_dates_if_no_event_info_block( $event_id ) {
}
if ( ! $is_event_info_block_present ) {
- delete_post_meta( $event_id, 'se_event_dates' );
+ // Delete all the event dates.
+ SE_Event_Dates::delete_all_event_dates( $event_id );
}
}
}
diff --git a/src/classes/class-se-event-query-dates.php b/src/classes/class-se-event-query-dates.php
index f87c16f..f285d1d 100644
--- a/src/classes/class-se-event-query-dates.php
+++ b/src/classes/class-se-event-query-dates.php
@@ -11,7 +11,7 @@
/**
* Event Dates
*/
-class SE_Event_Dates {
+class SE_Event_Query_Dates {
public const UPDATE_QUERY_DATES_HOOK = 'se_event_update_query_dates_cron';
/**
@@ -154,4 +154,4 @@ public static function handle_cron() {
}
// Self initialization.
-SE_Event_Dates::init();
+SE_Event_Query_Dates::init();
diff --git a/src/classes/class-se-migrate-events.php b/src/classes/class-se-migrate-events.php
new file mode 100644
index 0000000..109e836
--- /dev/null
+++ b/src/classes/class-se-migrate-events.php
@@ -0,0 +1,359 @@
+ array( 'migrate_1_0_0_to_2_0_0' ),
+ );
+
+ /**
+ * Initialize the class.
+ *
+ * @return void
+ */
+ public static function init() {
+
+ add_action( 'rest_api_init', array( __CLASS__, 'register_rest_route' ) );
+ add_action( 'se_migrate_events', array( __CLASS__, 'migrate_events' ) );
+ }
+
+ /**
+ * Registers the rest route.
+ *
+ * @return void
+ */
+ public static function register_rest_route() {
+ // The Namespace
+ $namespace = 'simple-events';
+
+ // Route to pass a list of events to update.
+ register_rest_route(
+ $namespace,
+ '/migrate-events',
+ array(
+ 'methods' => 'POST',
+ 'callback' => array( __CLASS__, 'migrate_events_rest' ),
+ 'permission_callback' => function () {
+ return current_user_can( 'manage_options' );
+ },
+ )
+ );
+
+ // Register the route to migrate all events.
+ register_rest_route(
+ $namespace,
+ '/migrate-all-events',
+ array(
+ 'methods' => array( 'POST', 'GET' ),
+ 'callback' => array( __CLASS__, 'migrate_events_rest_all' ),
+ 'permission_callback' => function () {
+ return true;
+ },
+ )
+ );
+ }
+
+ /**
+ * Get all events that need to be migrated.
+ *
+ * @return array
+ */
+ public static function get_events_to_migrate() {
+
+ $versions = array_keys( self::VERSION_UPGRADES );
+
+ // Get all events.
+ return get_posts(
+ array(
+ 'post_type' => SE_Event_Post_Type::$post_type,
+ // only results that have a version lower that the max version.
+ 'meta_query' => array(
+ 'relation' => 'OR',
+ array(
+ 'key' => 'se_event_version',
+ 'value' => max( $versions ),
+ 'compare' => '<',
+ ),
+ // or does not exist.
+ array(
+ 'key' => 'se_event_version',
+ 'compare' => 'NOT EXISTS',
+ ),
+ // or is empty.
+ array(
+ 'key' => 'se_event_version',
+ 'value' => '',
+ 'compare' => '=',
+ ),
+ ),
+ 'posts_per_page' => -1,
+ 'post_status' => 'any',
+ )
+ );
+ }
+
+ /**
+ * Checks if we have any events to migrate.
+ *
+ * @return boolean
+ */
+ public static function has_events_to_migrate() {
+ return count( self::get_events_to_migrate() ) > 0;
+ }
+
+ /**
+ * Migrate all events.
+ *
+ * @param WP_REST_Request $request The request object.
+ *
+ * @return WP_REST_Response
+ */
+ public static function migrate_events_rest_all( $request ) { // phpcs:ignore
+ // Check if we have events to migrate.
+ if ( ! self::has_events_to_migrate() ) {
+ return new WP_REST_Response(
+ array(
+ 'message' => esc_html__( 'No events to migrate.', 'simple-events' ),
+ ),
+ 200
+ );
+ }
+
+ try {
+ // Get all events to migrate.
+ $events = self::get_events_to_migrate();
+ // Migrate the events.
+ $response = self::migrate_events_by_ids( wp_list_pluck( $events, 'ID' ) );
+ } catch ( \Throwable $th ) {
+ return new WP_REST_Response(
+ array(
+ 'message' => esc_html__( 'An error occurred while migrating events.', 'simple-events' ),
+ 'error' => esc_html( $th->getMessage() ),
+ ),
+ 500
+ );
+ }
+ // Return the response.
+ return new WP_REST_Response(
+ array(
+ 'message' => esc_html__( 'Events migrated successfully.', 'simple-events' ),
+ 'data' => $response,
+ ),
+ 200
+ );
+ }
+
+ /**
+ * Migrate events via REST.
+ *
+ * @param WP_REST_Request $request The request object.
+ *
+ * @return WP_REST_Response
+ */
+ public static function migrate_events_rest( $request ) {
+ // Check if we have events in the body (form-data).
+ $event_ids = $request->get_param( 'events' );
+ // If no events are provided, return an error.
+ if ( empty( $event_ids ) ) {
+ return new WP_REST_Response(
+ array(
+ 'message' => esc_html__( 'No events provided for migration.', 'simple-events' ),
+ ),
+ 400
+ );
+ }
+
+ // Convert the event IDs from a string to an array.
+ $event_ids = json_decode( $event_ids, true );
+
+ // if there was an error decoding the JSON, return an error.
+ if ( json_last_error() !== JSON_ERROR_NONE ) {
+ return new WP_REST_Response(
+ array(
+ 'message' => esc_html__( 'Invalid event IDs provided : ', 'simple-events' ) . esc_html( json_last_error_msg() ),
+ ),
+ 400
+ );
+ }
+
+ // Cast to an array of integers from JSON
+ $event_ids = array_map( 'intval', $event_ids );
+ // Validate the event IDs.
+
+ try {
+ $response = self::migrate_events_by_ids( $event_ids );
+ } catch ( \Throwable $th ) {
+ return new WP_REST_Response(
+ array(
+ 'message' => esc_html__( 'An error occurred while migrating events.', 'simple-events' ),
+ 'error' => esc_html( $th->getMessage() ),
+ ),
+ 500
+ );
+ }
+
+ // Return the response.
+ return new WP_REST_Response(
+ array(
+ 'message' => esc_html__( 'Events migrated successfully.', 'simple-events' ),
+ 'data' => $response,
+ ),
+ 200
+ );
+ }
+
+ /**
+ * Migrate events.
+ *
+ * @param array $event_ids The event IDs to migrate.
+ *
+ * @return array
+ */
+ private static function migrate_events_by_ids( $event_ids ) {
+ // Iterate over the event IDs and update them.
+ $results = array();
+
+ foreach ( $event_ids as $event_id ) {
+ // Check if the event exists.
+ if ( ! get_post( $event_id ) ) {
+ $results[ $event_id ] = false;
+ continue;
+ }
+
+ // Migrate the event.
+ $success = self::migrate_event( $event_id );
+
+ $new_version = get_post_meta( $event_id, 'se_event_version', true ); //phpcs:ignore
+
+ $results[ $event_id ] = array(
+ 'success' => $success,
+ 'version' => ! $new_version || '' === $new_version ? '1.0.0' : $new_version, // Default to 1.0.0 if no version is set.
+ );
+ }
+
+ return $results;
+ }
+
+ /**
+ * Migrate a single event.
+ *
+ * @param integer $event_id The event ID to migrate.
+ *
+ * @return boolean
+ */
+ private static function migrate_event( $event_id ) {
+ // Get the event post.
+ $event_post = get_post( $event_id );
+
+ if ( ! $event_post || 'se-event' !== $event_post->post_type ) {
+ return false; // Not a valid event post.
+ }
+
+ try {
+ // Get the version of the event.
+ $migration_methods = self::get_migration_methods( get_post_meta( $event_id, 'se_event_version', true ) ?: '1.0.0' ); // phpcs:ignore
+ foreach ( $migration_methods as $version => $methods ) {
+ // Iterate over the methods.
+ foreach ( $methods as $method ) {
+ // Check if the method exists in the class.
+ if ( ! method_exists( __CLASS__, $method ) ) {
+ continue; // Skip if the method does not exist.
+ }
+
+ // Call the migration method.
+ call_user_func( array( __CLASS__, $method ), $event_id );
+ }
+
+ // Update the posts meta to say updated to.
+ update_post_meta( $event_id, 'se_event_version', $version );
+ }
+ } catch ( \Throwable $th ) {
+ return false; // If any error occurs, return false.
+ }
+ return true; // If everything goes well, return true.
+ }
+
+ /**
+ * Get the list of methods based on the version.
+ *
+ * @param string $version The version to check.
+ *
+ * @return array
+ */
+ private static function get_migration_methods( $version ) {
+ // If the version is not set, return all methods.
+ if ( empty( $version ) ) {
+ return self::VERSION_UPGRADES;
+ }
+
+ // Filter the methods based on the version.
+ return array_filter(
+ self::VERSION_UPGRADES,
+ function ( $methods, $min_version ) use ( $version ) {
+ return version_compare( $version, $min_version, '<' );
+ },
+ ARRAY_FILTER_USE_BOTH
+ );
+ }
+
+
+
+ ##################
+ # Migration Methods
+ ##################
+
+ /**
+ * Migrate from version 1.0.0 to 2.0.0.
+ *
+ * This method migrates the event dates from the old format to the new format.
+ *
+ * @param integer $event_id The event ID to migrate.
+ *
+ * @return void
+ */
+ public static function migrate_1_0_0_to_2_0_0( int $event_id ): void {
+ // Get all the events from its meta.
+ $dates = get_post_meta( $event_id, 'se_event_dates', true );
+ // Iterate over the dates.
+ if ( ! is_array( $dates ) || empty( $dates ) ) {
+ return; // No dates to migrate.
+ }
+ foreach ( $dates as $key => $date ) {
+ // Unpack
+ $start = $date['datetime_start'] ?? '';
+ $end = $date['datetime_end'] ?? '';
+ $all_day = $date['all_day'] ?? false;
+
+ se_event_create_event_date(
+ $event_id,
+ array(
+ 'all_day' => $all_day,
+ 'start_date' => $start,
+ 'end_date' => $end,
+ )
+ );
+ }
+ }
+}
+
+SE_Migrate_Events::init();
diff --git a/src/classes/class-se-settings.php b/src/classes/class-se-settings.php
index f142c7d..1fd65e7 100644
--- a/src/classes/class-se-settings.php
+++ b/src/classes/class-se-settings.php
@@ -26,6 +26,7 @@ public static function init() {
// Ajax actions.
add_action( 'wp_ajax_se_mark_existing_orders_as_completed', array( __CLASS__, 'mark_existing_orders_as_completed' ), 10 );
+ add_action( 'wp_ajax_se_clear_orphaned_events', array( __CLASS__, 'clear_orphaned_events' ), 10 );
}
/**
@@ -91,6 +92,25 @@ public static function settings_init() {
'se_section_archives',
array(
'label_for' => 'past_event_notice',
+ 'required' => true,
+ )
+ );
+
+ // Add a settings to define the all day message
+ add_settings_field(
+ 'all_day_message',
+ sprintf(
+ // translators: %s is a HTML break tag.
+ __( 'All Day Message%s', 'simple-events' ),
+ wp_kses_post( 'This message will be displayed for all day events. ' ),
+ ),
+ array( __CLASS__, 'text_cb' ),
+ 'simple_events',
+ 'se_section_archives',
+ array(
+ 'label_for' => 'all_day_message',
+ 'value' => '',
+ 'required' => false,
)
);
@@ -134,6 +154,38 @@ public static function settings_init() {
)
);
+ // Treat each date as own event for navigation.
+ add_settings_field(
+ 'treat_each_date_as_own_event',
+ sprintf(
+ // translators: %s is a HTML break tag.
+ __( 'Treat each date as own event%s', 'simple-events' ),
+ wp_kses_post( 'When this is selected, next and previous events will treat consecutive dates as unique. ' ),
+ ),
+ array( __CLASS__, 'field_cb' ),
+ 'simple_events',
+ 'se_section_archives',
+ array(
+ 'label_for' => 'treat_each_date_as_own_event',
+ )
+ );
+
+ // Allow grouping of dates with different time.
+ add_settings_field(
+ 'allow_grouping_dates_different_time',
+ sprintf(
+ // translators: %s is a HTML break tag.
+ __( 'Allow grouping of dates with different times.%s', 'simple-events' ),
+ wp_kses_post( 'When enabled, events with different time ranges (e.g., 9AM-5PM vs 10AM-6PM) will be grouped separately. When disabled, only events with identical times will be grouped together. ' ),
+ ),
+ array( __CLASS__, 'field_cb' ),
+ 'simple_events',
+ 'se_section_archives',
+ array(
+ 'label_for' => 'allow_grouping_dates_different_time',
+ )
+ );
+
// Select link for the caledar page.
add_settings_field(
'calendar_page',
@@ -255,6 +307,36 @@ public static function settings_init() {
'label_for' => 'disable_download_calendar',
)
);
+
+ add_settings_field(
+ 'clear_orphaned_events',
+ sprintf(
+ // translators: %s is a HTML break tag.
+ __( 'Clear orphaned events.%s', 'simple-events' ),
+ wp_kses_post( 'Removes events with missing or corrupted data. ' ),
+ ),
+ array( __CLASS__, 'clear_orphaned_events_cb' ),
+ 'simple_events',
+ 'se_section_calendar',
+ array(
+ 'action' => 'se_clear_orphaned_events',
+ 'btn_text' => __( 'Clear orphaned events', 'simple-events' ),
+ )
+ );
+
+ // Add the migrate events button, if we have events to migrate.
+ if ( SE_Migrate_Events::has_events_to_migrate() ) {
+ add_settings_field(
+ 'migrate_events',
+ esc_html__( 'Migrate Events', 'simple-events' ),
+ array( __CLASS__, 'migrate_events_cb' ),
+ 'simple_events',
+ 'se_section_calendar',
+ array(
+ 'label_for' => 'migrate_events',
+ )
+ );
+ }
}
/**
@@ -357,7 +439,6 @@ public static function field_cb( $args ) {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ #ID ); ?> - post_title ); ?>
+
+
+
+ vID, 'se_event_version', true ) ); ?>
+
+
+
+
+
+
+
+
+
+
+
+ >
+
+
+
+
+
>
get_results(
+ $wpdb->prepare(
+ "SELECT child.ID, child.post_title, child.post_parent FROM {$wpdb->prefix}posts AS child
+ LEFT JOIN {$wpdb->prefix}posts AS parent ON child.post_parent = parent.ID
+ WHERE child.post_type = %s AND (child.post_parent = 0 OR parent.ID IS NULL)",
+ SE_Event_Post_Type::$event_date_post_type
+ )
+ );
+
+ $deleted_events = 0;
+
+ // Delete orphaned events
+ if ( ! empty( $orphaned_events ) ) {
+ foreach ( $orphaned_events as $event ) {
+ wp_delete_post( $event->ID, true ); // Force delete permanently
+ ++$deleted_events;
+ }
+ }
+
+ // translators: %d is the number of orphaned events deleted.
+ wp_send_json_success( sprintf( __( '%d orphaned events deleted successfully', 'simple-events' ), $deleted_events ) );
+ }
+
+ /**
+ * Gets the all day message.
+ *
+ * @return string
+ */
+ public static function get_all_day_message() {
+ $options = get_option( 'se_options' );
+ if ( isset( $options['all_day_message'] ) && ! empty( $options['all_day_message'] ) ) {
+ return esc_html( $options['all_day_message'] );
+ }
+
+ return '';
+ }
}
SE_Settings::init();
diff --git a/src/event-functions.php b/src/event-functions.php
index a089f12..50f6813 100644
--- a/src/event-functions.php
+++ b/src/event-functions.php
@@ -120,9 +120,13 @@ function se_event_get_tickets_stock( $event_id ) {
* @param integer $event_id Event id.
* @param array $event_dates Event dates.
*
+ * @deprecated 2.0.0 Please use the new se_event_get_event_dates() instead.
+ *
* @return mixed
*/
function se_event_get_dates( $event_id, $event_dates = null ) {
+ __doing_it_wrong( __FUNCTION__, 'Please use the new se_event_get_event_dates() instead.', '2.0.0' );
+
if ( is_null( $event_dates ) ) {
$event_dates = get_post_meta( $event_id, 'se_event_dates', true );
}
@@ -146,94 +150,177 @@ function se_event_get_dates( $event_id, $event_dates = null ) {
/**
* Gets only the future event dates in a formatted string.
*
- * @param integer $event_id Event id.
- * @param boolean $date_only Whether to return only the date.
- * @param boolean $time_only Whether to return only the time.
- * @param array $event_dates Event dates.
+ * Please note in the original function we never actually used the time_only and date_only parameters.
+ * The inner call chain was as both to null.
+ *
+ * @param integer $event_id Event id.
+ * @param integer|null $event_date_id Event date id.
+ * @param boolean $date_only Whether to return only the date.
+ * @param boolean $time_only Whether to return only the time.
+ * @param array $event_dates Event dates.
*
* @return string
*/
-function se_event_get_future_dates( $event_id, $date_only = false, $time_only = false, $event_dates = null ) {
- // Get required post meta.
- $event_dates = se_event_get_dates( $event_id, $event_dates );
- $event_timezone = get_post_meta( $event_id, 'se_event_timezone', true );
- $hide_end_time = get_post_meta( $event_id, 'se_event_hide_end_time', true );
- $hide_start_time = get_post_meta( $event_id, 'se_event_hide_start_time', true );
- $display_timezone = (bool) get_post_meta( $event_id, 'se_event_display_timezone', true );
- $now = SE_Calendar::get_instance()->create_date_time( 'now' )->format( 'U' );
+function se_event_get_future_dates( $event_id, $event_date_id = null, $date_only = false, $time_only = false, $event_dates = null ) {
+
+ $date_display_formatter = new SE_Date_Display_Formatter( $event_id );
+ $now = SE_Calendar::get_instance()->create_date_time( 'now' )->format( 'U' );
+
+ // If dateonly is true, we need to return the date only.
+ if ( $date_only ) {
+ $date_display_formatter->set_date_only( true );
+ } elseif ( $time_only ) {
+ $date_display_formatter->set_time_only( true );
+ }
+ // If we dont have any dates.
if ( ! $event_dates ) {
- return '';
+ $event_dates = se_event_get_event_dates( $event_id );
+ }
+ // Filter out only the current event date, if set.
+ if ( se_event_treat_each_date_as_own_event() && $event_date_id ) {
+ $event_dates = array_filter(
+ $event_dates,
+ function ( $date ) use ( $event_date_id ) {
+ return $date['id'] === $event_date_id;
+ }
+ );
}
- // Iterate over all the events and remove any where the start and end has passed.
- foreach ( $event_dates as $key => $date ) {
- if ( $date['datetime_start'] < $now && $date['datetime_end'] < $now ) {
- unset( $event_dates[ $key ] );
+ // Filter out any dates that are in the past. (start and end)
+ $event_dates = array_filter(
+ $event_dates,
+ function ( $date ) use ( $now ) {
+ return $date['start_date'] > $now && $date['end_date'] > $now;
}
- }
+ );
+
+ // If we have no dates, return an empty string.
if ( empty( $event_dates ) ) {
return '';
}
- return se_event_format_dates(
+ return $date_display_formatter->format_dates( $event_dates );
+}
+
+/**
+ * Gets only the past event dates in a formatted string.
+ *
+ * @param integer $event_id Event id.
+ * @param integer|null $event_date_id Event date id.
+ * @param boolean $date_only Whether to return only the date.
+ * @param boolean $time_only Whether to return only the time.
+ * @param array $event_dates Event dates.
+ *
+ * @return string
+ */
+function se_event_get_past_dates( $event_id, $event_date_id = null, $date_only = false, $time_only = false, $event_dates = null ) {
+
+ // Match the se_event_get_future_dates but for past dates
+ $date_display_formatter = new SE_Date_Display_Formatter( $event_id );
+ $now = SE_Calendar::get_instance()->create_date_time( 'now' )->format( 'U' );
+
+ if ( $date_only ) {
+ $date_display_formatter->set_date_only( true );
+ } elseif ( $time_only ) {
+ $date_display_formatter->set_time_only( true );
+ }
+
+ // If we dont have any dates.
+ if ( ! $event_dates ) {
+ $event_dates = se_event_get_event_dates( $event_id );
+ }
+
+ // Filter out only the current event date, if set.
+ if ( se_event_treat_each_date_as_own_event() && $event_date_id ) {
+ $event_dates = array_filter(
+ $event_dates,
+ function ( $date ) use ( $event_date_id ) {
+ return $date['id'] === $event_date_id;
+ }
+ );
+ }
+
+ // Filter out any dates that are in the past. (start and end)
+ $event_dates = array_filter(
$event_dates,
- $event_timezone,
- $hide_end_time,
- $hide_start_time,
- $display_timezone,
- false,
- false
+ function ( $date ) use ( $now ) {
+ return $date['start_date'] < $now && $date['end_date'] < $now;
+ }
);
+
+ // If we have no dates, return an empty string.
+ if ( empty( $event_dates ) ) {
+ return '';
+ }
+
+ return $date_display_formatter->format_dates( $event_dates );
}
+
/**
* Get the event dates in a formatted string.
*
- * @param integer $event_id Event id.
- * @param boolean $date_only Whether to return only the date.
- * @param boolean $time_only Whether to return only the time.
- * @param array $event_dates Event dates.
+ * @param integer $event_id Event id.
+ * @param integer|null $event_date_id Event date id.
+ * @param boolean $date_only Whether to return only the date.
+ * @param boolean $time_only Whether to return only the time.
+ * @param array $event_dates Event dates.
*
* @return string
*/
-function se_event_get_formatted_dates( $event_id, $date_only = false, $time_only = false, $event_dates = null ) {
+function se_event_get_formatted_dates( $event_id, $event_date_id = null, $date_only = false, $time_only = false, $event_dates = null ) {
- // Get required post meta.
- $event_dates = se_event_get_dates( $event_id, $event_dates );
- $event_timezone = get_post_meta( $event_id, 'se_event_timezone', true );
- $hide_end_time = get_post_meta( $event_id, 'se_event_hide_end_time', true );
- $hide_start_time = get_post_meta( $event_id, 'se_event_hide_start_time', true );
- $display_timezone = (bool) get_post_meta( $event_id, 'se_event_display_timezone', true );
+ $date_display_formatter = new SE_Date_Display_Formatter( $event_id );
+
+ // if we dont have any dates.
+ if ( ! $event_dates ) {
+ $event_dates = se_event_get_event_dates( $event_id );
+ }
+ // Filter out only the current event date, if set.
+ if ( se_event_treat_each_date_as_own_event() && $event_date_id ) {
+ $event_dates = array_filter(
+ $event_dates,
+ function ( $date ) use ( $event_date_id ) {
+ return $date['id'] === $event_date_id;
+ }
+ );
+ }
if ( ! $event_dates ) {
return '';
}
- return se_event_format_dates(
- $event_dates,
- $event_timezone,
- $hide_end_time,
- $hide_start_time,
- $display_timezone,
- $date_only,
- $time_only
- );
+ if ( $date_only ) {
+ $date_display_formatter->set_date_only( true );
+ } elseif ( $time_only ) {
+ $date_display_formatter->set_time_only( true );
+ }
+
+ return $date_display_formatter->format_dates( $event_dates );
}
/**
* Formats the dates for the event.
*
- * @param array $event_dates Event dates.
- * @param string $timezone Timezone.
- * @param mixed $hide_end_time If we should hide the end time.
- * @param mixed $hide_start_time If we should hide the start time.
- * @param mixed $display_timezone If we should display the timezone.
- * @param mixed $date_only If we should only show the date.
- * @param mixed $time_only If we should only show the time.
+ * @param array $event_dates Event dates.
+ * @param string $timezone Timezone.
+ * @param mixed $hide_end_time If we should hide the end time.
+ * @param mixed $hide_start_time If we should hide the start time.
+ * @param mixed $display_timezone If we should display the timezone.
+ * @param mixed $date_only If we should only show the date.
+ * @param mixed $time_only If we should only show the time.
*
* @return string
+ *
+ * @deprecated 2.0.0
*/
function se_event_format_dates( $event_dates, $timezone, $hide_end_time, $hide_start_time, $display_timezone, $date_only, $time_only ) {
+ // Add doing it wrong, suggest they use the new dateFormatter class instead.
+ __doing_it_wrong( __FUNCTION__, 'Please use the new dateFormatter class instead.', '2.0.0' );
+
+ // Attempt to get the event date id from url.
+ $event_date_id = se_template_get_event_date_id();
+
$dates_count = is_array( $event_dates ) ? count( $event_dates ) : 1;
if ( ! empty( $event_timezone ) ) {
@@ -250,19 +337,20 @@ function se_event_format_dates( $event_dates, $timezone, $hide_end_time, $hide_s
// Get the start and end times from the first date.
// Assume all start and end times are the same until proven otherwise.
- $event_time_start = wp_date( get_option( 'time_format' ), $event_dates[0]['datetime_start'], $timezone );
- $event_time_end = wp_date( get_option( 'time_format' ), $event_dates[0]['datetime_end'], $timezone );
+ $event_time_start = wp_date( get_option( 'time_format' ), $event_dates[0]['start_date'], $timezone );
+ $event_time_end = wp_date( get_option( 'time_format' ), $event_dates[0]['end_date'], $timezone );
$same_times = ( 1 < $dates_count ) ? true : false;
// Loop over each available event date.
foreach ( $event_dates as $date ) {
+ $opening_li = $date['id'] === $event_date_id ? '' : ' ';
// Check if start and end times are on the same day.
- $same_day = wp_date( 'Y-m-d', $date['datetime_start'], $timezone ) === wp_date( 'Y-m-d', $date['datetime_end'], $timezone );
+ $same_day = wp_date( 'Y-m-d', $date['start_date'], $timezone ) === wp_date( 'Y-m-d', $date['end_date'], $timezone );
// Get start and end times.
- $time_start = ( $hide_start_time ) ? '' : wp_date( get_option( 'time_format' ), $date['datetime_start'], $timezone );
- $time_end = ( $hide_end_time ) ? '' : wp_date( get_option( 'time_format' ), $date['datetime_end'], $timezone );
+ $time_start = ( $hide_start_time ) ? '' : wp_date( get_option( 'time_format' ), $date['start_date'], $timezone );
+ $time_end = ( $hide_end_time ) ? '' : wp_date( get_option( 'time_format' ), $date['end_date'], $timezone );
$time_separator = ( 1 === (int) $hide_start_time ) ? '' : '–';
@@ -275,20 +363,20 @@ function se_event_format_dates( $event_dates, $timezone, $hide_end_time, $hide_s
$date['all_day'] = array_key_exists( 'all_day', $date ) ? filter_var( $date['all_day'], FILTER_VALIDATE_BOOLEAN ) : false;
// Start the output string.
- $single_date_output = wp_date( get_option( 'date_format' ), $date['datetime_start'], $timezone );
+ $single_date_output = wp_date( get_option( 'date_format' ), $date['start_date'], $timezone );
// Return early if we only want the date.
if ( $date_only ) {
- $end_date = wp_date( get_option( 'date_format' ), $date['datetime_end'], $timezone );
+ $end_date = wp_date( get_option( 'date_format' ), $date['end_date'], $timezone );
$date_only_output = ( $same_day ) ? $single_date_output : $single_date_output . ' – ' . $end_date;
- $dates_output .= ( $dates_count > 1 ) ? ' ' . $date_only_output . ' ' : $date_only_output;
+ $dates_output .= ( $dates_count > 1 ) ? $opening_li . $date_only_output . '' : $date_only_output;
continue;
}
// Return early if we only want the time.
if ( $time_only ) {
$time = sprintf( '%s %s %s', $time_start, $time_separator, $time_end );
- $dates_output .= ( $dates_count > 1 ) ? '' . $time . ' ' : $time;
+ $dates_output .= ( $dates_count > 1 ) ? $opening_li . $time . '' : $time;
continue;
}
@@ -298,7 +386,7 @@ function se_event_format_dates( $event_dates, $timezone, $hide_end_time, $hide_s
$single_date_output .= sprintf(
' %s – %s %s',
$time_start,
- wp_date( get_option( 'date_format' ), $date['datetime_end'], $timezone ),
+ wp_date( get_option( 'date_format' ), $date['end_date'], $timezone ),
$time_end
);
} elseif ( false === $date['all_day'] && $time_start !== $time_end ) {
@@ -313,7 +401,7 @@ function se_event_format_dates( $event_dates, $timezone, $hide_end_time, $hide_s
}
// Return output for this date.
- $dates_output .= ( $dates_count > 1 ) ? '' . $single_date_output . ' ' : $single_date_output;
+ $dates_output .= ( $dates_count > 1 ) ? $opening_li . $single_date_output . '' : $single_date_output;
}
// Overwrite output if all start and end times are the same and "Group event dates with matching times" otpion is selected.
@@ -399,30 +487,63 @@ function se_event_get_venue( $event_id ) {
* @return boolean
*/
function se_event_is_expired( $event_id ) {
- $event_end_date = get_post_meta( $event_id, 'se_event_date_end', true );
+ $event_dates = se_event_get_event_dates( $event_id );
+
+ $latest_date = null;
+ foreach ( $event_dates as $date ) {
+ // Get the end date.
+ $end_date = $date['end_date'];
+
+ // If the event is all day, get the start date.
+ if ( $date['all_day'] ) {
+ $temp = se_create_date_time_from_timestamp( $date['start_date'] );
+ $end_date = $temp->setTime( 23, 59, 59 )->getTimestamp();
+ }
- if ( ! empty( $event_end_date ) && $event_end_date < wp_date( 'U' ) ) {
- return true;
+ // If the end date is greater than the latest date, set it.
+ if ( $end_date > $latest_date ) {
+ $latest_date = $end_date;
+ }
}
- return false;
+ if ( null === $latest_date ) {
+ return false;
+ }
+
+ return $latest_date < SE_Calendar::get_instance()->create_date_time( 'now' )->format( 'U' );
}
/**
* Gets the calendar event link.
*
- * @param integer $event_id Event id.
+ * @param integer $event_id Event id.
+ * @param integer|null $event_date_id Event date id.
*
* @return string
*/
-function se_event_get_calendar_link( $event_id ) {
+function se_event_get_calendar_link( $event_id, $event_date_id = null ) {
// Set the link.
$external_link = esc_url( get_post_meta( $event_id, 'se_event_external_link', true ) );
$open_external_link = (bool) get_post_meta( $event_id, 'se_open_external_link', true );
+ if ( $external_link && $open_external_link ) {
+ return $external_link;
+ }
+
+ $permalink = get_the_permalink( $event_id );
+
+ // Either add ?se-date=7424 or append &se-date=7424 if permalink has ?
+ if ( $event_date_id ) {
+ $permalink .= sprintf(
+ '%sse-date=%s',
+ ( strpos( $permalink, '?' ) !== false ) ? '&' : '?',
+ esc_attr( $event_date_id )
+ );
+ }
+
return ( $external_link && $open_external_link )
? $external_link
- : get_the_permalink( $event_id );
+ : get_the_permalink( $event_id ) . ( $event_date_id ? '?se-date=' . $event_date_id : '' );
}
/**
@@ -479,6 +600,8 @@ function se_event_show_links_above_content(): bool {
* Updates an event start and end meta dates.
* This will ensure that the start and end dates will not be in the past.
*
+ * This is for legacy reasons only.
+ *
* @param integer $event_id Event id.
*
* @return void
@@ -528,3 +651,158 @@ function se_event_update_event_query_dates( $event_id ) {
update_post_meta( $event_id, 'se_event_date_end', esc_attr( $end_date ) );
}
}
+
+ /**
+ * Create event date.
+ *
+ * @param integer $event_id Event id.
+ * @param array{ start_date: integer, end_date: integer, all_day: boolean, hide_from_calendar: boolean, hide_from_feed: boolean, } $event_dates Event dates.
+ *
+ * @return \WP_Post|null
+ */
+function se_event_create_event_date( $event_id, $event_dates ) {
+ $default_args = array(
+ 'start_date' => 0,
+ 'end_date' => 0,
+ 'all_day' => false,
+ 'hide_from_calendar' => false,
+ 'hide_from_feed' => false,
+ );
+ // Merge the default args with the provided event dates.
+ $event_dates = wp_parse_args( $event_dates, $default_args );
+ // Validate the event dates, start date should be a timestamp and end date should be a timestamp.
+ if ( ! is_numeric( $event_dates['start_date'] ) ) {
+ return null;
+ }
+
+ // Validate parent event exists
+ if ( ! get_post( $event_id ) ) {
+ return null;
+ }
+
+ // Create the event date post.
+ $event_date_post = array(
+ 'post_title' => sprintf(
+ // translators: %s is the event title.
+ __( 'Event Date for %s', 'simple-events' ),
+ get_the_title( $event_id )
+ ),
+ 'post_content' => '',
+ 'post_status' => 'publish',
+ 'post_type' => SE_Event_Post_Type::$event_date_post_type,
+ 'post_parent' => $event_id,
+ );
+
+ // Insert the post into the database.
+ $event_date_id = wp_insert_post( $event_date_post );
+
+ if ( is_wp_error( $event_date_id ) || ! $event_date_id ) {
+ return null; // Failed to create the event date.
+ }
+
+ // Update the post meta for the event date.
+ update_post_meta( $event_date_id, 'se_event_date_start', esc_attr( $event_dates['start_date'] ) );
+ update_post_meta( $event_date_id, 'se_event_date_end', esc_attr( $event_dates['end_date'] ) );
+ update_post_meta( $event_date_id, 'se_event_all_day', boolval( $event_dates['all_day'] ) );
+ update_post_meta( $event_date_id, 'se_event_hide_from_calendar', boolval( $event_dates['hide_from_calendar'] ) );
+ update_post_meta( $event_date_id, 'se_event_hide_from_feed', boolval( $event_dates['hide_from_feed'] ) );
+
+ return get_post( $event_date_id );
+}
+
+/**
+ * Get the dates for an event.
+ *
+ * @param integer $event_id Event id.
+ *
+ * @return array{
+ * start_date: integer,
+ * end_date: integer,
+ * all_day: boolean,
+ * hide_from_calendar: boolean,
+ * hide_from_feed: boolean,
+ * }[]
+ *
+ * @throws \Exception If the event ID is invalid or if the event dates cannot be retrieved.
+ *
+ * @since 2.0.0
+ */
+function se_event_get_event_dates( $event_id ): array {
+ if ( ! is_numeric( $event_id ) || $event_id <= 0 ) {
+ throw new \Exception( esc_html( __( 'Invalid event ID provided.', 'simple-events' ) ) );
+ }
+
+ $event_dates = get_posts(
+ array(
+ 'post_type' => SE_Event_Post_Type::$event_date_post_type,
+ 'post_status' => 'publish',
+ 'posts_per_page' => -1,
+ 'post_parent' => $event_id,
+ 'fields' => 'ids',
+ )
+ );
+
+ // Map with meta.
+ $dates = array_map(
+ function ( $date_id ) {
+ $start_date = get_post_meta( $date_id, 'se_event_date_start', true );
+ $end_date = get_post_meta( $date_id, 'se_event_date_end', true );
+ $all_day = get_post_meta( $date_id, 'se_event_all_day', true );
+ $hide_from_calendar = get_post_meta( $date_id, 'se_event_hide_from_calendar', true );
+ $hide_from_feed = get_post_meta( $date_id, 'se_event_hide_from_feed', true );
+
+ return array(
+ 'id' => $date_id,
+ 'start_date' => esc_attr( $start_date ),
+ 'end_date' => esc_attr( $end_date ),
+ 'all_day' => boolval( $all_day ),
+ 'hide_from_calendar' => '' === $hide_from_calendar ? false : boolval( $hide_from_calendar ),
+ 'hide_from_feed' => '' === $hide_from_feed ? false : boolval( $hide_from_feed ),
+ );
+ },
+ $event_dates
+ );
+
+ // Legacy filter.
+ $dates = apply_filters( 'se_event_get_dates', $dates, $event_id );
+
+ return apply_filters( 'se_event_get_event_dates', $dates, $event_id );
+}
+
+/**
+ * Create a DateTime object from a timestamp, with an optional timezone.
+ *
+ * @param mixed $timestamp The Unix timestamp to create the DateTime object from.
+ * @param string|null $timezone The timezone to be used, or null to use the site timezone.
+ *
+ * @return DateTime The created DateTime object.
+ */
+function se_create_date_time_from_timestamp( $timestamp, $timezone = null ): DateTime {
+ /**
+ * If no timezone is passed, use the site timezone
+ */
+ if ( null === $timezone ) {
+ $timezone = wp_timezone_string();
+ }
+
+ try {
+ $date_time_object = new DateTime( 'now', new DateTimeZone( $timezone ) );
+ } catch ( Exception $e ) {
+ $date_time_object = new DateTime();
+ // todo handle exception
+ }
+
+ return $date_time_object->setTimestamp( $timestamp );
+}
+
+/**
+ * Checks if the settings are defined to treat each date as an own event.
+ *
+ * @return boolean
+ */
+function se_event_treat_each_date_as_own_event(): bool {
+ $settings = get_option( 'se_options' );
+ return is_array( $settings )
+ && array_key_exists( 'treat_each_date_as_own_event', $settings )
+ && 'on' === $settings['treat_each_date_as_own_event'];
+}
diff --git a/src/template-functions.php b/src/template-functions.php
index 03a9cdc..d291064 100644
--- a/src/template-functions.php
+++ b/src/template-functions.php
@@ -10,6 +10,16 @@
exit;
}
+/**
+ * Checks and get the event date id from url if set,
+ *
+ * @return integer|null The event date id or null if not set.
+ */
+function se_template_get_event_date_id() {
+ $event_date_id = array_key_exists( 'se-date', $_GET ) ? sanitize_text_field( $_GET['se-date'] ) : null; // phpcs:ignore
+ return is_numeric( $event_date_id ) ? absint( $event_date_id ) : null;
+}
+
if ( ! function_exists( 'se_template_content_wrapper_start' ) ) {
/**
@@ -54,6 +64,7 @@ function se_template_event_archive_title() {
* @return void
*/
function se_template_event_single_title() {
+
the_title( '', ' ' );
}
}
@@ -84,9 +95,13 @@ function se_template_event_thumbnail() {
/**
* Output the event date and time.
*
+ * @deprecated 2.0.0 This has been replaced by the new date formatter class.
+ *
* @return void
*/
function se_template_event_date() {
+ __doing_it_wrong( __FUNCTION__, 'Please use the new date formatter class instead.', '2.0.0' );
+
$event_dates = se_event_get_dates( get_the_ID() );
if ( ! empty( $event_dates ) ) {
@@ -194,8 +209,14 @@ function se_template_event_ticket_stock() {
* @return void
*/
function se_template_event_more_info() {
+ global $post;
+ if ( se_event_treat_each_date_as_own_event() && isset( $post->event_date_id ) ) {
+ $permalink = get_permalink( $post->post_parent ) . '?se-date=' . $post->event_date_id;
+ } else {
+ $permalink = get_permalink();
+ }
?>
-
+
%2$s',
- esc_url( get_permalink( $previous_event->ID ) ),
- apply_filters( 'se_event_previous_link_text', esc_html( '<< ' . get_the_title( $previous_event->ID ) ), $previous_event )
+ esc_url( get_permalink( $previous_event->post_parent ) . '?se-date=' . $previous_event->ID ),
+ apply_filters( 'se_event_previous_link_text', esc_html( '<< ' . get_the_title( $previous_event->post_parent ) ), $previous_event )
);
- $next_event = se_event_get_next_event( $event_start_date );
+ $next_event = se_event_get_next_event( get_the_ID(), se_template_get_event_date_id() );
$next_link = null === $next_event
? ''
: sprintf(
// translators: %1$s is the link to the next event, %2$s is the title of the next event.
'%s ',
- esc_url( get_permalink( $next_event->ID ) ),
- apply_filters( 'se_event_next_link_text', esc_html( get_the_title( $next_event->ID ) . ' >>' ), $next_event )
+ esc_url( get_permalink( $next_event->post_parent ) . '?se-date=' . $next_event->ID ),
+ apply_filters( 'se_event_next_link_text', esc_html( get_the_title( $next_event->post_parent ) . ' >>' ), $next_event )
);
$calendar_link = null !== $calendar_page
@@ -368,84 +381,135 @@ function se_template_event_next_previous(): void {
/**
* Gets the next event based on a time stamp.
*
- * @param integer $timestamp The timestamp to get the next event from.
+ * @param integer $event_id The event ID to get the next event from.
+ * @param integer|null $event_date_id The event date ID to get the next event from, if available.
*
* @return WP_Post|null The next event or null if none found.
*/
-function se_event_get_next_event( int $timestamp ): ?WP_Post {
- $query = new WP_Query(
- array(
- 'previous_events' => false,
- 'post_type' => 'se-event',
- 'posts_per_page' => -1,
- 'meta_query' => array(
- array(
- 'key' => 'se_event_date_start',
- 'value' => absint( $timestamp ),
- 'compare' => '>',
- 'type' => 'NUMERIC',
- ),
+function se_event_get_next_event( int $event_id, ?int $event_date_id = null ): ?WP_Post {
+ $options = get_option( 'se_options' );
+ $allow_grouping = isset( $options['treat_each_date_as_own_event'] ) ? 'on' === $options['treat_each_date_as_own_event'] : false;
+
+ // If we dont have an event date id, we need to get the event dates.
+ if ( ! $event_date_id ) {
+ $event_dates = se_event_get_event_dates( $event_id );
+ if ( empty( $event_dates ) ) {
+ return null;
+ }
+ $event_date_id = $event_dates[0]['id'];
+ }
+
+ // Define the query to get next events.
+ $args = array(
+ 'post_type' => SE_Event_Post_Type::$event_date_post_type,
+ 'posts_per_page' => 1,
+ 'orderby' => 'meta_value_num',
+ 'meta_key' => 'se_event_date_start',
+ 'order' => 'ASC',
+ 'post_status' => 'publish',
+ 'meta_query' => array(
+ array(
+ 'key' => 'se_event_date_start',
+ 'value' => get_post_meta( $event_date_id, 'se_event_date_start', true ),
+ 'compare' => '>',
+ 'type' => 'NUMERIC',
+ ),
+ array(
+ 'key' => 'se_event_hide_from_feed',
+ 'value' => 1,
+ 'compare' => '!=',
),
- )
+ ),
);
+ // If we dont allow grouping, add the event id to parent not in.
+ if ( ! $allow_grouping ) {
+ $args['post__not_in'] = array_map(
+ function ( $post ) {
+ return $post['id'];
+ },
+ se_event_get_event_dates( $event_id )
+ );
+ }
+
+ $query = new WP_Query( $args );
+ // If we have no posts, return null.
if ( ! $query->have_posts() ) {
return null;
}
- $event = $query->posts[0];
-
- // Reset the post data.
+ // Get the first next event.
+ $next_event = $query->posts[0];
wp_reset_postdata();
- return $event;
+ return $next_event;
}
/**
* Gets the previous event based on a time stamp.
*
- * @param integer $timestamp The timestamp to get the previous event from.
+ * @param integer $event_id The event ID to get the previous event from.
+ * @param integer|null $event_date_id The event date ID to get the previous event from, if available.
*
* @return WP_Post|null The previous event or null if none found.
*/
-function se_event_get_previous_event( int $timestamp ): ?WP_Post {
-
- // Ensure previous events are sorted by Descending order.
- add_action(
- 'pre_get_posts',
- function ( $wp_query ) {
- if ( array_key_exists( 'previous_events', $wp_query->query )
- && $wp_query->query['previous_events']
- ) {
- $wp_query->set( 'order', 'DESC' );
- }
- }
- );
+function se_event_get_previous_event( int $event_id, ?int $event_date_id = null ): ?WP_Post {
+ $options = get_option( 'se_options' );
+ $allow_grouping = isset( $options['treat_each_date_as_own_event'] ) ? 'on' === $options['treat_each_date_as_own_event'] : false;
+
+ // If we dont have an event date id, we need to get the event dates.
+ if ( ! $event_date_id ) {
+ $event_dates = se_event_get_event_dates( $event_id );
+ if ( empty( $event_dates ) ) {
+ return null;
+ }
+ $event_date_id = $event_dates[0]['id'];
+ }
- // Get the first previous event.
- $previous_events = new WP_Query(
+ // Define the query to get previous events.
+ $args = array(
+ 'post_type' => SE_Event_Post_Type::$event_date_post_type,
+ 'posts_per_page' => 1,
+ 'orderby' => 'meta_value_num',
+ 'meta_key' => 'se_event_date_start',
+ 'order' => 'DESC',
+ 'post_status' => 'publish',
+ 'meta_query' => array(
array(
- 'previous_events' => true,
- 'post_type' => 'se-event',
- 'posts_per_page' => 1,
- 'meta_query' => array(
- array(
- 'key' => 'se_event_date_start',
- 'value' => absint( $timestamp ),
- 'compare' => '<',
- 'type' => 'NUMERIC',
- ),
- ),
- )
+ 'key' => 'se_event_date_start',
+ 'value' => get_post_meta( $event_date_id, 'se_event_date_start', true ),
+ 'compare' => '<',
+ 'type' => 'NUMERIC',
+ ),
+ array(
+ 'key' => 'se_event_hide_from_feed',
+ 'value' => 1,
+ 'compare' => '!=',
+ ),
+ ),
+ );
+ // If we dont allow grouping, add the event id to parent not in.
+ if ( ! $allow_grouping ) {
+ $args['post__not_in'] = array_map(
+ function ( $post ) {
+ return $post['id'];
+ },
+ se_event_get_event_dates( $event_id )
);
- if ( ! $previous_events->have_posts() ) {
+ }
+
+ $query = new WP_Query( $args );
+
+ // If we have no posts, return null.
+ if ( ! $query->have_posts() ) {
return null;
}
- $event = $previous_events->posts[0];
+ // Get the first previous event.
+ $previous_event = $query->posts[0];
wp_reset_postdata();
- return $event;
+ return $previous_event;
}
if ( ! function_exists( 'se_expired_event_notice' ) ) {
@@ -472,14 +536,30 @@ function se_expired_event_notice() {
* @return void
*/
function se_template_event_content() {
+ global $post;
$show_on_frontend = get_post_meta( get_the_ID(), 'se_event_show_on_frontend', true );
-
if ( empty( $show_on_frontend ) ) {
return;
}
+ $date_display_formatter = new SE_Date_Display_Formatter( get_the_ID() );
+ $dates = se_event_get_event_dates( get_the_ID() );
+
+ // If we have an event date and we treating each date as own event, we need to get the event date id.
+ if ( se_event_treat_each_date_as_own_event() && isset( $post->event_date_id ) ) {
+ $dates = array_filter(
+ $dates,
+ function ( $date ) use ( $post ) {
+ return $date['id'] === $post->event_date_id;
+ }
+ );
+
+ $dates = array_values( $dates );
+ } else {
+ $date_display_formatter->set_date_only( true );
+ }
// Output the content for archive template.
- se_template_event_date();
+ echo wp_kses_post( $date_display_formatter->get_header_date( $dates ) );
se_template_event_location();
se_template_event_price();
se_template_event_ticket_stock();
diff --git a/src/templates/calendar/day/event.php b/src/templates/calendar/day/event.php
index bc369a0..a2b7749 100644
--- a/src/templates/calendar/day/event.php
+++ b/src/templates/calendar/day/event.php
@@ -27,7 +27,6 @@
if ( $se_hide_end_time ) {
$se_hide_css .= ' se-event-hide-end-time';
}
-
?>
@@ -51,7 +50,7 @@