diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml index 359877d89f72..8dcfa62b65c0 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/releases.yml @@ -18,7 +18,7 @@ jobs: outputs: version: ${{ steps.version.outputs.version }} - notes: ${{ steps.cleaned-notes.outputs.release-notes }} + notes: ${{ steps.cleaned-notes.outputs.notes }} steps: - name: Checkout repository @@ -90,10 +90,11 @@ jobs: - name: Cleanup release notes id: cleaned-notes run: | - NOTES="${{ steps.generated-notes.outputs.release-notes }}" - NOTES=$(echo $NOTES | sed '/## What/d') - NOTES=$(echo $NOTES | sed '/## New Contributors/,$d') - echo "release-notes=${NOTES}" >> "$GITHUB_OUTPUT" + RELEASE_NOTES=$(echo $RELEASE_NOTES | sed '/## What/d') + RELEASE_NOTES=$(echo $RELEASE_NOTES | sed '/## New Contributors/,$d') + echo "notes=${RELEASE_NOTES}" >> "$GITHUB_OUTPUT" + env: + RELEASE_NOTES: ${{ steps.generated-notes.outputs.release-notes }} - name: Create release uses: softprops/action-gh-release@v2 @@ -102,7 +103,7 @@ jobs: with: tag_name: v${{ steps.version.outputs.version }} name: v${{ steps.version.outputs.version }} - body: ${{ steps.cleaned-notes.outputs.release-notes }} + body: ${{ steps.cleaned-notes.outputs.notes }} target_commitish: ${{ github.ref_name }} make_latest: 'legacy' diff --git a/src/Illuminate/Database/DetectsLostConnections.php b/src/Illuminate/Database/DetectsLostConnections.php index 622545bdcb4d..3c1f7a927dc7 100644 --- a/src/Illuminate/Database/DetectsLostConnections.php +++ b/src/Illuminate/Database/DetectsLostConnections.php @@ -19,6 +19,7 @@ protected function causedByLostConnection(Throwable $e) return Str::contains($message, [ 'server has gone away', + 'Server has gone away', 'no connection to the server', 'Lost connection', 'is dead or not enabled', diff --git a/src/Illuminate/Database/Eloquent/Factories/Factory.php b/src/Illuminate/Database/Eloquent/Factories/Factory.php index d9a453359d23..e8c25101361b 100644 --- a/src/Illuminate/Database/Eloquent/Factories/Factory.php +++ b/src/Illuminate/Database/Eloquent/Factories/Factory.php @@ -240,9 +240,9 @@ public function createOneQuietly($attributes = []) */ public function createMany(int|iterable|null $records = null) { - if (is_null($records)) { - $records = $this->count ?? 1; - } + $records ??= ($this->count ?? 1); + + $this->count = null; if (is_numeric($records)) { $records = array_fill(0, $records, []); diff --git a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php index 7a90dc998cc0..ef17cce6371c 100755 --- a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php @@ -784,7 +784,7 @@ public function findOr($id, $columns = ['*'], ?Closure $callback = null) * @param mixed $operator * @param mixed $value * @param string $boolean - * @return \Illuminate\Database\Eloquent\Model|static + * @return \Illuminate\Database\Eloquent\Model|static|null */ public function firstWhere($column, $operator = null, $value = null, $boolean = 'and') { @@ -795,7 +795,7 @@ public function firstWhere($column, $operator = null, $value = null, $boolean = * Execute the query and get the first result. * * @param array $columns - * @return mixed + * @return \Illuminate\Database\Eloquent\Model|static|null */ public function first($columns = ['*']) { diff --git a/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php b/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php index 70dd922b20b0..2f3dc31fef60 100644 --- a/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php +++ b/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php @@ -320,7 +320,7 @@ public function updateOrCreate(array $attributes, array $values = []) * @param mixed $operator * @param mixed $value * @param string $boolean - * @return \Illuminate\Database\Eloquent\Model|static + * @return \Illuminate\Database\Eloquent\Model|static|null */ public function firstWhere($column, $operator = null, $value = null, $boolean = 'and') { @@ -331,7 +331,7 @@ public function firstWhere($column, $operator = null, $value = null, $boolean = * Execute the query and get the first related model. * * @param array $columns - * @return mixed + * @return \Illuminate\Database\Eloquent\Model|static|null */ public function first($columns = ['*']) { diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index 48876cd9c072..60c00de993ad 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -591,6 +591,10 @@ public function storagePath($path = '') return $this->joinPaths($this->storagePath ?: $_ENV['LARAVEL_STORAGE_PATH'], $path); } + if (isset($_SERVER['LARAVEL_STORAGE_PATH'])) { + return $this->joinPaths($this->storagePath ?: $_SERVER['LARAVEL_STORAGE_PATH'], $path); + } + return $this->joinPaths($this->storagePath ?: $this->basePath('storage'), $path); } diff --git a/src/Illuminate/Translation/Translator.php b/src/Illuminate/Translation/Translator.php index 7b9ab9a56d6c..2a956830df98 100755 --- a/src/Illuminate/Translation/Translator.php +++ b/src/Illuminate/Translation/Translator.php @@ -262,6 +262,16 @@ protected function makeReplacements($line, array $replace) $shouldReplace = []; foreach ($replace as $key => $value) { + if ($value instanceof Closure) { + $line = preg_replace_callback( + '/<'.$key.'>(.*?)<\/'.$key.'>/', + fn ($args) => $value($args[1]), + $line + ); + + continue; + } + if (is_object($value) && isset($this->stringableHandlers[get_class($value)])) { $value = call_user_func($this->stringableHandlers[get_class($value)], $value); } diff --git a/tests/Database/DatabaseEloquentFactoryTest.php b/tests/Database/DatabaseEloquentFactoryTest.php index f46ebc3213f2..cc3c78504554 100644 --- a/tests/Database/DatabaseEloquentFactoryTest.php +++ b/tests/Database/DatabaseEloquentFactoryTest.php @@ -129,14 +129,30 @@ public function test_basic_model_can_be_created() $users = FactoryTestUserFactory::new()->createMany(2); $this->assertInstanceOf(Collection::class, $users); $this->assertCount(2, $users); + $this->assertInstanceOf(FactoryTestUser::class, $users->first()); $users = FactoryTestUserFactory::times(2)->createMany(); $this->assertInstanceOf(Collection::class, $users); $this->assertCount(2, $users); + $this->assertInstanceOf(FactoryTestUser::class, $users->first()); + + $users = FactoryTestUserFactory::times(2)->createMany(); + $this->assertInstanceOf(Collection::class, $users); + $this->assertCount(2, $users); + $this->assertInstanceOf(FactoryTestUser::class, $users->first()); + + $users = FactoryTestUserFactory::times(3)->createMany([ + ['name' => 'Taylor Otwell'], + ['name' => 'Jeffrey Way'], + ]); + $this->assertInstanceOf(Collection::class, $users); + $this->assertCount(2, $users); + $this->assertInstanceOf(FactoryTestUser::class, $users->first()); $users = FactoryTestUserFactory::new()->createMany(); $this->assertInstanceOf(Collection::class, $users); $this->assertCount(1, $users); + $this->assertInstanceOf(FactoryTestUser::class, $users->first()); $users = FactoryTestUserFactory::times(10)->create(); $this->assertCount(10, $users); diff --git a/tests/Routing/RoutingUrlGeneratorTest.php b/tests/Routing/RoutingUrlGeneratorTest.php index 1d92d5e6e1b0..1a1858298300 100755 --- a/tests/Routing/RoutingUrlGeneratorTest.php +++ b/tests/Routing/RoutingUrlGeneratorTest.php @@ -779,7 +779,7 @@ public function testSignedUrl() $this->assertTrue($url->hasValidSignature($request)); - $request = Request::create($url->signedRoute('foo').'?tempered=true'); + $request = Request::create($url->signedRoute('foo').'?tampered=true'); $this->assertFalse($url->hasValidSignature($request)); } @@ -827,7 +827,7 @@ public function testSignedRelativeUrl() $this->assertTrue($url->hasValidSignature($request, false)); - $request = Request::create($url->signedRoute('foo', [], null, false).'?tempered=true'); + $request = Request::create($url->signedRoute('foo', [], null, false).'?tampered=true'); $this->assertFalse($url->hasValidSignature($request, false)); } @@ -906,7 +906,7 @@ public function testSignedUrlWithKeyResolver() $this->assertTrue($url->hasValidSignature($request)); - $request = Request::create($url->signedRoute('foo').'?tempered=true'); + $request = Request::create($url->signedRoute('foo').'?tampered=true'); $this->assertFalse($url->hasValidSignature($request)); diff --git a/tests/Support/SupportStrTest.php b/tests/Support/SupportStrTest.php index 1f4ab12ad738..f8a7770e136a 100755 --- a/tests/Support/SupportStrTest.php +++ b/tests/Support/SupportStrTest.php @@ -12,11 +12,13 @@ class SupportStrTest extends TestCase { - public function testStringCanBeLimitedByWords() + public function testStringCanBeLimitedByWords(): void { $this->assertSame('Taylor...', Str::words('Taylor Otwell', 1)); $this->assertSame('Taylor___', Str::words('Taylor Otwell', 1, '___')); $this->assertSame('Taylor Otwell', Str::words('Taylor Otwell', 3)); + $this->assertSame('Taylor Otwell', Str::words('Taylor Otwell', -1, '...')); + $this->assertSame('', Str::words('', 3, '...')); } public function testStringCanBeLimitedByWordsNonAscii() @@ -114,11 +116,13 @@ public function testStringApa() $this->assertSame(' ', Str::apa(' ')); } - public function testStringWithoutWordsDoesntProduceError() + public function testStringWithoutWordsDoesntProduceError(): void { $nbsp = chr(0xC2).chr(0xA0); $this->assertSame(' ', Str::words(' ')); $this->assertEquals($nbsp, Str::words($nbsp)); + $this->assertSame(' ', Str::words(' ')); + $this->assertSame("\t\t\t", Str::words("\t\t\t")); } public function testStringAscii() @@ -265,7 +269,7 @@ public function testStrBefore() $this->assertSame('han', Str::before('han2nah', 2)); } - public function testStrBeforeLast() + public function testStrBeforeLast(): void { $this->assertSame('yve', Str::beforeLast('yvette', 'tte')); $this->assertSame('yvet', Str::beforeLast('yvette', 't')); @@ -276,6 +280,10 @@ public function testStrBeforeLast() $this->assertSame('yv0et', Str::beforeLast('yv0et0te', '0')); $this->assertSame('yv0et', Str::beforeLast('yv0et0te', 0)); $this->assertSame('yv2et', Str::beforeLast('yv2et2te', 2)); + $this->assertSame('', Str::beforeLast('', 'test')); + $this->assertSame('', Str::beforeLast('yvette', 'yvette')); + $this->assertSame('laravel', Str::beforeLast('laravel framework', ' ')); + $this->assertSame('yvette', Str::beforeLast("yvette\tyv0et0te", "\t")); } public function testStrBetween() diff --git a/tests/Translation/TranslationTranslatorTest.php b/tests/Translation/TranslationTranslatorTest.php index 07ec429aab77..86c8948ef94f 100755 --- a/tests/Translation/TranslationTranslatorTest.php +++ b/tests/Translation/TranslationTranslatorTest.php @@ -257,6 +257,42 @@ public function testGetJsonReplacesWithStringable() ); } + public function testTagReplacements() + { + $t = new Translator($this->getLoader(), 'en'); + + $t->getLoader()->shouldReceive('load')->once()->with('en', '*', '*')->andReturn([]); + $t->getLoader()->shouldReceive('load')->once()->with('en', 'We have some nice documentation', '*')->andReturn([]); + + $this->assertSame( + 'We have some nice documentation', + $t->get( + 'We have some nice documentation', + [ + 'docs-link' => fn ($children) => "$children", + ] + ) + ); + } + + public function testTagReplacementsHandleMultipleOfSameTag() + { + $t = new Translator($this->getLoader(), 'en'); + + $t->getLoader()->shouldReceive('load')->once()->with('en', '*', '*')->andReturn([]); + $t->getLoader()->shouldReceive('load')->once()->with('en', 'bold something else also bold', '*')->andReturn([]); + + $this->assertSame( + 'bold something else also bold', + $t->get( + 'bold something else also bold', + [ + 'bold-this' => fn ($children) => "$children", + ] + ) + ); + } + public function testDetermineLocalesUsingMethod() { $t = new Translator($this->getLoader(), 'en');