[FEATURE] Add properties changefreq and priority to XML sitemap entries 34/59834/19
authorGuido Schmechel <guido.schmechel@brandung.de>
Sat, 2 Mar 2019 22:45:38 +0000 (23:45 +0100)
committerRichard Haeser <richard@maxserv.com>
Mon, 15 Apr 2019 19:48:40 +0000 (21:48 +0200)
The possibility to define the properties changefreq and priority for
XML sitemap entries has been implemented.

Resolves: #87433
Releases: master
Change-Id: I90b8c59ff916110be19aa8e8c888e77f25649bc3
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/59834
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Jörg Bösche <typo3@joergboesche.de>
Tested-by: Richard Haeser <richard@maxserv.com>
Reviewed-by: Jörg Bösche <typo3@joergboesche.de>
Reviewed-by: Richard Haeser <richard@maxserv.com>
14 files changed:
typo3/sysext/core/Documentation/Changelog/master/Feature-87433-AddChangefreqAndPriority.rst [new file with mode: 0644]
typo3/sysext/seo/Classes/XmlSitemap/PagesXmlSitemapDataProvider.php
typo3/sysext/seo/Classes/XmlSitemap/RecordsXmlSitemapDataProvider.php
typo3/sysext/seo/Configuration/TCA/Overrides/pages.php
typo3/sysext/seo/Resources/Private/Language/locallang_tca.xlf
typo3/sysext/seo/Resources/Private/Templates/XmlSitemap/Sitemap.xml
typo3/sysext/seo/Resources/Public/CSS/Sitemap.xsl
typo3/sysext/seo/Tests/Functional/Fixtures/content.typoscript [new file with mode: 0644]
typo3/sysext/seo/Tests/Functional/Fixtures/pages-sitemap.xml
typo3/sysext/seo/Tests/Functional/Fixtures/tt_content.xml [new file with mode: 0644]
typo3/sysext/seo/Tests/Functional/XmlSitemap/XmlSitemapIndexTest.php
typo3/sysext/seo/Tests/Functional/XmlSitemap/XmlSitemapPagesTest.php [new file with mode: 0644]
typo3/sysext/seo/Tests/Functional/XmlSitemap/XmlSitemapRecordsTest.php
typo3/sysext/seo/ext_tables.sql

diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-87433-AddChangefreqAndPriority.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-87433-AddChangefreqAndPriority.rst
new file mode 100644 (file)
index 0000000..f49d9c3
--- /dev/null
@@ -0,0 +1,79 @@
+.. include:: ../../Includes.txt
+
+=============================================
+Feature: #87433 - Add changefreq and priority
+=============================================
+
+See :issue:`87433`
+
+Description
+===========
+
+Sitemap.xml files may contain a change frequency and a priority for entries.
+
+Change frequencies define how often each page is approximately updated and hence how often it should be
+revisited (for example: News in an archive are "never" updated, while your home page might get "weekly" updates).
+
+Priority allows you to define how important the page is compared to other pages on your site. The priority is stated
+in a value from 0 to 1. Your most important pages can get an higher priority as other pages. This value does not
+affect how your pages are compared to pages of other websites.
+
+This feature allows to define the properties `changefreq` and `priority` for sitemap entries in TYPO3.
+
+The properties `changefreq` and `priority` of pages can be controlled via page properties.
+For records, the settings can be defined in TypoScript by mapping the properties to fields of the record by
+using the options `changeFreqField` and `priorityField`. `changeFreqField` needs to point to a field containing
+string values (see `pages` definition of field `sitemap_changefreq`), `priorityField` needs to point to a field with
+a decimal value between 0 and 1.
+
+
+.. code-block:: typoscript
+
+   plugin.tx_seo {
+      config {
+         xmlSitemap {
+            sitemaps {
+               <unique key> {
+                  provider = TYPO3\CMS\Seo\XmlSitemap\RecordsXmlSitemapDataProvider
+                  config {
+                     table = news_table
+                     sortField = sorting
+                     lastModifiedField = tstamp
+                     changeFreqField = news_changefreq
+                     priorityField = news_priority
+                     additionalWhere = AND (no_index = 0 OR no_follow = 0)
+                     pid = <page id('s) containing news records>
+                     url {
+                        pageId = <your detail page id>
+                        fieldToParameterMap {
+                           uid = tx_extension_pi1[news]
+                        }
+                        additionalGetParameters {
+                           tx_extension_pi1.controller = News
+                           tx_extension_pi1.action = detail
+                        }
+                        useCacheHash = 1
+                     }
+                  }
+               }
+            }
+         }
+      }
+   }
+
+
+Impact
+======
+
+Two new fields are available in the page properties: `sitemap_priority` (decimal) and `sitemap_changefreq` (list of values, for example "weekly", "daily", "never").
+
+Two new TypoScript options for the `RecordsXmlSitemapDataProvider` have been introduced:
+`changeFreqField` and `priorityField`.
+
+All pages and records get a priority of 0.5 by default.
+
+.. attention::
+   Both priority and change frequency does have no impact on your rankings. These options only gives hints to search engines
+   in which order and how often you would like a crawler to crawl your pages.
+
+.. index:: ext:seo
index c4f48c4..a8290fe 100644 (file)
@@ -66,7 +66,9 @@ class PagesXmlSitemapDataProvider extends AbstractXmlSitemapDataProvider
 
                 $this->items[] = [
                     'uid' => $page['uid'],
-                    'lastMod' => (int)$lastMod
+                    'lastMod' => (int)$lastMod,
+                    'changefreq' => $page['sitemap_changefreq'],
+                    'priority' => (float)$page['sitemap_priority'],
                 ];
             }
         }
index 0b79dcd..d192dba 100644 (file)
@@ -58,6 +58,9 @@ class RecordsXmlSitemapDataProvider extends AbstractXmlSitemapDataProvider
         $lastModifiedField = $this->config['lastModifiedField'] ?? 'tstamp';
         $sortField = $this->config['sortField'] ?? 'sorting';
 
+        $changeFreqField = $this->config['changeFreqField'] ?? '';
+        $priorityField = $this->config['priorityField'] ?? '';
+
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
             ->getQueryBuilderForTable($this->config['table']);
 
@@ -97,10 +100,15 @@ class RecordsXmlSitemapDataProvider extends AbstractXmlSitemapDataProvider
             ->fetchAll();
 
         foreach ($rows as $row) {
-            $this->items[] = [
+            $item = [
                 'data' => $row,
                 'lastMod' => (int)$row[$lastModifiedField]
             ];
+            if (!empty($changeFreqField)) {
+                $item['changefreq'] = $row[$changeFreqField];
+            }
+            $item['priority'] = !empty($priorityField) ? $row[$priorityField] : 0.5;
+            $this->items[] = $item;
         }
     }
 
index 278f430..1674d60 100644 (file)
@@ -46,6 +46,10 @@ $tca = [
             'label' => 'LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.palettes.canonical',
             'showitem' => 'canonical_link',
         ],
+        'sitemap' => [
+            'label' => 'LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.palettes.sitemap',
+            'showitem' => 'sitemap_changefreq, sitemap_priority',
+        ],
         'opengraph' => [
             'label' => 'LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.palettes.opengraph',
             'showitem' => 'og_title, --linebreak--, og_description, --linebreak--, og_image',
@@ -100,6 +104,45 @@ $tca = [
                 ]
             ]
         ],
+        'sitemap_changefreq' => [
+            'exclude' => true,
+            'label' => 'LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.sitemap_changefreq',
+            'config' => [
+                'type' => 'select',
+                'renderType' => 'selectSingle',
+                'items' => [
+                    ['LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.sitemap_changefreq.none', ''],
+                    ['LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.sitemap_changefreq.always', 'always'],
+                    ['LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.sitemap_changefreq.hourly', 'hourly'],
+                    ['LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.sitemap_changefreq.daily', 'daily'],
+                    ['LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.sitemap_changefreq.weekly', 'weekly'],
+                    ['LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.sitemap_changefreq.monthly', 'monthly'],
+                    ['LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.sitemap_changefreq.yearly', 'yearly'],
+                    ['LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.sitemap_changefreq.never', 'never'],
+                ],
+            ]
+        ],
+        'sitemap_priority' => [
+            'exclude' => true,
+            'label' => 'LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.sitemap_priority',
+            'config' => [
+                'type' => 'select',
+                'renderType' => 'selectSingle',
+                'items' => [
+                    ['0.0', '0.0'],
+                    ['0.1', '0.1'],
+                    ['0.2', '0.2'],
+                    ['0.3', '0.3'],
+                    ['0.4', '0.4'],
+                    ['0.5', '0.5'],
+                    ['0.6', '0.6'],
+                    ['0.7', '0.7'],
+                    ['0.8', '0.8'],
+                    ['0.9', '0.9'],
+                    ['1.0', '1.0'],
+                ],
+            ]
+        ],
         'canonical_link' => [
             'exclude' => true,
             'label' => 'LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.canonical_link',
@@ -237,6 +280,7 @@ $GLOBALS['TCA']['pages'] = array_replace_recursive($GLOBALS['TCA']['pages'], $tc
         --palette--;;seo,
         --palette--;;robots,
         --palette--;;canonical,
+        --palette--;;sitemap,
     --div--;LLL:EXT:seo/Resources/Private/Language/locallang_tca.xlf:pages.tabs.socialmedia,
         --palette--;;opengraph,
         --palette--;;twittercards',
index c8f3b95..b3c04d5 100644 (file)
                        <trans-unit id="pages.tabs.socialmedia">
                                <source>Social media</source>
                        </trans-unit>
+                       <trans-unit id="pages.palettes.sitemap">
+                               <source>Sitemap</source>
+                       </trans-unit>
+                       <trans-unit id="pages.sitemap_changefreq">
+                               <source>Change frequency</source>
+                       </trans-unit>
+                       <trans-unit id="pages.sitemap_changefreq.none">
+                               <source>None</source>
+                       </trans-unit>
+                       <trans-unit id="pages.sitemap_changefreq.always">
+                               <source>Always</source>
+                       </trans-unit>
+                       <trans-unit id="pages.sitemap_changefreq.hourly">
+                               <source>Hourly</source>
+                       </trans-unit>
+                       <trans-unit id="pages.sitemap_changefreq.daily">
+                               <source>Daily</source>
+                       </trans-unit>
+                       <trans-unit id="pages.sitemap_changefreq.weekly">
+                               <source>Weekly</source>
+                       </trans-unit>
+                       <trans-unit id="pages.sitemap_changefreq.monthly">
+                               <source>Monthly</source>
+                       </trans-unit>
+                       <trans-unit id="pages.sitemap_changefreq.yearly">
+                               <source>Yearly</source>
+                       </trans-unit>
+                       <trans-unit id="pages.sitemap_changefreq.never">
+                               <source>Never</source>
+                       </trans-unit>
+                       <trans-unit id="pages.sitemap_priority">
+                               <source>Priority</source>
+                       </trans-unit>
                </body>
        </file>
 </xliff>
index 7e80a83..f359bbb 100644 (file)
@@ -6,6 +6,10 @@
             <url>
                 <loc>{item.loc}</loc>
                 <lastmod>{item.lastMod -> f:format.date(format: 'c')}</lastmod>
+                <f:if condition="{item.changefreq}">
+                    <changefreq>{item.changefreq}</changefreq>
+                </f:if>
+                <priority>{item.priority}</priority>
             </url>
         </f:if>
     </f:for>
index 59a61df..ec9b5a5 100644 (file)
                         <table id="sitemap" cellpadding="3" width="100%">
                             <thead>
                                 <tr>
-                                    <th width="80%">URL</th>
+                                    <th width="40%">URL</th>
                                     <th title="Last Modification Time" width="20%">Last Mod.</th>
+                                    <th title="Change frequency" width="20%">Change freq.</th>
+                                    <th title="Priority" width="20%">Priority</th>
                                 </tr>
                             </thead>
                             <tbody>
                                         <td>
                                             <xsl:value-of select="concat(substring(sitemap:lastmod,0,11),concat(' ', substring(sitemap:lastmod,12,5)),concat(' ', substring(sitemap:lastmod,20,6)))"/>
                                         </td>
+                                        <td>
+                                            <xsl:value-of select="sitemap:changefreq"/>
+                                        </td>
+                                        <td>
+                                            <xsl:value-of select="sitemap:priority"/>
+                                        </td>
                                     </tr>
                                 </xsl:for-each>
                             </tbody>
diff --git a/typo3/sysext/seo/Tests/Functional/Fixtures/content.typoscript b/typo3/sysext/seo/Tests/Functional/Fixtures/content.typoscript
new file mode 100644 (file)
index 0000000..8628716
--- /dev/null
@@ -0,0 +1,26 @@
+plugin.tx_seo {
+  config {
+    xmlSitemap {
+      sitemaps {
+        content {
+          provider = TYPO3\CMS\Seo\XmlSitemap\RecordsXmlSitemapDataProvider
+          config {
+            table = tt_content
+            sortField = sorting
+            lastModifiedField = tstamp
+            changeFreqField = rowDescription
+            priorityField = bodytext
+            pid = 22
+            url {
+              pageId = 1
+              fieldToParameterMap {
+                uid = tx_example_content[id]
+              }
+              useCacheHash = 1
+            }
+          }
+        }
+      }
+    }
+  }
+}
index 721c9b3..a9a6023 100644 (file)
         <doktype>254</doktype>
         <deleted>0</deleted>
     </pages>
+    <pages>
+        <uid>12</uid>
+        <pid>1</pid>
+        <title>Complete Entry</title>
+        <tstamp>1491811200</tstamp>
+        <slug>/complete-entry</slug>
+        <deleted>0</deleted>
+        <sitemap_changefreq>daily</sitemap_changefreq>
+        <sitemap_priority>0.7</sitemap_priority>
+    </pages>
+    <pages>
+        <uid>13</uid>
+        <pid>1</pid>
+        <title>Only changefreq</title>
+        <tstamp>1491811200</tstamp>
+        <slug>/only-changefreq</slug>
+        <deleted>0</deleted>
+        <sitemap_changefreq>weekly</sitemap_changefreq>
+    </pages>
+    <pages>
+        <uid>14</uid>
+        <pid>1</pid>
+        <title>Clean</title>
+        <tstamp>1491811200</tstamp>
+        <slug>/clean</slug>
+        <deleted>0</deleted>
+    </pages>
 </dataset>
diff --git a/typo3/sysext/seo/Tests/Functional/Fixtures/tt_content.xml b/typo3/sysext/seo/Tests/Functional/Fixtures/tt_content.xml
new file mode 100644 (file)
index 0000000..09ba733
--- /dev/null
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<data>
+       <tt_content>
+               <uid>1</uid>
+               <pid>22</pid>
+               <tstamp>1553439588</tstamp>
+               <crdate>1553439588</crdate>
+               <deleted>0</deleted>
+               <hidden>0</hidden>
+               <starttime>0</starttime>
+               <endtime>0</endtime>
+               <sorting>256</sorting>
+               <header>Content example</header>
+               <rowDescription>hourly</rowDescription>
+               <bodytext>0.7</bodytext>
+       </tt_content>
+</data>
index 99e33db..9087144 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 declare(strict_types = 1);
 
-namespace TYPO3\CMS\Frontend\Tests\Functional\XmlSitemap;
+namespace TYPO3\CMS\Seo\Tests\Functional\XmlSitemap;
 
 /*
  * This file is part of the TYPO3 CMS project.
diff --git a/typo3/sysext/seo/Tests/Functional/XmlSitemap/XmlSitemapPagesTest.php b/typo3/sysext/seo/Tests/Functional/XmlSitemap/XmlSitemapPagesTest.php
new file mode 100644 (file)
index 0000000..3b4b7ae
--- /dev/null
@@ -0,0 +1,99 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Seo\Tests\Functional\XmlSitemap;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\AbstractTestCase;
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalResponse;
+
+/**
+ * Contains functional tests for the XmlSitemap Index
+ */
+class XmlSitemapPagesTest extends AbstractTestCase
+{
+    /**
+     * @var string[]
+     */
+    protected $coreExtensionsToLoad = [
+        'core', 'frontend', 'seo'
+    ];
+
+    /**
+     * @var string
+     */
+    protected $body;
+
+    /**
+     * @var InternalResponse
+     */
+    protected $response;
+
+    protected function setUp()
+    {
+        parent::setUp();
+        $this->importDataSet('EXT:seo/Tests/Functional/Fixtures/pages-sitemap.xml');
+        $this->setUpFrontendRootPage(
+            1,
+            [
+                'constants' => ['EXT:seo/Configuration/TypoScript/XmlSitemap/constants.typoscript'],
+                'setup' => ['EXT:seo/Configuration/TypoScript/XmlSitemap/setup.typoscript']
+            ]
+        );
+
+        $this->writeSiteConfiguration(
+            'website-local',
+            $this->buildSiteConfiguration(1, 'http://localhost/'),
+            [
+                $this->buildDefaultLanguageConfiguration('EN', '/')
+            ]
+        );
+
+        $this->response = $this->executeFrontendRequest(
+            (new InternalRequest('http://localhost/'))->withQueryParameters([
+                'id' => 1,
+                'type' => 1533906435,
+                'sitemap' => 'pages'
+            ])
+        );
+    }
+
+    /**
+     * @param string $urlPattern
+     * @test
+     * @dataProvider pagesToCheckDataProvider
+     */
+    public function checkIfPagesSiteMapContainsExpectedEntries($urlPattern): void
+    {
+        $this->assertEquals(200, $this->response->getStatusCode());
+        $this->assertArrayHasKey('Content-Length', $this->response->getHeaders());
+        $this->assertGreaterThan(0, $this->response->getHeader('Content-Length')[0]);
+
+        $this->assertRegExp($urlPattern, (string)$this->response->getBody());
+    }
+
+    /**
+     * @return array
+     */
+    public function pagesToCheckDataProvider(): array //18-03-2019 21:24:07
+    {
+        return [
+            'complete-entry' => ['/<url>\s+<loc>http:\/\/localhost\/complete\-entry<\/loc>\s+<lastmod>2017-04-10T08:00:00\+00:00<\/lastmod>\s+<changefreq>daily<\/changefreq>\s+<priority>0\.7<\/priority>\s+<\/url>/'],
+            'only-changefreq' => ['/<url>\s+<loc>http:\/\/localhost\/only\-changefreq<\/loc>\s+<lastmod>2017-04-10T08:00:00\+00:00<\/lastmod>\s+<changefreq>weekly<\/changefreq>\s+<priority>0\.5<\/priority>\s+<\/url>/'],
+            'clean' => ['/<url>\s+<loc>http:\/\/localhost\/clean<\/loc>\s+<lastmod>2017-04-10T08:00:00\+00:00<\/lastmod>\s+<priority>0\.5<\/priority>\s+<\/url>/'],
+        ];
+    }
+}
index 8c3f5b1..1504a09 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 declare(strict_types = 1);
 
-namespace TYPO3\CMS\Frontend\Tests\Functional\XmlSitemap;
+namespace TYPO3\CMS\Seo\Tests\Functional\XmlSitemap;
 
 /*
  * This file is part of the TYPO3 CMS project.
@@ -38,13 +38,15 @@ class XmlSitemapRecordsTest extends AbstractTestCase
         parent::setUp();
         $this->importDataSet('EXT:seo/Tests/Functional/Fixtures/pages-sitemap.xml');
         $this->importDataSet('EXT:seo/Tests/Functional/Fixtures/sys_category.xml');
+        $this->importDataSet('EXT:seo/Tests/Functional/Fixtures/tt_content.xml');
         $this->setUpFrontendRootPage(
             1,
             [
                 'constants' => ['EXT:seo/Configuration/TypoScript/XmlSitemap/constants.typoscript'],
                 'setup' => [
                     'EXT:seo/Configuration/TypoScript/XmlSitemap/setup.typoscript',
-                    'EXT:seo/Tests/Functional/Fixtures/records.typoscript'
+                    'EXT:seo/Tests/Functional/Fixtures/records.typoscript',
+                    'EXT:seo/Tests/Functional/Fixtures/content.typoscript'
                 ],
             ]
         );
@@ -80,6 +82,43 @@ class XmlSitemapRecordsTest extends AbstractTestCase
         $content = $stream->getContents();
         self::assertContains('http://localhost/?tx_example_category%5Bid%5D=1&amp;', $content);
         self::assertContains('http://localhost/?tx_example_category%5Bid%5D=2&amp;', $content);
+        self::assertContains('<priority>0.5</priority>', $content);
+
+        $this->assertGreaterThan(0, $response->getHeader('Content-Length')[0]);
+    }
+
+    /**
+     * @test
+     */
+    public function checkIfSiteMapIndexContainsCustomChangeFreqAndPriorityValues(): void
+    {
+        $this->writeSiteConfiguration(
+            'website-local',
+            $this->buildSiteConfiguration(1, 'http://localhost/'),
+            [
+                $this->buildDefaultLanguageConfiguration('EN', '/'),
+            ]
+        );
+
+        $response = $this->executeFrontendRequest(
+            (new InternalRequest('http://localhost/'))->withQueryParameters(
+                [
+                    'id' => 1,
+                    'type' => 1533906435,
+                    'sitemap' => 'content',
+                ]
+            )
+        );
+
+        $this->assertEquals(200, $response->getStatusCode());
+        $this->assertArrayHasKey('Content-Length', $response->getHeaders());
+        $stream = $response->getBody();
+        $stream->rewind();
+        $content = $stream->getContents();
+
+        self::assertContains('<changefreq>hourly</changefreq>', $content);
+        self::assertContains('<priority>0.7</priority>', $content);
+
         $this->assertGreaterThan(0, $response->getHeader('Content-Length')[0]);
     }
 }
index 0c52e87..5574545 100644 (file)
@@ -12,4 +12,6 @@ CREATE TABLE pages (
        twitter_description text,
        twitter_image int(11) unsigned DEFAULT '0' NOT NULL,
        canonical_link varchar(2048) DEFAULT '' NOT NULL,
+       sitemap_priority decimal(1,1) DEFAULT '0.5' NOT NULL,
+       sitemap_changefreq varchar(10) DEFAULT '' NOT NULL,
 );