diff --git a/.gitattributes b/.gitattributes index 475f5f2..89eb187 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,3 +4,4 @@ /.gitignore export-ignore /.travis.yml export-ignore /.scrutinizer.yml export-ignore +/codecov.yml export-ignore diff --git a/.travis.yml b/.travis.yml index 0af9272..9dedfe6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,6 @@ before_script: # Install composer dependencies - composer validate - - composer require --no-update silverstripe/recipe-core:1.0.x-dev - if [[ $DB == PGSQL ]]; then composer require --no-update silverstripe/postgresql 2.0.x-dev; fi - composer install --prefer-dist --no-interaction --no-progress --no-suggest --optimize-autoloader --verbose --profile diff --git a/composer.json b/composer.json index d3a548c..8839485 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,9 @@ ], "require": { - "silverstripe/recipe-cms": "^1" + "silverstripe/cms": "^4", + "silverstripe/versioned": "^1", + "silverstripe/siteconfig": "^4" }, "require-dev": { "phpunit/phpunit": "^5.7", diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 004ca3a..8a99dd1 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,4 +1,4 @@ - + tests/ diff --git a/src/Filters/CachedContentFilter.php b/src/Filters/CachedContentFilter.php index dc9d0af..8119e4b 100644 --- a/src/Filters/CachedContentFilter.php +++ b/src/Filters/CachedContentFilter.php @@ -2,39 +2,39 @@ namespace SilverStripe\VersionFeed\Filters; - use SilverStripe\Core\Config\Config; - - - /** * Caches results of a callback */ -class CachedContentFilter extends ContentFilter { - - /** - * Enable caching - * - * @config - * @var boolean - */ - private static $cache_enabled = true; - - public function getContent($key, $callback) { - $cache = $this->getCache(); - - // Return cached value if available - $cacheEnabled = Config::inst()->get(get_class(), 'cache_enabled'); - $result = (isset($_GET['flush']) || !$cacheEnabled) - ? null - : $cache->get($key); - if($result) return $result; - - // Fallback to generate result - $result = parent::getContent($key, $callback); - $lifetime = Config::inst()->get(ContentFilter::class, 'cache_lifetime') ?: null; - $cache->set($key, $result, $lifetime); - return $result; - } +class CachedContentFilter extends ContentFilter +{ + + /** + * Enable caching + * + * @config + * @var boolean + */ + private static $cache_enabled = true; + + public function getContent($key, $callback) + { + $cache = $this->getCache(); + + // Return cached value if available + $cacheEnabled = Config::inst()->get(get_class(), 'cache_enabled'); + $result = (isset($_GET['flush']) || !$cacheEnabled) + ? null + : $cache->get($key); + if ($result) { + return $result; + } + + // Fallback to generate result + $result = parent::getContent($key, $callback); + $lifetime = Config::inst()->get(ContentFilter::class, 'cache_lifetime') ?: null; + $cache->set($key, $result, $lifetime); + return $result; + } } diff --git a/src/Filters/ContentFilter.php b/src/Filters/ContentFilter.php index 0bda97d..8481eb7 100644 --- a/src/Filters/ContentFilter.php +++ b/src/Filters/ContentFilter.php @@ -7,58 +7,60 @@ use SilverStripe\Core\Config\Configurable; use Psr\SimpleCache\CacheInterface; use SilverStripe\Core\Injector\Injector; - - /** * Conditionally executes a given callback, attempting to return the desired results * of its execution. */ -abstract class ContentFilter { +abstract class ContentFilter +{ - use Configurable; - - /** - * Nested content filter - * - * @var ContentFilter - */ - protected $nestedContentFilter; + use Configurable; + + /** + * Nested content filter + * + * @var ContentFilter + */ + protected $nestedContentFilter; - /** - * Cache lifetime - * - * @config - * @var int - */ - private static $cache_lifetime = 300; - - public function __construct($nestedContentFilter = null) { - $this->nestedContentFilter = $nestedContentFilter; - } - - /** - * Gets the cache to use - * - * @return Zend_Cache_Frontend - */ - protected function getCache() { - return Injector::inst()->get( - CacheInterface::class . '.VersionFeedController' - ); - } - - /** - * 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() - */ - public function getContent($key, $callback) { - if($this->nestedContentFilter) { - return $this->nestedContentFilter->getContent($key, $callback); - } else { - return call_user_func($callback); - } - } + /** + * Cache lifetime + * + * @config + * @var int + */ + private static $cache_lifetime = 300; + + public function __construct($nestedContentFilter = null) + { + $this->nestedContentFilter = $nestedContentFilter; + } + + /** + * Gets the cache to use + * + * @return Zend_Cache_Frontend + */ + protected function getCache() + { + return Injector::inst()->get( + CacheInterface::class . '.VersionFeedController' + ); + } + + /** + * 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() + */ + public function getContent($key, $callback) + { + if ($this->nestedContentFilter) { + return $this->nestedContentFilter->getContent($key, $callback); + } else { + return call_user_func($callback); + } + } } diff --git a/src/Filters/RateLimitFilter.php b/src/Filters/RateLimitFilter.php index 8b2c536..aecda75 100644 --- a/src/Filters/RateLimitFilter.php +++ b/src/Filters/RateLimitFilter.php @@ -2,123 +2,120 @@ namespace SilverStripe\VersionFeed\Filters; - - - use SilverStripe\Core\Config\Config; use SilverStripe\Control\Controller; use SilverStripe\Control\HTTPResponse_Exception; - - - /** * Provides rate limiting of execution of a callback */ -class RateLimitFilter extends ContentFilter { - - /** - * Time duration (in second) to allow for generation of cached results. Requests to - * pages that within this time period that do not hit the cache (and would otherwise trigger - * a version query) will be presented with a 429 (rate limit) HTTP error - * - * @config - * @var int - */ - private static $lock_timeout = 5; - - /** - * Determine if the cache generation should be locked on a per-page basis. If true, concurrent page versions - * may be generated without rate interference. - * - * @config - * @var bool - */ - private static $lock_bypage = false; - - /** - * Determine if rate limiting should be applied independently to each IP address. This method is not - * reliable, as most DDoS attacks use multiple IP addresses. - * - * @config - * @var bool - */ - private static $lock_byuserip = false; +class RateLimitFilter extends ContentFilter +{ + + /** + * Time duration (in second) to allow for generation of cached results. Requests to + * pages that within this time period that do not hit the cache (and would otherwise trigger + * a version query) will be presented with a 429 (rate limit) HTTP error + * + * @config + * @var int + */ + private static $lock_timeout = 5; + + /** + * Determine if the cache generation should be locked on a per-page basis. If true, concurrent page versions + * may be generated without rate interference. + * + * @config + * @var bool + */ + private static $lock_bypage = false; + + /** + * Determine if rate limiting should be applied independently to each IP address. This method is not + * reliable, as most DDoS attacks use multiple IP addresses. + * + * @config + * @var bool + */ + private static $lock_byuserip = false; - /** - * Time duration (in sections) to deny further search requests after a successful search. - * Search requests within this time period while another query is in progress will be - * presented with a 429 (rate limit) - * - * @config - * @var int - */ - private static $lock_cooldown = 2; - - /** - * Cache key prefix - */ - const CACHE_PREFIX = 'RateLimitBegin'; - - /** - * Determines the key to use for saving the current rate - * - * @param string $itemkey Input key - * @return string Result key - */ - protected function getCacheKey($itemkey) { - $key = self::CACHE_PREFIX; - - // Add global identifier - if($this->config()->get('lock_bypage')) { - $key .= '_' . md5($itemkey); - } - - // Add user-specific identifier - if($this->config()->get('lock_byuserip') && Controller::has_curr()) { - $ip = Controller::curr()->getRequest()->getIP(); - $key .= '_' . md5($ip); - } - - return $key; - } + /** + * Time duration (in sections) to deny further search requests after a successful search. + * Search requests within this time period while another query is in progress will be + * presented with a 429 (rate limit) + * + * @config + * @var int + */ + private static $lock_cooldown = 2; + + /** + * Cache key prefix + */ + const CACHE_PREFIX = 'RateLimitBegin'; + + /** + * Determines the key to use for saving the current rate + * + * @param string $itemkey Input key + * @return string Result key + */ + protected function getCacheKey($itemkey) + { + $key = self::CACHE_PREFIX; + + // Add global identifier + if ($this->config()->get('lock_bypage')) { + $key .= '_' . md5($itemkey); + } + + // Add user-specific identifier + if ($this->config()->get('lock_byuserip') && Controller::has_curr()) { + $ip = Controller::curr()->getRequest()->getIP(); + $key .= '_' . md5($ip); + } + + return $key; + } - public function getContent($key, $callback) { - // Bypass rate limiting if flushing, or timeout isn't set - $timeout = $this->config()->get('lock_timeout'); - if(isset($_GET['flush']) || !$timeout) { - return parent::getContent($key, $callback); - } - - // Generate result with rate limiting enabled - $limitKey = $this->getCacheKey($key); - $cache = $this->getCache(); - if($lockedUntil = $cache->get($limitKey)) { - if(time() < $lockedUntil) { - // Politely inform visitor of limit - $response = new HTTPResponse_Exception('Too Many Requests.', 429); - $response->getResponse()->addHeader('Retry-After', 1 + $lockedUntil - time()); - throw $response; - } - } + public function getContent($key, $callback) + { + // Bypass rate limiting if flushing, or timeout isn't set + $timeout = $this->config()->get('lock_timeout'); + if (isset($_GET['flush']) || !$timeout) { + return parent::getContent($key, $callback); + } + + // Generate result with rate limiting enabled + $limitKey = $this->getCacheKey($key); + $cache = $this->getCache(); + if ($lockedUntil = $cache->get($limitKey)) { + if (time() < $lockedUntil) { + // Politely inform visitor of limit + $response = new HTTPResponse_Exception('Too Many Requests.', 429); + $response->getResponse()->addHeader('Retry-After', 1 + $lockedUntil - time()); + throw $response; + } + } - $lifetime = Config::inst()->get(ContentFilter::class, 'cache_lifetime') ?: null; - - // Apply rate limit - $cache->set($limitKey, time() + $timeout, $lifetime); - - // Generate results - $result = parent::getContent($key, $callback); + $lifetime = Config::inst()->get(ContentFilter::class, 'cache_lifetime') ?: null; + + // Apply rate limit + $cache->set($limitKey, time() + $timeout, $lifetime); + + // Generate results + $result = parent::getContent($key, $callback); - // Reset rate limit with optional cooldown - if($cooldown = $this->config()->get('lock_cooldown')) { - // Set cooldown on successful query execution - $cache->set($limitKey, time() + $cooldown, $lifetime); - } else { - // Without cooldown simply disable lock - $cache->delete($limitKey); - } - return $result; - } + // Reset rate limit with optional cooldown + if ($cooldown = $this->config()->get('lock_cooldown')) { + // Set cooldown on successful query execution + $cache->set($limitKey, time() + $cooldown, $lifetime); + } else { + // Without cooldown simply disable lock + $cache->delete($limitKey); + } + return $result; + } } diff --git a/src/VersionFeed.php b/src/VersionFeed.php index 2ad0c46..4dcd284 100644 --- a/src/VersionFeed.php +++ b/src/VersionFeed.php @@ -13,206 +13,215 @@ use SilverStripe\Forms\LiteralField; use SilverStripe\SiteConfig\SiteConfig; use SilverStripe\CMS\Model\SiteTreeExtension; -class VersionFeed extends SiteTreeExtension { - - private static $db = array( - 'PublicHistory' => 'Boolean(true)' - ); +class VersionFeed extends SiteTreeExtension +{ + + private static $db = array( + 'PublicHistory' => 'Boolean(true)' + ); - private static $defaults = array( - 'PublicHistory' => true - ); + private static $defaults = array( + 'PublicHistory' => true + ); - public function updateFieldLabels(&$labels) { - $labels['PublicHistory'] = _t('RSSHistory.LABEL', 'Make history public'); - } + public function updateFieldLabels(&$labels) + { + $labels['PublicHistory'] = _t('RSSHistory.LABEL', 'Make history public'); + } - /** - * Enable the allchanges feed - * - * @config - * @var bool - */ - private static $allchanges_enabled = true; + /** + * Enable the allchanges feed + * + * @config + * @var bool + */ + private static $allchanges_enabled = true; - /** - * Allchanges feed limit of items. - * - * @config - * @var int - */ - private static $allchanges_limit = 20; + /** + * Allchanges feed limit of items. + * + * @config + * @var int + */ + private static $allchanges_limit = 20; - /** - * Enables RSS feed for page-specific changes - * - * @config - * @var bool - */ - private static $changes_enabled = true; + /** + * Enables RSS feed for page-specific changes + * + * @config + * @var bool + */ + private static $changes_enabled = true; - /** - * Changes feed limit of items. - * - * @config - * @var int - */ - private static $changes_limit = 100; + /** + * Changes feed limit of items. + * + * @config + * @var int + */ + private static $changes_limit = 100; - /** - * Compile a list of changes to the current page, excluding non-published and explicitly secured versions. - * - * @param int $highestVersion Top version number to consider. - * @param int $limit Limit to the amount of items returned. - * - * @returns ArrayList List of cleaned records. - */ - public function getDiffList($highestVersion = null, $limit = 100) { - // This can leak secured content if it was protected via inherited setting. - // For now the users will need to be aware about this shortcoming. - $offset = $highestVersion ? "AND \"SiteTree_versions\".\"Version\"<='".(int)$highestVersion."'" : ''; - // Get just enough elements for diffing. We need one more than desired to have something to compare to. - $qLimit = (int)$limit + 1; - $versions = $this->owner->allVersions( - "\"WasPublished\"='1' AND \"CanViewType\" IN ('Anyone', 'Inherit') $offset", - "\"SiteTree\".\"LastEdited\" DESC, \"SiteTree\".\"ID\" DESC", - $qLimit - ); + /** + * Compile a list of changes to the current page, excluding non-published and explicitly secured versions. + * + * @param int $highestVersion Top version number to consider. + * @param int $limit Limit to the amount of items returned. + * + * @returns ArrayList List of cleaned records. + */ + public function getDiffList($highestVersion = null, $limit = 100) + { + // This can leak secured content if it was protected via inherited setting. + // For now the users will need to be aware about this shortcoming. + $offset = $highestVersion ? "AND \"SiteTree_Versions\".\"Version\"<='".(int)$highestVersion."'" : ''; + // Get just enough elements for diffing. We need one more than desired to have something to compare to. + $qLimit = (int)$limit + 1; + $versions = $this->owner->allVersions( + "\"WasPublished\"='1' AND \"CanViewType\" IN ('Anyone', 'Inherit') $offset", + "\"SiteTree\".\"LastEdited\" DESC, \"SiteTree\".\"ID\" DESC", + $qLimit + ); - // Process the list to add the comparisons. - $changeList = new ArrayList(); - $previous = null; - $count = 0; - foreach ($versions as $version) { - $changed = false; + // Process the list to add the comparisons. + $changeList = new ArrayList(); + $previous = null; + $count = 0; + foreach ($versions as $version) { + $changed = false; - // Check if we have something to compare with. - if (isset($previous)) { + // Check if we have something to compare with. + if (isset($previous)) { + // Produce the diff fields for use in the template. + if ($version->Title != $previous->Title) { + $diffTitle = Diff::compareHTML($version->Title, $previous->Title); - // Produce the diff fields for use in the template. - if ($version->Title != $previous->Title) { - $diffTitle = Diff::compareHTML($version->Title, $previous->Title); + $version->DiffTitle = DBField::create_field('HTMLText', null); + $version->DiffTitle->setValue( + sprintf( + '
%s ' . $diffTitle . '
', + _t('RSSHistory.TITLECHANGED', 'Title has changed:') + ) + ); + $changed = true; + } - $version->DiffTitle = DBField::create_field('HTMLText', null); - $version->DiffTitle->setValue( - sprintf( - '
%s ' . $diffTitle . '
', - _t('RSSHistory.TITLECHANGED', 'Title has changed:') - ) - ); - $changed = true; - } + if ($version->Content != $previous->Content) { + $diffContent = Diff::compareHTML($version->Content, $previous->Content); - if ($version->Content != $previous->Content) { - $diffContent = Diff::compareHTML($version->Content, $previous->Content); + $version->DiffContent = DBField::create_field('HTMLText', null); + $version->DiffContent->setValue('
'.$diffContent.'
'); + $changed = true; + } - $version->DiffContent = DBField::create_field('HTMLText', null); - $version->DiffContent->setValue('
'.$diffContent.'
'); - $changed = true; - } + // Copy the link so it can be cached. + $version->GeneratedLink = $version->AbsoluteLink(); + } - // Copy the link so it can be cached. - $version->GeneratedLink = $version->AbsoluteLink(); - } + // Omit the versions that haven't been visibly changed (only takes the above fields into consideration). + if ($changed) { + $changeList->push($version); + $count++; + } - // Omit the versions that haven't been visibly changed (only takes the above fields into consideration). - if ($changed) { - $changeList->push($version); - $count++; - } + // Store the last version for comparison. + $previous = $version; + } - // Store the last version for comparison. - $previous = $version; - } + // Make sure enough diff items have been generated to satisfy the $limit. If we ran out, add the final, + // non-diffed item (the initial version). This will also work for a single-diff request: if we are requesting + // a diff on the initial version we will just get that version, verbatim. + if ($previous && $versions->count()<$qLimit) { + $first = clone($previous); + $first->DiffContent = DBField::create_field('HTMLText', null); + $first->DiffContent->setValue('
' . $first->Content . '
'); + // Copy the link so it can be cached. + $first->GeneratedLink = $first->AbsoluteLink(); + $changeList->push($first); + } - // Make sure enough diff items have been generated to satisfy the $limit. If we ran out, add the final, - // non-diffed item (the initial version). This will also work for a single-diff request: if we are requesting - // a diff on the initial version we will just get that version, verbatim. - if ($previous && $versions->count()<$qLimit) { - $first = clone($previous); - $first->DiffContent = DBField::create_field('HTMLText', null); - $first->DiffContent->setValue('
' . $first->Content . '
'); - // Copy the link so it can be cached. - $first->GeneratedLink = $first->AbsoluteLink(); - $changeList->push($first); - } + return $changeList; + } - return $changeList; - } + /** + * Return a single diff representing this version. + * Returns the initial version if there is nothing to compare to. + * + * @returns DataObject Object with relevant fields diffed. + */ + public function getDiff() + { + $changes = $this->getDiffList($this->owner->Version, 1); + if ($changes && $changes->Count()) { + return $changes->First(); + } - /** - * Return a single diff representing this version. - * Returns the initial version if there is nothing to compare to. - * - * @returns DataObject Object with relevant fields diffed. - */ - public function getDiff() { - $changes = $this->getDiffList($this->owner->Version, 1); - if ($changes && $changes->Count()) { - return $changes->First(); - } + return null; + } - return null; - } + /** + * Compile a list of changes to the current page, excluding non-published and explicitly secured versions. + * + * @deprecated 2.0.0 Use VersionFeed::getDiffList instead + * + * @param int $highestVersion Top version number to consider. + * @param boolean $fullHistory Set to true to get the full change history, set to false for a single diff. + * @param int $limit Limit to the amount of items returned. + * + * @returns ArrayList List of cleaned records. + */ + public function getDiffedChanges($highestVersion = null, $fullHistory = true, $limit = 100) + { + return $this->getDiffList( + $highestVersion, + $fullHistory ? $limit : 1 + ); + } - /** - * Compile a list of changes to the current page, excluding non-published and explicitly secured versions. - * - * @deprecated 2.0.0 Use VersionFeed::getDiffList instead - * - * @param int $highestVersion Top version number to consider. - * @param boolean $fullHistory Set to true to get the full change history, set to false for a single diff. - * @param int $limit Limit to the amount of items returned. - * - * @returns ArrayList List of cleaned records. - */ - public function getDiffedChanges($highestVersion = null, $fullHistory = true, $limit = 100) { - return $this->getDiffList( - $highestVersion, - $fullHistory ? $limit : 1 - ); - } + public function updateSettingsFields(FieldList $fields) + { + if (!Config::inst()->get(get_class(), 'changes_enabled')) { + return; + } + + // Add public history field. + $fields->addFieldToTab('Root.Settings', $publicHistory = new FieldGroup( + new CheckboxField('PublicHistory', $this->owner->fieldLabel('PublicHistory')) + )); - public function updateSettingsFields(FieldList $fields) { - if(!Config::inst()->get(get_class(), 'changes_enabled')) return; - - // Add public history field. - $fields->addFieldToTab('Root.Settings', $publicHistory = new FieldGroup( - new CheckboxField('PublicHistory', $this->owner->fieldLabel('PublicHistory') - ))); + $warning = _t( + 'VersionFeed.Warning', + "Publicising the history will also disclose the changes that have at the time been protected " . + "from the public view." + ); - $warning = _t( - 'VersionFeed.Warning', - "Publicising the history will also disclose the changes that have at the time been protected " . - "from the public view." - ); + $fields->addFieldToTab('Root.Settings', new LiteralField('PublicHistoryWarning', $warning), 'PublicHistory'); - $fields->addFieldToTab('Root.Settings', new LiteralField('PublicHistoryWarning', $warning), 'PublicHistory'); + if ($this->owner->CanViewType!='Anyone') { + $warning = _t( + 'VersionFeed.Warning2', + "Changing access settings in such a way that this page or pages under it become publicly
" . + "accessible may result in publicising all historical changes on these pages too. Please review
" . + "this section's \"Public history\" settings to ascertain only intended information is disclosed." + ); - if ($this->owner->CanViewType!='Anyone') { - $warning = _t( - 'VersionFeed.Warning2', - "Changing access settings in such a way that this page or pages under it become publicly
" . - "accessible may result in publicising all historical changes on these pages too. Please review
" . - "this section's \"Public history\" settings to ascertain only intended information is disclosed." - ); + $fields->addFieldToTab('Root.Settings', new LiteralField('PublicHistoryWarning2', $warning), 'CanViewType'); + } + } - $fields->addFieldToTab('Root.Settings', new LiteralField('PublicHistoryWarning2', $warning), 'CanViewType'); - } - } + public function getSiteRSSLink() + { + // TODO: This link should be from the homepage, not this page. + if (Config::inst()->get(get_class(), 'allchanges_enabled') + && SiteConfig::current_site_config()->AllChangesEnabled + ) { + return $this->owner->Link('allchanges'); + } + } - public function getSiteRSSLink() { - // TODO: This link should be from the homepage, not this page. - if(Config::inst()->get(get_class(), 'allchanges_enabled') - && SiteConfig::current_site_config()->AllChangesEnabled - ) { - return $this->owner->Link('allchanges'); - } - } - - public function getDefaultRSSLink() { - if(Config::inst()->get(get_class(), 'changes_enabled') && $this->owner->PublicHistory) { - return $this->owner->Link('changes'); - } - } + public function getDefaultRSSLink() + { + if (Config::inst()->get(get_class(), 'changes_enabled') && $this->owner->PublicHistory) { + return $this->owner->Link('changes'); + } + } } diff --git a/src/VersionFeedController.php b/src/VersionFeedController.php index 55c92f8..502290f 100644 --- a/src/VersionFeedController.php +++ b/src/VersionFeedController.php @@ -16,174 +16,184 @@ use SilverStripe\View\Requirements; use SilverStripe\Core\Extension; use SilverStripe\VersionFeed\Filters\ContentFilter; -class VersionFeedController extends Extension { +class VersionFeedController extends Extension +{ - private static $allowed_actions = array( - 'changes', - 'allchanges' - ); - - /** - * Content handler - * - * @var ContentFilter - */ - protected $contentFilter; - - /** - * Sets the content filter - * - * @param ContentFilter $contentFilter - */ - public function setContentFilter(ContentFilter $contentFilter) { - $this->contentFilter = $contentFilter; - } - - /** - * 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 filterContent($key, $callback) { - if($this->contentFilter) { - return $this->contentFilter->getContent($key, $callback); - } else { - return call_user_func($callback); - } - } + private static $allowed_actions = array( + 'changes', + 'allchanges' + ); + + /** + * Content handler + * + * @var ContentFilter + */ + protected $contentFilter; + + /** + * Sets the content filter + * + * @param ContentFilter $contentFilter + */ + public function setContentFilter(ContentFilter $contentFilter) + { + $this->contentFilter = $contentFilter; + } + + /** + * 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 filterContent($key, $callback) + { + if ($this->contentFilter) { + return $this->contentFilter->getContent($key, $callback); + } else { + return call_user_func($callback); + } + } - public function onAfterInit() { - $this->linkToPageRSSFeed(); - $this->linkToAllSiteRSSFeed(); - } + public function onAfterInit() + { + $this->linkToPageRSSFeed(); + $this->linkToAllSiteRSSFeed(); + } - /** - * Get page-specific changes in a RSS feed. - */ - public function changes() { - // Check viewability of changes - if(!Config::inst()->get(VersionFeed::class, 'changes_enabled') - || !$this->owner->PublicHistory - || $this->owner->Version == '' - ) { - return $this->owner->httpError(404, 'Page history not viewable'); - } + /** + * Get page-specific changes in a RSS feed. + */ + public function changes() + { + // Check viewability of changes + if (!Config::inst()->get(VersionFeed::class, 'changes_enabled') + || !$this->owner->PublicHistory + || $this->owner->Version == '' + ) { + return $this->owner->httpError(404, 'Page history not viewable'); + } - // Cache the diffs to remove DOS possibility. - $target = $this->owner; - $key = implode('_', array('changes', $this->owner->ID, $this->owner->Version)); - $entries = $this->filterContent($key, function() use ($target) { - return $target->getDiffList(null, Config::inst()->get(VersionFeed::class, 'changes_limit')); - }); + // Cache the diffs to remove DOS possibility. + $target = $this->owner; + $key = implode('_', array('changes', $this->owner->ID, $this->owner->Version)); + $entries = $this->filterContent($key, function () use ($target) { + return $target->getDiffList(null, Config::inst()->get(VersionFeed::class, 'changes_limit')); + }); - // Generate the output. - $title = sprintf(_t('RSSHistory.SINGLEPAGEFEEDTITLE', 'Updates to %s page'), $this->owner->Title); - $rss = new RSSFeed($entries, $this->owner->request->getURL(), $title, '', 'Title', '', null); - $rss->setTemplate('Page_changes_rss'); - return $rss->outputToBrowser(); - } + // Generate the output. + $title = sprintf(_t('RSSHistory.SINGLEPAGEFEEDTITLE', 'Updates to %s page'), $this->owner->Title); + $rss = new RSSFeed($entries, $this->owner->request->getURL(), $title, '', 'Title', '', null); + $rss->setTemplate('Page_changes_rss'); + return $rss->outputToBrowser(); + } - /** - * Get all changes from the site in a RSS feed. - */ - public function allchanges() { - // Check viewability of allchanges - if(!Config::inst()->get(VersionFeed::class, 'allchanges_enabled') - || !SiteConfig::current_site_config()->AllChangesEnabled - ) { - return $this->owner->httpError(404, 'Global history not viewable'); - } + /** + * Get all changes from the site in a RSS feed. + */ + public function allchanges() + { + // Check viewability of allchanges + if (!Config::inst()->get(VersionFeed::class, 'allchanges_enabled') + || !SiteConfig::current_site_config()->AllChangesEnabled + ) { + return $this->owner->httpError(404, 'Global history not viewable'); + } - $limit = (int)Config::inst()->get(VersionFeed::class, 'allchanges_limit'); - $latestChanges = DB::query(' - SELECT * FROM "SiteTree_versions" + $limit = (int)Config::inst()->get(VersionFeed::class, 'allchanges_limit'); + $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 ' . $limit - ); - $lastChange = $latestChanges->record(); - $latestChanges->rewind(); + ORDER BY "LastEdited" DESC LIMIT ' . $limit); + $lastChange = $latestChanges->record(); + $latestChanges->rewind(); - if ($lastChange) { + if ($lastChange) { + // Cache the diffs to remove DOS possibility. + $key = 'allchanges' + . preg_replace('#[^a-zA-Z0-9_]#', '', $lastChange['LastEdited']) + . (Member::currentUserID() ?: 'public'); + $changeList = $this->filterContent($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. + $id = $record['RecordID']; + if (!isset($canView[$id])) { + $page = SiteTree::get()->byID($id); + $canView[$id] = $page && $page->canView(new Member()); + } + if (!$canView[$id]) { + continue; + } - // Cache the diffs to remove DOS possibility. - $key = 'allchanges' - . preg_replace('#[^a-zA-Z0-9_]#', '', $lastChange['LastEdited']) - . (Member::currentUserID() ?: 'public'); - $changeList = $this->filterContent($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. - $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 = SiteTree::create($record); + if ($diff = $version->getDiff()) { + $changeList->push($diff); + } + } - // Get the diff to the previous version. - $version = new Versioned_Version($record); - if ($diff = $version->getDiff()) { - $changeList->push($diff); - } - } + return $changeList; + }); + } else { + $changeList = new ArrayList(); + } - return $changeList; - }); - } else { - $changeList = new ArrayList(); - } + // Produce output + $url = $this->owner->getRequest()->getURL(); + $rss = new RSSFeed($changeList, $url, $this->linkToAllSitesRSSFeedTitle(), '', 'Title', '', null); + $rss->setTemplate('Page_allchanges_rss'); + return $rss->outputToBrowser(); + } + + /** + * Generates and embeds the RSS header link for the page-specific version rss feed + */ + public function linkToPageRSSFeed() + { + if (!Config::inst()->get(VersionFeed::class, 'changes_enabled') || !$this->owner->PublicHistory) { + return; + } + + RSSFeed::linkToFeed( + $this->owner->Link('changes'), + sprintf( + _t('RSSHistory.SINGLEPAGEFEEDTITLE', 'Updates to %s page'), + $this->owner->Title + ) + ); + } - // Produce output - $rss = new RSSFeed($changeList, $this->owner->request->getURL(), $this->linkToAllSitesRSSFeedTitle(), '', 'Title', '', null); - $rss->setTemplate('Page_allchanges_rss'); - return $rss->outputToBrowser(); - } - - /** - * Generates and embeds the RSS header link for the page-specific version rss feed - */ - public function linkToPageRSSFeed() { - if (!Config::inst()->get(VersionFeed::class, 'changes_enabled') || !$this->owner->PublicHistory) { - return; - } - - RSSFeed::linkToFeed( - $this->owner->Link('changes'), - sprintf( - _t('RSSHistory.SINGLEPAGEFEEDTITLE', 'Updates to %s page'), - $this->owner->Title - ) - ); - } + /** + * Generates and embeds the RSS header link for the global version rss feed + */ + public function linkToAllSiteRSSFeed() + { + if (!Config::inst()->get(VersionFeed::class, 'allchanges_enabled') + || !SiteConfig::current_site_config()->AllChangesEnabled + ) { + return; + } + + // RSS feed to all-site changes. + $title = Convert::raw2xml($this->linkToAllSitesRSSFeedTitle()); + $url = $this->owner->getSiteRSSLink(); - /** - * Generates and embeds the RSS header link for the global version rss feed - */ - public function linkToAllSiteRSSFeed() { - if(!Config::inst()->get(VersionFeed::class, 'allchanges_enabled') - || !SiteConfig::current_site_config()->AllChangesEnabled - ) { - return; - } - - // RSS feed to all-site changes. - $title = Convert::raw2xml($this->linkToAllSitesRSSFeedTitle()); - $url = $this->owner->getSiteRSSLink(); + Requirements::insertHeadTags( + '' + ); + } - Requirements::insertHeadTags( - ''); - } - - public function linkToAllSitesRSSFeedTitle() { - return sprintf(_t('RSSHistory.SITEFEEDTITLE', 'Updates to %s'), SiteConfig::current_site_config()->Title); - } + public function linkToAllSitesRSSFeedTitle() + { + return sprintf(_t('RSSHistory.SITEFEEDTITLE', 'Updates to %s'), SiteConfig::current_site_config()->Title); + } } diff --git a/src/VersionFeedSiteConfig.php b/src/VersionFeedSiteConfig.php index a646638..7eff3b3 100644 --- a/src/VersionFeedSiteConfig.php +++ b/src/VersionFeedSiteConfig.php @@ -2,11 +2,6 @@ namespace SilverStripe\VersionFeed; - - - - - use SilverStripe\Forms\FieldList; use SilverStripe\Core\Config\Config; use SilverStripe\VersionFeed\VersionFeed; @@ -14,36 +9,40 @@ use SilverStripe\Forms\CheckboxField; use SilverStripe\Forms\FieldGroup; use SilverStripe\ORM\DataExtension; - - /** * Allows global configuration of all changes */ -class VersionFeedSiteConfig extends DataExtension { - - private static $db = array( - 'AllChangesEnabled' => 'Boolean(true)' - ); +class VersionFeedSiteConfig extends DataExtension +{ + + private static $db = array( + 'AllChangesEnabled' => 'Boolean(true)' + ); - private static $defaults = array( - 'AllChangesEnabled' => true - ); + private static $defaults = array( + 'AllChangesEnabled' => true + ); - public function updateFieldLabels(&$labels) { - $labels['AllChangesEnabled'] = _t('VersionFeedSiteConfig.ALLCHANGESLABEL', 'Make global changes feed public'); - } - - public function updateCMSFields(FieldList $fields) { - if(!Config::inst()->get(VersionFeed::class, 'allchanges_enabled')) return; + public function updateFieldLabels(&$labels) + { + $labels['AllChangesEnabled'] = _t('VersionFeedSiteConfig.ALLCHANGESLABEL', 'Make global changes feed public'); + } + + public function updateCMSFields(FieldList $fields) + { + if (!Config::inst()->get(VersionFeed::class, 'allchanges_enabled')) { + return; + } - $fields->addFieldToTab('Root.Access', - FieldGroup::create(new CheckboxField('AllChangesEnabled', $this->owner->fieldLabel('AllChangesEnabled'))) - ->setTitle(_t('VersionFeedSiteConfig.ALLCHANGES', 'All page changes')) - ->setDescription(_t( - 'VersionFeed.Warning', - "Publicising the history will also disclose the changes that have at the time been protected " . - "from the public view." - )) - ); - } + $fields->addFieldToTab( + 'Root.Access', + FieldGroup::create(new CheckboxField('AllChangesEnabled', $this->owner->fieldLabel('AllChangesEnabled'))) + ->setTitle(_t('VersionFeedSiteConfig.ALLCHANGES', 'All page changes')) + ->setDescription(_t( + 'VersionFeed.Warning', + "Publicising the history will also disclose the changes that have at the time been protected " . + "from the public view." + )) + ); + } } diff --git a/tests/VersionFeedFunctionalTest.php b/tests/VersionFeedFunctionalTest.php index bbbeed3..ee06523 100644 --- a/tests/VersionFeedFunctionalTest.php +++ b/tests/VersionFeedFunctionalTest.php @@ -17,230 +17,242 @@ use SilverStripe\Dev\FunctionalTest; use SilverStripe\Control\Director; use Psr\SimpleCache\CacheInterface; +class VersionFeedFunctionalTest extends FunctionalTest +{ + protected $usesDatabase = true; + + protected $baseURI = 'http://www.fakesite.test'; -class VersionFeedFunctionalTest extends FunctionalTest { - protected $usesDatabase = true; - - protected $baseURI = 'http://www.fakesite.test'; + protected static $required_extensions = array( + 'Page' => array(VersionFeed::class), + 'PageController' => array(VersionFeedController::class), + ); - protected static $required_extensions = array( - 'Page' => array(VersionFeed::class), - 'PageController' => array(VersionFeedController::class), - ); + protected $userIP; - protected $userIP; + protected function setUp() + { + Director::config()->set('alternate_base_url', $this->baseURI); + + parent::setUp(); - protected function setUp() { - Director::config()->set('alternate_base_url', $this->baseURI); - - parent::setUp(); + $cache = Injector::inst()->get( + CacheInterface::class . '.VersionFeedController' + ); + $cache->clear(); - $cache = Injector::inst()->get( - CacheInterface::class . '.VersionFeedController' - ); - $cache->clear(); - - $this->userIP = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null; + $this->userIP = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null; // Enable history by default Config::modify()->set(VersionFeed::class, 'changes_enabled', true); Config::modify()->set(VersionFeed::class, 'allchanges_enabled', true); - // Disable caching and locking by default - Config::modify()->set(CachedContentFilter::class, 'cache_enabled', false); - Config::modify()->set(RateLimitFilter::class, 'lock_timeout', 0); - Config::modify()->set(RateLimitFilter::class, 'lock_bypage', false); - Config::modify()->set(RateLimitFilter::class, 'lock_byuserip', false); - Config::modify()->set(RateLimitFilter::class, 'lock_cooldown', false); - } + // Disable caching and locking by default + Config::modify()->set(CachedContentFilter::class, 'cache_enabled', false); + Config::modify()->set(RateLimitFilter::class, 'lock_timeout', 0); + Config::modify()->set(RateLimitFilter::class, 'lock_bypage', false); + Config::modify()->set(RateLimitFilter::class, 'lock_byuserip', false); + Config::modify()->set(RateLimitFilter::class, 'lock_cooldown', false); + } - public function tearDown() { - Director::config()->set('alternate_base_url', null); - - $_SERVER['REMOTE_ADDR'] = $this->userIP; + public function tearDown() + { + Director::config()->set('alternate_base_url', null); + + $_SERVER['REMOTE_ADDR'] = $this->userIP; - parent::tearDown(); - } + parent::tearDown(); + } - public function testPublicHistory() { - $page = $this->createPageWithChanges(array('PublicHistory' => false)); + public function testPublicHistory() + { + $page = $this->createPageWithChanges(array('PublicHistory' => false)); - $response = $this->get($page->RelativeLink('changes')); - $this->assertEquals(404, $response->getStatusCode()); + $response = $this->get($page->RelativeLink('changes')); + $this->assertEquals(404, $response->getStatusCode()); - $response = $this->get($page->RelativeLink('allchanges')); - $this->assertEquals(200, $response->getStatusCode()); - $xml = simplexml_load_string($response->getBody()); - $this->assertFalse((bool)$xml->channel->item); + $response = $this->get($page->RelativeLink('allchanges')); + $this->assertEquals(200, $response->getStatusCode()); + $xml = simplexml_load_string($response->getBody()); + $this->assertFalse((bool)$xml->channel->item); - $page = $this->createPageWithChanges(array('PublicHistory' => true)); + $page = $this->createPageWithChanges(array('PublicHistory' => true)); - $response = $this->get($page->RelativeLink('changes')); - $this->assertEquals(200, $response->getStatusCode()); - $xml = simplexml_load_string($response->getBody()); - $this->assertTrue((bool)$xml->channel->item); + $response = $this->get($page->RelativeLink('changes')); + $this->assertEquals(200, $response->getStatusCode()); + $xml = simplexml_load_string($response->getBody()); + $this->assertTrue((bool)$xml->channel->item); - $response = $this->get($page->RelativeLink('allchanges')); - $this->assertEquals(200, $response->getStatusCode()); - $xml = simplexml_load_string($response->getBody()); - $this->assertTrue((bool)$xml->channel->item); - } + $response = $this->get($page->RelativeLink('allchanges')); + $this->assertEquals(200, $response->getStatusCode()); + $xml = simplexml_load_string($response->getBody()); + $this->assertTrue((bool)$xml->channel->item); + } - public function testRateLimiting() { - // Re-enable locking just for this test - Config::modify()->set(RateLimitFilter::class, 'lock_timeout', 20); - Config::modify()->set(CachedContentFilter::class, 'cache_enabled', true); + public function testRateLimiting() + { + // Re-enable locking just for this test + Config::modify()->set(RateLimitFilter::class, 'lock_timeout', 20); + Config::modify()->set(CachedContentFilter::class, 'cache_enabled', true); - $page1 = $this->createPageWithChanges(array('PublicHistory' => true, 'Title' => 'Page1')); - $page2 = $this->createPageWithChanges(array('PublicHistory' => true, 'Title' => 'Page2')); + $page1 = $this->createPageWithChanges(array('PublicHistory' => true, 'Title' => 'Page1')); + $page2 = $this->createPageWithChanges(array('PublicHistory' => true, 'Title' => 'Page2')); - // Artifically set cache lock - Config::modify()->set(RateLimitFilter::class, 'lock_byuserip', false); - $cache = Injector::inst()->get( - CacheInterface::class . '.VersionFeedController' - ); - $cache->set(RateLimitFilter::CACHE_PREFIX, time() + 10); + // Artifically set cache lock + Config::modify()->set(RateLimitFilter::class, 'lock_byuserip', false); + $cache = Injector::inst()->get( + CacheInterface::class . '.VersionFeedController' + ); + $cache->set(RateLimitFilter::CACHE_PREFIX, time() + 10); - // Test normal hit - $response = $this->get($page1->RelativeLink('changes')); - $this->assertEquals(429, $response->getStatusCode()); - $this->assertGreaterThan(0, $response->getHeader('Retry-After')); - $response = $this->get($page2->RelativeLink('changes')); - $this->assertEquals(429, $response->getStatusCode()); - $this->assertGreaterThan(0, $response->getHeader('Retry-After')); + // Test normal hit + $response = $this->get($page1->RelativeLink('changes')); + $this->assertEquals(429, $response->getStatusCode()); + $this->assertGreaterThan(0, $response->getHeader('Retry-After')); + $response = $this->get($page2->RelativeLink('changes')); + $this->assertEquals(429, $response->getStatusCode()); + $this->assertGreaterThan(0, $response->getHeader('Retry-After')); - // Test page specific lock - Config::modify()->set(RateLimitFilter::class, 'lock_bypage', true); - $key = implode('_', array( - 'changes', - $page1->ID, - Versioned::get_versionnumber_by_stage(SiteTree::class, 'Live', $page1->ID, false) - )); - $key = RateLimitFilter::CACHE_PREFIX . '_' . md5($key); - $cache->set($key, time() + 10); - $response = $this->get($page1->RelativeLink('changes')); - $this->assertEquals(429, $response->getStatusCode()); - $this->assertGreaterThan(0, $response->getHeader('Retry-After')); - $response = $this->get($page2->RelativeLink('changes')); - $this->assertEquals(200, $response->getStatusCode()); - Config::modify()->set(RateLimitFilter::class, 'lock_bypage', false); + // Test page specific lock + Config::modify()->set(RateLimitFilter::class, 'lock_bypage', true); + $key = implode('_', array( + 'changes', + $page1->ID, + Versioned::get_versionnumber_by_stage(SiteTree::class, 'Live', $page1->ID, false) + )); + $key = RateLimitFilter::CACHE_PREFIX . '_' . md5($key); + $cache->set($key, time() + 10); + $response = $this->get($page1->RelativeLink('changes')); + $this->assertEquals(429, $response->getStatusCode()); + $this->assertGreaterThan(0, $response->getHeader('Retry-After')); + $response = $this->get($page2->RelativeLink('changes')); + $this->assertEquals(200, $response->getStatusCode()); + Config::modify()->set(RateLimitFilter::class, 'lock_bypage', false); - // Test rate limit hit by IP - Config::modify()->set(RateLimitFilter::class, 'lock_byuserip', true); - $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; - $cache->set(RateLimitFilter::CACHE_PREFIX . '_' . md5('127.0.0.1'), time() + 10); - $response = $this->get($page1->RelativeLink('changes')); - $this->assertEquals(429, $response->getStatusCode()); - $this->assertGreaterThan(0, $response->getHeader('Retry-After')); + // Test rate limit hit by IP + Config::modify()->set(RateLimitFilter::class, 'lock_byuserip', true); + $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; + $cache->set(RateLimitFilter::CACHE_PREFIX . '_' . md5('127.0.0.1'), time() + 10); + $response = $this->get($page1->RelativeLink('changes')); + $this->assertEquals(429, $response->getStatusCode()); + $this->assertGreaterThan(0, $response->getHeader('Retry-After')); - // Test rate limit doesn't hit other IP - $_SERVER['REMOTE_ADDR'] = '127.0.0.20'; - $cache->set(RateLimitFilter::CACHE_PREFIX . '_' . md5('127.0.0.1'), time() + 10); - $response = $this->get($page1->RelativeLink('changes')); - $this->assertEquals(200, $response->getStatusCode()); + // Test rate limit doesn't hit other IP + $_SERVER['REMOTE_ADDR'] = '127.0.0.20'; + $cache->set(RateLimitFilter::CACHE_PREFIX . '_' . md5('127.0.0.1'), time() + 10); + $response = $this->get($page1->RelativeLink('changes')); + $this->assertEquals(200, $response->getStatusCode()); - // Restore setting - Config::modify()->set(RateLimitFilter::class, 'lock_byuserip', false); - Config::modify()->set(RateLimitFilter::class, 'lock_timeout', 0); - Config::modify()->set(CachedContentFilter::class, 'cache_enabled', false); - } + // Restore setting + Config::modify()->set(RateLimitFilter::class, 'lock_byuserip', false); + Config::modify()->set(RateLimitFilter::class, 'lock_timeout', 0); + Config::modify()->set(CachedContentFilter::class, 'cache_enabled', false); + } - public function testContainsChangesForPageOnly() { - $page1 = $this->createPageWithChanges(array('Title' => 'Page1')); - $page2 = $this->createPageWithChanges(array('Title' => 'Page2')); + public function testContainsChangesForPageOnly() + { + $page1 = $this->createPageWithChanges(array('Title' => 'Page1')); + $page2 = $this->createPageWithChanges(array('Title' => 'Page2')); - $response = $this->get($page1->RelativeLink('changes')); - $xml = simplexml_load_string($response->getBody()); - $titles = array_map(function($item) {return (string)$item->title;}, $xml->xpath('//item')); - // TODO Unclear if this should contain the original version - $this->assertContains('Changed: Page1', $titles); - $this->assertNotContains('Changed: Page2', $titles); + $response = $this->get($page1->RelativeLink('changes')); + $xml = simplexml_load_string($response->getBody()); + $titles = array_map(function ($item) { + return (string)$item->title; + }, $xml->xpath('//item')); + // TODO Unclear if this should contain the original version + $this->assertContains('Changed: Page1', $titles); + $this->assertNotContains('Changed: Page2', $titles); - $response = $this->get($page2->RelativeLink('changes')); - $xml = simplexml_load_string($response->getBody()); - $titles = array_map(function($item) {return (string)$item->title;}, $xml->xpath('//item')); - // TODO Unclear if this should contain the original version - $this->assertNotContains('Changed: Page1', $titles); - $this->assertContains('Changed: Page2', $titles); - } + $response = $this->get($page2->RelativeLink('changes')); + $xml = simplexml_load_string($response->getBody()); + $titles = array_map(function ($item) { + return (string)$item->title; + }, $xml->xpath('//item')); + // TODO Unclear if this should contain the original version + $this->assertNotContains('Changed: Page1', $titles); + $this->assertContains('Changed: Page2', $titles); + } - public function testContainsAllChangesForAllPages() { - $page1 = $this->createPageWithChanges(array('Title' => 'Page1')); - $page2 = $this->createPageWithChanges(array('Title' => 'Page2')); + public function testContainsAllChangesForAllPages() + { + $page1 = $this->createPageWithChanges(array('Title' => 'Page1')); + $page2 = $this->createPageWithChanges(array('Title' => 'Page2')); - $response = $this->get($page1->RelativeLink('allchanges')); - $xml = simplexml_load_string($response->getBody()); - $titles = array_map(function($item) {return (string)$item->title;}, $xml->xpath('//item')); - $this->assertContains('Page1', $titles); - $this->assertContains('Page2', $titles); - } + $response = $this->get($page1->RelativeLink('allchanges')); + $xml = simplexml_load_string($response->getBody()); + $titles = array_map(function ($item) { + return (string)$item->title; + }, $xml->xpath('//item')); + $this->assertContains('Page1', $titles); + $this->assertContains('Page2', $titles); + } - protected function createPageWithChanges($seed = null) { - $page = new Page(); + protected function createPageWithChanges($seed = null) + { + $page = new Page(); - $seed = array_merge(array( - 'Title' => 'My Title', - 'Content' => 'My Content' - ), $seed); - $page->update($seed); - $page->write(); - $page->publish('Stage', 'Live'); + $seed = array_merge(array( + 'Title' => 'My Title', + 'Content' => 'My Content' + ), $seed); + $page->update($seed); + $page->write(); + $page->publish('Stage', 'Live'); - $page->update(array( - 'Title' => 'Changed: ' . $seed['Title'], - 'Content' => 'Changed: ' . $seed['Content'], - )); - $page->write(); - $page->publish('Stage', 'Live'); + $page->update(array( + 'Title' => 'Changed: ' . $seed['Title'], + 'Content' => 'Changed: ' . $seed['Content'], + )); + $page->write(); + $page->publish('Stage', 'Live'); - $page->update(array( - 'Title' => 'Changed again: ' . $seed['Title'], - 'Content' => 'Changed again: ' . $seed['Content'], - )); - $page->write(); - $page->publish('Stage', 'Live'); + $page->update(array( + 'Title' => 'Changed again: ' . $seed['Title'], + 'Content' => 'Changed again: ' . $seed['Content'], + )); + $page->write(); + $page->publish('Stage', 'Live'); - $page->update(array( - 'Title' => 'Unpublished: ' . $seed['Title'], - 'Content' => 'Unpublished: ' . $seed['Content'], - )); - $page->write(); + $page->update(array( + 'Title' => 'Unpublished: ' . $seed['Title'], + 'Content' => 'Unpublished: ' . $seed['Content'], + )); + $page->write(); - return $page; - } + return $page; + } - /** - * Tests response code for globally disabled feedss - */ - public function testFeedViewability() { + /** + * Tests response code for globally disabled feedss + */ + public function testFeedViewability() + { - // Nested loop through each configuration - foreach(array(true, false) as $publicHistory_Page) { - $page = $this->createPageWithChanges(array('PublicHistory' => $publicHistory_Page, 'Title' => 'Page')); + // Nested loop through each configuration + foreach (array(true, false) as $publicHistory_Page) { + $page = $this->createPageWithChanges(array('PublicHistory' => $publicHistory_Page, 'Title' => 'Page')); - // Test requests to 'changes' action - foreach(array(true, false) as $publicHistory_Config) { - Config::modify()->set(VersionFeed::class, 'changes_enabled', $publicHistory_Config); - $expectedResponse = $publicHistory_Page && $publicHistory_Config ? 200 : 404; - $response = $this->get($page->RelativeLink('changes')); - $this->assertEquals($expectedResponse, $response->getStatusCode()); - } + // Test requests to 'changes' action + foreach (array(true, false) as $publicHistory_Config) { + Config::modify()->set(VersionFeed::class, 'changes_enabled', $publicHistory_Config); + $expectedResponse = $publicHistory_Page && $publicHistory_Config ? 200 : 404; + $response = $this->get($page->RelativeLink('changes')); + $this->assertEquals($expectedResponse, $response->getStatusCode()); + } - // Test requests to 'allchanges' action on each page - foreach(array(true, false) as $allChanges_Config) { - foreach(array(true, false) as $allChanges_SiteConfig) { - Config::modify()->set(VersionFeed::class, 'allchanges_enabled', $allChanges_Config); - $siteConfig = SiteConfig::current_site_config(); - $siteConfig->AllChangesEnabled = $allChanges_SiteConfig; - $siteConfig->write(); - - $expectedResponse = $allChanges_Config && $allChanges_SiteConfig ? 200 : 404; - $response = $this->get($page->RelativeLink('allchanges')); - $this->assertEquals($expectedResponse, $response->getStatusCode()); - } - } - } - } + // Test requests to 'allchanges' action on each page + foreach (array(true, false) as $allChanges_Config) { + foreach (array(true, false) as $allChanges_SiteConfig) { + Config::modify()->set(VersionFeed::class, 'allchanges_enabled', $allChanges_Config); + $siteConfig = SiteConfig::current_site_config(); + $siteConfig->AllChangesEnabled = $allChanges_SiteConfig; + $siteConfig->write(); + $expectedResponse = $allChanges_Config && $allChanges_SiteConfig ? 200 : 404; + $response = $this->get($page->RelativeLink('allchanges')); + $this->assertEquals($expectedResponse, $response->getStatusCode()); + } + } + } + } } - diff --git a/tests/VersionFeedTest.php b/tests/VersionFeedTest.php index 0384456..2e77ebf 100644 --- a/tests/VersionFeedTest.php +++ b/tests/VersionFeedTest.php @@ -2,7 +2,6 @@ namespace SilverStripe\VersionFeed\Tests; - use Page; use SilverStripe\VersionFeed\VersionFeed; use SilverStripe\VersionFeed\VersionFeedController; @@ -10,56 +9,58 @@ use SilverStripe\Dev\SapphireTest; use SilverStripe\CMS\Model\SiteTree; use SilverStripe\CMS\Controllers\ContentController; +class VersionFeedTest extends SapphireTest +{ -class VersionFeedTest extends SapphireTest { + protected $usesDatabase = true; - protected $usesDatabase = true; + protected static $required_extensions = [ + SiteTree::class => [VersionFeed::class], + ContentController::class => [VersionFeedController::class], + ]; - protected static $required_extensions = [ - SiteTree::class => [VersionFeed::class], - ContentController::class => [VersionFeedController::class], - ]; + protected $illegalExtensions = [ + 'SiteTree' => ['Translatable'] + ]; - protected $illegalExtensions = [ - 'SiteTree' => ['Translatable'] - ]; + public function testDiffedChangesExcludesRestrictedItems() + { + $this->markTestIncomplete(); + } - public function testDiffedChangesExcludesRestrictedItems() { - $this->markTestIncomplete(); - } + public function testDiffedChangesIncludesFullHistory() + { + $this->markTestIncomplete(); + } - public function testDiffedChangesIncludesFullHistory() { - $this->markTestIncomplete(); - } + public function testDiffedChangesTitle() + { + $page = new Page(['Title' => 'My Title']); + $page->write(); + $page->publish('Stage', 'Live'); + + $page->Title = 'My Changed Title'; + $page->write(); + $page->publish('Stage', 'Live'); - public function testDiffedChangesTitle() { - $page = new Page(['Title' => 'My Title']); - $page->write(); - $page->publish('Stage', 'Live'); - - $page->Title = 'My Changed Title'; - $page->write(); - $page->publish('Stage', 'Live'); + $page->Title = 'My Unpublished Changed Title'; + $page->write(); - $page->Title = 'My Unpublished Changed Title'; - $page->write(); + // Strip spaces from test output because they're not reliably maintained by the HTML Tidier + $cleanDiffOutput = function ($val) { + return str_replace(' ', '', strip_tags($val)); + }; - // Strip spaces from test output because they're not reliably maintained by the HTML Tidier - $cleanDiffOutput = function($val) { - return str_replace(' ','',strip_tags($val)); - }; - - $this->assertContains( - str_replace(' ' ,'',_t('RSSHistory.TITLECHANGED', 'Title has changed:') . 'My Changed Title'), - array_map($cleanDiffOutput, $page->getDiffList()->column('DiffTitle')), - 'Detects published title changes' - ); - - $this->assertNotContains( - str_replace(' ' ,'',_t('RSSHistory.TITLECHANGED', 'Title has changed:') . 'My Unpublished Changed Title'), - array_map($cleanDiffOutput, $page->getDiffList()->column('DiffTitle')), - 'Ignores unpublished title changes' - ); - } + $this->assertContains( + str_replace(' ', '', _t('RSSHistory.TITLECHANGED', 'Title has changed:') . 'My Changed Title'), + array_map($cleanDiffOutput, $page->getDiffList()->column('DiffTitle')), + 'Detects published title changes' + ); + $this->assertNotContains( + str_replace(' ', '', _t('RSSHistory.TITLECHANGED', 'Title has changed:') . 'My Unpublished Changed Title'), + array_map($cleanDiffOutput, $page->getDiffList()->column('DiffTitle')), + 'Ignores unpublished title changes' + ); + } }