feat: implement Sitemapable (#181)

This commit is contained in:
Will Rossiter 2023-10-28 09:23:54 +13:00
parent def6be347c
commit 8a7c8aa9cc
No known key found for this signature in database
GPG Key ID: 7FD2A809B22259EF
10 changed files with 164 additions and 95 deletions

View File

@ -6,13 +6,13 @@
## Maintainer Contact
* Will Rossiter (Nickname: wrossiter, willr) <will@fullscreen.io>
- Will Rossiter (Nickname: wrossiter, willr) <will@fullscreen.io>
## Installation
> composer require "wilr/silverstripe-googlesitemaps"
If you're using Silverstripe 5 then version 3 or `dev-main` will work.
If you're using Silverstripe 5 then version `3` or `dev-main` will work.
For Silverstripe 4 use the `2.x` branch line.
@ -34,4 +34,4 @@ See docs/en for more information about configuring the module.
## Troubleshooting
* Flush this route to ensure the changes take effect (e.g http://yoursite.com/sitemap.xml?flush=1)
- Flush this route to ensure the changes take effect (e.g http://yoursite.com/sitemap.xml?flush=1)

10
_config.php Normal file
View File

@ -0,0 +1,10 @@
<?php
use SilverStripe\Core\ClassInfo;
use Wilr\GoogleSitemaps\GoogleSitemap;
if (0 === strpos(ltrim($_SERVER['REQUEST_URI'], '/'), 'sitemap')) {
foreach (ClassInfo::implementorsOf(Sitemapable::class) as $className) {
GoogleSitemap::register_dataobject($className);
}
}

View File

@ -30,42 +30,32 @@ manually, including requesting to have the page excluded from the sitemap.
Most module configuration is done via the SilverStripe Config API. Create a new
config file `mysite/_config/googlesitemaps.yml` with the following outline:
---
Name: customgooglesitemaps
After: googlesitemaps
---
Wilr\GoogleSitemaps\GoogleSitemap:
enabled: true
objects_per_sitemap: 1000
google_notification_enabled: false
use_show_in_search: true
---
Name: customgooglesitemaps
After: googlesitemaps
---
Wilr\GoogleSitemaps\GoogleSitemap:
enabled: true
objects_per_sitemap: 1000
google_notification_enabled: false
use_show_in_search: true
You can now alter any of those properties to set your needs. A popular option
is to turn on automatic pinging so that Google is notified of any updates to
your page. You can set this in the file we created in the last paragraph by
editing the `google_notification_enabled` option to true
---
Name: customgooglesitemaps
After: googlesitemaps
---
Wilr\GoogleSitemaps\GoogleSitemap:
enabled: true
objects_per_sitemap: 1000
google_notification_enabled: true
use_show_in_search: true
### Bing Ping Support
To ping Bing whenever your sitemap is updated, set `bing_notification_enabled`
---
Name: customgooglesitemaps
After: googlesitemaps
---
Wilr\GoogleSitemaps\GoogleSitemap:
enabled: true
bing_notification_enabled: true
enabled: true
objects_per_sitemap: 1000
google_notification_enabled: true
use_show_in_search: true
### Including DataObjects
@ -76,41 +66,40 @@ database as DataObject subclasses.
To include a DataObject instance in the Sitemap it requires that your subclass
defines two functions:
* AbsoluteLink() function which returns the URL for this DataObject
* canView() function which returns a boolean value.
- AbsoluteLink() function which returns the URL for this DataObject
- canView() function which returns a boolean value.
The following is a barebones example of a DataObject called 'MyDataObject'. It
assumes that you have a controller called 'MyController' which has a show method
to show the DataObject by its ID.
<?php
<?php
use SilverStripe\ORM\DataObject;
use SilverStripe\Control\Director;
class MyDataObject extends DataObject {
class MyDataObject extends DataObject {
function canView($member = null) {
return true;
}
function canView($member = null) {
return true;
}
function AbsoluteLink() {
return Director::absoluteURL($this->Link());
}
function Link() {
return 'MyController/show/'. $this->ID;
}
}
function AbsoluteLink() {
return Director::absoluteURL($this->Link());
}
function Link() {
return 'MyController/show/'. $this->ID;
}
}
After those methods have been defined on your DataObject you now need to tell
the Google Sitemaps module that it should be listed in the sitemap.xml file. To
do that, include the following in your _config.php file.
do that, include the following in your \_config.php file.
use Wilr\GoogleSitemaps\GoogleSitemap;
GoogleSitemap::register_dataobject('MyDataObject');
GoogleSitemap::register_dataobject('MyDataObject');
If you need to change the frequency of the indexing, you can pass the change
frequency (daily, weekly, monthly) as a second parameter to register_dataobject(), So
@ -118,7 +107,7 @@ instead of the previous code you would write:
use Wilr\GoogleSitemaps\GoogleSitemap;
GoogleSitemap::register_dataobject('MyDataObject', 'daily');
GoogleSitemap::register_dataobject('MyDataObject', 'daily');
See the following blog post for more information:
@ -133,8 +122,26 @@ urls to include.
use Wilr\GoogleSitemaps\GoogleSitemap;
GoogleSitemap::register_routes(array(
'/my-custom-controller/',
'/Security/',
'/Security/login/'
));
GoogleSitemap::register_routes(array(
'/my-custom-controller/',
'/Security/',
'/Security/login/'
));
### Sitemapable
For automatic registration of a DataObject subclass, implement the `Sitemapable`
extension
```
<?php
class MyDataObject extends DataObject implements Sitemapable
{
public function AbsoluteLink()
{
// ..
}
}
```

View File

@ -1,9 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="SilverStripe">
<description>CodeSniffer ruleset for SilverStripe coding conventions.</description>
<file>src</file>
<file>tests</file>
<!-- base rules are PSR-2 -->
<rule ref="PSR2" >
<rule ref="PSR2">
<!-- Current exclusions -->
<exclude name="PSR1.Methods.CamelCapsMethodName" />
</rule>

View File

@ -75,8 +75,7 @@ class GoogleSitemapController extends Controller
}
}
if (
GoogleSitemap::enabled()
if (GoogleSitemap::enabled()
&& $class
&& ($page > 0)
&& ($class == SiteTree::class || $class == 'GoogleSitemapRoute' || GoogleSitemap::is_registered($class))

View File

@ -90,8 +90,7 @@ class GoogleSitemap
* @var array
*/
private static $search_indexes = [
'google' => 'http://www.google.com/webmasters/sitemaps/ping?sitemap=',
'bing' => 'http://www.bing.com/ping?sitemap=',
'google' => 'http://www.google.com/webmasters/sitemaps/ping?sitemap='
];
/**

14
src/Sitemapable.php Normal file
View File

@ -0,0 +1,14 @@
<?php
namespace Wilr\GoogleSitemaps;
interface Sitemapable
{
/**
* Return the absolute URL for this object
*
* @return string
*/
public function AbsoluteLink();
}

View File

@ -20,12 +20,22 @@ use Wilr\GoogleSitemaps\Tests\Model\UnviewableDataObject;
class GoogleSitemapTest extends FunctionalTest
{
protected static $fixture_file = [
'GoogleSitemapTest.yml',
'GoogleSitemapPageTest.yml',
'GoogleSitemapTest.yml'
];
protected $usesDatabase = true;
public static function get_fixture_file()
{
$files = [__DIR__ . '/GoogleSitemapTest.yml'];
if (class_exists('Page')) {
$files[] = __DIR__ . '/GoogleSitemapPageTest.yml';
}
return $files;
}
protected static $extra_dataobjects = [
TestDataObject::class,
OtherDataObject::class,
@ -55,13 +65,13 @@ class GoogleSitemapTest extends FunctionalTest
public function testCanIncludeInGoogleSitemap(): void
{
GoogleSitemap::register_dataobject(TestDataObject::class, '');
$unused = $this->objFromFixture(TestDataObject::class, 'UnindexedDataObject');
$this->assertFalse($unused->canIncludeInGoogleSitemap());
$used = $this->objFromFixture(TestDataObject::class, 'DataObjectTest2');
$this->assertTrue($used->canIncludeInGoogleSitemap());
$used->setPrivate();
$this->assertFalse($used->canIncludeInGoogleSitemap());
}
public function testIndexFileWithCustomRoute(): void
@ -77,31 +87,29 @@ class GoogleSitemapTest extends FunctionalTest
public function testGetItems(): void
{
GoogleSitemap::register_dataobject(TestDataObject::class, '');
$google = new GoogleSitemap();
$items = GoogleSitemap::get_items(TestDataObject::class, 1);
$this->assertEquals(2, $items->count());
$items = $google->getItems(TestDataObject::class, 1);
$this->assertListEquals(array(
array("Priority" => "0.2"),
array("Priority" => "0.4")
), $items);
$this->assertEquals(3, $items->count());
GoogleSitemap::register_dataobject(OtherDataObject::class);
$this->assertEquals(1, GoogleSitemap::get_items(OtherDataObject::class, 1)->count());
$this->assertEquals(1, $google->getItems(OtherDataObject::class, 1)->count());
GoogleSitemap::register_dataobject(UnviewableDataObject::class);
$this->assertEquals(0, GoogleSitemap::get_items(UnviewableDataObject::class, 1)->count());
$this->assertEquals(0, $google->getItems(UnviewableDataObject::class, 1)->count());
}
public function testGetItemsWithCustomRoutes(): void
{
GoogleSitemap::register_routes(array(
GoogleSitemap::register_routes([
'/test-route/',
'/someother-route/',
'/fake-sitemap-route/'
));
]);
$items = GoogleSitemap::get_items('GoogleSitemapRoute', 1);
$google = new GoogleSitemap();
$items = $google->getItems('GoogleSitemapRoute', 1);
$this->assertEquals(3, $items->count());
}
@ -110,6 +118,16 @@ class GoogleSitemapTest extends FunctionalTest
GoogleSitemap::register_dataobject(TestDataObject::class);
GoogleSitemap::register_dataobject(OtherDataObject::class);
$obj = $this->objFromFixture(TestDataObject::class, 'DataObjectTest1');
$table = $obj->baseTable();
DB::query("UPDATE \"" . $table . "\" SET \"LastEdited\"='2023-02-13 00:00:00'");
$obj2 = $this->objFromFixture(OtherDataObject::class, 'OtherDataObjectTest2');
$table = $obj2->baseTable();
DB::query("UPDATE \"" . $table . "\" SET \"LastEdited\"='2023-02-13 00:00:00'");
$response = $this->get('sitemap.xml');
$body = $response->getBody();
@ -153,9 +171,19 @@ class GoogleSitemapTest extends FunctionalTest
Config::inst()->set(GoogleSitemap::class, 'objects_per_sitemap', 1);
GoogleSitemap::register_dataobject(TestDataObject::class);
$obj = $this->objFromFixture(TestDataObject::class, 'DataObjectTest1');
$obj1 = $this->objFromFixture(TestDataObject::class, 'DataObjectTest2');
$obj2 = $this->objFromFixture(TestDataObject::class, 'UnindexedDataObject');
$table = $obj->baseTable();
DB::query("UPDATE \"" . $table . "\" SET \"LastEdited\"='2023-02-13 00:00:00' WHERE \"ID\"='" . $obj->ID . "'");
DB::query("UPDATE \"" . $table . "\" SET \"LastEdited\"='2023-02-13 00:00:00' WHERE \"ID\"='" . $obj1->ID . "'");
DB::query("UPDATE \"" . $table . "\" SET \"LastEdited\"='2023-02-13 00:00:00' WHERE \"ID\"='" . $obj2->ID . "'");
$response = $this->get('sitemap.xml');
$body = $response->getBody();
$this->assertXmlStringEqualsXmlFile(__DIR__ . '/xml/' . __FUNCTION__ . '.xml', $body);
Config::inst()->set(GoogleSitemap::class, 'objects_per_sitemap', $original);
@ -164,11 +192,11 @@ class GoogleSitemapTest extends FunctionalTest
public function testRegisterRoutesIncludesAllRoutes(): void
{
GoogleSitemap::register_route('/test/');
GoogleSitemap::register_routes(array(
GoogleSitemap::register_routes([
'/test/', // duplication should be replaced
'/unittests/',
'/anotherlink/'
), 'weekly');
], 'weekly');
$response = $this->get('sitemap.xml/sitemap/GoogleSitemapRoute/1');
$body = $response->getBody();
@ -193,7 +221,7 @@ class GoogleSitemapTest extends FunctionalTest
public function testGetItemsWithPages(): void
{
if (!class_exists('Page')) {
if (!class_exists(SiteTree::class)) {
$this->markTestIncomplete('No cms module installed, page related test skipped');
}
@ -205,10 +233,10 @@ class GoogleSitemapTest extends FunctionalTest
$page2->publishSingle();
$page2->flushCache();
$this->assertListContains(array(
array('Title' => 'Testpage1'),
array('Title' => 'Testpage2')
), GoogleSitemap::inst()->getItems(SiteTree::class), "There should be 2 pages in the sitemap after publishing");
$this->assertListContains([
['Title' => 'Testpage1'],
['Title' => 'Testpage2']
], GoogleSitemap::inst()->getItems(SiteTree::class), "There should be 2 pages in the sitemap after publishing");
// check if we make a page readonly that it is hidden
$page2->CanViewType = 'LoggedInUsers';
@ -217,9 +245,9 @@ class GoogleSitemapTest extends FunctionalTest
$this->logOut();
$this->assertListEquals(array(
array('Title' => 'Testpage1')
), GoogleSitemap::inst()->getItems(SiteTree::class), "There should be only 1 page, other is logged in only");
$this->assertListEquals([
['Title' => 'Testpage1']
], GoogleSitemap::inst()->getItems(SiteTree::class), "There should be only 1 page, other is logged in only");
}
public function testAccess(): void

View File

@ -1,15 +1,15 @@
Wilr\GoogleSitemaps\Tests\Model\TestDataObject:
DataObjectTest1:
Priority: 0.4
DataObjectTest2:
Priority: 0.2
UnindexedDataObject:
Priority: -1
DataObjectTest1:
Priority: 0.4
DataObjectTest2:
Priority: 0.2
UnindexedDataObject:
Priority: -1
Wilr\GoogleSitemaps\Tests\Model\OtherDataObject:
OtherDataObjectTest2:
Priority: 0.3
OtherDataObjectTest2:
Priority: 0.3
Wilr\GoogleSitemaps\Tests\Model\UnviewableDataObject:
Unviewable1:
Priority: 0.4
Unviewable1:
Priority: 0.4

View File

@ -8,6 +8,7 @@ use SilverStripe\Control\Director;
class TestDataObject extends DataObject implements TestOnly
{
protected $private = false;
private static $db = array(
'Priority' => 'Varchar(10)'
@ -15,9 +16,19 @@ class TestDataObject extends DataObject implements TestOnly
public function canView($member = null)
{
if ($this->private) {
return false;
}
return true;
}
public function setPrivate()
{
$this->private = true;
}
public function AbsoluteLink()
{
return Director::absoluteBaseURL();