/**
  * 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());
 }
Exemplo n.º 7
0
 /**
  * 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;
 }
Exemplo n.º 10
0
 /**
  * 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');
 }
Exemplo n.º 14
0
 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();
 }