From b5d1172083e9b63a87e95f65d17d7c15df2a876a Mon Sep 17 00:00:00 2001 From: Emmanuel Beauchamps Date: Mon, 24 Feb 2025 11:03:48 +0100 Subject: [PATCH 01/10] [5.x] French translations (#11488) Good for 5.48 --- resources/lang/fr.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/resources/lang/fr.json b/resources/lang/fr.json index d1bc185a55..054d857a70 100644 --- a/resources/lang/fr.json +++ b/resources/lang/fr.json @@ -768,6 +768,7 @@ "Released on :date": "Publiée le :date", "Remember me": "Se souvenir de moi", "Remove": "Enlever", + "Remove All": "Tout enlever", "Remove all empty nodes": "Enlever tous les noeuds vides", "Remove Asset": "Enlever la ressource", "Remove child page|Remove :count child pages": "Enlever la page enfant|Enlever :count pages enfants", @@ -950,6 +951,7 @@ "Taxonomy saved": "Taxonomie enregistrée", "Template": "Modèle", "Templates": "Modèles", + "Term": "Terme", "Term created": "Terme créé", "Term deleted": "Terme supprimé", "Term references updated": "Références des termes mises à jour", @@ -1010,6 +1012,7 @@ "Uncheck All": "Décocher tout", "Underline": "Souligner", "Unlink": "Dissocier", + "Unlink All": "Tout dissocier", "Unlisted Addons": "Addons non répertoriés", "Unordered List": "Liste non ordonnée", "Unpin from Favorites": "Supprimer des favoris", From 0b256e3b02be2e3eba3e8e54862e4e6346967738 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Mon, 24 Feb 2025 15:55:40 +0000 Subject: [PATCH 02/10] [5.x] Only show spatie/fork prompt when pcntl extension is loaded (#11493) --- src/Console/Commands/InstallSsg.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Console/Commands/InstallSsg.php b/src/Console/Commands/InstallSsg.php index 0f8455dafa..404e5b1b45 100644 --- a/src/Console/Commands/InstallSsg.php +++ b/src/Console/Commands/InstallSsg.php @@ -68,6 +68,7 @@ function () { if ( ! Composer::isInstalled('spatie/fork') + && extension_loaded('pcntl') && confirm('Would you like to install spatie/fork? It allows for running multiple workers at once.') ) { spin( From 9139cb0d20d896e024c7a34965dd539bff55958a Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Mon, 24 Feb 2025 14:17:11 -0500 Subject: [PATCH 03/10] [5.x] Fix carbon integer casting (#11496) --- src/API/AbstractCacher.php | 2 +- src/Git/Git.php | 2 +- src/GraphQL/ResponseCache/DefaultCache.php | 2 +- src/Http/Controllers/CP/SessionTimeoutController.php | 2 +- src/Providers/CacheServiceProvider.php | 2 +- src/StaticCaching/Cachers/AbstractCacher.php | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/API/AbstractCacher.php b/src/API/AbstractCacher.php index 12f964c6db..6529775059 100644 --- a/src/API/AbstractCacher.php +++ b/src/API/AbstractCacher.php @@ -55,6 +55,6 @@ protected function normalizeKey($key) */ public function cacheExpiry() { - return Carbon::now()->addMinutes($this->config('expiry')); + return Carbon::now()->addMinutes((int) $this->config('expiry')); } } diff --git a/src/Git/Git.php b/src/Git/Git.php index a19b24d27b..5dad753680 100644 --- a/src/Git/Git.php +++ b/src/Git/Git.php @@ -85,7 +85,7 @@ public function commit($message = null) public function dispatchCommit($message = null) { if ($delay = config('statamic.git.dispatch_delay')) { - $delayInMinutes = now()->addMinutes($delay); + $delayInMinutes = now()->addMinutes((int) $delay); $message = null; } diff --git a/src/GraphQL/ResponseCache/DefaultCache.php b/src/GraphQL/ResponseCache/DefaultCache.php index baf313376f..d49014bce3 100644 --- a/src/GraphQL/ResponseCache/DefaultCache.php +++ b/src/GraphQL/ResponseCache/DefaultCache.php @@ -19,7 +19,7 @@ public function put(Request $request, $response) { $key = $this->track($request); - $ttl = Carbon::now()->addMinutes(config('statamic.graphql.cache.expiry', 60)); + $ttl = Carbon::now()->addMinutes((int) config('statamic.graphql.cache.expiry', 60)); Cache::put($key, $response, $ttl); } diff --git a/src/Http/Controllers/CP/SessionTimeoutController.php b/src/Http/Controllers/CP/SessionTimeoutController.php index a2b6483016..4278b61614 100644 --- a/src/Http/Controllers/CP/SessionTimeoutController.php +++ b/src/Http/Controllers/CP/SessionTimeoutController.php @@ -14,7 +14,7 @@ public function __invoke() $lastActivity = session('last_activity', now()->timestamp); return Carbon::createFromTimestamp($lastActivity, config('app.timezone')) - ->addMinutes(config('session.lifetime')) + ->addMinutes((int) config('session.lifetime')) ->diffInSeconds(); } } diff --git a/src/Providers/CacheServiceProvider.php b/src/Providers/CacheServiceProvider.php index 6a598cf431..064d2b8812 100644 --- a/src/Providers/CacheServiceProvider.php +++ b/src/Providers/CacheServiceProvider.php @@ -76,7 +76,7 @@ private function macroRememberWithExpiration() $keyValuePair = $callback(); $value = reset($keyValuePair); - $expiration = Carbon::now()->addMinutes(key($keyValuePair)); + $expiration = Carbon::now()->addMinutes((int) key($keyValuePair)); return Cache::remember($cacheKey, $expiration, function () use ($value) { return $value; diff --git a/src/StaticCaching/Cachers/AbstractCacher.php b/src/StaticCaching/Cachers/AbstractCacher.php index 4f81543fd7..22a7adf216 100644 --- a/src/StaticCaching/Cachers/AbstractCacher.php +++ b/src/StaticCaching/Cachers/AbstractCacher.php @@ -66,7 +66,7 @@ public function getBaseUrl() */ public function getDefaultExpiration() { - return $this->config('expiry'); + return (int) $this->config('expiry'); } /** From 18c0d137e46d573ba5f6168add3d431daea74d88 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 25 Feb 2025 14:52:23 +0000 Subject: [PATCH 04/10] [5.x] Remove duplicate translation line from `translator` command (#11494) Co-authored-by: Jason Varga --- resources/lang/az.json | 2 +- resources/lang/cs.json | 2 +- resources/lang/da.json | 2 +- resources/lang/de.json | 2 +- resources/lang/de_CH.json | 2 +- resources/lang/es.json | 2 +- resources/lang/fa.json | 2 +- resources/lang/fr.json | 2 +- resources/lang/hu.json | 2 +- translator | 1 - 10 files changed, 9 insertions(+), 10 deletions(-) diff --git a/resources/lang/az.json b/resources/lang/az.json index a0aaad55f6..da103825c1 100644 --- a/resources/lang/az.json +++ b/resources/lang/az.json @@ -528,7 +528,7 @@ "Icon": "İkon", "ID": "ID", "ID regenerated and Stache cleared": "ID yenidən yaradıldı və Stache təmizləndi", - "If you’re having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "Əgər \":actionText\" düyməsini tıklamaqda çətinlik çəkirsinizsə, aşağıdakı URL-ni kopyalayıb veb brauzerinizə yapışdırın:", + "If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "Əgər \":actionText\" düyməsini tıklamaqda çətinlik çəkirsinizsə, aşağıdakı URL-ni kopyalayıb veb brauzerinizə yapışdırın:", "Image": "Şəkil", "Image Cache": "Şəkil Keşi", "Image cache cleared.": "Şəkil keşi təmizləndi.", diff --git a/resources/lang/cs.json b/resources/lang/cs.json index 6727896c68..655855886e 100644 --- a/resources/lang/cs.json +++ b/resources/lang/cs.json @@ -528,7 +528,7 @@ "Icon": "Ikona", "ID": "ID", "ID regenerated and Stache cleared": "ID obnoveno a Stache vyprázdněno", - "If you’re having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "Pokud máte problém s kliknutím na tlačítko \":actionText\", zkopírujte a vložte URL níže do webového prohlížeče:", + "If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "Pokud máte problém s kliknutím na tlačítko \":actionText\", zkopírujte a vložte URL níže do webového prohlížeče:", "Image": "Obrázek", "Image Cache": "Cache obrázků", "Image cache cleared.": "Cache obrázků smazána.", diff --git a/resources/lang/da.json b/resources/lang/da.json index 634f0b7087..16d81a021b 100644 --- a/resources/lang/da.json +++ b/resources/lang/da.json @@ -528,7 +528,7 @@ "Icon": "Ikon", "ID": "ID", "ID regenerated and Stache cleared": "ID regenereret og Stache ryddet", - "If you’re having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "Hvis du har problemer med at klikke på knappen \" :actionText \", skal du kopiere og indsætte nedenstående URL i din webbrowser:", + "If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "Hvis du har problemer med at klikke på knappen \" :actionText \", skal du kopiere og indsætte nedenstående URL i din webbrowser:", "Image": "Billede", "Image Cache": "Billedcache", "Image cache cleared.": "Billedcache ryddet.", diff --git a/resources/lang/de.json b/resources/lang/de.json index 5210ebd357..8266c8aded 100644 --- a/resources/lang/de.json +++ b/resources/lang/de.json @@ -528,7 +528,7 @@ "Icon": "Icon", "ID": "ID", "ID regenerated and Stache cleared": "ID regeneriert und Stache gelöscht", - "If you’re having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "Wenn du beim Klicken auf den Button „:actionText“ Probleme hast, kopiere die folgende URL und füge sie in deinem Browser ein:", + "If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "Wenn du beim Klicken auf den Button „:actionText“ Probleme hast, kopiere die folgende URL und füge sie in deinem Browser ein:", "Image": "Bild", "Image Cache": "Bildercache", "Image cache cleared.": "Der Bildercache wurde gelöscht.", diff --git a/resources/lang/de_CH.json b/resources/lang/de_CH.json index c471f625b5..6da9859473 100644 --- a/resources/lang/de_CH.json +++ b/resources/lang/de_CH.json @@ -528,7 +528,7 @@ "Icon": "Icon", "ID": "ID", "ID regenerated and Stache cleared": "ID regeneriert und Stache gelöscht", - "If you’re having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "Wenn du beim Klicken auf den Button «:actionText» Probleme hast, kopiere die folgende URL und füge sie in deinem Browser ein:", + "If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "Wenn du beim Klicken auf den Button «:actionText» Probleme hast, kopiere die folgende URL und füge sie in deinem Browser ein:", "Image": "Bild", "Image Cache": "Bildercache", "Image cache cleared.": "Der Bildercache wurde gelöscht.", diff --git a/resources/lang/es.json b/resources/lang/es.json index 58698bd2b3..63ba945dfd 100644 --- a/resources/lang/es.json +++ b/resources/lang/es.json @@ -528,7 +528,7 @@ "Icon": "Icono", "ID": "IDENTIFICACIÓN", "ID regenerated and Stache cleared": "ID regenerada y bigote limpiado", - "If you’re having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "Si tienes problemas para hacer clic en el botón de \":actionText\", copia y pega el siguiente URL en tu navegador:", + "If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "Si tienes problemas para hacer clic en el botón de \":actionText\", copia y pega el siguiente URL en tu navegador:", "Image": "Imagen", "Image Cache": "Caché de imágenes", "Image cache cleared.": "Caché de imágenes borrado.", diff --git a/resources/lang/fa.json b/resources/lang/fa.json index d597b207dd..c873455503 100644 --- a/resources/lang/fa.json +++ b/resources/lang/fa.json @@ -528,7 +528,7 @@ "Icon": "آیکون", "ID": "شناسه", "ID regenerated and Stache cleared": "شناسه با موفقیت از نو ساخته و کش خالی شد", - "If you’re having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "اگر مشکلی در خصوص کلیلک روی دکمه‌ی \":actionText\" وجود دارد، می‌توانید آدرس زیر را کپی و در آدرس بار مرورگر پیست کنید:", + "If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "اگر مشکلی در خصوص کلیلک روی دکمه‌ی \":actionText\" وجود دارد، می‌توانید آدرس زیر را کپی و در آدرس بار مرورگر پیست کنید:", "Image": "تصویر", "Image Cache": "کش تصویر", "Image cache cleared.": "کش تصویر پاک شد", diff --git a/resources/lang/fr.json b/resources/lang/fr.json index 054d857a70..fc8e148f36 100644 --- a/resources/lang/fr.json +++ b/resources/lang/fr.json @@ -528,7 +528,7 @@ "Icon": "Icône", "ID": "ID", "ID regenerated and Stache cleared": "ID regénéré et Stache effacé", - "If you’re having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "Si vous ne parvenez pas à cliquer sur le bouton \":actionText\", copiez et collez l'URL ci-dessous dans votre navigateur Web :", + "If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "Si vous ne parvenez pas à cliquer sur le bouton \":actionText\", copiez et collez l'URL ci-dessous dans votre navigateur Web :", "Image": "Image", "Image Cache": "Cache des images", "Image cache cleared.": "Cache des images effacé.", diff --git a/resources/lang/hu.json b/resources/lang/hu.json index 9595fa7431..a023f82a11 100644 --- a/resources/lang/hu.json +++ b/resources/lang/hu.json @@ -528,7 +528,7 @@ "Icon": "Ikon", "ID": "ID", "ID regenerated and Stache cleared": "ID újragenerálva, Stache törölve", - "If you’re having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "Ha nem tudja megkattintani a(z) \":actionText\" gombot, másolja ki és illessze be az alábbi URL-t a böngészőjébe:", + "If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "Ha nem tudja megkattintani a(z) \":actionText\" gombot, másolja ki és illessze be az alábbi URL-t a böngészőjébe:", "Image": "Kép", "Image Cache": "Képgyorsítótár", "Image cache cleared.": "A képgyorsítótár törölve.", diff --git a/translator b/translator index f53294f5bd..eca1e5f644 100644 --- a/translator +++ b/translator @@ -46,7 +46,6 @@ $additionalStrings = [ 'Whoops!', 'Regards', "If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:", - "If you’re having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:", // laravel <8.48.0 'All rights reserved.', 'The given data was invalid.', 'Protected Page', From 7f8ec820e072274bf5d007eb44737f7e9ed85021 Mon Sep 17 00:00:00 2001 From: Daryl <32465543+dmxmo@users.noreply.github.com> Date: Tue, 25 Feb 2025 06:52:43 -0800 Subject: [PATCH 05/10] [5.x] Fix: Include port in CSP for Live Preview (#11498) Co-authored-by: Duncan McClean Co-authored-by: Jason Varga --- src/Tokens/Handlers/LivePreview.php | 4 +++- .../Entries/AddsHeadersToLivePreviewTest.php | 13 +++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/Tokens/Handlers/LivePreview.php b/src/Tokens/Handlers/LivePreview.php index 39e4fbc7d7..9f4e8e3de2 100644 --- a/src/Tokens/Handlers/LivePreview.php +++ b/src/Tokens/Handlers/LivePreview.php @@ -39,6 +39,8 @@ private function getSchemeAndHost(Site $site): string { $parts = parse_url($site->absoluteUrl()); - return $parts['scheme'].'://'.$parts['host']; + $port = isset($parts['port']) ? ':'.$parts['port'] : ''; + + return $parts['scheme'].'://'.$parts['host'].$port; } } diff --git a/tests/Feature/Entries/AddsHeadersToLivePreviewTest.php b/tests/Feature/Entries/AddsHeadersToLivePreviewTest.php index c25ee610d2..928044a50e 100644 --- a/tests/Feature/Entries/AddsHeadersToLivePreviewTest.php +++ b/tests/Feature/Entries/AddsHeadersToLivePreviewTest.php @@ -58,17 +58,22 @@ public function it_doesnt_set_header_when_single_site() public function it_sets_header_when_multisite() { config()->set('statamic.system.multisite', true); + $this->setSites([ - 'en' => ['url' => 'http://localhost/', 'locale' => 'en'], - 'fr' => ['url' => 'http://localhost/fr/', 'locale' => 'fr'], - 'third' => ['url' => 'http://third/', 'locale' => 'en'], + 'one' => ['url' => 'http://withport.com:8080/', 'locale' => 'en'], + 'two' => ['url' => 'http://withport.com:8080/fr/', 'locale' => 'fr'], + 'three' => ['url' => 'http://withoutport.com/', 'locale' => 'en'], + 'four' => ['url' => 'http://withoutport.com/fr/', 'locale' => 'fr'], + 'five' => ['url' => 'http://third.com/', 'locale' => 'en'], + 'six' => ['url' => 'http://third.com/fr/', 'locale' => 'fr'], ]); + $substitute = EntryFactory::collection('test')->id('2')->slug('charlie')->data(['title' => 'Substituted title', 'foo' => 'Substituted foo'])->make(); LivePreview::tokenize('test-token', $substitute); $this->get('/test?token=test-token') ->assertHeader('X-Statamic-Live-Preview', true) - ->assertHeader('Content-Security-Policy', 'frame-ancestors http://localhost http://third'); + ->assertHeader('Content-Security-Policy', 'frame-ancestors http://withport.com:8080 http://withoutport.com http://third.com'); } } From 600ecc537a80816453a9993c42bffb7ba42ee969 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Tue, 25 Feb 2025 11:38:05 -0500 Subject: [PATCH 06/10] [5.x] Fix session expiry component (#11501) --- src/Http/Controllers/CP/SessionTimeoutController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Http/Controllers/CP/SessionTimeoutController.php b/src/Http/Controllers/CP/SessionTimeoutController.php index 4278b61614..f4091039e6 100644 --- a/src/Http/Controllers/CP/SessionTimeoutController.php +++ b/src/Http/Controllers/CP/SessionTimeoutController.php @@ -13,8 +13,8 @@ public function __invoke() // remember me would have already been served a 403 error and wouldn't have got this far. $lastActivity = session('last_activity', now()->timestamp); - return Carbon::createFromTimestamp($lastActivity, config('app.timezone')) + return abs((int) Carbon::createFromTimestamp($lastActivity, config('app.timezone')) ->addMinutes((int) config('session.lifetime')) - ->diffInSeconds(); + ->diffInSeconds()); } } From 6af62e586fabde581de1d350978a2b2a3d88dcf2 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Tue, 25 Feb 2025 11:41:41 -0500 Subject: [PATCH 07/10] changelog --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 881ec976f3..41edc632b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Release Notes +## 5.48.1 (2025-02-25) + +### What's fixed +- Fix session expiry component [#11501](https://github.com/statamic/cms/issues/11501) by @jasonvarga +- Include port in CSP for Live Preview [#11498](https://github.com/statamic/cms/issues/11498) by @dmxmo +- Remove duplicate translation line from `translator` command [#11494](https://github.com/statamic/cms/issues/11494) by @duncanmcclean +- Fix carbon integer casting [#11496](https://github.com/statamic/cms/issues/11496) by @jasonvarga +- Only show spatie/fork prompt when pcntl extension is loaded [#11493](https://github.com/statamic/cms/issues/11493) by @duncanmcclean +- French translations [#11488](https://github.com/statamic/cms/issues/11488) by @ebeauchamps + + + ## 5.48.0 (2025-02-21) ### What's new From b35e734eeb2472028b66b72f5fba40eac3dc0c3b Mon Sep 17 00:00:00 2001 From: Marco Rieser Date: Tue, 25 Feb 2025 20:29:27 +0100 Subject: [PATCH 08/10] [5.x] Asset Container returns relative url for same site (#11372) Co-authored-by: Jason Varga --- src/Assets/AssetContainer.php | 5 ++++- tests/Assets/AssetContainerTest.php | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Assets/AssetContainer.php b/src/Assets/AssetContainer.php index 29765fce49..86263865e7 100644 --- a/src/Assets/AssetContainer.php +++ b/src/Assets/AssetContainer.php @@ -27,6 +27,7 @@ use Statamic\Facades\Stache; use Statamic\Facades\URL; use Statamic\Support\Arr; +use Statamic\Support\Str; use Statamic\Support\Traits\FluentlyGetsAndSets; class AssetContainer implements Arrayable, ArrayAccess, AssetContainerContract, Augmentable @@ -138,7 +139,9 @@ public function url() return null; } - $url = rtrim($this->disk()->url('/'), '/'); + $url = (string) Str::of($this->disk()->url('/')) + ->rtrim('/') + ->after(config('app.url')); return ($url === '') ? '/' : $url; } diff --git a/tests/Assets/AssetContainerTest.php b/tests/Assets/AssetContainerTest.php index 507c735879..9bc8f92ffe 100644 --- a/tests/Assets/AssetContainerTest.php +++ b/tests/Assets/AssetContainerTest.php @@ -137,6 +137,21 @@ public function it_gets_the_url_from_the_disk_config_when_its_relative() $this->assertEquals('http://localhost/container', $container->absoluteUrl()); } + #[Test] + public function it_gets_the_url_from_the_disk_config_when_its_app_url() + { + config(['filesystems.disks.test' => [ + 'driver' => 'local', + 'root' => __DIR__.'/__fixtures__/container', + 'url' => 'http://localhost/container', + ]]); + + $container = (new AssetContainer)->disk('test'); + + $this->assertEquals('/container', $container->url()); + $this->assertEquals('http://localhost/container', $container->absoluteUrl()); + } + #[Test] public function its_private_if_the_disk_has_no_url() { From ec8e5c72785be78fd838dda81dc48ff8b6e6cad7 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 25 Feb 2025 19:42:42 +0000 Subject: [PATCH 09/10] [5.x] Laravel 12 (#11433) Co-authored-by: Jason Varga --- .github/workflows/tests.yml | 4 +- composer.json | 12 +-- .../LaravelTwelveTokenRepository.php | 98 +++++++++++++++++++ src/Auth/Passwords/PasswordBrokerManager.php | 15 ++- tests/Auth/Protect/PasswordEntryTest.php | 12 +-- 5 files changed, 126 insertions(+), 15 deletions(-) create mode 100644 src/Auth/Passwords/LaravelTwelveTokenRepository.php diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8f222fa9f1..f60b3bf6ff 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,7 +17,7 @@ jobs: strategy: matrix: php: [8.1, 8.2, 8.3, 8.4] - laravel: [10.*, 11.*] + laravel: [10.*, 11.*, 12.*] stability: [prefer-lowest, prefer-stable] os: [ubuntu-latest] include: @@ -32,6 +32,8 @@ jobs: exclude: - php: 8.1 laravel: 11.* + - php: 8.1 + laravel: 12.* - php: 8.4 laravel: 10.* diff --git a/composer.json b/composer.json index 5fdafcae10..a2934dd6a1 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ "composer/semver": "^3.4", "guzzlehttp/guzzle": "^6.3 || ^7.0", "james-heinrich/getid3": "^1.9.21", - "laravel/framework": "^10.40 || ^11.34", + "laravel/framework": "^10.40 || ^11.34 || ^12.0", "laravel/prompts": "^0.1.16 || ^0.2.0 || ^0.3.0", "league/commonmark": "^2.2", "league/csv": "^9.0", @@ -23,12 +23,12 @@ "michelf/php-smartypants": "^1.8.1", "nesbot/carbon": "^2.62.1 || ^3.0", "pixelfear/composer-dist-plugin": "^0.1.4", - "rebing/graphql-laravel": "^9.7", + "rebing/graphql-laravel": "^9.8", "rhukster/dom-sanitizer": "^1.0.6", "spatie/blink": "^1.3", - "spatie/ignition": "^1.15", + "spatie/ignition": "^1.15.1", "statamic/stringy": "^3.1.2", - "stillat/blade-parser": "^1.10.1", + "stillat/blade-parser": "^1.10.1 || ^2.0", "symfony/lock": "^6.4", "symfony/var-exporter": "^6.0", "symfony/yaml": "^6.0 || ^7.0", @@ -42,8 +42,8 @@ "google/cloud-translate": "^1.6", "laravel/pint": "1.16.0", "mockery/mockery": "^1.6.10", - "orchestra/testbench": "^8.14 || ^9.2", - "phpunit/phpunit": "^10.5.35", + "orchestra/testbench": "^8.14 || ^9.2 || ^10.0", + "phpunit/phpunit": "^10.5.35 || ^11.5.3", "spatie/laravel-ray": "^1.37" }, "config": { diff --git a/src/Auth/Passwords/LaravelTwelveTokenRepository.php b/src/Auth/Passwords/LaravelTwelveTokenRepository.php new file mode 100644 index 0000000000..17b2891cc2 --- /dev/null +++ b/src/Auth/Passwords/LaravelTwelveTokenRepository.php @@ -0,0 +1,98 @@ +path = storage_path("statamic/password_resets/$table.yaml"); + } + + public function create(CanResetPasswordContract $user) + { + $email = $user->getEmailForPasswordReset(); + + $token = $this->createNewToken(); + + $this->insert($this->getPayload($email, $token)); + + return $token; + } + + protected function insert($payload) + { + $resets = $this->getResets(); + + $resets[$payload['email']] = [ + 'token' => $payload['token'], + 'created_at' => $payload['created_at']->timestamp, + ]; + + $this->putResets($resets); + } + + public function delete(CanResetPasswordContract $user) + { + $this->putResets( + $this->getResets()->forget($user->email()) + ); + } + + public function deleteExpired() + { + $this->putResets($this->getResets()->reject(function ($item, $email) { + return $this->tokenExpired($item['created_at']); + })); + } + + public function exists(CanResetPasswordContract $user, $token) + { + $record = $this->getResets()->get($user->email()); + + return $record && + ! $this->tokenExpired(Carbon::createFromTimestamp($record['created_at'], config('app.timezone'))) + && $this->hasher->check($token, $record['token']); + } + + public function recentlyCreatedToken(CanResetPasswordContract $user) + { + $record = $this->getResets()->get($user->email()); + + return $record && parent::tokenRecentlyCreated($record['created_at']); + } + + protected function getResets() + { + if (! $this->files->exists($this->path)) { + return collect(); + } + + return collect(YAML::parse($this->files->get($this->path))); + } + + protected function putResets($resets) + { + if (! $this->files->isDirectory($dir = dirname($this->path))) { + $this->files->makeDirectory($dir); + } + + $this->files->put($this->path, YAML::dump($resets->all())); + } +} diff --git a/src/Auth/Passwords/PasswordBrokerManager.php b/src/Auth/Passwords/PasswordBrokerManager.php index 3e70c7cd14..0267a1e2ac 100644 --- a/src/Auth/Passwords/PasswordBrokerManager.php +++ b/src/Auth/Passwords/PasswordBrokerManager.php @@ -15,12 +15,23 @@ protected function createTokenRepository(array $config) $key = base64_decode(substr($key, 7)); } - return new TokenRepository( + if (version_compare(app()->version(), '12', '<')) { + return new TokenRepository( + $this->app['files'], + $this->app['hash'], + $config['table'], + $key, + $config['expire'], + $config['throttle'] ?? 0 + ); + } + + return new LaravelTwelveTokenRepository( $this->app['files'], $this->app['hash'], $config['table'], $key, - $config['expire'], + ($config['expire'] ?? 60) * 60, $config['throttle'] ?? 0 ); } diff --git a/tests/Auth/Protect/PasswordEntryTest.php b/tests/Auth/Protect/PasswordEntryTest.php index cd8a89e1dd..371912b833 100644 --- a/tests/Auth/Protect/PasswordEntryTest.php +++ b/tests/Auth/Protect/PasswordEntryTest.php @@ -110,16 +110,16 @@ public static function localPasswordProvider() { return [ 'string' => [ - 'value' => 'the-local-password', - 'submitted' => 'the-local-password', + 'the-local-password', + 'the-local-password', ], 'array with single value' => [ - 'value' => ['the-local-password'], - 'submitted' => 'the-local-password', + ['the-local-password'], + 'the-local-password', ], 'array with multiple values' => [ - 'value' => ['first-local-password', 'second-local-password'], - 'submitted' => 'second-local-password', + ['first-local-password', 'second-local-password'], + 'second-local-password', ], ]; } From da2431819316bcb72d2bf763e0fe83d8546746d9 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Tue, 25 Feb 2025 15:14:29 -0500 Subject: [PATCH 10/10] changelog --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41edc632b5..5d11289b91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Release Notes +## 5.49.0 (2025-02-25) + +### What's new +- Laravel 12 support [#11433](https://github.com/statamic/cms/issues/11433) by @duncanmcclean + +### What's fixed +- Asset Container returns relative url for same site [#11372](https://github.com/statamic/cms/issues/11372) by @marcorieser + + + ## 5.48.1 (2025-02-25) ### What's fixed