This commit is contained in:
Damian Mooyman 2014-04-24 00:35:04 +00:00
commit 41c1e48597
9 changed files with 112 additions and 45 deletions

View File

@ -24,6 +24,8 @@ Or just clone/download the git repository into a subfolder (usually called "vers
For usage instructions see [user manual](docs/en/user.md).
For developer-level instructions see [developer manual](docs/en/developer.md).
## Contributing
### Translations

View File

@ -1,7 +0,0 @@
<?php
SiteTree::add_extension('VersionFeed');
ContentController::add_extension('VersionFeed_Controller');
// Set the cache lifetime to 5 mins.
SS_Cache::set_cache_lifetime('VersionFeed_Controller', 5*60);

25
_config/versionfeed.yml Normal file
View File

@ -0,0 +1,25 @@
---
Name: versionedfeedconfig
---
SiteTree:
extensions:
- VersionFeed
ContentController:
extensions:
- VersionFeed_Controller
---
Name: versionedfeedpolicy
Only:
ModuleExists: policyfilter
---
ContentController:
extensions:
- FilteredExtension
dependencies:
ContentFilter: %$ContentFilter
filter_policy:
rate_lock_timeout: 10
rate_lock_byitem: true
rate_lock_byuserip: false
cache_lifetime: 3600
cache_factoryname: 'VersionFeed_Cache'

View File

@ -3,7 +3,7 @@
class VersionFeed extends SiteTreeExtension {
private static $db = array(
'PublicHistory' => 'Boolean'
'PublicHistory' => 'Boolean(true)'
);
private static $defaults = array(

View File

@ -6,8 +6,20 @@ class VersionFeed_Controller extends Extension {
'changes',
'allchanges'
);
/**
* Evaluates the result of the given callback
*
* @param string $key Unique key for this
* @param callable $callback Callback for evaluating the content
* @return mixed Result of $callback()
*/
protected function getContent($key, $callback) {
$result = $this->owner->extend('filterContent', $key, $callback);
return reset($result) ?: call_user_func($callback);
}
function onAfterInit() {
public function onAfterInit() {
// RSS feed for per-page changes.
if ($this->owner->PublicHistory) {
RSSFeed::linkToFeed($this->owner->Link() . 'changes',
@ -26,18 +38,15 @@ class VersionFeed_Controller extends Extension {
/**
* Get page-specific changes in a RSS feed.
*/
function changes() {
if(!$this->owner->PublicHistory) throw new SS_HTTPResponse_Exception('Page history not viewable', 404);;
public function changes() {
if(!$this->owner->PublicHistory) throw new SS_HTTPResponse_Exception('Page history not viewable', 404);
// Cache the diffs to remove DOS possibility.
$cache = SS_Cache::factory('VersionFeed_Controller');
$cache->setOption('automatic_serialization', true);
$target = $this->owner;
$key = implode('_', array('changes', $this->owner->ID, $this->owner->Version));
$entries = $cache->load($key);
if(!$entries || isset($_GET['flush'])) {
$entries = $this->owner->getDiffedChanges();
$cache->save($entries, $key);
}
$entries = $this->getContent($key, function() use ($target) {
return $target->getDiffedChanges();
});
// Generate the output.
$title = sprintf(_t('RSSHistory.SINGLEPAGEFEEDTITLE', 'Updates to %s page'), $this->owner->Title);
@ -49,42 +58,47 @@ class VersionFeed_Controller extends Extension {
/**
* Get all changes from the site in a RSS feed.
*/
function allchanges() {
public function allchanges() {
$latestChanges = DB::query('SELECT * FROM "SiteTree_versions" WHERE "WasPublished"=\'1\' AND "CanViewType" IN (\'Anyone\', \'Inherit\') AND "ShowInSearch"=1 AND ("PublicHistory" IS NULL OR "PublicHistory" = \'1\') ORDER BY "LastEdited" DESC LIMIT 20');
$latestChanges = DB::query('
SELECT * FROM "SiteTree_versions"
WHERE "WasPublished" = \'1\'
AND "CanViewType" IN (\'Anyone\', \'Inherit\')
AND "ShowInSearch" = 1
AND ("PublicHistory" IS NULL OR "PublicHistory" = \'1\')
ORDER BY "LastEdited" DESC LIMIT 20'
);
$lastChange = $latestChanges->record();
$latestChanges->rewind();
if ($lastChange) {
// Cache the diffs to remove DOS possibility.
$member = Member::currentUser();
$cache = SS_Cache::factory('VersionFeed_Controller');
$cache->setOption('automatic_serialization', true);
$key = 'allchanges' . preg_replace('#[^a-zA-Z0-9_]#', '', $lastChange['LastEdited']) .
($member ? $member->ID : 'public');
$changeList = $cache->load($key);
if(!$changeList || isset($_GET['flush'])) {
$key = 'allchanges'
. preg_replace('#[^a-zA-Z0-9_]#', '', $lastChange['LastEdited'])
. (Member::currentUserID() ?: 'public');
$changeList = $this->getContent($key, function() use ($latestChanges) {
$changeList = new ArrayList();
$canView = array();
foreach ($latestChanges as $record) {
// Check if the page should be visible.
// WARNING: although we are providing historical details, we check the current configuration.
$page = SiteTree::get()->filter(array('ID'=>$record['RecordID']))->First();
if (!$page->canView(new Member())) continue;
$id = $record['RecordID'];
if(!isset($canView[$id])) {
$page = SiteTree::get()->byID($id);
$canView[$id] = $page && $page->canView(new Member());
}
if (!$canView[$id]) continue;
// Get the diff to the previous version.
$version = new Versioned_Version($record);
$changes = $version->getDiffedChanges($version->Version, false);
if ($changes && $changes->Count()) $changeList->push($changes->First());
}
$cache->save($changeList, $key);
}
return $changeList;
});
} else {
$changeList = new ArrayList();
}

View File

@ -14,5 +14,8 @@
{
"silverstripe/framework": "3.*",
"silverstripe/cms": "3.*"
},
"suggest": {
"silverstripe/policyfilter": "Provide caching and rate limiting of versioned content"
}
}
}

View File

@ -0,0 +1,8 @@
# 1.0.3
## Upgrading
* Added support for the [Policy Filter Module](http://github.com/silverstripe/silverstripe-policyfilter)
to apply rate limiting and caching of content.
See the [Developer Documentation](../developer.md) for information on how these affect
cache performance and behaviour.

View File

@ -21,3 +21,32 @@ and returning the URL of your desired RSS feed:
}
This can be used in templates as `$DefaultRSSLink`.
### Rate limiting and caching
If the [Policy Filter Module](http://github.com/silverstripe/silverstripe-policyfilter) has been installed then
additional rate limiting and caching functionality will be applied automatically. For more information on
how this modules works see the policy filter documentation.
By default all content is filtered based on the rules specified in `versionfeed/_config/versionfeed.yml`.
For large servers with a high level of traffic (more than 1 visits every 10 seconds) the default settings should
be sufficient.
For smaller servers where it's reasonable to apply a more strict approach to rate limiting, consider setting the
`ContentController.filter_policy.rate_lock_byitem` config to false, meaning that a single limit will be
applied to all URLs. If left on true, then each URL will have its own rate limit, and on smaller servers with lots of
concurrent requests this can still overwhelm capacity. This will also leave smaller servers vulnerable to DDoS
attacks which target many URLs simultaneously. This config will have no effect on the `allchanges` method.
`ContentController.filter_policy.rate_lock_byuserip` can also be set to true in order to prevent requests
from different users interfering with one another. However, this can provide an ineffective safeguard
against malicious DDoS attacks which use multiple IP addresses.
Another important variable is the `ContentController.filter_policy.rate_lock_timeout` config, which is set to 10
seconds by default. This should be increased on sites which may be slow to generate page versions, whether
due to lower server capacity or volume of content (number of page versions). Requests to this page after
the timeout will not trigger any rate limit safeguard, so you should be sure that this is set to an appropriate level.
The default cache of 1 hour can also be adjusted by setting the `ContentController.filter_policy.cache_lifetime`
to any time period in seconds.

View File

@ -6,13 +6,6 @@ class VersionFeedFunctionalTest extends FunctionalTest {
'Page_Controller' => array('VersionFeed_Controller'),
);
public function setUp() {
parent::setUp();
$cache = SS_Cache::factory('VersionFeed_Controller');
$cache->clean(Zend_Cache::CLEANING_MODE_ALL);
}
public function testPublicHistory() {
$page = $this->createPageWithChanges(array('PublicHistory' => false));
@ -101,4 +94,4 @@ class VersionFeedFunctionalTest extends FunctionalTest {
return $page;
}
}
}