This commit is contained in:
Daniel Hensby 2015-07-26 23:47:02 +00:00
commit e3d4ee0e02
14 changed files with 267 additions and 98 deletions

24
.travis.yml Normal file
View File

@ -0,0 +1,24 @@
# See https://github.com/silverstripe-labs/silverstripe-travis-support for setup details
language: php
php:
- 5.3
sudo: false
env:
- DB=MYSQL CORE_RELEASE=3.1
- DB=PGSQL CORE_RELEASE=3
matrix:
include:
- php: 5.4
env: DB=MYSQL CORE_RELEASE=3
before_script:
- git clone git://github.com/silverstripe-labs/silverstripe-travis-support.git ~/travis-support
- php ~/travis-support/travis_setup.php --source `pwd` --target ~/builds/ss
- cd ~/builds/ss
script:
- phpunit googlesitemaps/tests/

View File

@ -1,5 +1,7 @@
# Google Sitemaps Module
[![Build Status](https://secure.travis-ci.org/silverstripe-labs/silverstripe-googlesitemaps.png?branch=master)](http://travis-ci.org/silverstripe-labs/silverstripe-googlesitemaps)
## Maintainer Contact
* Will Rossiter (Nickname: wrossiter, willr) <will@fullscreen.io>
@ -18,6 +20,8 @@ information on your site to be discovered by Google quickly.
Therefore, all Silverstripe websites contain a special controller which can
be visited: http://yoursite.com/sitemap.xml
Flush this route to ensure the changes take effect with: http://yoursite.com/sitemap.xml?flush=1
See http://en.wikipedia.org/wiki/Sitemaps for info on this format.
## Usage Overview

View File

@ -2,7 +2,7 @@
// add the extension to pages
if (class_exists('SiteTree')) {
Object::add_extension('SiteTree', 'GoogleSitemapSiteTreeExtension');
SiteTree::add_extension('GoogleSitemapSiteTreeExtension');
}
// if you need to add this to DataObjects include the following in

View File

@ -16,10 +16,21 @@
* By default, Google is not notified, and will pick up your new
* sitemap whenever the GoogleBot visits your website.
*
* Enabling notification of Google after every publish (in your _config.php):
* To Enable notification of Google after every publish set google_notification_enabled
* to true in the googlesitemaps.yml config file.
* This file is usually located in the _config folder of your project folder.
* e.g mysite/_config/googlesitemaps.yml
*
* <example>
* GoogleSitemap::enable_google_notificaton();
* ---
* Name: customgooglesitemaps
* After: googlesitemaps
* ---
* GoogleSitemap:
* enabled: true
* objects_per_sitemap: 1000
* google_notification_enabled: true
* use_show_in_search: true
* </example>
*
* @see http://www.google.com/support/webmasters/bin/answer.py?hl=en&answer=34609
@ -60,7 +71,7 @@ class GoogleSitemap {
*/
public static function register_dataobject($className, $changeFreq = 'monthly', $priority = '0.6') {
if (!self::is_registered($className)) {
Object::add_extension($className, 'GoogleSitemapExtension');
$className::add_extension('GoogleSitemapExtension');
self::$dataobjects[$className] = array(
'frequency' => ($changeFreq) ? $changeFreq : 'monthly',
@ -171,12 +182,28 @@ class GoogleSitemap {
* @return ArrayList
*/
public static function get_items($class, $page = 1) {
//normalise the class name
try {
$reflectionClass = new ReflectionClass($class);
$class = $reflectionClass->getName();
} catch (ReflectionException $e) {
// this can happen when $class is GoogleSitemapRoute
//we should try to carry on
}
$output = new ArrayList();
$count = Config::inst()->get('GoogleSitemap', 'objects_per_sitemap');
$filter = Config::inst()->get('GoogleSitemap', 'use_show_in_search');
// todo migrate to extension hook or DI point for other modules to
// modify state filters
if(class_exists('Translatable')) {
Translatable::disable_locale_filter();
}
if($class == "SiteTree") {
$filter = ($filter) ? "\"ShowInSearch\" = 1" : "";
$instances = Versioned::get_by_stage('SiteTree', 'Live', $filter);
}
else if($class == "GoogleSitemapRoute") {
@ -263,6 +290,12 @@ class GoogleSitemap {
$filter = Config::inst()->get('GoogleSitemap', 'use_show_in_search');
if(class_exists('SiteTree')) {
// move to extension hook. At the moment moduleexists config hook
// does not work.
if(class_exists('Translatable')) {
Translatable::disable_locale_filter();
}
$filter = ($filter) ? "\"ShowInSearch\" = 1" : "";
$instances = Versioned::get_by_stage('SiteTree', 'Live', $filter);
$count = $instances->count();
@ -270,11 +303,13 @@ class GoogleSitemap {
$neededForPage = ceil($count / $countPerFile);
for($i = 1; $i <= $neededForPage; $i++) {
$sliced = $instances
->limit($countPerFile, ($i - 1) * $countPerFile)
->last();
$lastModified = ($sliced) ? $sliced->dbObject('LastEdited')->Format('Y-m-d') : date('Y-m-d');
$lastEdited = $instances
->limit($countPerFile, ($i - 1) * $countPerFile)
->sort(array())
->max('LastEdited');
$lastModified = ($lastEdited) ? date('Y-m-d', strtotime($lastEdited)) : date('Y-m-d');
$sitemaps->push(new ArrayData(array(
'ClassName' => 'SiteTree',
@ -378,46 +413,6 @@ class GoogleSitemap {
return $response;
}
/**
* Enable pings to google.com whenever sitemap changes.
*
* @return void
*/
public static function enable_google_notification() {
Deprecation::notice('1.1', 'GoogleSitemap::enable() is deprecated. Please use Config API instead. See documentation.');
Config::inst()->remove('GoogleSitemap', 'google_notification_enabled');
Config::inst()->update('GoogleSitemap', 'google_notification_enabled', true);
}
/**
* Disables pings to google when the sitemap changes.
*
* @deprecated 1.1
* @return void
*/
public static function disable_google_notification() {
Deprecation::notice('1.1', 'GoogleSitemap::enable() is deprecated. Please use Config API instead. See documentation.');
Config::inst()->remove('GoogleSitemap', 'google_notification_enabled');
Config::inst()->update('GoogleSitemap', 'google_notification_enabled', false);
}
/**
* Enable Google Sitemap support. Requests to the sitemap.xml route will
* result in an XML sitemap being provided.
*
* @deprecated 1.1
* @return void
*/
public static function enable() {
Deprecation::notice('1.1', 'GoogleSitemap::enable() is deprecated. Please use Config API instead. See documentation.');
Config::inst()->remove('GoogleSitemap', 'enabled');
Config::inst()->update('GoogleSitemap', 'enabled', true);
}
/**
* Is GoogleSitemap enabled?
*
@ -425,19 +420,5 @@ class GoogleSitemap {
*/
public static function enabled() {
return (Config::inst()->get('GoogleSitemap', 'enabled', Config::INHERITED));
}
/**
* Disable Google Sitemap support. Any requests to the sitemap.xml route
* will produce a 404 response.
*
* @deprecated 1,1
* @return void
*/
public static function disable() {
Deprecation::notice('1.1', 'GoogleSitemap::disable() is deprecated. Please use Config API instead. See documentation.');
Config::inst()->remove('GoogleSitemap', 'enabled');
Config::inst()->update('GoogleSitemap', 'enabled', false);
}
}
}
}

View File

@ -17,7 +17,7 @@ class GoogleSitemapController extends Controller {
/**
* @var array
*/
public static $allowed_actions = array(
private static $allowed_actions = array(
'index',
'sitemap'
);
@ -30,9 +30,10 @@ class GoogleSitemapController extends Controller {
*/
public function index($url) {
if(GoogleSitemap::enabled()) {
SSViewer::set_source_file_comments(false);
Config::inst()->update('SSViewer', 'set_source_file_comments', false);
$this->getResponse()->addHeader('Content-Type', 'application/xml; charset="utf-8"');
$this->getResponse()->addHeader('X-Robots-Tag', 'noindex');
$sitemaps = GoogleSitemap::get_sitemaps();
$this->extend('updateGoogleSitemaps', $sitemaps);
@ -56,9 +57,10 @@ class GoogleSitemapController extends Controller {
$page = $this->request->param('OtherID');
if(GoogleSitemap::enabled() && $class && $page) {
SSViewer::set_source_file_comments(false);
Config::inst()->update('SSViewer', 'set_source_file_comments', false);
$this->getResponse()->addHeader('Content-Type', 'application/xml; charset="utf-8"');
$this->getResponse()->addHeader('X-Robots-Tag', 'noindex');
$items = GoogleSitemap::get_items($class, $page);
$this->extend('updateGoogleSitemapItems', $items, $class, $page);
@ -70,4 +72,4 @@ class GoogleSitemapController extends Controller {
return new SS_HTTPResponse('Page not found', 404);
}
}
}
}

View File

@ -7,17 +7,17 @@
* @package googlesitemaps
*/
class GoogleSitemapExtension extends DataExtension {
/**
* @return boolean
*/
public function canIncludeInGoogleSitemap() {
$can = true;
if(method_exists($this, 'AbsoluteLink')) {
$objHttp = parse_url($this->AbsoluteLink(), PHP_URL_HOST);
$hostHttp = parse_url('http://' . $_SERVER['HTTP_HOST'], PHP_URL_HOST);
/**
* @return boolean
*/
public function canIncludeInGoogleSitemap() {
$can = true;
if($this->owner->hasMethod('AbsoluteLink')) {
$hostHttp = parse_url(Director::protocolAndHost(), PHP_URL_HOST);
$objHttp = parse_url($this->owner->AbsoluteLink(), PHP_URL_HOST);
if($objHttp != $hostHttp) {
$can = false;
}
@ -31,10 +31,10 @@ class GoogleSitemapExtension extends DataExtension {
$can = $this->owner->getGooglePriority();
}
$this->owner->extend('alterCanIncludeInGoogleSitemap', $can);
$this->owner->invokeWithExtensions('alterCanIncludeInGoogleSitemap', $can);
return $can;
}
}
/**

View File

@ -8,7 +8,7 @@ class GoogleSitemapSiteTreeExtension extends GoogleSitemapExtension {
/**
* @var array
*/
public static $db = array(
private static $db = array(
"Priority" => "Varchar(5)"
);
@ -56,13 +56,35 @@ class GoogleSitemapSiteTreeExtension extends GoogleSitemapExtension {
$labels['Priority'] = _t('GoogleSitemaps.METAPAGEPRIO', "Page Priority");
}
/**
* Ensure that all parent pages of this page (if any) are published
*
* @return boolean
*/
public function hasPublishedParent() {
// Skip root pages
if(empty($this->owner->ParentID)) return true;
// Ensure direct parent exists
$parent = $this->owner->Parent();
if(empty($parent) || !$parent->exists()) return false;
// Check ancestry
return $parent->hasPublishedParent();
}
/**
* @return boolean
*/
public function canIncludeInGoogleSitemap() {
// Check that parent page is published
if(!$this->owner->hasPublishedParent()) return false;
$result = parent::canIncludeInGoogleSitemap();
$result = ($this instanceof ErrorPage) ? false : $result;
$result = ($this->owner instanceof ErrorPage) ? false : $result;
return $result;
}
@ -71,6 +93,7 @@ class GoogleSitemapSiteTreeExtension extends GoogleSitemapExtension {
* @return mixed
*/
public function getGooglePriority() {
setlocale(LC_ALL, "en_US.UTF8");
$priority = $this->owner->getField('Priority');
if(!$priority) {
@ -84,7 +107,7 @@ class GoogleSitemapSiteTreeExtension extends GoogleSitemapExtension {
} else if ($priority == -1) {
return false;
} else {
return (is_float($priority) && $priority <= 1.0) ? $priority : 0.5;
return (is_numeric($priority) && $priority <= 1.0) ? $priority : 0.5;
}
}
}

View File

@ -10,6 +10,6 @@
"email": "will@fullscreen.io"
}],
"require": {
"silverstripe/framework": "3.0.*"
"silverstripe/framework": "~3.1"
}
}

View File

@ -75,7 +75,7 @@ to show the DataObject by its ID.
class MyDataObject extends DataObject {
function canView() {
function canView($member = null) {
return true;
}

9
lang/lt.yml Normal file
View File

@ -0,0 +1,9 @@
lt:
GoogleSitemaps:
METANOTEPRIORITY: 'Šio puslapio Google svetainės medžio prioriteto nustatymas (%s)'
METAPAGEPRIO: 'Puslapio prioritetas'
PRIORITYAUTOSET: 'Automatiškai nustatyti prioritetą pagal puslapio gylį'
PRIORITYLEASTIMPORTANT: 'Mažiausiai svarbus'
PRIORITYMOSTIMPORTANT: 'Labiausiai svarbus'
PRIORITYNOTINDEXED: 'Neindeksuojamas'
TABGOOGLESITEMAP: 'Google svetainės medis'

View File

@ -1,7 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type='text/xsl' href='{$AbsoluteBaseURL}googlesitemaps/templates/xml-sitemapindex.xsl'?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"><% loop Sitemaps %>
<sitemap>
<loc>{$BaseHref}sitemap.xml/sitemap/$ClassName/$Page.xml</loc>
<loc>{$AbsoluteBaseURL}sitemap.xml/sitemap/$ClassName/$Page.xml</loc>
<% if LastModified %><lastmod>$LastModified</lastmod><% end_if %>
</sitemap><% end_loop %>
</sitemapindex>
</sitemapindex>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type='text/xsl' href='{$BaseHref}googlesitemaps/templates/xml-sitemap.xsl'?>
<?xml-stylesheet type='text/xsl' href='{$AbsoluteBaseURL}googlesitemaps/templates/xml-sitemap.xsl'?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<% loop $Items %>
<url>

View File

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:html="http://www.w3.org/TR/REC-html40" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:sitemap="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>XML Sitemap</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" href="googlesitemaps/css/style.css" />
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script type="text/javascript" src="googlesitemaps/javascript/jquery.tablesorter.min.js"></script>
<script type="text/javascript"><![CDATA[
$(document).ready(function() {
$("#sitemapindex").tablesorter( { sortList: [[0,0]],widgets: ['zebra'] } );
});
]]></script>
</head>
<body>
<div id="content">
<h1>
<a href="http://silverstripe.org" target="_blank">XML Sitemap
<span class="ss_link">&#8594; silverstripe.org</span>
</a>
</h1>
<p class="expl">
This sitemap consists of <xsl:value-of select="count(sitemap:sitemapindex/sitemap:sitemap)"/> part(s).
</p>
<table id="sitemapindex" cellpadding="3" class="tablesorter">
<thead>
<tr>
<th width="90%">URL</th>
<th width="10%">Last Change</th>
</tr>
</thead>
<tbody>
<xsl:variable name="lower" select="'abcdefghijklmnopqrstuvwxyz'"/>
<xsl:variable name="upper" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>
<xsl:for-each select="sitemap:sitemapindex/sitemap:sitemap">
<tr>
<td>
<xsl:variable name="itemURL">
<xsl:value-of select="sitemap:loc"/>
</xsl:variable>
<a href="{$itemURL}">
<xsl:value-of select="sitemap:loc"/>
</a>
</td>
<td>
<xsl:value-of select="concat(substring(sitemap:lastmod,0,11),concat(' ', substring(sitemap:lastmod,12,5)))"/>
</td>
</tr>
</xsl:for-each>
</tbody>
</table>
<p id="Footer" class="expl">Generated by the SilverStripe
<a href="https://github.com/silverstripe-labs/silverstripe-googlesitemaps" target="_blank" title="SilverStripe Google Sitemaps module on Github">Google Sitemaps Module</a>
<br />More information about XML sitemaps on <a href="http://sitemaps.org" target="_blank">sitemaps.org</a>.
</p>
</div>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

View File

@ -89,20 +89,32 @@ class GoogleSitemapTest extends FunctionalTest {
$expected = "<loc>". Director::absoluteURL("sitemap.xml/sitemap/GoogleSitemapTest_UnviewableDataObject/2") ."</loc>";
$this->assertEquals(0, substr_count($body, $expected) , 'A link to a GoogleSitemapTest_UnviewableDataObject does not exist');
}
}
public function testLastModifiedDateOnRootXML() {
GoogleSitemap::register_dataobject("GoogleSitemapTest_DataObject");
Config::inst()->update('GoogleSitemap', 'enabled', true);
DB::query("
UPDATE GoogleSitemapTest_DataObject SET LastEdited = '2012-01-14'"
);
if(!class_exists('Page')) {
$this->markTestIncomplete('No cms module installed, page related test skipped');
}
$page = $this->objFromFixture('Page', 'Page1');
$page->publish('Stage', 'Live');
$page->flushCache();
$page2 = $this->objFromFixture('Page', 'Page2');
$page2->publish('Stage', 'Live');
$page2->flushCache();
DB::query("UPDATE \"SiteTree_Live\" SET \"LastEdited\"='2014-03-14 00:00:00' WHERE \"ID\"='".$page->ID."'");
DB::query("UPDATE \"SiteTree_Live\" SET \"LastEdited\"='2014-01-01 00:00:00' WHERE \"ID\"='".$page2->ID."'");
$response = $this->get('sitemap.xml');
$body = $response->getBody();
$expected = "<lastmod>2012-01-14</lastmod>";
$this->assertEquals(1, substr_count($body, $expected));
$expected = '<lastmod>2014-03-14</lastmod>';
$this->assertEquals(1, substr_count($body, $expected) , 'The last mod date should use most recent LastEdited date');
}
public function testIndexFilePaginatedSitemapFiles() {
@ -149,6 +161,19 @@ class GoogleSitemapTest extends FunctionalTest {
Config::inst()->update('GoogleSitemap', 'objects_per_sitemap', $original);
}
public function testAccessingNestedSiteMapCaseInsensitive() {
$original = Config::inst()->get('GoogleSitemap', 'objects_per_sitemap');
Config::inst()->update('GoogleSitemap', 'objects_per_sitemap', 1);
GoogleSitemap::register_dataobject("GoogleSitemapTest_DataObject");
$response = $this->get('sitemap.xml/sitemap/googlesitemaptest_dataobject/1');
$body = $response->getBody();
$this->assertEquals(200, $response->getStatusCode(), 'successful loaded nested sitemap');
Config::inst()->update('GoogleSitemap', 'objects_per_sitemap', $original);
}
public function testGetItemsWithPages() {
if(!class_exists('Page')) {
$this->markTestIncomplete('No cms module installed, page related test skipped');
@ -227,11 +252,47 @@ class GoogleSitemapTest extends FunctionalTest {
// invalid field doesn't break google
$page->Priority = 'foo';
$this->assertEquals(0.5, $page->getGooglePriority());
// custom value (set as string as db field is varchar)
$page->Priority = '0.2';
$this->assertEquals(0.2, $page->getGooglePriority());
// -1 indicates that we should not index this
$page->Priority = -1;
$this->assertFalse($page->getGooglePriority());
}
public function testUnpublishedPage() {
if(!class_exists('SiteTree')) {
$this->markTestSkipped('Test skipped; CMS module required for testUnpublishedPage');
}
$orphanedPage = new SiteTree();
$orphanedPage->ParentID = 999999; // missing parent id
$orphanedPage->write();
$orphanedPage->publish("Stage", "Live");
$rootPage = new SiteTree();
$rootPage->ParentID = 0;
$rootPage->write();
$rootPage->publish("Stage", "Live");
$oldMode = Versioned::get_reading_mode();
Versioned::reading_stage('Live');
try {
$this->assertEmpty($orphanedPage->hasPublishedParent());
$this->assertEmpty($orphanedPage->canIncludeInGoogleSitemap());
$this->assertNotEmpty($rootPage->hasPublishedParent());
$this->assertNotEmpty($rootPage->canIncludeInGoogleSitemap());
} catch(Exception $ex) {
Versioned::set_reading_mode($oldMode);
throw $ex;
} // finally {
Versioned::set_reading_mode($oldMode);
// }
}
}
/**
@ -249,7 +310,7 @@ class GoogleSitemapTest_DataObject extends DataObject implements TestOnly {
}
public function AbsoluteLink() {
return Director::baseURL();
return Director::absoluteBaseURL();
}
}
@ -268,7 +329,7 @@ class GoogleSitemapTest_OtherDataObject extends DataObject implements TestOnly {
}
public function AbsoluteLink() {
return Director::baseURL();
return Director::absoluteBaseURL();
}
}
@ -287,6 +348,6 @@ class GoogleSitemapTest_UnviewableDataObject extends DataObject implements TestO
}
public function AbsoluteLink() {
return Director::baseURL();
return Director::absoluteBaseURL();
}
}