public function setUp() { parent::setUp(); $this->logInWithPermission('ADMIN'); Versioned::set_stage(Versioned::DRAFT); // Set backend root to /AssetFieldTest AssetStoreTest_SpyStore::activate('AssetFieldTest'); $create = function ($path) { Filesystem::makeFolder(dirname($path)); $fh = fopen($path, "w+"); fwrite($fh, str_repeat('x', 1000000)); fclose($fh); }; // Write all DBFile references foreach (AssetFieldTest_Object::get() as $object) { $path = AssetStoreTest_SpyStore::getLocalPath($object->File); $create($path); } // Create a test files for each of the fixture references $files = File::get()->exclude('ClassName', 'SilverStripe\\Assets\\Folder'); foreach ($files as $file) { $path = AssetStoreTest_SpyStore::getLocalPath($file); $create($path); } }
public function setUp() { parent::setUp(); $this->logInWithPermission('ADMIN'); Versioned::set_stage(Versioned::DRAFT); // Set backend root to /ImageTest AssetStoreTest_SpyStore::activate('FileTest'); // Create a test folders for each of the fixture references $folderIDs = $this->allFixtureIDs('Folder'); foreach ($folderIDs as $folderID) { $folder = DataObject::get_by_id('Folder', $folderID); $filePath = ASSETS_PATH . '/FileTest/' . $folder->getFilename(); SS_Filesystem::makeFolder($filePath); } // Create a test files for each of the fixture references $fileIDs = $this->allFixtureIDs('File'); foreach ($fileIDs as $fileID) { $file = DataObject::get_by_id('File', $fileID); $root = ASSETS_PATH . '/FileTest/'; if ($folder = $file->Parent()) { $root .= $folder->getFilename(); } $path = $root . substr($file->getHash(), 0, 10) . '/' . basename($file->getFilename()); SS_Filesystem::makeFolder(dirname($path)); $fh = fopen($path, "w+"); fwrite($fh, str_repeat('x', 1000000)); fclose($fh); } // Conditional fixture creation in case the 'cms' module is installed if (class_exists('SilverStripe\\CMS\\Model\\ErrorPage')) { $page = new ErrorPage(array('Title' => 'Page not Found', 'ErrorCode' => 404)); $page->write(); $page->copyVersionToStage('Stage', 'Live'); } }
/** * Perform migration * * @param string $base Absolute base path (parent of assets folder). Will default to BASE_PATH * @return int Number of files successfully migrated */ public function run($base = null) { if (empty($base)) { $base = BASE_PATH; } // Check if the File dataobject has a "Filename" field. // If not, cannot migrate /** @skipUpgrade */ if (!DB::get_schema()->hasField('File', 'Filename')) { return 0; } // Set max time and memory limit increase_time_limit_to(); increase_memory_limit_to(); // Loop over all files $count = 0; $originalState = Versioned::get_reading_mode(); Versioned::set_stage(Versioned::DRAFT); $filenameMap = $this->getFilenameArray(); foreach ($this->getFileQuery() as $file) { // Get the name of the file to import $filename = $filenameMap[$file->ID]; $success = $this->migrateFile($base, $file, $filename); if ($success) { $count++; } } Versioned::set_reading_mode($originalState); return $count; }
public function preRequest(HTTPRequest $request, Session $session, DataModel $model) { // Bootstrap session so that Session::get() accesses the right instance $dummyController = new Controller(); $dummyController->setSession($session); $dummyController->setRequest($request); $dummyController->pushCurrent(); // Block non-authenticated users from setting the stage mode if (!Versioned::can_choose_site_stage($request)) { $permissionMessage = sprintf(_t("ContentController.DRAFT_SITE_ACCESS_RESTRICTION", 'You must log in with your CMS password in order to view the draft or archived content. ' . '<a href="%s">Click here to go back to the published site.</a>'), Convert::raw2xml(Controller::join_links(Director::baseURL(), $request->getURL(), "?stage=Live"))); // Force output since RequestFilter::preRequest doesn't support response overriding $response = Security::permissionFailure($dummyController, $permissionMessage); $session->inst_save(); $dummyController->popCurrent(); // Prevent output in testing if (class_exists('SilverStripe\\Dev\\SapphireTest', false) && SapphireTest::is_running_test()) { throw new HTTPResponse_Exception($response); } $response->output(); die; } Versioned::choose_site_stage(); $dummyController->popCurrent(); return true; }
public function testHasOnes() { /** @var DataDifferencerTest_Object $obj1 */ $obj1 = $this->objFromFixture('DataDifferencerTest_Object', 'obj1'); $image1 = $this->objFromFixture('Image', 'image1'); $image2 = $this->objFromFixture('Image', 'image2'); $relobj1 = $this->objFromFixture('DataDifferencerTest_HasOneRelationObject', 'relobj1'); $relobj2 = $this->objFromFixture('DataDifferencerTest_HasOneRelationObject', 'relobj2'); // create a new version $beforeVersion = $obj1->Version; $obj1->ImageID = $image2->ID; $obj1->HasOneRelationID = $relobj2->ID; $obj1->write(); $afterVersion = $obj1->Version; $this->assertNotEquals($beforeVersion, $afterVersion); /** @var DataDifferencerTest_Object $obj1v1 */ $obj1v1 = Versioned::get_version('DataDifferencerTest_Object', $obj1->ID, $beforeVersion); /** @var DataDifferencerTest_Object $obj1v2 */ $obj1v2 = Versioned::get_version('DataDifferencerTest_Object', $obj1->ID, $afterVersion); $differ = new DataDifferencer($obj1v1, $obj1v2); $obj1Diff = $differ->diffedData(); $this->assertContains($image1->Name, $obj1Diff->getField('Image')); $this->assertContains($image2->Name, $obj1Diff->getField('Image')); $this->assertContains('<ins>obj2</ins><del>obj1</del>', str_replace(' ', '', $obj1Diff->getField('HasOneRelationID'))); }
public function tearDown() { AssetStoreTest_SpyStore::reset(); if ($this->oldReadingMode) { Versioned::set_reading_mode($this->oldReadingMode); } parent::tearDown(); }
public function getSchemaStateDefaults() { $state = parent::getSchemaStateDefaults(); if ($record = $this->getRecord()) { $latest = Versioned::get_latest_version($record->baseClass(), $record->ID); if ($latest) { $state['data']['fileId'] = $latest->ID; $state['data']['latestVersionId'] = $latest->Version; } } return $state; }
/** * Test Hierarchy::AllHistoricalChildren(). */ public function testAllHistoricalChildren() { // Delete some objs $this->objFromFixture('HierarchyTest_Object', 'obj2b')->delete(); $this->objFromFixture('HierarchyTest_Object', 'obj3a')->delete(); $this->objFromFixture('HierarchyTest_Object', 'obj3')->delete(); // Check that obj1-3 appear at the top level of the AllHistoricalChildren tree $this->assertEquals(array("Obj 1", "Obj 2", "Obj 3"), singleton('HierarchyTest_Object')->AllHistoricalChildren()->column('Title')); // Check numHistoricalChildren $this->assertEquals(3, singleton('HierarchyTest_Object')->numHistoricalChildren()); // Check that both obj 2 children are returned $obj2 = $this->objFromFixture('HierarchyTest_Object', 'obj2'); $this->assertEquals(array("Obj 2a", "Obj 2b"), $obj2->AllHistoricalChildren()->column('Title')); // Check numHistoricalChildren $this->assertEquals(2, $obj2->numHistoricalChildren()); // Obj 3 has been deleted; let's bring it back from the grave $obj3 = Versioned::get_including_deleted("HierarchyTest_Object", "\"Title\" = 'Obj 3'")->First(); // Check that all obj 3 children are returned $this->assertEquals(array("Obj 3a", "Obj 3b", "Obj 3c", "Obj 3d"), $obj3->AllHistoricalChildren()->column('Title')); // Check numHistoricalChildren $this->assertEquals(4, $obj3->numHistoricalChildren()); }
protected function init() { parent::init(); // Special case for dev/build: Defer permission checks to DatabaseAdmin->init() (see #4957) $requestedDevBuild = stripos($this->getRequest()->getURL(), 'dev/build') === 0 && stripos($this->getRequest()->getURL(), 'dev/build/defaults') === false; // We allow access to this controller regardless of live-status or ADMIN permission only // if on CLI. Access to this controller is always allowed in "dev-mode", or of the user is ADMIN. $canAccess = $requestedDevBuild || Director::isDev() || Director::is_cli() || Permission::check("ADMIN"); if (!$canAccess) { Security::permissionFailure($this); return; } // check for valid url mapping // lacking this information can cause really nasty bugs, // e.g. when running Director::test() from a FunctionalTest instance global $_FILE_TO_URL_MAPPING; if (Director::is_cli()) { if (isset($_FILE_TO_URL_MAPPING)) { $testPath = BASE_PATH; $matched = false; while ($testPath && $testPath != "/" && !preg_match('/^[A-Z]:\\\\$/', $testPath)) { if (isset($_FILE_TO_URL_MAPPING[$testPath])) { $matched = true; break; } $testPath = dirname($testPath); } if (!$matched) { echo 'Warning: You probably want to define ' . 'an entry in $_FILE_TO_URL_MAPPING that covers "' . Director::baseFolder() . '"' . "\n"; } } else { echo 'Warning: You probably want to define $_FILE_TO_URL_MAPPING in ' . 'your _ss_environment.php as instructed on the "sake" page of the doc.silverstripe.org wiki' . "\n"; } } // Backwards compat: Default to "draft" stage, which is important // for tasks like dev/build which call DataObject->requireDefaultRecords(), // but also for other administrative tasks which have assumptions about the default stage. Versioned::set_stage(Versioned::DRAFT); }
/** * Return all the children that this page had, including pages that were deleted from both stage & live. * * @return DataList * @throws Exception */ public function AllHistoricalChildren() { if (!$this->owner->hasExtension('SilverStripe\\ORM\\Versioning\\Versioned')) { throw new Exception('Hierarchy->AllHistoricalChildren() only works with Versioned extension applied'); } $baseTable = $this->owner->baseTable(); $parentIDColumn = $this->owner->getSchema()->sqlColumnForField($this->owner, 'ParentID'); return Versioned::get_including_deleted($this->owner->baseClass(), [$parentIDColumn => $this->owner->ID], "\"{$baseTable}\".\"ID\" ASC"); }
public function tearDown() { // Preserve memory settings ini_set('memory_limit', $this->originalMemoryLimit ? $this->originalMemoryLimit : -1); // Restore email configuration $this->originalMailer = null; $this->mailer = null; // Restore password validation if ($this->originalMemberPasswordValidator) { Member::set_password_validator($this->originalMemberPasswordValidator); } // Restore requirements if ($this->originalRequirements) { Requirements::set_backend($this->originalRequirements); } // Mark test as no longer being run - we use originalIsRunningTest to allow for nested SapphireTest calls self::$is_running_test = $this->originalIsRunningTest; $this->originalIsRunningTest = null; // Reset mocked datetime DBDatetime::clear_mock_now(); // Stop the redirection that might have been requested in the test. // Note: Ideally a clean Controller should be created for each test. // Now all tests executed in a batch share the same controller. $controller = Controller::has_curr() ? Controller::curr() : null; if ($controller && $controller->response && $controller->response->getHeader('Location')) { $controller->response->setStatusCode(200); $controller->response->removeHeader('Location'); } Versioned::set_reading_mode($this->originalReadingMode); //unnest injector / config now that tests are over Injector::unnest(); Config::unnest(); }
/** * Test files being replaced */ public function testReplaceFile() { Versioned::set_stage(Versioned::DRAFT); /** @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(); $object1TupleOld = $object1->Header->getValue(); $object2TupleOld = $object2->Image->getValue(); $object3TupleOld = $object3->Header->getValue(); // Replace image and write each to filesystem $fish1 = realpath(__DIR__ . '/../model/testimages/test-image-high-quality.jpg'); $object1->Header->setFromLocalFile($fish1, 'Header/Replaced_MyObjectHeader.jpg'); $object1->write(); $object2->Image->setFromLocalFile($fish1, 'Images/Replaced_BeautifulFish.jpg'); $object2->write(); $object3->Header->setFromLocalFile($fish1, 'Archived/Replaced_MyObjectHeader.jpg'); $object3->write(); // Check that old published records are left public, but removed for unversioned object2 $this->assertEquals(AssetStore::VISIBILITY_PUBLIC, $this->getAssetStore()->getVisibility($object1TupleOld['Filename'], $object1TupleOld['Hash'])); $this->assertEquals(null, $this->getAssetStore()->getVisibility($object2TupleOld['Filename'], $object2TupleOld['Hash'])); $this->assertEquals(AssetStore::VISIBILITY_PUBLIC, $this->getAssetStore()->getVisibility($object3TupleOld['Filename'], $object3TupleOld['Hash'])); // Check that visibility of new file is correct // Note that $object2 has no canView() is true, so assets end up public $this->assertEquals(AssetStore::VISIBILITY_PROTECTED, $object1->Header->getVisibility()); $this->assertEquals(AssetStore::VISIBILITY_PUBLIC, $object2->Image->getVisibility()); $this->assertEquals(AssetStore::VISIBILITY_PROTECTED, $object3->Header->getVisibility()); // Publish changes to versioned records $object1->publishSingle(); $object3->publishSingle(); // After publishing, old object1 is deleted, but since object3 has archiving enabled, // the orphaned file is intentionally left in the protected store $this->assertEquals(null, $this->getAssetStore()->getVisibility($object1TupleOld['Filename'], $object1TupleOld['Hash'])); $this->assertEquals(AssetStore::VISIBILITY_PROTECTED, $this->getAssetStore()->getVisibility($object3TupleOld['Filename'], $object3TupleOld['Hash'])); // And after publish, all files are public $this->assertEquals(AssetStore::VISIBILITY_PUBLIC, $object1->Header->getVisibility()); $this->assertEquals(AssetStore::VISIBILITY_PUBLIC, $object3->Header->getVisibility()); }
/** * @uses LeftAndMainExtension->init() * @uses LeftAndMainExtension->accessedCMS() * @uses CMSMenu */ protected function init() { parent::init(); SSViewer::config()->update('rewrite_hash_links', false); ContentNegotiator::config()->update('enabled', false); // set language $member = Member::currentUser(); if (!empty($member->Locale)) { i18n::set_locale($member->Locale); } if (!empty($member->DateFormat)) { i18n::config()->date_format = $member->DateFormat; } if (!empty($member->TimeFormat)) { i18n::config()->time_format = $member->TimeFormat; } // can't be done in cms/_config.php as locale is not set yet CMSMenu::add_link('Help', _t('LeftAndMain.HELP', 'Help', 'Menu title'), $this->config()->help_link, -2, array('target' => '_blank')); // Allow customisation of the access check by a extension // Also all the canView() check to execute Controller::redirect() if (!$this->canView() && !$this->getResponse()->isFinished()) { // When access /admin/, we should try a redirect to another part of the admin rather than be locked out $menu = $this->MainMenu(); foreach ($menu as $candidate) { if ($candidate->Link && $candidate->Link != $this->Link() && $candidate->MenuItem->controller && singleton($candidate->MenuItem->controller)->canView()) { $this->redirect($candidate->Link); return; } } if (Member::currentUser()) { Session::set("BackURL", null); } // if no alternate menu items have matched, return a permission error $messageSet = array('default' => _t('LeftAndMain.PERMDEFAULT', "You must be logged in to access the administration area; please enter your credentials below."), 'alreadyLoggedIn' => _t('LeftAndMain.PERMALREADY', "I'm sorry, but you can't access that part of the CMS. If you want to log in as someone else, do" . " so below."), 'logInAgain' => _t('LeftAndMain.PERMAGAIN', "You have been logged out of the CMS. If you would like to log in again, enter a username and" . " password below.")); Security::permissionFailure($this, $messageSet); return; } // Don't continue if there's already been a redirection request. if ($this->redirectedTo()) { return; } // Audit logging hook if (empty($_REQUEST['executeForm']) && !$this->getRequest()->isAjax()) { $this->extend('accessedCMS'); } // Set the members html editor config if (Member::currentUser()) { HTMLEditorConfig::set_active_identifier(Member::currentUser()->getHtmlEditorConfigForCMS()); } // Set default values in the config if missing. These things can't be defined in the config // file because insufficient information exists when that is being processed $htmlEditorConfig = HTMLEditorConfig::get_active(); $htmlEditorConfig->setOption('language', i18n::get_tinymce_lang()); Requirements::customScript("\n\t\t\twindow.ss = window.ss || {};\n\t\t\twindow.ss.config = " . $this->getCombinedClientConfig() . ";\n\t\t"); Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/client/dist/js/vendor.js'); Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/client/dist/js/bundle.js'); Requirements::css(ltrim(FRAMEWORK_ADMIN_DIR . '/client/dist/styles/bundle.css', '/')); Requirements::add_i18n_javascript(ltrim(FRAMEWORK_DIR . '/client/lang', '/'), false, true); Requirements::add_i18n_javascript(FRAMEWORK_ADMIN_DIR . '/client/lang', false, true); if ($this->config()->session_keepalive_ping) { Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/client/dist/js/LeftAndMain.Ping.js'); } if (Director::isDev()) { // TODO Confuses jQuery.ondemand through document.write() Requirements::javascript(ADMIN_THIRDPARTY_DIR . '/jquery-entwine/src/jquery.entwine.inspector.js'); Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/client/dist/js/leaktools.js'); } // Custom requirements $extraJs = $this->stat('extra_requirements_javascript'); if ($extraJs) { foreach ($extraJs as $file => $config) { if (is_numeric($file)) { $file = $config; } Requirements::javascript($file); } } $extraCss = $this->stat('extra_requirements_css'); if ($extraCss) { foreach ($extraCss as $file => $config) { if (is_numeric($file)) { $file = $config; $config = array(); } Requirements::css($file, isset($config['media']) ? $config['media'] : null); } } $extraThemedCss = $this->stat('extra_requirements_themedCss'); if ($extraThemedCss) { foreach ($extraThemedCss as $file => $config) { if (is_numeric($file)) { $file = $config; $config = array(); } Requirements::themedCSS($file, isset($config['media']) ? $config['media'] : null); } } $dummy = null; $this->extend('init', $dummy); // Assign default cms theme and replace user-specified themes SSViewer::set_themes($this->config()->admin_themes); //set the reading mode for the admin to stage Versioned::set_stage(Versioned::DRAFT); }
public function testLazyLoadedFieldsDoNotReferenceVersionsTable() { // Save another record, sanity check that we're getting the right one $obj2 = new VersionedTest_Subclass(); $obj2->Name = "test2"; $obj2->ExtraField = "foo2"; $obj2->write(); $obj1 = new VersionedLazySub_DataObject(); $obj1->PageName = "old-value"; $obj1->ExtraField = "old-value"; $obj1ID = $obj1->write(); $obj1->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE); $obj1 = VersionedLazySub_DataObject::get()->byID($obj1ID); $this->assertEquals('old-value', $obj1->PageName, "Correct value on base table when fetching base class"); $this->assertEquals('old-value', $obj1->ExtraField, "Correct value on sub table when fetching base class"); $obj1 = VersionedLazy_DataObject::get()->byID($obj1ID); $this->assertEquals('old-value', $obj1->PageName, "Correct value on base table when fetching sub class"); $this->assertEquals('old-value', $obj1->ExtraField, "Correct value on sub table when fetching sub class"); // Force inconsistent state to test behaviour (shouldn't select from *_versions) DB::query(sprintf("UPDATE \"VersionedLazy_DataObject_versions\" SET \"PageName\" = 'versioned-value' " . "WHERE \"RecordID\" = %d", $obj1ID)); DB::query(sprintf("UPDATE \"VersionedLazySub_DataObject_versions\" SET \"ExtraField\" = 'versioned-value' " . "WHERE \"RecordID\" = %d", $obj1ID)); $obj1 = VersionedLazySub_DataObject::get()->byID($obj1ID); $this->assertEquals('old-value', $obj1->PageName, "Correct value on base table when fetching base class"); $this->assertEquals('old-value', $obj1->ExtraField, "Correct value on sub table when fetching base class"); $obj1 = VersionedLazy_DataObject::get()->byID($obj1ID); $this->assertEquals('old-value', $obj1->PageName, "Correct value on base table when fetching sub class"); $this->assertEquals('old-value', $obj1->ExtraField, "Correct value on sub table when fetching sub class"); // Update live table only to test behaviour (shouldn't select from *_versions or stage) DB::query(sprintf('UPDATE "VersionedLazy_DataObject_Live" SET "PageName" = \'live-value\' WHERE "ID" = %d', $obj1ID)); DB::query(sprintf('UPDATE "VersionedLazySub_DataObject_Live" SET "ExtraField" = \'live-value\' WHERE "ID" = %d', $obj1ID)); Versioned::set_stage(Versioned::LIVE); $obj1 = VersionedLazy_DataObject::get()->byID($obj1ID); $this->assertEquals('live-value', $obj1->PageName, "Correct value from base table when fetching base class on live stage"); $this->assertEquals('live-value', $obj1->ExtraField, "Correct value from sub table when fetching base class on live stage"); }
/** * Values that are overwritten with null are saved to the _versions table correctly. */ public function testWriteNullValueToVersion() { $record = VersionedTest_Subclass::create(); $record->Title = "Test A"; $record->write(); $version = Versioned::get_latest_version($record->ClassName, $record->ID); $this->assertEquals(1, $version->Version); $this->assertEquals($record->Title, $version->Title); $record->Title = null; $record->write(); $version = Versioned::get_latest_version($record->ClassName, $record->ID); $this->assertEquals(2, $version->Version); $this->assertEquals($record->Title, $version->Title); }
/** * Ensure that related objects are disassociated on live */ public function testUnlinkDisassociated() { $this->publishAllFixtures(); /** @var ChangeSetTest_Base $base */ $base = $this->objFromFixture(ChangeSetTest_Base::class, 'base'); /** @var ChangeSetTest_Mid $mid1 $mid2 */ $mid1 = $this->objFromFixture(ChangeSetTest_Mid::class, 'mid1'); $mid2 = $this->objFromFixture(ChangeSetTest_Mid::class, 'mid2'); // Remove mid1 from stage $this->assertEquals($base->ID, $mid1->BaseID); $this->assertEquals($base->ID, $mid2->BaseID); $mid1->deleteFromStage(Versioned::DRAFT); // Publishing recursively should unlinkd this object $changeset = new ChangeSet(); $changeset->write(); $changeset->addObject($base); // Assert changeset only contains root object $this->assertChangeSetLooksLike($changeset, ['ChangeSetTest_Base.base' => ChangeSetItem::EXPLICITLY]); $changeset->publish(); // mid1 on live exists, but has BaseID set to zero $mid1Live = Versioned::get_by_stage(ChangeSetTest_Mid::class, Versioned::LIVE)->byID($mid1->ID); $this->assertNotNull($mid1Live); $this->assertEquals($mid1->ID, $mid1Live->ID); $this->assertEquals(0, $mid1Live->BaseID); // mid2 on live exists and retains BaseID $mid2Live = Versioned::get_by_stage(ChangeSetTest_Mid::class, Versioned::LIVE)->byID($mid2->ID); $this->assertNotNull($mid2Live); $this->assertEquals($mid2->ID, $mid2Live->ID); $this->assertEquals($base->ID, $mid2Live->BaseID); }
/** * Tests for the bug #5994 - Moving folder after executing Folder::findOrMake will not set the Filenames properly */ public function testFindOrMakeFolderThenMove() { $folder1 = $this->objFromFixture('SilverStripe\\Assets\\Folder', 'folder1'); Folder::find_or_make($folder1->Filename); $folder2 = $this->objFromFixture('SilverStripe\\Assets\\Folder', 'folder2'); // Publish file1 /** @var File $file1 */ $file1 = DataObject::get_by_id('SilverStripe\\Assets\\File', $this->idFromFixture('SilverStripe\\Assets\\File', 'file1-folder1'), false); $file1->publishRecursive(); // set ParentID. This should cause updateFilesystem to be called on all children $folder1->ParentID = $folder2->ID; $folder1->write(); // Check if the file in the folder moved along /** @var File $file1Draft */ $file1Draft = Versioned::get_by_stage('SilverStripe\\Assets\\File', Versioned::DRAFT)->byID($file1->ID); $this->assertFileExists(AssetStoreTest_SpyStore::getLocalPath($file1Draft)); $this->assertEquals('FileTest-folder2/FileTest-folder1/File1.txt', $file1Draft->Filename, 'The file DataObject has updated path'); // File should be located in new folder $this->assertEquals(ASSETS_PATH . '/FolderTest/.protected/FileTest-folder2/FileTest-folder1/55b443b601/File1.txt', AssetStoreTest_SpyStore::getLocalPath($file1Draft)); // Published (live) version remains in the old location /** @var File $file1Live */ $file1Live = Versioned::get_by_stage('SilverStripe\\Assets\\File', Versioned::LIVE)->byID($file1->ID); $this->assertEquals(ASSETS_PATH . '/FolderTest/FileTest-folder1/55b443b601/File1.txt', AssetStoreTest_SpyStore::getLocalPath($file1Live)); // Publishing the draft to live should move the new file to the public store $file1Draft->publishRecursive(); $this->assertEquals(ASSETS_PATH . '/FolderTest/FileTest-folder2/FileTest-folder1/55b443b601/File1.txt', AssetStoreTest_SpyStore::getLocalPath($file1Draft)); }
/** * Check if this ChangeSetItem can be published * * @param Member $member * @return bool */ public function canPublish($member = null) { // Check canMethod to invoke on object switch ($this->getChangeType()) { case static::CHANGE_DELETED: /** @var Versioned|DataObject $object */ $object = Versioned::get_by_stage($this->ObjectClass, Versioned::LIVE)->byID($this->ObjectID); if (!$object || !$object->canUnpublish($member)) { return false; } break; default: /** @var Versioned|DataObject $object */ $object = Versioned::get_by_stage($this->ObjectClass, Versioned::DRAFT)->byID($this->ObjectID); if (!$object || !$object->canPublish($member)) { return false; } break; } // If object can be published/unpublished let extensions deny return $this->can(__FUNCTION__, $member); }
/** * Update filesystem of all children */ public function updateChildFilesystem() { // Don't synchronise on live (rely on publishing instead) if (Versioned::get_stage() === Versioned::LIVE) { return; } $this->flushCache(); // Writing this record should trigger a write (and potential updateFilesystem) on each child foreach ($this->AllChildren() as $child) { $child->write(); } }
/** * Check if this ChangeSetItem can be published * * @param Member $member * @return bool */ public function canPublish($member = null) { // Check canMethod to invoke on object switch ($this->getChangeType()) { case static::CHANGE_DELETED: /** @var Versioned|DataObject $object */ $object = Versioned::get_by_stage($this->ObjectClass, Versioned::LIVE)->byID($this->ObjectID); if ($object) { return $object->canUnpublish($member); } break; default: /** @var Versioned|DataObject $object */ $object = Versioned::get_by_stage($this->ObjectClass, Versioned::DRAFT)->byID($this->ObjectID); if ($object) { return $object->canPublish($member); } break; } return false; }
/** * @param int $from * @param int $to * * @return string */ public function humanizedChanges($from, $to) { if (!$from) { return _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdmin.UPLOADEDFILE', "Uploaded file"); } $fromRecord = Versioned::get_version($this->owner->class, $this->owner->ID, $from); $toRecord = Versioned::get_version($this->owner->class, $this->owner->ID, $to); $diff = new DataDifferencer($fromRecord, $toRecord); $changes = $diff->changedFieldNames(); $k = array_search('LastEdited', $changes); if ($k !== false) { unset($changes[$k]); } $output = array(); foreach ($changes as $change) { $human = $change; if ($change == "ParentID") { // updated folder ID $human = _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdminFile.MOVEDFOLDER', "Moved file"); } elseif ($change == 'Title') { $human = _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdminFile.RENAMEDTITLE', "Updated title to ") . $fromRecord->Title; } elseif ($change == 'Name') { $human = _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdminFile.RENAMEDFILE', "Renamed file to ") . $fromRecord->Filename; } elseif ($change == 'File') { // check to make sure the files are actually different if ($fromRecord->getHash() != $toRecord->getHash()) { $human = _t('SilverStripe\\AssetAdmin\\Controller\\AssetAdminFile.RENAMEDFILE', "Replaced file"); } else { $human = false; } } else { $human = false; } if ($human) { $output[] = $human; } } return implode(", ", $output); }
/** * Safely query and return all pages queried * * @param array $ids * @return SS_List */ protected function getPages($ids) { // Check empty set if (empty($ids)) { return new ArrayList(); } $recordClass = $this->recordClass; // Bypass translatable filter if (class_exists('Translatable') && $recordClass::has_extension('Translatable')) { Translatable::disable_locale_filter(); } // Bypass versioned filter if ($recordClass::has_extension(Versioned::class)) { // Workaround for get_including_deleted not supporting byIDs filter very well // Ensure we select both stage / live records $pages = Versioned::get_including_deleted($recordClass, array('"RecordID" IN (' . DB::placeholders($ids) . ')' => $ids)); } else { $pages = DataObject::get($recordClass)->byIDs($ids); } if (class_exists('Translatable') && $recordClass::has_extension('Translatable')) { Translatable::enable_locale_filter(); } return $pages; }
public function testPublishing() { /** @var ManyManyThroughListTest_VersionedObject $draftParent */ $draftParent = $this->objFromFixture(ManyManyThroughListTest_VersionedObject::class, 'parent1'); $draftParent->publishRecursive(); // Modify draft stage $item1 = $draftParent->Items()->filter(['Title' => 'versioned item 1'])->first(); $item1->Title = 'new versioned item 1'; $item1->getJoin()->Title = 'new versioned join 1'; $item1->write(false, false, false, true); // Write joined components $draftParent->Title = 'new versioned title'; $draftParent->write(); // Check owned objects on stage $draftOwnedObjects = $draftParent->findOwned(true); $this->assertDOSEquals([['Title' => 'new versioned join 1'], ['Title' => 'versioned join 2'], ['Title' => 'new versioned item 1'], ['Title' => 'versioned item 2']], $draftOwnedObjects); // Check live record is still old values // This tests that both the join table and many_many tables // inherit the necessary query parameters from the parent object. /** @var ManyManyThroughListTest_VersionedObject $liveParent */ $liveParent = Versioned::get_by_stage(ManyManyThroughListTest_VersionedObject::class, Versioned::LIVE)->byID($draftParent->ID); $liveOwnedObjects = $liveParent->findOwned(true); $this->assertDOSEquals([['Title' => 'versioned join 1'], ['Title' => 'versioned join 2'], ['Title' => 'versioned item 1'], ['Title' => 'versioned item 2']], $liveOwnedObjects); // Publish draft changes $draftParent->publishRecursive(); $liveParent = Versioned::get_by_stage(ManyManyThroughListTest_VersionedObject::class, Versioned::LIVE)->byID($draftParent->ID); $liveOwnedObjects = $liveParent->findOwned(true); $this->assertDOSEquals([['Title' => 'new versioned join 1'], ['Title' => 'versioned join 2'], ['Title' => 'new versioned item 1'], ['Title' => 'versioned item 2']], $liveOwnedObjects); }
/** * Test a URL request, returning a response object. This method is the counterpart of * Director::direct() that is used in functional testing. It will execute the URL given, and * return the result as an SS_HTTPResponse object. * * @uses getControllerForURL() The rule-lookup logic is handled by this. * @uses Controller::run() Handles the page logic for a Director::direct() call. * * @param string $url The URL to visit. * @param array $postVars The $_POST & $_FILES variables. * @param array|Session $session The {@link Session} object representing the current session. * By passing the same object to multiple calls of Director::test(), you can simulate a persisted * session. * @param string $httpMethod The HTTP method, such as GET or POST. It will default to POST if * postVars is set, GET otherwise. Overwritten by $postVars['_method'] if present. * @param string $body The HTTP body. * @param array $headers HTTP headers with key-value pairs. * @param array|Cookie_Backend $cookies to populate $_COOKIE. * @param HTTP_Request $request The {@see HTTP_Request} object generated as a part of this request. * * @return SS_HTTPResponse * * @throws SS_HTTPResponse_Exception */ public static function test($url, $postVars = null, $session = array(), $httpMethod = null, $body = null, $headers = array(), $cookies = array(), &$request = null) { Config::nest(); Injector::nest(); // These are needed so that calling Director::test() does not muck with whoever is calling it. // Really, it's some inappropriate coupling and should be resolved by making less use of statics. $oldReadingMode = Versioned::get_reading_mode(); $getVars = array(); if (!$httpMethod) { $httpMethod = $postVars || is_array($postVars) ? "POST" : "GET"; } if (!$session) { $session = Injector::inst()->create('Session', array()); } $cookieJar = $cookies instanceof Cookie_Backend ? $cookies : Injector::inst()->createWithArgs('Cookie_Backend', array($cookies ?: array())); // Back up the current values of the superglobals $existingRequestVars = isset($_REQUEST) ? $_REQUEST : array(); $existingGetVars = isset($_GET) ? $_GET : array(); $existingPostVars = isset($_POST) ? $_POST : array(); $existingSessionVars = isset($_SESSION) ? $_SESSION : array(); $existingCookies = isset($_COOKIE) ? $_COOKIE : array(); $existingServer = isset($_SERVER) ? $_SERVER : array(); $existingRequirementsBackend = Requirements::backend(); Config::inst()->update('Cookie', 'report_errors', false); Requirements::set_backend(Injector::inst()->create('Requirements_Backend')); // Set callback to invoke prior to return $onCleanup = function () use($existingRequestVars, $existingGetVars, $existingPostVars, $existingSessionVars, $existingCookies, $existingServer, $existingRequirementsBackend, $oldReadingMode) { // Restore the super globals $_REQUEST = $existingRequestVars; $_GET = $existingGetVars; $_POST = $existingPostVars; $_SESSION = $existingSessionVars; $_COOKIE = $existingCookies; $_SERVER = $existingServer; Requirements::set_backend($existingRequirementsBackend); // These are needed so that calling Director::test() does not muck with whoever is calling it. // Really, it's some inappropriate coupling and should be resolved by making less use of statics Versioned::set_reading_mode($oldReadingMode); Injector::unnest(); // Restore old CookieJar, etc Config::unnest(); }; if (strpos($url, '#') !== false) { $url = substr($url, 0, strpos($url, '#')); } // Handle absolute URLs if (parse_url($url, PHP_URL_HOST)) { $bits = parse_url($url); // If a port is mentioned in the absolute URL, be sure to add that into the HTTP host if (isset($bits['port'])) { $_SERVER['HTTP_HOST'] = $bits['host'] . ':' . $bits['port']; } else { $_SERVER['HTTP_HOST'] = $bits['host']; } } // Ensure URL is properly made relative. // Example: url passed is "/ss31/my-page" (prefixed with BASE_URL), this should be changed to "my-page" $url = self::makeRelative($url); $urlWithQuerystring = $url; if (strpos($url, '?') !== false) { list($url, $getVarsEncoded) = explode('?', $url, 2); parse_str($getVarsEncoded, $getVars); } // Replace the super globals with appropriate test values $_REQUEST = ArrayLib::array_merge_recursive((array) $getVars, (array) $postVars); $_GET = (array) $getVars; $_POST = (array) $postVars; $_SESSION = $session ? $session->inst_getAll() : array(); $_COOKIE = $cookieJar->getAll(false); Injector::inst()->registerService($cookieJar, 'Cookie_Backend'); $_SERVER['REQUEST_URI'] = Director::baseURL() . $urlWithQuerystring; $request = new SS_HTTPRequest($httpMethod, $url, $getVars, $postVars, $body); if ($headers) { foreach ($headers as $k => $v) { $request->addHeader($k, $v); } } // Pre-request filtering // @see issue #2517 $model = DataModel::inst(); $output = Injector::inst()->get('RequestProcessor')->preRequest($request, $session, $model); if ($output === false) { $onCleanup(); throw new SS_HTTPResponse_Exception(_t('Director.INVALID_REQUEST', 'Invalid request'), 400); } // TODO: Pass in the DataModel $result = Director::handleRequest($request, $session, $model); // Ensure that the result is an SS_HTTPResponse object if (is_string($result)) { if (substr($result, 0, 9) == 'redirect:') { $response = new SS_HTTPResponse(); $response->redirect(substr($result, 9)); $result = $response; } else { $result = new SS_HTTPResponse($result); } } $output = Injector::inst()->get('RequestProcessor')->postRequest($request, $result, $model); if ($output === false) { $onCleanup(); throw new SS_HTTPResponse_Exception("Invalid response"); } // Return valid response $onCleanup(); return $result; }
/** * Test that rolling back to a single version works recursively */ public function testRecursiveRollback() { /** @var VersionedOwnershipTest_Subclass $subclass2 */ $this->sleep(1); $subclass2 = $this->objFromFixture('VersionedOwnershipTest_Subclass', 'subclass2_published'); // Create a few new versions $versions = []; for ($version = 1; $version <= 3; $version++) { // Write owned objects $this->sleep(1); foreach ($subclass2->findOwned(true) as $obj) { $obj->Title .= " - v{$version}"; $obj->write(); } // Write parent $this->sleep(1); $subclass2->Title .= " - v{$version}"; $subclass2->write(); $versions[$version] = $subclass2->Version; } // Check reverting to first version $this->sleep(1); $subclass2->doRollbackTo($versions[1]); /** @var VersionedOwnershipTest_Subclass $subclass2Draft */ $subclass2Draft = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::DRAFT)->byID($subclass2->ID); $this->assertEquals('Subclass 2 - v1', $subclass2Draft->Title); $this->assertDOSEquals([['Title' => 'Related 2 - v1'], ['Title' => 'Attachment 3 - v1'], ['Title' => 'Attachment 4 - v1'], ['Title' => 'Attachment 5 - v1'], ['Title' => 'Related Many 4 - v1']], $subclass2Draft->findOwned(true)); // Check rolling forward to a later version $this->sleep(1); $subclass2->doRollbackTo($versions[3]); /** @var VersionedOwnershipTest_Subclass $subclass2Draft */ $subclass2Draft = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::DRAFT)->byID($subclass2->ID); $this->assertEquals('Subclass 2 - v1 - v2 - v3', $subclass2Draft->Title); $this->assertDOSEquals([['Title' => 'Related 2 - v1 - v2 - v3'], ['Title' => 'Attachment 3 - v1 - v2 - v3'], ['Title' => 'Attachment 4 - v1 - v2 - v3'], ['Title' => 'Attachment 5 - v1 - v2 - v3'], ['Title' => 'Related Many 4 - v1 - v2 - v3']], $subclass2Draft->findOwned(true)); // And rolling back one version $this->sleep(1); $subclass2->doRollbackTo($versions[2]); /** @var VersionedOwnershipTest_Subclass $subclass2Draft */ $subclass2Draft = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::DRAFT)->byID($subclass2->ID); $this->assertEquals('Subclass 2 - v1 - v2', $subclass2Draft->Title); $this->assertDOSEquals([['Title' => 'Related 2 - v1 - v2'], ['Title' => 'Attachment 3 - v1 - v2'], ['Title' => 'Attachment 4 - v1 - v2'], ['Title' => 'Attachment 5 - v1 - v2'], ['Title' => 'Related Many 4 - v1 - v2']], $subclass2Draft->findOwned(true)); }
public function testPublish() { $this->publishAllFixtures(); $base = $this->objFromFixture('ChangeSetTest_Base', 'base'); $baseID = $base->ID; $baseBefore = $base->Version; $end1 = $this->objFromFixture('ChangeSetTest_End', 'end1'); $end1ID = $end1->ID; $end1Before = $end1->Version; // Create a new changest $changeset = new ChangeSet(); $changeset->write(); $changeset->addObject($base); $changeset->addObject($end1); // Make a lot of changes // - ChangeSetTest_Base.base modified // - ChangeSetTest_End.end1 deleted // - new ChangeSetTest_Mid added $base->Foo = 343; $base->write(); $baseAfter = $base->Version; $midNew = new ChangeSetTest_Mid(); $midNew->Bar = 39; $midNew->write(); $midNewID = $midNew->ID; $midNewAfter = $midNew->Version; $end1->delete(); $changeset->addObject($midNew); // Publish $this->logInWithPermission('ADMIN'); $this->assertTrue($changeset->canPublish()); $this->assertTrue($changeset->isSynced()); $changeset->publish(); $this->assertEquals(ChangeSet::STATE_PUBLISHED, $changeset->State); // Check each item has the correct before/after version applied $baseChange = $changeset->Changes()->filter(['ObjectClass' => 'ChangeSetTest_Base', 'ObjectID' => $baseID])->first(); $this->assertEquals((int) $baseBefore, (int) $baseChange->VersionBefore); $this->assertEquals((int) $baseAfter, (int) $baseChange->VersionAfter); $this->assertEquals((int) $baseChange->VersionBefore + 1, (int) $baseChange->VersionAfter); $this->assertEquals((int) $baseChange->VersionAfter, (int) Versioned::get_versionnumber_by_stage('ChangeSetTest_Base', Versioned::LIVE, $baseID)); $end1Change = $changeset->Changes()->filter(['ObjectClass' => 'ChangeSetTest_End', 'ObjectID' => $end1ID])->first(); $this->assertEquals((int) $end1Before, (int) $end1Change->VersionBefore); $this->assertEquals(0, (int) $end1Change->VersionAfter); $this->assertEquals(0, (int) Versioned::get_versionnumber_by_stage('ChangeSetTest_End', Versioned::LIVE, $end1ID)); $midNewChange = $changeset->Changes()->filter(['ObjectClass' => 'ChangeSetTest_Mid', 'ObjectID' => $midNewID])->first(); $this->assertEquals(0, (int) $midNewChange->VersionBefore); $this->assertEquals((int) $midNewAfter, (int) $midNewChange->VersionAfter); $this->assertEquals((int) $midNewAfter, (int) Versioned::get_versionnumber_by_stage('ChangeSetTest_Mid', Versioned::LIVE, $midNewID)); // Test trying to re-publish is blocked $this->setExpectedException('BadMethodCallException', "ChangeSet can't be published if it has been already published or reverted."); $changeset->publish(); }
/** * Checks all stages other than the current stage, and check the visibility * of assets attached to those records. * * @param AssetManipulationList $manipulation Set of manipulations to add assets to */ protected function addAssetsFromOtherStages(AssetManipulationList $manipulation) { // Skip unversioned or unsaved assets if (!$this->isVersioned() || !$this->owner->isInDB()) { return; } // Unauthenticated member to use for checking visibility $baseClass = $this->owner->baseClass(); $baseTable = $this->owner->baseTable(); $filter = array("\"{$baseTable}\".\"ID\"" => $this->owner->ID); $stages = $this->owner->getVersionedStages(); // {@see Versioned::getVersionedStages} foreach ($stages as $stage) { // Skip current stage; These should be handled explicitly if ($stage === Versioned::get_stage()) { continue; } // Check if record exists in this stage $record = Versioned::get_one_by_stage($baseClass, $stage, $filter); if (!$record) { continue; } // Check visibility of this record, and record all attached assets $state = $this->getRecordState($record); $this->addAssetsFromRecord($manipulation, $record, $state); } }
/** * This will check if the parent record and/or name do not match the name on the underlying * DBFile record, and if so, copy this file to the new location, and update the record to * point to this new file. * * This method will update the File {@see DBFile} field value on success, so it must be called * before writing to the database * * @return bool True if changed */ public function updateFilesystem() { if (!$this->config()->update_filesystem) { return false; } // Check the file exists if (!$this->File->exists()) { return false; } // Avoid moving files on live; Rely on this being done on stage prior to publish. if (Versioned::get_stage() !== Versioned::DRAFT) { return false; } // Check path updated record will point to // If no changes necessary, skip $pathBefore = $this->File->getFilename(); $pathAfter = $this->generateFilename(); if ($pathAfter === $pathBefore) { return false; } // Copy record to new location via stream $stream = $this->File->getStream(); $this->File->setFromStream($stream, $pathAfter); return true; }
/** * Helper method for applicablePages() methods. Acts as a skeleton implementation. * * @param array $ids The IDs passed to applicablePages * @param string $methodName The canXXX() method to call on each page to check if the action is applicable * @param bool $checkStagePages Set to true if you want to check stage pages * @param bool $checkLivePages Set to true if you want to check live pages (e.g, for deleted-from-draft) * @return array */ public function applicablePagesHelper($ids, $methodName, $checkStagePages = true, $checkLivePages = true) { if (!is_array($ids)) { user_error("Bad \$ids passed to applicablePagesHelper()", E_USER_WARNING); } if (!is_string($methodName)) { user_error("Bad \$methodName passed to applicablePagesHelper()", E_USER_WARNING); } $applicableIDs = array(); $managedClass = $this->managedClass; $draftPages = DataObject::get($managedClass)->byIDs($ids); // Filter out the live-only ids $onlyOnLive = array_fill_keys($ids, true); if ($checkStagePages) { foreach ($draftPages as $obj) { unset($onlyOnLive[$obj->ID]); if ($obj->{$methodName}()) { $applicableIDs[] = $obj->ID; } } } $onlyOnLive = array_keys($onlyOnLive); if ($checkLivePages && $onlyOnLive && Object::has_extension($managedClass, 'SilverStripe\\ORM\\Versioning\\Versioned')) { // Get the pages that only exist on live (deleted from stage) $livePages = Versioned::get_by_stage($managedClass, "Live")->byIDs($onlyOnLive); foreach ($livePages as $obj) { if ($obj->{$methodName}()) { $applicableIDs[] = $obj->ID; } } } return $applicableIDs; }
public function setUp() { parent::setUp(); Versioned::set_stage(Versioned::DRAFT); AssetStoreTest_SpyStore::activate('UploadTest'); }