diff --git a/CHANGELOG.md b/CHANGELOG.md
index d20220d4a22..a47778da6ac 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,7 +8,7 @@
* Support using `.env` and `.env.local.php` files (see #768).
* Do not install the tests with "prefer-dist" (see #762).
* Simplify registering custom fragment types (see #776).
- * Add a SERP preview wherever page meta data can be edited.
+ * Add a Google search results preview wherever page meta data can be edited.
* Dynamically add the robots.txt and favicon.ico files per root page (see #717).
* Moved folderUrl setting to the root page (see #706).
* Do not generate request tokens via ESI if not needed (see #710).
diff --git a/calendar-bundle/src/Resources/contao/dca/tl_calendar_events.php b/calendar-bundle/src/Resources/contao/dca/tl_calendar_events.php
index 93352356947..dfcb45c9b86 100644
--- a/calendar-bundle/src/Resources/contao/dca/tl_calendar_events.php
+++ b/calendar-bundle/src/Resources/contao/dca/tl_calendar_events.php
@@ -259,7 +259,7 @@
'label' => &$GLOBALS['TL_LANG']['MSC']['serpPreview'],
'exclude' => true,
'inputType' => 'serpPreview',
- 'eval' => array('serpPreview'=>array('title'=>array('pageTitle', 'title'), 'description'=>array('description', 'teaser'))),
+ 'eval' => array('url_callback'=>array('tl_calendar_events', 'getSerpUrl'), 'titleFields'=>array('pageTitle', 'title'), 'descriptionFields'=>array('description', 'teaser')),
'sql' => null
),
'location' => array
@@ -737,6 +737,18 @@ public function setEmptyEndTime($varValue, Contao\DataContainer $dc)
return $varValue;
}
+ /**
+ * Return the SERP URL
+ *
+ * @param Contao\CalendarEventsModel $model
+ *
+ * @return string
+ */
+ public function getSerpUrl(Contao\CalendarEventsModel $model)
+ {
+ return Contao\Events::generateEventUrl($model, true);
+ }
+
/**
* Add the type of input field
*
diff --git a/core-bundle/src/Resources/contao/dca/tl_page.php b/core-bundle/src/Resources/contao/dca/tl_page.php
index 17c75645695..d50fa17e3b3 100644
--- a/core-bundle/src/Resources/contao/dca/tl_page.php
+++ b/core-bundle/src/Resources/contao/dca/tl_page.php
@@ -269,7 +269,7 @@
'label' => &$GLOBALS['TL_LANG']['MSC']['serpPreview'],
'exclude' => true,
'inputType' => 'serpPreview',
- 'eval' => array('serpPreview'=>array('title'=>array('pageTitle', 'title'))),
+ 'eval' => array('url_callback'=>array('tl_page', 'getSerpUrl'), 'titleFields'=>array('pageTitle', 'title')),
'sql' => null
),
'redirect' => array
@@ -989,6 +989,18 @@ public function checkRootType($varValue, Contao\DataContainer $dc)
return $varValue;
}
+ /**
+ * Return the SERP URL
+ *
+ * @param Contao\PageModel $model
+ *
+ * @return string
+ */
+ public function getSerpUrl(Contao\PageModel $model)
+ {
+ return $model->getAbsoluteUrl();
+ }
+
/**
* Show a warning if there is no language fallback page
*/
diff --git a/core-bundle/src/Resources/contao/languages/en/default.xlf b/core-bundle/src/Resources/contao/languages/en/default.xlf
index 930339105ed..1939e24f992 100644
--- a/core-bundle/src/Resources/contao/languages/en/default.xlf
+++ b/core-bundle/src/Resources/contao/languages/en/default.xlf
@@ -1848,10 +1848,10 @@
-
+
-
+
diff --git a/core-bundle/src/Resources/contao/widgets/SerpPreview.php b/core-bundle/src/Resources/contao/widgets/SerpPreview.php
index 35ba838f900..d587311c683 100644
--- a/core-bundle/src/Resources/contao/widgets/SerpPreview.php
+++ b/core-bundle/src/Resources/contao/widgets/SerpPreview.php
@@ -11,7 +11,10 @@
namespace Contao;
/**
- * @property array $serpPreview
+ * @property array $titleFields
+ * @property array $descriptionFields
+ * @property string $aliasField
+ * @property callable $url_callback
*/
class SerpPreview extends Widget
{
@@ -26,7 +29,7 @@ class SerpPreview extends Widget
public function generate()
{
/** @var Model $class */
- $class = $this->serpPreview['class'] ?? Model::getClassFromTable($this->strTable);
+ $class = Model::getClassFromTable($this->strTable);
$model = $class::findByPk($this->activeRecord->id);
if (!$model instanceof Model)
@@ -37,15 +40,30 @@ public function generate()
$id = $model->id;
$title = StringUtil::substr($this->getTitle($model), 64);
$description = StringUtil::substr($this->getDescription($model), 160);
+ $alias = $this->getAlias($model);
+
+ // Get the URL with a %s placeholder for the alias or ID
$url = $this->getUrl($model);
- list($baseUrl) = explode($model->alias ?: $model->id, $url);
- $urlSuffix = System::getContainer()->getParameter('contao.url_suffix');
+ list($baseUrl, $urlSuffix) = explode('%s', $url);
+
+ // Use the base URL for the index page
+ if ($model instanceof PageModel && $alias == 'index')
+ {
+ $url = $baseUrl;
+ }
+ else
+ {
+ $url = sprintf($url, $alias ?: $model->id);
+ }
+
+ // Get the input field suffix (edit multiple mode)
$suffix = substr($this->objDca->inputName, \strlen($this->objDca->field));
- $titleField = $this->getTitleField() . $suffix;
- $titleFallbackField = $this->getTitleFallbackField() . $suffix;
- $aliasField = $this->getAliasField() . $suffix;
- $descriptionField = $this->getDescriptionField() . $suffix;
- $descriptionFallbackField = $this->getDescriptionFallbackField() . $suffix;
+
+ $titleField = $this->getTitleField($suffix);
+ $titleFallbackField = $this->getTitleFallbackField($suffix);
+ $aliasField = $this->getAliasField($suffix);
+ $descriptionField = $this->getDescriptionField($suffix);
+ $descriptionFallbackField = $this->getDescriptionFallbackField($suffix);
return <<
@@ -72,115 +90,116 @@ public function generate()
private function getTitle(Model $model)
{
- if (!isset($this->serpPreview['title']))
+ if (!isset($this->titleFields))
{
return $model->title;
}
- if (\is_array($this->serpPreview['title']))
- {
- return $model->{$this->serpPreview['title'][0]} ?: $model->{$this->serpPreview['title'][1]};
- }
-
- return $model->{$this->serpPreview['title']};
+ return $model->{$this->titleFields[0]} ?: $model->{$this->titleFields[1]};
}
private function getDescription(Model $model)
{
- if (!isset($this->serpPreview['description']))
+ if (!isset($this->descriptionFields))
{
return $model->description;
}
- if (\is_array($this->serpPreview['description']))
+ return $model->{$this->descriptionFields[0]} ?: $model->{$this->descriptionFields[1]};
+ }
+
+ private function getAlias(Model $model)
+ {
+ if (!isset($this->aliasField))
{
- return $model->{$this->serpPreview['description'][0]} ?: $model->{$this->serpPreview['description'][1]};
+ return $model->alias;
}
- return $model->{$this->serpPreview['description']};
+ return $model->{$this->aliasField};
}
+ /**
+ * @todo Use the router to generate the URL in a future version (see #831)
+ */
private function getUrl(Model $model)
{
- if (isset($this->serpPreview['url']))
+ if (!isset($this->url_callback))
{
- return $this->serpPreview['url'];
+ throw new \LogicException('No url_callback given');
}
- // FIXME: use the router to generate the URL (see #831)
- switch (true)
- {
- case $model instanceof PageModel:
- return $model->getAbsoluteUrl();
-
- case $model instanceof NewsModel:
- return News::generateNewsUrl($model, false, true);
+ $alias = $this->getAlias($model);
+ $placeholder = bin2hex(random_bytes(10));
- case $model instanceof CalendarEventsModel:
- return Events::generateEventUrl($model, true);
+ // Pass a detached clone with the alias set to the placeholder
+ $tempModel = clone $model;
+ $tempModel->origAlias = $tempModel->$alias;
+ $tempModel->$alias = $placeholder;
+ $tempModel->preventSaving(false);
- default:
- throw new \RuntimeException(sprintf('Unsupported model class "%s"', \get_class($model)));
+ if (\is_array($this->url_callback))
+ {
+ $url = System::importStatic($this->url_callback[0])->{$this->url_callback[1]}($tempModel);
}
- }
-
- private function getTitleField()
- {
- if (!isset($this->serpPreview['title']))
+ elseif (\is_callable($this->url_callback))
{
- return 'ctrl_title';
+ $url = \call_user_func($this->url_callback, $tempModel);
}
-
- if (\is_array($this->serpPreview['title']))
+ else
{
- return 'ctrl_' . $this->serpPreview['title'][0];
+ throw new \LogicException('Please provide the url_callback as callable');
}
- return 'ctrl_' . $this->serpPreview['title'];
+ return str_replace($placeholder, '%s', $url);
}
- private function getTitleFallbackField()
+ private function getTitleField($suffix)
{
- if (!isset($this->serpPreview['title']) || !\is_array($this->serpPreview['title']))
+ if (!isset($this->titleFields[0]))
{
- return '';
+ return 'ctrl_title' . $suffix;
}
- return 'ctrl_' . $this->serpPreview['title'][1];
+ return 'ctrl_' . $this->titleFields[0] . $suffix;
}
- private function getAliasField()
+ private function getTitleFallbackField($suffix)
{
- if (!isset($this->serpPreview['alias']))
+ if (!isset($this->titleFields[1]))
{
- return 'ctrl_alias';
+ return '';
}
- return 'ctrl_' . $this->serpPreview['alias'];
+ return 'ctrl_' . $this->titleFields[1] . $suffix;
}
- private function getDescriptionField()
+ private function getDescriptionField($suffix)
{
- if (!isset($this->serpPreview['description']))
+ if (!isset($this->descriptionFields[0]))
{
- return 'ctrl_description';
+ return 'ctrl_description' . $suffix;
}
- if (\is_array($this->serpPreview['description']))
+ return 'ctrl_' . $this->descriptionFields[0] . $suffix;
+ }
+
+ private function getDescriptionFallbackField($suffix)
+ {
+ if (!isset($this->descriptionFields[1]))
{
- return 'ctrl_' . $this->serpPreview['description'][0];
+ return '';
}
- return 'ctrl_' . $this->serpPreview['description'];
+ return 'ctrl_' . $this->descriptionFields[1] . $suffix;
}
- private function getDescriptionFallbackField()
+ private function getAliasField($suffix)
{
- if (!isset($this->serpPreview['description']) || !\is_array($this->serpPreview['description']))
+ if (!isset($this->aliasField))
{
- return '';
+ return 'ctrl_alias' . $suffix;
}
- return 'ctrl_' . $this->serpPreview['description'][1];
+ return 'ctrl_' . $this->aliasField . $suffix;
}
}
diff --git a/news-bundle/src/Resources/contao/dca/tl_news.php b/news-bundle/src/Resources/contao/dca/tl_news.php
index fa2fe28ee36..efead5496db 100644
--- a/news-bundle/src/Resources/contao/dca/tl_news.php
+++ b/news-bundle/src/Resources/contao/dca/tl_news.php
@@ -239,7 +239,7 @@
'label' => &$GLOBALS['TL_LANG']['MSC']['serpPreview'],
'exclude' => true,
'inputType' => 'serpPreview',
- 'eval' => array('serpPreview'=>array('title'=>array('pageTitle', 'headline'), 'description'=>array('description', 'teaser'))),
+ 'eval' => array('url_callback'=>array('tl_news', 'getSerpUrl'), 'titleFields'=>array('pageTitle', 'headline'), 'descriptionFields'=>array('description', 'teaser')),
'sql' => null
),
'subheadline' => array
@@ -675,6 +675,18 @@ public function loadTime($value)
return strtotime('1970-01-01 ' . date('H:i:s', $value));
}
+ /**
+ * Return the SERP URL
+ *
+ * @param Contao\NewsModel $model
+ *
+ * @return string
+ */
+ public function getSerpUrl(Contao\NewsModel $model)
+ {
+ return Contao\News::generateNewsUrl($model, false, true);
+ }
+
/**
* List a news article
*