diff --git a/docs/changelog/7.x.x.txt b/docs/changelog/7.x.x.txt index 43dfa6f8d..224c0e3ca 100644 --- a/docs/changelog/7.x.x.txt +++ b/docs/changelog/7.x.x.txt @@ -68,6 +68,9 @@ - Collaboration System wobjects can now be subclassed and still work with the existing Thread and Post assets. - fix: Added some additional i18n that was missing. + - add: Settings can now return error messages to the user + - add: Password recovery can now be based on profile fields or simply by the + user's e-mail address. 7.3.20 - fix: Deactivated Users Subscriptions (perlDreamer Consulting, LLC) diff --git a/lib/WebGUI/Auth.pm b/lib/WebGUI/Auth.pm index cd7dc1943..a7213817a 100644 --- a/lib/WebGUI/Auth.pm +++ b/lib/WebGUI/Auth.pm @@ -367,6 +367,22 @@ sub deleteParams { #------------------------------------------------------------------- +=head2 deleteSingleParam ( ) + +Removes a single authentication parameter from the database. + +=cut + +sub deleteSingleParam { + my $self = shift; + my ($userId, $authMethod, $fieldName) = @_; + + $self->session->db->write('delete from authentication where userId = ? and authMethod = ? and fieldName = ?', [$userId, $authMethod, $fieldName]); + +} + +#------------------------------------------------------------------- + =head2 displayAccount ( method [,vars] ) Superclass method that performs general functionality for viewing editable fields related to a user's account. diff --git a/lib/WebGUI/Auth/WebGUI.pm b/lib/WebGUI/Auth/WebGUI.pm index 72f73226c..c74727439 100644 --- a/lib/WebGUI/Auth/WebGUI.pm +++ b/lib/WebGUI/Auth/WebGUI.pm @@ -400,9 +400,10 @@ sub editUserFormSave { =cut sub editUserSettingsForm { - my $self = shift; - my $i18n = WebGUI::International->new($self->session,'AuthWebGUI'); - my $f = WebGUI::HTMLForm->new($self->session); + my $self = shift; + my $i18n = WebGUI::International->new($self->session,'AuthWebGUI'); + my $f = WebGUI::HTMLForm->new($self->session); + $f->integer( -name=>"webguiPasswordLength", -value=>$self->session->setting->get("webguiPasswordLength"), @@ -453,11 +454,14 @@ sub editUserSettingsForm { -value=>$self->session->setting->get("webguiChangePassword"), -label=>$i18n->get(18) ); - $f->yesNo( + $f->selectList( -name => "webguiPasswordRecovery", -value => $self->session->setting->get("webguiPasswordRecovery"), -label => $i18n->get(6), - -hoverHelp => $i18n->get('webguiPasswordRecovery hoverHelp') + -hoverHelp => $i18n->get('webguiPasswordRecovery hoverHelp'), + -options => $self->getPasswordRecoveryTypesAvailable, + -size => 1, + -multiple => 0, ); $f->yesNo( -name => "webguiPasswordRecoveryRequireUsername", @@ -510,9 +514,11 @@ sub editUserSettingsForm { #------------------------------------------------------------------- sub editUserSettingsFormSave { - my $self = shift; - my $f = $self->session->form; - my $s = $self->session->setting; + my $self = shift; + my $f = $self->session->form; + my $s = $self->session->setting; + my $i18n = WebGUI::International->new($self->session, 'AuthWebGUI'); + my @errors; # Array of errors to return, if any. See WebGUI::Operation::Settings->www_saveSettings $s->set("webguiPasswordLength", $f->process("webguiPasswordLength","integer")); $s->set("webguiRequiredDigits", $f->process("webguiRequiredDigits","integer")); $s->set("webguiNonWordCharacters", $f->process("webguiNonWordCharacters","integer")); @@ -524,9 +530,27 @@ sub editUserSettingsFormSave { $s->set("webguiChangeUsername", $f->process("webguiChangeUsername","yesNo")); $s->set("webguiChangePassword", $f->process("webguiChangePassword","yesNo")); - # Special case to make sure we have at least one field enabled before allowing - # password recovery to be turned on. - $s->set("webguiPasswordRecovery", $f->process("webguiPasswordRecovery","yesNo") && ($self->session->db->quickArray("SELECT COUNT(*) FROM userProfileField WHERE requiredForPasswordRecovery = 1"))[0] > 0); + # Make sure we have the ability to recover a password if we're trying to + # enable password recovery + my $passwordRecoveryType = $f->process("webguiPasswordRecovery", "selectList"); + if ($passwordRecoveryType eq "profile") { + # Profile recovery requires at least one field set to required + my ($passwordRecoveryFields) + = $self->session->db->quickArray( + "SELECT COUNT(*) FROM userProfileField WHERE requiredForPasswordRecovery = 1" + ); + + if ($passwordRecoveryFields <= 0) { + push @errors, $i18n->get("error passwordRecoveryType no profile fields required"); + } + else { + $s->set("webguiPasswordRecovery", $passwordRecoveryType); + } + } + # Recovery types that need no error checking + else { + $s->set("webguiPasswordRecovery", $passwordRecoveryType); + } $s->set("webguiPasswordRecoveryRequireUsername", $f->process("webguiPasswordRecoveryRequireUsername","yesNo")); $s->set("webguiValidateEmail", $f->process("webguiValidateEmail","yesNo")); @@ -536,6 +560,13 @@ sub editUserSettingsFormSave { $s->set("webguiExpiredPasswordTemplate", $f->process("webguiExpiredPasswordTemplate","template")); $s->set("webguiLoginTemplate", $f->process("webguiLoginTemplate","template")); $s->set("webguiPasswordRecoveryTemplate", $f->process("webguiPasswordRecoveryTemplate","template")); + + if (@errors) { + return \@errors; + } + else { + return; + } } #------------------------------------------------------------------- @@ -564,10 +595,45 @@ sub getLoginTemplateId { #------------------------------------------------------------------- sub getPasswordRecoveryTemplateId { - my $self = shift; - return $self->session->setting->get("webguiPasswordRecoveryTemplate") || "PBtmpl0000000000000014"; + my $self = shift; + return $self->session->setting->get("webguiPasswordRecoveryTemplate") || "PBtmpl0000000000000014"; } +#------------------------------------------------------------------- +sub getPasswordRecoveryType { + my $self = shift; + return $self->session->setting->get("webguiPasswordRecovery"); +} + +#---------------------------------------------------------------------------- + +=head2 getPasswordRecoveryTypesAvailable + +Returns a hash reference of password recovery types. Keys are the type, values +are an i18n label for the user. + +=cut + +sub getPasswordRecoveryTypesAvailable { + my $self = shift; + my $i18n = WebGUI::International->new($self->session, 'AuthWebGUI'); + + tie my %types, 'Tie::IxHash', ( + "" => $i18n->get("setting passwordRecoveryType none"), + profile => $i18n->get("setting passwordRecoveryType profile"), + email => $i18n->get("setting passwordRecoveryType email"), + ); + + return \%types; +} + +#------------------------------------------------------------------- +sub getUserIdByPasswordRecoveryToken { + my $self = shift; + my $session = shift; + my $token = shift; + return $session->db->quickScalar("select userId from authentication where fieldName = 'emailRecoverPasswordVerificationNumber' and fieldData = ?", [$token]); +} #------------------------------------------------------------------- sub login { @@ -594,22 +660,88 @@ sub login { #------------------------------------------------------------------- sub new { - my $class = shift; - my $session = shift; - my $authMethod = $_[0]; - my $userId = $_[1]; - my @callable = ('validateEmail','createAccount','deactivateAccount','displayAccount','displayLogin','login','logout','recoverPassword','resetExpiredPassword','recoverPasswordFinish','createAccountSave','deactivateAccountConfirm','resetExpiredPasswordSave','updateAccount'); - my $self = WebGUI::Auth->new($session,$authMethod,$userId,\@callable); - bless $self, $class; + my $class = shift; + my $session = shift; + my $authMethod = $_[0]; + my $userId = $_[1]; + my @callable = ('validateEmail','createAccount','deactivateAccount','displayAccount','displayLogin','login','logout','recoverPassword','resetExpiredPassword','recoverPasswordFinish','createAccountSave','deactivateAccountConfirm','resetExpiredPasswordSave','updateAccount', 'emailResetPassword', 'emailResetPasswordFinish'); + my $self = WebGUI::Auth->new($session,$authMethod,$userId,\@callable); + bless $self, $class; } - #------------------------------------------------------------------- + +=head2 recoverPassword ( ) + +Initiates the password recovery process. Checks for recovery type, and then runs the appropriate method. + +=cut + sub recoverPassword { - my $self = shift; - return $self->displayLogin unless $self->session->setting->get('webguiPasswordRecovery') and $self->userId eq '1'; - my @fields = @{WebGUI::ProfileField->getPasswordRecoveryFields($self->session)}; - return $self->displayLogin unless @fields; + my $self = shift; + + return $self->displayLogin unless ($self->session->setting->get('webguiPasswordRecovery') ne '') and $self->userId eq '1'; + + my $type = $self->getPasswordRecoveryType; + + #$self->session->errorHandler->warn("recovery type: $type"); + + if ($type eq 'profile') { + $self->profileRecoverPassword; + } + elsif ($type eq 'email') { + $self->emailRecoverPassword; + } +} + +#------------------------------------------------------------------- + +sub emailRecoverPassword { + my $self = shift; + + my $i18n = WebGUI::International->new($self->session); + my $output + = "

" . $i18n->get('recover password banner', 'AuthWebGUI') . "



" + . "

" . $i18n->get('email recover password start message', 'AuthWebGUI') ."

" + ; + + my $f = WebGUI::HTMLForm->new($self->session); + + $f->hidden( + name => 'op', + value => 'auth', + ); + + $f->hidden( + name => "method", + value => "recoverPasswordFinish", + ); + + $f->text( + name => "username", + label => $i18n->get('password recovery login label', 'AuthWebGUI'), + hoverHelp => $i18n->get('password recovery login help', 'AuthWebGUI'), + ); + + $f->email( + name =>"email", + label => $i18n->get('password recovery email label', 'AuthWebGUI'), + hoverHelp => $i18n->get('password recovery email help', 'AuthWebGUI'), + ); + + $f->submit(); + + $output .= $f->print; + return $output; + } + +#------------------------------------------------------------------- + +sub profileRecoverPassword { + my $self = shift; + + my @fields = @{WebGUI::ProfileField->getPasswordRecoveryFields($self->session)}; + return $self->displayLogin unless @fields; my $vars = {}; my $i18n = WebGUI::International->new($self->session); @@ -647,16 +779,37 @@ sub recoverPassword { return WebGUI::Asset::Template->new($self->session,$self->getPasswordRecoveryTemplateId)->process($vars); } - + #------------------------------------------------------------------- + +=head2 recoverPasswordFinish ( ) + +Handles data for recovery of password. Gets password recovery type, and then runs the appropriate method. + +=cut + sub recoverPasswordFinish { - my $self = shift; - my $i18n = WebGUI::International->new($self->session); - my $i18n2 = WebGUI::International->new($self->session, 'AuthWebGUI'); - return $self->displayLogin unless $self->session->setting->get('webguiPasswordRecovery') and $self->userId eq '1'; + my $self = shift; - my $username; - if ($self->getSetting('passwordRecoveryRequireUsername')) { + my $type = $self->getPasswordRecoveryType; + + if ($type eq 'profile') { + $self->profileRecoverPasswordFinish; + } elsif ($type eq 'email') { + $self->emailRecoverPasswordFinish; + } + } + +#------------------------------------------------------------------- + +sub profileRecoverPasswordFinish { + my $self = shift; + my $i18n = WebGUI::International->new($self->session); + my $i18n2 = WebGUI::International->new($self->session, 'AuthWebGUI'); + return $self->displayLogin unless ($self->session->setting->get('webguiPasswordRecovery') ne '') and $self->userId eq '1'; + + my $username; + if ($self->getSetting('passwordRecoveryRequireUsername')) { $username = $self->session->form->process('authWebGUI.username'); return $self->recoverPassword($i18n2->get('password recovery no username')) unless defined $username; } @@ -742,6 +895,146 @@ sub recoverPasswordFinish { } } +#------------------------------------------------------------------- + +sub emailRecoverPasswordFinish { + my $self = shift; + return $self->displayLogin unless ($self->session->setting->get('webguiPasswordRecovery') ne '') and $self->userId eq '1'; + + my $i18n = WebGUI::International->new($self->session); + my $session = $self->session; + my ($form) = $session->quick(qw/form/); + my $email = $form->param('email'); + my $username = $form->param('username'); + my $user; + +# get user from email + $user = WebGUI::User->newByEmail($session, $email) if $email; +# get user from username + if ($username) { + $user = WebGUI::User->newByUsername($session, $username) unless $user; + } +# return error unless we get a valid user. + + unless ($user) { + return $i18n->get('recover password not found', 'AuthWebGUI'); + } + +# generate information necessry to proceed + my $recoveryGuid = $session->id->generate(); + my $url = $session->url->getSiteURL; + my $userId = $user->userId; #get the user guid + $email = $user->profileField('email') unless $email; #get email address from the profile, unless we already have it + + my $authsettings = $self->getParams; + $authsettings->{emailRecoverPasswordVerificationNumber} = $recoveryGuid; + + $self->saveParams($userId, 'WebGUI', $authsettings); + + my $mail = WebGUI::Mail::Send->create($session, { to=>$email, subject=>'WebGUI password recovery'}); + $mail->addText($i18n->get('recover password email text1', 'AuthWebGUI') . $url. ". \n\n".$i18n->get('recover password email text2', 'AuthWebGUI')." \n\n ".$url."?op=auth;method=emailResetPassword;token=$recoveryGuid"."\n\n ". $i18n->get('recover password email text3', 'AuthWebGUI')); + $mail->send; + return "

". $i18n->get('recover password banner', 'AuthWebGUI')."



". $i18n->get('email recover password finish message1', 'AuthWebGUI'). $email . $i18n->get('email recover password finish message2', 'AuthWebGUI') . "

"; +} + +#------------------------------------------------------------------- +# handler for the link generated and mailed by emailRecoverPasswordFinish + +sub emailResetPassword { + my $self = shift; + my $errormsg = shift; + + my $session = $self->session; + my ($form) = $session->quick(qw/form/); + my $passwordRecoveryToken = $form->param('token'); + + my $i18n = WebGUI::International->new($self->session); + my $userId = $self->getUserIdByPasswordRecoveryToken($session, $passwordRecoveryToken); + + my $u = $self->user(WebGUI::User->new($self->session, $userId)); + $self->session->user({user=>$u}); + +# do not proceed unless we have an incoming guid from the email, and that guid corresponds to a valid user. + unless ($passwordRecoveryToken && $userId) { + return $session->privilege->insufficient; + } + +# login the user and take them to a page where they can change their password. + + my $output = "

".$i18n->get('recover password banner', 'AuthWebGUI') ."



". $i18n->get('email password recovery end message', 'AuthWebGUI')."

"; + + $output .= $errormsg if $errormsg; + + my $f = WebGUI::HTMLForm->new($self->session); + + $f->hidden( + name => 'op', + value => 'auth', + ); + + $f->hidden( + name => "method", + value => "emailResetPasswordFinish", + ); + + $f->hidden( + name => "token", + value => "$passwordRecoveryToken", + ); + + $f->password( + name=>"newpassword", + label=> $i18n->get('new password label', 'AuthWebGUI'), + hoverHelp=> $i18n->get('new password help', 'AuthWebGUI'), + ); + + $f->password( + name=>"newpwdverify", + label => $i18n->get('new password verify', 'AuthWebGUI'), + hoverHelp=> $i18n->get('new password verify help', 'AuthWebGUI'), + ); + + $f->submit( + value => 'submit' + ); + + + + $output .= $f->print; + return $output; + +} + +#------------------------------------------------------------------- + +sub emailResetPasswordFinish { + my $self = shift; + my $session = $self->session; + my ($form) = $session->quick(qw/form/); + my $password = $form->param('newpassword'); + my $passwordConfirm = $form->param('newpwdverify'); + my $passwordRecoveryToken = $form->param('token'); + + my $userId = $self->getUserIdByPasswordRecoveryToken($session, $passwordRecoveryToken); + + return $session->privilege->insufficient unless $userId; + + if ($self->_isValidPassword($password, $passwordConfirm)) { + $self->user(WebGUI::User->new($self->session, $userId)); + $self->saveParams($userId, $self->authMethod, + { identifier => Digest::MD5::md5_base64($password), + passwordLastUpdated => $self->session->datetime->time }); + $self->_logSecurityMessage; + +# delete the emailRecoverPasswordVerificationNumber + $self->deleteSingleParam($userId, $self->authMethod, 'emailRecoverPasswordVerificationNumber'); + return $self->SUPER::login; + } else { + return $self->emailResetPassword($self->error); + } + +} + #------------------------------------------------------------------- sub resetExpiredPassword { my $self = shift; diff --git a/lib/WebGUI/Operation/Settings.pm b/lib/WebGUI/Operation/Settings.pm index 7c9d21258..27edc9d9e 100644 --- a/lib/WebGUI/Operation/Settings.pm +++ b/lib/WebGUI/Operation/Settings.pm @@ -440,47 +440,84 @@ sub definition { #------------------------------------------------------------------- -=head2 www_editSettings ( $session ) +=head2 www_editSettings ( $session, $argsHash ) Display a form for sitewide settings, if the user is in group Admin (3). +argsHash is a hash reference of additional arguments to this sub. Available +keys: + + errors - An array reference of errors with processing the site settings + NOTE: Must be i18n BEFORE given to this sub + message - A benign message to the user + NOTE: Must be i18n BEFORE given to this sub + =cut sub www_editSettings { - my $session = shift; + my $session = shift; + my $argsHash = shift; return $session->privilege->adminOnly() unless ($session->user->isInGroup(3)); - my $i18n = WebGUI::International->new($session, "WebGUI"); - my %tabs; - tie %tabs, 'Tie::IxHash'; - %tabs = ( - company=>{ label=>$i18n->get("company") }, - content=>{ label=>$i18n->get("content") }, - ui=>{ label=>$i18n->get("ui") }, - messaging=>{ label=>$i18n->get("messaging") }, - misc=>{ label=>$i18n->get("misc") }, - user=>{ label=>$i18n->get("user") }, - auth=>{ label=>$i18n->get("authentication") }, - ); + my $i18n = WebGUI::International->new($session, "WebGUI"); + my $output = ''; + + # Show any errors or message + if ($argsHash->{message}) { + $output .= '

' . $argsHash->{message} . '

'; + } + my @errors = @{ $argsHash->{errors} }; + if (@errors) { + $output .= '

' . $i18n->get("editSettings error occurred") . '

' + . ''; + } + + # Available tabs + # TODO: Build this from the definition instead. + tie my %tabs, 'Tie::IxHash', ( + company => { label => $i18n->get("company") }, + content => { label => $i18n->get("content") }, + ui => { label => $i18n->get("ui") }, + messaging => { label => $i18n->get("messaging") }, + misc => { label => $i18n->get("misc") }, + user => { label => $i18n->get("user") }, + auth => { label => $i18n->get("authentication") }, + ); + + # Start the form my $tabform = WebGUI::TabForm->new($session,\%tabs); $tabform->hidden({ - name=>"op", - value=>"saveSettings"}); + name => "op", + value => "saveSettings" + }); + my $definitions = definition($session, $i18n); foreach my $definition (@{$definitions}) { $tabform->getTab($definition->{tab})->dynamicField(%{$definition}); } + + # Get fieldsets for avaiable auth methods foreach (@{$session->config->get("authMethods")}) { $tabform->getTab("auth")->fieldSetStart($_); my $authInstance = WebGUI::Operation::Auth::getInstance($session,$_,1); $tabform->getTab("auth")->raw($authInstance->editUserSettingsForm); $tabform->getTab("auth")->fieldSetEnd; } + $tabform->submit(); + $output .= $tabform->print; + my $ac = WebGUI::AdminConsole->new($session,"settings"); $ac->setHelp("settings"); - return $ac->render($tabform->print); + return $ac->render($output); } +#---------------------------------------------------------------------------- + =head2 www_saveSettings ( $session ) Form postprocessor for www_editSettings. Returns adminOnly() unless the user @@ -488,22 +525,29 @@ is in group Admin (3). Returns the user to the Edit Settings screen, www_editSe =cut -#------------------------------------------------------------------- sub www_saveSettings { - my $session = shift; + my $session = shift; return $session->privilege->adminOnly() unless ($session->user->isInGroup(3)); - my $i18n = WebGUI::International->new($session, "WebGUI"); + my $i18n = WebGUI::International->new($session, "WebGUI"); + my $setting = $session->setting; + my $form = $session->form; + my @errors; # Errors trying to save the form + my $definitions = definition($session, $i18n); - my $setting = $session->setting; - my $form = $session->form; foreach my $definition (@{$definitions}) { $setting->set($definition->{name}, $form->process($definition->{name}, $definition->{fieldType}, undef, $definition)); } + foreach (@{$session->config->get("authMethods")}) { - my $authInstance = WebGUI::Operation::Auth::getInstance($session,$_,1); - $authInstance->editUserSettingsFormSave; + my $authInstance = WebGUI::Operation::Auth::getInstance($session,$_,1); + + my $authErrors = $authInstance->editUserSettingsFormSave; + if ($authErrors) { + push @errors, @{ $authErrors }; + } } - return www_editSettings($session); + + return www_editSettings($session, { errors => \@errors, message => $i18n->get("editSettings done") }); } 1; diff --git a/lib/WebGUI/i18n/English/AuthWebGUI.pm b/lib/WebGUI/i18n/English/AuthWebGUI.pm index 88427bd53..5d7fca5e3 100644 --- a/lib/WebGUI/i18n/English/AuthWebGUI.pm +++ b/lib/WebGUI/i18n/English/AuthWebGUI.pm @@ -573,6 +573,113 @@ our $I18N = { message => q{Number of upper-case characters required in password}, lastUpdated => 0, }, + + 'password recovery email label' => { + message => q|Email Address|, + lastUpdated => 1177127324, + }, + + 'password recovery email hoverHelp' => { + message => q|Enter your login name|, + lastUpdated => 1177127324, + }, + + 'password recovery login label' => { + message => q|Login Name|, + lastUpdated => 1177127324, + }, + + 'password recovery login hoverHelp' => { + message => q|Enter your email address here|, + lastUpdated => 1177127324, + }, + + 'new password label' => { + message => q|New Password|, + lastUpdated => 1177127324, + }, + + 'new password help' => { + message => q|Enter your new password here|, + lastUpdated => 1177127324, + }, + + 'new password verify' => { + message => q|Verify New Password|, + lastUpdated => 1177127324, + }, + + 'new password verify help' => { + message => q|Enter your password again to verify|, + lastUpdated => 177127324, + }, + + 'recover password not found' => { + message => q|We have no record of a user matching the information you have given|, + lastUpdated => 177127324, + }, + + 'recover password email text1' => { + message => q|We have received your request the password for |, + lastUpdated => 177127324, + }, + + 'recover password email text2' => { + message => q|Pleae use the link below to visit the site and change your password|, + lastUpdated => 177127324, + }, + + 'recover password email text3' => { + message => q|If you did not request your password to be recovered, please contact the system administrator by replying to this message|, + lastUpdated => 177127324, + }, + + 'recover password banner' => { + message => q|Password Recovery|, + lastUpdated => 177127324, + }, + + 'email recover password finish message1' => { + message => q|An email has been sent to |, + lastUpdated => 177127324, + }, + + 'email recover password finish message2' => { + message => q| with instructions for resetting your password.|, + lastUpdated => 177127324, + }, + + 'email recover password start message' => { + message => q|Enter either your email address or your login below to initiate the passwrod recovery process.|, + lastUpdated => 177127324, + }, + + 'email password recovery end message' => { + message => q|Enter a new password for your account below.|, + lastUpdated => 177127324, + }, + + 'setting passwordRecoveryType profile' => { + message => "Profile field", + lastUpdated => 0, + }, + + 'setting passwordRecoveryType email' => { + message => "E-mail address", + lastUpdated => 0, + }, + + 'setting passwordRecoveryType none' => { + message => "No", + lastUpdated => 0, + }, + + 'error passwordRecoveryType no profile fields required' => { + message => q{Cannot enable WebGUI authentication password recovery + by profile field: There are no user profile fields required for password recovery.}, + lastUpdated => 0, + }, + }; 1; diff --git a/lib/WebGUI/i18n/English/WebGUI.pm b/lib/WebGUI/i18n/English/WebGUI.pm index a580f85a6..abfeba26a 100644 --- a/lib/WebGUI/i18n/English/WebGUI.pm +++ b/lib/WebGUI/i18n/English/WebGUI.pm @@ -4251,6 +4251,16 @@ Get a copy of wget and use this: wget -p -r --html-extension -k http://the message => q|Choose a template for sending private messages|, lastUpdated => 1181019679, }, + + 'editSettings error occurred' => { + message => q{The following errors occurred while trying to save settings.}, + lastUpdated => 0, + }, + + 'editSettings done' => { + message => "Settings saved!", + lastUpdated => 0, + }, };