Compare commits

...

180 Commits
1.2.2 ... 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
Ingo Schommer 9a95e58d6e Travis support 2013-03-29 10:12:03 +01:00
55 changed files with 2043 additions and 1431 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

3
.gitignore vendored
View File

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

View File

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

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) 2011, 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.

View File

@ -1,28 +1,37 @@
# Google Sitemaps Module
[![Build Status](https://secure.travis-ci.org/silverstripe-labs/silverstripe-googlesitemaps.png?branch=master)](http://travis-ci.org/silverstripe-labs/silverstripe-googlesitemaps)
[![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>
- Will Rossiter (Nickname: wrossiter, willr) <will@fullscreen.io>
## Requirements
## Installation
* SilverStripe 3.0
> 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
SilverStripe provides support for the Google Sitemaps XML system, 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 by Google quickly.
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.
Therefore, all Silverstripe websites contain a special controller which can
be visited: http://yoursite.com/sitemap.xml
Any new pages published or unpublished on your website automatically update the
Sitemap.
See http://en.wikipedia.org/wiki/Sitemaps for info on this format.
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,11 +1,10 @@
<?php
// add the extension to pages
if (class_exists('SiteTree')) {
SiteTree::add_extension('GoogleSitemapSiteTreeExtension');
use SilverStripe\Core\ClassInfo;
use Wilr\GoogleSitemaps\GoogleSitemap;
if (0 === strpos(ltrim($_SERVER['REQUEST_URI'], '/'), 'sitemap')) {
foreach (ClassInfo::implementorsOf(Sitemapable::class) as $className) {
GoogleSitemap::register_dataobject($className);
}
}
// if you need to add this to DataObjects include the following in
// your own _config:
// GoogleSiteMap::register_dataobject('MyDataObject');

View File

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

View File

@ -1,6 +1,6 @@
---
Name: googlesitemaproutes
---
Director:
SilverStripe\Control\Director:
rules:
'sitemap.xml': 'GoogleSitemapController'
'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,402 +0,0 @@
<?php
/**
* 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.
*
* 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 {
/**
* List of {@link DataObject} class names to include. As well as the change
* frequency and priority of each class.
*
* @var array
*/
private static $dataobjects = array();
/**
* 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 = array();
/**
* 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');
self::$dataobjects[$className] = array(
'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) {
return isset(self::$dataobjects[$className]);
}
/**
* 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 static function get_items($class, $page = 1) {
$output = new ArrayList();
$count = Config::inst()->get('GoogleSitemap', 'objects_per_sitemap');
$filter = Config::inst()->get('GoogleSitemap', 'use_show_in_search');
// todo migrate to extension hook or DI point for other modules to
// modify state filters
if(class_exists('Translatable')) {
Translatable::disable_locale_filter();
}
if($class == "SiteTree") {
$filter = ($filter) ? "\"ShowInSearch\" = 1" : "";
$instances = Versioned::get_by_stage('SiteTree', 'Live', $filter);
}
else if($class == "GoogleSitemapRoute") {
$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);
}
$instances = $instances->limit(
$count,
($page - 1) * $count
);
if($instances) {
foreach($instances as $obj) {
if($obj->canIncludeInGoogleSitemap()) {
$output->push($obj);
}
}
}
return $output;
}
/**
* 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'];
}
}
}
/**
* 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 static function get_sitemaps() {
$countPerFile = Config::inst()->get('GoogleSitemap', 'objects_per_sitemap');
$sitemaps = new ArrayList();
$filter = Config::inst()->get('GoogleSitemap', 'use_show_in_search');
if(class_exists('SiteTree')) {
// move to extension hook. At the moment moduleexists config hook
// does not work.
if(class_exists('Translatable')) {
Translatable::disable_locale_filter();
}
$filter = ($filter) ? "\"ShowInSearch\" = 1" : "";
$instances = Versioned::get_by_stage('SiteTree', 'Live', $filter);
$count = $instances->count();
$neededForPage = ceil($count / $countPerFile);
for($i = 1; $i <= $neededForPage; $i++) {
$sliced = $instances
->limit($countPerFile, ($i - 1) * $countPerFile)
->last();
$lastModified = ($sliced) ? $sliced->dbObject('LastEdited')->Format('Y-m-d') : date('Y-m-d');
$sitemaps->push(new ArrayData(array(
'ClassName' => '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');
$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) ? $sliced->dbObject('LastEdited')->Format('Y-m-d') : date('Y-m-d');
$sitemaps->push(new ArrayData(array(
'ClassName' => $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;
}
/**
* Notifies Google about changes to your sitemap. This behavior is disabled
* by default, to enable, read the documentation provided in the docs folder.
*
* After notifications have been enabled, every publish / unpublish of a page.
* will notify Google of the update.
*
* If the site is in development mode no ping will be sent regardless whether
* the Google notification is enabled.
*
* @return string Response text
*/
public 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
$active = Config::inst()->get('GoogleSitemap', 'google_notification_enabled');
if(!$active || Director::isDev()) {
return;
}
$location = urlencode(Controller::join_links(
Director::absoluteBaseURL(),
'sitemap.xml'
));
$response = self::send_ping(
"www.google.com", "/webmasters/sitemaps/ping", sprintf("sitemap=%s", $location)
);
return $response;
}
/**
* Send an HTTP request to the host.
*
* @return String Response text
*/
protected static function send_ping($host, $path, $query) {
$socket = fsockopen($host, 80, $errno, $error);
if (!$socket) {
return $error;
}
if ($query) {
$query = '?' . $query;
}
$request = "GET {$path}{$query} HTTP/1.1\r\nHost: $host\r\nConnection: Close\r\n\r\n";
fwrite($socket, $request);
$response = stream_get_contents($socket);
return $response;
}
/**
* Is GoogleSitemap enabled?
*
* @return boolean
*/
public static function enabled() {
return (Config::inst()->get('GoogleSitemap', 'enabled', Config::INHERITED));
}
}

View File

@ -1,73 +0,0 @@
<?php
/**
* 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 = array(
'index',
'sitemap'
);
/**
* 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()) {
Config::inst()->update('SSViewer', 'set_source_file_comments', false);
$this->getResponse()->addHeader('Content-Type', 'application/xml; charset="utf-8"');
$sitemaps = GoogleSitemap::get_sitemaps();
$this->extend('updateGoogleSitemaps', $sitemaps);
return array(
'Sitemaps' => $sitemaps
);
} else {
return new SS_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->request->param('ID');
$page = $this->request->param('OtherID');
if(GoogleSitemap::enabled() && $class && $page) {
Config::inst()->update('SSViewer', 'set_source_file_comments', false);
$this->getResponse()->addHeader('Content-Type', 'application/xml; charset="utf-8"');
$items = GoogleSitemap::get_items($class, $page);
$this->extend('updateGoogleSitemapItems', $items, $class, $page);
return array(
'Items' => $items
);
} else {
return new SS_HTTPResponse('Page not found', 404);
}
}
}

View File

@ -1,116 +0,0 @@
<?php
/**
* Decorate the page object to provide google sitemaps with
* additionally options and configuration.
*
* @package googlesitemaps
*/
class GoogleSitemapExtension extends DataExtension {
/**
* @return boolean
*/
public function canIncludeInGoogleSitemap() {
$can = true;
if($this->owner->hasMethod('AbsoluteLink')) {
$hostHttp = parse_url(Director::protocolAndHost(), PHP_URL_HOST);
$objHttp = parse_url($this->owner->AbsoluteLink(), PHP_URL_HOST);
if($objHttp != $hostHttp) {
$can = false;
}
}
if($can) {
$can = $this->owner->canView();
}
if($can) {
$can = $this->owner->getGooglePriority();
}
$this->owner->invokeWithExtensions('alterCanIncludeInGoogleSitemap', $can);
return $can;
}
/**
* @return void
*/
public function onAfterPublish() {
GoogleSitemap::ping();
}
/**
* @return void
*/
public 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.
*
* @return mixed
*/
public function getGooglePriority() {
$field = $this->owner->hasField('Priority');
if(isset($this->Priority) || ($field && $this->Priority = $this->owner->getField('Priority'))) {
return ($this->Priority < 0) ? false : $this->Priority;
}
return GoogleSitemap::get_priority_for_class($this->owner->class);
}
/**
* 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 SS_Datetime
*/
public function getChangeFrequency() {
if($freq = GoogleSitemap::get_frequency_for_class($this->owner->class)) {
return $freq;
}
$date = date('Y-m-d H:i:s');
$created = new SS_Datetime();
$created->value = ($this->owner->Created) ? $this->owner->Created : $date;
$now = new SS_Datetime();
$now->value = $date;
$versions = ($this->owner->Version) ? $this->owner->Version : 1;
$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) {
$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

@ -1,113 +0,0 @@
<?php
/**
* @package googlesitemaps
*/
class GoogleSitemapSiteTreeExtension extends GoogleSitemapExtension {
/**
* @var array
*/
private static $db = array(
"Priority" => "Varchar(5)"
);
/**
* @param FieldList
*/
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'),
new LiteralField("GoogleSitemapIntro", $message),
$priority = new DropdownField("Priority", $this->owner->fieldLabel('Priority'), $prorities, $this->owner->Priority)
));
$priority->setEmptyString(_t('GoogleSitemaps.PRIORITYAUTOSET', 'Auto-set based on page depth'));
}
/**
* @param FieldList
*
* @return void
*/
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;
return $result;
}
/**
* @return mixed
*/
public function getGooglePriority() {
setlocale(LC_ALL, "en_US.UTF8");
$priority = $this->owner->getField('Priority');
if(!$priority) {
$parentStack = $this->owner->parentStack();
$numParents = is_array($parentStack) ? count($parentStack) - 1 : 0;
$num = max(0.1, 1.0 - ($numParents / 10));
$result = str_replace(",", ".", $num);
return $result;
} else if ($priority == -1) {
return false;
} else {
return (is_float($priority) && $priority <= 1.0) ? $priority : 0.5;
}
}
}

View File

@ -1,15 +1,53 @@
{
"name": "silverstripe/googlesitemaps",
"description": "SilverStripe support for the Google Sitemaps XML system, 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 by Google quickly.",
"type": "silverstripe-module",
"keywords": ["silverstripe", "googlesitemaps", "seo"],
"homepage": "https://github.com/silverstripe-labs/silverstripe-googlesitemaps",
"license": "BSD",
"authors": [{
"name": "Will Rossiter",
"email": "will@fullscreen.io"
}],
"require": {
"silverstripe/framework": "~3.1"
}
"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
}
}
}

View File

@ -1,131 +1,145 @@
*{
margin: 0;
padding: 0;
: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 {
font-family: Helvetica, Arial, sans-serif;
font-size: 14px;
color: #545353;
color: #005A92;
background: #B0BEC7;
padding: 20px;
}
#content {
margin: 0 auto;
max-width: 1200px;
background: #fff;
padding: 20px 30px;
-webkit-box-shadow: 2px 2px 5px 1px rgba(0, 0, 0, 0.2);
box-shadow: 2px 2px 5px 1px rgba(0, 0, 0, 0.2);
-webkit-border-radius: 5px;
border-radius: 5px;
}
h1{
font-size: 20px;
line-height: 24px;
font-weight: bold;
color: #1556B2;
color: #005A92;
/* text-shadow: 1px 1px 1px rgba(0,0,0,0.7);
filter: dropshadow(color=#000, offx=2, offy=2);*/
padding-left: 31px;
background: url("../images/logo_small.png") transparent left top no-repeat scroll;
margin: 0 0 20px 0;
display: inline-block;
}
h1 a{
font: inherit;
color: inherit;
text-decoration: none;
}
h1 .ss_link{
visibility: hidden;
font-size: 9px;
display: block;
text-align: right;
margin-top: -5px;
}
h1:hover .ss_link{
visibility: visible;
text-decoration: underline;
}
.expl {
margin: 10px 3px;
line-height: 1.3em;
}
.expl a {
color: #1556B2;
font-weight: bold;
text-decoration: none;
}
.expl a:hover{
text-decoration: underline;
background: var(--color-white);
color: var(--color-grey-dark);
}
table.tablesorter {
background-color: #CDCDCD;
margin:20px 0pt 15px;
font-size: 8pt;
width: 100%;
text-align: left;
border: none;
border-collapse: collapse;
border-bottom: 1px solid #005A92;
}
table.tablesorter thead tr th, table.tablesorter tfoot tr th {
/*background-color: #e6EEEE;*/
background-color: #F5FAFA;
font-size: 8pt;
padding: 4px 20px 4px 10px;
}
table.tablesorter thead tr .headerSortDown, table.tablesorter thead tr .headerSortUp {
background-color: #B0BEC7;
background-color: #e6EEEE;
table {
border-collapse: collapse;
color: var(--color-black);
text-align: left;
width: 100%;
}
table.tablesorter thead tr .header {
background-image: url("../images/bg.gif");
background-repeat: no-repeat;
background-position: center right;
cursor: pointer;
border-bottom: 1px solid #005A92;
}
table.tablesorter thead tr .headerSortUp {
background-image: url("../images/asc.gif");
}
table.tablesorter thead tr .headerSortDown {
background-image: url("../images/desc.gif");
a {
color: inherit;
text-decoration: none;
}
table.tablesorter tbody td {
color: #005A92;
padding: 4px;
.content {
margin: 0 auto;
max-width: 122rem;
padding: 2rem;
}
vertical-align: top;
.content__title {
align-items: center;
color: var(--color-black);
display: inline-flex;
font-size: 2rem;
font-weight: 600;
margin: 0 0 3rem;
}
table.tablesorter tbody tr{
background-color: #FFF;
.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;
}
table.tablesorter tbody tr.odd {
background-color:#EFF2F3;
.content__text {
margin: 1rem 0;
}
table.tablesorter tbody tr:hover {
background-color: #D8E1E8;
.content__text a {
color: var(--color-blue-bright);
text-decoration: underline;
}
table.tablesorter tbody a{
color: #444;color: #005A92;
text-decoration: none;
.content__text a:hover {
color: var(--color-blue-dark);
text-decoration: none;
}
table.tablesorter tbody a:hover{
text-decoration: underline;
.table-wrapper {
border-top: 0.1rem solid var(--color-grey-medium);
margin: 3rem 0 5rem;
overflow-x: auto;
}
table.tablesorter tbody tr:hover td,
table.tablesorter tbody tr:hover a{
color: #000;
.table__cell {
padding: 1rem;
}
#Footer{
margin: 50px 0 10px;
text-align: right;
font-size: 0.8em;
.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;
}

View File

@ -1,119 +1,144 @@
# Google Sitemaps Module
SilverStripe 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 website rank well in search engines, and to encourage the
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 websites contain a special controller which can be
visited: http://yoursite.com/sitemap.xml. This is not a file directly, but
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
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 can
automatically inform Google of the change, encouraging a Google to take notice.
If you install the SilverStripe Google Analytics module, you can see if Google
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 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,
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
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 Config API. Create a new
config file `mysite/_config/googlesitemaps.yml` with the following outline:
Most module configuration is done via the Silverstripe CMS Config API. Create a
new config file `mysite/_config/googlesitemaps.yml` with the following outline:
---
Name: customgooglesitemaps
After: googlesitemaps
---
GoogleSitemap:
enabled: true
objects_per_sitemap: 1000
google_notification_enabled: false
use_show_in_search: true
```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. A popular option
is to turn on automatic pinging so that Google is notified of any updates to
your page. You can set this in the file we created in the last paragraph by
editing the `google_notification_enabled` option to true
You can now alter any of those properties to set your needs.
---
Name: customgooglesitemaps
After: googlesitemaps
---
GoogleSitemap:
enabled: true
objects_per_sitemap: 1000
google_notification_enabled: true
use_show_in_search: true
```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
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
To include a DataObject instance in the Sitemap it requires that your subclass
defines two functions:
* AbsoluteLink() function which returns the URL for this DataObject
* canView() function which returns a boolean value.
- AbsoluteLink() function which returns the URL for this DataObject
- canView() function which returns a boolean value.
The following is a barebones example of a DataObject called 'MyDataObject'. It
assumes that you have a controller called 'MyController' which has a show method
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
class MyDataObject extends DataObject {
function canView($member = null) {
return true;
}
function AbsoluteLink() {
return Director::absoluteURL($this->Link());
}
function Link() {
return 'MyController/show/'. $this->ID;
}
}
<?php
use SilverStripe\ORM\DataObject;
use SilverStripe\Control\Director;
After those methods have been defined on your DataObject you now need to tell
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.
do that, include the following in your \_config.php file.
GoogleSitemap::register_dataobject('MyDataObject');
use Wilr\GoogleSitemaps\GoogleSitemap;
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
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:
GoogleSitemap::register_dataobject('MyDataObject', 'daily');
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 url's in your sitemap for
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.
URLs to include.
GoogleSitemap::register_routes(array(
'/my-custom-controller/',
'/Security/',
'/Security/login/'
));
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()
{
// ..
}
}
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -1,4 +0,0 @@
(function($){$.extend({tablesorter:new
function(){var parsers=[],widgets=[];this.defaults={cssHeader:"header",cssAsc:"headerSortUp",cssDesc:"headerSortDown",cssChildRow:"expand-child",sortInitialOrder:"asc",sortMultiSortKey:"shiftKey",sortForce:null,sortAppend:null,sortLocaleCompare:true,textExtraction:"simple",parsers:{},widgets:[],widgetZebra:{css:["even","odd"]},headers:{},widthFixed:false,cancelSelection:true,sortList:[],headerList:[],dateFormat:"us",decimal:'/\.|\,/g',onRenderHeader:null,selectorHeaders:'thead th',debug:false};function benchmark(s,d){log(s+","+(new Date().getTime()-d.getTime())+"ms");}this.benchmark=benchmark;function log(s){if(typeof console!="undefined"&&typeof console.debug!="undefined"){console.log(s);}else{alert(s);}}function buildParserCache(table,$headers){if(table.config.debug){var parsersDebug="";}if(table.tBodies.length==0)return;var rows=table.tBodies[0].rows;if(rows[0]){var list=[],cells=rows[0].cells,l=cells.length;for(var i=0;i<l;i++){var p=false;if($.metadata&&($($headers[i]).metadata()&&$($headers[i]).metadata().sorter)){p=getParserById($($headers[i]).metadata().sorter);}else if((table.config.headers[i]&&table.config.headers[i].sorter)){p=getParserById(table.config.headers[i].sorter);}if(!p){p=detectParserForColumn(table,rows,-1,i);}if(table.config.debug){parsersDebug+="column:"+i+" parser:"+p.id+"\n";}list.push(p);}}if(table.config.debug){log(parsersDebug);}return list;};function detectParserForColumn(table,rows,rowIndex,cellIndex){var l=parsers.length,node=false,nodeValue=false,keepLooking=true;while(nodeValue==''&&keepLooking){rowIndex++;if(rows[rowIndex]){node=getNodeFromRowAndCellIndex(rows,rowIndex,cellIndex);nodeValue=trimAndGetNodeText(table.config,node);if(table.config.debug){log('Checking if value was empty on row:'+rowIndex);}}else{keepLooking=false;}}for(var i=1;i<l;i++){if(parsers[i].is(nodeValue,table,node)){return parsers[i];}}return parsers[0];}function getNodeFromRowAndCellIndex(rows,rowIndex,cellIndex){return rows[rowIndex].cells[cellIndex];}function trimAndGetNodeText(config,node){return $.trim(getElementText(config,node));}function getParserById(name){var l=parsers.length;for(var i=0;i<l;i++){if(parsers[i].id.toLowerCase()==name.toLowerCase()){return parsers[i];}}return false;}function buildCache(table){if(table.config.debug){var cacheTime=new Date();}var totalRows=(table.tBodies[0]&&table.tBodies[0].rows.length)||0,totalCells=(table.tBodies[0].rows[0]&&table.tBodies[0].rows[0].cells.length)||0,parsers=table.config.parsers,cache={row:[],normalized:[]};for(var i=0;i<totalRows;++i){var c=$(table.tBodies[0].rows[i]),cols=[];if(c.hasClass(table.config.cssChildRow)){cache.row[cache.row.length-1]=cache.row[cache.row.length-1].add(c);continue;}cache.row.push(c);for(var j=0;j<totalCells;++j){cols.push(parsers[j].format(getElementText(table.config,c[0].cells[j]),table,c[0].cells[j]));}cols.push(cache.normalized.length);cache.normalized.push(cols);cols=null;};if(table.config.debug){benchmark("Building cache for "+totalRows+" rows:",cacheTime);}return cache;};function getElementText(config,node){var text="";if(!node)return"";if(!config.supportsTextContent)config.supportsTextContent=node.textContent||false;if(config.textExtraction=="simple"){if(config.supportsTextContent){text=node.textContent;}else{if(node.childNodes[0]&&node.childNodes[0].hasChildNodes()){text=node.childNodes[0].innerHTML;}else{text=node.innerHTML;}}}else{if(typeof(config.textExtraction)=="function"){text=config.textExtraction(node);}else{text=$(node).text();}}return text;}function appendToTable(table,cache){if(table.config.debug){var appendTime=new Date()}var c=cache,r=c.row,n=c.normalized,totalRows=n.length,checkCell=(n[0].length-1),tableBody=$(table.tBodies[0]),rows=[];for(var i=0;i<totalRows;i++){var pos=n[i][checkCell];rows.push(r[pos]);if(!table.config.appender){var l=r[pos].length;for(var j=0;j<l;j++){tableBody[0].appendChild(r[pos][j]);}}}if(table.config.appender){table.config.appender(table,rows);}rows=null;if(table.config.debug){benchmark("Rebuilt table:",appendTime);}applyWidget(table);setTimeout(function(){$(table).trigger("sortEnd");},0);};function buildHeaders(table){if(table.config.debug){var time=new Date();}var meta=($.metadata)?true:false;var header_index=computeTableHeaderCellIndexes(table);$tableHeaders=$(table.config.selectorHeaders,table).each(function(index){this.column=header_index[this.parentNode.rowIndex+"-"+this.cellIndex];this.order=formatSortingOrder(table.config.sortInitialOrder);this.count=this.order;if(checkHeaderMetadata(this)||checkHeaderOptions(table,index))this.sortDisabled=true;if(checkHeaderOptionsSortingLocked(table,index))this.order=this.lockedOrder=checkHeaderOptionsSortingLocked(table,index);if(!this.sortDisabled){var $th=$(this).addClass(table.config.cssHeader);if(table.config.onRenderHeader)table.config.onRenderHeader.apply($th);}table.config.headerList[index]=this;});if(table.config.debug){benchmark("Built headers:",time);log($tableHeaders);}return $tableHeaders;};function computeTableHeaderCellIndexes(t){var matrix=[];var lookup={};var thead=t.getElementsByTagName('THEAD')[0];var trs=thead.getElementsByTagName('TR');for(var i=0;i<trs.length;i++){var cells=trs[i].cells;for(var j=0;j<cells.length;j++){var c=cells[j];var rowIndex=c.parentNode.rowIndex;var cellId=rowIndex+"-"+c.cellIndex;var rowSpan=c.rowSpan||1;var colSpan=c.colSpan||1
var firstAvailCol;if(typeof(matrix[rowIndex])=="undefined"){matrix[rowIndex]=[];}for(var k=0;k<matrix[rowIndex].length+1;k++){if(typeof(matrix[rowIndex][k])=="undefined"){firstAvailCol=k;break;}}lookup[cellId]=firstAvailCol;for(var k=rowIndex;k<rowIndex+rowSpan;k++){if(typeof(matrix[k])=="undefined"){matrix[k]=[];}var matrixrow=matrix[k];for(var l=firstAvailCol;l<firstAvailCol+colSpan;l++){matrixrow[l]="x";}}}}return lookup;}function checkCellColSpan(table,rows,row){var arr=[],r=table.tHead.rows,c=r[row].cells;for(var i=0;i<c.length;i++){var cell=c[i];if(cell.colSpan>1){arr=arr.concat(checkCellColSpan(table,headerArr,row++));}else{if(table.tHead.length==1||(cell.rowSpan>1||!r[row+1])){arr.push(cell);}}}return arr;};function checkHeaderMetadata(cell){if(($.metadata)&&($(cell).metadata().sorter===false)){return true;};return false;}function checkHeaderOptions(table,i){if((table.config.headers[i])&&(table.config.headers[i].sorter===false)){return true;};return false;}function checkHeaderOptionsSortingLocked(table,i){if((table.config.headers[i])&&(table.config.headers[i].lockedOrder))return table.config.headers[i].lockedOrder;return false;}function applyWidget(table){var c=table.config.widgets;var l=c.length;for(var i=0;i<l;i++){getWidgetById(c[i]).format(table);}}function getWidgetById(name){var l=widgets.length;for(var i=0;i<l;i++){if(widgets[i].id.toLowerCase()==name.toLowerCase()){return widgets[i];}}};function formatSortingOrder(v){if(typeof(v)!="Number"){return(v.toLowerCase()=="desc")?1:0;}else{return(v==1)?1:0;}}function isValueInArray(v,a){var l=a.length;for(var i=0;i<l;i++){if(a[i][0]==v){return true;}}return false;}function setHeadersCss(table,$headers,list,css){$headers.removeClass(css[0]).removeClass(css[1]);var h=[];$headers.each(function(offset){if(!this.sortDisabled){h[this.column]=$(this);}});var l=list.length;for(var i=0;i<l;i++){h[list[i][0]].addClass(css[list[i][1]]);}}function fixColumnWidth(table,$headers){var c=table.config;if(c.widthFixed){var colgroup=$('<colgroup>');$("tr:first td",table.tBodies[0]).each(function(){colgroup.append($('<col>').css('width',$(this).width()));});$(table).prepend(colgroup);};}function updateHeaderSortCount(table,sortList){var c=table.config,l=sortList.length;for(var i=0;i<l;i++){var s=sortList[i],o=c.headerList[s[0]];o.count=s[1];o.count++;}}function multisort(table,sortList,cache){if(table.config.debug){var sortTime=new Date();}var dynamicExp="var sortWrapper = function(a,b) {",l=sortList.length;for(var i=0;i<l;i++){var c=sortList[i][0];var order=sortList[i][1];var s=(table.config.parsers[c].type=="text")?((order==0)?makeSortFunction("text","asc",c):makeSortFunction("text","desc",c)):((order==0)?makeSortFunction("numeric","asc",c):makeSortFunction("numeric","desc",c));var e="e"+i;dynamicExp+="var "+e+" = "+s;dynamicExp+="if("+e+") { return "+e+"; } ";dynamicExp+="else { ";}var orgOrderCol=cache.normalized[0].length-1;dynamicExp+="return a["+orgOrderCol+"]-b["+orgOrderCol+"];";for(var i=0;i<l;i++){dynamicExp+="}; ";}dynamicExp+="return 0; ";dynamicExp+="}; ";if(table.config.debug){benchmark("Evaling expression:"+dynamicExp,new Date());}eval(dynamicExp);cache.normalized.sort(sortWrapper);if(table.config.debug){benchmark("Sorting on "+sortList.toString()+" and dir "+order+" time:",sortTime);}return cache;};function makeSortFunction(type,direction,index){var a="a["+index+"]",b="b["+index+"]";if(type=='text'&&direction=='asc'){return"("+a+" == "+b+" ? 0 : ("+a+" === null ? Number.POSITIVE_INFINITY : ("+b+" === null ? Number.NEGATIVE_INFINITY : ("+a+" < "+b+") ? -1 : 1 )));";}else if(type=='text'&&direction=='desc'){return"("+a+" == "+b+" ? 0 : ("+a+" === null ? Number.POSITIVE_INFINITY : ("+b+" === null ? Number.NEGATIVE_INFINITY : ("+b+" < "+a+") ? -1 : 1 )));";}else if(type=='numeric'&&direction=='asc'){return"("+a+" === null && "+b+" === null) ? 0 :("+a+" === null ? Number.POSITIVE_INFINITY : ("+b+" === null ? Number.NEGATIVE_INFINITY : "+a+" - "+b+"));";}else if(type=='numeric'&&direction=='desc'){return"("+a+" === null && "+b+" === null) ? 0 :("+a+" === null ? Number.POSITIVE_INFINITY : ("+b+" === null ? Number.NEGATIVE_INFINITY : "+b+" - "+a+"));";}};function makeSortText(i){return"((a["+i+"] < b["+i+"]) ? -1 : ((a["+i+"] > b["+i+"]) ? 1 : 0));";};function makeSortTextDesc(i){return"((b["+i+"] < a["+i+"]) ? -1 : ((b["+i+"] > a["+i+"]) ? 1 : 0));";};function makeSortNumeric(i){return"a["+i+"]-b["+i+"];";};function makeSortNumericDesc(i){return"b["+i+"]-a["+i+"];";};function sortText(a,b){if(table.config.sortLocaleCompare)return a.localeCompare(b);return((a<b)?-1:((a>b)?1:0));};function sortTextDesc(a,b){if(table.config.sortLocaleCompare)return b.localeCompare(a);return((b<a)?-1:((b>a)?1:0));};function sortNumeric(a,b){return a-b;};function sortNumericDesc(a,b){return b-a;};function getCachedSortType(parsers,i){return parsers[i].type;};this.construct=function(settings){return this.each(function(){if(!this.tHead||!this.tBodies)return;var $this,$document,$headers,cache,config,shiftDown=0,sortOrder;this.config={};config=$.extend(this.config,$.tablesorter.defaults,settings);$this=$(this);$.data(this,"tablesorter",config);$headers=buildHeaders(this);this.config.parsers=buildParserCache(this,$headers);cache=buildCache(this);var sortCSS=[config.cssDesc,config.cssAsc];fixColumnWidth(this);$headers.click(function(e){var totalRows=($this[0].tBodies[0]&&$this[0].tBodies[0].rows.length)||0;if(!this.sortDisabled&&totalRows>0){$this.trigger("sortStart");var $cell=$(this);var i=this.column;this.order=this.count++%2;if(this.lockedOrder)this.order=this.lockedOrder;if(!e[config.sortMultiSortKey]){config.sortList=[];if(config.sortForce!=null){var a=config.sortForce;for(var j=0;j<a.length;j++){if(a[j][0]!=i){config.sortList.push(a[j]);}}}config.sortList.push([i,this.order]);}else{if(isValueInArray(i,config.sortList)){for(var j=0;j<config.sortList.length;j++){var s=config.sortList[j],o=config.headerList[s[0]];if(s[0]==i){o.count=s[1];o.count++;s[1]=o.count%2;}}}else{config.sortList.push([i,this.order]);}};setTimeout(function(){setHeadersCss($this[0],$headers,config.sortList,sortCSS);appendToTable($this[0],multisort($this[0],config.sortList,cache));},1);return false;}}).mousedown(function(){if(config.cancelSelection){this.onselectstart=function(){return false};return false;}});$this.bind("update",function(){var me=this;setTimeout(function(){me.config.parsers=buildParserCache(me,$headers);cache=buildCache(me);},1);}).bind("updateCell",function(e,cell){var config=this.config;var pos=[(cell.parentNode.rowIndex-1),cell.cellIndex];cache.normalized[pos[0]][pos[1]]=config.parsers[pos[1]].format(getElementText(config,cell),cell);}).bind("sorton",function(e,list){$(this).trigger("sortStart");config.sortList=list;var sortList=config.sortList;updateHeaderSortCount(this,sortList);setHeadersCss(this,$headers,sortList,sortCSS);appendToTable(this,multisort(this,sortList,cache));}).bind("appendCache",function(){appendToTable(this,cache);}).bind("applyWidgetId",function(e,id){getWidgetById(id).format(this);}).bind("applyWidgets",function(){applyWidget(this);});if($.metadata&&($(this).metadata()&&$(this).metadata().sortlist)){config.sortList=$(this).metadata().sortlist;}if(config.sortList.length>0){$this.trigger("sorton",[config.sortList]);}applyWidget(this);});};this.addParser=function(parser){var l=parsers.length,a=true;for(var i=0;i<l;i++){if(parsers[i].id.toLowerCase()==parser.id.toLowerCase()){a=false;}}if(a){parsers.push(parser);};};this.addWidget=function(widget){widgets.push(widget);};this.formatFloat=function(s){var i=parseFloat(s);return(isNaN(i))?0:i;};this.formatInt=function(s){var i=parseInt(s);return(isNaN(i))?0:i;};this.isDigit=function(s,config){return/^[-+]?\d*$/.test($.trim(s.replace(/[,.']/g,'')));};this.clearTableBody=function(table){if($.browser.msie){function empty(){while(this.firstChild)this.removeChild(this.firstChild);}empty.apply(table.tBodies[0]);}else{table.tBodies[0].innerHTML="";}};}});$.fn.extend({tablesorter:$.tablesorter.construct});var ts=$.tablesorter;ts.addParser({id:"text",is:function(s){return true;},format:function(s){return $.trim(s.toLocaleLowerCase());},type:"text"});ts.addParser({id:"digit",is:function(s,table){var c=table.config;return $.tablesorter.isDigit(s,c);},format:function(s){return $.tablesorter.formatFloat(s);},type:"numeric"});ts.addParser({id:"currency",is:function(s){return/^[£$€?.]/.test(s);},format:function(s){return $.tablesorter.formatFloat(s.replace(new RegExp(/[£$€]/g),""));},type:"numeric"});ts.addParser({id:"ipAddress",is:function(s){return/^\d{2,3}[\.]\d{2,3}[\.]\d{2,3}[\.]\d{2,3}$/.test(s);},format:function(s){var a=s.split("."),r="",l=a.length;for(var i=0;i<l;i++){var item=a[i];if(item.length==2){r+="0"+item;}else{r+=item;}}return $.tablesorter.formatFloat(r);},type:"numeric"});ts.addParser({id:"url",is:function(s){return/^(https?|ftp|file):\/\/$/.test(s);},format:function(s){return jQuery.trim(s.replace(new RegExp(/(https?|ftp|file):\/\//),''));},type:"text"});ts.addParser({id:"isoDate",is:function(s){return/^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(s);},format:function(s){return $.tablesorter.formatFloat((s!="")?new Date(s.replace(new RegExp(/-/g),"/")).getTime():"0");},type:"numeric"});ts.addParser({id:"percent",is:function(s){return/\%$/.test($.trim(s));},format:function(s){return $.tablesorter.formatFloat(s.replace(new RegExp(/%/g),""));},type:"numeric"});ts.addParser({id:"usLongDate",is:function(s){return s.match(new RegExp(/^[A-Za-z]{3,10}\.? [0-9]{1,2}, ([0-9]{4}|'?[0-9]{2}) (([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(AM|PM)))$/));},format:function(s){return $.tablesorter.formatFloat(new Date(s).getTime());},type:"numeric"});ts.addParser({id:"shortDate",is:function(s){return/\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}/.test(s);},format:function(s,table){var c=table.config;s=s.replace(/\-/g,"/");if(c.dateFormat=="us"){s=s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/,"$3/$1/$2");}else if(c.dateFormat=="uk"){s=s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/,"$3/$2/$1");}else if(c.dateFormat=="dd/mm/yy"||c.dateFormat=="dd-mm-yy"){s=s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2})/,"$1/$2/$3");}return $.tablesorter.formatFloat(new Date(s).getTime());},type:"numeric"});ts.addParser({id:"time",is:function(s){return/^(([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(am|pm)))$/.test(s);},format:function(s){return $.tablesorter.formatFloat(new Date("2000/01/01 "+s).getTime());},type:"numeric"});ts.addParser({id:"metadata",is:function(s){return false;},format:function(s,table,cell){var c=table.config,p=(!c.parserMetadataName)?'sortValue':c.parserMetadataName;return $(cell).metadata()[p];},type:"numeric"});ts.addWidget({id:"zebra",format:function(table){if(table.config.debug){var time=new Date();}var $tr,row=-1,odd;$("tr:visible",table.tBodies[0]).each(function(i){$tr=$(this);if(!$tr.hasClass(table.config.cssChildRow))row++;odd=(row%2==0);$tr.removeClass(table.config.widgetZebra.css[odd?0:1]).addClass(table.config.widgetZebra.css[odd?1:0])});if(table.config.debug){$.tablesorter.benchmark("Applying Zebra widget",time);}}});})(jQuery);

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/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'

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

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type='text/xsl' href='{$BaseHref}googlesitemaps/templates/xml-sitemap.xsl'?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<% loop $Items %>
<url>
<loc>$AbsoluteLink</loc>
<% if $LastEdited %><lastmod>$LastEdited.Format(c)</lastmod><% end_if %>
<% if $ChangeFrequency %><changefreq>$ChangeFrequency</changefreq><% end_if %>
<% if $GooglePriority %><priority>$GooglePriority</priority><% end_if %>
</url>
<% end_loop %>
</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

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

View File

@ -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

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

View File

@ -1,324 +1,348 @@
<?php
/**
* @package googlesitemaps
* @subpackage tests
*/
class GoogleSitemapTest extends FunctionalTest {
namespace Wilr\GoogleSitemaps\Tests;
public static $fixture_file = 'googlesitemaps/tests/GoogleSitemapTest.yml';
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;
protected $extraDataObjects = array(
'GoogleSitemapTest_DataObject',
'GoogleSitemapTest_OtherDataObject',
'GoogleSitemapTest_UnviewableDataObject'
);
class GoogleSitemapTest extends FunctionalTest
{
protected static $fixture_file = [
'GoogleSitemapTest.yml'
];
public function setUp() {
parent::setUp();
protected $usesDatabase = true;
if(class_exists('Page')) {
$this->loadFixture('googlesitemaps/tests/GoogleSitemapPageTest.yml');
}
GoogleSitemap::clear_registered_dataobjects();
GoogleSitemap::clear_registered_routes();
}
public static function get_fixture_file()
{
$files = [__DIR__ . '/GoogleSitemapTest.yml'];
public function tearDown() {
parent::tearDown();
if (class_exists('Page')) {
$files[] = __DIR__ . '/GoogleSitemapPageTest.yml';
}
GoogleSitemap::clear_registered_dataobjects();
GoogleSitemap::clear_registered_routes();
}
return $files;
}
public function testIndexFileWithCustomRoute() {
GoogleSitemap::register_route('/test/');
protected static $extra_dataobjects = [
TestDataObject::class,
OtherDataObject::class,
UnviewableDataObject::class
];
$response = $this->get('sitemap.xml');
$body = $response->getBody();
protected static $extra_extensions = [
GoogleSitemapExtension::class
];
$expected = "<loc>". Director::absoluteURL("sitemap.xml/sitemap/GoogleSitemapRoute/1") ."</loc>";
$this->assertEquals(1, substr_count($body, $expected) , 'A link to the custom routes exists');
}
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);
public function testGetItems() {
GoogleSitemap::register_dataobject("GoogleSitemapTest_DataObject", '');
$obj = $this->objFromFixture(TestDataObject::class, 'DataObjectTest1');
$obj1 = $this->objFromFixture(TestDataObject::class, 'DataObjectTest2');
$obj2 = $this->objFromFixture(TestDataObject::class, 'UnindexedDataObject');
$items = GoogleSitemap::get_items('GoogleSitemapTest_DataObject', 1);
$this->assertEquals(2, $items->count());
$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 . "'");
$this->assertDOSEquals(array(
array("Priority" => "0.2"),
array("Priority" => "0.4")
), $items);
GoogleSitemap::register_dataobject("GoogleSitemapTest_OtherDataObject");
$this->assertEquals(1, GoogleSitemap::get_items('GoogleSitemapTest_OtherDataObject', 1)->count());
$response = $this->get('sitemap.xml');
$body = $response->getBody();
$this->assertXmlStringEqualsXmlFile(__DIR__ . '/xml/' . __FUNCTION__ . '.xml', $body);
GoogleSitemap::register_dataobject("GoogleSitemapTest_UnviewableDataObject");
$this->assertEquals(0, GoogleSitemap::get_items('GoogleSitemapTest_UnviewableDataObject', 1)->count());
}
Config::inst()->set(GoogleSitemap::class, 'objects_per_sitemap', $original);
}
public function testGetItemsWithCustomRoutes() {
GoogleSitemap::register_routes(array(
'/test-route/',
'/someother-route/',
'/fake-sitemap-route/'
));
public function testRegisterRoutesIncludesAllRoutes(): void
{
GoogleSitemap::register_route('/test/');
GoogleSitemap::register_routes([
'/test/', // duplication should be replaced
'/unittests/',
'/anotherlink/'
], 'weekly');
$items = GoogleSitemap::get_items('GoogleSitemapRoute', 1);
$this->assertEquals(3, $items->count());
}
$response = $this->get('sitemap.xml/sitemap/GoogleSitemapRoute/1');
$body = $response->getBody();
public function testAccessingSitemapRootXMLFile() {
GoogleSitemap::register_dataobject("GoogleSitemapTest_DataObject");
GoogleSitemap::register_dataobject("GoogleSitemapTest_OtherDataObject");
$this->assertEquals(200, $response->getStatusCode(), 'successful loaded nested sitemap');
$this->assertEquals(3, substr_count($body, "<loc>"));
}
$response = $this->get('sitemap.xml');
$body = $response->getBody();
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);
// the sitemap should contain <loc> to both those files and not the other
// dataobject as it hasn't been registered
$expected = "<loc>". Director::absoluteURL("sitemap.xml/sitemap/GoogleSitemapTest_DataObject/1") ."</loc>";
$this->assertEquals(1, substr_count($body, $expected) , 'A link to GoogleSitemapTest_DataObject exists');
$expected = "<loc>". Director::absoluteURL("sitemap.xml/sitemap/GoogleSitemapTest_OtherDataObject/1") ."</loc>";
$this->assertEquals(1, substr_count($body, $expected) , 'A link to GoogleSitemapTest_OtherDataObject exists');
$response = $this->get('sitemap.xml/sitemap/Wilr-GoogleSitemaps-Tests-Model-TestDataObject/1');
$body = $response->getBody();
$expected = "<loc>". Director::absoluteURL("sitemap.xml/sitemap/GoogleSitemapTest_UnviewableDataObject/2") ."</loc>";
$this->assertEquals(0, substr_count($body, $expected) , 'A link to a GoogleSitemapTest_UnviewableDataObject does not exist');
}
$this->assertEquals(200, $response->getStatusCode(), 'successful loaded nested sitemap');
public function testLastModifiedDateOnRootXML() {
GoogleSitemap::register_dataobject("GoogleSitemapTest_DataObject");
Config::inst()->set(GoogleSitemap::class, 'objects_per_sitemap', $original);
}
DB::query("
UPDATE GoogleSitemapTest_DataObject SET LastEdited = '2012-01-14'"
);
public function testGetItemsWithPages(): void
{
if (!class_exists(SiteTree::class)) {
$this->markTestIncomplete('No cms module installed, page related test skipped');
}
$response = $this->get('sitemap.xml');
$body = $response->getBody();
$page = $this->objFromFixture('Page', 'Page1');
$page->publishSingle();
$page->flushCache();
$expected = "<lastmod>2012-01-14</lastmod>";
$this->assertEquals(1, substr_count($body, $expected));
}
$page2 = $this->objFromFixture('Page', 'Page2');
$page2->publishSingle();
$page2->flushCache();
public function testIndexFilePaginatedSitemapFiles() {
$original = Config::inst()->get('GoogleSitemap', 'objects_per_sitemap');
Config::inst()->update('GoogleSitemap', 'objects_per_sitemap', 1);
GoogleSitemap::register_dataobject("GoogleSitemapTest_DataObject");
$this->assertListContains([
['Title' => 'Testpage1'],
['Title' => 'Testpage2']
], GoogleSitemap::inst()->getItems(SiteTree::class), "There should be 2 pages in the sitemap after publishing");
$response = $this->get('sitemap.xml');
$body = $response->getBody();
$expected = "<loc>". Director::absoluteURL("sitemap.xml/sitemap/GoogleSitemapTest_DataObject/1") ."</loc>";
$this->assertEquals(1, substr_count($body, $expected) , 'A link to the first page of GoogleSitemapTest_DataObject exists');
// check if we make a page readonly that it is hidden
$page2->CanViewType = 'LoggedInUsers';
$page2->write();
$page2->publishSingle();
$expected = "<loc>". Director::absoluteURL("sitemap.xml/sitemap/GoogleSitemapTest_DataObject/2") ."</loc>";
$this->assertEquals(1, substr_count($body, $expected) , 'A link to the second page GoogleSitemapTest_DataObject exists');
$this->logOut();
Config::inst()->update('GoogleSitemap', 'objects_per_sitemap', $original);
}
$this->assertListEquals([
['Title' => 'Testpage1']
], GoogleSitemap::inst()->getItems(SiteTree::class), "There should be only 1 page, other is logged in only");
}
public function testRegisterRoutesIncludesAllRoutes() {
GoogleSitemap::register_route('/test/');
GoogleSitemap::register_routes(array(
'/test/', // duplication should be replaced
'/unittests/',
'/anotherlink/'
), 'weekly');
public function testAccess(): void
{
Config::inst()->set(GoogleSitemap::class, 'enabled', true);
$response = $this->get('sitemap.xml/sitemap/GoogleSitemapRoute/1');
$body = $response->getBody();
$response = $this->get('sitemap.xml');
$this->assertEquals(200, $response->getStatusCode(), 'successful loaded nested sitemap');
$this->assertEquals(3, substr_count($body, "<loc>"));
}
$this->assertEquals(200, $response->getStatusCode(), 'Sitemap returns a 200 success when enabled');
$this->assertEquals('application/xml; charset="utf-8"', $response->getHeader('Content-Type'));
public function testAccessingNestedSiteMap() {
$original = Config::inst()->get('GoogleSitemap', 'objects_per_sitemap');
Config::inst()->update('GoogleSitemap', 'objects_per_sitemap', 1);
GoogleSitemap::register_dataobject("GoogleSitemapTest_DataObject");
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'));
$response = $this->get('sitemap.xml/sitemap/GoogleSitemapTest_DataObject/1');
$body = $response->getBody();
Config::inst()->set(GoogleSitemap::class, 'enabled', false);
$this->assertEquals(200, $response->getStatusCode(), 'successful loaded nested sitemap');
$response = $this->get('sitemap.xml');
$this->assertEquals(404, $response->getStatusCode(), 'Sitemap index returns a 404 when disabled');
Config::inst()->update('GoogleSitemap', 'objects_per_sitemap', $original);
}
$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 testGetItemsWithPages() {
if(!class_exists('Page')) {
$this->markTestIncomplete('No cms module installed, page related test skipped');
}
$page = $this->objFromFixture('Page', 'Page1');
$page->publish('Stage', 'Live');
$page->flushCache();
$page2 = $this->objFromFixture('Page', 'Page2');
$page2->publish('Stage', 'Live');
$page2->flushCache();
public function testDecoratorAddsFields(): void
{
if (!class_exists("Page")) {
$this->markTestIncomplete('No cms module installed, page related test skipped');
}
$this->assertDOSContains(array(
array('Title' => 'Testpage1'),
array('Title' => 'Testpage2')
), GoogleSitemap::get_items('SiteTree'), "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->publish('Stage', 'Live');
$this->session()->inst_set('loggedInAs', null);
$this->assertDOSEquals(array(
array('Title' => 'Testpage1')
), GoogleSitemap::get_items('SiteTree'), "There should be only 1 page, other is logged in only");
}
public function testAccess() {
Config::inst()->update('GoogleSitemap', 'enabled', true);
$response = $this->get('sitemap.xml');
$page = $this->objFromFixture('Page', 'Page1');
$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("GoogleSitemapTest_DataObject");
$response = $this->get('sitemap.xml/sitemap/GoogleSitemapTest_DataObject/1');
$this->assertEquals(200, $response->getStatusCode(), 'Sitemap returns a 200 success when enabled');
$this->assertEquals('application/xml; charset="utf-8"', $response->getHeader('Content-Type'));
$fields = $page->getSettingsFields();
$tab = $fields->fieldByName('Root')->fieldByName('Settings')->fieldByName('GoogleSitemap');
Config::inst()->remove('GoogleSitemap', 'enabled');
Config::inst()->update('GoogleSitemap', 'enabled', false);
$response = $this->get('sitemap.xml');
$this->assertEquals(404, $response->getStatusCode(), 'Sitemap index returns a 404 when disabled');
$this->assertInstanceOf(Tab::class, $tab);
$this->assertInstanceOf(DropdownField::class, $tab->fieldByName('Priority'));
$this->assertInstanceOf(LiteralField::class, $tab->fieldByName('GoogleSitemapIntro'));
}
$response = $this->get('sitemap.xml/sitemap/GoogleSitemapTest_DataObject/1');
$this->assertEquals(404, $response->getStatusCode(), 'Sitemap file returns a 404 when disabled');
}
public function testDecoratorAddsFields() {
if(!class_exists("Page")) {
$this->markTestIncomplete('No cms module installed, page related test skipped');
}
public function testGetPriority(): 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', $tab);
$this->assertInstanceOf('DropdownField', $tab->fieldByName('Priority'));
$this->assertInstanceOf('LiteralField', $tab->fieldByName('GoogleSitemapIntro'));
}
public function testGetPriority() {
if(!class_exists("Page")) {
$this->markTestIncomplete('No cms module installed, page related test skipped');
}
$page = $this->objFromFixture('Page', 'Page1');
$page = $this->objFromFixture('Page', 'Page1');
// invalid field doesn't break google
$page->Priority = 'foo';
$this->assertEquals(0.5, $page->getGooglePriority());
// -1 indicates that we should not index this
$page->Priority = -1;
$this->assertFalse($page->getGooglePriority());
}
public function testUnpublishedPage() {
if(!class_exists('SiteTree')) {
$this->markTestSkipped('Test skipped; CMS module required for testUnpublishedPage');
}
$orphanedPage = new SiteTree();
$orphanedPage->ParentID = 999999; // missing parent id
$orphanedPage->write();
$orphanedPage->publish("Stage", "Live");
$rootPage = new SiteTree();
$rootPage->ParentID = 0;
$rootPage->write();
$rootPage->publish("Stage", "Live");
$oldMode = Versioned::get_reading_mode();
Versioned::reading_stage('Live');
try {
$this->assertEmpty($orphanedPage->hasPublishedParent());
$this->assertEmpty($orphanedPage->canIncludeInGoogleSitemap());
$this->assertNotEmpty($rootPage->hasPublishedParent());
$this->assertNotEmpty($rootPage->canIncludeInGoogleSitemap());
} catch(Exception $ex) {
Versioned::set_reading_mode($oldMode);
throw $ex;
} // finally {
Versioned::set_reading_mode($oldMode);
// }
}
}
/**
* @package googlesitemaps
* @subpackage tests
*/
class GoogleSitemapTest_DataObject extends DataObject implements TestOnly {
public static $db = array(
'Priority' => 'Varchar(10)'
);
public function canView($member = null) {
return true;
}
public function AbsoluteLink() {
return Director::absoluteBaseURL();
}
}
/**
* @package googlesitemaps
* @subpackage tests
*/
class GoogleSitemapTest_OtherDataObject extends DataObject implements TestOnly {
public static $db = array(
'Priority' => 'Varchar(10)'
);
public function canView($member = null) {
return true;
}
public function AbsoluteLink() {
return Director::absoluteBaseURL();
}
}
/**
* @package googlesitemaps
* @subpackage tests
*/
class GoogleSitemapTest_UnviewableDataObject extends DataObject implements TestOnly {
public static $db = array(
'Priority' => 'Varchar(10)'
);
public function canView($member = null) {
return false;
}
public function AbsoluteLink() {
return Director::absoluteBaseURL();
}
// 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

@ -1,15 +1,15 @@
GoogleSitemapTest_DataObject:
DataObjectTest1:
Priority: 0.4
DataObjectTest2:
Priority: 0.2
UnindexedDataObject:
Priority: -1
Wilr\GoogleSitemaps\Tests\Model\TestDataObject:
DataObjectTest1:
Priority: 0.4
DataObjectTest2:
Priority: 0.2
UnindexedDataObject:
Priority: -1
GoogleSitemapTest_OtherDataObject:
OtherDataObjectTest2:
Priority: 0.3
Wilr\GoogleSitemaps\Tests\Model\OtherDataObject:
OtherDataObjectTest2:
Priority: 0.3
GoogleSitemapTest_UnviewableDataObject:
Unviewable1:
Priority: 0.4
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>