/** * Compares current draft with live version, * and returns true if no live version exists, * meaning the page was never published. * * @return boolean */ public function getIsAddedToStage() { // new unsaved pages could be never be published if ($this->isNew()) { return false; } $stageVersion = Versioned::get_versionnumber_by_stage($this->owner->class, 'Stage', $this->owner->ID); $liveVersion = Versioned::get_versionnumber_by_stage($this->owner->class, 'Live', $this->owner->ID); return $stageVersion && !$liveVersion; }
public function testRateLimiting() { // Re-enable locking just for this test Config::inst()->update('VersionFeed\\Filters\\RateLimitFilter', 'lock_timeout', 20); Config::inst()->update('VersionFeed\\Filters\\CachedContentFilter', 'cache_enabled', true); $page1 = $this->createPageWithChanges(array('PublicHistory' => true, 'Title' => 'Page1')); $page2 = $this->createPageWithChanges(array('PublicHistory' => true, 'Title' => 'Page2')); // Artifically set cache lock Config::inst()->update('VersionFeed\\Filters\\RateLimitFilter', 'lock_byuserip', false); $cache = SS_Cache::factory('VersionFeed_Controller'); $cache->setOption('automatic_serialization', true); $cache->save(time() + 10, \VersionFeed\Filters\RateLimitFilter::CACHE_PREFIX); // 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::inst()->update('VersionFeed\\Filters\\RateLimitFilter', 'lock_bypage', true); $key = implode('_', array('changes', $page1->ID, Versioned::get_versionnumber_by_stage('SiteTree', 'Live', $page1->ID, false))); $key = \VersionFeed\Filters\RateLimitFilter::CACHE_PREFIX . '_' . md5($key); $cache->save(time() + 10, $key); $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::inst()->update('VersionFeed\\Filters\\RateLimitFilter', 'lock_bypage', false); // Test rate limit hit by IP Config::inst()->update('VersionFeed\\Filters\\RateLimitFilter', 'lock_byuserip', true); $_SERVER['HTTP_CLIENT_IP'] = '127.0.0.1'; $cache->save(time() + 10, \VersionFeed\Filters\RateLimitFilter::CACHE_PREFIX . '_' . md5('127.0.0.1')); $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['HTTP_CLIENT_IP'] = '127.0.0.20'; $cache->save(time() + 10, \VersionFeed\Filters\RateLimitFilter::CACHE_PREFIX . '_' . md5('127.0.0.1')); $response = $this->get($page1->RelativeLink('changes')); $this->assertEquals(200, $response->getStatusCode()); // Restore setting Config::inst()->update('VersionFeed\\Filters\\RateLimitFilter', 'lock_byuserip', false); Config::inst()->update('VersionFeed\\Filters\\RateLimitFilter', 'lock_timeout', 0); Config::inst()->update('VersionFeed\\Filters\\CachedContentFilter', 'cache_enabled', false); }
public function updateStatusFlags(&$flags) { static $prepop = true; if ($prepop) { Versioned::prepopulate_versionnumber_cache('SiteTree', 'Stage'); Versioned::prepopulate_versionnumber_cache('SiteTree', 'Live'); $prepop = false; } $stageVersion = intval(Versioned::get_versionnumber_by_stage('SiteTree', 'Stage', $this->owner->ID)); $liveVersion = intval(Versioned::get_versionnumber_by_stage('SiteTree', 'Live', $this->owner->ID)); if ($liveVersion == 0) { $flags['status_draft'] = ''; } elseif ($stageVersion > $liveVersion) { $flags['status_draft_published'] = ''; } else { $flags['status_published'] = ''; } }
/** * Determines if the current record is deleted from stage * @returns boolean */ public function recordIsDeletedFromStage() { // for SiteTree records if ($this->owner->hasMethod('getIsDeletedFromStage')) { return $this->owner->IsDeletedFromStage; } if (!$this->owner->record->checkVersioned()) { return false; } if (!$this->owner->record->isInDB()) { return true; } $class = $this->owner->record->class; $stageVersion = Versioned::get_versionnumber_by_stage($class, 'Stage', $this->owner->record->ID); // Return true for both completely deleted pages and for pages just deleted from stage return !$stageVersion; }
/** * Return if this form has been modified on the stage site and not published. * this is used on the workflow module and for a couple highlighting things * * @return boolean */ public function getIsModifiedOnStage() { // new unsaved pages could be never be published if ($this->isNew()) { return false; } $stageVersion = Versioned::get_versionnumber_by_stage('UserDefinedForm', 'Stage', $this->ID); $liveVersion = Versioned::get_versionnumber_by_stage('UserDefinedForm', 'Live', $this->ID); $isModified = $stageVersion && $stageVersion != $liveVersion; if (!$isModified) { if ($this->Fields()) { foreach ($this->Fields() as $field) { if ($field->getIsModifiedOnStage()) { $isModified = true; break; } } } } return $isModified; }
/** * Generate the CMS fields from the fields from the original page. */ public function getCMSFields() { $fields = parent::getCMSFields(); // Setup the linking to the original page. $copyContentFromField = new TreeDropdownField("CopyContentFromID", _t('VirtualPage.CHOOSE', "Linked Page"), "SiteTree"); // filter doesn't let you select children of virtual pages as as source page //$copyContentFromField->setFilterFunction(create_function('$item', 'return !($item instanceof VirtualPage);')); // Setup virtual fields if ($virtualFields = $this->getVirtualFields()) { $roTransformation = new ReadonlyTransformation(); foreach ($virtualFields as $virtualField) { if ($fields->dataFieldByName($virtualField)) { $fields->replaceField($virtualField, $fields->dataFieldByName($virtualField)->transform($roTransformation)); } } } $msgs = array(); $fields->addFieldToTab("Root.Main", $copyContentFromField, "Title"); // Create links back to the original object in the CMS if ($this->CopyContentFrom()->exists()) { $link = "<a class=\"cmsEditlink\" href=\"admin/pages/edit/show/{$this->CopyContentFromID}\">" . _t('VirtualPage.EditLink', 'edit') . "</a>"; $msgs[] = _t('VirtualPage.HEADERWITHLINK', "This is a virtual page copying content from \"{title}\" ({link})", array('title' => $this->CopyContentFrom()->Title, 'link' => $link)); } else { $msgs[] = _t('VirtualPage.HEADER', "This is a virtual page"); $msgs[] = _t('SITETREE.VIRTUALPAGEWARNING', 'Please choose a linked page and save first in order to publish this page'); } if ($this->CopyContentFromID && !Versioned::get_versionnumber_by_stage('SiteTree', 'Live', $this->CopyContentFromID)) { $msgs[] = _t('SITETREE.VIRTUALPAGEDRAFTWARNING', 'Please publish the linked page in order to publish the virtual page'); } $fields->addFieldToTab("Root.Main", new LiteralField('VirtualPageMessage', '<div class="message notice">' . implode('. ', $msgs) . '.</div>'), 'CopyContentFromID'); return $fields; }
public function testPublishCreateNewVersion() { $page1 = $this->objFromFixture('VersionedTest_DataObject', 'page1'); $page1->Content = 'orig'; $page1->write(); $firstVersion = $page1->Version; $page1->publish('Stage', 'Live', false); $this->assertEquals($firstVersion, $page1->Version, 'publish() with $createNewVersion=FALSE does not create a new version'); $page1->Content = 'changed'; $page1->write(); $secondVersion = $page1->Version; $this->assertTrue($firstVersion < $secondVersion, 'write creates new version'); $page1->publish('Stage', 'Live', true); $thirdVersion = Versioned::get_latest_version('VersionedTest_DataObject', $page1->ID)->Version; $liveVersion = Versioned::get_versionnumber_by_stage('VersionedTest_DataObject', 'Live', $page1->ID); $stageVersion = Versioned::get_versionnumber_by_stage('VersionedTest_DataObject', 'Stage', $page1->ID); $this->assertTrue($secondVersion < $thirdVersion, 'publish() with $createNewVersion=TRUE creates a new version'); $this->assertEquals($liveVersion, $thirdVersion, 'publish() with $createNewVersion=TRUE publishes to live'); $this->assertEquals($stageVersion, $secondVersion, 'publish() with $createNewVersion=TRUE does not affect stage'); }
/** * checks if records is changed on stage * @return boolean */ public function getIsModifiedOnStage() { // new unsaved fields could be never be published if ($this->isNew()) { return false; } $stageVersion = Versioned::get_versionnumber_by_stage('EditableFormField', 'Stage', $this->ID); $liveVersion = Versioned::get_versionnumber_by_stage('EditableFormField', 'Live', $this->ID); return $stageVersion && $stageVersion != $liveVersion; }
/** * Returns true if is page is publishable by anyone at all * Return false if the source page isn't published yet. * * Note that isPublishable doesn't affect ete from live, only publish. */ public function isPublishable() { // No source if (!$this->CopyContentFrom() || !$this->CopyContentFrom()->ID) { return false; } // Unpublished source if (!Versioned::get_versionnumber_by_stage('SiteTree', 'Live', $this->CopyContentFromID)) { return false; } // Default - publishable return true; }
/** * Versioned objects needs special handling, we can not * handle them reliably as long as they exist on a stage. * * @param DataObject $object The object which will be * checked. * * @return boolean True if there are other versions for the * object which prevents relations from being handled. */ public static function version_exist(DataObject $object) { if (Object::has_extension($object->ClassName, 'Versioned')) { $ID = (int)$object->ID; foreach (self::version_stages($object) as $stage) { if (Versioned::get_versionnumber_by_stage($object->ClassName, $stage, $ID)) return true; } } return false; }
/** * Determine if there are any additional restrictions on this object for the given reading version. * * Override this in a subclass to customise any additional effect that Versioned applies to canView. * * This is expected to be called by canView, and thus is only responsible for denying access if * the default canView would otherwise ALLOW access. Thus it should not be called in isolation * as an authoritative permission check. * * This has the following extension points: * - canViewDraft is invoked if Mode = stage and Stage = stage * - canViewArchived is invoked if Mode = archive * * @param Member $member * @return bool False is returned if the current viewing mode denies visibility */ public function canViewVersioned($member = null) { // Bypass when live stage $mode = $this->owner->getSourceQueryParam("Versioned.mode"); $stage = $this->owner->getSourceQueryParam("Versioned.stage"); if ($mode === 'stage' && $stage === static::get_live_stage()) { return true; } // Bypass if site is unsecured if (Session::get('unsecuredDraftSite')) { return true; } // If there are less than 2 stages, we can exit early since comparing stages is not needed if (count($this->stages) < 2) { return true; } // If we weren't definitely loaded from live, and we can't view non-live content, we need to // check to make sure this version is the live version and so can be viewed. $latestVersion = Versioned::get_versionnumber_by_stage($this->owner->class, $this->liveStage, $this->owner->ID); if ($latestVersion == $this->owner->Version) { // Even if this is loaded from a non-live stage, this is the live version return true; } // Extend versioned behaviour $extended = $this->owner->extendedCan('canViewNonLive', $member); if ($extended !== null) { return (bool) $extended; } // Fall back to default permission check $permissions = Config::inst()->get($this->owner->class, 'non_live_permissions', Config::FIRST_SET); $check = Permission::checkMember($member, $permissions); return (bool) $check; }
/** * * @param Member $member * @return boolean */ public function canView($member = null) { if (!$member || !is_a($member, 'Member') || is_numeric($member)) { $member = Member::currentUserID(); } // admin override if ($member && Permission::checkMember($member, array("ADMIN", "SITETREE_VIEW_ALL"))) { return true; } // make sure we were loaded off an allowed stage // Were we definitely loaded directly off Live during our query? $fromLive = true; foreach (array('mode' => 'stage', 'stage' => 'live') as $param => $match) { $fromLive = $fromLive && strtolower((string) $this->getCachedSourceQueryParam("Versioned.{$param}")) == $match; } if (!$fromLive && !Session::get('unsecuredDraftSite') && !Permission::checkMember($member, array('CMS_ACCESS_LeftAndMain', 'CMS_ACCESS_CMSMain', 'VIEW_DRAFT_CONTENT'))) { // If we weren't definitely loaded from live, and we can't view non-live content, we need to // check to make sure this version is the live version and so can be viewed if (Versioned::get_versionnumber_by_stage($this->ClassName, 'Live', $this->ID) != $this->Version) { return false; } } // Orphaned pages (in the current stage) are unavailable, except for admins via the CMS if ($this->isOrphaned()) { return false; } // Standard mechanism for accepting permission changes from extensions $extended = $this->extendedCan('canView', $member); if ($extended !== null) { return $extended; } // check for empty spec if (!$this->CanViewType || $this->CanViewType == 'Anyone') { return true; } // check for inherit if ($this->CanViewType == 'Inherit') { if ($parent = $this->getParent()) { return $parent->canView($member); } else { return $this->getSiteConfig()->canView($member); } } // check for any logged-in users if ($this->CanViewType == 'LoggedInUsers' && $member) { return true; } // check for specific groups if ($member && is_numeric($member)) { $member = DataObject::get_by_id('Member', $member); } if ($this->CanViewType == 'OnlyTheseUsers' && $member && $member->inGroups($this->ViewerGroups)) { return true; } return false; }
/** * Compares current draft with live version, * and returns true if no live version exists, * meaning the page was never published. * * @return boolean */ public function getIsAddedToStage() { // new unsaved pages could be never be published if($this->isNew()) return false; $stageVersion = Versioned::get_versionnumber_by_stage('SiteTree', 'Stage', $this->ID); $liveVersion = Versioned::get_versionnumber_by_stage('SiteTree', 'Live', $this->ID); return ($stageVersion && !$liveVersion); }
public function hasApprovedVersion() { $live_version = Versioned::get_versionnumber_by_stage($this->owner->class, 'Live', $this->owner->ID); return (bool) $live_version; }
private static function isObjectModifiedOnStage($object) { // new unsaved fields could be never be published if (self::isNew($object)) { return false; } $stageVersion = Versioned::get_versionnumber_by_stage($object->ClassName, 'Stage', $object->ID); $liveVersion = Versioned::get_versionnumber_by_stage($object->ClassName, 'Live', $object->ID); return $stageVersion && $stageVersion != $liveVersion || $object->hasMethod('getIsModifiedOnStage') && $object->getIsModifiedOnStage(false); }