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 .= '' . $tag . '>';
}
- $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 .= '' . $tag . '>';
- }
- }
-
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!
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.
- -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 = ''; + $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.
- -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); }