From 6adc8d4a371291ec5e374d490c0357f1f5cfb231 Mon Sep 17 00:00:00 2001
From: Duncan McClean
Date: Wed, 11 Dec 2024 14:33:39 +0000
Subject: [PATCH 001/490] [5.x] Fix type error in `HandleEntrySchedule` job
(#11244)
---
src/Entries/MinuteEntries.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/Entries/MinuteEntries.php b/src/Entries/MinuteEntries.php
index 5a39f73f986..13a21d52f39 100644
--- a/src/Entries/MinuteEntries.php
+++ b/src/Entries/MinuteEntries.php
@@ -2,13 +2,13 @@
namespace Statamic\Entries;
-use Carbon\Carbon;
+use Carbon\CarbonInterface;
use Statamic\Facades\Collection;
use Statamic\Facades\Entry;
class MinuteEntries
{
- public function __construct(private readonly Carbon $minute)
+ public function __construct(private readonly CarbonInterface $minute)
{
}
From b51b69231c2fd026ec94b8fdf79e9a22eb650ce2 Mon Sep 17 00:00:00 2001
From: John Koster
Date: Wed, 11 Dec 2024 14:36:52 -0600
Subject: [PATCH 002/490] [5.x] Adds "no_results" to automatic array variables
(#11234)
---
.../Language/Runtime/NodeProcessor.php | 1 +
tests/Antlers/Runtime/TemplateTest.php | 73 +++++++++++++++++++
2 files changed, 74 insertions(+)
diff --git a/src/View/Antlers/Language/Runtime/NodeProcessor.php b/src/View/Antlers/Language/Runtime/NodeProcessor.php
index b2be4c19dc6..b5dc8a5eaad 100644
--- a/src/View/Antlers/Language/Runtime/NodeProcessor.php
+++ b/src/View/Antlers/Language/Runtime/NodeProcessor.php
@@ -2550,6 +2550,7 @@ protected function addLoopIterationVariables($loop)
$value['count'] = $index + 1;
$value['index'] = $index;
$value['total_results'] = $total;
+ $value['no_results'] = false;
$value['first'] = $index === 0;
$value['last'] = $index === $lastIndex;
diff --git a/tests/Antlers/Runtime/TemplateTest.php b/tests/Antlers/Runtime/TemplateTest.php
index b3af5515df7..bcc54ad609f 100644
--- a/tests/Antlers/Runtime/TemplateTest.php
+++ b/tests/Antlers/Runtime/TemplateTest.php
@@ -6,6 +6,7 @@
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\MessageBag;
+use Illuminate\Support\Str;
use Illuminate\Support\ViewErrorBag;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
@@ -22,6 +23,7 @@
use Statamic\Fields\LabeledValue;
use Statamic\Fields\Value;
use Statamic\Fields\Values;
+use Statamic\Tags\Concerns\OutputsItems;
use Statamic\Tags\Tags;
use Statamic\View\Cascade;
use Tests\Antlers\Fixtures\Addon\Tags\RecursiveChildren;
@@ -2599,6 +2601,77 @@ public function test_it_returns_escaped_content()
$input = 'Hey, look at that @{{ noun }}!';
$this->assertSame('Hey, look at that {{ noun }}!', $this->renderString($input, []));
}
+
+ #[Test]
+ public function no_results_value_is_added_automatically()
+ {
+ (new class extends Tags
+ {
+ use OutputsItems;
+ public static $handle = 'the_tag';
+
+ public function index()
+ {
+ if ($this->params->get('has_value')) {
+ return $this->output(collect([
+ 'one',
+ 'two',
+ 'three',
+ ]));
+ }
+
+ return $this->parseNoResults();
+ }
+ })::register();
+
+ $template = <<<'TEMPLATE'
+{{ the_tag }}
+ {{ if no_results }}
+ No Results 1.
+ {{ the_tag has_value="true" }}
+ {{ if no_results }}
+ No Results 2.
+ {{ else }}
+ {{ value }}
+ {{ /if }}
+ {{ /the_tag }}
+
+ {{ if no_results }} No Results 1.1 {{ /if }}
+ {{ else }}
+ Has Results 1.
+ {{ /if }}
+{{ /the_tag }}
+TEMPLATE;
+
+ $this->assertSame(
+ 'No Results 1. one two three No Results 1.1',
+ Str::squish($this->renderString($template, [], true))
+ );
+
+ $template = <<<'TEMPLATE'
+{{ the_tag }}
+ {{ if no_results }}
+ No Results 1.
+ {{ the_tag has_value="true" as="items" }}
+ {{ if no_results }}
+ No Results 2.
+ {{ else }}
+ {{ items }}{{ value }} {{ /items }}
+ {{ /if }}
+ {{ /the_tag }}
+
+ {{ if no_results }} No Results 1.1 {{ /if }}
+ {{ else }}
+ Has Results 1.
+ {{ /if }}
+{{ /the_tag }}
+TEMPLATE;
+
+ $this->assertSame(
+ 'No Results 1. one two three No Results 1.1',
+ Str::squish($this->renderString($template, [], true))
+ );
+ }
}
class NonArrayableObject
From 26751a9be294f01132a946af5006d3169e0f2c63 Mon Sep 17 00:00:00 2001
From: Jason Varga
Date: Wed, 11 Dec 2024 15:39:15 -0500
Subject: [PATCH 003/490] [5.x] Fix subdirectory autodiscovery on Windows
(#11246)
---
src/Providers/ExtensionServiceProvider.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Providers/ExtensionServiceProvider.php b/src/Providers/ExtensionServiceProvider.php
index 2a32a74bc24..92432a21c56 100644
--- a/src/Providers/ExtensionServiceProvider.php
+++ b/src/Providers/ExtensionServiceProvider.php
@@ -336,7 +336,7 @@ protected function registerAppExtensions($folder, $requiredClass)
}
foreach ($this->app['files']->allFiles($path) as $file) {
- $relativePathOfFolder = str_replace(app_path('/'), '', $file->getPath());
+ $relativePathOfFolder = str_replace(app_path(DIRECTORY_SEPARATOR), '', $file->getPath());
$namespace = str_replace('/', '\\', $relativePathOfFolder);
$class = $file->getBasename('.php');
From 7ecc2be207d4fa176c5fc5c8754cfcdfabbad308 Mon Sep 17 00:00:00 2001
From: Philipp Daun
Date: Wed, 11 Dec 2024 22:19:20 +0100
Subject: [PATCH 004/490] [5.x] Fix asset upload concurrency on folder upload
(#11225)
---
resources/js/components/assets/Uploader.vue | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/resources/js/components/assets/Uploader.vue b/resources/js/components/assets/Uploader.vue
index 816c97f956c..6e0904d4e67 100644
--- a/resources/js/components/assets/Uploader.vue
+++ b/resources/js/components/assets/Uploader.vue
@@ -68,6 +68,15 @@ export default {
},
+ computed: {
+
+ activeUploads() {
+ return this.uploads.filter(u => u.instance.state === 'started');
+ }
+
+ },
+
+
methods: {
browse() {
@@ -230,6 +239,9 @@ export default {
},
processUploadQueue() {
+ // If we're already uploading, don't start another
+ if (this.activeUploads.length) return;
+
// Make sure we're not grabbing a running or failed upload
const upload = this.uploads.find(u => u.instance.state === 'new' && !u.errorMessage);
if (!upload) return;
@@ -248,6 +260,8 @@ export default {
response.status === 200
? this.handleUploadSuccess(id, json)
: this.handleUploadError(id, response.status, json);
+
+ this.processUploadQueue();
});
},
From d62cc13484aa28ec532f814669630f8c5c4a3915 Mon Sep 17 00:00:00 2001
From: Jason Varga
Date: Wed, 11 Dec 2024 16:36:25 -0500
Subject: [PATCH 005/490] changelog
---
CHANGELOG.md | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b623051c33b..ecc966d77c0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,15 @@
# Release Notes
+## 5.42.1 (2024-12-11)
+
+### What's fixed
+- Fix asset upload concurrency on folder upload [#11225](https://github.com/statamic/cms/issues/11225) by @daun
+- Fix subdirectory autodiscovery on Windows [#11246](https://github.com/statamic/cms/issues/11246) by @jasonvarga
+- Fix type error in `HandleEntrySchedule` job [#11244](https://github.com/statamic/cms/issues/11244) by @duncanmcclean
+- Fix `no_results` cascade [#11234](https://github.com/statamic/cms/issues/11234) by @JohnathonKoster
+
+
+
## 5.42.0 (2024-12-05)
### What's new
From 684eb68224bb013bc3d988fb6c1bd75823733fd1 Mon Sep 17 00:00:00 2001
From: Alexander Jensen
Date: Thu, 12 Dec 2024 16:58:02 +0100
Subject: [PATCH 006/490] [5.x] Fix collection title_format when using
translations (#11248)
---
src/Entries/Entry.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Entries/Entry.php b/src/Entries/Entry.php
index 0de0795936b..06a18e7c0e3 100644
--- a/src/Entries/Entry.php
+++ b/src/Entries/Entry.php
@@ -1008,7 +1008,7 @@ public function autoGeneratedTitle()
// Since the slug is generated from the title, we'll avoid augmenting
// the slug which could result in an infinite loop in some cases.
- $title = $this->withLocale($this->site()->locale(), fn () => (string) Antlers::parse($format, $this->augmented()->except('slug')->all()));
+ $title = $this->withLocale($this->site()->lang(), fn () => (string) Antlers::parse($format, $this->augmented()->except('slug')->all()));
return trim($title);
}
From c02847598a921f20b6749dbe145f899626f288a5 Mon Sep 17 00:00:00 2001
From: Jason Varga
Date: Thu, 12 Dec 2024 11:20:41 -0500
Subject: [PATCH 007/490] [5.x] Move bard source button into field actions
(#11250)
---
.../js/components/fieldtypes/bard/BardFieldtype.vue | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/resources/js/components/fieldtypes/bard/BardFieldtype.vue b/resources/js/components/fieldtypes/bard/BardFieldtype.vue
index 98585b74f1b..2e7c1b96dcc 100644
--- a/resources/js/components/fieldtypes/bard/BardFieldtype.vue
+++ b/resources/js/components/fieldtypes/bard/BardFieldtype.vue
@@ -31,9 +31,6 @@
:config="config"
:bard="_self"
:editor="editor" />
-
@@ -49,9 +46,6 @@
:config="config"
:bard="_self"
:editor="editor" />
-
@@ -370,6 +364,12 @@ export default {
visibleWhenReadOnly: true,
visible: this.config.fullscreen,
},
+ {
+ title: __('Show HTML Source'),
+ run: () => this.showSource = !this.showSource,
+ visibleWhenReadOnly: true,
+ visible: this.allowSource,
+ },
];
},
From bcd4e17e97c5013500c7d527249a505aa57e4392 Mon Sep 17 00:00:00 2001
From: Michael Aerni
Date: Thu, 12 Dec 2024 15:19:10 -0500
Subject: [PATCH 008/490] =?UTF-8?q?[5.x]=20Throw=20404=20on=20collection?=
=?UTF-8?q?=20routes=20if=20taxonomy=20isn=E2=80=99t=20assigned=20to=20col?=
=?UTF-8?q?lection=20(#10438)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Jason Varga
---
src/Taxonomies/LocalizedTerm.php | 4 ++++
src/Taxonomies/Taxonomy.php | 4 ++++
tests/Data/Taxonomies/ViewsTest.php | 24 ++++++++++++++++++++++++
3 files changed, 32 insertions(+)
diff --git a/src/Taxonomies/LocalizedTerm.php b/src/Taxonomies/LocalizedTerm.php
index 86e5488372f..f40b394ae00 100644
--- a/src/Taxonomies/LocalizedTerm.php
+++ b/src/Taxonomies/LocalizedTerm.php
@@ -375,6 +375,10 @@ public function toResponse($request)
throw new NotFoundHttpException;
}
+ if ($this->collection() && ! $this->taxonomy()->collections()->contains($this->collection())) {
+ throw new NotFoundHttpException;
+ }
+
return (new DataResponse($this))->toResponse($request);
}
diff --git a/src/Taxonomies/Taxonomy.php b/src/Taxonomies/Taxonomy.php
index b2bfd4e76e2..b7cd44e4b28 100644
--- a/src/Taxonomies/Taxonomy.php
+++ b/src/Taxonomies/Taxonomy.php
@@ -381,6 +381,10 @@ public function toResponse($request)
throw new NotFoundHttpException;
}
+ if ($this->collection() && ! $this->collections()->contains($this->collection())) {
+ throw new NotFoundHttpException;
+ }
+
return (new \Statamic\Http\Responses\DataResponse($this))
->with([
'terms' => $termQuery = $this->queryTerms()->where('site', $site),
diff --git a/tests/Data/Taxonomies/ViewsTest.php b/tests/Data/Taxonomies/ViewsTest.php
index b3175a7c7e9..812488cb9db 100644
--- a/tests/Data/Taxonomies/ViewsTest.php
+++ b/tests/Data/Taxonomies/ViewsTest.php
@@ -139,6 +139,18 @@ public function the_collection_specific_taxonomy_url_404s_if_the_view_doesnt_exi
$this->get('/the-blog/tags/test')->assertNotFound();
}
+ #[Test]
+ public function the_collection_specific_taxonomy_url_404s_if_the_collection_is_not_configured()
+ {
+ $this->mountBlogPageToBlogCollection();
+
+ $this->viewShouldReturnRaw('blog.tags.index', '{{ title }} index');
+
+ $this->blogCollection->taxonomies([])->save();
+
+ $this->get('/the-blog/tags')->assertNotFound();
+ }
+
#[Test]
public function it_loads_the_collection_specific_taxonomy_url_if_the_view_exists()
{
@@ -157,6 +169,18 @@ public function the_collection_specific_term_url_404s_if_the_view_doesnt_exist()
$this->get('/the-blog/tags/test')->assertNotFound();
}
+ #[Test]
+ public function the_collection_specific_term_url_404s_if_the_collection_is_not_assigned_to_the_taxonomy()
+ {
+ $this->mountBlogPageToBlogCollection();
+
+ $this->viewShouldReturnRaw('blog.tags.show', 'showing {{ title }}');
+
+ $this->blogCollection->taxonomies([])->save();
+
+ $this->get('/the-blog/tags/test')->assertNotFound();
+ }
+
#[Test]
public function it_loads_the_collection_specific_term_url_if_the_view_exists()
{
From 1003d402934541aefa9f409444933ed1c4cd0e11 Mon Sep 17 00:00:00 2001
From: Duncan McClean
Date: Thu, 12 Dec 2024 20:21:55 +0000
Subject: [PATCH 009/490] [5.x] Table Fieldtype: Add `max_columns` and
`max_rows` options (#11224)
Co-authored-by: Jason Varga
---
resources/lang/en/fieldtypes.php | 2 ++
src/Fieldtypes/Table.php | 10 ++++++++++
2 files changed, 12 insertions(+)
diff --git a/resources/lang/en/fieldtypes.php b/resources/lang/en/fieldtypes.php
index 7b0e3756653..f493e2394d8 100644
--- a/resources/lang/en/fieldtypes.php
+++ b/resources/lang/en/fieldtypes.php
@@ -169,6 +169,8 @@
'slug.config.show_regenerate' => 'Show the regenerate button to re-slugify from the target field.',
'slug.title' => 'Slug',
'structures.title' => 'Structures',
+ 'table.config.max_columns' => 'Set a maximum number of columns.',
+ 'table.config.max_rows' => 'Set a maximum number of rows.',
'table.title' => 'Table',
'taggable.config.options' => 'Provide pre-defined tags that can be selected.',
'taggable.config.placeholder' => 'Type and press ↩ Enter',
diff --git a/src/Fieldtypes/Table.php b/src/Fieldtypes/Table.php
index bf235cc516c..d75e770fafc 100644
--- a/src/Fieldtypes/Table.php
+++ b/src/Fieldtypes/Table.php
@@ -18,6 +18,16 @@ protected function configFieldItems(): array
'instructions' => __('statamic::messages.fields_default_instructions'),
'type' => 'table',
],
+ 'max_rows' => [
+ 'display' => __('Max Rows'),
+ 'instructions' => __('statamic::fieldtypes.table.config.max_rows'),
+ 'type' => 'integer',
+ ],
+ 'max_columns' => [
+ 'display' => __('Max Columns'),
+ 'instructions' => __('statamic::fieldtypes.table.config.max_columns'),
+ 'type' => 'integer',
+ ],
];
}
From f210188646acc713cb738dbce1f3f09c730110e4 Mon Sep 17 00:00:00 2001
From: Ryan Mitchell
Date: Thu, 12 Dec 2024 21:20:50 +0000
Subject: [PATCH 010/490] [5.x] Support glide urls with URL params (#11003)
Co-authored-by: Jason Varga
---
src/Imaging/GlideManager.php | 7 ++++
src/Imaging/ImageGenerator.php | 4 ++-
tests/Imaging/ImageGeneratorTest.php | 50 ++++++++++++++++++++++++++++
3 files changed, 60 insertions(+), 1 deletion(-)
diff --git a/src/Imaging/GlideManager.php b/src/Imaging/GlideManager.php
index ef36459de73..927490a8267 100644
--- a/src/Imaging/GlideManager.php
+++ b/src/Imaging/GlideManager.php
@@ -167,6 +167,13 @@ private function getCachePathCallable()
$hashCallable = $this->getHashCallable();
return function ($path, $params) use ($hashCallable) {
+ $qs = Str::contains($path, '?') ? Str::after($path, '?') : null;
+ $path = Str::before($path, '?');
+
+ if ($qs) {
+ $path = Str::replaceLast('.', '-'.md5($qs).'.', $path);
+ }
+
$sourcePath = $this->getSourcePath($path);
if ($this->sourcePathPrefix) {
diff --git a/src/Imaging/ImageGenerator.php b/src/Imaging/ImageGenerator.php
index bd38934577e..cb44c6dde75 100644
--- a/src/Imaging/ImageGenerator.php
+++ b/src/Imaging/ImageGenerator.php
@@ -119,12 +119,13 @@ private function doGenerateByUrl($url, array $params)
$this->setParams($params);
$parsed = $this->parseUrl($url);
+ $qs = $parsed['query'];
$this->server->setSource($this->guzzleSourceFilesystem($parsed['base']));
$this->server->setSourcePathPrefix('/');
$this->server->setCachePathPrefix('http');
- return $this->generate($parsed['path']);
+ return $this->generate($parsed['path'].($qs ? '?'.$qs : ''));
}
/**
@@ -330,6 +331,7 @@ private function parseUrl($url)
return [
'path' => Str::after($parsed['path'], '/'),
'base' => $parsed['scheme'].'://'.$parsed['host'],
+ 'query' => $parsed['query'] ?? null,
];
}
}
diff --git a/tests/Imaging/ImageGeneratorTest.php b/tests/Imaging/ImageGeneratorTest.php
index 44bbaf745d3..b61446458ed 100644
--- a/tests/Imaging/ImageGeneratorTest.php
+++ b/tests/Imaging/ImageGeneratorTest.php
@@ -211,6 +211,56 @@ public function it_generates_an_image_by_external_url()
Event::assertDispatchedTimes(GlideImageGenerated::class, 1);
}
+ #[Test]
+ public function it_generates_an_image_by_external_url_with_query_string()
+ {
+ Event::fake();
+
+ $cacheKey = 'url::https://example.com/foo/hoff.jpg?query=david::4dbc41d8e3ba1ccd302641e509b48768';
+ $this->assertNull(Glide::cacheStore()->get($cacheKey));
+
+ $this->assertCount(0, $this->generatedImagePaths());
+
+ $this->app->bind('statamic.imaging.guzzle', function () {
+ $file = UploadedFile::fake()->image('', 30, 60);
+ $contents = file_get_contents($file->getPathname());
+
+ $response = new Response(200, [], $contents);
+
+ // Glide, Flysystem, or the Guzzle adapter will try to perform the requests
+ // at different points to check if the file exists or to get the content
+ // of it. Here we'll just mock the same response multiple times.
+ return new Client(['handler' => new MockHandler([
+ $response, $response, $response,
+ ])]);
+ });
+
+ // Generate the image twice to make sure it's cached.
+ foreach (range(1, 2) as $i) {
+ $path = $this->makeGenerator()->generateByUrl(
+ 'https://example.com/foo/hoff.jpg?query=david',
+ $userParams = ['w' => 100, 'h' => 100]
+ );
+ }
+
+ $qsHash = md5('query=david');
+
+ // Since we can't really mock the server, we'll generate the md5 hash the same
+ // way it does. It will not include the fit parameter since it's not an asset.
+ $md5 = $this->getGlideMd5("foo/hoff-{$qsHash}.jpg", $userParams);
+
+ // While writing this test I noticed that we don't include the domain in the
+ // cache path, so the same file path on two different domains will conflict.
+ // TODO: Fix this.
+ $expectedPath = "http/foo/hoff-{$qsHash}.jpg/{$md5}/hoff-{$qsHash}.jpg";
+
+ $this->assertEquals($expectedPath, $path);
+ $this->assertCount(1, $paths = $this->generatedImagePaths());
+ $this->assertContains($expectedPath, $paths);
+ $this->assertEquals($expectedPath, Glide::cacheStore()->get($cacheKey));
+ Event::assertDispatchedTimes(GlideImageGenerated::class, 1);
+ }
+
#[Test]
public function the_watermark_disk_is_the_public_directory_by_default()
{
From abaa0c20082dd1d7268e96fa92b3c0112a50a6b6 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 12 Dec 2024 16:32:26 -0500
Subject: [PATCH 011/490] [5.x] Bump nanoid from 3.3.6 to 3.3.8 (#11251)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
package-lock.json | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index cb4fd1f3bcb..1cc793ed479 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7938,9 +7938,9 @@
}
},
"node_modules/nanoid": {
- "version": "3.3.6",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
- "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
+ "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
"funding": [
{
"type": "github",
From 881e6c5c3412fdd027bb8e9f0649f61d521aca94 Mon Sep 17 00:00:00 2001
From: Arthur Perton
Date: Mon, 16 Dec 2024 16:22:57 +0100
Subject: [PATCH 012/490] [5.x] Add some options to the static warm command to
limit the number of requests (#11258)
---
src/Console/Commands/StaticWarm.php | 54 ++++++++++++++
tests/Console/Commands/StaticWarmTest.php | 85 ++++++++++++++++++++++-
2 files changed, 138 insertions(+), 1 deletion(-)
diff --git a/src/Console/Commands/StaticWarm.php b/src/Console/Commands/StaticWarm.php
index 7bb26f59b67..03e33da0b53 100644
--- a/src/Console/Commands/StaticWarm.php
+++ b/src/Console/Commands/StaticWarm.php
@@ -38,6 +38,9 @@ class StaticWarm extends Command
{--p|password= : HTTP authentication password}
{--insecure : Skip SSL verification}
{--uncached : Only warm uncached URLs}
+ {--max-depth= : Maximum depth of URLs to warm}
+ {--include= : Only warm specific URLs}
+ {--exclude= : Exclude specific URLs}
';
protected $description = 'Warms the static cache by visiting all URLs';
@@ -179,6 +182,9 @@ private function uris(): Collection
->merge($this->customRouteUris())
->merge($this->additionalUris())
->unique()
+ ->filter(fn ($uri) => $this->shouldInclude($uri))
+ ->reject(fn ($uri) => $this->shouldExclude($uri))
+ ->reject(fn ($uri) => $this->exceedsMaxDepth($uri))
->reject(function ($uri) use ($cacher) {
if ($this->option('uncached') && $cacher->hasCachedPage(HttpRequest::create($uri))) {
return true;
@@ -192,6 +198,54 @@ private function uris(): Collection
->values();
}
+ private function shouldInclude($uri): bool
+ {
+ if (! $inclusions = $this->option('include')) {
+ return true;
+ }
+
+ $inclusions = explode(',', $inclusions);
+
+ return collect($inclusions)->contains(fn ($included) => $this->uriMatches($uri, $included));
+ }
+
+ private function shouldExclude($uri): bool
+ {
+ if (! $exclusions = $this->option('exclude')) {
+ return false;
+ }
+
+ $exclusions = explode(',', $exclusions);
+
+ return collect($exclusions)->contains(fn ($excluded) => $this->uriMatches($uri, $excluded));
+ }
+
+ private function uriMatches($uri, $pattern): bool
+ {
+ $uri = URL::makeRelative($uri);
+
+ if (Str::endsWith($pattern, '*')) {
+ $prefix = Str::removeRight($pattern, '*');
+
+ if (Str::startsWith($uri, $prefix) && ! (Str::endsWith($prefix, '/') && $uri === $prefix)) {
+ return true;
+ }
+ } elseif (Str::removeRight($uri, '/') === Str::removeRight($pattern, '/')) {
+ return true;
+ }
+
+ return false;
+ }
+
+ private function exceedsMaxDepth($uri): bool
+ {
+ if (! $max = $this->option('max-depth')) {
+ return false;
+ }
+
+ return count(explode('/', trim(URL::makeRelative($uri), '/'))) > $max;
+ }
+
private function shouldVerifySsl(): bool
{
if ($this->option('insecure')) {
diff --git a/tests/Console/Commands/StaticWarmTest.php b/tests/Console/Commands/StaticWarmTest.php
index daa5eb03d32..bcb7f5fdd87 100644
--- a/tests/Console/Commands/StaticWarmTest.php
+++ b/tests/Console/Commands/StaticWarmTest.php
@@ -44,7 +44,7 @@ public function it_warms_the_static_cache()
}
#[Test]
- public function it_only_visits_uncached_urls_when_the_eco_option_is_used()
+ public function it_only_visits_uncached_urls_when_the_uncached_option_is_used()
{
$mock = Mockery::mock(Cacher::class);
$mock->shouldReceive('hasCachedPage')->times(2)->andReturn(true, false);
@@ -58,6 +58,89 @@ public function it_only_visits_uncached_urls_when_the_eco_option_is_used()
->assertExitCode(0);
}
+ #[Test]
+ public function it_only_visits_included_urls()
+ {
+ config(['statamic.static_caching.strategy' => 'half']);
+
+ $this->createPage('blog');
+ $this->createPage('news');
+
+ Collection::make('blog')
+ ->routes('/blog/{slug}')
+ ->template('default')
+ ->save();
+
+ Collection::make('news')
+ ->routes('/news/{slug}')
+ ->template('default')
+ ->save();
+
+ EntryFactory::slug('post-1')->collection('blog')->id('blog-post-1')->create();
+ EntryFactory::slug('post-2')->collection('blog')->id('blog-post-2')->create();
+ EntryFactory::slug('article-1')->collection('news')->id('news-article-1')->create();
+ EntryFactory::slug('article-2')->collection('news')->id('news-article-2')->create();
+ EntryFactory::slug('article-3')->collection('news')->id('news-article-3')->create();
+
+ $this->artisan('statamic:static:warm', ['--include' => '/blog/post-1,/news/*'])
+ ->expectsOutput('Visiting 4 URLs...')
+ ->assertExitCode(0);
+ }
+
+ #[Test]
+ public function it_doesnt_visit_excluded_urls()
+ {
+ config(['statamic.static_caching.strategy' => 'half']);
+
+ $this->createPage('blog');
+ $this->createPage('news');
+
+ Collection::make('blog')
+ ->routes('/blog/{slug}')
+ ->template('default')
+ ->save();
+
+ Collection::make('news')
+ ->routes('/news/{slug}')
+ ->template('default')
+ ->save();
+
+ EntryFactory::slug('post-1')->collection('blog')->id('blog-post-1')->create();
+ EntryFactory::slug('post-2')->collection('blog')->id('blog-post-2')->create();
+ EntryFactory::slug('article-1')->collection('news')->id('news-article-1')->create();
+ EntryFactory::slug('article-2')->collection('news')->id('news-article-2')->create();
+ EntryFactory::slug('article-3')->collection('news')->id('news-article-3')->create();
+
+ $this->artisan('statamic:static:warm', ['--exclude' => '/about,/contact,/blog/*,/news/article-2'])
+ ->expectsOutput('Visiting 4 URLs...')
+ ->assertExitCode(0);
+ }
+
+ #[Test]
+ public function it_respects_max_depth()
+ {
+ config(['statamic.static_caching.strategy' => 'half']);
+
+ Collection::make('blog')
+ ->routes('/awesome/blog/{slug}')
+ ->template('default')
+ ->save();
+
+ Collection::make('news')
+ ->routes('/news/{slug}')
+ ->template('default')
+ ->save();
+
+ EntryFactory::slug('post-1')->collection('blog')->id('blog-post-1')->create();
+ EntryFactory::slug('post-2')->collection('blog')->id('blog-post-2')->create();
+ EntryFactory::slug('post-3')->collection('blog')->id('blog-post-3')->create();
+ EntryFactory::slug('article-1')->collection('news')->id('news-article-1')->create();
+
+ $this->artisan('statamic:static:warm', ['--max-depth' => 2])
+ ->expectsOutput('Visiting 3 URLs...')
+ ->assertExitCode(0);
+ }
+
#[Test]
public function it_doesnt_queue_the_requests_when_connection_is_set_to_sync()
{
From b884d63c1377c4256613141c6ef970069dbf36d0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Miloslav=20Ko=C5=A1t=C3=AD=C5=99?=
Date: Mon, 16 Dec 2024 20:55:21 +0100
Subject: [PATCH 013/490] [5.x] OAuth: option not to create or update user
during authentication (#10853)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Koštíř Miloslav
Co-authored-by: Jason Varga
---
config/oauth.php | 38 +++++++++++
resources/views/auth/unauthorized.blade.php | 6 +-
src/Http/Controllers/OAuthController.php | 47 +++++++++++--
src/OAuth/Provider.php | 20 +++++-
tests/OAuth/ProviderTest.php | 75 ++++++++++++++++++++-
5 files changed, 177 insertions(+), 9 deletions(-)
diff --git a/config/oauth.php b/config/oauth.php
index 2b5b931b1ab..d7e3fd2488e 100644
--- a/config/oauth.php
+++ b/config/oauth.php
@@ -15,6 +15,44 @@
'callback' => 'oauth/{provider}/callback',
],
+ /*
+ |--------------------------------------------------------------------------
+ | Create User
+ |--------------------------------------------------------------------------
+ |
+ | Whether or not a user account should be created upon authentication
+ | with an OAuth provider. If disabled, a user account will be need
+ | to be explicitly created ahead of time.
+ |
+ */
+
+ 'create_user' => true,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Merge User Data
+ |--------------------------------------------------------------------------
+ |
+ | When authenticating with an OAuth provider, the user data returned
+ | such as their name will be merged with the existing user account.
+ |
+ */
+
+ 'merge_user_data' => true,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Unauthorized Redirect
+ |--------------------------------------------------------------------------
+ |
+ | This controls where the user is taken after authenticating with
+ | an OAuth provider but their account is unauthorized. This may
+ | happen when the create_user option has been set to false.
+ |
+ */
+
+ 'unauthorized_redirect' => null,
+
/*
|--------------------------------------------------------------------------
| Remember Me
diff --git a/resources/views/auth/unauthorized.blade.php b/resources/views/auth/unauthorized.blade.php
index 3502faa78ba..d781cefc793 100644
--- a/resources/views/auth/unauthorized.blade.php
+++ b/resources/views/auth/unauthorized.blade.php
@@ -10,7 +10,11 @@
diff --git a/src/Http/Controllers/OAuthController.php b/src/Http/Controllers/OAuthController.php
index 880a6ea9227..ac98813dfac 100644
--- a/src/Http/Controllers/OAuthController.php
+++ b/src/Http/Controllers/OAuthController.php
@@ -36,14 +36,24 @@ public function handleProviderCallback(Request $request, string $provider)
return $this->redirectToProvider($request, $provider);
}
- $user = $oauth->findOrCreateUser($providerUser);
+ if ($user = $oauth->findUser($providerUser)) {
+ if (config('statamic.oauth.merge_user_data', true)) {
+ $user = $oauth->mergeUser($user, $providerUser);
+ }
+ } elseif (config('statamic.oauth.create_user', true)) {
+ $user = $oauth->createUser($providerUser);
+ }
+
+ if ($user) {
+ session()->put('oauth-provider', $provider);
- session()->put('oauth-provider', $provider);
+ Auth::guard($request->session()->get('statamic.oauth.guard'))
+ ->login($user, config('statamic.oauth.remember_me', true));
- Auth::guard($request->session()->get('statamic.oauth.guard'))
- ->login($user, config('statamic.oauth.remember_me', true));
+ return redirect()->to($this->successRedirectUrl());
+ }
- return redirect()->to($this->successRedirectUrl());
+ return redirect()->to($this->unauthorizedRedirectUrl());
}
protected function successRedirectUrl()
@@ -60,4 +70,31 @@ protected function successRedirectUrl()
return Arr::get($query, 'redirect', $default);
}
+
+ protected function unauthorizedRedirectUrl()
+ {
+ // If a URL has been explicitly defined, use that.
+ if ($url = config('statamic.oauth.unauthorized_redirect')) {
+ return $url;
+ }
+
+ // We'll check the redirect to see if they were intending on
+ // accessing the CP. If they were, we'll redirect them to
+ // the unauthorized page in the CP. Otherwise, to home.
+
+ $default = '/';
+ $previous = session('_previous.url');
+
+ if (! $query = Arr::get(parse_url($previous), 'query')) {
+ return $default;
+ }
+
+ parse_str($query, $query);
+
+ if (! $redirect = Arr::get($query, 'redirect')) {
+ return $default;
+ }
+
+ return $redirect === '/'.config('statamic.cp.route') ? cp_route('unauthorized') : $default;
+ }
}
diff --git a/src/OAuth/Provider.php b/src/OAuth/Provider.php
index b2000522286..0aeafdf8fe9 100644
--- a/src/OAuth/Provider.php
+++ b/src/OAuth/Provider.php
@@ -45,15 +45,31 @@ public function getUserId(string $id): ?string
}
public function findOrCreateUser($socialite): StatamicUser
+ {
+ if ($user = $this->findUser($socialite)) {
+ return config('statamic.oauth.merge_user_data', true)
+ ? $this->mergeUser($user, $socialite)
+ : $user;
+ }
+
+ return $this->createUser($socialite);
+ }
+
+ /**
+ * Find a Statamic user by a Socialite user.
+ *
+ * @param SocialiteUser $socialite
+ */
+ public function findUser($socialite): ?StatamicUser
{
if (
($user = User::findByOAuthId($this, $socialite->getId())) ||
($user = User::findByEmail($socialite->getEmail()))
) {
- return $this->mergeUser($user, $socialite);
+ return $user;
}
- return $this->createUser($socialite);
+ return null;
}
/**
diff --git a/tests/OAuth/ProviderTest.php b/tests/OAuth/ProviderTest.php
index e5d2edecd99..917aba151e6 100644
--- a/tests/OAuth/ProviderTest.php
+++ b/tests/OAuth/ProviderTest.php
@@ -74,6 +74,8 @@ public function it_merges_data()
$user = $this->user()->save();
+ $this->assertEquals(['name' => 'foo', 'extra' => 'bar'], $user->data()->all());
+
$provider->mergeUser($user, $this->socialite());
$this->assertEquals(['name' => 'Foo Bar', 'extra' => 'bar'], $user->data()->all());
@@ -122,20 +124,91 @@ public function it_creates_a_user()
}
#[Test]
- public function it_finds_an_existing_user_by_email()
+ public function it_finds_an_existing_user_via_find_user_method()
+ {
+ $provider = $this->provider();
+
+ $savedUser = $this->user()->save();
+
+ $this->assertCount(1, UserFacade::all());
+ $this->assertEquals([$savedUser], UserFacade::all()->all());
+
+ $foundUser = $provider->findUser($this->socialite());
+
+ $this->assertCount(1, UserFacade::all());
+ $this->assertEquals([$savedUser], UserFacade::all()->all());
+ $this->assertEquals($savedUser, $foundUser);
+ }
+
+ #[Test]
+ public function it_does_not_find_or_create_a_user_via_find_user_method()
+ {
+ $this->assertCount(0, UserFacade::all());
+
+ $provider = $this->provider();
+ $foundUser = $provider->findUser($this->socialite());
+
+ $this->assertNull($foundUser);
+
+ $this->assertCount(0, UserFacade::all());
+ $user = UserFacade::all()->get(0);
+ $this->assertNull($user);
+ }
+
+ #[Test]
+ public function it_finds_an_existing_user_via_find_or_create_user_method()
+ {
+ $provider = $this->provider();
+
+ $savedUser = $this->user()->save();
+
+ $this->assertCount(1, UserFacade::all());
+ $this->assertEquals([$savedUser], UserFacade::all()->all());
+ $this->assertEquals('foo', $savedUser->name);
+
+ $foundUser = $provider->findOrCreateUser($this->socialite());
+
+ $this->assertCount(1, UserFacade::all());
+ $this->assertEquals([$savedUser], UserFacade::all()->all());
+ $this->assertEquals($savedUser, $foundUser);
+ $this->assertEquals('Foo Bar', $savedUser->name);
+ }
+
+ #[Test]
+ public function it_finds_an_existing_user_via_find_or_create_user_method_but_doesnt_merge_data()
{
+ config(['statamic.oauth.merge_user_data' => false]);
+
$provider = $this->provider();
$savedUser = $this->user()->save();
$this->assertCount(1, UserFacade::all());
$this->assertEquals([$savedUser], UserFacade::all()->all());
+ $this->assertEquals('foo', $savedUser->name);
$foundUser = $provider->findOrCreateUser($this->socialite());
$this->assertCount(1, UserFacade::all());
$this->assertEquals([$savedUser], UserFacade::all()->all());
$this->assertEquals($savedUser, $foundUser);
+ $this->assertEquals('foo', $savedUser->name);
+ }
+
+ #[Test]
+ public function it_creates_a_user_via_find_or_create_user_method()
+ {
+ $this->assertCount(0, UserFacade::all());
+
+ $provider = $this->provider();
+ $provider->findOrCreateUser($this->socialite());
+
+ $this->assertCount(1, UserFacade::all());
+ $user = UserFacade::all()->get(0);
+ $this->assertNotNull($user);
+ $this->assertEquals('foo@bar.com', $user->email());
+ $this->assertEquals('Foo Bar', $user->name());
+ $this->assertEquals($user->id(), $provider->getUserId('foo-bar'));
}
#[Test]
From c0318e9e3904f824c998a39b1048488a9656f8b7 Mon Sep 17 00:00:00 2001
From: Maurice
Date: Mon, 16 Dec 2024 23:47:13 +0100
Subject: [PATCH 014/490] [5.x] Fix ButtonGroup not showing active state if
value are numbers (#10916)
Co-authored-by: Jason Varga
---
.../components/fieldtypes/ButtonGroupFieldtype.vue | 4 ++--
tests/Fieldtypes/HasSelectOptionsTests.php | 14 +++++++++++---
2 files changed, 13 insertions(+), 5 deletions(-)
diff --git a/resources/js/components/fieldtypes/ButtonGroupFieldtype.vue b/resources/js/components/fieldtypes/ButtonGroupFieldtype.vue
index f991d0ece21..ff826d51fb1 100644
--- a/resources/js/components/fieldtypes/ButtonGroupFieldtype.vue
+++ b/resources/js/components/fieldtypes/ButtonGroupFieldtype.vue
@@ -7,10 +7,10 @@
ref="button"
type="button"
:name="name"
- @click="updateSelectedOption($event.target.value)"
+ @click="updateSelectedOption(option.value)"
:value="option.value"
:disabled="isReadOnly"
- :class="{'active': value === option.value}"
+ :class="{'active': value == option.value}"
v-text="option.label || option.value"
/>
diff --git a/tests/Fieldtypes/HasSelectOptionsTests.php b/tests/Fieldtypes/HasSelectOptionsTests.php
index 16719bf2cd3..849209c6f1c 100644
--- a/tests/Fieldtypes/HasSelectOptionsTests.php
+++ b/tests/Fieldtypes/HasSelectOptionsTests.php
@@ -14,26 +14,30 @@ public function it_preloads_options($options, $expected)
$field = $this->field(['options' => $options]);
$this->assertArrayHasKey('options', $preloaded = $field->preload());
- $this->assertEquals($expected, $preloaded['options']);
+ $this->assertSame($expected, $preloaded['options']);
}
public static function optionsProvider()
{
return [
'list' => [
- ['one', 'two', 'three'],
+ ['one', 'two', 'three', 50, '100'],
[
['value' => 'one', 'label' => 'one'],
['value' => 'two', 'label' => 'two'],
['value' => 'three', 'label' => 'three'],
+ ['value' => 50, 'label' => 50],
+ ['value' => '100', 'label' => '100'],
],
],
'associative' => [
- ['one' => 'One', 'two' => 'Two', 'three' => 'Three'],
+ ['one' => 'One', 'two' => 'Two', 'three' => 'Three', 50 => '50', '100' => 100],
[
['value' => 'one', 'label' => 'One'],
['value' => 'two', 'label' => 'Two'],
['value' => 'three', 'label' => 'Three'],
+ ['value' => 50, 'label' => '50'],
+ ['value' => 100, 'label' => 100],
],
],
'multidimensional' => [
@@ -41,11 +45,15 @@ public static function optionsProvider()
['key' => 'one', 'value' => 'One'],
['key' => 'two', 'value' => 'Two'],
['key' => 'three', 'value' => 'Three'],
+ ['key' => 50, 'value' => 50],
+ ['key' => '100', 'value' => 100],
],
[
['value' => 'one', 'label' => 'One'],
['value' => 'two', 'label' => 'Two'],
['value' => 'three', 'label' => 'Three'],
+ ['value' => 50, 'label' => 50],
+ ['value' => '100', 'label' => 100],
],
],
];
From ae98dff984f39792ed2199f654be8e85fcb4ef89 Mon Sep 17 00:00:00 2001
From: Duncan McClean
Date: Tue, 17 Dec 2024 15:34:28 +0000
Subject: [PATCH 015/490] [5.x] Fix autoloading when addon has multiple service
providers (#11128)
Co-authored-by: Jason Varga
Co-authored-by: Erin Dalzell
---
src/Providers/AddonServiceProvider.php | 62 +++++++++++++++++++++++---
1 file changed, 56 insertions(+), 6 deletions(-)
diff --git a/src/Providers/AddonServiceProvider.php b/src/Providers/AddonServiceProvider.php
index c637764fd13..5667b256989 100644
--- a/src/Providers/AddonServiceProvider.php
+++ b/src/Providers/AddonServiceProvider.php
@@ -182,6 +182,10 @@ abstract class AddonServiceProvider extends ServiceProvider
*/
protected $translations = true;
+ private static array $autoloaded = [];
+
+ private static array $bootedAddons = [];
+
public function boot()
{
Statamic::booted(function () {
@@ -216,6 +220,8 @@ public function boot()
->bootFieldsets()
->bootPublishAfterInstall()
->bootAddon();
+
+ static::$bootedAddons[] = $this->getAddon()->id();
});
}
@@ -454,6 +460,10 @@ protected function bootVite()
protected function bootConfig()
{
+ if (! $this->shouldBootRootItems()) {
+ return $this;
+ }
+
$filename = $this->getAddon()->slug();
$directory = $this->getAddon()->directory();
$origin = "{$directory}config/{$filename}.php";
@@ -473,6 +483,10 @@ protected function bootConfig()
protected function bootTranslations()
{
+ if (! $this->shouldBootRootItems()) {
+ return $this;
+ }
+
$slug = $this->getAddon()->slug();
$directory = $this->getAddon()->directory();
$origin = "{$directory}lang";
@@ -518,7 +532,7 @@ protected function bootRoutes()
$web = Arr::get(
array: $this->routes,
key: 'web',
- default: $this->app['files']->exists($path = $directory.'routes/web.php') ? $path : null
+ default: $this->shouldBootRootItems() && $this->app['files']->exists($path = $directory.'routes/web.php') ? $path : null
);
if ($web) {
@@ -528,7 +542,7 @@ protected function bootRoutes()
$cp = Arr::get(
array: $this->routes,
key: 'cp',
- default: $this->app['files']->exists($path = $directory.'routes/cp.php') ? $path : null
+ default: $this->shouldBootRootItems() && $this->app['files']->exists($path = $directory.'routes/cp.php') ? $path : null
);
if ($cp) {
@@ -538,7 +552,7 @@ protected function bootRoutes()
$actions = Arr::get(
array: $this->routes,
key: 'actions',
- default: $this->app['files']->exists($path = $directory.'routes/actions.php') ? $path : null
+ default: $this->shouldBootRootItems() && $this->app['files']->exists($path = $directory.'routes/actions.php') ? $path : null
);
if ($actions) {
@@ -620,6 +634,10 @@ protected function bootUpdateScripts()
protected function bootViews()
{
+ if (! $this->shouldBootRootItems()) {
+ return $this;
+ }
+
if (file_exists($this->getAddon()->directory().'resources/views')) {
$this->loadViewsFrom(
$this->getAddon()->directory().'resources/views',
@@ -746,6 +764,10 @@ protected function bootPublishAfterInstall()
protected function bootBlueprints()
{
+ if (! $this->shouldBootRootItems()) {
+ return $this;
+ }
+
if (! file_exists($path = "{$this->getAddon()->directory()}resources/blueprints")) {
return $this;
}
@@ -760,6 +782,10 @@ protected function bootBlueprints()
protected function bootFieldsets()
{
+ if (! $this->shouldBootRootItems()) {
+ return $this;
+ }
+
if (! file_exists($path = "{$this->getAddon()->directory()}resources/fieldsets")) {
return $this;
}
@@ -783,7 +809,8 @@ protected function autoloadFilesFromFolder($folder, $requiredClass = null)
return [];
}
- $path = $addon->directory().$addon->autoload().'/'.$folder;
+ $reflection = new \ReflectionClass(static::class);
+ $path = dirname($reflection->getFileName()).'/'.$folder;
if (! $this->app['files']->exists($path)) {
return [];
@@ -797,19 +824,42 @@ protected function autoloadFilesFromFolder($folder, $requiredClass = null)
}
$class = $file->getBasename('.php');
- $fqcn = $this->namespace().'\\'.str_replace('/', '\\', $folder).'\\'.$class;
+ $fqcn = $reflection->getNamespaceName().'\\'.str_replace('/', '\\', $folder).'\\'.$class;
if ((new \ReflectionClass($fqcn))->isAbstract() || (new \ReflectionClass($fqcn))->isInterface()) {
continue;
}
if ($requiredClass && ! is_subclass_of($fqcn, $requiredClass)) {
- return;
+ continue;
+ }
+
+ if (in_array($fqcn, static::$autoloaded)) {
+ continue;
}
$autoloadable[] = $fqcn;
+ static::$autoloaded[] = $fqcn;
}
return $autoloadable;
}
+
+ private function shouldBootRootItems()
+ {
+ $addon = $this->getAddon();
+
+ // We'll keep track of addons that have been booted to ensure that multiple
+ // providers don't try to boot things twice. This could happen if there are
+ // multiple providers in the root autoload directory (src) of an addon.
+ if (in_array($addon->id(), static::$bootedAddons)) {
+ return false;
+ }
+
+ // We only want to boot root items if the provider is in the autoloaded directory.
+ // i.e. It's the "root" provider. If it's in a subdirectory maybe the developer
+ // is organizing their providers. Things like tags etc. can be autoloaded but
+ // root level things like routes, views, config, blueprints, etc. will not.
+ return dirname((new \ReflectionClass(static::class))->getFileName()) === $addon->directory().$addon->autoload();
+ }
}
From b70a97bd54e8008b94c176137148e151a260e605 Mon Sep 17 00:00:00 2001
From: Duncan McClean
Date: Tue, 17 Dec 2024 15:34:50 +0000
Subject: [PATCH 016/490] [5.x] Prevent "Set Alt" button from running Replace
Asset action prematurely (#11269)
---
resources/js/components/fieldtypes/assets/AssetRow.vue | 1 +
resources/js/components/fieldtypes/assets/AssetTile.vue | 1 +
2 files changed, 2 insertions(+)
diff --git a/resources/js/components/fieldtypes/assets/AssetRow.vue b/resources/js/components/fieldtypes/assets/AssetRow.vue
index c23587b073a..b2538abebf5 100644
--- a/resources/js/components/fieldtypes/assets/AssetRow.vue
+++ b/resources/js/components/fieldtypes/assets/AssetRow.vue
@@ -34,6 +34,7 @@
|
|
1 %s%s