diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index 67088bfee303..b6198d0f5614 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -253,6 +253,42 @@ public static function endsWith($haystack, $needles) return false; } + /** + * Extracts an excerpt from text that matches the first instance of a phrase. + * + * @param string $text + * @param string $phrase + * @param array $options + * @return string|null + */ + public static function excerpt($text, $phrase = '', $options = []) + { + $radius = $options['radius'] ?? 100; + $omission = $options['omission'] ?? '...'; + + preg_match('/^(.*?)('.preg_quote((string) $phrase).')(.*)$/iu', (string) $text, $matches); + + if (empty($matches)) { + return null; + } + + $start = ltrim($matches[1]); + + $start = str(mb_substr($start, max(mb_strlen($start, 'UTF-8') - $radius, 0), $radius, 'UTF-8'))->ltrim()->unless( + fn ($startWithRadius) => $startWithRadius->exactly($start), + fn ($startWithRadius) => $startWithRadius->prepend($omission), + ); + + $end = rtrim($matches[3]); + + $end = str(mb_substr($end, 0, $radius, 'UTF-8'))->rtrim()->unless( + fn ($endWithRadius) => $endWithRadius->exactly($end), + fn ($endWithRadius) => $endWithRadius->append($omission), + ); + + return $start->append($matches[2], $end)->toString(); + } + /** * Cap a string with a single instance of a given value. * diff --git a/src/Illuminate/Support/Stringable.php b/src/Illuminate/Support/Stringable.php index 224c92468391..3ae7ed89f036 100644 --- a/src/Illuminate/Support/Stringable.php +++ b/src/Illuminate/Support/Stringable.php @@ -195,6 +195,18 @@ public function exactly($value) return $this->value === $value; } + /** + * Extracts an excerpt from text that matches the first instance of a phrase. + * + * @param string $phrase + * @param array $options + * @return string|null + */ + public function excerpt($phrase = '', $options = []) + { + return Str::excerpt($this->value, $phrase, $options); + } + /** * Explode the string into an array. * diff --git a/tests/Support/SupportStrTest.php b/tests/Support/SupportStrTest.php index a031ec4c015f..89a164f05ef1 100755 --- a/tests/Support/SupportStrTest.php +++ b/tests/Support/SupportStrTest.php @@ -149,6 +149,59 @@ public function testEndsWith() $this->assertFalse(Str::endsWith('你好', 'a')); } + public function testStrExcerpt() + { + $this->assertSame('...is a beautiful morn...', Str::excerpt('This is a beautiful morning', 'beautiful', ['radius' => 5])); + $this->assertSame('This is a...', Str::excerpt('This is a beautiful morning', 'this', ['radius' => 5])); + $this->assertSame('...iful morning', Str::excerpt('This is a beautiful morning', 'morning', ['radius' => 5])); + $this->assertNull(Str::excerpt('This is a beautiful morning', 'day')); + $this->assertSame('...is a beautiful! mor...', Str::excerpt('This is a beautiful! morning', 'Beautiful', ['radius' => 5])); + $this->assertSame('...is a beautiful? mor...', Str::excerpt('This is a beautiful? morning', 'beautiful', ['radius' => 5])); + $this->assertSame('', Str::excerpt('', '', ['radius' => 0])); + $this->assertSame('a', Str::excerpt('a', 'a', ['radius' => 0])); + $this->assertSame('...b...', Str::excerpt('abc', 'B', ['radius' => 0])); + $this->assertSame('abc', Str::excerpt('abc', 'b', ['radius' => 1])); + $this->assertSame('abc...', Str::excerpt('abcd', 'b', ['radius' => 1])); + $this->assertSame('...abc', Str::excerpt('zabc', 'b', ['radius' => 1])); + $this->assertSame('...abc...', Str::excerpt('zabcd', 'b', ['radius' => 1])); + $this->assertSame('zabcd', Str::excerpt('zabcd', 'b', ['radius' => 2])); + $this->assertSame('zabcd', Str::excerpt(' zabcd ', 'b', ['radius' => 4])); + $this->assertSame('...abc...', Str::excerpt('z abc d', 'b', ['radius' => 1])); + $this->assertSame('[...]is a beautiful morn[...]', Str::excerpt('This is a beautiful morning', 'beautiful', ['omission' => '[...]', 'radius' => 5])); + $this->assertSame( + 'This is the ultimate supercalifragilisticexpialidoceous very looooooooooooooooooong looooooooooooong beautiful morning with amazing sunshine and awesome tempera[...]', + Str::excerpt('This is the ultimate supercalifragilisticexpialidoceous very looooooooooooooooooong looooooooooooong beautiful morning with amazing sunshine and awesome temperatures. So what are you gonna do about it?', 'very', + ['omission' => '[...]'], + )); + + $this->assertSame('...y...', Str::excerpt('taylor', 'y', ['radius' => 0])); + $this->assertSame('...ayl...', Str::excerpt('taylor', 'Y', ['radius' => 1])); + $this->assertSame('