public function testSendHTML()
 {
     // Set custom $project - used in email headers
     global $project;
     $oldProject = $project;
     $project = 'emailtest';
     Injector::inst()->registerService(new EmailTest_Mailer(), 'SilverStripe\\Control\\Email\\Mailer');
     $email = new Email('*****@*****.**', '*****@*****.**', 'Test send plain', 'Testing Email->send()', null, '*****@*****.**', '*****@*****.**');
     $email->attachFile(__DIR__ . '/fixtures/attachment.txt', null, 'text/plain');
     $email->addCustomHeader('foo', 'bar');
     $sent = $email->send(123);
     // Restore old project name after sending
     $project = $oldProject;
     $this->assertEquals('*****@*****.**', $sent['to']);
     $this->assertEquals('*****@*****.**', $sent['from']);
     $this->assertEquals('Test send plain', $sent['subject']);
     $this->assertContains('Testing Email->send()', $sent['content']);
     $this->assertNull($sent['plaincontent']);
     $this->assertEquals(array(0 => array('contents' => 'Hello, I\'m a text document.', 'filename' => 'attachment.txt', 'mimetype' => 'text/plain')), $sent['files']);
     $this->assertEquals(array('foo' => 'bar', 'X-SilverStripeMessageID' => 'emailtest.123', 'X-SilverStripeSite' => 'emailtest', 'Cc' => '*****@*****.**', 'Bcc' => '*****@*****.**'), $sent['customheaders']);
 }
        $databaseConfig['timezone'] = SS_DATABASE_TIMEZONE;
    }
    // For schema enabled drivers:
    if (defined('SS_DATABASE_SCHEMA')) {
        $databaseConfig["schema"] = SS_DATABASE_SCHEMA;
    }
    // For SQlite3 memory databases (mainly for testing purposes)
    if (defined('SS_DATABASE_MEMORY')) {
        $databaseConfig["memory"] = SS_DATABASE_MEMORY;
    }
}
if (defined('SS_SEND_ALL_EMAILS_TO')) {
    Email::config()->send_all_emails_to = SS_SEND_ALL_EMAILS_TO;
}
if (defined('SS_SEND_ALL_EMAILS_FROM')) {
    Email::config()->send_all_emails_from = SS_SEND_ALL_EMAILS_FROM;
}
if (defined('SS_DEFAULT_ADMIN_USERNAME')) {
    if (!defined('SS_DEFAULT_ADMIN_PASSWORD')) {
        user_error("SS_DEFAULT_ADMIN_PASSWORD must be defined in your _ss_environment.php," . "if SS_DEFAULT_ADMIN_USERNAME is defined.  See " . "http://doc.silverstripe.org/framework/en/topics/environment-management for more information", E_USER_ERROR);
    } else {
        Security::setDefaultAdmin(SS_DEFAULT_ADMIN_USERNAME, SS_DEFAULT_ADMIN_PASSWORD);
    }
}
if (defined('SS_USE_BASIC_AUTH') && SS_USE_BASIC_AUTH) {
    BasicAuth::config()->entire_site_protected = SS_USE_BASIC_AUTH;
}
if (defined('SS_ERROR_LOG')) {
    $logger = Injector::inst()->get('Logger');
    if ($logger instanceof Logger) {
        $logger->pushHandler(new StreamHandler(BASE_PATH . '/' . SS_ERROR_LOG, Logger::WARNING));
 public function setUp()
 {
     //nest config and injector for each test so they are effectively sandboxed per test
     Config::nest();
     Injector::nest();
     $this->originalReadingMode = Versioned::get_reading_mode();
     // We cannot run the tests on this abstract class.
     if (get_class($this) == __CLASS__) {
         $this->markTestSkipped(sprintf('Skipping %s ', get_class($this)));
         return;
     }
     // Mark test as being run
     $this->originalIsRunningTest = self::$is_running_test;
     self::$is_running_test = true;
     // i18n needs to be set to the defaults or tests fail
     i18n::set_locale(i18n::config()->get('default_locale'));
     i18n::config()->date_format = null;
     i18n::config()->time_format = null;
     // Set default timezone consistently to avoid NZ-specific dependencies
     date_default_timezone_set('UTC');
     // Remove password validation
     $this->originalMemberPasswordValidator = Member::password_validator();
     $this->originalRequirements = Requirements::backend();
     Member::set_password_validator(null);
     Cookie::config()->update('report_errors', false);
     if (class_exists('SilverStripe\\CMS\\Controllers\\RootURLController')) {
         RootURLController::reset();
     }
     if (class_exists('Translatable')) {
         Translatable::reset();
     }
     Versioned::reset();
     DataObject::reset();
     if (class_exists('SilverStripe\\CMS\\Model\\SiteTree')) {
         SiteTree::reset();
     }
     Hierarchy::reset();
     if (Controller::has_curr()) {
         Controller::curr()->setSession(Session::create(array()));
     }
     Security::$database_is_ready = null;
     // Add controller-name auto-routing
     // @todo Fix to work with namespaced controllers
     Director::config()->update('rules', array('$Controller//$Action/$ID/$OtherID' => '*'));
     $fixtureFiles = $this->getFixturePaths();
     // Todo: this could be a special test model
     $this->model = DataModel::inst();
     // Set up fixture
     if ($fixtureFiles || $this->usesDatabase) {
         if (!self::using_temp_db()) {
             self::create_temp_db();
         }
         DataObject::singleton()->flushCache();
         self::empty_temp_db();
         foreach ($this->requireDefaultRecordsFrom as $className) {
             $instance = singleton($className);
             if (method_exists($instance, 'requireDefaultRecords')) {
                 $instance->requireDefaultRecords();
             }
             if (method_exists($instance, 'augmentDefaultRecords')) {
                 $instance->augmentDefaultRecords();
             }
         }
         foreach ($fixtureFiles as $fixtureFilePath) {
             $fixture = YamlFixture::create($fixtureFilePath);
             $fixture->writeInto($this->getFixtureFactory());
         }
         $this->logInWithPermission("ADMIN");
     }
     // Preserve memory settings
     $this->originalMemoryLimit = ini_get('memory_limit');
     // turn off template debugging
     SSViewer::config()->update('source_file_comments', false);
     // Clear requirements
     Requirements::clear();
     // Set up email
     $this->mailer = new TestMailer();
     Injector::inst()->registerService($this->mailer, 'SilverStripe\\Control\\Email\\Mailer');
     Email::config()->remove('send_all_emails_to');
 }
 /**
  * Return the appropriate error content for the given status code
  *
  * @param int $statusCode
  * @return string Content in an appropriate format for the current request
  */
 public function output($statusCode)
 {
     // TODO: Refactor into a content-type option
     if (Director::is_ajax()) {
         return $this->getTitle();
     }
     $renderer = Debug::create_debug_view();
     $output = $renderer->renderHeader();
     $output .= $renderer->renderInfo("Website Error", $this->getTitle(), $this->getBody());
     if (Email::config()->admin_email) {
         $mailto = Email::obfuscate(Email::config()->admin_email);
         $output .= $renderer->renderParagraph('Contact an administrator: ' . $mailto . '');
     }
     $output .= $renderer->renderFooter();
     return $output;
 }
 /**
  * Event handler called before writing to the database.
  */
 public function onBeforeWrite()
 {
     if ($this->SetPassword) {
         $this->Password = $this->SetPassword;
     }
     // If a member with the same "unique identifier" already exists with a different ID, don't allow merging.
     // Note: This does not a full replacement for safeguards in the controller layer (e.g. in a registration form),
     // but rather a last line of defense against data inconsistencies.
     $identifierField = Member::config()->unique_identifier_field;
     if ($this->{$identifierField}) {
         // Note: Same logic as Member_Validator class
         $filter = array("\"{$identifierField}\"" => $this->{$identifierField});
         if ($this->ID) {
             $filter[] = array('"Member"."ID" <> ?' => $this->ID);
         }
         $existingRecord = DataObject::get_one('SilverStripe\\Security\\Member', $filter);
         if ($existingRecord) {
             throw new ValidationException(ValidationResult::create(false, _t('Member.ValidationIdentifierFailed', 'Can\'t overwrite existing member #{id} with identical identifier ({name} = {value}))', 'Values in brackets show "fieldname = value", usually denoting an existing email address', array('id' => $existingRecord->ID, 'name' => $identifierField, 'value' => $this->{$identifierField}))));
         }
     }
     // We don't send emails out on dev/tests sites to prevent accidentally spamming users.
     // However, if TestMailer is in use this isn't a risk.
     if ((Director::isLive() || Email::mailer() instanceof TestMailer) && $this->isChanged('Password') && $this->record['Password'] && $this->config()->notify_password_change) {
         /** @var Email $e */
         $e = Email::create();
         $e->setSubject(_t('Member.SUBJECTPASSWORDCHANGED', "Your password has been changed", 'Email subject'));
         $e->setTemplate('ChangePasswordEmail');
         $e->populateTemplate($this);
         $e->setTo($this->Email);
         $e->send();
     }
     // The test on $this->ID is used for when records are initially created.
     // Note that this only works with cleartext passwords, as we can't rehash
     // existing passwords.
     if (!$this->ID && $this->Password || $this->isChanged('Password')) {
         //reset salt so that it gets regenerated - this will invalidate any persistant login cookies
         // or other information encrypted with this Member's settings (see self::encryptWithUserSettings)
         $this->Salt = '';
         // Password was changed: encrypt the password according the settings
         $encryption_details = Security::encrypt_password($this->Password, $this->Salt, $this->PasswordEncryption ? $this->PasswordEncryption : Security::config()->password_encryption_algorithm, $this);
         // Overwrite the Password property with the hashed value
         $this->Password = $encryption_details['password'];
         $this->Salt = $encryption_details['salt'];
         $this->PasswordEncryption = $encryption_details['algorithm'];
         // If we haven't manually set a password expiry
         if (!$this->isChanged('PasswordExpiry')) {
             // then set it for us
             if (self::config()->password_expiry_days) {
                 $this->PasswordExpiry = date('Y-m-d', time() + 86400 * self::config()->password_expiry_days);
             } else {
                 $this->PasswordExpiry = null;
             }
         }
     }
     // save locale
     if (!$this->Locale) {
         $this->Locale = i18n::get_locale();
     }
     parent::onBeforeWrite();
 }
 /**
  * Forgot password form handler method.
  * Called when the user clicks on "I've lost my password".
  * Extensions can use the 'forgotPassword' method to veto executing
  * the logic, by returning FALSE. In this case, the user will be redirected back
  * to the form without further action. It is recommended to set a message
  * in the form detailing why the action was denied.
  *
  * @skipUpgrade
  * @param array $data Submitted data
  * @return HTTPResponse
  */
 public function forgotPassword($data)
 {
     // Ensure password is given
     if (empty($data['Email'])) {
         $this->sessionMessage(_t('Member.ENTEREMAIL', 'Please enter an email address to get a password reset link.'), 'bad');
         return $this->controller->redirect('Security/lostpassword');
     }
     // Find existing member
     /** @var Member $member */
     $member = Member::get()->filter("Email", $data['Email'])->first();
     // Allow vetoing forgot password requests
     $results = $this->extend('forgotPassword', $member);
     if ($results && is_array($results) && in_array(false, $results, true)) {
         return $this->controller->redirect('Security/lostpassword');
     }
     if ($member) {
         $token = $member->generateAutologinTokenAndStoreHash();
         /** @var Email $e */
         $e = Email::create();
         $e->setSubject(_t('Member.SUBJECTPASSWORDRESET', "Your password reset link", 'Email subject'));
         $e->setTemplate('ForgotPasswordEmail');
         $e->populateTemplate($member);
         $e->populateTemplate(array('PasswordResetLink' => Security::getPasswordResetLink($member, $token)));
         $e->setTo($member->Email);
         $e->send();
         return $this->controller->redirect('Security/passwordsent/' . urlencode($data['Email']));
     } elseif ($data['Email']) {
         // Avoid information disclosure by displaying the same status,
         // regardless wether the email address actually exists
         return $this->controller->redirect('Security/passwordsent/' . rawurlencode($data['Email']));
     } else {
         $this->sessionMessage(_t('Member.ENTEREMAIL', 'Please enter an email address to get a password reset link.'), 'bad');
         return $this->controller->redirect('Security/lostpassword');
     }
 }
 /**
  * Send an email with HTML content.
  *
  * @see sendPlain() for sending plaintext emails only.
  * @uses Mailer->sendHTML()
  *
  * @param string $messageID Optional message ID so the message can be identified in bounces etc.
  * @return mixed Success of the sending operation from an MTA perspective. Doesn't actually give any indication if
  * the mail has been delivered to the recipient properly). See Mailer->sendPlain() for return type details.
  */
 public function send($messageID = null)
 {
     Requirements::clear();
     $this->parseVariables();
     if (empty($this->from)) {
         $this->from = Email::config()->admin_email;
     }
     $headers = $this->customHeaders;
     if ($messageID) {
         $headers['X-SilverStripeMessageID'] = project() . '.' . $messageID;
     }
     if (project()) {
         $headers['X-SilverStripeSite'] = project();
     }
     $to = $this->to;
     $from = $this->from;
     $subject = $this->subject;
     if ($sendAllTo = $this->config()->send_all_emails_to) {
         $subject .= " [addressed to {$to}";
         $to = $sendAllTo;
         if ($this->cc) {
             $subject .= ", cc to {$this->cc}";
         }
         if ($this->bcc) {
             $subject .= ", bcc to {$this->bcc}";
         }
         $subject .= ']';
         unset($headers['Cc']);
         unset($headers['Bcc']);
     } else {
         if ($this->cc) {
             $headers['Cc'] = $this->cc;
         }
         if ($this->bcc) {
             $headers['Bcc'] = $this->bcc;
         }
     }
     if ($ccAllTo = $this->config()->cc_all_emails_to) {
         if (!empty($headers['Cc']) && trim($headers['Cc'])) {
             $headers['Cc'] .= ', ' . $ccAllTo;
         } else {
             $headers['Cc'] = $ccAllTo;
         }
     }
     if ($bccAllTo = $this->config()->bcc_all_emails_to) {
         if (!empty($headers['Bcc']) && trim($headers['Bcc'])) {
             $headers['Bcc'] .= ', ' . $bccAllTo;
         } else {
             $headers['Bcc'] = $bccAllTo;
         }
     }
     if ($sendAllfrom = $this->config()->send_all_emails_from) {
         if ($from) {
             $subject .= " [from {$from}]";
         }
         $from = $sendAllfrom;
     }
     Requirements::restore();
     return self::mailer()->sendHTML($to, $from, $subject, $this->body, $this->attachments, $headers, $this->plaintext_body);
 }