Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion config/bolt/contenttypes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ homepage:
pages:
name: Pages
singular_name: Page
title_format: [ heading, subheading ]
title_format: '{heading} - {subheading}'
excerpt_format: '📦 {teaser}'
fields:
heading:
type: text
Expand Down
2 changes: 1 addition & 1 deletion src/Configuration/Parser/ContentTypesParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ protected function parseContentType($key, array $contentType): ?ContentType

// Make sure title_format is set
if (isset($contentType['title_format'])) {
$contentType['title_format'] = (array) $contentType['title_format'];
$contentType['title_format'] = $contentType['title_format'];
} elseif (isset($contentType['fields']['slug']['uses'])) {
$contentType['title_format'] = (array) $contentType['fields']['slug']['uses'];
} else {
Expand Down
80 changes: 32 additions & 48 deletions src/Twig/ContentExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ public function getFilters(): array

return [
new TwigFilter('title', [$this, 'getTitle'], $safe),
new TwigFilter('title_fields', [$this, 'guessTitleFields']),
new TwigFilter('title_fields', [$this, 'getTitleFields']),
new TwigFilter('image', [$this, 'getImage']),
new TwigFilter('excerpt', [$this, 'getExcerpt'], $safe),
new TwigFilter('previous', [$this, 'getPreviousContent']),
Expand Down Expand Up @@ -165,10 +165,21 @@ public function getAnyTitle(Content $content, int $length = 120): string
}

public function getTitle(Content $content, string $locale = '', int $length = 120): string
{
if (ComposeValueHelper::isSuitable($content)) {
$title = ComposeValueHelper::get($content, $content->getDefinition()->get('title_format'));
} else {
$title = $this->getFieldBasedTitle($content);
}

return Html::trimText($title, $length);
}

private function getFieldBasedTitle(Content $content): string
{
$titleParts = [];

foreach ($this->guessTitleFields($content) as $fieldName) {
foreach (ComposeValueHelper::guessTitleFields($content) as $fieldName) {
$field = $content->getField($fieldName);

if (! empty($locale)) {
Expand All @@ -184,53 +195,16 @@ public function getTitle(Content $content, string $locale = '', int $length = 12
$titleParts[] = $value;
}

return Html::trimText(implode(' ', $titleParts), $length);
return implode(' ', $titleParts);
}

public function guessTitleFields(Content $content): array
public function getTitleFields(Content $content): array
{
$definition = $content->getDefinition();

// First, see if we have a "title format" in the Content Type.
if ($definition !== null && $definition->has('title_format')) {
$names = $definition->get('title_format');

$namesCollection = Collection::wrap($names)->filter(function (string $name) use ($content): bool {
if ($content->hasFieldDefined($name) === false) {
throw new \RuntimeException(sprintf(
"Content '%s' has field '%s' added to title_format config option, but the field is not present in Content's definition.",
$content->getContentTypeName(),
$name
));
}

return $content->hasField($name);
});

if ($namesCollection->isNotEmpty()) {
return $namesCollection->values()->toArray();
}
}

// Alternatively, see if we have a field named 'title' or somesuch.
$names = ['title', 'name', 'caption', 'subject']; // English
$names = array_merge($names, ['titel', 'naam', 'kop', 'onderwerp']); // Dutch
$names = array_merge($names, ['nom', 'sujet']); // French
$names = array_merge($names, ['nombre', 'sujeto']); // Spanish

foreach ($names as $name) {
if ($content->hasField($name)) {
return (array) $name;
}
if (ComposeValueHelper::isSuitable($content)) {
return ComposeValueHelper::getFieldNames($content->getDefinition()->get('title_format'));
}

foreach ($content->getFields() as $field) {
if ($field instanceof Excerptable) {
return (array) $field->getName();
}
}

return [];
return ComposeValueHelper::guessTitleFields($content);
}

/**
Expand Down Expand Up @@ -268,18 +242,28 @@ public function getExcerpt($content, int $length = 280, bool $includeTitle = fal
return Excerpt::getExcerpt((string) $content, $length);
}

if (ComposeValueHelper::isSuitable($content, 'excerpt_format')) {
$excerpt = ComposeValueHelper::get($content, $content->getDefinition()->get('excerpt_format'));
} else {
$excerpt = $this->getFieldBasedExcerpt($content, $length, $includeTitle);
}

return Excerpt::getExcerpt(rtrim($excerpt, '. '), $length, $focus);
}

private function getFieldBasedExcerpt(Content $content, int $length, bool $includeTitle = false): string
{
$excerptParts = [];

if ($includeTitle) {
$title = $this->getTitle($content);
if ($title !== '') {
$title = Html::trimText($title, $length);
$length -= mb_strlen($title);
$excerptParts[] = $title;
}
}

$skipFields = $this->guessTitleFields($content);
$skipFields = ComposeValueHelper::guessTitleFields($content);

foreach ($content->getFields() as $field) {
if ($field instanceof Excerptable && in_array($field->getName(), $skipFields, true) === false) {
Expand All @@ -293,14 +277,14 @@ public function getExcerpt($content, int $length = 280, bool $includeTitle = fal
$specialChars = ['.', ',', '!', '?'];
$excerpt = array_reduce($excerptParts, function (string $excerpt, string $part) use ($specialChars): string {
if (in_array(mb_substr($part, -1), $specialChars, true) === false) {
// add comma add end of string if it doesn't have sentence end
// add period at end of string if it doesn't have sentence end
$part .= '.';
}

return $excerpt . $part . ' ';
}, '');

return Excerpt::getExcerpt(rtrim($excerpt, '. '), $length, $focus);
return rtrim($excerpt, '. ');
}

public function getPreviousContent(Content $content, string $byColumn = 'id', bool $sameContentType = true): ?Content
Expand Down
73 changes: 73 additions & 0 deletions src/Utils/ComposeValueHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,25 @@
namespace Bolt\Utils;

use Bolt\Entity\Content;
use Bolt\Entity\Field\Excerptable;
use Tightenco\Collect\Support\Collection;

class ComposeValueHelper
{
public static function isSuitable(Content $record, string $which = 'title_format'): bool
{
$definition = $record->getDefinition();

if ($definition !== null && $definition->has($which)) {
$format = $definition->get($which);
if (is_string($format) && mb_strpos($format, '{') !== false) {
return true;
}
}

return false;
}

public static function get(Content $record, string $format = '', string $locale = ''): string
{
if (empty($format)) {
Expand Down Expand Up @@ -48,4 +64,61 @@ function ($match) use ($record, $locale) {
$format
);
}

public static function getFieldNames(string $format): array
{
preg_match_all('/{([\w]+)}/i', $format, $matches);

return $matches[1];
}

public static function guessTitleFields(Content $content): array
{
$definition = $content->getDefinition();

// First, see if we have a "title format" in the Content Type.
if ($definition !== null && $definition->has('title_format')) {
if (self::isSuitable($content)) {
$names = self::getFieldNames($definition->get('title_format'));
} else {
$names = $definition->get('title_format');
}

$namesCollection = Collection::wrap($names)->filter(function (string $name) use ($content): bool {
if ($content->hasFieldDefined($name) === false) {
throw new \RuntimeException(sprintf(
"Content '%s' has field '%s' added to title_format config option, but the field is not present in Content's definition.",
$content->getContentTypeName(),
$name
));
}

return $content->hasField($name);
});

if ($namesCollection->isNotEmpty()) {
return $namesCollection->values()->toArray();
}
}

// Alternatively, see if we have a field named 'title' or somesuch.
$names = ['title', 'name', 'caption', 'subject']; // English
$names = array_merge($names, ['titel', 'naam', 'kop', 'onderwerp']); // Dutch
$names = array_merge($names, ['nom', 'sujet']); // French
$names = array_merge($names, ['nombre', 'sujeto']); // Spanish

foreach ($names as $name) {
if ($content->hasField($name)) {
return (array) $name;
}
}

foreach ($content->getFields() as $field) {
if ($field instanceof Excerptable) {
return (array) $field->getName();
}
}

return [];
}
}