/** * Modified version of the SiteTree publish method. * * @return <type> */ public function doPublish() { if (!$this->owner->canPublish()) { return false; } $class = $this->owner->class; $ownerId = $this->owner->ID; $dataClasses = ClassInfo::dataClassesFor($class); $dataClasses = array_values($dataClasses); $class = $dataClasses[count($dataClasses) - 1]; $original = Versioned::get_one_by_stage("{$class}", "Live", "\"{$class}\".\"ID\" = {$ownerId}"); if (!$original) { $original = new $class(); } $this->owner->invokeWithExtensions('onBeforePublish', $original); // Handle activities undertaken by decorators $this->owner->Status = "Published"; //$this->PublishedByID = Member::currentUser()->ID; $this->owner->write(); $this->owner->publish("Stage", "Live"); if ($this->owner->hasField('Sort')) { // find the table that actually defines the sortable field $class = get_class($this->owner); if ($this->owner->hasMethod('findClassDefiningSortField')) { $class = $this->owner->findClassDefiningSortField(); } DB::query("UPDATE \"{$class}_Live\"\n\t\t\t\tSET \"Sort\" = (SELECT \"{$class}\".\"Sort\" FROM \"{$class}\" WHERE \"{$class}_Live\".\"ID\" = \"{$class}\".\"ID\")"); } // Handle activities undertaken by decorators $this->owner->invokeWithExtensions('onAfterPublish', $original); return true; }
/** * @param FieldSet $actions * @parma SiteTree $page */ public static function update_cms_actions(&$actions, $page) { $openRequest = $page->OpenWorkflowRequest(); // if user doesn't have publish rights if (!$page->canPublish() || $openRequest) { // authors shouldn't be able to revert, as this republishes the page. // they should rather change the page and re-request publication $actions->removeByName('action_revert'); } // Remove the one click publish if they are not an admin/workflow admin. if (self::$force_publishers_to_use_workflow && !Permission::checkMember(Member::currentUser(), 'IS_WORKFLOW_ADMIN')) { $actions->removeByName('action_publish'); } // Remove the save & publish button if you don't have edit rights if (!$page->canEdit()) { $actions->removeByName('action_publish'); } $liveVersion = Versioned::get_one_by_stage('SiteTree', 'Live', "\"SiteTree_Live\".\"ID\" = {$page->ID}"); if ($liveVersion && $liveVersion->ExpiryDate != null && $liveVersion->ExpiryDate != '0000-00-00 00:00:00') { if ($page->canApprove()) { $actions->push(new FormAction('cms_cancelexpiry', _t('WorkflowPublicationRequest.BUTTONCANCELEXPIRY', 'Cancel expiry'))); } } // Optional method $isPublishable = $page->hasMethod('isPublishable') ? $page->isPublishable() : true; if (!$openRequest && $page->canEdit() && $isPublishable && $page->stagesDiffer('Stage', 'Live') && ($page->Version > 1 || $page->Title != "New Page") && !$page->IsDeletedFromStage && (!$page->canPublish() || self::$publisher_can_create_wf_requests)) { $actions->push($requestPublicationAction = new FormAction('cms_requestpublication', _t('SiteTreeCMSWorkflow.BUTTONREQUESTPUBLICATION', 'Request Publication'))); // don't allow creation of a second request by another author if (!self::can_create(null, $page)) { $actions->makeFieldReadonly($requestPublicationAction->Name()); } } }
function ItemEditForm() { $form = parent::ItemEditForm(); $actions = $form->Actions(); $majorActions = CompositeField::create()->setName('MajorActions')->setTag('fieldset')->addExtraClass('ss-ui-buttonset'); $rootTabSet = new TabSet('ActionMenus'); $moreOptions = new Tab('MoreOptions', _t('SiteTree.MoreOptions', 'More options', 'Expands a view for more buttons')); $rootTabSet->push($moreOptions); $rootTabSet->addExtraClass('ss-ui-action-tabset action-menus'); // Render page information into the "more-options" drop-up, on the top. $baseClass = ClassInfo::baseDataClass($this->record->class); $live = Versioned::get_one_by_stage($this->record->class, 'Live', "\"{$baseClass}\".\"ID\"='{$this->record->ID}'"); $existsOnLive = $this->record->getExistsOnLive(); $published = $this->record->isPublished(); $moreOptions->push(new LiteralField('Information', $this->record->customise(array('Live' => $live, 'ExistsOnLive' => $existsOnLive))->renderWith('SiteTree_Information'))); $actions->removeByName('action_doSave'); $actions->removeByName('action_doDelete'); if ($this->record->canEdit()) { if ($this->record->IsDeletedFromStage) { if ($existsOnLive) { $majorActions->push(FormAction::create('revert', _t('CMSMain.RESTORE', 'Restore'))); if ($this->record->canDelete() && $this->record->canDeleteFromLive()) { $majorActions->push(FormAction::create('unpublish', _t('CMSMain.DELETEFP', 'Delete'))->addExtraClass('ss-ui-action-destructive')); } } else { if (!$this->record->isNew()) { $majorActions->push(FormAction::create('restore', _t('CMSMain.RESTORE', 'Restore'))->setAttribute('data-icon', 'decline')); } else { $majorActions->push(FormAction::create('save', _t('SiteTree.BUTTONSAVED', 'Saved'))->addExtraClass('ss-ui-alternate ss-ui-action-constructive')->setAttribute('data-icon', 'accept')->setAttribute('data-icon-alternate', 'addpage')->setAttribute('data-text-alternate', _t('CMSMain.SAVEDRAFT', 'Save draft'))->setUseButtonTag(true)); $majorActions->push(FormAction::create('publish', _t('SiteTree.BUTTONPUBLISHED', 'Published'))->addExtraClass('ss-ui-alternate ss-ui-action-constructive')->setAttribute('data-icon', 'accept')->setAttribute('data-icon-alternate', 'disk')->setAttribute('data-text-alternate', _t('SiteTree.BUTTONSAVEPUBLISH', 'Save & publish'))->setUseButtonTag(true)); } } } else { if ($this->record->canDelete() && !$published) { $moreOptions->push(FormAction::create('delete', _t('SiteTree.BUTTONDELETE', 'Delete draft'))->addExtraClass('ss-ui-action-destructive')->setUseButtonTag(true)); } $majorActions->push(FormAction::create('save', _t('SiteTree.BUTTONSAVED', 'Saved'))->setAttribute('data-icon', 'accept')->setAttribute('data-icon-alternate', 'addpage')->setAttribute('data-text-alternate', _t('CMSMain.SAVEDRAFT', 'Save draft'))->setUseButtonTag(true)); } } $publish = FormAction::create('publish', $published ? _t('SiteTree.BUTTONPUBLISHED', 'Published') : _t('SiteTree.BUTTONSAVEPUBLISH', 'Save & publish'))->setAttribute('data-icon', 'accept')->setAttribute('data-icon-alternate', 'disk')->setAttribute('data-text-alternate', _t('SiteTree.BUTTONSAVEPUBLISH', 'Save & publish'))->setUseButtonTag(true); if (!$published || $this->record->stagesDiffer('Stage', 'Live') && $published) { $publish->addExtraClass('ss-ui-alternate'); } if ($this->record->canPublish() && !$this->record->IsDeletedFromStage) { $majorActions->push($publish); } if ($published && $this->record->canPublish() && !$this->record->IsDeletedFromStage && $this->record->canDeleteFromLive()) { $moreOptions->push(FormAction::create('unpublish', _t('SiteTree.BUTTONUNPUBLISH', 'Unpublish'), 'delete')->addExtraClass('ss-ui-action-destructive')->setUseButtonTag(true)); } if ($this->record->stagesDiffer('Stage', 'Live') && !$this->record->IsDeletedFromStage && $this->record->isPublished() && $this->record->canEdit()) { $moreOptions->push(FormAction::create('rollback', _t('SiteTree.BUTTONCANCELDRAFT', 'Cancel draft changes'))->setDescription(_t('SiteTree.BUTTONCANCELDRAFTDESC', 'Delete your draft and revert to the currently published page'))->setUseButtonTag(true)); } $actions->push($majorActions); $actions->push($rootTabSet); if ($this->record->hasMethod('getCMSValidator')) { $form->setValidator($this->record->getCMSValidator()); } return $form; }
/** * To process this job, we need to get the next page whose ID is the next greater than the last * processed. This way we don't need to remember a bunch of data about what we've processed */ public function process() { if (ClassInfo::exists('Subsite')) { Subsite::disable_subsite_filter(); } $class = $this->reindexType; $pages = $class::get(); $pages = $pages->filter(array('ID:GreaterThan' => $this->lastIndexedID)); $pages = $pages->limit(Config::inst()->get(__CLASS__, 'at_a_time')); $pages = $pages->sort('ID ASC'); if (ClassInfo::exists('Subsite')) { Subsite::$disable_subsite_filter = false; } if (!$pages || !$pages->count()) { $this->isComplete = true; return; } $mode = Versioned::get_reading_mode(); Versioned::reading_stage('Stage'); // index away $service = singleton('SolrSearchService'); $live = array(); $stage = array(); $all = array(); foreach ($pages as $page) { // Make sure the current page is not orphaned. if ($page->ParentID > 0) { $parent = $page->getParent(); if (is_null($parent) || $parent === false) { continue; } } // Appropriately index the current page, taking versioning into account. if ($page->hasExtension('Versioned')) { $stage[] = $page; $base = $page->baseTable(); $idField = '"' . $base . '_Live"."ID"'; $livePage = Versioned::get_one_by_stage($page->ClassName, 'Live', $idField . ' = ' . $page->ID); if ($livePage) { $live[] = $livePage; } } else { $all[] = $page; } $this->lastIndexedID = $page->ID; } if (count($all)) { $service->indexMultiple($all); } if (count($stage)) { $service->indexMultiple($stage, 'Stage'); } if (count($live)) { $service->indexMultiple($live, 'Live'); } Versioned::set_reading_mode($mode); $this->lastIndexedID = $page->ID; $this->currentStep += $pages->count(); }
/** * Test version support. If an object has versioned then both the live and * staging tables should be updated. Other live records should be removed * as well. */ public function testVersionedObjects() { $versioned = $this->objFromFixture('PopulateFactoryTest_TestVersionedObject', 'objV1'); $versioned->publish('Stage', 'Live'); $obj = $this->factory->createObject('PopulateFactoryTest_TestVersionedObject', 'test', array('Content' => 'Updated Version Foo', 'PopulateMergeWhen' => "Title = 'Version Foo'")); $this->assertEquals($versioned->ID, $obj->ID); $this->assertEquals('Updated Version Foo', $obj->Content); $check = Versioned::get_one_by_stage('PopulateFactoryTest_TestVersionedObject', 'Live', "Title = 'Version Foo'"); $this->assertEquals('Updated Version Foo', $check->Content); }
public function testFileDelete() { \Versioned::reading_stage('Stage'); /** @var AssetControlExtensionTest_VersionedObject $object1 */ $object1 = AssetControlExtensionTest_VersionedObject::get()->filter('Title', 'My object')->first(); /** @var AssetControlExtensionTest_Object $object2 */ $object2 = AssetControlExtensionTest_Object::get()->filter('Title', 'Unversioned')->first(); /** @var AssetControlExtensionTest_ArchivedObject $object3 */ $object3 = AssetControlExtensionTest_ArchivedObject::get()->filter('Title', 'Archived')->first(); $this->assertTrue($object1->Download->exists()); $this->assertTrue($object1->Header->exists()); $this->assertTrue($object2->Image->exists()); $this->assertTrue($object3->Header->exists()); $this->assertEquals(AssetStore::VISIBILITY_PUBLIC, $object1->Download->getVisibility()); $this->assertEquals(AssetStore::VISIBILITY_PUBLIC, $object1->Header->getVisibility()); $this->assertEquals(AssetStore::VISIBILITY_PUBLIC, $object2->Image->getVisibility()); $this->assertEquals(AssetStore::VISIBILITY_PUBLIC, $object3->Header->getVisibility()); // Check live stage for versioned objects $object1Live = Versioned::get_one_by_stage('AssetControlExtensionTest_VersionedObject', 'Live', array('"ID"' => $object1->ID)); $object3Live = Versioned::get_one_by_stage('AssetControlExtensionTest_ArchivedObject', 'Live', array('"ID"' => $object3->ID)); $this->assertTrue($object1Live->Download->exists()); $this->assertTrue($object1Live->Header->exists()); $this->assertTrue($object3Live->Header->exists()); $this->assertEquals(AssetStore::VISIBILITY_PUBLIC, $object1Live->Download->getVisibility()); $this->assertEquals(AssetStore::VISIBILITY_PUBLIC, $object1Live->Header->getVisibility()); $this->assertEquals(AssetStore::VISIBILITY_PUBLIC, $object3Live->Header->getVisibility()); // Delete live records; Should cause versioned records to be protected $object1Live->deleteFromStage('Live'); $object3Live->deleteFromStage('Live'); $this->assertTrue($object1->Download->exists()); $this->assertTrue($object1->Header->exists()); $this->assertTrue($object3->Header->exists()); $this->assertTrue($object1Live->Download->exists()); $this->assertTrue($object1Live->Header->exists()); $this->assertTrue($object3Live->Header->exists()); $this->assertEquals(AssetStore::VISIBILITY_PROTECTED, $object1->Download->getVisibility()); $this->assertEquals(AssetStore::VISIBILITY_PROTECTED, $object1->Header->getVisibility()); $this->assertEquals(AssetStore::VISIBILITY_PROTECTED, $object3->Header->getVisibility()); // Delete draft record; Should remove all records // Archived assets only should remain $object1->delete(); $object2->delete(); $object3->delete(); $this->assertFalse($object1->Download->exists()); $this->assertFalse($object1->Header->exists()); $this->assertFalse($object2->Image->exists()); $this->assertTrue($object3->Header->exists()); $this->assertFalse($object1Live->Download->exists()); $this->assertFalse($object1Live->Header->exists()); $this->assertTrue($object3Live->Header->exists()); $this->assertNull($object1->Download->getVisibility()); $this->assertNull($object1->Header->getVisibility()); $this->assertNull($object2->Image->getVisibility()); $this->assertEquals(AssetStore::VISIBILITY_PROTECTED, $object3->Header->getVisibility()); }
function testUnpublishing() { $id = $this->form->ID; $this->form->Fields()->removeAll(); $this->form->Fields()->add(new EditableFormField()); $this->form->doUnPublish(); $live = Versioned::get_one_by_stage("UserDefinedForm", "Live", "\"UserDefinedForm_Live\".\"ID\" = {$id}"); $stage = Versioned::get_one_by_stage("UserDefinedForm", "Stage", "\"UserDefinedForm\".\"ID\" = {$id}"); $this->assertEquals($live, false); $this->assertEquals($stage->Fields()->Count(), 1); }
function testDeletedPagesFilter() { $deletedPage = $this->objFromFixture('Page', 'page2'); $deletedPage->publish('Stage', 'Live'); $deletedPageID = $deletedPage->ID; $deletedPage->delete(); $deletedPage = Versioned::get_one_by_stage('SiteTree', 'Live', sprintf('"SiteTree_Live"."ID" = %d', $deletedPageID)); $f = new CMSSiteTreeFilter_DeletedPages(); $results = $f->pagesIncluded(); $this->assertTrue($f->isPageIncluded($deletedPage)); }
function onBeforeDelete() { // check if Page still exists in live mode $className = $this->ClassName; $livePage = Versioned::get_one_by_stage($className, "Live", "\"{$className}_Live\".\"ID\" = {$this->ID}"); // check if Page still exists in stage mode $stagePage = Versioned::get_one_by_stage($className, "Stage", "\"{$className}\".\"ID\" = {$this->ID}"); // if Page only exists in Live OR Stage mode -> Page will be deleted completely if (!($livePage && $stagePage)) { // delete existing Albums $this->Albums()->removeAll(); } parent::onBeforeDelete(); }
/** * Migrate a versioned field in all stages * * @param EditableFormField $field */ protected function upgradeField(EditableFormField $field) { $this->log("Upgrading formfield ID = " . $field->ID); // Check versions this field exists on $filter = sprintf('"EditableFormField"."ID" = \'%d\' AND "Migrated" = 0', $field->ID); $stageField = Versioned::get_one_by_stage('EditableFormField', 'Stage', $filter); $liveField = Versioned::get_one_by_stage('EditableFormField', 'Live', $filter); if ($stageField) { $this->upgradeFieldInStage($stageField, 'Stage'); } if ($liveField) { $this->upgradeFieldInStage($liveField, 'Live'); } }
public function doPublish() { if ($this->owner->hasMethod('canPublish') && !$this->owner->canPublish()) return false; $original = Versioned::get_one_by_stage($this->owner->ClassName, 'Live', "\"{$this->owner->ClassName}\".\"ID\" = {$this->owner->ID}"); $original = $original? $original: new $this->owner->ClassName; $this->owner->invokeWithExtensions('onBeforePublish', $original); $this->owner->publish('Stage', 'Live'); $this->owner->invokeWithExtensions('onAfterPublish', $original); return true; }
function testRollbackTo() { $page1 = $this->objFromFixture('Page', 'page1'); $page1->Content = 'orig'; $page1->write(); $page1->publish('Stage', 'Live'); $origVersion = $page1->Version; $page1->Content = 'changed'; $page1->write(); $page1->publish('Stage', 'Live'); $changedVersion = $page1->Version; $page1->doRollbackTo($origVersion); $page1 = Versioned::get_one_by_stage('Page', 'Stage', sprintf('"SiteTree"."ID" = %d', $page1->ID)); $this->assertTrue($page1->Version > $changedVersion, 'Create a new higher version number'); $this->assertEquals('orig', $page1->Content, 'Copies the content from the old version'); }
/** * @param SS_HTTPRequest $request * * @return string|HTMLText */ public function preview(SS_HTTPRequest $request) { $key = $request->param('Key'); $token = $request->param('Token'); /** * @var ShareToken $shareToken */ $shareToken = ShareToken::get()->filter('token', $token)->first(); if (!$shareToken) { return $this->errorPage(); } $page = Versioned::get_one_by_stage('SiteTree', 'Stage', sprintf('"SiteTree"."ID" = \'%d\'', $shareToken->PageID)); $latest = Versioned::get_latest_version('SiteTree', $shareToken->PageID); $controller = $this->getControllerFor($page); if (!$shareToken->isExpired() && $page->generateKey($shareToken->Token) === $key) { Requirements::css(SHAREDRAFTCONTENT_DIR . '/css/top-bar.css'); // Temporarily un-secure the draft site and switch to draft $oldSecured = Session::get('unsecuredDraftSite'); $oldMode = Versioned::get_reading_mode(); $restore = function () use($oldSecured, $oldMode) { Session::set('unsecuredDraftSite', $oldSecured); Versioned::set_reading_mode($oldMode); }; // Process page inside an unsecured draft container try { Session::set('unsecuredDraftSite', true); Versioned::reading_stage('Stage'); // Create mock request; Simplify request to single top level reqest $pageRequest = new SS_HTTPRequest('GET', $page->URLSegment); $pageRequest->match('$URLSegment//$Action/$ID/$OtherID', true); $rendered = $controller->handleRequest($pageRequest, $this->model); // Render draft heading $data = new ArrayData(array('Page' => $page, 'Latest' => $latest)); $include = (string) $data->renderWith('Includes/TopBar'); } catch (Exception $ex) { $restore(); throw $ex; } $restore(); return str_replace('</body>', $include . '</body>', (string) $rendered->getBody()); } else { return $this->errorPage(); } }
function testImportPublishAll() { $data = <<<YML Parent1 Parent2 \tChild2_1 \t\tGrandchild2_1_1 \tChild2_2 Parent3 YML; $this->import($data, array('PublishAll' => 1)); $parent1 = Versioned::get_one_by_stage('Page', 'Live', "\"Title\" = 'Parent1'"); $parent2 = Versioned::get_one_by_stage('Page', 'Live', "\"Title\" = 'Parent2'"); $parent3 = Versioned::get_one_by_stage('Page', 'Live', "\"Title\" = 'Parent3'"); $child2_1 = Versioned::get_one_by_stage('Page', 'Live', "\"Title\" = 'Child2_1'"); $child2_2 = Versioned::get_one_by_stage('Page', 'Live', "\"Title\" = 'Child2_2'"); $grandchild2_1_1 = Versioned::get_one_by_stage('Page', 'Live', "\"Title\" = 'Grandchild2_1_1'"); $this->assertInstanceOf('Page', $parent1); $this->assertInstanceOf('Page', $parent2); $this->assertInstanceOf('Page', $parent3); $this->assertInstanceOf('Page', $child2_1); $this->assertInstanceOf('Page', $child2_2); $this->assertInstanceOf('Page', $grandchild2_1_1); }
function run($request) { $ids = array(); echo "#################################\n"; echo "# Adding translation groups to existing records" . "\n"; echo "#################################\n"; $allSiteTreeIDs = DB::query('SELECT `ID` FROM `SiteTree`')->column(); if ($allSiteTreeIDs) { foreach ($allSiteTreeIDs as $id) { $original = DataObject::get_by_id('SiteTree', $id); $existingGroupID = $original->getTranslationGroup(); if (!$existingGroupID) { $original->addTranslationGroup($original->ID); } $original->destroy(); unset($original); } } DataObject::flush_and_destroy_cache(); echo sprintf("Created translation groups for %d records\n", count($allSiteTreeIDs)); foreach (array('Stage', 'Live') as $stage) { echo "\n\n#################################\n"; echo "# Migrating stage {$stage}" . "\n"; echo "#################################\n"; $suffix = $stage == 'Live' ? '_Live' : ''; // First get all entries in SiteTree_lang // This should be all translated pages $trans = DB::query(sprintf('SELECT * FROM `_obsolete_SiteTree_lang%s`', $suffix)); // Iterate over each translated pages foreach ($trans as $oldtrans) { $newLocale = i18n::get_locale_from_lang($oldtrans['Lang']); echo sprintf("Migrating from %s to %s translation of '%s' (#%d)\n", $oldtrans['Lang'], $newLocale, Convert::raw2xml($oldtrans['Title']), $oldtrans['OriginalLangID']); // Get the untranslated page $original = Versioned::get_one_by_stage($oldtrans['ClassName'], $stage, '`SiteTree`.`ID` = ' . $oldtrans['OriginalLangID']); if (!$original) { echo sprintf("Couldn't find original for #%d", $oldtrans['OriginalLangID']); continue; } // write locale to $original $original->Locale = i18n::get_locale_from_lang(Translatable::default_lang()); $original->writeToStage($stage); // Clone the original, and set it up as a translation $existingTrans = $original->getTranslation($newLocale, $stage); if ($existingTrans) { echo sprintf("Found existing new-style translation for #%d. Already merged? Skipping.\n", $oldtrans['OriginalLangID']); continue; } // Doesn't work with stage/live split //$newtrans = $original->createTranslation($newLocale); $newtrans = $original->duplicate(false); $newtrans->OriginalID = $original->ID; // we have to "guess" a locale based on the language $newtrans->Locale = $newLocale; if ($stage == 'Live' && array_key_exists($original->ID, $ids)) { $newtrans->ID = $ids[$original->ID]; } // Look at each class in the ancestry, and see if there is a _lang table for it foreach (ClassInfo::ancestry($oldtrans['ClassName']) as $classname) { $oldtransitem = false; // If the class is SiteTree, we already have the DB record, else check for the table and get the record if ($classname == 'SiteTree') { $oldtransitem = $oldtrans; } elseif (in_array(strtolower($classname) . '_lang', DB::tableList())) { $oldtransitem = DB::query(sprintf('SELECT * FROM `_obsolete_%s_lang%s` WHERE `OriginalLangID` = %d AND `Lang` = \'%s\'', $classname, $suffix, $original->ID, $oldtrans['Lang']))->first(); } // Copy each translated field into the new translation if ($oldtransitem) { foreach ($oldtransitem as $key => $value) { if (!in_array($key, array('ID', 'OriginalLangID'))) { $newtrans->{$key} = $value; } } } } // Write the new translation to the database $sitelang = Translatable::get_current_locale(); Translatable::set_current_locale($newtrans->Locale); $newtrans->writeToStage($stage); Translatable::set_current_locale($sitelang); $newtrans->addTranslationGroup($original->getTranslationGroup(), true); if ($stage == 'Stage') { $ids[$original->ID] = $newtrans->ID; } } } echo "\n\n#################################\n"; echo "Done!\n"; }
/** * Get related product * - live version if in cart, or * - historical version if order is placed * * @param boolean $forcecurrent - force getting latest version of the product. * @return Product */ public function Product($forcecurrent = false) { //TODO: this might need some unit testing to make sure it compliles with comment description //ie use live if in cart (however I see no logic for checking cart status) if ($this->ProductID && $this->ProductVersion && !$forcecurrent) { return Versioned::get_version('Product', $this->ProductID, $this->ProductVersion); } elseif ($this->ProductID && ($product = Versioned::get_one_by_stage("Product", "Live", "\"Product\".\"ID\" = " . $this->ProductID))) { return $product; } return false; }
public function testRevertToLiveFixesBrokenLinks() { // Create page and virutal page $p = new Page(); $p->Title = "source"; $p->write(); $pageID = $p->ID; $this->assertTrue($p->doPublish()); // Content links are one kind of link to pages $p2 = new Page(); $p2->Title = "regular link"; $p2->Content = "<a href=\"[sitetree_link,id={$p->ID}]\">test</a>"; $p2->write(); $this->assertTrue($p2->doPublish()); // Virtual pages are another $vp = new VirtualPage(); $vp->CopyContentFromID = $p->ID; $vp->write(); // Redirector links are a third $rp = new RedirectorPage(); $rp->Title = "redirector"; $rp->LinkType = 'Internal'; $rp->LinkToID = $p->ID; $rp->write(); $this->assertTrue($rp->doPublish()); // Confirm that there are no broken links to begin with $this->assertFalse($p2->HasBrokenLink); $this->assertFalse($vp->HasBrokenLink); $this->assertFalse($rp->HasBrokenLink); // Delete from draft and confirm that broken links are marked $pID = $p->ID; $p->delete(); $vp->flushCache(); $vp = DataObject::get_by_id('SiteTree', $vp->ID); $p2->flushCache(); $p2 = DataObject::get_by_id('SiteTree', $p2->ID); $rp->flushCache(); $rp = DataObject::get_by_id('SiteTree', $rp->ID); $this->assertEquals(1, $p2->HasBrokenLink); $this->assertEquals(1, $vp->HasBrokenLink); $this->assertEquals(1, $rp->HasBrokenLink); // Call doRevertToLive and confirm that broken links are restored $pLive = Versioned::get_one_by_stage('SiteTree', 'Live', '"SiteTree"."ID" = ' . $pID); $pLive->doRevertToLive(); $p2->flushCache(); $p2 = DataObject::get_by_id('SiteTree', $p2->ID); $vp->flushCache(); $vp = DataObject::get_by_id('SiteTree', $vp->ID); $rp->flushCache(); $rp = DataObject::get_by_id('SiteTree', $rp->ID); $this->assertFalse((bool) $p2->HasBrokenLink); $this->assertFalse((bool) $vp->HasBrokenLink); $this->assertFalse((bool) $rp->HasBrokenLink); }
protected function getLivePage() { $baseTable = ClassInfo::baseDataClass($this->record->class); return Versioned::get_one_by_stage($baseTable, 'Live', array("\"{$baseTable}\".\"ID\"" => $this->record->ID)); }
/** * Reverts a page by publishing it to live. * Use {@link restorepage()} if you want to restore a page * which was deleted from draft without publishing. * * @uses SiteTree->doRevertToLive() */ public function revert($data, $form) { if (!isset($data['ID'])) { return new SS_HTTPResponse("Please pass an ID in the form content", 400); } $id = (int) $data['ID']; $restoredPage = Versioned::get_latest_version("ContentModule", $id); if (!$restoredPage) { return new SS_HTTPResponse("ContentModule #{$id} not found", 400); } $record = Versioned::get_one_by_stage('SiteTree', 'Live', sprintf("\"SiteTree_Live\".\"ID\" = '%d'", (int) $data['ID'])); // a user can restore a page without publication rights, as it just adds a new draft state // (this action should just be available when page has been "deleted from draft") if ($record && !$record->canEdit()) { return Security::permissionFailure($this); } if (!$record || !$record->ID) { throw new SS_HTTPResponse_Exception("Bad record ID #{$id}", 404); } $record->doRevertToLive(); $this->response->addHeader('X-Status', rawurlencode(_t('CMSMain.RESTORED', "Restored '{title}' successfully", 'Param %s is a title', array('title' => $record->Title)))); return $this->getResponseNegotiator()->respond($this->request); }
public function run(SS_List $pages) { Deprecation::notice('4.0', 'Delete From Live is deprecated. Use Unpublish instead'); $status = array('modified' => array(), 'deleted' => array()); foreach ($pages as $page) { $id = $page->ID; // Perform the action if ($page->canDelete()) { $page->doDeleteFromLive(); } // check to see if the record exists on the stage site, if it doesn't remove the tree node $stageRecord = Versioned::get_one_by_stage('SiteTree', 'Stage', array('"SiteTree"."ID"' => $id)); if ($stageRecord) { $status['modified'][$stageRecord->ID] = array('TreeTitle' => $stageRecord->TreeTitle); } else { $status['deleted'][$id] = array(); } } return $this->response(_t('CMSBatchActions.DELETED_PAGES', 'Deleted %d pages from published site, %d failures'), $status); }
/** * Returns the old record that will be replaced by this publication. */ public function fromRecord() { $bt = defined('DB::USE_ANSI_SQL') ? "\"" : "`"; return Versioned::get_one_by_stage('SiteTree', 'Live', "{$bt}SiteTree_Live{$bt}.{$bt}ID{$bt} = {$this->PageID}", true, "\"Created\" DESC"); }
/** * Return a few pieces of information about a change to a page * - Send the new status message * - Update the action buttons * - Update the treenote * - Send a status message */ function tellBrowserAboutPublicationChange($page, $statusMessage) { $JS_title = Convert::raw2js($page->TreeTitle()); $JS_stageURL = $page->IsDeletedFromStage ? '' : Convert::raw2js($page->AbsoluteLink()); $liveRecord = Versioned::get_one_by_stage('SiteTree', 'Live', "\"SiteTree\".\"ID\" = {$page->ID}"); $JS_liveURL = $liveRecord ? Convert::raw2js($liveRecord->AbsoluteLink()) : ''; FormResponse::add($this->getActionUpdateJS($page)); FormResponse::update_status($page->Status); if ($JS_stageURL || $JS_liveURL) { FormResponse::add("\$('sitetree').setNodeTitle({$page->ID}, '{$JS_title}');"); } else { FormResponse::add("var node = \$('sitetree').getTreeNodeByIdx('{$page->ID}');"); FormResponse::add("if(node && node.parentTreeNode) node.parentTreeNode.removeTreeNode(node);"); FormResponse::add("\$('Form_EditForm').reloadIfSetTo({$page->ID});"); } if ($statusMessage) { FormResponse::status_message($statusMessage, 'good'); } FormResponse::add("\$('Form_EditForm').elements.StageURLSegment.value = '{$JS_stageURL}';"); FormResponse::add("\$('Form_EditForm').elements.LiveURLSegment.value = '{$JS_liveURL}';"); FormResponse::add("\$('Form_EditForm').notify('PagePublished', \$('Form_EditForm').elements.ID.value);"); return FormResponse::respond(); }
protected function rebaseOrphans($orphanIDs) { $holder = new SiteTree(); $holder->ShowInMenus = 0; $holder->ShowInSearch = 0; $holder->ParentID = 0; $holder->Title = $this->rebaseHolderTitle(); $holder->write(); $removedOrphans = array(); foreach ($orphanIDs as $id) { $stageRecord = Versioned::get_one_by_stage($this->orphanedSearchClass, 'Stage', sprintf("\"%s\".\"ID\" = %d", ClassInfo::baseDataClass($this->orphanedSearchClass), $id)); if ($stageRecord) { $removedOrphans[$stageRecord->ID] = sprintf('Rebased %s (#%d)', $stageRecord->Title, $stageRecord->ID); $stageRecord->ParentID = $holder->ID; $stageRecord->ShowInMenus = 0; $stageRecord->ShowInSearch = 0; $stageRecord->write(); $stageRecord->doUnpublish(); $stageRecord->destroy(); //unset($stageRecord); } $liveRecord = Versioned::get_one_by_stage($this->orphanedSearchClass, 'Live', sprintf("\"%s\".\"ID\" = %d", ClassInfo::baseDataClass($this->orphanedSearchClass), $id)); if ($liveRecord) { $removedOrphans[$liveRecord->ID] = sprintf('Rebased %s (#%d)', $liveRecord->Title, $liveRecord->ID); $liveRecord->ParentID = $holder->ID; $liveRecord->ShowInMenus = 0; $liveRecord->ShowInSearch = 0; $liveRecord->write(); if (!$stageRecord) { $liveRecord->doRestoreToStage(); } $liveRecord->doUnpublish(); $liveRecord->destroy(); unset($liveRecord); } if ($stageRecord) { unset($stageRecord); } } return $removedOrphans; }
function testDoRevertToLive() { $this->logInWithPermission('ADMIN'); $form = $this->objFromFixture('UserDefinedForm', 'basic-form-page'); $field = $form->Fields()->First(); $field->Title = 'Title'; $field->write(); $form->doPublish(); $field->Title = 'Edited title'; $field->write(); // check that the published version is not updated $live = Versioned::get_one_by_stage("EditableFormField", "Live", "\"EditableFormField_Live\".\"ID\" = {$field->ID}"); $this->assertEquals('Title', $live->Title); // revert back to the live data $form->doRevertToLive(); $form->flushCache(); $check = Versioned::get_one_by_stage("EditableFormField", "Stage", "\"EditableFormField\".\"ID\" = {$field->ID}"); $this->assertEquals('Title', $check->Title); }
function testLinkTrackingOnExtraContentFields() { $page1 = $this->objFromFixture('Page', 'page1'); $page2 = $this->objFromFixture('Page', 'page2'); $page1->doPublish(); $page2->doPublish(); // assert backlink to page 2 doesn't exist $this->assertNotContains($page2->ID, $page1->BackLinkTracking()->column('ID'), 'Assert backlink to page 2 doesn\'t exist'); // add hyperlink to page 1 on page 2 $page2->ExtraContent .= '<p><a href="[sitetree_link id='.$page1->ID.']">Testing page 1 link</a></p>'; $page2->write(); $page2->doPublish(); // assert backlink to page 2 exists $this->assertContains($page2->ID, $page1->BackLinkTracking()->column('ID'), 'Assert backlink to page 2 exists'); // update page1 url $page1 = $this->objFromFixture('Page', 'page1'); $page1->URLSegment = "page1-new-url"; $page1->write(); // confirm that draft link on page2 has been rewritten $page2 = $this->objFromFixture('Page', 'page2'); $this->assertEquals('<p><a href="'.Director::baseURL().'page1-new-url/">Testing page 1 link</a></p>', $page2->obj('ExtraContent')->forTemplate()); // confirm that published link hasn't $page2Live = Versioned::get_one_by_stage("Page", "Live", "\"SiteTree\".\"ID\" = $page2->ID"); Versioned::reading_stage('Live'); $this->assertEquals('<p><a href="'.Director::baseURL().'page1/">Testing page 1 link</a></p>', $page2Live->obj('ExtraContent')->forTemplate()); // publish page1 and confirm that the link on the published page2 has now been updated $page1->doPublish(); $page2Live = Versioned::get_one_by_stage("Page", "Live", "\"SiteTree\".\"ID\" = $page2->ID"); $this->assertEquals('<p><a href="'.Director::baseURL().'page1-new-url/">Testing page 1 link</a></p>', $page2Live->obj('ExtraContent')->forTemplate()); // remove hyperlink to page 1 $page2->ExtraContent = '<p>No links anymore!</p>'; $page2->write(); // assert backlink to page 2 no longer exists $this->assertNotContains($page2->ID, $page1->BackLinkTracking()->column('ID'), 'Assert backlink to page 2 has been removed'); }
function getHTML($page) { if (Versioned::current_archived_date()) { return "<a class=\"current\">" . _t('ContentController.ARCHIVEDSITE', 'Archived Site') . "</a>"; } else { // Display the archive link if the page currently displayed in the CMS is other version than live and draft $currentDraft = Versioned::get_one_by_stage('SiteTree', 'Draft', '"SiteTree"."ID" = ' . $page->ID); $currentLive = Versioned::get_one_by_stage('SiteTree', 'Live', '"SiteTree"."ID" = ' . $page->ID); if ((!$currentDraft || $currentDraft && $page->Version != $currentDraft->Version) && (!$currentLive || $currentLive && $page->Version != $currentLive->Version)) { $pageLink = $page->AbsoluteLink(); return "<a href=\"{$pageLink}?archiveDate={$page->LastEdited}\" class=\"newWindow\" target=\"site\" style=\"left : -3px;\">" . _t('ContentController.ARCHIVEDSITE', 'Archived Site') . "</a>"; } } }
public function testPageTypeChangePropagatesToLive() { $page = new SiteTree(); $page->MySharedNonVirtualField = 'original'; $page->write(); $page->publish('Stage', 'Live'); $virtual = new VirtualPageTest_VirtualPageSub(); $virtual->CopyContentFromID = $page->ID; $virtual->write(); $virtual->publish('Stage', 'Live'); $page->Title = 'original'; // 'Title' is a virtual field // Publication would causes the virtual field to copy through onBeforeWrite(), // but we want to test that it gets copied on class name change instead $page->write(); $nonVirtual = $virtual; $nonVirtual->ClassName = 'VirtualPageTest_ClassA'; $nonVirtual->MySharedNonVirtualField = 'changed on new type'; $nonVirtual->write(); // not publishing the page type change here $this->assertEquals('original', $nonVirtual->Title, 'Copies virtual fields from original draft into new instance on type change '); $nonVirtualLive = Versioned::get_one_by_stage('SiteTree', 'Live', '"SiteTree_Live"."ID" = ' . $nonVirtual->ID); $this->assertNotNull($nonVirtualLive); $this->assertEquals('VirtualPageTest_ClassA', $nonVirtualLive->ClassName); $this->assertEquals('changed on new type', $nonVirtualLive->MySharedNonVirtualField); $page->MySharedNonVirtualField = 'changed only on original'; $page->write(); $page->publish('Stage', 'Live'); $nonVirtualLive = Versioned::get_one_by_stage('SiteTree', 'Live', '"SiteTree_Live"."ID" = ' . $nonVirtual->ID, false); $this->assertEquals('changed on new type', $nonVirtualLive->MySharedNonVirtualField, 'No field copying from previous original after page type changed'); }
public function testURLSegmentMultiByte() { $origAllow = Config::inst()->get('URLSegmentFilter', 'default_allow_multibyte'); Config::inst()->update('URLSegmentFilter', 'default_allow_multibyte', true); $sitetree = new SiteTree(); $sitetree->write(); $sitetree->URLSegment = 'brötchen'; $sitetree->write(); $sitetree = DataObject::get_by_id('SiteTree', $sitetree->ID, false); $this->assertEquals($sitetree->URLSegment, rawurlencode('brötchen')); $sitetree->publish('Stage', 'Live'); $sitetree = DataObject::get_by_id('SiteTree', $sitetree->ID, false); $this->assertEquals($sitetree->URLSegment, rawurlencode('brötchen')); $sitetreeLive = Versioned::get_one_by_stage('SiteTree', 'Live', '"SiteTree"."ID" = ' . $sitetree->ID, false); $this->assertEquals($sitetreeLive->URLSegment, rawurlencode('brötchen')); Config::inst()->update('URLSegmentFilter', 'default_allow_multibyte', $origAllow); }
/** * Test that a draft-deleted page can still be opened in the CMS */ function testDraftDeletedPageCanBeOpenedInCMS() { $this->session()->inst_set('loggedInAs', $this->idFromFixture('Member', 'admin')); // Set up a page that is delete from live $page = $this->objFromFixture('Page', 'page1'); $pageID = $page->ID; $page->doPublish(); $page->delete(); $response = $this->get('admin/cms/getitem?ID=' . $pageID . '&ajax=1'); $livePage = Versioned::get_one_by_stage("SiteTree", "Live", "\"SiteTree\".\"ID\" = {$pageID}"); $this->assertType('SiteTree', $livePage); $this->assertTrue($livePage->canDelete()); // Check that the 'restore' button exists as a simple way of checking that the correct page is returned. $this->assertRegExp('/<input[^>]+type="submit"[^>]+name="action_(restore|revert)"/i', $response->getBody()); }
/** * Test that publishing processes respects lazy loaded fields */ public function testLazyLoadFields() { $originalMode = Versioned::get_reading_mode(); // Generate staging record and retrieve it from stage in live mode Versioned::reading_stage('Stage'); $obj = new VersionedTest_Subclass(); $obj->Name = 'bob'; $obj->ExtraField = 'Field Value'; $obj->write(); $objID = $obj->ID; $filter = sprintf('"VersionedTest_DataObject"."ID" = \'%d\'', Convert::raw2sql($objID)); Versioned::reading_stage('Live'); // Check fields are unloaded prior to access $objLazy = Versioned::get_one_by_stage('VersionedTest_DataObject', 'Stage', $filter, false); $lazyFields = $objLazy->getQueriedDatabaseFields(); $this->assertTrue(isset($lazyFields['ExtraField_Lazy'])); $this->assertEquals('VersionedTest_Subclass', $lazyFields['ExtraField_Lazy']); // Check lazy loading works when viewing a Stage object in Live mode $this->assertEquals('Field Value', $objLazy->ExtraField); // Test that writeToStage respects lazy loaded fields $objLazy = Versioned::get_one_by_stage('VersionedTest_DataObject', 'Stage', $filter, false); $objLazy->writeToStage('Live'); $objLive = Versioned::get_one_by_stage('VersionedTest_DataObject', 'Live', $filter, false); $liveLazyFields = $objLive->getQueriedDatabaseFields(); // Check fields are unloaded prior to access $this->assertTrue(isset($liveLazyFields['ExtraField_Lazy'])); $this->assertEquals('VersionedTest_Subclass', $liveLazyFields['ExtraField_Lazy']); // Check that live record has original value $this->assertEquals('Field Value', $objLive->ExtraField); Versioned::set_reading_mode($originalMode); }