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); }