/** * Virtual "sleep" that doesn't actually slow execution, only advances DBDateTime::now() * * @param int $minutes */ protected function sleep($minutes) { $now = DBDatetime::now(); $date = DateTime::createFromFormat('Y-m-d H:i:s', $now->getValue()); $date->modify("+{$minutes} minutes"); DBDatetime::set_mock_now($date->format('Y-m-d H:i:s')); }
/** * Set a cookie * * @param string $name The name of the cookie * @param string $value The value for the cookie to hold * @param int $expiry The number of days until expiry; 0 indicates a cookie valid for the current session * @param string $path The path to save the cookie on (falls back to site base) * @param string $domain The domain to make the cookie available on * @param boolean $secure Can the cookie only be sent over SSL? * @param boolean $httpOnly Prevent the cookie being accessible by JS */ public function set($name, $value, $expiry = 90, $path = null, $domain = null, $secure = false, $httpOnly = true) { //are we setting or clearing a cookie? false values are reserved for clearing cookies (see PHP manual) $clear = false; if ($value === false || $value === '' || $expiry < 0) { $clear = true; $value = false; } //expiry === 0 is a special case where we set a cookie for the current user session if ($expiry !== 0) { //don't do the maths if we are clearing $expiry = $clear ? -1 : DBDatetime::now()->Format('U') + 86400 * $expiry; } //set the path up $path = $path ? $path : Director::baseURL(); //send the cookie $this->outputCookie($name, $value, $expiry, $path, $domain, $secure, $httpOnly); //keep our variables in check if ($clear) { unset($this->new[$name], $this->current[$name]); } else { $this->new[$name] = $this->current[$name] = $value; } }
/** * Writes all changes to this object to the database. * - It will insert a record whenever ID isn't set, otherwise update. * - All relevant tables will be updated. * - $this->onBeforeWrite() gets called beforehand. * - Extensions such as Versioned will ammend the database-write to ensure that a version is saved. * * @uses DataExtension->augmentWrite() * * @param boolean $showDebug Show debugging information * @param boolean $forceInsert Run INSERT command rather than UPDATE, even if record already exists * @param boolean $forceWrite Write to database even if there are no changes * @param boolean $writeComponents Call write() on all associated component instances which were previously * retrieved through {@link getComponent()}, {@link getComponents()} or * {@link getManyManyComponents()} (Default: false) * @return int The ID of the record * @throws ValidationException Exception that can be caught and handled by the calling function */ public function write($showDebug = false, $forceInsert = false, $forceWrite = false, $writeComponents = false) { $now = DBDatetime::now()->Rfc2822(); // Execute pre-write tasks $this->preWrite(); // Check if we are doing an update or an insert $isNewRecord = !$this->isInDB() || $forceInsert; // Check changes exist, abort if there are none $hasChanges = $this->updateChanges($isNewRecord); if ($hasChanges || $forceWrite || $isNewRecord) { // New records have their insert into the base data table done first, so that they can pass the // generated primary key on to the rest of the manipulation $baseTable = $this->baseTable(); $this->writeBaseRecord($baseTable, $now); // Write the DB manipulation for all changed fields $this->writeManipulation($baseTable, $now, $isNewRecord); // If there's any relations that couldn't be saved before, save them now (we have an ID here) $this->writeRelations(); $this->onAfterWrite(); $this->changed = array(); } else { if ($showDebug) { Debug::message("no changes for DataObject"); } // Used by DODs to clean up after themselves, eg, Versioned $this->invokeWithExtensions('onAfterSkippedWrite'); } // Ensure Created and LastEdited are populated if (!isset($this->record['Created'])) { $this->record['Created'] = $now; } $this->record['LastEdited'] = $now; // Write relations as necessary if ($writeComponents) { $this->writeComponents(true); } // Clears the cache for this object so get_one returns the correct object. $this->flushCache(); return $this->record['ID']; }
public function testAgoInFuture() { DBDatetime::set_mock_now('2000-12-31 00:00:00'); $this->assertEquals('in 10 years', DBField::create_field('Date', '2010-12-31')->Ago(), 'Exact past match on years'); $this->assertEquals('in 1 day', DBField::create_field('Date', '2001-01-01')->Ago(true, 1), 'Approximate past match on minutes'); $this->assertEquals('in 24 hours', DBField::create_field('Date', '2001-01-01')->Ago(), 'Approximate past match on minutes'); DBDatetime::clear_mock_now(); }
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 that objects are correctly published recursively */ public function testRecursivePublish() { /** @var VersionedOwnershipTest_Subclass $parent */ $parent = $this->objFromFixture('VersionedOwnershipTest_Subclass', 'subclass1_published'); $parentID = $parent->ID; $banner1 = $this->objFromFixture('VersionedOwnershipTest_RelatedMany', 'relatedmany1_published'); $banner2 = $this->objFromFixture('VersionedOwnershipTest_RelatedMany', 'relatedmany2_published'); $banner2ID = $banner2->ID; // Modify, Add, and Delete banners on stage $banner1->Title = 'Renamed Banner 1'; $banner1->write(); $banner2->delete(); $banner4 = new VersionedOwnershipTest_RelatedMany(); $banner4->Title = 'New Banner'; $parent->Banners()->add($banner4); // Check state of objects before publish $oldLiveBanners = [['Title' => 'Related Many 1'], ['Title' => 'Related Many 2']]; $newBanners = [['Title' => 'Renamed Banner 1'], ['Title' => 'Related Many 3'], ['Title' => 'New Banner']]; $parentDraft = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::DRAFT)->byID($parentID); $this->assertDOSEquals($newBanners, $parentDraft->Banners()); $parentLive = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::LIVE)->byID($parentID); $this->assertDOSEquals($oldLiveBanners, $parentLive->Banners()); // On publishing of owner, all children should now be updated $now = DBDatetime::now(); DBDatetime::set_mock_now($now); // Lock 'now' to predictable time $parent->publishRecursive(); // Now check each object has the correct state $parentDraft = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::DRAFT)->byID($parentID); $this->assertDOSEquals($newBanners, $parentDraft->Banners()); $parentLive = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::LIVE)->byID($parentID); $this->assertDOSEquals($newBanners, $parentLive->Banners()); // Check that the deleted banner hasn't actually been deleted from the live stage, // but in fact has been unlinked. $banner2Live = Versioned::get_by_stage('VersionedOwnershipTest_RelatedMany', Versioned::LIVE)->byID($banner2ID); $this->assertEmpty($banner2Live->PageID); // Test that a changeset was created /** @var ChangeSet $changeset */ $changeset = ChangeSet::get()->sort('"ChangeSet"."ID" DESC')->first(); $this->assertNotEmpty($changeset); // Test that this changeset is inferred $this->assertTrue((bool) $changeset->IsInferred); $this->assertEquals("Generated by publish of 'Subclass 1' at " . $now->Nice(), $changeset->getTitle()); // Test that this changeset contains all items $this->assertDOSContains([['ObjectID' => $parent->ID, 'ObjectClass' => $parent->baseClass(), 'Added' => ChangeSetItem::EXPLICITLY], ['ObjectID' => $banner1->ID, 'ObjectClass' => $banner1->baseClass(), 'Added' => ChangeSetItem::IMPLICITLY], ['ObjectID' => $banner4->ID, 'ObjectClass' => $banner4->baseClass(), 'Added' => ChangeSetItem::IMPLICITLY]], $changeset->Changes()); // Objects that are unlinked should not need to be a part of the changeset $this->assertNotDOSContains([['ObjectID' => $banner2ID, 'ObjectClass' => $banner2->baseClass()]], $changeset->Changes()); }
/** * Returns true if date is today. * @return boolean */ public function IsToday() { return date('Y-m-d', strtotime($this->value)) == DBDatetime::now()->Format('Y-m-d'); }
/** * Generates a new login hash associated with a device * The device is assigned a globally unique device ID * The returned login hash stores the hashed token in the * database, for this device and this member * @param Member $member The logged in user * @return RememberLoginHash The generated login hash */ public static function generate(Member $member) { if (!$member->exists()) { return null; } if (static::config()->force_single_token) { RememberLoginHash::get()->filter('MemberID', $member->ID)->removeAll(); } /** @var RememberLoginHash $rememberLoginHash */ $rememberLoginHash = RememberLoginHash::create(); do { $deviceID = $rememberLoginHash->getNewDeviceID(); } while (RememberLoginHash::get()->filter('DeviceID', $deviceID)->count()); $rememberLoginHash->DeviceID = $deviceID; $rememberLoginHash->Hash = $rememberLoginHash->getNewHash($member); $rememberLoginHash->MemberID = $member->ID; $now = DBDatetime::now(); $expiryDate = new DateTime($now->Rfc2822()); $tokenExpiryDays = static::config()->token_expiry_days; $expiryDate->add(new DateInterval('P' . $tokenExpiryDays . 'D')); $rememberLoginHash->ExpiryDate = $expiryDate->format('Y-m-d H:i:s'); $rememberLoginHash->extend('onAfterGenerateToken'); $rememberLoginHash->write(); return $rememberLoginHash; }
/** * Export core. * * @param GridField $gridField * @return ArrayData */ public function generatePrintData(GridField $gridField) { $printColumns = $this->getPrintColumnsForGridField($gridField); $header = null; if ($this->printHasHeader) { $header = new ArrayList(); foreach ($printColumns as $field => $label) { $header->push(new ArrayData(array("CellString" => $label))); } } $items = $gridField->getManipulatedList(); $itemRows = new ArrayList(); /** @var DataObject $item */ foreach ($items->limit(null) as $item) { $itemRow = new ArrayList(); foreach ($printColumns as $field => $label) { $value = $gridField->getDataFieldValue($item, $field); if ($item->escapeTypeForField($field) != 'xml') { $value = Convert::raw2xml($value); } $itemRow->push(new ArrayData(array("CellString" => $value))); } $itemRows->push(new ArrayData(array("ItemRow" => $itemRow))); if ($item->hasMethod('destroy')) { $item->destroy(); } } $ret = new ArrayData(array("Title" => $this->getTitle($gridField), "Header" => $header, "ItemRows" => $itemRows, "Datetime" => DBDatetime::now(), "Member" => Member::currentUser())); return $ret; }
/** * Tell this member that someone made a failed attempt at logging in as them. * This can be used to lock the user out temporarily if too many failed attempts are made. */ public function registerFailedLogin() { if (self::config()->lock_out_after_incorrect_logins) { // Keep a tally of the number of failed log-ins so that we can lock people out $this->FailedLoginCount = $this->FailedLoginCount + 1; if ($this->FailedLoginCount >= self::config()->lock_out_after_incorrect_logins) { $lockoutMins = self::config()->lock_out_delay_mins; $this->LockedOutUntil = date('Y-m-d H:i:s', DBDatetime::now()->Format('U') + $lockoutMins * 60); $this->FailedLoginCount = 0; } } $this->extend('registerFailedLogin'); $this->write(); }
public function testChangePasswordFromLostPassword() { $admin = $this->objFromFixture('SilverStripe\\Security\\Member', 'test'); $admin->FailedLoginCount = 99; $admin->LockedOutUntil = DBDatetime::now()->Format('Y-m-d H:i:s'); $admin->write(); $this->assertNull($admin->AutoLoginHash, 'Hash is empty before lost password'); // Request new password by email $response = $this->get('Security/lostpassword'); $response = $this->post('Security/LostPasswordForm', array('Email' => '*****@*****.**')); $this->assertEmailSent('*****@*****.**'); // Load password link from email $admin = DataObject::get_by_id('SilverStripe\\Security\\Member', $admin->ID); $this->assertNotNull($admin->AutoLoginHash, 'Hash has been written after lost password'); // We don't have access to the token - generate a new token and hash pair. $token = $admin->generateAutologinTokenAndStoreHash(); // Check. $response = $this->get('Security/changepassword/?m=' . $admin->ID . '&t=' . $token); $this->assertEquals(302, $response->getStatusCode()); $this->assertEquals(Director::baseUrl() . 'Security/changepassword', $response->getHeader('Location')); // Follow redirection to form without hash in GET parameter $response = $this->get('Security/changepassword'); $changedResponse = $this->doTestChangepasswordForm('1nitialPassword', 'changedPassword'); $this->assertEquals($this->idFromFixture('SilverStripe\\Security\\Member', 'test'), $this->session()->inst_get('loggedInAs')); // Check if we can login with the new password $goodResponse = $this->doTestLoginForm('*****@*****.**', 'changedPassword'); $this->assertEquals(302, $goodResponse->getStatusCode()); $this->assertEquals($this->idFromFixture('SilverStripe\\Security\\Member', 'test'), $this->session()->inst_get('loggedInAs')); $admin = DataObject::get_by_id('SilverStripe\\Security\\Member', $admin->ID, false); $this->assertNull($admin->LockedOutUntil); $this->assertEquals(0, $admin->FailedLoginCount); }
public function testArchiveRelatedDataWithoutVersioned() { DBDatetime::set_mock_now('2009-01-01 00:00:00'); $relatedData = new VersionedTest_RelatedWithoutVersion(); $relatedData->Name = 'Related Data'; $relatedDataId = $relatedData->write(); $testData = new VersionedTest_DataObject(); $testData->Title = 'Test'; $testData->Content = 'Before Content'; $testData->Related()->add($relatedData); $id = $testData->write(); DBDatetime::set_mock_now('2010-01-01 00:00:00'); $testData->Content = 'After Content'; $testData->write(); Versioned::reading_archived_date('2009-01-01 19:00:00'); $fetchedData = VersionedTest_DataObject::get()->byId($id); $this->assertEquals('Before Content', $fetchedData->Content, 'We see the correct content of the older version'); $relatedData = VersionedTest_RelatedWithoutVersion::get()->byId($relatedDataId); $this->assertEquals(1, $relatedData->Related()->count(), 'We have a relation, with no version table, querying it still works'); }
public function testDefaultAdminLockOut() { Config::inst()->update('SilverStripe\\Security\\Member', 'lock_out_after_incorrect_logins', 1); Config::inst()->update('SilverStripe\\Security\\Member', 'lock_out_delay_mins', 10); DBDatetime::set_mock_now('2016-04-18 00:00:00'); $controller = new Security(); $form = new Form($controller, 'Form', new FieldList(), new FieldList()); // Test correct login MemberAuthenticator::authenticate(array('Email' => 'admin', 'Password' => 'wrongpassword'), $form); $this->assertTrue(Member::default_admin()->isLockedOut()); $this->assertEquals(Member::default_admin()->LockedOutUntil, '2016-04-18 00:10:00'); }
public function testExpiredRememberMeHashAutologin() { $m1 = $this->objFromFixture('SilverStripe\\Security\\Member', 'noexpiry'); $m1->login(true); $firstHash = RememberLoginHash::get()->filter('MemberID', $m1->ID)->First(); $this->assertNotNull($firstHash); // re-generates the hash so we can get the token $firstHash->Hash = $firstHash->getNewHash($m1); $token = $firstHash->getToken(); $firstHash->ExpiryDate = '2000-01-01 00:00:00'; $firstHash->write(); DBDateTime::set_mock_now('1999-12-31 23:59:59'); $response = $this->get('Security/login', $this->session(), null, array('alc_enc' => $m1->ID . ':' . $token, 'alc_device' => $firstHash->DeviceID)); $message = _t('Member.LOGGEDINAS', "You're logged in as {name}.", array('name' => $m1->FirstName)); $this->assertContains($message, $response->getBody()); $this->session()->inst_set('loggedInAs', null); // re-generates the hash so we can get the token $firstHash->Hash = $firstHash->getNewHash($m1); $token = $firstHash->getToken(); $firstHash->ExpiryDate = '2000-01-01 00:00:00'; $firstHash->write(); DBDateTime::set_mock_now('2000-01-01 00:00:01'); $response = $this->get('Security/login', $this->session(), null, array('alc_enc' => $m1->ID . ':' . $token, 'alc_device' => $firstHash->DeviceID)); $this->assertNotContains($message, $response->getBody()); $this->session()->inst_set('loggedInAs', null); DBDatetime::clear_mock_now(); }
public function testAgoInFuture() { DBDatetime::set_mock_now('2000-12-31 00:00:00'); $this->assertEquals('in 10 years', DBField::create_field('Datetime', '2010-12-31 12:00:00')->Ago(), 'Exact past match on years'); $this->assertEquals('in 1 hour', DBField::create_field('Datetime', '2000-12-31 1:01:05')->Ago(true, 1), 'Approximate past match on minutes, significance=1'); $this->assertEquals('in 61 mins', DBField::create_field('Datetime', '2000-12-31 1:01:05')->Ago(), 'Approximate past match on minutes'); DBDatetime::clear_mock_now(); }
/** * Publish this object and all owned objects to Live * * @return bool */ public function publishRecursive() { // Create a new changeset for this item and publish it $changeset = ChangeSet::create(); $changeset->IsInferred = true; $changeset->Name = _t('Versioned.INFERRED_TITLE', "Generated by publish of '{title}' at {created}", ['title' => $this->owner->Title, 'created' => DBDatetime::now()->Nice()]); $changeset->write(); $changeset->addObject($this->owner); return $changeset->publish(); }