diff --git a/src/Utility/Text.php b/src/Utility/Text.php index f73c3fea459..e6c191b51a7 100644 --- a/src/Utility/Text.php +++ b/src/Utility/Text.php @@ -608,49 +608,70 @@ public static function truncate($text, $length = 100, array $options = []) } } - $truncate .= mb_substr($tag[3], 0, $left + $entitiesLength); + if (!$options['exact']) { + $words = explode(' ', $tag[3]); + // Keep at least one word. + if (count($words) === 1) { + $truncate .= mb_substr($tag[3], 0, $left + $entitiesLength); + } else { + $wordLength = 0; + $addWords = []; + // Append words until the length is crossed. + foreach ($words as $word) { + // Add words until we have enough letters. + if ($wordLength < $left + $entitiesLength) { + $addWords[] = $word; + } + // Include inter-word space. + $wordLength += mb_strlen($word) + 1; + } + $truncate .= implode(' ', $addWords); + + // If the string is longer than requested, find the last space and cut there. + $lastSpace = mb_strrpos($truncate, ' '); + if (mb_strlen($truncate) > $totalLength && $lastSpace !== false) { + $remainder = mb_substr($truncate, $lastSpace); + $truncate = mb_substr($truncate, 0, $lastSpace); + + // Re-add close tags that were cut off. + preg_match_all('/<\/([a-z]+)>/', $remainder, $droppedTags, PREG_SET_ORDER); + if ($droppedTags) { + foreach ($droppedTags as $closingTag) { + if (!in_array($closingTag[1], $openTags)) { + array_unshift($openTags, $closingTag[1]); + } + } + } + } + } + } else { + $truncate .= mb_substr($tag[3], 0, $left + $entitiesLength); + } break; } - $truncate .= $tag[3]; + $totalLength += $contentLength; if ($totalLength >= $length) { break; } } - } else { - if (mb_strlen($text) <= $length) { - return $text; + + $truncate .= $options['ellipsis']; + + foreach ($openTags as $tag) { + $truncate .= ''; } - $truncate = mb_substr($text, 0, $length - mb_strlen($options['ellipsis'])); + return $truncate; + } + + if (mb_strlen($text) <= $length) { + return $text; } + $truncate = mb_substr($text, 0, $length - mb_strlen($options['ellipsis'])); + if (!$options['exact']) { $spacepos = mb_strrpos($truncate, ' '); - if ($options['html']) { - $truncateCheck = mb_substr($truncate, 0, $spacepos); - $lastOpenTag = mb_strrpos($truncateCheck, '<'); - $lastCloseTag = mb_strrpos($truncateCheck, '>'); - if ($lastOpenTag > $lastCloseTag) { - preg_match_all('/<[\w]+[^>]*>/s', $truncate, $lastTagMatches); - $lastTag = array_pop($lastTagMatches[0]); - $spacepos = mb_strrpos($truncate, $lastTag) + mb_strlen($lastTag); - } - $bits = mb_substr($truncate, $spacepos); - preg_match_all('/<\/([a-z]+)>/', $bits, $droppedTags, PREG_SET_ORDER); - if (!empty($droppedTags)) { - if (!empty($openTags)) { - foreach ($droppedTags as $closingTag) { - if (!in_array($closingTag[1], $openTags)) { - array_unshift($openTags, $closingTag[1]); - } - } - } else { - foreach ($droppedTags as $closingTag) { - $openTags[] = $closingTag[1]; - } - } - } - } $truncate = mb_substr($truncate, 0, $spacepos); // If truncate still empty, then we don't need to count ellipsis in the cut. @@ -660,13 +681,6 @@ public static function truncate($text, $length = 100, array $options = []) } $truncate .= $options['ellipsis']; - - if ($options['html']) { - foreach ($openTags as $tag) { - $truncate .= ''; - } - } - return $truncate; } diff --git a/tests/TestCase/Utility/TextTest.php b/tests/TestCase/Utility/TextTest.php index f994012e5c9..d70e64e48ec 100644 --- a/tests/TestCase/Utility/TextTest.php +++ b/tests/TestCase/Utility/TextTest.php @@ -556,7 +556,7 @@ public function testTruncate() $this->assertSame($this->Text->truncate($text1, 15, ['html' => true]), "The quick brow\xe2\x80\xa6"); $this->assertSame($this->Text->truncate($text1, 15, ['exact' => false, 'html' => true]), "The quick\xe2\x80\xa6"); $this->assertSame($this->Text->truncate($text2, 10, ['html' => true]), "Heizölrüc\xe2\x80\xa6"); - $this->assertSame($this->Text->truncate($text2, 10, ['exact' => false, 'html' => true]), "Heizö\xe2\x80\xa6"); + $this->assertSame($this->Text->truncate($text2, 10, ['exact' => false, 'html' => true]), "Heizölrüc\xe2\x80\xa6"); $this->assertSame($this->Text->truncate($text3, 20, ['html' => true]), "© 2005-2007, Cake S\xe2\x80\xa6"); $this->assertSame($this->Text->truncate($text4, 15, ['html' => true]), " This image ta\xe2\x80\xa6"); $this->assertSame($this->Text->truncate($text4, 45, ['html' => true]), " This image tag is not XHTML conform!

But the\xe2\x80\xa6"); @@ -576,43 +576,31 @@ public function testTruncate() 'exact' => false, 'html' => true ]); - $expected = '

...

'; - $this->assertEquals($expected, $result); - - $text = '

El biógrafo de Steve Jobs, Walter -Isaacson, explica porqué Jobs le pidió que le hiciera su biografía en -este artículo de El País.

-

Por qué Steve era distinto.

-

http://www.elpais.com/articulo/primer/plano/ -Steve/era/distinto/elpepueconeg/20111009elpneglse_4/Tes

-

Ya se ha publicado la biografía de -Steve Jobs escrita por Walter Isaacson "Steve Jobs by Walter -Isaacson", aquí os dejamos la dirección de amazon donde -podeís adquirirla.

-

http://www.amazon.com/Steve- -Jobs-Walter-Isaacson/dp/1451648537

'; - $result = $this->Text->truncate($text, 500, [ - 'ellipsis' => '... ', + $expected = '

Iamates...

'; + $this->assertEquals($expected, $result); + } + + /** + * Test truncate() method with both exact and html. + * @return void + */ + public function testTruncateExactHtml() + { + $text = 'hello world'; + $expected = 'hell..'; + $result = Text::truncate($text, 6, array( + 'ellipsis' => '..', + 'exact' => true, + 'html' => true + )); + $this->assertEquals($expected, $result); + + $expected = 'hell..'; + $result = Text::truncate($text, 6, array( + 'ellipsis' => '..', 'exact' => false, 'html' => true - ]); - $expected = '

El biógrafo de Steve Jobs, Walter -Isaacson, explica porqué Jobs le pidió que le hiciera su biografía en -este artículo de El País.

-

Por qué Steve era distinto.

-

http://www.elpais.com/articulo/primer/plano/ -Steve/era/distinto/elpepueconeg/20111009elpneglse_4/Tes

-

Ya se ha publicado la biografía de -Steve Jobs escrita por Walter Isaacson "Steve Jobs by Walter -Isaacson", aquí os dejamos la dirección de amazon donde -podeís adquirirla.

-

...

'; + )); $this->assertEquals($expected, $result); }