Mail Parser: Completely remove Webklex IMAP and all dependcies

This commit is contained in:
johnnyq
2026-06-24 13:39:07 -04:00
parent 63ad3256ee
commit 171a0d38f8
779 changed files with 6408 additions and 82971 deletions

View File

@@ -0,0 +1,7 @@
# Security Policy
## Reporting a Vulnerability
To report a security vulnerability, please use the
[Tidelift security contact](https://tidelift.com/security).
Tidelift will coordinate the fix and disclosure.

View File

@@ -109,8 +109,8 @@
"phpdoc": "php phpdoc.php",
"phpmd": "phpmd src text /phpmd.xml",
"phpmd-test": "phpmd tests text /tests/phpmd-test.xml",
"phpstan": "phpstan analyse --configuration phpstan.neon",
"phpunit": "phpunit --verbose",
"phpstan": "phpstan analyse --configuration phpstan.neon --memory-limit 1024M",
"phpunit": "phpunit",
"style-check": [
"@phpcs",
"@phpstan",

View File

@@ -127,63 +127,102 @@ This project exists thanks to all the people who contribute.
Support this project by becoming a sponsor. Your logo will show up here with a link to your website.
<!-- <open-collective-sponsors> -->
<a title="Нова українська букмекерська контора" href="https://betking.com.ua/sports-book/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Ставки на спорт" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/c56d2fe2-f9fb-4d63-947c-77575f4b15c6/stavki.jpg" width="96" height="96"></a>
<a title="Онлайн казино 777 Україна" href="https://777.ua/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Онлайн казино 777" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/7e572d50-1ce8-4d69-ae12-86cc80371373/ok-ua-777.png" width="96" height="96"></a>
<a title="Non GamStop Bookies UK" href="https://netto.co.uk/betting-sites-not-on-gamstop/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Non GamStop Bookies UK" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/51bfaa05-02b3-4cd9-b1a4-9d0d8f34cbae/%D0%97%D0%BD%D1%96%D0%BC%D0%BE%D0%BA%20%D0%B5%D0%BA%D1%80%D0%B0%D0%BD%D0%B0%202025-07-04%20%D0%BE%2015.21.16%20(1)%20(1)%20(1).jpg" width="126" height="96"></a>
<a title="Trusted last mile route planning and route optimization" href="https://route4me.com/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Route4Me Route Planner" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/237386c3-48a2-47c6-97ac-5f888cdb4cda/Route4MeIconLogo.png" width="96" height="96"></a>
<a title="Bei Releaf erhalten Sie schnell und diskret Ihr Cannabis Rezept online. Unsere Ärzte prüfen Ihre Angaben und stellen bei Eignung das Rezept aus. Anschließend können Sie legal und sicher medizinisches Cannabis über unsere Partnerapotheken kaufen." href="https://releaf.com/de?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Releaf Medizinischer Cannabis Shop" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/b686d646-5029-4b4c-8cab-9645ab2679de/9da596d1-f48a-41ec-947d-a64dd8e7529c.png" width="96" height="96"></a>
<a title="Buitenlandse Online Casino" href="https://nl.trustpilot.com/review/buitenlandsecasino.vip?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Buitenlandse Online Casino" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/9465308a-8e44-4606-af5d-5a81a3c6567b/ChatGPT%20Image%20Nov%2015%2C%202025%2C%2002_53_25%20PM.png" width="96" height="96"></a>
<a title="Online Casino Zonder Registratie" href="https://nl.trustpilot.com/review/zonderregistratiecasinos.com?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Online Casino Zonder Registratie" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/cf382ddc-627b-4bd9-8870-43ee28d2a628/casino-zonder-registratie.png" width="127.5" height="96"></a>
<a title="Mi misión es la educación y transparencia en el mundo de los casinos online" href="https://www.educatransparencia.cl/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Transparencia en Casinos Online" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/c034fdcb-1d17-4c83-8105-f3cfa4f874d6/educalogocito.png" width="192" height="96"></a>
<a title="Wildflower" href="https://www.trustpilot.com/review/wildflower.uno?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Wildflower" src="https://images.opencollective.com/wildflower_cases/avatar/256.png" width="96" height="96"></a>
<a title="Best non Gamstop sites in the UK" href="https://www.pieria.co.uk/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Best non Gamstop sites in the UK" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/34e340b8-e1de-4932-8a76-1b3ce2ec7ee8/logo_white%20bg%20(8).png" width="96" height="96"></a>
<a title="gaia-wines.gr" href="https://www.gaia-wines.gr/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="gaia-wines.gr" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/a9b971ee-db5f-4400-8c4b-76cf9bc35015/IMAGE%202024-06-14%2013%3A54%3A14.jpg" width="96" height="96"></a>
<a title="#1 Guide To Online Gambling In Canada" href="https://casinohex.org/canada/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="CasinoHex Canada" src="https://opencollective-production.s3.us-west-1.amazonaws.com/79fdbcc0-a997-11eb-abbc-25e48b63c6dc.jpg" width="127.5" height="96"></a>
<a title="We specialize in the online gambling industry, helping players access reliable and verified information about the best online casinos and pokies in Australia. Our team tests casinos and games, collects user reviews from Trustpilot, and organizes them in o" href="https://au.trustpilot.com/review/bestpayidpokies.net?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="PayID Pokies" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/985a0ae7-54c3-4680-8816-bc8d656f7562/payidpokies.png" width="64" height="64"></a>
<a title="TopRating.casino ➢ Гід по онлайн-казино в Україні" href="https://toprating.casino/ua/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Top Rating casino" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/fec14fc4-85b1-4fdc-971e-e8aabfc60926/toprating%20logo.jpg" width="64" height="64"></a>
<a title="iDealeCasinos" href="https://idealecasinos.nl/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="iDealeCasinos" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/907841d3-435e-44b4-9684-c33fd8635ece/ideale-casinos-square-white-logo-300.png" width="64" height="64"></a>
<a title="Find the best Interac online casinos in Canada with secure payments, exclusive bonuses, and fast withdrawals." href="https://interac-casino.com/en-ca/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Interac-casino.com - Canada" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/865d9613-74db-45b6-9f33-3ba992682259/2025-09-10%2019.33.08.jpg" width="64" height="64"></a>
<a title="Porównanie kasyn online w Polsce. Darmowe automaty online." href="https://onlinekasyno-polis.pl/" target="_blank"><img alt="Online Kasyno Polis" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/12fe53d4-b2e4-4601-b9ea-7b652c414a38/274px%20274px-2.png" width="64" height="64"></a><details><summary>See more</summary>
<a title="OnlineCasinosSpelen" href="https://onlinecasinosspelen.com?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="OnlineCasinosSpelen" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/47e87426-6a55-4f69-9fb5-4e5032dc35a8/5d10dd22-320e-47d4-84e6-d144874f1f5f.png" width="64" height="64"></a>
<a title="Betwinner is an online bookmaker offering sports betting, casino games, and more." href="https://guidebook.betwinner.com/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Guidebook.BetWinner" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/82cab29a-7002-4924-83bf-2eecb03d07c4/0x0.png" width="64" height="64"></a>
<a title="Discover the Top 10 Online Casino Australia sites for fast payouts, big bonuses, and top real money pokies" href="https://au.trustpilot.com/review/top-10-online-casino-australia.com?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Top 10 Casino Australia" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/cd3f2230-ca38-41e1-9f31-f3069b910be3/top-10-online-casino-aus.jpg" width="64" height="64"></a>
<a title="We test dozens of casinos every month and select the coolest ones for Australian players." href="https://au.trustpilot.com/review/payid-casino.net?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="PayID Pokies Sites" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/ecd8a5f4-fd86-4903-a512-cbfaff35e7ef/payidpokiessites.png" width="69" height="64"></a>
<a title="We collect all reviews about the best online pokies." href="https://au.trustpilot.com/review/aussiepokies.net?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Online Pokies in Australia" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/44d84116-8762-4167-9433-a9e5da542e29/pokies.png" width="64" height="64"></a>
<a title="Plinko Game" href="https://www.trustpilot.com/review/plinkoplay.top?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Plinko Game" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/5e953df9-1876-45b6-9ffc-f2009804935b/plinkoo.png" width="64" height="64"></a>
<a title="Онлайн казино casino.ua" href="https://casino.ua/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Онлайн казино casino.ua" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/32790ee6-245b-45bd-acf7-7a661fe2cf9f/logo.png" width="64" height="64"></a>
<a title="Real Money Pokies" href="https://onlinecasinoskiwi.co.nz/real-money-pokies/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Real Money Pokies" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/d0f7382e-32ea-4425-a8c4-3019f9ed501c/NZ_logo%20(6)%20(2).jpg" width="64" height="64"></a>
<a title="Uudet Nettikasinot" href="https://fi.parhaatuudetkasinot.com/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="parhaatuudetkasinot.com" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/09e109d6-8ad2-4ade-ab24-4427028c8e38/260bfa9d-6a5c-494d-9ec0-a624327429ae.png" width="64" height="64"></a>
<a title="matej_j" href="https://matej.jurancic.com?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="matej_j" src="https://www.gravatar.com/avatar/a145a33757bfa7be46eb0b449485f13c?default=404" width="64" height="64"></a>
<a title="New Casino Bonuses" href="https://newcasinobonuses.gb.net/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="New Casino Bonuses" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/38e356d8-3597-406a-a263-1c348723aa17/new_casino_bonuses_logo.png" width="64" height="64"></a>
<a title="Best online sports betting and casino company." href="https://global.fun88.com/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Fun88" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/1688ebca-4984-4b9b-a24d-8fdd1233892f/fun88-logo.png" width="64" height="64"></a>
<a title="Top 50 Online Casinos UK" href="https://eastvillafc.co.uk?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="East Villa FC" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/2560f1b5-20d6-4cf4-83cd-2457bf111fc3/trophy-fav-blue.png" width="64" height="64"></a>
<a title="Instant withdrawal casino" href="https://au.trustpilot.com/review/quickcashoutcasinos.com?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Instant withdrawal casino" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/e4be26b6-a57a-4758-ad56-bd31c759465e/quickcashoutcasinos2.png" width="75" height="64"></a>
<a title="บริษัทรับแทงบอลออนไลน์ที่ดีที่สุดในประเทศไทย" href="https://www.fun88thh.com/th/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Fun88" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/bbf7174c-fc01-4ce6-86b5-f621e350969d/fun88-logo.png" width="64" height="64"></a>
<a title="Nhà cái cá cược thể thao trực tuyến tốt nhất tại Việt Nam" href="https://www.fun88vnu.com/vn/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Fun88" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/ed704e1d-3894-48bb-83e3-6094b2c68a5c/fun88-logo.png" width="64" height="64"></a>
<a title="Casino ohne oasis" href="https://de.trustpilot.com/review/onlinecasinoohneoasis.me?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Casino ohne oasis" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/f59e1e3a-6378-4aa7-91f5-6934a58bc21d/oasis.png" width="64" height="64"></a>
<a title="Najlepsi Bukmacherzy" href="https://najlepsibukmacherzy.pl/ranking-legalnych-bukmacherow/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Najlepsi Bukmacherzy" src="https://opencollective-production.s3.us-west-1.amazonaws.com/81588eb0-608a-11eb-ad1c-b119533ac7bb.png" width="64" height="64"></a>
<a title="Paysafecard Casino" href="https://de.trustpilot.com/review/onlinecasinospaysafecard.com?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Paysafecard Casino" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/641d34bb-f65f-4df5-a6ec-ad5ab35df1e0/paysafe.png" width="64" height="64"></a>
<a title="Offshore bookmakers review site." href="https://www.sportsbookreviewsonline.com/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Sportsbook Reviews Online" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/6d499f24-d669-4fc6-bb5f-b87184aa7963/sportsbookreviewsonline_com.png" width="42" height="42"></a>
<a title="Get professional support for Carbon" href="https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&amp;utm_medium=referral&amp;utm_campaign=docs" target="_blank"><img alt="Tidelift" src="https://carbonphp.github.io/carbon/sponsors/tidelift-brand.png" width="84" height="42"></a>
<a title="Ставки на спорт Favbet" href="https://www.favbet.ua/uk/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Ставки на спорт Favbet" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/d86d313e-7b17-42fa-8b76-3f17fbf681a2/favbet-logo.jpg" width="42" height="42"></a>
<a title="Casino-portugal.pt" href="https://casino-portugal.pt/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Casino-portugal.pt" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/870b4bd0-b6aa-4129-9827-e8ac02cfde56/167bdc1d-0a19-414d-a288-cfc3278b388f.png" width="42" height="42"></a>
<a title="Best Casinos not on Gamstop in the UK 2025" href="https://www.vso.org.uk/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="best non Gamstop casinos" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/3f48874e-f2f6-4062-a2a2-1500677ee3d9/125%D1%85125%20(1).jpg" width="42" height="42"></a>
<a title="https://play-fortune.pl/kasyno/z-minimalnym-depozytem/" href="https://play-fortune.pl/kasyno/z-minimalnym-depozytem/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="https://play-fortune.pl/kasyno/z-minimalnym-depozytem/" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/cbeea308-5148-4f6c-ac6e-dbfa029aadd1/PL.png" width="42" height="42"></a>
<a title="Playfortune.net.br" href="https://playfortune.net.br/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Playfortune.net.br" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/bf7ba31d-648c-470f-bf7e-2293c5ca0509/62a0353e-3858-41f1-bbb3-597449949e66.png" width="42" height="42"></a>
<a title="Znajdź najlepsze zakłady bukmacherskie w Polsce w 2023 roku. Probukmacher.pl to Twoje kompendium wiedzy na temat bukmacherów!" href="https://www.probukmacher.pl?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Probukmacher" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/caf50271-4560-4ffe-a434-ea15239168db/Screenshot_1.png" width="58" height="42"></a>
<a title="Онлайн казино та БК (ставки на спорт) в Україні" href="https://betking.com.ua/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="betking онлайн казино" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/08587758-582c-4136-aba5-2519230960d3/betking.jpg" width="42" height="42"></a>
<a title="BetPokies NZ features top fast payout casinos where winnings are processed within hours. The speed depends on the casino and payment method, with the platform highlighting the best options for smooth, reliable play." href="https://betpokies.co.nz/casinos/fast-payout?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Fastest Payout Online Casino" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/37cba042-a941-4bfd-87c4-b4f4c1a98641/7912b3ee-3c62-4ebd-b435-6a9a88b2228b.png" width="42" height="42"></a>
<a title="inkedin" href="https://inkedin.com?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="inkedin" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/6cb31863-b725-49c5-820c-2c2be7d54adf/cc930e12-04de-4574-9cca-171e07d277c3.png" width="42" height="42"></a>
<a title="Best PayID Pokies in Australia" href="https://ausgambler.net/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="AusGambler" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/b120ff74-a4cc-4e25-a96f-2b040d60de14/payidgambler.png" width="42" height="42"></a>
<a title="Актуальний та повносправний рейтинг онлайн казино України, ґрунтований на відгуках реальних гравців." href="https://uk.onlinecasino.in.ua/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Онлайн казино України" src="https://opencollective-production.s3.us-west-1.amazonaws.com/c0b4b090-eef8-11ec-9cb7-0527a205b226.png" width="42" height="42"></a>
<a title="Post-production resource scheduling app" href="https://freispace.com/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="freispace" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/7d553b00-78ff-4442-91dd-33c220e82ac4/freispace-icon-inverted.png" width="42" height="42"></a>
<a title="bestecasinozondercruks" href="https://nl.trustpilot.com/review/bestecasinozondercruks.online?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="bestecasinozondercruks" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/4948eca9-abd5-49ea-bb70-1ea6053f8663/ChatGPT%20Image%20Dec%2027%2C%202025%2C%2010_22_47%20PM-min.png" width="42" height="42"></a>
<a title="Run Weekly" href="https://www.runweekly.com/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Run Weekly" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/4d7e1fad-f160-4b64-ab41-d493023f9369/2de8e56c-e9fa-497c-8882-3736ec946664.png" width="42" height="42"></a>
<a title="Are you looking for the latest online casino sites to play in the UK? How about ones that actually deliver on their promises? Every year new online casinos pops up, but are they worth your time? Check out https://helpdirect.org.uk" href="https://helpdirect.org.uk/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="New Casino Sites UK (2025) Help Direct" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/d5e8c98f-7df3-40e8-bfe1-3c1583222eab/help-direct-logo-white-trans.png" width="42" height="42"></a>
<a title="UnAIMyText" href="https://unaimytext.com/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="UnAIMyText" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/fa5b36d5-69e6-44a4-bbdf-b5d264399365/icon_resized.png" width="42" height="42"></a>
<a title="Online Casino utan Svensk licens" href="https://se.trustpilot.com/review/casino-utan-svensk-licens-se.se?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Online Casino utan Svensk licens" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/64a7a6b0-e843-4cc2-a04e-0e829ccade58/logose.png" width="42" height="42"></a>
<a title="Slots City® ➢ Лучшее лицензионно казино онлайн и оффлайн на гривны в Украине. 【 Более1500 игровых автоматов и слотов】✅ Официально и Безопасно" href="https://slotscity.ua/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Slots City" src="https://opencollective-production.s3.us-west-1.amazonaws.com/d7e298c0-7abe-11ed-8553-230872f5e54d.png" width="59" height="42"></a>
<a title="ssddanbrown" href="https://github.com/ssddanbrown" target="_blank"><img alt="ssddanbrown" src="https://avatars.githubusercontent.com/u/8343178?s=128&v=4" width="42" height="42"></a></details><!-- </open-collective-sponsors> -->
<table>
<tr>
<td><a title="Онлайн казино 777 Україна" href="https://777.ua/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Онлайн казино 777" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/7e572d50-1ce8-4d69-ae12-86cc80371373/ok-ua-777.png" width="96" height="96"></a></td>
<td><a title="Нова українська букмекерська контора" href="https://betking.com.ua/sports-book/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Ставки на спорт" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/c56d2fe2-f9fb-4d63-947c-77575f4b15c6/stavki.jpg" width="96" height="96"></a></td>
<td><a title="Non GamStop Bookies UK" href="https://netto.co.uk/betting-sites-not-on-gamstop/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Non GamStop Bookies UK" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/51bfaa05-02b3-4cd9-b1a4-9d0d8f34cbae/%D0%97%D0%BD%D1%96%D0%BC%D0%BE%D0%BA%20%D0%B5%D0%BA%D1%80%D0%B0%D0%BD%D0%B0%202025-07-04%20%D0%BE%2015.21.16%20(1)%20(1)%20(1).jpg" width="126" height="96"></a></td>
<td><a title="Trusted last mile route planning and route optimization" href="https://route4me.com/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Route4Me Route Planner" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/237386c3-48a2-47c6-97ac-5f888cdb4cda/Route4MeIconLogo.png" width="96" height="96"></a></td>
<td><a title="Bei Releaf erhalten Sie schnell und diskret Ihr Cannabis Rezept online. Unsere Ärzte prüfen Ihre Angaben und stellen bei Eignung das Rezept aus. Anschließend können Sie legal und sicher medizinisches Cannabis über unsere Partnerapotheken kaufen." href="https://releaf.com/de?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Releaf Medizinischer Cannabis Shop" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/b686d646-5029-4b4c-8cab-9645ab2679de/9da596d1-f48a-41ec-947d-a64dd8e7529c.png" width="96" height="96"></a></td>
<td><a title="Best non Gamstop sites in the UK" href="https://www.pieria.co.uk/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Best non Gamstop sites in the UK" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/34e340b8-e1de-4932-8a76-1b3ce2ec7ee8/logo_white%20bg%20(8).png" width="96" height="96"></a></td>
<td><a title="#1 Guide To Online Gambling In Canada" href="https://casinohex.org/canada/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="CasinoHex Canada" src="https://opencollective-production.s3.us-west-1.amazonaws.com/79fdbcc0-a997-11eb-abbc-25e48b63c6dc.jpg" width="127.5" height="96"></a></td>
<td><a title="We specialize in the online gambling industry, helping players access reliable and verified information about the best online casinos and pokies in Australia. Our team tests casinos and games, collects user reviews from Trustpilot, and organizes them in o" href="https://au.trustpilot.com/review/bestpayidpokies.net?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="PayID Pokies" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/985a0ae7-54c3-4680-8816-bc8d656f7562/payidpokies.png" width="64" height="64"></a></td>
</tr>
<tr>
<td><a title="Mi misión es la educación y transparencia en el mundo de los casinos online" href="https://www.educatransparencia.cl/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Transparencia en Casinos Online" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/c034fdcb-1d17-4c83-8105-f3cfa4f874d6/educalogocito.png" width="128" height="64"></a></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</table>
<details><summary>See more</summary>
<table>
<tr>
<td><a title="gaia-wines.gr" href="https://www.gaia-wines.gr/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="gaia-wines.gr" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/a9b971ee-db5f-4400-8c4b-76cf9bc35015/IMAGE%202024-06-14%2013%3A54%3A14.jpg" width="96" height="96"></a></td>
<td><a title="OnlineCasinosSpelen" href="https://onlinecasinosspelen.com?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="OnlineCasinosSpelen" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/47e87426-6a55-4f69-9fb5-4e5032dc35a8/5d10dd22-320e-47d4-84e6-d144874f1f5f.png" width="64" height="64"></a></td>
<td><a title="We test dozens of casinos every month and select the coolest ones for Australian players." href="https://au.trustpilot.com/review/payid-casino.net?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="PayID Pokies Sites" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/ecd8a5f4-fd86-4903-a512-cbfaff35e7ef/payidpokiessites.png" width="69" height="64"></a></td>
<td><a title="We collect all reviews about the best online pokies." href="https://au.trustpilot.com/review/aussiepokies.net?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Online Pokies in Australia" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/44d84116-8762-4167-9433-a9e5da542e29/pokies.png" width="64" height="64"></a></td>
<td><a title="Plinko Game" href="https://www.trustpilot.com/review/plinkoplay.top?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Plinko Game" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/5e953df9-1876-45b6-9ffc-f2009804935b/plinkoo.png" width="64" height="64"></a></td>
<td><a title="Casinomithandyrechnung" href="https://at.trustpilot.com/review/casinomithandyrechnung.net?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Casinomithandyrechnung" src="https://images.opencollective.com/casinomithandyrechnung/avatar/256.png" width="64" height="64"></a></td>
<td><a title="lolajack" href="https://nl.trustpilot.com/review/lolajack-casino-nl.com?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="lolajack" src="https://images.opencollective.com/lolajack/avatar/256.png" width="64" height="64"></a></td>
<td><a title="marketing in germany" href="https://de.trustpilot.com/review/paysafecardcasinos.org?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Paysafecard Casino" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/1b2f45ea-02bc-4a78-a84a-355f68c8a293/paysafe.png" width="64" height="64"></a></td>
<td><a title="Games" href="https://au.trustpilot.com/review/bestaustraliaonlinepokies.com?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Australian Online Pokies" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/7ca6ca2e-6542-4736-8952-b9adb7f4c9cf/39aac4bc53ff11f198d9ce36ed903bf5_1.jpg" width="64" height="64"></a></td>
<td><a title="Buitenlandse Online Casino" href="https://nl.trustpilot.com/review/buitenlandsecasino.vip?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Buitenlandse Online Casino" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/9465308a-8e44-4606-af5d-5a81a3c6567b/ChatGPT%20Image%20Nov%2015%2C%202025%2C%2002_53_25%20PM.png" width="64" height="64"></a></td>
<td><a title="Betwinner is an online bookmaker offering sports betting, casino games, and more." href="https://guidebook.betwinner.com/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Guidebook.BetWinner" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/82cab29a-7002-4924-83bf-2eecb03d07c4/0x0.png" width="64" height="64"></a></td>
<td><a title="Porównanie kasyn online w Polsce. Darmowe automaty online." href="https://onlinekasyno-polis.pl/" target="_blank"><img alt="Online Kasyno Polis" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/12fe53d4-b2e4-4601-b9ea-7b652c414a38/274px%20274px-2.png" width="64" height="64"></a></td>
</tr>
<tr>
<td><a title="Online Casino Zonder Registratie" href="https://nl.trustpilot.com/review/zonderregistratiecasinos.com?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Online Casino Zonder Registratie" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/cf382ddc-627b-4bd9-8870-43ee28d2a628/casino-zonder-registratie.png" width="85" height="64"></a></td>
<td><a title="TopRating.casino ➢ Гід по онлайн-казино в Україні" href="https://toprating.casino/ua/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Top Rating casino" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/fec14fc4-85b1-4fdc-971e-e8aabfc60926/toprating%20logo.jpg" width="64" height="64"></a></td>
<td><a title="iDealeCasinos" href="https://idealecasinos.nl/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="iDealeCasinos" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/907841d3-435e-44b4-9684-c33fd8635ece/ideale-casinos-square-white-logo-300.png" width="64" height="64"></a></td>
<td><a title="Uudet Nettikasinot" href="https://fi.parhaatuudetkasinot.com/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="parhaatuudetkasinot.com" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/09e109d6-8ad2-4ade-ab24-4427028c8e38/260bfa9d-6a5c-494d-9ec0-a624327429ae.png" width="64" height="64"></a></td>
<td><a title="New Casino Bonuses" href="https://newcasinobonuses.gb.net/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="New Casino Bonuses" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/38e356d8-3597-406a-a263-1c348723aa17/new_casino_bonuses_logo.png" width="64" height="64"></a></td>
<td><a title="Best online sports betting and casino company." href="https://global.fun88.com/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Fun88" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/1688ebca-4984-4b9b-a24d-8fdd1233892f/fun88-logo.png" width="64" height="64"></a></td>
<td><a title="Top 50 Online Casinos UK" href="https://eastvillafc.co.uk?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="East Villa FC" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/2560f1b5-20d6-4cf4-83cd-2457bf111fc3/trophy-fav-blue.png" width="64" height="64"></a></td>
<td><a title="บริษัทรับแทงบอลออนไลน์ที่ดีที่สุดในประเทศไทย" href="https://www.fun88thf.com/th/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Fun88" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/bbf7174c-fc01-4ce6-86b5-f621e350969d/fun88-logo.png" width="64" height="64"></a></td>
<td><a title="Nhà cái cá cược thể thao trực tuyến tốt nhất tại Việt Nam" href="https://www.fun88fp.com/vn/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Fun88" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/ed704e1d-3894-48bb-83e3-6094b2c68a5c/fun88-logo.png" width="64" height="64"></a></td>
<td><a title="Casino ohne oasis" href="https://de.trustpilot.com/review/onlinecasinoohneoasis.me?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Casino ohne oasis" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/f59e1e3a-6378-4aa7-91f5-6934a58bc21d/oasis.png" width="64" height="64"></a></td>
<td><a title="Najlepsi Bukmacherzy" href="https://najlepsibukmacherzy.pl/ranking-legalnych-bukmacherow/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Najlepsi Bukmacherzy" src="https://opencollective-production.s3.us-west-1.amazonaws.com/81588eb0-608a-11eb-ad1c-b119533ac7bb.png" width="64" height="64"></a></td>
<td><a title="Paysafecard Casino" href="https://de.trustpilot.com/review/onlinecasinospaysafecard.com?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Paysafecard Casino" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/641d34bb-f65f-4df5-a6ec-ad5ab35df1e0/paysafe.png" width="64" height="64"></a></td>
</tr>
<tr>
<td><a title="WNB Casino Bewertung" href="https://wnb-casino.de/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="WNB Casino Bewertung" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/1adcf96c-dba5-40c0-aa5f-cc352e98b376/WNB%20Casino%20Bewertung.png" width="64" height="64"></a></td>
<td><a title="Casino mit Schneller Auszahlung" href="https://de.trustpilot.com/review/casinoschnellauszahlung.com?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Casino mit Schneller Auszahlung" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/031e76f8-0041-4c7c-a942-2d8f21b5a54b/casinoschnellauszahlung.com%202.png" width="64" height="64"></a></td>
<td><a title="Casinos ohne OASIS" href="https://de.trustpilot.com/review/ohneoasis.de?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Casinos ohne OASIS" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/49904abe-0786-4b30-a9c8-6c508441f7c7/ohneoasis.de.png" width="65" height="64"></a></td>
<td><a title="Wettanbieter ohne OASIS" href="https://de.trustpilot.com/review/wettanbieterohneoasis.io?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Wettanbieter ohne OASIS" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/44468112-6a16-4cc7-aa0a-d30020fa78a5/wettenoasis2.png" width="62" height="64"></a></td>
<td><a title="Casino ohne Lizenz" href="https://de.trustpilot.com/review/casinoohnelizenz.org?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Casino ohne Lizenz" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/f89d7bf4-c7d9-4ad1-a9e5-a904ba63126f/casinoohnelizenz.org.png" width="64" height="64"></a></td>
<td><a title="Casino ohne Limit" href="https://de.trustpilot.com/review/limitfreicasino.com?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Casino ohne Limit" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/4525f686-fc19-47c6-9087-d5f41ecd6cfd/limitfreicasino.com.png" width="64" height="64"></a></td>
<td><a title="Casino ohne Verifizierung" href="https://de.trustpilot.com/review/ohneverifizierungcasino.com?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Casino ohne Verifizierung" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/2255004a-1c42-4399-83bc-32c21a65b66a/ohneverifizierungcasino.com.png" width="64" height="64"></a></td>
<td><a title="Casino ohne Anmeldung" href="https://de.trustpilot.com/review/ohneanmeldungcasino.com?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Casino ohne Anmeldung" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/0be23739-2dd0-4f3f-85a3-8d1d67164681/ohneanmeldungcasino.com.png" width="64" height="64"></a></td>
<td><a title="Crypto Casino" href="https://de.trustpilot.com/review/cryptocasinozone.net?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Crypto Casino" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/a5a37da5-46ad-4940-8d58-7cf4326bd53d/cryptocasinozone.net.png" width="64" height="64"></a></td>
<td><a title="Paypal Casino" href="https://de.trustpilot.com/review/paypalcasinosite.com?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Paypal Casino" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/2c3c6c04-e009-4f93-a080-4f5f83b875b9/paypalcasinosite.com.png" width="64" height="64"></a></td>
<td><a title="Онлайн казино casino.ua" href="https://casino.ua/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Онлайн казино casino.ua" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/32790ee6-245b-45bd-acf7-7a661fe2cf9f/logo.png" width="64" height="64"></a></td>
<td><a title="Real Money Pokies" href="https://onlinecasinoskiwi.co.nz/real-money-pokies/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Real Money Pokies" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/d0f7382e-32ea-4425-a8c4-3019f9ed501c/NZ_logo%20(6)%20(2).jpg" width="42" height="42"></a></td>
</tr>
<tr>
<td><a title="Get professional support for Carbon" href="https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&amp;utm_medium=referral&amp;utm_campaign=docs" target="_blank"><img alt="Tidelift" src="https://carbonphp.github.io/carbon/sponsors/tidelift-brand.png" width="84" height="42"></a></td>
<td><a title="Find the best Interac online casinos in Canada with secure payments, exclusive bonuses, and fast withdrawals." href="https://interac-casino.com/en-ca/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Interac-casino.com - Canada" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/865d9613-74db-45b6-9f33-3ba992682259/2025-09-10%2019.33.08.jpg" width="42" height="42"></a></td>
<td><a title="Discover the Top 10 Online Casino Australia sites for fast payouts, big bonuses, and top real money pokies" href="https://au.trustpilot.com/review/top-10-online-casino-australia.com?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Top 10 Casino Australia" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/cd3f2230-ca38-41e1-9f31-f3069b910be3/top-10-online-casino-aus.jpg" width="42" height="42"></a></td>
<td><a title="Offshore bookmakers review site." href="https://www.sportsbookreviewsonline.com/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Sportsbook Reviews Online" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/6d499f24-d669-4fc6-bb5f-b87184aa7963/sportsbookreviewsonline_com.png" width="42" height="42"></a></td>
<td><a title="Wildflower" href="https://www.trustpilot.com/review/wildflower.uno?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Wildflower" src="https://images.opencollective.com/wildflower_cases/avatar/256.png" width="42" height="42"></a></td>
<td><a title="Casino-portugal.pt" href="https://casino-portugal.pt/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Casino-portugal.pt" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/870b4bd0-b6aa-4129-9827-e8ac02cfde56/167bdc1d-0a19-414d-a288-cfc3278b388f.png" width="42" height="42"></a></td>
<td><a title="Ставки на спорт Favbet" href="https://www.favbet.ua/uk/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Ставки на спорт Favbet" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/d86d313e-7b17-42fa-8b76-3f17fbf681a2/favbet-logo.jpg" width="42" height="42"></a></td>
<td><a title="Znajdź najlepsze zakłady bukmacherskie w Polsce w 2023 roku. Probukmacher.pl to Twoje kompendium wiedzy na temat bukmacherów!" href="https://www.probukmacher.pl?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Probukmacher" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/caf50271-4560-4ffe-a434-ea15239168db/Screenshot_1.png" width="58" height="42"></a></td>
<td><a title="Instant withdrawal casino" href="https://au.trustpilot.com/review/quickcashoutcasinos.com?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Instant withdrawal casino" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/e4be26b6-a57a-4758-ad56-bd31c759465e/quickcashoutcasinos2.png" width="49" height="42"></a></td>
<td><a title="Best Casinos not on Gamstop in the UK 2025" href="https://www.vso.org.uk/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="best non Gamstop casinos" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/3f48874e-f2f6-4062-a2a2-1500677ee3d9/125%D1%85125%20(1).jpg" width="42" height="42"></a></td>
<td><a title="inkedin" href="https://inkedin.com?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="inkedin" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/6cb31863-b725-49c5-820c-2c2be7d54adf/cc930e12-04de-4574-9cca-171e07d277c3.png" width="42" height="42"></a></td>
<td><a title="Онлайн казино та БК (ставки на спорт) в Україні" href="https://betking.com.ua/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="betking онлайн казино" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/08587758-582c-4136-aba5-2519230960d3/betking.jpg" width="42" height="42"></a></td>
</tr>
<tr>
<td><a title="Post-production resource scheduling app" href="https://freispace.com/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="freispace" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/7d553b00-78ff-4442-91dd-33c220e82ac4/freispace-icon-inverted.png" width="42" height="42"></a></td>
<td><a title="bestecasinozondercruks" href="https://nl.trustpilot.com/review/bestecasinozondercruks.online?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="bestecasinozondercruks" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/4948eca9-abd5-49ea-bb70-1ea6053f8663/ChatGPT%20Image%20Dec%2027%2C%202025%2C%2010_22_47%20PM-min.png" width="42" height="42"></a></td>
<td><a title="Run Weekly" href="https://www.runweekly.com/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="Run Weekly" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/4d7e1fad-f160-4b64-ab41-d493023f9369/2de8e56c-e9fa-497c-8882-3736ec946664.png" width="42" height="42"></a></td>
<td><a title="Are you looking for the latest online casino sites to play in the UK? How about ones that actually deliver on their promises? Every year new online casinos pops up, but are they worth your time? Check out https://helpdirect.org.uk" href="https://helpdirect.org.uk/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="New Casino Sites UK (2025) Help Direct" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/d5e8c98f-7df3-40e8-bfe1-3c1583222eab/help-direct-logo-white-trans.png" width="42" height="42"></a></td>
<td><a title="UnAIMyText" href="https://unaimytext.com/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="UnAIMyText" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/fa5b36d5-69e6-44a4-bbdf-b5d264399365/icon_resized.png" width="42" height="42"></a></td>
<td><a title="TopKasynoOnline PL" href="https://pl.topkasynoonline.com/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank"><img alt="TopKasynoOnline PL" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/6cdee969-d731-44d6-b8ff-fc4f6d4dab9c/TKOlogo.png" width="42" height="42"></a></td>
<td><a title="ssddanbrown" href="https://github.com/ssddanbrown" target="_blank"><img alt="ssddanbrown" src="https://avatars.githubusercontent.com/u/8343178?s=128&v=4" width="42" height="42"></a></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</table>
</details><!-- </open-collective-sponsors> -->
[[See all](https://carbonphp.github.io/carbon/#sponsors)]

View File

@@ -48,6 +48,13 @@ abstract class AbstractTranslator extends SymfonyTranslator
*/
protected array $directories = [];
/**
* Cache for language files.
*
* @var array<string, array>
*/
protected array $fileCache = [];
/**
* Set to true while constructing.
*/
@@ -171,7 +178,7 @@ abstract class AbstractTranslator extends SymfonyTranslator
foreach ($this->getDirectories() as $directory) {
$file = \sprintf('%s/%s.php', rtrim($directory, '\\/'), $locale);
$data = @include $file;
$data = ($this->fileCache[$file] ??= self::loadFile($file));
if ($data !== false) {
$this->messages[$locale] = $data;
@@ -305,7 +312,7 @@ abstract class AbstractTranslator extends SymfonyTranslator
*/
public function getMessages(?string $locale = null): array
{
return $locale === null ? $this->messages : $this->messages[$locale];
return $locale === null ? $this->messages : ($this->messages[$locale] ?? []);
}
/**
@@ -315,6 +322,12 @@ abstract class AbstractTranslator extends SymfonyTranslator
*/
public function setLocale($locale): void
{
$previousLocale = $this->getLocale();
if ($previousLocale === $locale && isset($this->messages[$locale])) {
return;
}
$locale = preg_replace_callback('/[-_]([a-z]{2,}|\d{2,})/', function ($matches) {
// _2-letters or YUE is a region, _3+-letters is a variant
$upper = strtoupper($matches[1]);
@@ -326,8 +339,6 @@ abstract class AbstractTranslator extends SymfonyTranslator
return '_'.ucfirst($matches[1]);
}, strtolower($locale));
$previousLocale = $this->getLocale();
if ($previousLocale === $locale && isset($this->messages[$locale])) {
return;
}
@@ -407,7 +418,12 @@ abstract class AbstractTranslator extends SymfonyTranslator
$this->initializing = false;
}
private static function compareChunkLists($referenceChunks, $chunks)
private function loadFile(string $file): array|false
{
return file_exists($file) ? (include $file) : false;
}
private static function compareChunkLists(array $referenceChunks, array $chunks): int
{
$score = 0;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -437,6 +437,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface, U
$spec = $years;
$isStringSpec = (\is_string($spec) && !preg_match('/^[\d.]/', $spec));
$inverted = false;
if (!$isStringSpec || (float) $years) {
$spec = static::PERIOD_PREFIX;
@@ -463,8 +464,17 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface, U
}
}
if ($isStringSpec && str_starts_with($spec, '-')) {
$inverted = true;
$spec = substr($spec, 1);
}
try {
parent::__construct($spec);
if ($inverted) {
$this->invert = 1;
}
} catch (Throwable $exception) {
try {
parent::__construct('PT0S');
@@ -548,8 +558,17 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface, U
}
foreach (['years', 'months', 'weeks', 'days', 'hours', 'minutes', 'seconds'] as $unit) {
if ($$unit < 0) {
$this->set($unit, $$unit);
$value = $$unit;
if (
(
\is_int($value)
|| \is_float($value)
|| (\is_string($value) && preg_match('/^[\d.-]+$/', $value))
)
&& $value < 0
) {
$this->set($unit, $value);
}
}
}
@@ -763,6 +782,42 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface, U
return $instance;
}
public static function monthWithAnchorDay(int $day): static
{
return new static(function (CarbonInterface $date, bool $negated) use ($day) {
$next = $date->day(1)->addMonths($negated ? -1 : 1);
return $next->day(min($day, $next->daysInMonth));
});
}
public static function monthNoOverflow(): static
{
return new static(static function (CarbonInterface $date, bool $negated) {
return $negated
? $date->subMonthNoOverflow()
: $date->addMonthNoOverflow();
});
}
public static function yearWithAnchorDay(int $day): static
{
return new static(function (CarbonInterface $date, bool $negated) use ($day) {
$next = $date->day(1)->addYears($negated ? -1 : 1);
return $next->day(min($day, $next->daysInMonth));
});
}
public static function yearNoOverflow(): static
{
return new static(static function (CarbonInterface $date, bool $negated) {
return $negated
? $date->subYearNoOverflow()
: $date->addYearNoOverflow();
});
}
/**
* Return the original source used to create the current interval.
*
@@ -1117,8 +1172,8 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface, U
*/
public static function diff($start, $end = null, bool $absolute = false, array $skip = []): static
{
$start = $start instanceof CarbonInterface ? $start : Carbon::make($start);
$end = $end instanceof CarbonInterface ? $end : Carbon::make($end);
$start = self::carbonOrMake($start);
$end = self::carbonOrMake($end);
$rawInterval = $start->diffAsDateInterval($end, $absolute);
$interval = static::instance($rawInterval, $skip);
@@ -2159,6 +2214,14 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface, U
$class = ($params[0] ?? null) instanceof DateTime ? CarbonPeriod::class : CarbonPeriodImmutable::class;
if ($this->step) {
$dates = array_filter($params, static fn (mixed $param) => $param instanceof DateTimeInterface);
if (\count($dates) >= 2 && $dates[0] > $dates[1]) {
$this->invert();
}
}
return $class::create($this, ...$params);
}
@@ -2225,13 +2288,15 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface, U
/**
* Add the passed interval to the current instance.
*
* @param string|DateInterval $unit
* @param int|float $value
* @param Unit|string|DateInterval $unit
* @param int|float $value
*
* @return $this
*/
public function add($unit, $value = 1): static
{
$this->checkNoStepIsDefined(__METHOD__);
if (is_numeric($unit)) {
[$value, $unit] = [$unit, $value];
}
@@ -2268,13 +2333,15 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface, U
/**
* Subtract the passed interval to the current instance.
*
* @param string|DateInterval $unit
* @param int|float $value
* @param Unit|string|DateInterval $unit
* @param int|float $value
*
* @return $this
*/
public function sub($unit, $value = 1): static
{
$this->checkNoStepIsDefined(__METHOD__);
if (is_numeric($unit)) {
[$value, $unit] = [$unit, $value];
}
@@ -2326,7 +2393,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface, U
}
/**
* Add given parameters to the current interval.
* Subtract given parameters to the current interval.
*
* @param int $years
* @param int $months
@@ -2372,6 +2439,8 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface, U
*/
public function times($factor): static
{
$this->checkNoStepIsDefined(__METHOD__);
if ($factor < 0) {
$this->invert = $this->invert ? 0 : 1;
$factor = -$factor;
@@ -2434,6 +2503,8 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface, U
*/
public function multiply($factor): static
{
$this->checkNoStepIsDefined(__METHOD__);
if ($factor < 0) {
$this->invert = $this->invert ? 0 : 1;
$factor = -$factor;
@@ -2472,38 +2543,46 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface, U
*
* @return string
*/
public static function getDateIntervalSpec(DateInterval $interval, bool $microseconds = false, array $skip = []): string
{
public static function getDateIntervalSpec(
DateInterval $interval,
bool $microseconds = false,
array $skip = [],
bool $withNegatives = false,
): string {
$date = array_filter([
static::PERIOD_YEARS => abs($interval->y),
static::PERIOD_MONTHS => abs($interval->m),
static::PERIOD_DAYS => abs($interval->d),
static::PERIOD_YEARS => $withNegatives ? $interval->y : abs($interval->y),
static::PERIOD_MONTHS => $withNegatives ? $interval->m : abs($interval->m),
static::PERIOD_DAYS => $withNegatives ? $interval->d : abs($interval->d),
]);
$skip = array_map([Unit::class, 'toNameIfUnit'], $skip);
$days = abs((int) $interval->days);
if (
$interval->days >= CarbonInterface::DAYS_PER_WEEK * CarbonInterface::WEEKS_PER_MONTH &&
$days >= CarbonInterface::DAYS_PER_WEEK * CarbonInterface::WEEKS_PER_MONTH &&
(!isset($date[static::PERIOD_YEARS]) || \count(array_intersect(['y', 'year', 'years'], $skip))) &&
(!isset($date[static::PERIOD_MONTHS]) || \count(array_intersect(['m', 'month', 'months'], $skip)))
) {
$date = [
static::PERIOD_DAYS => abs($interval->days),
static::PERIOD_DAYS => $withNegatives ? $interval->days : $days,
];
}
$seconds = abs($interval->s);
if ($microseconds && $interval->f > 0) {
$seconds = \sprintf('%d.%06d', $seconds, abs($interval->f) * 1000000);
$seconds = $withNegatives ? $interval->s : abs($interval->s);
if ($microseconds && $interval->f !== 0.0) {
$seconds = $withNegatives
? number_format($seconds + $interval->f, 6, '.', '')
: \sprintf('%d.%06d', $seconds, abs($interval->f) * 1000000);
}
$time = array_filter([
static::PERIOD_HOURS => abs($interval->h),
static::PERIOD_MINUTES => abs($interval->i),
static::PERIOD_HOURS => $withNegatives ? $interval->h : abs($interval->h),
static::PERIOD_MINUTES => $withNegatives ? $interval->i : abs($interval->i),
static::PERIOD_SECONDS => $seconds,
]);
$specString = static::PERIOD_PREFIX;
$specString = ($withNegatives && $interval->invert ? '-' : '').static::PERIOD_PREFIX;
foreach ($date as $key => $value) {
$specString .= $value.$key;
@@ -2511,6 +2590,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface, U
if (\count($time) > 0) {
$specString .= static::PERIOD_TIME_PREFIX;
foreach ($time as $key => $value) {
$specString .= $value.$key;
}
@@ -2524,9 +2604,9 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface, U
*
* @return string
*/
public function spec(bool $microseconds = false): string
public function spec(bool $microseconds = false, bool $withNegatives = false): string
{
return static::getDateIntervalSpec($this, $microseconds);
return static::getDateIntervalSpec($this, $microseconds, [], $withNegatives);
}
/**
@@ -2981,6 +3061,12 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface, U
)->invert($inverted)->cascade());
}
$initiallyInverted = (bool) $this->invert;
if ($initiallyInverted) {
$this->invert();
}
$base = CarbonImmutable::parse('2000-01-01 00:00:00', 'UTC')
->roundUnit($unit, $precision, $function);
$next = $base->add($this);
@@ -2996,7 +3082,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface, U
->diff($base),
);
return $this->invert($inverted);
return $this->invert($initiallyInverted xor $inverted);
}
/**
@@ -3514,6 +3600,13 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface, U
}
}
private static function carbonOrMake(mixed $dateTime): CarbonInterface
{
return $dateTime instanceof CarbonInterface
? $dateTime
: Carbon::make($dateTime);
}
private static function incrementUnit(DateInterval $instance, string $unit, int $value): void
{
if ($value === 0) {

View File

@@ -426,6 +426,96 @@ class CarbonPeriod extends DatePeriodBase implements Countable, JsonSerializable
return self::createFromIso($iso, $options);
}
public static function monthly(
DateTimeInterface|string|int|null $start = null,
DateTimeInterface|string|int|null $end = null,
?int $recurrences = null,
?int $anchorDay = null,
OverflowMode $mode = OverflowMode::AnchorDay,
?int $options = null,
): static {
if ($anchorDay !== null && $mode !== OverflowMode::AnchorDay) {
throw new InvalidArgumentException(
'$anchorDay parameter must not be set for $mode OverflowMode::'.$mode->name,
);
}
if ($end !== null && $recurrences !== null) {
throw new InvalidArgumentException(
'You must specify $end or $recurrences but not both',
);
}
if (\is_int($start)) {
$start = CarbonImmutable::createFromTimestamp($start);
} elseif (\is_string($start)) {
$start = CarbonImmutable::parse($start);
}
$start ??= CarbonImmutable::now();
if (\is_int($end)) {
$end = CarbonImmutable::createFromTimestamp($end);
}
return (new static(
$start,
match ($mode) {
OverflowMode::AnchorDay => CarbonInterval::monthWithAnchorDay(
$anchorDay ?? $start->day,
),
OverflowMode::NoOverflow => CarbonInterval::monthNoOverflow(),
OverflowMode::Overflow => CarbonInterval::month(),
},
$end ?? $recurrences,
))->setOptions($options ?? self::IMMUTABLE);
}
public static function yearly(
DateTimeInterface|string|int|null $start = null,
DateTimeInterface|string|int|null $end = null,
?int $recurrences = null,
?int $anchorDay = null,
OverflowMode $mode = OverflowMode::AnchorDay,
?int $options = null,
): static {
if ($anchorDay !== null && $mode !== OverflowMode::AnchorDay) {
throw new InvalidArgumentException(
'$anchorDay parameter must not be set for $mode OverflowMode::'.$mode->name,
);
}
if ($end !== null && $recurrences !== null) {
throw new InvalidArgumentException(
'You must specify $end or $recurrences but not both',
);
}
if (\is_int($start)) {
$start = CarbonImmutable::createFromTimestamp($start);
} elseif (\is_string($start)) {
$start = CarbonImmutable::parse($start);
}
$start ??= CarbonImmutable::now();
if (\is_int($end)) {
$end = CarbonImmutable::createFromTimestamp($end);
}
return (new static(
$start,
match ($mode) {
OverflowMode::AnchorDay => CarbonInterval::yearWithAnchorDay(
$anchorDay ?? $start->day,
),
OverflowMode::NoOverflow => CarbonInterval::yearNoOverflow(),
OverflowMode::Overflow => CarbonInterval::month(),
},
$end ?? $recurrences,
))->setOptions($options ?? self::IMMUTABLE);
}
/**
* Return whether the given interval contains non-zero value of any time unit.
*/
@@ -695,20 +785,6 @@ class CarbonPeriod extends DatePeriodBase implements Countable, JsonSerializable
}
}
if ($raw === null && isset($sortedArguments['start'])) {
$end = $sortedArguments['end'] ?? max(1, $sortedArguments['recurrences'] ?? 1);
if (\is_float($end)) {
$end = $end === INF ? PHP_INT_MAX : (int) round($end);
}
$raw = [
$sortedArguments['start'],
$sortedArguments['interval'] ?? CarbonInterval::day(),
$end,
];
}
$this->setFromAssociativeArray($sortedArguments);
if ($this->startDate === null) {
@@ -1250,13 +1326,7 @@ class CarbonPeriod extends DatePeriodBase implements Countable, JsonSerializable
$self = $this->copyIfImmutable();
$self->carbonRecurrences = $recurrences === INF ? INF : (int) $recurrences;
if (!$self->hasFilter(static::RECURRENCES_FILTER)) {
return $self->addFilter(static::RECURRENCES_FILTER);
}
$self->handleChangedParameters();
return $self;
return self::addFilterOrHandleChangedParameters($self, static::RECURRENCES_FILTER);
}
/**
@@ -1282,6 +1352,8 @@ class CarbonPeriod extends DatePeriodBase implements Countable, JsonSerializable
$self = $self->toggleOptions(static::EXCLUDE_START_DATE, !$inclusive);
}
$self->syncNativePeriod();
return $self;
}
@@ -1301,22 +1373,38 @@ class CarbonPeriod extends DatePeriodBase implements Countable, JsonSerializable
throw new InvalidPeriodDateException('Invalid end date.');
}
if (!$date) {
return $this->removeFilter(static::END_DATE_FILTER);
}
// ::make() is responsible for converting strings to DateTimeInterface objects
\assert(!\is_string($date));
$self = $this->copyIfImmutable();
if (!$date) {
$self = $self->removeFilter(static::END_DATE_FILTER);
$self->syncNativePeriod();
return $self;
}
\assert($date instanceof DateTimeInterface);
$self->endDate = $date;
if (
$self->startDate !== null
&& $self->dateInterval !== null
&& !$self->dateInterval->invert
&& $self->startDate > $self->endDate
) {
$self->dateInterval->invert = 1;
}
if ($inclusive !== null) {
$self = $self->toggleOptions(static::EXCLUDE_END_DATE, !$inclusive);
}
if (!$self->hasFilter(static::END_DATE_FILTER)) {
return $self->addFilter(static::END_DATE_FILTER);
}
$self = self::addFilterOrHandleChangedParameters($self, static::END_DATE_FILTER);
$self->handleChangedParameters();
$self->syncNativePeriod();
return $self;
}
@@ -2286,11 +2374,7 @@ class CarbonPeriod extends DatePeriodBase implements Countable, JsonSerializable
'options' => $this->setOptions(...),
'recurrences' => $this->setRecurrences(...),
'current' => function (mixed $current): void {
if (!($current instanceof CarbonInterface)) {
$current = $this->resolveCarbon($current);
}
$this->carbonCurrent = $current;
$this->carbonCurrent = $this->carbonOrResolve($current);
},
'start' => 'startDate',
'interval' => $this->setDateInterval(...),
@@ -2409,7 +2493,7 @@ class CarbonPeriod extends DatePeriodBase implements Countable, JsonSerializable
return true;
}
if ($this->dateInterval->invert ? $current > $this->endDate : $current < $this->endDate) {
if ($this->dateInterval->invert ? ($current > $this->endDate) : ($current < $this->endDate)) {
return true;
}
@@ -2440,6 +2524,28 @@ class CarbonPeriod extends DatePeriodBase implements Countable, JsonSerializable
$this->validationResult = null;
}
/**
* Synchronize the native DatePeriod properties with the current state.
*/
protected function syncNativePeriod(): void
{
if (\PHP_VERSION_ID < 80200) {
return; // @codeCoverageIgnore
}
// Default interval if not set (matches __construct logic)
$interval = $this->dateInterval ?? \Carbon\CarbonInterval::day();
// Reinitialize the parent DatePeriod to update $start, $end, etc.
// This mirrors the logic in __construct and initializeSerialization.
parent::__construct(
$this->startDate,
$interval,
$this->endDate ?? max(1, min(2147483639, $this->recurrences ?? 1)),
$this->options ?? 0,
);
}
/**
* Validate current date and stop iteration when necessary.
*
@@ -2510,7 +2616,10 @@ class CarbonPeriod extends DatePeriodBase implements Countable, JsonSerializable
$attempts = 0;
do {
$this->carbonCurrent = $this->carbonCurrent->add($this->dateInterval);
$this->carbonCurrent = $this->carbonCurrent->add(
$this->dateInterval,
$this->dateInterval->getStep() && $this->dateInterval->invert ? -1 : 1,
);
$this->validationResult = null;
@@ -2563,6 +2672,13 @@ class CarbonPeriod extends DatePeriodBase implements Countable, JsonSerializable
: static::create($period, ...$arguments);
}
private function carbonOrResolve(mixed $dateTime): CarbonInterface
{
return $dateTime instanceof CarbonInterface
? $dateTime
: $this->resolveCarbon($dateTime);
}
private function orderCouple($first, $second): array
{
return $first > $second ? [$second, $first] : [$first, $second];
@@ -2715,4 +2831,17 @@ class CarbonPeriod extends DatePeriodBase implements Countable, JsonSerializable
);
// @codeCoverageIgnoreEnd
}
private static function addFilterOrHandleChangedParameters(
self $period,
array|callable|string $filter,
): self {
if (!$period->hasFilter($filter)) {
return $period->addFilter($filter);
}
$period->handleChangedParameters();
return $period;
}
}

View File

@@ -16,33 +16,40 @@ namespace Carbon\Exceptions;
use RuntimeException as BaseRuntimeException;
use Throwable;
/**
* @final
*/
class ImmutableException extends BaseRuntimeException implements RuntimeException
{
/**
* The value.
*
* @var string
*/
protected $value;
protected string $value;
/**
* Constructor.
*
* @param string $value the immutable type/value
* @param string $value the immutable message
* @param int $code
* @param Throwable|null $previous
*
* @deprecated Use ImmutableException::fromClass() or ImmutableException::fromMethod() to construct an
* ImmutableException instance.
*/
public function __construct($value, $code = 0, ?Throwable $previous = null)
public function __construct($message, int $code = 0, ?Throwable $previous = null, ?string $value = null)
{
$this->value = $value;
parent::__construct("$value is immutable.", $code, $previous);
$this->value ??= $value ?? $message;
parent::__construct($message, $code, $previous);
}
public static function fromClass(string $class, int $code = 0, ?Throwable $previous = null): self
{
return new self("$class class is immutable.", $code, $previous, "$class class");
}
public static function fromMethod(string $class, string $method, int $code = 0, ?Throwable $previous = null): self
{
return new self("$method not allowed on $class.", $code, $previous, "$class::$method method");
}
/**
* Get the value.
*
* @return string
*/
public function getValue(): string
{
return $this->value;

View File

@@ -14,6 +14,7 @@ declare(strict_types=1);
namespace Carbon;
use Closure;
use DateTime;
use DateTimeImmutable;
use DateTimeInterface;
use DateTimeZone;
@@ -70,7 +71,7 @@ use Throwable;
* the types of objects that can be built, for instance:
* @method array getAvailableLocales() Returns the list of internally available locales and already loaded custom locales.
* (It will ignore custom translator dynamic loading.)
* @method Language[] getAvailableLocalesInfo() Returns list of Language object for each available locale. This object allow you to get the ISO name, native
* @method array getAvailableLocalesInfo() Returns list of Language object for each available locale. This object allow you to get the ISO name, native
* name, region and variant of the locale.
* @method array getDays() Get the days of the week.
* @method ?string getFallbackLocale() Get the fallback locale.
@@ -553,16 +554,13 @@ class Factory
/**
* Set a Carbon instance (real or mock) to be returned when a "now"
* instance is created. The provided instance will be returned
* instance is created. The provided instance will be returned
* specifically under the following conditions:
* - A call to the static now() method, ex. Carbon::now()
* - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null)
* - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now')
* - When a string containing the desired time is passed to Carbon::parse().
*
* Note the timezone parameter was left out of the examples above and
* has no affect as the mock value will be returned regardless of its value.
*
* Only the moment is mocked with setTestNow(), the timezone will still be the one passed
* as parameter of date_default_timezone_get() as a fallback (see setTestNowAndTimezone()).
*
@@ -576,14 +574,14 @@ class Factory
public function setTestNow(mixed $testNow = null): void
{
$this->useTimezoneFromTestNow = false;
$this->testNow = $testNow instanceof self || $testNow instanceof Closure
$this->testNow = $testNow instanceof Closure
? $testNow
: $this->make($testNow);
}
/**
* Set a Carbon instance (real or mock) to be returned when a "now"
* instance is created. The provided instance will be returned
* instance is created. The provided instance will be returned
* specifically under the following conditions:
* - A call to the static now() method, ex. Carbon::now()
* - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null)
@@ -696,11 +694,21 @@ class Factory
}
if (!($testNow instanceof CarbonInterface)) {
$timezone ??= $this->useTimezoneFromTestNow ? $testNow->getTimezone() : null;
$timezone ??= $this->useTimezoneFromTestNow
? $testNow->getTimezone()
: new CarbonTimeZone(date_default_timezone_get());
$testNow = $this->__call('instance', [$testNow, $timezone]);
}
}
if ($testNow !== null && $timezone === null) {
if ($testNow instanceof DateTime) {
$testNow = clone $testNow;
}
$testNow = $testNow->setTimezone(date_default_timezone_get());
}
return $testNow;
}

View File

@@ -67,7 +67,7 @@ use Symfony\Contracts\Translation\TranslatorInterface;
* the types of objects that can be built, for instance:
* @method array getAvailableLocales() Returns the list of internally available locales and already loaded custom locales.
* (It will ignore custom translator dynamic loading.)
* @method Language[] getAvailableLocalesInfo() Returns list of Language object for each available locale. This object allow you to get the ISO name, native
* @method array getAvailableLocalesInfo() Returns list of Language object for each available locale. This object allow you to get the ISO name, native
* name, region and variant of the locale.
* @method array getDays() Get the days of the week.
* @method ?string getFallbackLocale() Get the fallback locale.

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
/**
* This file is part of the Carbon package.
*
* (c) Brian Nesbitt <brian@nesbot.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Carbon;
enum OverflowMode
{
case AnchorDay;
case NoOverflow;
case Overflow;
}

File diff suppressed because it is too large Load Diff

View File

@@ -13,6 +13,7 @@ declare(strict_types=1);
namespace Carbon\Traits;
use BadMethodCallException;
use Carbon\Callback;
use Carbon\Carbon;
use Carbon\CarbonImmutable;
@@ -28,12 +29,12 @@ trait IntervalStep
*
* @var Closure|null
*/
protected $step;
protected ?Closure $step = null;
/**
* Get the dynamic step in use.
*
* @return Closure
* @return Closure|null
*/
public function getStep(): ?Closure
{
@@ -64,8 +65,7 @@ trait IntervalStep
*/
public function convertDate(DateTimeInterface $dateTime, bool $negated = false): CarbonInterface
{
/** @var CarbonInterface $carbonDate */
$carbonDate = $dateTime instanceof CarbonInterface ? $dateTime : $this->resolveCarbon($dateTime);
$carbonDate = $this->carbonOrResolve($dateTime);
if ($this->step) {
$carbonDate = Callback::parameter($this->step, $carbonDate->avoidMutation());
@@ -91,4 +91,23 @@ trait IntervalStep
return Carbon::instance($dateTime);
}
private function carbonOrResolve(mixed $dateTime): CarbonInterface
{
return $dateTime instanceof CarbonInterface
? $dateTime
: $this->resolveCarbon($dateTime);
}
private function checkNoStepIsDefined(string $method): void
{
if ($this->step !== null) {
$chunks = explode('::', $method, 2);
$method = $chunks[1] ?? $method;
throw new BadMethodCallException(
"->$method() cannot be called on an interval with a step",
);
}
}
}

View File

@@ -345,7 +345,7 @@ trait Localization
* @param string|null $locale
* @param string ...$fallbackLocales
*
* @return $this|string
* @return ($locale is null ? string : static)
*/
public function locale(?string $locale = null, string ...$fallbackLocales): static|string
{
@@ -407,7 +407,16 @@ trait Localization
$translator = static::getTranslator();
if (method_exists($translator, 'setFallbackLocales')) {
$translator->setFallbackLocales([$locale]);
$fallbackLocales = [$locale];
if (
method_exists($translator, 'getFallbackLocales')
&& $fallbackLocales === $translator->getFallbackLocales()
) {
return;
}
$translator->setFallbackLocales($fallbackLocales);
if ($translator instanceof Translator) {
$preferredLocale = $translator->getLocale();

View File

@@ -30,16 +30,13 @@ trait Test
/**
* Set a Carbon instance (real or mock) to be returned when a "now"
* instance is created. The provided instance will be returned
* instance is created. The provided instance will be returned
* specifically under the following conditions:
* - A call to the static now() method, ex. Carbon::now()
* - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null)
* - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now')
* - When a string containing the desired time is passed to Carbon::parse().
*
* Note the timezone parameter was left out of the examples above and
* has no affect as the mock value will be returned regardless of its value.
*
* Only the moment is mocked with setTestNow(), the timezone will still be the one passed
* as parameter of date_default_timezone_get() as a fallback (see setTestNowAndTimezone()).
*
@@ -57,7 +54,7 @@ trait Test
/**
* Set a Carbon instance (real or mock) to be returned when a "now"
* instance is created. The provided instance will be returned
* instance is created. The provided instance will be returned
* specifically under the following conditions:
* - A call to the static now() method, ex. Carbon::now()
* - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null)

View File

@@ -20,10 +20,12 @@ use Carbon\Exceptions\InvalidFormatException;
use Carbon\Exceptions\InvalidIntervalException;
use Carbon\Exceptions\UnitException;
use Carbon\Exceptions\UnsupportedUnitException;
use Carbon\OverflowMode;
use Carbon\Unit;
use Closure;
use DateInterval;
use DateMalformedStringException;
use InvalidArgumentException;
use ReturnTypeWillChange;
/**
@@ -244,12 +246,13 @@ trait Units
*
* @param Unit|int|string|DateInterval|Closure|CarbonConverterInterface $unit
* @param Unit|int|float|string $value
* @param bool|null $overflow
* @param OverflowMode|bool|null $overflow
* @param int|null $anchorDay
*
* @return static
*/
#[ReturnTypeWillChange]
public function add($unit, $value = 1, ?bool $overflow = null): static
public function add($unit, $value = 1, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null): static
{
$unit = Unit::toNameIfUnit($unit);
$value = Unit::toNameIfUnit($value);
@@ -263,7 +266,14 @@ trait Units
}
if ($unit instanceof Closure) {
$result = $this->resolveCarbon($unit($this, false));
$inverted = ($value < 0);
$result = $this;
self::disallowDecimalPart($value);
for ($i = abs($value); $i > 0; $i--) {
$result = $result->resolveCarbon($unit($result, $inverted));
}
if ($this !== $result && $this->isMutable()) {
return $this->modify($result->rawFormat('Y-m-d H:i:s.u e O'));
@@ -280,16 +290,22 @@ trait Units
[$value, $unit] = [$unit, $value];
}
return $this->addUnit((string) $unit, $value, $overflow);
return $this->addUnit((string) $unit, $value, $overflow, $anchorDay);
}
/**
* Add given units to the current instance.
*/
public function addUnit(Unit|string $unit, $value = 1, ?bool $overflow = null): static
{
public function addUnit(
Unit|string $unit,
$value = 1,
OverflowMode|bool|null $overflow = null,
?int $anchorDay = null,
): static {
$unit = Unit::toName($unit);
$overflow = $this->getOverflowMode($overflow, $anchorDay);
$originalArgs = \func_get_args();
$date = $this;
@@ -311,8 +327,13 @@ trait Units
$value *= $factor;
}
if ($overflow === OverflowMode::AnchorDay) {
$anchorDay ??= $date->day;
$overflow = OverflowMode::NoOverflow;
}
if ($unit === 'weekday') {
$weekendDays = $this->transmitFactory(static fn () => static::getWeekendDays());
$weekendDays = $this->transmitFactory(static::getWeekendDays(...));
if ($weekendDays !== [static::SATURDAY, static::SUNDAY]) {
$absoluteValue = abs($value);
@@ -337,10 +358,9 @@ trait Units
} elseif ($canOverflow = (\in_array($unit, [
'month',
'year',
]) && ($overflow === false || (
]) && ($overflow === OverflowMode::NoOverflow || (
$overflow === null &&
($ucUnit = ucfirst($unit).'s') &&
!($this->{'local'.$ucUnit.'Overflow'} ?? static::{'shouldOverflow'.$ucUnit}())
!$this->shouldUnitOverflow($unit)
)))) {
$day = $date->day;
}
@@ -355,10 +375,16 @@ trait Units
try {
$date = self::rawAddUnit($date, $unit, $value);
if (isset($timeString)) {
$date = $date?->setTimeFromTimeString($timeString);
} elseif (isset($canOverflow, $day) && $canOverflow && $day !== $date?->day) {
$date = $date?->modify('last day of previous month');
if ($date !== null) {
if (isset($timeString)) {
$date = $date->setTimeFromTimeString($timeString);
} elseif (isset($canOverflow, $day) && $canOverflow && $day !== $date->day) {
$date = $date->modify('last day of previous month');
}
if ($anchorDay !== null) {
$date = $date->setAnchorDay($anchorDay);
}
}
} catch (DateMalformedStringException|InvalidFormatException|UnsupportedUnitException $exception) {
$date = null;
@@ -374,9 +400,13 @@ trait Units
/**
* Subtract given units to the current instance.
*/
public function subUnit(Unit|string $unit, $value = 1, ?bool $overflow = null): static
{
return $this->addUnit($unit, -$value, $overflow);
public function subUnit(
Unit|string $unit,
$value = 1,
OverflowMode|bool|null $overflow = null,
?int $anchorDay = null,
): static {
return $this->addUnit($unit, -$value, $overflow, $anchorDay);
}
/**
@@ -396,12 +426,13 @@ trait Units
*
* @param Unit|int|string|DateInterval|Closure|CarbonConverterInterface $unit
* @param Unit|int|float|string $value
* @param bool|null $overflow
* @param OverflowMode|bool|null $overflow
* @param int|null $anchorDay
*
* @return static
*/
#[ReturnTypeWillChange]
public function sub($unit, $value = 1, ?bool $overflow = null): static
public function sub($unit, $value = 1, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null): static
{
$unit = Unit::toNameIfUnit($unit);
$value = Unit::toNameIfUnit($value);
@@ -415,7 +446,14 @@ trait Units
}
if ($unit instanceof Closure) {
$result = $this->resolveCarbon($unit($this, true));
$inverted = ($value < 0);
$result = $this;
self::disallowDecimalPart($value);
for ($i = abs($value); $i > 0; $i--) {
$result = $result->resolveCarbon($unit($result, !$inverted));
}
if ($this !== $result && $this->isMutable()) {
return $this->modify($result->rawFormat('Y-m-d H:i:s.u e O'));
@@ -432,7 +470,7 @@ trait Units
[$value, $unit] = [$unit, $value];
}
return $this->addUnit((string) $unit, -(float) $value, $overflow);
return $this->addUnit((string) $unit, -(float) $value, $overflow, $anchorDay);
}
/**
@@ -442,31 +480,163 @@ trait Units
*
* @param Unit|int|string|DateInterval $unit
* @param Unit|int|float|string $value
* @param bool|null $overflow
* @param OverflowMode|bool|null $overflow
* @param int|null $anchorDay
*
* @return static
*/
public function subtract($unit, $value = 1, ?bool $overflow = null): static
public function subtract($unit, $value = 1, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null): static
{
if (\is_string($unit) && \func_num_args() === 1) {
$unit = CarbonInterval::make($unit, [], true);
}
return $this->sub($unit, $value, $overflow);
return $this->sub($unit, $value, $overflow, $anchorDay);
}
/**
* Add given amount of time to the current date.
*
* @SuppressWarnings(ExcessiveParameterList)
*/
private function doPlus(
int $years = 0,
int $months = 0,
int|float $weeks = 0,
int|float $days = 0,
int|float $hours = 0,
int|float $minutes = 0,
int|float $seconds = 0,
int|float $microseconds = 0,
OverflowMode|bool|null $overflow = null,
?int $anchorDay = null,
): static {
return $this->addUnit(Unit::Year, $years, $overflow, $anchorDay)
->addUnit(Unit::Month, $months, $overflow, $anchorDay)
->add("
$weeks weeks $days days
$hours hours $minutes minutes $seconds seconds $microseconds microseconds
");
}
/**
* Subtract given amount of time to the current date.
*
* @SuppressWarnings(ExcessiveParameterList)
*/
private function doMinus(
int $years = 0,
int $months = 0,
int|float $weeks = 0,
int|float $days = 0,
int|float $hours = 0,
int|float $minutes = 0,
int|float $seconds = 0,
int|float $microseconds = 0,
OverflowMode|bool|null $overflow = null,
?int $anchorDay = null,
): static {
return $this->subUnit(Unit::Year, $years, $overflow, $anchorDay)
->subUnit(Unit::Month, $months, $overflow, $anchorDay)
->sub("
$weeks weeks $days days
$hours hours $minutes minutes $seconds seconds $microseconds microseconds
");
}
private function callPlusOrMinus(string $method, array $parameters): ?static
{
return match ($method) {
'plus' => $this->doPlus(...$parameters),
'minus' => $this->doMinus(...$parameters),
default => null,
};
}
private static function rawAddUnit(self $date, string $unit, int|float $value): ?static
{
try {
$absoluteValue = abs($value);
return $date->rawAdd(
CarbonInterval::fromString(abs($value)." $unit")->invert($value < 0),
CarbonInterval::fromString(self::getNumberAsString($absoluteValue)." $unit")
->invert($value < 0),
);
} catch (InvalidIntervalException $exception) {
try {
return $date->modify("$value $unit");
return $date->modify(self::getNumberAsString($value)." $unit");
} catch (InvalidFormatException) {
throw new UnsupportedUnitException($unit, previous: $exception);
}
}
}
private static function getNumberAsString(int|float $value): string
{
$stringValue = (string) $value;
if ($value < -1 || $value > 1) {
if (str_contains($stringValue, 'E')) {
return number_format($value, 0, '.', '');
}
return $stringValue;
}
if (str_contains($stringValue, 'E')) {
return number_format($value, 14, '.', '');
}
return $stringValue;
}
/**
* Set current day of the instance to the passed value if it exits in the
* current month, else set current day to the last day of the month.
*/
public function setAnchorDay(int $anchorDay): static
{
if ($anchorDay < 1) {
throw new InvalidArgumentException('$anchorDay must be greater than 0');
}
return $this->day(min($anchorDay, $this->daysInMonth));
}
private static function disallowDecimalPart(mixed $value): void
{
if (((float) $value) !== ((float) (int) $value)) {
throw new InvalidArgumentException(
'Interval objects cannot be multiplied by a non-integer value.',
);
}
}
private function getOverflowMode(
OverflowMode|bool|null $overflow = null,
?int $anchorDay = null,
): ?OverflowMode {
if ($anchorDay !== null) {
$overflow ??= OverflowMode::AnchorDay;
if ($overflow !== OverflowMode::AnchorDay) {
throw new InvalidArgumentException(
'$anchorDay can be set only $overflow = OverflowMode::AnchorDay',
);
}
}
return match ($overflow) {
true => OverflowMode::Overflow,
false => OverflowMode::NoOverflow,
default => $overflow,
};
}
private function shouldUnitOverflow(string $unit): bool
{
$ucUnit = ucfirst($unit).'s';
return $this->{'local'.$ucUnit.'Overflow'} ?? static::{'shouldOverflow'.$ucUnit}();
}
}

View File

@@ -94,7 +94,7 @@ class TranslatorImmutable extends Translator
private function disallowMutation($method)
{
if ($this->constructed) {
throw new ImmutableException($method.' not allowed on '.static::class);
throw ImmutableException::fromMethod(static::class, $method);
}
}
}