Compare commits

...

265 Commits
0.1.3 ... main

Author SHA1 Message Date
Will Rossiter 794778ae20
Merge pull request #198 from somardigital/main
BUG - Pass empty string instead of null when no image exists
2024-04-08 16:04:08 +12:00
Sukhwinder Singh 05e6963f04 BUG - Pass empty string instead of null when no image exists 2024-04-08 15:08:56 +12:00
Will Rossiter 3f75c06a77
Merge pull request #197 from silverstripeltd/feature/removal-of-ping
Removal of ping for google site maps
2024-03-26 16:04:23 +13:00
jareddreyerss a13d3dd9de Removal of ping from google site maps
See https://developers.google.com/search/blog/2023/06/sitemaps-lastmod-ping for context.

Accordingly removed all references to notification config and/or ping.
2024-03-26 14:20:14 +13:00
Will Rossiter 59acd419ef
Merge pull request #194 from sunnysideup/patch-1 2024-02-22 07:24:41 +13:00
Sunny Side Up aeeb960566
DOC: fixing yml annotation 2024-02-21 20:50:47 +13:00
Will Rossiter c7f260bb33
Merge pull request #193 from BrookeNZ/main 2024-01-03 07:13:52 +13:00
Brooke Lord c2f5162e97 Additional styling tweaks 2023-12-29 20:29:05 +13:00
Brooke Lord 59e56c3b69 Update xml-sitemap.ss 2023-12-29 20:20:32 +13:00
Brooke Lord d9da059a83 Update Silverstripe CMS wording 2023-12-29 17:13:43 +13:00
Brooke Lord 54ddc6ccf7 UI refresh 2023-12-29 17:13:43 +13:00
Will Rossiter cbccf88a62
chore: lint 2023-10-29 13:53:26 +13:00
Will Rossiter 8a7c8aa9cc
feat: implement Sitemapable (#181) 2023-10-29 13:30:08 +13:00
Will Rossiter def6be347c
fix: cast variables (#192) 2023-10-28 07:59:55 +13:00
Will Rossiter 523d196d12
Merge pull request #191 from satrun77/pulls/fix-trailing-slash
Ensure trailing slash removed from AbsoluteLink
2023-08-29 16:36:46 +12:00
Mohamed Alsharaf f064330acd Ensure trailing slash removed from AbsoluteLink
This is to resolve duplicated trailing slash when add_trailing_slash
property is set to true in YML config.

Also, relates to #188
2023-08-29 15:57:24 +12:00
Will Rossiter f1bddb626d
Merge pull request #190 from visualmetricsio/fix/absolute-link-scope
Fix absolute link scope
2023-08-29 06:16:25 +12:00
Luke Fromhold 77a366871e Fix absolute link scope 2023-08-28 20:33:51 +10:00
Will Rossiter 81b8d870a4
fix: absolute link (188) 2023-07-19 21:41:47 +12:00
Will Rossiter 9b0e0f1064
Merge pull request #179 from td204/2.2.0-fixAlterCanInclude
response was array, need to check boolean value
2023-07-19 21:16:34 +12:00
Will Rossiter 7370c85dae
Merge pull request #189 from tiller1010/hotfix/2.2.2/other-id-type-check 2023-07-14 16:32:29 +12:00
Tyler bf6156c103 fix: added type check for other id 2023-07-13 15:38:30 -04:00
Will Rossiter a29de80408
chore: update alias 2023-05-18 19:18:54 +12:00
Will Rossiter 0fbfec9f66
chore: migrate to gh actions from travis 2023-02-13 16:27:48 +13:00
Will Rossiter 54e5043622
Merge pull request #186 from satrun77/pulls/silverstripe-5
Add support for silverstripe 5
2023-02-13 16:23:37 +13:00
Mohamed Alsharaf 286115174d Add support for silverstripe 5 2023-02-13 14:06:48 +13:00
Will Rossiter cfcb002cf3
Merge pull request #184 from Zazama/main 2022-07-29 05:56:34 +12:00
Jan Metzger 0e965ecd46 Convert in GoogleSitemapController to int, avoid 500 errors on non-nomeric values 2022-07-28 12:28:58 +02:00
Will Rossiter cb56ac11d9
fix: gracefully handle missing images 2022-07-05 13:27:32 +12:00
Will Rossiter 6adb11b5bf
Merge pull request #181 from evanshunt/main 2021-12-16 07:42:33 +13:00
sinan-evanshunt 8d321bd0a1
Update GoogleSitemapSiteTreeExtension.php 2021-12-15 11:16:30 -07:00
terry 35665402cb response was array, need to check boolean value 2021-08-20 23:07:09 +02:00
Will Rossiter 7326028c7c
MNT dev-main 2021-08-17 14:44:22 +12:00
Will Rossiter 401f723e7e
Merge pull request #177 from lerni/german-translation
add german translation
2021-01-12 07:11:20 +13:00
Will Rossiter 71e62966d0
Merge pull request #176 from lerni/Title-for-Images
Add Title for Images
2021-01-12 07:10:34 +13:00
Lukas Erni 1915f9ab59 add german translation 2021-01-11 14:22:24 +01:00
Lukas Erni 1d535ade02 Add Title for Images 2021-01-11 14:14:44 +01:00
Will Rossiter 56b25a424f
Merge pull request #174 from wilr/pr/172
Handle cases such as 'Class.RelationName'
2020-11-20 09:53:35 +13:00
Will Rossiter 67e8d601d5
Update GoogleSitemapSiteTreeExtension.php 2020-11-20 09:46:56 +13:00
Will Rossiter 67c8ad044e
Handle cases such as 'Class.RelationName' 2020-11-20 09:01:40 +13:00
Will Rossiter a333b56711
Rename CHANGELOG to CHANGELOG.md 2020-11-19 12:00:42 +13:00
Will Rossiter 745de7a7af
Update CHANGELOG (#171) 2020-11-19 12:00:28 +13:00
Will Rossiter b5f322646f
Merge pull request #170 from xini/fix-priority-calculations
fix hierarchy based priority calculations
2020-11-19 11:34:29 +13:00
Will Rossiter 312b78f88b
Index manyMany images (Fixes #172) 2020-11-19 11:33:25 +13:00
Florian Thoma f026001f8b fix number of ancestors (getAncestors by default doesn't return the current page) 2020-09-19 16:22:17 +10:00
Florian Thoma ced2213be4 fix hierarchy beased priority calculations 2020-09-19 16:16:26 +10:00
Will Rossiter 17b21683a2
Merge pull request #169 from tomaszpirc/patch-1
Create sl.yml
2020-09-11 06:23:40 +12:00
Tomasz Pirc 7668ba8f48
Create sl.yml
Translation to Slovenian
2020-09-10 16:04:01 +02:00
Will Rossiter 4214c091e5
Merge pull request #168 from christopherdarling/patch-1
add .upgrade.yml for SilverStripe upgrader
2020-06-05 15:28:44 +12:00
Christopher Darling 799f669da2
add .upgrade.yml for SilverStripe upgrader 2020-06-03 15:58:13 +01:00
Will Rossiter 982d96c203
Merge pull request #167 from pavol-tk/patch-1
Use ClassName property instead of class
2020-06-03 08:36:14 +12:00
Pavol Tuka 56ef63f6c3
Use ClassName property instead of class
Property "class" does not exists. We should use ClassName.
2020-06-02 14:08:18 +02:00
Will Rossiter c1fc291e59
Remove explicit standard 2020-02-12 11:35:24 +13:00
Will Rossiter 36c9b9f1d4
Update .travis.yml 2020-02-12 11:31:38 +13:00
Will Rossiter 1397fa0e30
Update LICENSE 2020-02-12 11:10:45 +13:00
Will Rossiter 473b88ee05
Merge pull request #157 from smindel/issue-156-utilise-stream-context
FIX: enable stream context to pass through CWP egres proxy, fixes #156
2019-05-13 12:09:59 +12:00
Andreas Piening 2caacf8e86 FIX: enable stream context to pass through CWP egres proxy, fixes #156 2019-05-13 11:42:13 +12:00
Will Rossiter 7f8937dd1f
Remove link to helpful robot 2019-04-30 15:52:03 +12:00
Will Rossiter 764a8f5bb2
Merge pull request #153 from wilr/wilr-patch-1
Support falsey values in alter (Fixes #152)
2019-04-30 15:50:27 +12:00
Will Rossiter d8beb33d86
Merge pull request #154 from Taitava/finnish-translation
Create a Finnish translation
2019-04-30 15:46:31 +12:00
Will Rossiter 2860c390b0
If invokeWithExtensions returns an empty array, don't override 2019-04-30 15:45:39 +12:00
Jarkko Linnanvirta 41390002d5
Create a Finnish translation
If anyone want's to change/improve any of these translated terms (now or later), please feel free to do so. Not a big thing for me. :)
2019-03-30 16:54:05 +02:00
Will Rossiter 710b8c3518
Support falsey values in alter (Fixes #152) 2019-03-14 22:31:55 +13:00
Will Rossiter 4d131b06ac
Merge pull request #151 from andykarwal/issue-150
updated namespace on xml template
2018-09-25 13:25:46 +12:00
Andy Karwal f7b48e2b99 updated namespace on xml template 2018-09-25 13:20:49 +12:00
Will Rossiter 71c662e4d1
Add replace 2018-05-22 12:58:09 +12:00
Will Rossiter 64ead69f4e Set Content-Type for stylesheets 2018-03-22 07:52:04 +13:00
Will Rossiter 572da5fa8a Update DBDatetime API use. (Fixes #146) 2018-03-21 07:25:14 +13:00
Will Rossiter a0a8c34d2f Remove copy of xsl folder (Fixes #144) 2018-03-20 16:01:23 +13:00
Will Rossiter adb3ed94fc Correct path to stylesheet, update stylesheets for images 2018-03-20 09:45:38 +13:00
Daniel Hensby c3fcd84542
Merge branch '1' 2018-03-07 12:07:40 +00:00
Daniel Hensby 776e6bd9d9
Merge branch '1.6' into 1 2018-03-07 11:46:53 +00:00
Daniel Hensby 36091bc840
Merge branch '1.1' into 1.6 2018-03-07 11:45:24 +00:00
Daniel Hensby f343f312eb
Merge pull request #141 from dhensby/pulls/1.6/test-passing
Get travis passing
2018-03-07 11:42:57 +00:00
Daniel Hensby 6e49a3e01e
Merge branch '1.0' into 1.1 2018-03-07 11:36:15 +00:00
Daniel Hensby b42305dfef
Merge pull request #142 from dhensby/pulls/1.0/test-passing
get tests running in 1.0
2018-03-07 11:28:31 +00:00
Daniel Hensby 729311249c
get tests running 2018-03-07 11:23:19 +00:00
Daniel Hensby 326b1ce4b0
Get travis passing 2018-03-07 11:13:30 +00:00
Daniel Hensby 98f65de824
Fix linting issues 2018-03-07 10:59:58 +00:00
Will Rossiter 628dbd8404
Merge pull request #121 from timezoneone/J003064/images-for-xml-sitemaps
added a way to include any images displayed on the pages to the googl…
2018-03-07 08:02:49 +13:00
Nivanka Fonseka c5388b6e5b
typo and updated the if conditions 2018-03-06 17:54:15 +00:00
Nivanka Fonseka 29be1bc504
added a way to include any images displayed on the pages to the google site maps and updated the XSL to show them on the screen. 2018-03-06 17:48:03 +00:00
Will Rossiter c00f420a8b
Merge pull request #126 from danaenz/subsites-support
Add Subsite support
2018-03-06 11:48:33 +13:00
Will Rossiter 36ef636249
Merge pull request #140 from zanderwar/pulls/fixes/139
FIX missing timezone designator
2018-03-02 08:34:35 +13:00
zanderwar e51dd3877f FIX missing timezone designator 2018-03-01 19:51:05 +10:00
Daniel Hensby f51c9e12bf
Merge pull request #138 from creative-commoners/pulls/2.0/cldr-date
FIX Use CLDR date format in sitemap template
2018-02-15 20:18:39 +00:00
Daniel Hensby 64a9465d7e
Merge pull request #137 from creative-commoners/pulls/2.0/ss4-updates
FIX Update namespace for ErrorPage
2018-02-15 10:11:57 +00:00
Daniel Hensby bfaaa42e10
Merge pull request #136 from creative-commoners/pulls/2.0/phpcs-fix
FIX Add phpcs to dev requirements to fix CI builds
2018-02-15 10:04:26 +00:00
Robbie Averill d5b35cf36c FIX Use CLDR date format in sitemap template 2018-02-15 13:58:08 +13:00
Robbie Averill b11c6d771e Update URL in XSL references to module 2018-02-15 12:07:20 +13:00
Robbie Averill 450283bd1b FIX Apply PSR-2 linting ruleset and phpcs configuration 2018-02-15 12:07:20 +13:00
Robbie Averill 1d8c6faa2e FIX Update namespace for ErrorPage 2018-02-15 11:11:36 +13:00
Robbie Averill d1d5c2b268 FIX Add phpcs to dev requirements to fix CI builds 2018-02-15 11:04:19 +13:00
Will Rossiter 9e7aed0f2d Add phpcs to travis build 2018-01-18 18:40:29 +13:00
Will Rossiter 742724dad4 Fix unit tests 2018-01-18 17:00:56 +13:00
Will Rossiter 38c489c931
Remove object use (#133) 2018-01-16 08:01:25 +13:00
Will Rossiter 0daa163ec1
Add controller requirement (Fixes #128) 2017-11-29 21:38:33 +00:00
Will Rossiter c8e142718d Update tests 2017-10-20 11:16:53 +13:00
Will Rossiter 2699956b23 Vendorize and add namespaces 2017-10-20 09:05:18 +13:00
Danae Miller-Clendon 27898476d8 Add Subsite support by ensuring the host matches correctly to the current Subsite domain and protocol 2017-10-06 13:13:31 +13:00
Will Rossiter 71567fec58 Correct visibility 2017-09-15 07:36:11 +12:00
Will Rossiter d88ed57a07 Merge pull request #124 from icecaster/ss4
Date formatting broken in xml templates
2017-08-18 15:39:39 +12:00
tim 030dd83613 using SS4 date formatting http://userguide.icu-project.org/formatparse/datetime 2017-08-18 15:24:03 +12:00
Will Rossiter b40a66422b Update readme.md 2017-07-31 08:05:52 +12:00
Will Rossiter 313e4649c1 Remove Object reference 2017-06-02 12:26:56 +12:00
Will Rossiter 840f664cd9 Update namespace paths for versioned split 2017-05-11 06:56:53 +12:00
Will Rossiter 56b23dbdf8 Remove unneeded config flag 2017-05-10 07:40:17 +12:00
Will Rossiter 77f6a02ae7 Remove specific config flag
Fixes #120
2017-05-06 09:37:26 +12:00
Will Rossiter 526a0eec4d Merge pull request #110 from mikenz/patch-2
More Silverstripe 4 compatibility
2017-03-22 17:18:06 +13:00
Will Rossiter e0821f8f03 Remove requirements (see composer.json) Fixes #117 2017-01-24 17:16:24 +13:00
Will Rossiter 830abcc4f9 Merge pull request #115 from madmatt/patch-1
Update composer.json
2016-12-23 15:10:19 +13:00
Matt Peel fa92f0d13f Update composer.json
`master` branch of this module no longer works with SS3, due to the Director class being namespaced (so the URL rule no longer matches SS3 sites).
2016-12-23 12:08:14 +13:00
Will Rossiter 8307b56f3f Merge pull request #113 from digitall-it/master
Add italian translation
2016-11-16 15:20:50 +13:00
digitall-it d8f8cafd2d Add italian translation 2016-11-16 02:42:22 +01:00
Mike Cochrane b8766edbee More Silverstripe 4 compatibility 2016-11-06 16:28:05 +13:00
Mike Cochrane 9f845f5af7 More Silverstripe 4 compatibility 2016-11-06 16:24:03 +13:00
Mike Cochrane 4333bb6777 More Silverstrie 4 compatibility 2016-11-06 16:19:01 +13:00
Will Rossiter 51fa6f4c25 Merge pull request #109 from kinglozzer/4-compat
SilverStripe 4 compatibility
2016-11-05 19:02:26 +13:00
Loz Calver 671522de5f Remove PHP 7 from allowed failures 2016-11-04 15:53:23 +00:00
Loz Calver 286c79160a SilverStripe 4 compatibility 2016-11-03 12:34:32 +00:00
Will Rossiter 552684ce12 Allow 4.0 installations 2016-10-10 15:59:32 +13:00
Will Rossiter 930fd7f9ac Documentation for install (#108) 2016-09-27 06:43:00 +13:00
Will Rossiter e607ea9fc4 Add support for pinging bing (Fixes #22)
- Fix README install link
- Update documentation
2016-09-17 08:27:16 +12:00
Will Rossiter 31f9182e95 Update installer name 2016-09-12 12:10:42 +12:00
Will Rossiter 31a5bab455 Update build 2016-09-12 11:56:23 +12:00
Will Rossiter c031c58774 Update repo path 2016-09-12 11:50:42 +12:00
Will Rossiter 083251bd6d Update composer name 2016-09-12 11:47:25 +12:00
Will Rossiter bb7fbb8308 Merge pull request #106 from webbuilders-group/not-allowed-block
BUGFIX: Fixed issue where classes not in the sitemap would cause a crash
2016-06-30 08:46:15 +12:00
UndefinedOffset cc0c20b3a8 Changed is_registered to check both the current case and lower case version of the class
Fixed test failure resulting from GoogleSitemapRoute
2016-06-29 14:49:06 -03:00
UndefinedOffset 26838c70d0 BUGFIX: Fixed issue where classes not in the sitemap would cause a crash 2016-06-29 13:58:39 -03:00
Will Rossiter 49053f7d8f Merge pull request #105 from flamytwista/patch-1
Russian Translation
2016-06-24 11:12:36 +12:00
Taras Yemtsov 02197a35b0 Russian Translation 2016-06-24 00:01:32 +03:00
Will Rossiter 11a894a10e Merge pull request #103 from mikenz/patch-1
Exclude all Redirector templates, not just RedirectorPage
2016-06-09 13:59:49 +12:00
Mike Cochrane 4701bd985a Exclude all Redirector templates, not just RedirectorPage
Classes that extend RedirectorPage are still included in the sitemap if exclude_redirector_pages is enabled. This change excludes all subclasses as well.
2016-06-09 13:55:43 +12:00
Will Rossiter 6fd2901aaf Merge pull request #100 from nikrolls/support-namespaced-dataobjects
Added support for namespaced data objects
2016-04-13 08:00:40 +12:00
Nik Rolls ecd3551ec0 Added support for namespaced data objects 2016-04-12 22:58:32 +12:00
Will Rossiter 356350ede5 Remove redirector pages (Fixes #43) 2016-03-04 16:40:30 +13:00
Will Rossiter 9f461b8d7c Include cms tests in travis (Fixes #87) 2016-03-04 16:29:34 +13:00
Will Rossiter 90ddb6c98f Merge pull request #98 from helpfulrobot/add-standard-scrutinizer-config
Added standard Scrutinizer config
2016-03-04 16:18:45 +13:00
Daniel Hensby b7da114f99 Merge pull request #99 from patricknelson/issue-78-invoke-byref-issue
FIX for #78 to workaround ->alterCanIncludeInGoogleSitemap() not working
2016-02-26 10:41:03 +00:00
Patrick Nelson 5228024ba5 FIX for #78 to workaround ->alterCanIncludeInGoogleSitemap() not working due to core bug with ->invokeWithExtensions() not allowing references. 2016-02-25 18:02:20 -05:00
helpfulrobot 7e42cbef76 Added standard Scrutinizer config 2016-02-17 05:13:44 +13:00
Will Rossiter 99282db32a Merge pull request #97 from helpfulrobot/add-standard-code-of-conduct-file
Added standard code of conduct file
2016-02-16 12:14:32 +13:00
helpfulrobot 9c4e8be4bd Added standard code of conduct file 2016-02-15 14:09:11 +13:00
Will Rossiter 9f6e1bab41 Merge pull request #95 from helpfulrobot/add-standard-gitattributes-file
Added standard .gitattributes file
2016-01-15 20:22:32 +13:00
helpfulrobot 0cfd3d80f5 Added standard .gitattributes file 2016-01-15 16:53:11 +13:00
Damian Mooyman 289ed77139 Merge pull request #94 from helpfulrobot/convert-to-psr-2
Converted to PSR-2
2015-12-18 10:02:57 +13:00
helpfulrobot 7af5077d99 Converted to PSR-2 2015-12-18 07:09:19 +13:00
Damian Mooyman 5d10786341 Merge pull request #93 from helpfulrobot/add-standard-editorconfig-file
Added standard .editorconfig file
2015-12-17 13:29:03 +13:00
helpfulrobot 41238f5186 Added standard .editorconfig file 2015-12-17 10:06:07 +13:00
Will Rossiter dd41e39484 Merge pull request #91 from patricknelson/master
FIX/NEW for #90 Allow ability to alter DataList and deprecate static interface for better extensibility.
2015-10-17 17:29:29 +13:00
Patrick Nelson a641ded926 FIX/NEW for #90 Extending alterDataList after both Versioned::get_by_stage() and also new DataList(). No list left behind! 2015-10-12 17:23:01 -04:00
Patrick Nelson e2a1dc5250 FIX/NEW for #90 Allow ability to alter DataList and deprecate static interface for better extensibility. 2015-10-12 17:08:07 -04:00
Will Rossiter c549ca4d3f Merge pull request #88 from silverstripe-rebelalliance/master
Use protocolless URL for external resource
2015-09-07 20:00:33 +12:00
Ed Linklater 4e1db4d3f9 Always use HTTPS for loading external resources 2015-08-26 14:58:46 +12:00
Ed Linklater 58376e277c Use protocolless URL for external resource so it loads if sitemap is requested via HTTPS 2015-08-26 14:16:42 +12:00
Will Rossiter a1ac8d229a Add installation note 2015-07-27 15:41:40 +12:00
Will Rossiter 82778473a8 Merge pull request #85 from dhensby/pulls/case-class
Allow URLs to be case insensitive
2015-07-27 15:21:04 +12:00
Daniel Hensby 0ffc6ac2be Allow URLs to be case insensitive 2015-07-27 00:46:49 +01:00
Damian Mooyman 26f1bbefdd Merge pull request #84 from dhensby/patch-1
Move to new travis containerised infrastructure
2015-07-22 09:05:37 +12:00
Daniel Hensby 40a3a0adeb Move to new travis containerised infrastructure 2015-07-21 11:01:25 +01:00
Will Rossiter f9202c28ad Merge pull request #82 from textagroup/ping_example
Updated out of date info on notifying Google
2015-04-28 11:06:07 +12:00
Kirk Mayo 3a3de779af Updated out of date info on notifying Google 2015-04-24 11:52:29 +12:00
Will Rossiter 092b2e10ae Merge pull request #80 from simonwelsh/patch-1
Null out the order by clause
2015-03-14 12:26:00 +13:00
Simon Welsh 91a543ea63 Null out the order by clause 2015-03-12 21:38:22 +11:00
Will Rossiter 6d530af1a2 Merge pull request #79 from uniun/lithuanian
Lithuanian translation
2015-01-19 21:48:47 +13:00
Elvinas L. 744d879efb Lithuanian translation 2015-01-16 10:27:52 +02:00
Will Rossiter d3a12fe225 Merge pull request #77 from 3Dgoo/feature/template-update-base-href
Replacing deprecated $BaseHref in templates
2014-11-11 16:46:45 +13:00
bob (Plastyk) 5c2547a3a3 Replacing deprecated $BaseHref in templates with $AbsoluteBaseURL. 2014-11-11 09:49:03 +10:30
Will Rossiter 05e62cbbc8 Merge pull request #74 from gherkins/patch-1
add X-Robots-Tag header
2014-10-21 21:27:53 +13:00
Max Girkens a87116b3c3 add X-Robots-Tag header
adding the X-Robots-Tag header might be a good thing, 
as it prevents the sitemap itself from getting indexed by google and showing up in the SERPs

see https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag
2014-10-20 16:43:01 +02:00
Will Rossiter 5f26b65061 Merge pull request #71 from nickspiel/patch-1
Added instructions to flush sitemap
2014-09-23 21:00:03 +12:00
Nick Spiel 096138d594 Added instructions to flush sitemap 2014-09-23 17:47:16 +10:00
Will Rossiter edfcd22021 Merge pull request #70 from nfauchelle/master
Fix issue with last mod timestamp of the sitemap index file
2014-09-02 20:17:46 +12:00
Nick 92bcb27cac Fix issue where the last mod timestamp of the sitemap index file wasnt using the most recently last edited timestamp, but the last item in the stack. Update test to check this, and include test for changeset bbd5e29 2014-09-02 00:35:23 +12:00
Will Rossiter e67f807f8a Merge pull request #69 from lerni/master
adjust css/js path in xml-sitemapindex.xsl
2014-08-19 17:20:40 +12:00
Lukas eeac25c93e ajust css/js path in xml-sitemapindex.xsl
matters if root is in a subdirectory
2014-08-18 22:30:28 +02:00
Sean Harvey 27e79a4e02 Merge pull request #68 from lerni/master
check is_numeric instead of is_float for $priority
2014-08-14 09:29:51 +12:00
Lukas bbd5e29630 check is_numeric instead of is_float for $priority
fix https://github.com/silverstripe-labs/silverstripe-googlesitemaps/issues/66#issuecomment-52112157
2014-08-13 23:27:34 +02:00
Will Rossiter 198a850f8d Remove deprecated methods 2014-07-18 10:16:18 +12:00
Will Rossiter 12e46e5b2b Remove translatable filter. 2014-07-18 10:15:57 +12:00
Will Rossiter 8ebf4e9f22 Merge pull request #56 from Focus-Flow/feature/styled-sitempaindex
Feature/styled sitempaindex
2014-07-17 10:18:31 +12:00
Will Rossiter 3b3742a1bc Merge pull request #63 from tractorcow/pulls/fix-unpublished-ancestry
BUG Fix issue with children of unpublished pages appearing in sitemap as apparent root pages
2014-01-27 20:41:57 -08:00
Damian Mooyman 5f06483b73 BUG Fix issue with children of unpublished pages appearing in sitemap as apparent root pages 2014-01-28 16:41:11 +13:00
Jyrki Lilja 708cb5d166 Add reference to xml-sitemapindex.xsl in GoogleSitemapController.ss 2013-11-08 10:37:50 +02:00
Jyrki Lilja ba69d670e5 Create xml-sitemapindex.xsl 2013-11-08 10:36:36 +02:00
Will Rossiter a2297c43ef Merge pull request #54 from ARNHOE/patch-1
Update Documentation
2013-08-27 22:51:17 -07:00
ARNHOE 3a1a99cbd5 Fixed issue with canView not being same as DataObject::canView 2013-08-27 23:02:21 +02:00
Will Rossiter 1416ad24ae Merge pull request #52 from TomSpeak/master
FIX Changing `alterCanIncludeInGoogleSitemap` to call owner and extensions
2013-08-27 13:46:35 -07:00
Thomas Speak e385b2f80c FIX Changing `alterCanIncludeInGoogleSitemap` to call owner object and extensions
At the moment `alterCanIncludeInGoogleSitemap` is only called on other extensions, but really we'd want to see this called on the owner object and its extensions.

Fixed to use `invokeWithExtensions` rather than `extend`
2013-08-27 15:32:52 +01:00
Thomas Speak 9dcc00bafd FIX: missing owner call 2013-08-24 10:27:33 +12:00
Will Rossiter 8403cb5773 Merge pull request #51 from TomSpeak/patch-2
FIX: canIncludeGoogleSitemap missing owner call
2013-08-23 14:27:46 -07:00
Thomas Speak b0cc13c9ed [FIX] canIncludeGoogleSitemap
Added $this->owner
2013-08-23 18:37:20 +01:00
Ingo Schommer 7ba2e67a17 Merge pull request #48 from kinglozzer/patch-1
Change dependency versions to work for 3.1.0-rc1
2013-08-14 02:51:49 -07:00
Loz Calver 48b5c0db70 Change dependency versions to work for 3.1.0-rc1 2013-08-14 10:48:40 +01:00
Will Rossiter affee1788f Merge pull request #45 from g4b0/priority_locale
Priority locale
2013-07-18 00:51:10 -07:00
g4b0 9b7c14abc4 Bugfix: forced locale en_US for priority, since on some locale (eg. Italian) the separator is a comma. 2013-07-18 09:23:53 +02:00
Will Rossiter 92615299b7 Merge pull request #41 from Mark-M/patch-1
FIX Use new config API
2013-05-31 15:03:34 -07:00
Mark-M cc215ace29 FIX Using new config API
Used the new config API to  turn off source file comments. This stops deprecation notices being thrown
2013-05-31 15:33:38 +02:00
Ingo Schommer a233c2d514 Fixed composer constraints 2013-05-07 14:05:42 +03:00
Will Rossiter eadc960341 Merge pull request #37 from tractorcow/3.1-static-update
API 3.1 compatibility: Static configurable properties must be private and immutable.
2013-04-02 01:28:56 -07:00
Damian Mooyman 1f7c2e7b73 API 3.1 compatibility: Static configurable properties must be private and immutable.
Static configurable properties (db, allowed_actions, etc) are now private. See: http://doc.silverstripe.org/framework/en/3.1/changelogs/3.1.0#static-properties-are-immutable-and-private-you-must-use-config-api
2013-04-02 11:57:42 +13:00
Ingo Schommer 0b832f968e Travis support 2013-03-29 10:12:45 +01:00
Ingo Schommer 9a95e58d6e Travis support 2013-03-29 10:12:03 +01:00
Ingo Schommer ac1e61faae Fixed composer version constraint 2013-03-26 10:49:40 +01:00
Will Rossiter c6119d3de9 Upgrades for 3.1 compatibility 2013-03-26 22:30:38 +13:00
Will Rossiter c69a4a58ae 1.1 branch for 3.0 2013-03-26 22:25:50 +13:00
Will Rossiter a015e227d9 Merge pull request #34 from ryanwachtl/patch-1
Update index.md
2013-03-08 13:02:43 -08:00
Ryan Wachtl 7c3bde4d9f Update index.md
`GoogleSitemap::register()` should be `GoogleSitemap::register_dataobject()`
2013-03-08 14:44:14 -06:00
Julian Seidenberg de283133fe Merge pull request #33 from ARNHOE/patch-1
added nl.yml language
2013-01-29 14:17:10 -08:00
ARNHOE 37e8101c04 added nl.yml language 2013-01-29 03:07:12 -08:00
Will Rossiter d42fba7906 Add extension hook points. 2013-01-18 10:51:07 +13:00
Will Rossiter 1c5e737d06 API register_routes() for registering custom paths.
This replaces the existing updateItems() + extension hook in favour of consistency with the register_dataobject() interface.

Note that this will likely be superseded by something to do with the Config API in the future.
2013-01-18 10:51:06 +13:00
Ingo Schommer 747059b6b7 Fixed enabled() usage (regression from last commit) 2013-01-17 17:20:50 +01:00
Will Rossiter 8bbc14ee23 API Implement sitemap.xml partitioning (Fixes #9)
Misc upgrade of module code so that site map.xml provides a index site map file based on the standards. Moved configuration vars to the Config API.

Considering how large a change this is, I've branched a 1.0 release off in github.
2013-01-15 22:29:59 +13:00
Ingo Schommer 90f3c82660 Merge branch 'master' into upstream-master (big-o team branch) 2013-01-07 13:59:39 +01:00
Ingo Schommer d5f673272e Update composer.json 2013-01-03 22:22:33 +01:00
Will Rossiter 6cc800a0db Merge pull request #28 from hdrlab/patch-1
A fix so that non-indexed pages remain non-indexed
2012-12-18 23:26:06 -08:00
hdrlab 620e09bd7c A fix so that non-indexed pages remain non-indexed
Pages that were set to be "non-indexed" were being reset to their 
default priorities the next time that the page was saved. This fix
makes sure that this doesn't happen.
2012-12-19 20:23:20 +13:00
Will Rossiter 3785409b3d Add composer.json 2012-12-08 11:55:46 +13:00
Will Rossiter f41fd87624 FIX: fix change freq not showing up in form. (Fixes: #27) 2012-11-07 19:47:55 +13:00
Will Rossiter 706c06e8a0 Fix typo in doc. (Fixes #24) 2012-10-15 20:08:44 +13:00
Will Rossiter a20e84ca9b Merge pull request #21 from oddnoc/fsockopen-port-correction
Specify port 80 for connecting to Google
2012-09-27 19:40:06 -07:00
Fred Condo bc1f3b3a81 Specify port 80 for connecting to Google 2012-09-26 14:51:52 -04:00
Will Rossiter c9b4c3f88e Merge pull request #19 from oddnoc/http-sendrequest-fix
FIX: Replace HTTP::sendRequest call
2012-09-18 23:09:34 -07:00
Fred Condo 378258b0e3 FIX: Replace HTTP::sendRequest call
- HTTP::sendRequest() is gone from the HTTP class
- Substitute slightly simpler code in a protected self::send_ping() method
2012-09-18 10:01:02 -04:00
Will Rossiter 079b949bf2 FIX: resolve test errors 2012-09-06 22:20:44 +12:00
Hendrik Schaper 41ce3396fa NEW: xsl stylesheet 2012-09-06 21:59:25 +12:00
Will Rossiter 6ce0524d4c Add yaml language files 2012-09-06 21:57:43 +12:00
Andrew O'Neil 8d787c486e BUGFIX: Clear sitemap registration at the end of each test 2012-07-23 15:01:33 +12:00
Will Rossiter d67270fe7a FIX: remove typo 2012-07-09 16:38:30 +12:00
Will Rossiter 929e97accf API: Remove public access to frequency and priorities.
Cleaned up related functions and general spring clean of the module. Documented example of manually setting routes through an extension class.
2012-07-06 17:07:30 +12:00
Will Rossiter 5bc3e07be6 API: add updateItems hook 2012-06-30 21:42:50 +12:00
Will Rossiter df9b224937 FIX: skip unit tests and loading page objects if cms module is not installed. 2012-06-30 21:39:35 +12:00
Will Rossiter c5c92abad2 MINOR: moved director route to new config API 2012-06-10 21:02:29 +12:00
Will Rossiter a72dd1a094 Merge pull request #16 from joernroeder/master
ENHANCEMENT: Remove SiteTree dependencies.
2012-05-11 15:22:20 -07:00
Jörn Röder 8a41b6fdf2 check if SiteTree exists before adding extension 2012-05-11 10:53:46 +02:00
Jörn Röder e98598ac79 check if SiteTree exists before getting pages. 2012-05-11 10:51:06 +02:00
Will Rossiter 39828c215d Merge pull request #15 from UndefinedOffset/master
Compatability fix with SS 3.0 Beta 2
2012-04-20 21:01:26 -07:00
UndefinedOffset e5e2ab57f2 BUGFIX: Fixed "Strict Standards" error with SilverStripe beta 2 caused by extraStatics() declaration not matching DataExtension 2012-04-20 11:13:12 -03:00
Will Rossiter a813a471f9 MINOR: updated contact email 2012-03-26 22:57:39 +13:00
Will Rossiter 872fd9b258 BUGFIX: update unit tests for googlesitemap module for 3.0. MINOR: version bump in README 2012-03-26 22:50:30 +13:00
Will Rossiter 295aba26e6 Merge pull request #5 from UndefinedOffset/ss-3.0
API CHANGE: compatibility with SilverStripe 3.0. For SilverStripe 2.4 releases please use the 0.2 branch.
2012-03-26 02:39:40 -07:00
Ed 05d7bb8f82 Better compatibility with SilverStripe 3.0 2012-03-25 13:07:44 -03:00
Roland Lehmann e801e91636 ENHANCEMENT: Implemented a basic suite of unit tests. MINOR: fixed indentation in GoogleSiteMapDecorator. BUGFIX: fixed potential corrupt XML from invalid Priority fields. Fixed #4 2011-07-04 23:16:57 +12:00
Will Rossiter 65c59fc020 ENHANCEMENT: added ability to define a custom prority to dataobjects. 2011-06-13 11:16:07 +12:00
Roland Lehmann 805c7e2ee4 added some documentation; added test files, tests do not work yet 2011-06-12 16:14:29 +02:00
Roland Lehmann 74de83227a encapsulated calculation of a pages change frequency 2011-06-11 17:20:20 +02:00
Roland Lehmann a9019b9099 added some documentation 2011-06-11 16:38:31 +02:00
Roland Lehmann c841987427 The change frequency is now configurable via GoogleSitemap::register_dataobject() 2011-06-11 12:26:17 +02:00
Roland Lehmann 54929f3e50 merged pull manually 2011-06-11 11:49:35 +02:00
rlehmann 9c0d50e726 FEATURE: added ability to add google sitemap indexes for dataobjects. Added documentation and.
APICHANGE: moved GoogleSiteMapDecorator to GoogleSitemapSiteTreeDecorator to use the original for a hook for extending on googlesitemaps on dataobjects only.
2011-06-11 14:17:05 +12:00
Roland Lehmann 4ca8402508 googlesitemap.xml is now able to show any dataobject shown in a detail view in the frontend 2011-06-07 20:19:04 +02:00
Will Rossiter c62538a860 MINOR: updated documentation with content from wiki. Fixed syntax error in comments which threw apidocs off 2011-04-26 21:17:49 +12:00
Will Rossiter ef019199c4 Merged pull request #1 from Innovatif/master.
Removed unnecessary executable bit on files...
2011-04-26 02:10:06 -07:00
Blaž f1129a7d2d Removed executable bit. 2011-03-10 12:26:12 +01:00
wrossiter cb812b5c98 MINOR: removed old README file 2010-06-22 21:53:40 +00:00
wrossiter 8886348de0 BUGFIX: fixed notice level error. MINOR: updated readme to markdown format. 2010-06-22 21:49:25 +00:00
sharvey 20842ce5fe BUGFIX Fixed undefined class HTTPRequest, replacing it with SS_HTTPRequest as the class was renamed 2010-05-04 05:37:07 +00:00
mstephens 8d99fa75f2 BUGFIX: show in search filter not quoted for postgresql 2010-03-23 04:04:32 +00:00
sharvey e0222dc8b7 BUGFIX Removed backticks from GoogleSitemap 2010-03-09 01:02:03 +00:00
sharvey 33686e4d98 BUGFIX #5035 Fixed broken link to priorities help in Google Sitemap tab for each page in CMS, link now also opens in a new tab/window 2010-02-25 04:06:10 +00:00
mandrew a8d1e91ddf REVERT reverted back to origional SS_Datetime function 2010-02-02 22:28:11 +00:00
mandrew e9eecda415 BUGFIX updated google site map with correct SSDatetime function name 2010-02-02 22:09:18 +00:00
lhudson 7a4f572fbc BUGFIX: Remove ref to obsolete ContentNegotiator::disable. See sapphire r93682 2010-01-21 01:40:35 +00:00
50 changed files with 2206 additions and 318 deletions

17
.editorconfig Normal file
View File

@ -0,0 +1,17 @@
# For more information about the properties used in this file,
# please see the EditorConfig documentation:
# http://editorconfig.org
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[{*.yml,package.json}]
indent_size = 2
# The indent size used in the package.json file cannot be changed:
# https://github.com/npm/npm/pull/3180#issuecomment-16336516

4
.gitattributes vendored Normal file
View File

@ -0,0 +1,4 @@
/tests export-ignore
/docs export-ignore
/.gitignore export-ignore
/.travis.yml export-ignore

11
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,11 @@
name: CI
on:
push:
pull_request:
workflow_dispatch:
jobs:
ci:
name: CI
uses: silverstripe/gha-ci/.github/workflows/ci.yml@v1

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.DS_Store
/vendor
/public
/composer.lock

2
.upgrade.yml Normal file
View File

@ -0,0 +1,2 @@
mappings:
GoogleSitemap: Wilr\GoogleSitemaps\GoogleSitemap

View File

@ -1,5 +0,0 @@
0.1
- Initial Build
0.2
- Moved Decorator to a separate file

37
LICENSE
View File

@ -1,24 +1,13 @@
* Copyright (c) 2008, Silverstripe Ltd.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the <organization> nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY Silverstripe Ltd. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL Silverstripe Ltd. BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
BSD 3-Clause License
Copyright (c) 2020, Fullscreen Interactive All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

22
README
View File

@ -1,22 +0,0 @@
###############################################
Google Sitemaps Module
###############################################
Maintainer Contact
-----------------------------------------------
Will Rossiter
<will (at) silverstripe (dot) com>
Requirements
-----------------------------------------------
SilverStripe 2.4 or newer
Documentation
-----------------------------------------------
http://doc.silverstripe.com/doku.php?id=modules:googlesitemaps
Installation Instructions
-----------------------------------------------
Usage Overview
-----------------------------------------------

37
README.md Normal file
View File

@ -0,0 +1,37 @@
# Google Sitemaps Module
[![Build Status](http://img.shields.io/travis/wilr/silverstripe-googlesitemaps.svg?style=flat-square)](http://travis-ci.org/wilr/silverstripe-googlesitemaps)
[![Version](http://img.shields.io/packagist/v/wilr/silverstripe-googlesitemaps.svg?style=flat-square)](https://packagist.org/packages/wilr/silverstripe-googlesitemaps)
[![License](http://img.shields.io/packagist/l/wilr/silverstripe-googlesitemaps.svg?style=flat-square)](LICENSE.md)
## Maintainer Contact
- 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.
For Silverstripe 4 use the `2.x` branch line.
## Documentation
Provides support for the [Sitemaps XML Protocol](http://www.sitemaps.org/protocol.html),
enabling Google, Bing and other search engines to index the web pages on your
site. This helps your SilverStripe website rank well in search engines, and to
encourage the information on your site to be discovered by Google quickly.
Any new pages published or unpublished on your website automatically update the
Sitemap.
The XML Sitemap can be accessed by going to http://yoursite.com/sitemap.xml
## Usage Overview
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)

View File

@ -1,10 +1,10 @@
<?php
// adds a rule to make www.site.com/sitemap.xml work
Director::addRules(10, array(
'sitemap.xml' => 'GoogleSitemap',
));
use SilverStripe\Core\ClassInfo;
use Wilr\GoogleSitemaps\GoogleSitemap;
// add the extension
Object::add_extension('SiteTree', 'GoogleSitemapDecorator');
?>
if (0 === strpos(ltrim($_SERVER['REQUEST_URI'], '/'), 'sitemap')) {
foreach (ClassInfo::implementorsOf(Sitemapable::class) as $className) {
GoogleSitemap::register_dataobject($className);
}
}

View File

@ -0,0 +1,14 @@
---
Name: googlesitemaps
---
Wilr\GoogleSitemaps\GoogleSitemap:
enabled: true
objects_per_sitemap: 1000
use_show_in_search: true
---
Only:
classexists: SilverStripe\CMS\Model\SiteTree
---
SilverStripe\CMS\Model\SiteTree:
extensions:
- Wilr\GoogleSitemaps\Extensions\GoogleSitemapSiteTreeExtension

6
_config/routes.yml Normal file
View File

@ -0,0 +1,6 @@
---
Name: googlesitemaproutes
---
SilverStripe\Control\Director:
rules:
'sitemap.xml': 'Wilr\GoogleSitemaps\Control\GoogleSitemapController'

1
code-of-conduct.md Normal file
View File

@ -0,0 +1 @@
When having discussions about this module in issues or pull request please adhere to the [SilverStripe Community Code of Conduct](https://docs.silverstripe.org/en/contributing/code_of_conduct).

View File

@ -1,161 +0,0 @@
<?php
/**
* Initial implementation of Sitemap support.
* GoogleSitemap should handle requests to 'sitemap.xml'
* the other two classes are used to render the sitemap.
*
* You can notify ("ping") Google about a changed sitemap
* automatically whenever a new page is published or unpublished.
* 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):
* <example
* GoogleSitemap::enable_google_notificaton();
* </example>
*
* @see http://www.google.com/support/webmasters/bin/answer.py?hl=en&answer=34609
*
* @package googlesitemaps
*/
class GoogleSitemap extends Controller {
/**
* @var boolean
*/
protected static $enabled = true;
/**
* @var DataObjectSet
*/
protected $Pages;
/**
* @var boolean
*/
protected static $google_notification_enabled = false;
/**
* @var boolean
*/
protected static $use_show_in_search = true;
public function Items() {
$filter = '';
if(self::$use_show_in_search) {
$filter = '`ShowInSearch` = 1';
}
$this->Pages = Versioned::get_by_stage('SiteTree', 'Live', $filter);
$newPages = new DataObjectSet();
if($this->Pages) {
foreach($this->Pages as $page) {
// Only include pages from this host and pages which are not an instance of ErrorPage
// We prefix $_SERVER['HTTP_HOST'] with 'http://' so that parse_url to help parse_url identify the host name component; we could use another protocol (like
// 'ftp://' as the prefix and the code would work the same.
if(parse_url($page->AbsoluteLink(), PHP_URL_HOST) == parse_url('http://' . $_SERVER['HTTP_HOST'], PHP_URL_HOST) && !($page instanceof ErrorPage)) {
// If the page has been set to 0 priority, we set a flag so it won't be included
if($page->canView() && (!isset($page->Priority) || $page->Priority > 0)) {
// The one field that isn't easy to deal with in the template is
// Change frequency, so we set that here.
$properties = $page->toMap();
$created = new SS_Datetime();
$created->value = $properties['Created'];
$now = new SS_Datetime();
$now->value = date('Y-m-d H:i:s');
$versions = $properties['Version'];
$timediff = $now->format('U') - $created->format('U');
// Check how many revisions have been made over the lifetime of the
// Page for a rough estimate of it's changing frequency.
$period = $timediff / ($versions + 1);
if($period > 60*60*24*365) { // > 1 year
$page->ChangeFreq='yearly';
} elseif($period > 60*60*24*30) { // > ~1 month
$page->ChangeFreq='monthly';
} elseif($period > 60*60*24*7) { // > 1 week
$page->ChangeFreq='weekly';
} elseif($period > 60*60*24) { // > 1 day
$page->ChangeFreq='daily';
} elseif($period > 60*60) { // > 1 hour
$page->ChangeFreq='hourly';
} else { // < 1 hour
$page->ChangeFreq='always';
}
$newPages->push($page);
}
}
}
return $newPages;
}
}
/**
* Notifies Google about changes to your sitemap.
* Triggered automatically on every publish/unpublish of a page.
* This behaviour is disabled by default, enable with:
* GoogleSitemap::enable_google_notificaton();
*
* If the site is in "dev-mode", no ping will be sent regardless wether
* the Google notification is enabled.
*
* @return string Response text
*/
static function ping() {
if(!self::$enabled) return false;
//Don't ping if the site has disabled it, or if the site is in dev mode
if(!GoogleSitemap::$google_notification_enabled || Director::isDev())
return;
$location = urlencode(Director::absoluteBaseURL() . '/sitemap.xml');
$response = HTTP::sendRequest("www.google.com", "/webmasters/sitemaps/ping",
"sitemap=" . $location);
return $response;
}
/**
* Enable pings to google.com whenever sitemap changes.
*/
public static function enable_google_notification() {
self::$google_notification_enabled = true;
}
/**
* Disables pings to google when the sitemap changes.
*/
public static function disable_google_notification() {
self::$google_notification_enabled = false;
}
function index($url) {
if(self::$enabled) {
SSViewer::set_source_file_comments(false);
// We need to override the default content-type
ContentNegotiator::disable();
$this->getResponse()->addHeader('Content-Type', 'application/xml; charset="utf-8"');
// But we want to still render.
return array();
} else {
return new HTTPResponse('Not allowed', 405);
}
}
public static function enable() {
self::$enabled = true;
}
public static function disable() {
self::$enabled = false;
}
}

View File

@ -1,88 +0,0 @@
<?php
/**
* Decorate the page object to provide google sitemaps with
* additionally options and configuration.
*
* @package googlesitemaps
*/
class GoogleSitemapDecorator extends SiteTreeDecorator {
function extraStatics() {
return array(
'db' => array(
"Priority" => "Varchar(5)",
)
);
}
function updateCMSFields(&$fields) {
$pagePriorities = array(
'' => _t('SiteTree.PRIORITYAUTOSET','Auto-set based on page depth'),
'-1' => _t('SiteTree.PRIORITYNOTINDEXED', "Not indexed"), // We set this to -ve one because a blank value implies auto-generation of Priority
'1.0' => '1 - ' . _t('SiteTree.PRIORITYMOSTIMPORTANT', "Most important"),
'0.9' => '2',
'0.8' => '3',
'0.7' => '4',
'0.6' => '5',
'0.5' => '6',
'0.4' => '7',
'0.3' => '8',
'0.2' => '9',
'0.1' => '10 - ' . _t('SiteTree.PRIORITYLEASTIMPORTANT', "Least important")
);
$tabset = $fields->findOrMakeTab('Root.Content');
$tabset->push(
$addTab = new Tab(
'GoogleSitemap',
_t('SiteTree.TABGOOGLESITEMAP', 'Google Sitemap'),
new LiteralField(
"GoogleSitemapIntro",
"<p>" .
sprintf(
_t(
'SiteTree.METANOTEPRIORITY',
"Manually specify a Google Sitemaps priority for this page (%s)"
),
'<a href="https://www.google.com/webmasters/tools/docs/en/protocol.html#prioritydef">?</a>'
) .
"</p>"
),
new DropdownField("Priority", $this->owner->fieldLabel('Priority'), $pagePriorities)
)
);
}
function updateFieldLabels(&$labels) {
parent::updateFieldLabels($labels);
$labels['Priority'] = _t('SiteTree.METAPAGEPRIO', "Page Priority");
}
function onAfterPublish() {
GoogleSitemap::ping();
}
function onAfterUnpublish() {
GoogleSitemap::ping();
}
/**
* The default value of the priority field depends on the depth of the page in
* the site tree, so it must be calculated dynamically.
*/
function getPriority() {
if(!$this->owner->getField('Priority')) {
$parentStack = $this->owner->parentStack();
$numParents = is_array($parentStack) ? count($parentStack) - 1: 0;
return max(0.1, 1.0 - ($numParents / 10));
} elseif($this->owner->getField('Priority') == -1) {
return 0;
} else {
return $this->owner->getField('Priority');
}
}
}
?>

53
composer.json Normal file
View File

@ -0,0 +1,53 @@
{
"name": "wilr/silverstripe-googlesitemaps",
"description": "SilverStripe support for the Google Sitemaps XML, enabling Google and other search engines to see all urls on your site. This helps your SilverStripe website rank well in search engines, and to encourage the information on your site to be discovered quickly.",
"type": "silverstripe-vendormodule",
"keywords": [
"silverstripe",
"googlesitemaps",
"seo"
],
"homepage": "https://github.com/wilr/silverstripe-googlesitemaps",
"license": "BSD-3-Clause",
"authors": [
{
"name": "Will Rossiter",
"email": "will@fullscreen.io"
}
],
"require": {
"php": "^8.1",
"silverstripe/framework": "^5"
},
"require-dev": {
"phpunit/phpunit": "^9.5",
"squizlabs/php_codesniffer": "^3"
},
"replace": {
"silverstripe/googlesitemaps": "*"
},
"extra": {
"branch-alias": {
"dev-main": "3.x-dev"
},
"expose": [
"images",
"css"
]
},
"autoload": {
"psr-4": {
"Wilr\\GoogleSitemaps\\": "src/",
"Wilr\\GoogleSitemaps\\Tests\\": "tests/"
}
},
"prefer-stable": true,
"minimum-stability": "dev",
"config": {
"allow-plugins": {
"composer/installers": true,
"silverstripe/vendor-plugin": true,
"silverstripe/recipe-plugin": true
}
}
}

145
css/style.css Normal file
View File

@ -0,0 +1,145 @@
:root {
--color-black: #292B2E;
--color-blue-bright: #005AE1;
--color-blue-dark: #003E94;
--color-blue-light: #E6EDF4;
--color-grey-dark: #595D64;
--color-grey-light: #f7f8f8;
--color-grey-medium: #DCDEE0;
--color-white: #fff;
}
* {
margin: 0;
padding: 0;
}
html {
font-size: 10px;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
}
body, table {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
font-size: 1.4rem;
font-weight: 400;
line-height: 1.4;
}
body {
background: var(--color-white);
color: var(--color-grey-dark);
}
table {
border-collapse: collapse;
color: var(--color-black);
text-align: left;
width: 100%;
}
a {
color: inherit;
text-decoration: none;
}
.content {
margin: 0 auto;
max-width: 122rem;
padding: 2rem;
}
.content__title {
align-items: center;
color: var(--color-black);
display: inline-flex;
font-size: 2rem;
font-weight: 600;
margin: 0 0 3rem;
}
.content__title::before {
content: "";
background: url("../images/cms_logo.svg") left center no-repeat;
border-right: 0.1rem solid var(--color-grey-medium);
display: inline-block;
height: 3.2rem;
margin-right: 1.5rem;
padding-right: 1.5rem;
width: 3.2rem;
}
.content__text {
margin: 1rem 0;
}
.content__text a {
color: var(--color-blue-bright);
text-decoration: underline;
}
.content__text a:hover {
color: var(--color-blue-dark);
text-decoration: none;
}
.table-wrapper {
border-top: 0.1rem solid var(--color-grey-medium);
margin: 3rem 0 5rem;
overflow-x: auto;
}
.table__cell {
padding: 1rem;
}
.table__cell--w-10 {
width: 10%;
}
.table__cell--w-15 {
width: 15%;
}
.table__cell--w-65 {
width: 65%;
}
.table__cell--w-85 {
width: 85%;
}
.table tbody tr:nth-child(odd) td {
background-color: var(--color-grey-light);
}
.table tbody tr:hover td {
background-color: var(--color-blue-light);
}
.table a:hover {
text-decoration: underline;
}
.image-table__cell {
padding: 1rem 1rem 0 0;
}
.image-table__cell--image {
width: 6rem;
}
.image-table__cell--text {
width: calc(100% - 6rem);
}
.image-table__image {
height: 4rem;
object-fit: cover;
width: 6rem;
}
.image-table__title {
display: block;
}

0
docs/_manifest_exclude Normal file
View File

144
docs/en/index.md Normal file
View File

@ -0,0 +1,144 @@
# Google Sitemaps Module
Silverstripe CMS provides support for the Google Sitemaps XML system, enabling
Google and other search engines to see all pages on your site. This helps
your Silverstripe CMS website rank well in search engines, and to encourage the
information on your site to be discovered by Google quickly.
Therefore, all Silverstripe CMS websites contain a special controller which can
be visited: http://yoursite.com/sitemap.xml. This is not a file directly, but
rather a custom route which points to the GoogleSitemap controller.
See http://en.wikipedia.org/wiki/Sitemaps for info on the Google Sitemap
format.
Whenever you publish a new or republish an existing page, Silverstripe CMS can
automatically inform Google of the change, encouraging a Google to take notice.
If you install the Silverstripe CMS Google Analytics module, you can see if Google
has updated your page as a result.
By default, Silverstripe CMS informs Google that the importance of a page depends
on its position of in the sitemap. "Top level" pages are most important, and
the deeper a page is nested, the less important it is. (For each level,
Importance drops from 1.0, to 0.9, to 0.8, and so on, until 0.1 is reached).
In the CMS, in the Settings tab for each page, you can set the importance
manually, including requesting to have the page excluded from the sitemap.
## Configuration
Most module configuration is done via the Silverstripe CMS Config API. Create a
new config file `mysite/_config/googlesitemaps.yml` with the following outline:
```yml
---
Name: customgooglesitemaps
After: googlesitemaps
---
Wilr\GoogleSitemaps\GoogleSitemap:
enabled: true
objects_per_sitemap: 1000
use_show_in_search: true
```
You can now alter any of those properties to set your needs.
```yml
---
Name: customgooglesitemaps
After: googlesitemaps
---
Wilr\GoogleSitemaps\GoogleSitemap:
enabled: true
objects_per_sitemap: 1000
use_show_in_search: true
```
### Including DataObjects
The module provides support for including DataObject subclasses as pages in the
SiteTree such as comments, forum posts and other pages which are stored in your
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.
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
use SilverStripe\ORM\DataObject;
use SilverStripe\Control\Director;
class MyDataObject extends DataObject {
function canView($member = null) {
return true;
}
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.
use Wilr\GoogleSitemaps\GoogleSitemap;
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
instead of the previous code you would write:
use Wilr\GoogleSitemaps\GoogleSitemap;
GoogleSitemap::register_dataobject('MyDataObject', 'daily');
See the following blog post for more information:
http://www.silvercart.org/blog/dataobjects-and-googlesitemaps/
### Including custom routes
Occasionally you may have a need to include custom URLs in your sitemap for
your Controllers and other pages which don't exist in the database. To update
the sitemap to include those links call register_routes() with your array of
URLs to include.
use Wilr\GoogleSitemaps\GoogleSitemap;
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()
{
// ..
}
}
```

4
images/cms_logo.svg Normal file
View File

@ -0,0 +1,4 @@
<svg width="32" height="19" viewBox="0 0 32 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.832 1.85439L9.09495 6.56978C8.16307 7.22084 7.93658 8.50206 8.58764 9.43478C9.2387 10.3667 10.5199 10.5931 11.4526 9.94209L18.1897 5.2267C20.9787 3.27352 24.8307 3.95216 26.7772 6.74111C28.7304 9.53005 28.0518 13.3821 25.2628 15.3286L23.145 16.8087C24.8993 19.7899 28.8675 18.5354 30.6702 15.3353C32.5549 11.9905 32.4863 7.72146 30.1428 4.37673C26.8926 -0.263432 20.4714 -1.39422 15.832 1.85439Z" fill="#005AE1"/>
<path d="M16.1671 16.6776L22.9042 11.9622C23.836 11.3112 24.0625 10.03 23.4115 9.09724C22.7604 8.16536 21.4792 7.93887 20.5465 8.58993L13.8094 13.3053C11.0204 15.2585 7.1684 14.5799 5.22191 11.7909C3.26873 9.00196 3.94737 5.14992 6.73631 3.20343L8.85414 1.72329C7.1007 -1.25705 3.1325 -0.00256691 1.32975 3.19758C-0.554895 6.54231 -0.486362 10.8114 1.85712 14.1561C5.10574 18.7955 11.5269 19.9338 16.1671 16.6785V16.6776Z" fill="#005AE1"/>
</svg>

After

Width:  |  Height:  |  Size: 970 B

0
lang/_manifest_exclude Normal file
View File

9
lang/de.yml Normal file
View File

@ -0,0 +1,9 @@
de:
GoogleSitemaps:
METANOTEPRIORITY: 'Manuelle Google Sitemaps-Priorität für diese Seite (%s)'
METAPAGEPRIO: 'Page Priorität'
PRIORITYAUTOSET: 'Automatisch basierend auf Verschachtelung im Seitenbaum'
PRIORITYLEASTIMPORTANT: 'Am wenigsten wichtig'
PRIORITYMOSTIMPORTANT: 'Am wichtigsten'
PRIORITYNOTINDEXED: 'Nicht indexiert'
TABGOOGLESITEMAP: 'Google Sitemap'

9
lang/en.yml Normal file
View File

@ -0,0 +1,9 @@
en:
GoogleSitemaps:
METANOTEPRIORITY: 'Manually specify a Google Sitemaps priority for this page (%s)'
METAPAGEPRIO: 'Page Priority'
PRIORITYAUTOSET: 'Auto-set based on page depth'
PRIORITYLEASTIMPORTANT: 'Least important'
PRIORITYMOSTIMPORTANT: 'Most important'
PRIORITYNOTINDEXED: 'Not indexed'
TABGOOGLESITEMAP: 'Google Sitemap'

9
lang/fi.yml Normal file
View File

@ -0,0 +1,9 @@
fi:
GoogleSitemaps:
METANOTEPRIORITY: 'Määritä Google Sitemaps -prioriteetti tälle sivulle (%s)'
METAPAGEPRIO: 'Sivun prioriteetti'
PRIORITYAUTOSET: 'Päättele automaattisesti valikkorakenteen mukaan'
PRIORITYLEASTIMPORTANT: 'Vähiten tärkein'
PRIORITYMOSTIMPORTANT: 'Kaikista tärkein'
PRIORITYNOTINDEXED: 'Pyydä olemaan näyttämättä hakukoneissa'
TABGOOGLESITEMAP: 'Google Sitemap'

9
lang/it.yml Normal file
View File

@ -0,0 +1,9 @@
it:
GoogleSitemaps:
METANOTEPRIORITY: 'Specifica manualmente una priorità Sitemaps di Google per questa pagina (%s)'
METAPAGEPRIO: 'Priorità Pagina'
PRIORITYAUTOSET: 'Imposta automaticamente in base alla profondità della pagina'
PRIORITYLEASTIMPORTANT: 'Meno importante'
PRIORITYMOSTIMPORTANT: 'Più importante'
PRIORITYNOTINDEXED: 'Non indicizzato'
TABGOOGLESITEMAP: 'Sitemap di Google'

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'

9
lang/nl.yml Normal file
View File

@ -0,0 +1,9 @@
nl:
GoogleSitemaps:
METANOTEPRIORITY: 'Handmatig de prioriteit van Google Sitemaps aanpassen voor deze pagina (%s)'
METAPAGEPRIO: 'Pagina prioriteit'
PRIORITYAUTOSET: 'Automatisch de pagina diepte maken'
PRIORITYLEASTIMPORTANT: 'Minst belangrijke'
PRIORITYMOSTIMPORTANT: 'Meest belangrijke'
PRIORITYNOTINDEXED: 'niet geïndexeerd'
TABGOOGLESITEMAP: 'Google Sitemap'

10
lang/ru.yml Normal file
View File

@ -0,0 +1,10 @@
ru:
GoogleSitemaps:
METANOTEPRIORITY: 'Установить вручную Google Sitemaps приоритет для этой страшицы (%s)'
METAPAGEPRIO: 'Приоритет страницы'
PRIORITYAUTOSET: 'Автоматичисеки на основании глубины просмотра'
PRIORITYLEASTIMPORTANT: 'Наименее важная'
PRIORITYMOSTIMPORTANT: 'Наиболее Важная'
PRIORITYNOTINDEXED: 'Не индексировать'
TABGOOGLESITEMAP: 'Google Sitemap'

9
lang/sl.yml Normal file
View File

@ -0,0 +1,9 @@
sl:
GoogleSitemaps:
METANOTEPRIORITY: 'Ročno nastavite (Google Sitemaps priority) pomembnost trenutne strani (%s)'
METAPAGEPRIO: 'Pomembnost strani'
PRIORITYAUTOSET: 'Samodejno glede na nivo v strukturi'
PRIORITYLEASTIMPORTANT: 'Manj pomembna'
PRIORITYMOSTIMPORTANT: 'Bolj pomembna'
PRIORITYNOTINDEXED: 'Ne indeksiraj'
TABGOOGLESITEMAP: 'Google Sitemap'

11
phpcs.xml.dist Normal file
View File

@ -0,0 +1,11 @@
<?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">
<!-- Current exclusions -->
<exclude name="PSR1.Methods.CamelCapsMethodName" />
</rule>
</ruleset>

14
phpunit.xml.dist Normal file
View File

@ -0,0 +1,14 @@
<?xml version="1.0"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" bootstrap="vendor/silverstripe/framework/tests/bootstrap.php" colors="true" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
<coverage includeUncoveredFiles="true">
<include>
<directory suffix=".php">src/</directory>
</include>
<exclude>
<directory suffix=".php">tests/</directory>
</exclude>
</coverage>
<testsuite name="Default">
<directory>tests</directory>
</testsuite>
</phpunit>

View File

@ -0,0 +1,137 @@
<?php
namespace Wilr\GoogleSitemaps\Control;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Control\Controller;
use SilverStripe\Control\Director;
use SilverStripe\Control\HTTPResponse;
use Wilr\GoogleSitemaps\GoogleSitemap;
use SilverStripe\View\ArrayData;
/**
* Controller for displaying the sitemap.xml. The module displays an index
* sitemap at the sitemap.xml level, then outputs the individual objects
* at a second level.
*
* <code>
* http://site.com/sitemap.xml/
* http://site.com/sitemap.xml/sitemap/$ClassName-$Page.xml
* </code>
*
* @package googlesitemaps
*/
class GoogleSitemapController extends Controller
{
/**
* @var array
*/
private static $allowed_actions = [
'index',
'sitemap',
'styleSheetIndex',
'styleSheet'
];
/**
* Default controller action for the sitemap.xml file. Renders a index
* file containing a list of links to sub sitemaps containing the data.
*
* @return mixed
*/
public function index($url)
{
if (GoogleSitemap::enabled()) {
$this->getResponse()->addHeader('Content-Type', 'application/xml; charset="utf-8"');
$this->getResponse()->addHeader('X-Robots-Tag', 'noindex');
$sitemaps = GoogleSitemap::inst()->getSitemaps();
$this->extend('updateGoogleSitemaps', $sitemaps);
return $this->customise(new ArrayData([
'Sitemaps' => $sitemaps,
]))->renderWith(__CLASS__);
} else {
return new HTTPResponse('Page not found', 404);
}
}
/**
* Specific controller action for displaying a particular list of links
* for a class
*
* @return mixed
*/
public function sitemap()
{
$class = $this->unsanitiseClassName($this->request->param('ID'));
$page = intval($this->request->param('OtherID'));
if ($page) {
if (!is_numeric($page)) {
return new HTTPResponse('Page not found', 404);
}
}
if (GoogleSitemap::enabled()
&& $class
&& ($page > 0)
&& ($class == SiteTree::class || $class == 'GoogleSitemapRoute' || GoogleSitemap::is_registered($class))
) {
$this->getResponse()->addHeader('Content-Type', 'application/xml; charset="utf-8"');
$this->getResponse()->addHeader('X-Robots-Tag', 'noindex');
$items = GoogleSitemap::inst()->getItems($class, $page);
$this->extend('updateGoogleSitemapItems', $items, $class, $page);
return array(
'Items' => $items
);
}
return new HTTPResponse('Page not found', 404);
}
/**
* Unsanitise a namespaced class' name from a URL param
* @return string
*/
protected function unsanitiseClassName($class)
{
return str_replace('-', '\\', (string) $class);
}
/**
* Render the stylesheet for the sitemap index
*
* @return DBHTMLText
*/
public function styleSheetIndex()
{
$html = $this->renderWith('xml-sitemapindex');
$this->getResponse()->addHeader('Content-Type', 'text/xsl; charset="utf-8"');
return $html;
}
/**
* Render the stylesheet for the sitemap
*
* @return DBHTMLText
*/
public function styleSheet()
{
$html = $this->renderWith('xml-sitemap');
$this->getResponse()->addHeader('Content-Type', 'text/xsl; charset="utf-8"');
return $html;
}
public function AbsoluteLink($action = null)
{
return rtrim(Controller::join_links(Director::absoluteBaseURL(), 'sitemap.xml', $action), '/');
}
}

View File

@ -0,0 +1,146 @@
<?php
namespace Wilr\GoogleSitemaps\Extensions;
use SilverStripe\Control\Director;
use SilverStripe\ORM\DataExtension;
use SilverStripe\ORM\FieldType\DBDatetime;
use SilverStripe\Subsites\Model\Subsite;
use Wilr\GoogleSitemaps\GoogleSitemap;
/**
* Decorate the page object to provide google sitemaps with additional options
* and configuration.
*/
class GoogleSitemapExtension extends DataExtension
{
/**
* @return boolean
*/
public function canIncludeInGoogleSitemap()
{
$can = true;
if ($this->owner->hasMethod('AbsoluteLink')) {
$hostHttp = parse_url(Director::protocolAndHost(), PHP_URL_HOST);
// Subsite support
if (class_exists(Subsite::class)) {
// Subsite will have a different domain from Director::protocolAndHost
if ($subsite = Subsite::currentSubsite()) {
$hostHttp = parse_url(Director::protocol() . $subsite->getPrimaryDomain(), PHP_URL_HOST);
}
}
$objHttp = parse_url($this->owner->AbsoluteLink() ?? '', PHP_URL_HOST);
if ($objHttp != $hostHttp) {
$can = false;
}
}
if ($can) {
$can = $this->owner->canView();
}
if ($can) {
$can = ($this->owner->getGooglePriority() !== false);
}
if ($can === false) {
return false;
}
// Allow override. invokeWithExtensions will either return a single result (true|false) if defined on the object
// or an array if on extensions.
$override = $this->owner->invokeWithExtensions('alterCanIncludeInGoogleSitemap', $can);
if ($override !== null) {
if (is_array($override)) {
if (!empty($override)) {
$can = min($override, $can);
}
} else {
$can = $override;
}
}
if (is_array($can) && isset($can[0])) {
return $can[0];
}
return $can;
}
/**
* The default value of the priority field depends on the depth of the page in
* the site tree, so it must be calculated dynamically.
*
* @return mixed
*/
public function getGooglePriority()
{
$field = $this->owner->hasField('Priority');
if ($field) {
$priority = $this->owner->getField('Priority');
return ($priority < 0) ? false : $priority;
}
return GoogleSitemap::get_priority_for_class($this->owner->ClassName);
}
/**
* Returns a pages change frequency calculated by pages age and number of
* versions. Google expects always, hourly, daily, weekly, monthly, yearly
* or never as values.
*
* @see http://support.google.com/webmasters/bin/answer.py?hl=en&answer=183668&topic=8476&ctx=topic
*
* @return DBDatetime
*/
public function getChangeFrequency()
{
if ($freq = GoogleSitemap::get_frequency_for_class($this->owner->ClassName)) {
return $freq;
}
$date = date('Y-m-d H:i:s');
$created = new DBDatetime();
$created->value = ($this->owner->Created) ? $this->owner->Created : $date;
$now = new DBDatetime();
$now->value = $date;
$versions = ($this->owner->Version) ? $this->owner->Version : 1;
if ($now && $created) {
$timediff = $now->getTimestamp() - $created->getTimestamp();
// Check how many revisions have been made over the lifetime of the
// Page for a rough estimate of it's changing frequency.
$period = $timediff / ($versions + 1);
} else {
$period = 0;
}
if ($period > 60 * 60 * 24 * 365) {
$freq = 'yearly';
} elseif ($period > 60 * 60 * 24 * 30) {
$freq = 'monthly';
} elseif ($period > 60 * 60 * 24 * 7) {
$freq = 'weekly';
} elseif ($period > 60 * 60 * 24) {
$freq = 'daily';
} elseif ($period > 60 * 60) {
$freq = 'hourly';
} else {
$freq = 'always';
}
return $freq;
}
}

View File

@ -0,0 +1,218 @@
<?php
namespace Wilr\GoogleSitemaps\Extensions;
use SilverStripe\Assets\Image;
use SilverStripe\ErrorPage\ErrorPage;
use SilverStripe\Forms\DropdownField;
use SilverStripe\Forms\LiteralField;
use SilverStripe\Forms\Tab;
use SilverStripe\ORM\ArrayList;
use Throwable;
class GoogleSitemapSiteTreeExtension extends GoogleSitemapExtension
{
private static $db = [
"Priority" => "Varchar(5)"
];
public function updateSettingsFields(&$fields)
{
$prorities = array(
'-1' => _t('GoogleSitemaps.PRIORITYNOTINDEXED', "Not indexed"),
'1.0' => '1 - ' . _t('GoogleSitemaps.PRIORITYMOSTIMPORTANT', "Most important"),
'0.9' => '2',
'0.8' => '3',
'0.7' => '4',
'0.6' => '5',
'0.5' => '6',
'0.4' => '7',
'0.3' => '8',
'0.2' => '9',
'0.1' => '10 - ' . _t('GoogleSitemaps.PRIORITYLEASTIMPORTANT', "Least important")
);
$tabset = $fields->findOrMakeTab('Root.Settings');
$message = "<p>";
$message .= sprintf(
_t(
'GoogleSitemaps.METANOTEPRIORITY',
"Manually specify a Google Sitemaps priority for this page (%s)"
),
'<a href="http://www.google.com/support/webmasters/bin/answer.py?hl=en&answer=71936#prioritize" '
. 'target="_blank">?</a>'
);
$message .= "</p>";
$tabset->push(new Tab(
'GoogleSitemap',
_t('GoogleSitemaps.TABGOOGLESITEMAP', 'Google Sitemap'),
LiteralField::create("GoogleSitemapIntro", $message),
$priority = DropdownField::create(
"Priority",
$this->owner->fieldLabel('Priority'),
$prorities,
$this->owner->Priority
)
));
$priority->setEmptyString(_t('GoogleSitemaps.PRIORITYAUTOSET', 'Auto-set based on page depth'));
}
public function updateFieldLabels(&$labels)
{
parent::updateFieldLabels($labels);
$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->owner instanceof ErrorPage) ? false : $result;
if (is_array($result) && isset($result[0])) {
return $result[0];
}
return $result;
}
/**
* @return mixed
*/
public function getGooglePriority()
{
setlocale(LC_ALL, "en_US.UTF8");
$priority = $this->owner->getField('Priority');
if (!$priority) {
$parentStack = $this->owner->getAncestors();
$numParents = $parentStack->count();
$num = max(0.1, 1.0 - ($numParents / 10));
$result = str_replace(",", ".", $num);
return $result;
} elseif ($priority == -1) {
return false;
} else {
return (is_numeric($priority) && $priority <= 1.0) ? $priority : 0.5;
}
}
public function ImagesForSitemap()
{
$list = new ArrayList();
$cachedImages = [];
foreach ($this->owner->hasOne() as $field => $type) {
if (strpos($type, '.') !== false) {
$type = explode('.', $type)[0];
}
if (singleton($type) instanceof Image) {
$image = $this->owner->getComponent($field);
try {
if ($image && $image->exists() && !isset($cachedImages[$image->ID])) {
$cachedImages[$image->ID] = true;
$list->push($image);
}
} catch (Throwable $e) {
//
}
}
}
foreach ($this->owner->hasMany() as $field => $type) {
if (singleton($type) instanceof Image) {
$images = $this->owner->getComponents($field);
foreach ($images as $image) {
try {
if ($image && $image->exists() && !isset($cachedImages[$image->ID])) {
$cachedImages[$image->ID] = true;
$list->push($image);
}
} catch (Throwable $e) {
//
}
}
}
}
foreach ($this->owner->manyMany() as $field => $type) {
$image = false;
if (is_array($type) && isset($type['through'])) {
if (singleton($type['through']) instanceof Image) {
$image = true;
}
} else {
if (strpos($type, '.') !== false) {
$type = explode('.', $type)[0];
}
if (singleton($type) instanceof Image) {
$image = true;
}
}
if ($image) {
$images = $this->owner->$field();
foreach ($images as $image) {
try {
if ($image && $image->exists() && !isset($cachedImages[$image->ID])) {
$cachedImages[$image->ID] = true;
$list->push($image);
}
} catch (Throwable $e) {
//
}
}
}
}
$this->owner->extend('updateImagesForSitemap', $list);
return $list;
}
}

466
src/GoogleSitemap.php Normal file
View File

@ -0,0 +1,466 @@
<?php
namespace Wilr\GoogleSitemaps;
use SilverStripe\Control\Director;
use SilverStripe\Control\Controller;
use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Config\Config;
use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\DataList;
use SilverStripe\Versioned\Versioned;
use SilverStripe\View\ArrayData;
use SilverStripe\Core\Extensible;
use SilverStripe\Core\Injector\Injectable;
use SilverStripe\Core\Config\Configurable;
use Wilr\GoogleSitemaps\Extensions\GoogleSitemapExtension;
use ReflectionClass;
use ReflectionException;
/**
* Sitemaps are a way to tell Google about pages on your site that they might
* not otherwise discover. In its simplest terms, a XML Sitemap usually called
* a Sitemap, with a capital S—is a list of the pages on your website.
*
* Creating and submitting a Sitemap helps make sure that Google knows about
* all the pages on your site, including URLs that may not be discoverable by
* Google's normal crawling process.
*
* The GoogleSitemap handle requests to 'sitemap.xml' the other two classes are
* used to render the sitemap.
*
* The config file is usually located in the _config folder of your project folder.
* e.g. app/_config/googlesitemaps.yml
*
* <example>
* ---
* Name: customgooglesitemaps
* After: googlesitemaps
* ---
* Wilr\GoogleSitemaps\GoogleSitemap:
* enabled: true
* objects_per_sitemap: 1000
* use_show_in_search: true
* </example>
*
* @see http://www.google.com/support/webmasters/bin/answer.py?hl=en&answer=34609
*/
class GoogleSitemap
{
use Extensible;
use Injectable;
use Configurable;
/**
* List of {@link DataObject} class names to include. As well as the change
* frequency and priority of each class.
*
* @var array
*/
private static $dataobjects = [];
/**
* List of custom routes to include in the sitemap (such as controller
* subclasses) as well as the change frequency and priority.
*
* @var array
*/
private static $routes = [];
/**
* @config
*
* @var boolean
*/
private static $exclude_redirector_pages = true;
/**
* Decorates the given DataObject with {@link GoogleSitemapDecorator}
* and pushes the class name to the registered DataObjects.
* Note that all registered DataObjects need the method AbsoluteLink().
*
* @param string $className name of DataObject to register
* @param string $changeFreq how often is this DataObject updated?
* Possible values:
* always, hourly, daily, weekly, monthly, yearly, never
* @param string $priority How important is this DataObject in comparison to other urls?
* Possible values: 0.1, 0.2 ... , 0.9, 1.0
*
* @return void
*/
public static function register_dataobject($className, $changeFreq = 'monthly', $priority = '0.6')
{
if (!self::is_registered($className)) {
$className::add_extension(GoogleSitemapExtension::class);
self::$dataobjects[$className] = [
'frequency' => ($changeFreq) ? $changeFreq : 'monthly',
'priority' => ($priority) ? $priority : '0.6'
];
}
}
/**
* Registers multiple dataobjects in a single line. See {@link register_dataobject}
* for the heavy lifting
*
* @param array $dataobjects array of class names of DataObject to register
* @param string $changeFreq how often is this DataObject updated?
* Possible values:
* always, hourly, daily, weekly, monthly, yearly, never
* @param string $priority How important is this DataObject in comparison to other urls?
* Possible values: 0.1, 0.2 ... , 0.9, 1.0
*
* @return void
*/
public static function register_dataobjects($dataobjects, $changeFreq = 'monthly', $priority = '0.6')
{
foreach ($dataobjects as $obj) {
self::register_dataobject($obj, $changeFreq, $priority);
}
}
/**
* Checks whether the given class name is already registered or not.
*
* @param string $className Name of DataObject to check
*
* @return bool
*/
public static function is_registered($className)
{
if (!isset(self::$dataobjects[$className])) {
$lowerKeys = array_change_key_case(self::$dataobjects);
return isset($lowerKeys[$className]);
}
return true;
}
/**
* Unregisters a class from the sitemap. Mostly used for the test suite
*
* @param string
*/
public static function unregister_dataobject($className)
{
unset(self::$dataobjects[$className]);
}
/**
* Clears registered {@link DataObjects}. Useful for unit tests.
*
* @return void
*/
public static function clear_registered_dataobjects()
{
self::$dataobjects = array();
}
/**
* Register a given route to the sitemap list
*
* @param string
* @param string
* @param string
*
* @return void
*/
public static function register_route($route, $changeFreq = 'monthly', $priority = '0.6')
{
self::$routes = array_merge(self::$routes, array(
$route => array(
'frequency' => ($changeFreq) ? $changeFreq : 'monthly',
'priority' => ($priority) ? $priority : '0.6'
)
));
}
/**
* Registers a given list of relative urls. Will be merged with the current
* registered routes. If you want to replace them, please call {@link clear_routes}
*
* @param array
* @param string
* @param string
*
* @return void
*/
public static function register_routes($routes, $changeFreq = 'monthly', $priority = '0.6')
{
foreach ($routes as $route) {
self::register_route($route, $changeFreq, $priority);
}
}
/**
* Clears registered routes
*
* @return void
*/
public static function clear_registered_routes()
{
self::$routes = array();
}
/**
* Constructs the list of data to include in the rendered sitemap. Links
* can include pages from the website, dataobjects (such as forum posts)
* as well as custom registered paths.
*
* @param string
* @param int
*
* @return ArrayList
*/
public function getItems($class, $page = 1)
{
$page = (int) $page;
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 = (int) Config::inst()->get(__CLASS__, 'objects_per_sitemap');
$filter = Config::inst()->get(__CLASS__, 'use_show_in_search');
$redirector = Config::inst()->get(__CLASS__, 'exclude_redirector_pages');
// 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 == 'SilverStripe\CMS\Model\SiteTree') {
$instances = Versioned::get_by_stage('SilverStripe\CMS\Model\SiteTree', 'Live');
if ($filter) {
$instances = $instances->filter('ShowInSearch', 1);
}
if ($redirector) {
foreach (ClassInfo::subclassesFor('SilverStripe\\CMS\\Model\\RedirectorPage') as $redirectorClass) {
$instances = $instances->exclude('ClassName', $redirectorClass);
}
}
} elseif ($class == "GoogleSitemapRoute") {
$instances = array_slice(self::$routes, ($page - 1) * $count, $count);
$output = new ArrayList();
if ($instances) {
foreach ($instances as $route => $config) {
$output->push(new ArrayData(array(
'AbsoluteLink' => Director::absoluteURL($route),
'ChangeFrequency' => $config['frequency'],
'GooglePriority' => $config['priority']
)));
}
}
return $output;
} else {
$instances = new DataList($class);
}
$this->extend("alterDataList", $instances, $class);
$instances = $instances->limit(
$count,
($page - 1) * $count
);
if ($instances) {
foreach ($instances as $obj) {
if ($obj->canIncludeInGoogleSitemap()) {
$output->push($obj);
}
}
}
return $output;
}
/**
* Static interface to instance level ->getItems() for backward compatibility.
*
* @param string
* @param int
*
* @return ArrayList
* @deprecated Please create an instance and call ->getSitemaps() instead.
*/
public static function get_items($class, $page = 1)
{
return static::inst()->getItems($class, $page);
}
/**
* Returns the string frequency of edits for a particular dataobject class.
*
* Frequency for {@link SiteTree} objects can be determined from the version
* history.
*
* @param string
*
* @return string
*/
public static function get_frequency_for_class($class)
{
foreach (self::$dataobjects as $type => $config) {
if ($class == $type) {
return $config['frequency'];
}
}
return '';
}
/**
* Returns the default priority of edits for a particular dataobject class.
*
* @param string
*
* @return float
*/
public static function get_priority_for_class($class)
{
foreach (self::$dataobjects as $type => $config) {
if ($class == $type) {
return $config['priority'];
}
}
return 0.5;
}
/**
* The google site map is broken down into multiple smaller files to
* prevent overbearing a server. By default separate {@link DataObject}
* records are keep in separate files and broken down into chunks.
*
* @return ArrayList
*/
public function getSitemaps()
{
$countPerFile = Config::inst()->get(__CLASS__, 'objects_per_sitemap');
$sitemaps = new ArrayList();
$filter = Config::inst()->get(__CLASS__, 'use_show_in_search');
if (class_exists('SilverStripe\CMS\Model\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" : "";
$class = 'SilverStripe\CMS\Model\SiteTree';
$instances = Versioned::get_by_stage($class, 'Live', $filter);
$this->extend("alterDataList", $instances, $class);
$count = $instances->count();
$neededForPage = ceil($count / $countPerFile);
for ($i = 1; $i <= $neededForPage; $i++) {
$lastEdited = $instances
->limit($countPerFile, ($i - 1) * $countPerFile)
->sort(null)
->max('LastEdited');
$lastModified = ($lastEdited) ? date('Y-m-d', strtotime($lastEdited)) : date('Y-m-d');
$sitemaps->push(new ArrayData(array(
'ClassName' => $this->sanitiseClassName('SilverStripe\CMS\Model\SiteTree'),
'LastModified' => $lastModified,
'Page' => $i
)));
}
}
if (count(self::$dataobjects) > 0) {
foreach (self::$dataobjects as $class => $config) {
$list = new DataList($class);
$list = $list->sort('LastEdited ASC');
$this->extend("alterDataList", $list, $class);
$neededForClass = ceil($list->count() / $countPerFile);
for ($i = 1; $i <= $neededForClass; $i++) {
// determine the last modified date for this slice
$sliced = $list
->limit($countPerFile, ($i - 1) * $countPerFile)
->last();
$lastModified = ($sliced) ? date('Y-m-d', strtotime($sliced->LastEdited)) : date('Y-m-d');
$sitemaps->push(new ArrayData(array(
'ClassName' => $this->sanitiseClassName($class),
'Page' => $i,
'LastModified' => $lastModified
)));
}
}
}
if (count(self::$routes) > 0) {
$needed = ceil(count(self::$routes) / $countPerFile);
for ($i = 1; $i <= $needed; $i++) {
$sitemaps->push(new ArrayData(array(
'ClassName' => 'GoogleSitemapRoute',
'Page' => $i
)));
}
}
return $sitemaps;
}
/**
* Static interface to instance level ->getSitemaps() for backward compatibility.
*
* @return ArrayList
* @deprecated Please create an instance and call ->getSitemaps() instead.
*/
public static function get_sitemaps()
{
return static::inst()->getSitemaps();
}
/**
* Is GoogleSitemap enabled?
*
* @return boolean
*/
public static function enabled()
{
return (Config::inst()->get(__CLASS__, 'enabled'));
}
/**
* Convenience method for manufacturing an instance for hew instance-level
* methods (and for easier type definition).
*
* @return GoogleSitemap
*/
public static function inst()
{
return GoogleSitemap::create();
}
/**
* Sanitise a namespaced class' name for inclusion in a link
* @return string
*/
protected function sanitiseClassName($class)
{
return str_replace('\\', '-', (string) $class);
}
}

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

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<% control Items %>
<url>
<loc>$AbsoluteLink</loc>
<lastmod>$LastEdited.Format(c)</lastmod>
<% if ChangeFreq %><changefreq>$ChangeFreq</changefreq><% end_if %>
<% if Priority %><priority>$Priority</priority><% end_if %>
</url>
<% end_control %>
</urlset>

View File

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

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type='text/xsl' href='{$AbsoluteLink('styleSheet')}'?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">
<% loop $Items %>
<url>
<loc>{$AbsoluteLink}</loc>
<% if $LastEdited %><lastmod>{$LastEdited.Rfc3339()}</lastmod><% end_if %>
<% if $ChangeFrequency %><changefreq>$ChangeFrequency</changefreq><% end_if %>
<% if $GooglePriority %><priority>$GooglePriority</priority><% end_if %>
<% if $ImagesForSitemap %><% loop $ImagesForSitemap %>
<image:image>
<image:loc>{$AbsoluteLink}</image:loc>
<image:title>{$Title}</image:title>
</image:image>
<% end_loop %><% end_if %>
</url>
<% end_loop %>
</urlset>

91
templates/xml-sitemap.ss Normal file
View File

@ -0,0 +1,91 @@
<?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="$resourceURL(wilr/silverstripe-googlesitemaps:css/style.css)" />
</head>
<body>
<div class="content">
<h1 class="content__title">
<a href="https://www.silverstripe.org" target="_blank" rel="noopener noreferrer">XML Sitemap</a>
</h1>
<p class="content__text">
This sitemap contains <xsl:value-of select="count(sitemap:urlset/sitemap:url)"/> URLs.
</p>
<div class="table-wrapper">
<table id="sitemap" class="table">
<thead>
<tr>
<th class="table__cell table__cell--w-65">URL</th>
<th class="table__cell table__cell--w-10">Priority</th>
<th class="table__cell table__cell--w-10">Change Freq.</th>
<th class="table__cell table__cell--w-15">Last Change</th>
</tr>
</thead>
<tbody>
<xsl:variable name="lower" select="'abcdefghijklmnopqrstuvwxyz'"/>
<xsl:variable name="upper" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>
<xsl:for-each select="sitemap:urlset/sitemap:url">
<tr>
<td class="table__cell">
<xsl:variable name="itemURL">
<xsl:value-of select="sitemap:loc"/>
</xsl:variable>
<xsl:variable name="imagesCount" select="count(image:image)"/>
<a href="{\$itemURL}">
<xsl:value-of select="sitemap:loc"/>
</a>
<xsl:if test="\$imagesCount &gt; 0">
<table class="image-table">
<tr>
<th class="image-table__cell">Images</th>
</tr>
<xsl:for-each select="image:image">
<xsl:variable name="imageURL">
<xsl:value-of select="image:loc"/>
</xsl:variable>
<tr>
<td class="image-table__cell image-table__cell--image">
<img class="image-table__image" src="{\$imageURL}" width="60" height="40" />
</td>
<td class="image-table__cell image-table__cell--text">
<span class="image-table__title"><xsl:value-of select="image:title"/></span>
<a href="{\$imageURL}">
<xsl:value-of select="image:loc"/>
</a>
</td>
</tr>
</xsl:for-each>
</table>
</xsl:if>
</td>
<td class="table__cell">
<xsl:value-of select="concat(sitemap:priority*100,'%')"/>
</td>
<td class="table__cell">
<xsl:value-of select="concat(translate(substring(sitemap:changefreq, 1, 1),concat(\$lower, \$upper),concat(\$upper, \$lower)),substring(sitemap:changefreq, 2))"/>
</td>
<td class="table__cell">
<xsl:value-of select="concat(substring(sitemap:lastmod,0,11),concat(' ', substring(sitemap:lastmod,12,5)))"/>
</td>
</tr>
</xsl:for-each>
</tbody>
</table>
</div>
<p class="content__text">
Generated by the Silverstripe CMS
<a href="https://github.com/wilr/silverstripe-googlesitemaps" target="_blank" rel="noopener noreferrer" title="Silverstripe CMS Google Sitemaps module on Github">Google Sitemaps Module</a>
</p>
<p class="content__text">
More information about XML sitemaps on <a href="https://sitemaps.org" target="_blank" rel="noopener noreferrer">sitemaps.org</a>
</p>
</div>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,59 @@
<?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="$resourceURL(wilr/silverstripe-googlesitemaps:css/style.css)" />
</head>
<body>
<div class="content">
<h1 class="content__title">
<a href="https://www.silverstripe.org" target="_blank" rel="noopener noreferrer">XML Sitemap</a>
</h1>
<p class="content__text">
This sitemap consists of <xsl:value-of select="count(sitemap:sitemapindex/sitemap:sitemap)"/> part(s).
</p>
<div class="table-wrapper">
<table id="sitemapindex" class="table">
<thead>
<tr>
<th class="table__cell table__cell--w-85">URL</th>
<th class="table__cell table__cell--w-15">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 class="table__cell">
<xsl:variable name="itemURL">
<xsl:value-of select="sitemap:loc"/>
</xsl:variable>
<a href="{\$itemURL}">
<xsl:value-of select="sitemap:loc"/>
</a>
</td>
<td class="table__cell">
<xsl:value-of select="concat(substring(sitemap:lastmod,0,11),concat(' ', substring(sitemap:lastmod,12,5)))"/>
</td>
</tr>
</xsl:for-each>
</tbody>
</table>
</div>
<p class="content__text">
Generated by the Silverstripe CMS
<a href="https://github.com/wilr/silverstripe-googlesitemaps" target="_blank" rel="noopener noreferrer" title="SilverStripe Google Sitemaps module on Github">Google Sitemaps Module</a>
</p>
<p class="content__text">
More information about XML sitemaps on <a href="https://sitemaps.org" target="_blank" rel="noopener noreferrer">sitemaps.org</a>
</p>
</div>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,6 @@
Page:
Page1:
Title: Testpage1
Priority: 0.2
Page2:
Title: Testpage2

348
tests/GoogleSitemapTest.php Normal file
View File

@ -0,0 +1,348 @@
<?php
namespace Wilr\GoogleSitemaps\Tests;
use Exception;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Core\Config\Config;
use SilverStripe\Dev\FunctionalTest;
use SilverStripe\Forms\DropdownField;
use SilverStripe\Forms\LiteralField;
use SilverStripe\Forms\Tab;
use SilverStripe\ORM\DB;
use SilverStripe\Versioned\Versioned;
use Wilr\GoogleSitemaps\Extensions\GoogleSitemapExtension;
use Wilr\GoogleSitemaps\GoogleSitemap;
use Wilr\GoogleSitemaps\Tests\Model\OtherDataObject;
use Wilr\GoogleSitemaps\Tests\Model\TestDataObject;
use Wilr\GoogleSitemaps\Tests\Model\UnviewableDataObject;
class GoogleSitemapTest extends FunctionalTest
{
protected static $fixture_file = [
'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,
UnviewableDataObject::class
];
protected static $extra_extensions = [
GoogleSitemapExtension::class
];
protected function setUp(): void
{
parent::setUp();
GoogleSitemap::clear_registered_dataobjects();
GoogleSitemap::clear_registered_routes();
}
protected function tearDown(): void
{
parent::tearDown();
GoogleSitemap::clear_registered_dataobjects();
GoogleSitemap::clear_registered_routes();
}
public function testCanIncludeInGoogleSitemap(): void
{
GoogleSitemap::register_dataobject(TestDataObject::class, '');
$used = $this->objFromFixture(TestDataObject::class, 'DataObjectTest2');
$this->assertTrue($used->canIncludeInGoogleSitemap());
$used->setPrivate();
$this->assertFalse($used->canIncludeInGoogleSitemap());
}
public function testIndexFileWithCustomRoute(): void
{
GoogleSitemap::register_route('/test/');
$response = $this->get('sitemap.xml');
$body = $response->getBody();
$this->assertXmlStringEqualsXmlFile(
__DIR__ . '/xml/' . __FUNCTION__ . '.xml',
$body,
'A link to the custom routes exists'
);
}
public function testGetItems(): void
{
GoogleSitemap::register_dataobject(TestDataObject::class, '');
$google = new GoogleSitemap();
$items = $google->getItems(TestDataObject::class, 1);
$this->assertEquals(3, $items->count());
GoogleSitemap::register_dataobject(OtherDataObject::class);
$this->assertEquals(1, $google->getItems(OtherDataObject::class, 1)->count());
GoogleSitemap::register_dataobject(UnviewableDataObject::class);
$this->assertEquals(0, $google->getItems(UnviewableDataObject::class, 1)->count());
}
public function testGetItemsWithCustomRoutes(): void
{
GoogleSitemap::register_routes([
'/test-route/',
'/someother-route/',
'/fake-sitemap-route/'
]);
$google = new GoogleSitemap();
$items = $google->getItems('GoogleSitemapRoute', 1);
$this->assertEquals(3, $items->count());
}
public function testAccessingSitemapRootXMLFile(): void
{
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();
$this->assertXmlStringEqualsXmlFile(__DIR__ . '/xml/' . __FUNCTION__ . '.xml', $body);
}
public function testLastModifiedDateOnRootXML(): void
{
Config::inst()->set(GoogleSitemap::class, 'enabled', true);
if (!class_exists('Page')) {
$this->markTestIncomplete('No cms module installed, page related test skipped');
}
$page = $this->objFromFixture('Page', 'Page1');
$page->publishSingle();
$page->flushCache();
$p2 = $this->objFromFixture('Page', 'Page2');
$p2->publishSingle();
$p2->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\"='" . $p2->ID . "'");
$response = $this->get('sitemap.xml');
$body = $response->getBody();
$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(): void
{
$original = Config::inst()->get('GoogleSitemap', 'objects_per_sitemap');
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');
$t = $obj->baseTable();
DB::query("UPDATE \"" . $t . "\" SET \"LastEdited\"='2023-02-13 00:00:00' WHERE \"ID\"='" . $obj->ID . "'");
DB::query("UPDATE \"" . $t . "\" SET \"LastEdited\"='2023-02-13 00:00:00' WHERE \"ID\"='" . $obj1->ID . "'");
DB::query("UPDATE \"" . $t . "\" 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);
}
public function testRegisterRoutesIncludesAllRoutes(): void
{
GoogleSitemap::register_route('/test/');
GoogleSitemap::register_routes([
'/test/', // duplication should be replaced
'/unittests/',
'/anotherlink/'
], 'weekly');
$response = $this->get('sitemap.xml/sitemap/GoogleSitemapRoute/1');
$body = $response->getBody();
$this->assertEquals(200, $response->getStatusCode(), 'successful loaded nested sitemap');
$this->assertEquals(3, substr_count($body, "<loc>"));
}
public function testAccessingNestedSiteMap(): void
{
$original = Config::inst()->get('GoogleSitemap', 'objects_per_sitemap');
Config::inst()->set(GoogleSitemap::class, 'objects_per_sitemap', 1);
GoogleSitemap::register_dataobject(TestDataObject::class);
$response = $this->get('sitemap.xml/sitemap/Wilr-GoogleSitemaps-Tests-Model-TestDataObject/1');
$body = $response->getBody();
$this->assertEquals(200, $response->getStatusCode(), 'successful loaded nested sitemap');
Config::inst()->set(GoogleSitemap::class, 'objects_per_sitemap', $original);
}
public function testGetItemsWithPages(): void
{
if (!class_exists(SiteTree::class)) {
$this->markTestIncomplete('No cms module installed, page related test skipped');
}
$page = $this->objFromFixture('Page', 'Page1');
$page->publishSingle();
$page->flushCache();
$page2 = $this->objFromFixture('Page', 'Page2');
$page2->publishSingle();
$page2->flushCache();
$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';
$page2->write();
$page2->publishSingle();
$this->logOut();
$this->assertListEquals([
['Title' => 'Testpage1']
], GoogleSitemap::inst()->getItems(SiteTree::class), "There should be only 1 page, other is logged in only");
}
public function testAccess(): void
{
Config::inst()->set(GoogleSitemap::class, 'enabled', true);
$response = $this->get('sitemap.xml');
$this->assertEquals(200, $response->getStatusCode(), 'Sitemap returns a 200 success when enabled');
$this->assertEquals('application/xml; charset="utf-8"', $response->getHeader('Content-Type'));
GoogleSitemap::register_dataobject(TestDataObject::class);
$response = $this->get('sitemap.xml/sitemap/Wilr-GoogleSitemaps-Tests-Model-TestDataObject/1');
$this->assertEquals(200, $response->getStatusCode(), 'Sitemap returns a 200 success when enabled');
$this->assertEquals('application/xml; charset="utf-8"', $response->getHeader('Content-Type'));
Config::inst()->set(GoogleSitemap::class, 'enabled', false);
$response = $this->get('sitemap.xml');
$this->assertEquals(404, $response->getStatusCode(), 'Sitemap index returns a 404 when disabled');
$response = $this->get('sitemap.xml/sitemap/Wilr-GoogleSitemaps-Tests-Model-TestDataObject/1');
$this->assertEquals(404, $response->getStatusCode(), 'Sitemap file returns a 404 when disabled');
}
public function testDecoratorAddsFields(): void
{
if (!class_exists("Page")) {
$this->markTestIncomplete('No cms module installed, page related test skipped');
}
$page = $this->objFromFixture('Page', 'Page1');
$fields = $page->getSettingsFields();
$tab = $fields->fieldByName('Root')->fieldByName('Settings')->fieldByName('GoogleSitemap');
$this->assertInstanceOf(Tab::class, $tab);
$this->assertInstanceOf(DropdownField::class, $tab->fieldByName('Priority'));
$this->assertInstanceOf(LiteralField::class, $tab->fieldByName('GoogleSitemapIntro'));
}
public function testGetPriority(): void
{
if (!class_exists("Page")) {
$this->markTestIncomplete('No cms module installed, page related test skipped');
}
$page = $this->objFromFixture('Page', 'Page1');
// 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(): void
{
if (!class_exists('SilverStripe\CMS\Model\SiteTree')) {
$this->markTestSkipped('Test skipped; CMS module required for testUnpublishedPage');
}
$orphanedPage = new \SilverStripe\CMS\Model\SiteTree();
$orphanedPage->ParentID = 999999; // missing parent id
$orphanedPage->write();
$orphanedPage->publishSingle();
$rootPage = new \SilverStripe\CMS\Model\SiteTree();
$rootPage->ParentID = 0;
$rootPage->write();
$rootPage->publishSingle();
$oldMode = Versioned::get_reading_mode();
Versioned::set_reading_mode('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);
// }
}
}

View File

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

View File

@ -0,0 +1,24 @@
<?php
namespace Wilr\GoogleSitemaps\Tests\Model;
use SilverStripe\ORM\DataObject;
use SilverStripe\Dev\TestOnly;
use SilverStripe\Control\Director;
class OtherDataObject extends DataObject implements TestOnly
{
private static $db = array(
'Priority' => 'Varchar(10)'
);
public function canView($member = null)
{
return true;
}
public function AbsoluteLink()
{
return Director::absoluteBaseURL();
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace Wilr\GoogleSitemaps\Tests\Model;
use SilverStripe\ORM\DataObject;
use SilverStripe\Dev\TestOnly;
use SilverStripe\Control\Director;
class TestDataObject extends DataObject implements TestOnly
{
protected $private = false;
private static $db = array(
'Priority' => 'Varchar(10)'
);
public function canView($member = null)
{
if ($this->private) {
return false;
}
return true;
}
public function setPrivate()
{
$this->private = true;
}
public function AbsoluteLink()
{
return Director::absoluteBaseURL();
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace Wilr\GoogleSitemaps\Tests\Model;
use SilverStripe\ORM\DataObject;
use SilverStripe\Dev\TestOnly;
use SilverStripe\Control\Director;
class UnviewableDataObject extends DataObject implements TestOnly
{
private static $db = array(
'Priority' => 'Varchar(10)'
);
public function canView($member = null)
{
return false;
}
public function AbsoluteLink()
{
return Director::absoluteBaseURL();
}
}

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type='text/xsl' href='http://localhost/sitemap.xml/styleSheetIndex'?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap>
<loc>http://localhost/sitemap.xml/sitemap/Wilr-GoogleSitemaps-Tests-Model-TestDataObject/1</loc>
<lastmod>2023-02-13</lastmod>
</sitemap>
<sitemap>
<loc>http://localhost/sitemap.xml/sitemap/Wilr-GoogleSitemaps-Tests-Model-OtherDataObject/1</loc>
<lastmod>2023-02-13</lastmod>
</sitemap>
</sitemapindex>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type='text/xsl' href='http://localhost/sitemap.xml/styleSheetIndex'?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap>
<loc>http://localhost/sitemap.xml/sitemap/Wilr-GoogleSitemaps-Tests-Model-TestDataObject/1</loc>
<lastmod>2023-02-13</lastmod>
</sitemap>
<sitemap>
<loc>http://localhost/sitemap.xml/sitemap/Wilr-GoogleSitemaps-Tests-Model-TestDataObject/2</loc>
<lastmod>2023-02-13</lastmod>
</sitemap>
<sitemap>
<loc>http://localhost/sitemap.xml/sitemap/Wilr-GoogleSitemaps-Tests-Model-TestDataObject/3</loc>
<lastmod>2023-02-13</lastmod>
</sitemap>
</sitemapindex>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type='text/xsl' href='http://localhost/sitemap.xml/styleSheetIndex'?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap>
<loc>http://localhost/sitemap.xml/sitemap/GoogleSitemapRoute/1</loc>
</sitemap>
</sitemapindex>