diff --git a/tests/tine20/Admin/Controller/UserTest.php b/tests/tine20/Admin/Controller/UserTest.php index 1bf5ba64f5..5c20bb3db5 100644 --- a/tests/tine20/Admin/Controller/UserTest.php +++ b/tests/tine20/Admin/Controller/UserTest.php @@ -321,6 +321,37 @@ public function testUpdateUserAdbContainer() Addressbook_Controller_Contact::getInstance()->get($user->contact_id)->getIdFromProperty('container_id')); } + public function testAddUserUpdateContact() + { + $userToCreate = TestCase::getTestUser(); + $pw = Tinebase_Record_Abstract::generateUID(12); + + $this->_usernamesToDelete[] = $userToCreate->accountLoginName; + $user = Admin_Controller_User::getInstance()->create($userToCreate, $pw, $pw); + + $newContact = Addressbook_Controller_Contact::getInstance()->create(new Addressbook_Model_Contact([ + 'n_given' => 'foo', + 'n_family' => 'test', + 'email' => 'foo@tine20.org', + 'tel_cell_private' => '+49TELCELLPRIVATE', + ])); + + $oldContactId = $user->contact_id; + $user->contact_id = $newContact->getId(); + $user = Admin_Controller_User::getInstance()->update($user, $pw, $pw); + static::assertEquals($newContact->getId(), $user->contact_id); + + $oldContact = Addressbook_Controller_Contact::getInstance()->get($oldContactId); + static::assertEquals($oldContact->type, Addressbook_Model_Contact::CONTACTTYPE_CONTACT, 'old user contact should switch type to contact'); + + $newContact = Addressbook_Controller_Contact::getInstance()->get($user->contact_id); + static::assertEquals($newContact->type, Addressbook_Model_Contact::CONTACTTYPE_USER, 'new user contact should switch type to user'); + static::assertEquals($newContact->email, $user->accountEmailAddress, 'new contact email should be the same as user email'); + static::assertEquals($newContact->n_fn, $user->accountFullName); + static::assertEquals($newContact->n_fileas, $user->accountDisplayName); + + } + public function testUpdateUserWithEmailButNoPassword() { $this->_skipWithoutEmailSystemAccountConfig(); diff --git a/tests/tine20/Admin/Frontend/Json/UserTest.php b/tests/tine20/Admin/Frontend/Json/UserTest.php index bda0ccc247..995ffcc27a 100644 --- a/tests/tine20/Admin/Frontend/Json/UserTest.php +++ b/tests/tine20/Admin/Frontend/Json/UserTest.php @@ -273,6 +273,53 @@ public function testUpdateUserWithoutContainerACL() self::assertEquals(2, $account['groups']['totalcount']); } + /** + * testUpdateUserSendSMS + * + */ + public function testUpdateUserSendSMS() + { + Tinebase_Config::getInstance()->{Tinebase_Config::SMS}->{Tinebase_Config::SMS_ADAPTERS} = [ + Tinebase_Model_Sms_AdapterConfigs::FLD_ADAPTER_CONFIGS => [ + [ + Tinebase_Model_Sms_AdapterConfig::FLD_NAME => 'sms1', + Tinebase_Model_Sms_AdapterConfig::FLD_ADAPTER_CLASS => Tinebase_Model_Sms_GenericHttpAdapter::class, + Tinebase_Model_Sms_AdapterConfig::FLD_ADAPTER_CONFIG => [ + Tinebase_Model_Sms_GenericHttpAdapter::FLD_URL => 'https://shoo.tld/restapi/message', + Tinebase_Model_Sms_GenericHttpAdapter::FLD_BODY => '{"encoding":"auto","body":"{{ message }}","originator":"{{ app.branding.title }}","recipients":["{{ cellphonenumber }}"],"route":"2345"}', + Tinebase_Model_Sms_GenericHttpAdapter::FLD_METHOD => 'POST', + Tinebase_Model_Sms_GenericHttpAdapter::FLD_HEADERS => [ + 'Auth-Bearer' => 'unittesttokenshaaaaalalala' + ], + ], + ], + ], + ]; + + $userArray = $this->_originalTestUser->toArray(); + + $userArray['send_password_via_sms'] = true; + $userArray['sms_phone_number'] = '777777777'; + $userArray['accountPassword'] = 'tine20admin'; + + $smsConfig = Tinebase_Config::getInstance()->{Tinebase_Config::SMS}->{Tinebase_Config::SMS_ADAPTERS} + ?->{Tinebase_Model_Sms_AdapterConfigs::FLD_ADAPTER_CONFIGS}->getFirstRecord(); + $smsAdapterConfig = $smsConfig ? $smsConfig->{Tinebase_Model_Sms_AdapterConfig::FLD_ADAPTER_CONFIG} : null; + $smsAdapterConfig->setHttpClientConfig([ + 'adapter' => ($httpClientTestAdapter = new Tinebase_ZendHttpClientAdapter()) + ]); + $httpClientTestAdapter->writeBodyCallBack = function($body) { + Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' sms request body: ' . $body); + }; + $httpClientTestAdapter->setResponse(new Zend_Http_Response(200, [])); + + $result = $this->_json->saveUser($userArray); + + $this->assertStringContainsString('Instance: ' . Tinebase_Config::getInstance()->get(Tinebase_Config::BRANDING_TITLE) . ' , new password: ' . $userArray['accountPassword'], + $httpClientTestAdapter->lastRequestBody); + $this->assertTrue($result['sms']['777777777']); + } + /** * @param Tinebase_Model_FullUser $account * @return Tinebase_Model_Container diff --git a/tine20/Addressbook/Controller.php b/tine20/Addressbook/Controller.php index fe2f3ee3a2..3253bb92e4 100644 --- a/tine20/Addressbook/Controller.php +++ b/tine20/Addressbook/Controller.php @@ -88,6 +88,16 @@ protected function _handleEvent(Tinebase_Event_Abstract $_eventObject) case 'Admin_Event_AddAccount': $this->createPersonalFolder($_eventObject->account); break; + case Admin_Event_UpdateAccount::class: + /** @var Admin_Event_UpdateAccount $_eventObject */ + if ($_eventObject->account->contact_id !== $_eventObject->oldAccount->contact_id) { + $contact = Addressbook_Controller_Contact::getInstance()->get($_eventObject->oldAccount->contact_id); + $contact->type = Addressbook_Model_Contact::CONTACTTYPE_CONTACT; + $contact = Addressbook_Controller_Contact::getInstance()->update($contact); + if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . ' ' . __LINE__ + . ' switch deprecated user contact type to contact : ' . $contact->getId()); + } + break; case 'Tinebase_Event_User_DeleteAccount': /** * @var Tinebase_Event_User_DeleteAccount $_eventObject diff --git a/tine20/Addressbook/Controller/Contact.php b/tine20/Addressbook/Controller/Contact.php index 05d631c9b7..9ddc12519c 100644 --- a/tine20/Addressbook/Controller/Contact.php +++ b/tine20/Addressbook/Controller/Contact.php @@ -688,7 +688,7 @@ protected function _inspectBeforeUpdate($_record, $_oldRecord) $this->_checkAndSetShortName($_record, $_oldRecord); - if (isset($_oldRecord->type) && $_oldRecord->type == Addressbook_Model_Contact::CONTACTTYPE_USER) { + if (isset($_oldRecord->type) && $_oldRecord->type == Addressbook_Model_Contact::CONTACTTYPE_USER && empty($_record->type)) { $_record->type = Addressbook_Model_Contact::CONTACTTYPE_USER; } diff --git a/tine20/Admin/Frontend/Json.php b/tine20/Admin/Frontend/Json.php index 9cb71fa922..915851df3b 100644 --- a/tine20/Admin/Frontend/Json.php +++ b/tine20/Admin/Frontend/Json.php @@ -449,6 +449,67 @@ public function saveUser($recordData) 'totalcount' => count($userRoles) ); + + if (!empty($password) && isset($recordData['sms_phone_number'])) { + $smsAdapterConfigs = Tinebase_Config::getInstance()->{Tinebase_Config::SMS}->{Tinebase_Config::SMS_ADAPTERS} + ?->{Tinebase_Model_Sms_AdapterConfigs::FLD_ADAPTER_CONFIGS}; + + if (!$smsAdapterConfigs || count($smsAdapterConfigs) === 0) { + if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug( + __METHOD__ . '::' . __LINE__ . ' sms adapter configs is not found , skip sending new password message'); + } else { + $smsAdapterConfig = $smsAdapterConfigs->getFirstRecord(); + $smsAdapterConfig = $smsAdapterConfig->{Tinebase_Model_Sms_AdapterConfig::FLD_ADAPTER_CONFIG}; + + if (empty($smsAdapterConfig->getHttpClientConfig())) { + $smsAdapterConfig->setHttpClientConfig([ + 'adapter' => ($genericHttpAdapter = new Tinebase_ZendHttpClientAdapter()) + ]); + + $genericHttpAdapter->writeBodyCallBack = function($body) { + $colorGreen = "\033[43m"; + $colorReset = "\033[0m"; + Tinebase_Core::getLogger()->warn($colorGreen . __METHOD__ . '::' . __LINE__ . ' sms request body: ' . $body . $colorReset . PHP_EOL); + }; + $genericHttpAdapter->setResponse(new Zend_Http_Response(200, [])); + } + + $template = Tinebase_Config::getInstance()->{Tinebase_Config::SMS}->{Tinebase_Config::SMS_MESSAGE_TEMPLATES}->get(Tinebase_Config::SMS_TEMPLATE_NEW_PASSWORD); + $locale = Tinebase_Core::getLocale(); + if (! $locale) { + $locale = Tinebase_Translation::getLocale(); + } + $twig = new Tinebase_Twig($locale, Tinebase_Translation::getTranslation(), [ + Tinebase_Twig::TWIG_LOADER => + new Tinebase_Twig_CallBackLoader(__METHOD__ . 'password', time() - 1, function () use ($template) { + return $template; + }) + ]); + $message = $twig->load(__METHOD__ . 'password')->render([ + 'instanceName' => Tinebase_Config::getInstance()->get(Tinebase_Config::BRANDING_TITLE), + 'password' => $password, + ]); + + $smsSendConfig = new Tinebase_Model_Sms_SendConfig([ + Tinebase_Model_Sms_SendConfig::FLD_MESSAGE => $message, + Tinebase_Model_Sms_SendConfig::FLD_RECIPIENT_NUMBER => $recordData['sms_phone_number'], + Tinebase_Model_Sms_SendConfig::FLD_ADAPTER_CLASS => Tinebase_Model_Sms_GenericHttpAdapter::class, + Tinebase_Model_Sms_SendConfig::FLD_ADAPTER_CONFIG => $smsAdapterConfig, + ]); + + if (Tinebase_Sms::send($smsSendConfig)) { + try { + $result['sms'] = [ + $recordData['sms_phone_number'] => true, + ]; + } catch (Zend_Session_Exception $zse) { + if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) + Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' ' . $zse->getMessage()) ; + } + } + } + } + Tinebase_Core::setExecutionLifeTime($oldMaxExcecutionTime); return $result; diff --git a/tine20/Admin/js/user/EditDialog.js b/tine20/Admin/js/user/EditDialog.js index 955430941b..5616b96c92 100644 --- a/tine20/Admin/js/user/EditDialog.js +++ b/tine20/Admin/js/user/EditDialog.js @@ -54,7 +54,7 @@ Tine.Admin.UserEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, { * @private */ initComponent: function () { - var accountBackend = Tine.Tinebase.registry.get('accountBackend'); + const accountBackend = Tine.Tinebase.registry.get('accountBackend'); this.ldapBackend = (accountBackend === 'Ldap' || accountBackend === 'ActiveDirectory'); this.twingEnv = getTwingEnv(); @@ -62,6 +62,7 @@ Tine.Admin.UserEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, { for (const [fieldName, template] of Object.entries(Tine.Tinebase.configManager.get('accountTwig'))) { loader.setTemplate(fieldName, template); } + this.hasSmsAdapters = Tine.Tinebase.registry.get('hasSmsAdapters'); Tine.Admin.UserEditDialog.superclass.initComponent.call(this); }, @@ -134,6 +135,13 @@ Tine.Admin.UserEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, { this.getForm().findField('personalFSQuota').setValue(xprops.personalFSQuota); } + this.defaultContainer = this.record.get('container_id'); + + if (this.hasSmsAdapters) { + const contact = this.record.get('contact_id'); + this.loadSMSContactPhoneNumbers(contact); + } + this.mustChangePasswordCheck() Tine.Admin.UserEditDialog.superclass.onRecordLoad.call(this); @@ -161,6 +169,17 @@ Tine.Admin.UserEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, { this.mustChangeTriggerPlugin.setVisible(true) }, + + loadSMSContactPhoneNumbers(contact) { + const phoneFields = _.sortBy(_.filter(Tine.Addressbook.Model.Contact.getModelConfiguration().fields, (field) => { + return field?.specialType === 'Addressbook_Model_ContactProperties_Phone' && contact?.[field.fieldName]; + }), (field) => {return _.get(field, 'uiconfig.sort')}); + const mobilePhones = phoneFields.map((phoneField) => { + return [phoneField.fieldName, contact?.[phoneField.fieldName], `${contact?.[phoneField.fieldName]} [${phoneField.label}]`]; + }); + this.phoneCombo.store.loadData(mobilePhones); + }, + /** * @private */ @@ -220,11 +239,21 @@ Tine.Admin.UserEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, { this.record.set('password_must_change', this.record.get('password_must_change_actual')) } - var xprops = this.record.get('xprops'); + let xprops = this.record.get('xprops'); + this.unsetLocalizedDateTimeFields(this.record, ['accountLastLogin', 'accountLastPasswordChange']); + + const sendPwdViaSMS = this.getForm().findField('send_password_via_sms').getValue(); + const smsPhoneNumber = this.getForm().findField('sms_phone_number').getValue(); + + if (sendPwdViaSMS && smsPhoneNumber) { + this.record.set('sms_phone_number', smsPhoneNumber); + } + xprops = Ext.isObject(xprops) ? xprops : {}; xprops.personalFSQuota = this.getForm().findField('personalFSQuota').getValue(); Tine.Tinebase.common.assertComparable(xprops); this.record.set('xprops', xprops); + }, /** * need to unset localized datetime fields before saving @@ -923,7 +952,43 @@ Tine.Admin.UserEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, { qtip: i18n._('Password is expired in accordance with the password policy and needs to be changed'), preserveElStyle: true }) + this.saveInaddressbookFields = this.getSaveInAddessbookFields(this); + this.saveInaddressbookFields.push({ + hideLabel: true, + xtype: 'checkbox', + boxLabel: this.app.i18n.gettext('Send password via SMS'), + hidden: !this.hasSmsAdapters, + disabled: true, + ctCls: 'admin-checkbox', + fieldClass: 'admin-checkbox-box', + name: 'send_password_via_sms', + listeners: { + scope: this, + check: function(cb, checked) { + const checkbox = this.getForm().findField('sms_phone_number'); + checkbox.setDisabled(!checked); + } + } + },{ + xtype: 'combo', + fieldLabel: this.app.i18n.gettext('Mobile Phone'), + name: 'sms_phone_number', + hidden: !this.hasSmsAdapters, + disabled: true, + ref: '../../../../../phoneCombo', + store: new Ext.data.ArrayStore({ + idIndex: 0, + fields: ['name', 'value', 'display_value'] + }), + mode: 'local', + triggerAction: 'all', + editable: true, + valueField: 'value', + displayField: 'display_value', + forceSelection: false, + }); + this.saveInaddressbookFields.push({ hideLabel: true, xtype: 'checkbox', @@ -1024,6 +1089,8 @@ Tine.Admin.UserEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, { if (this.passwordConfirmWindow) { field.passwordsMatch = false; } + const checkbox = this.getForm().findField('send_password_via_sms'); + if (checkbox) checkbox.setDisabled(false); } }, validateValue: function (value) { @@ -1036,6 +1103,7 @@ Tine.Admin.UserEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, { checkboxToggle: false, columnWidth: 1, layout: 'hfit', + style: 'margin-bottom: 8px', items: [this.MFAPanel] }], [{ vtype: 'email', @@ -1052,7 +1120,7 @@ Tine.Admin.UserEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, { fieldLabel: this.app.i18n.gettext('OpenID'), emptyText: '(' + this.app.i18n.gettext('Login name') + ')', name: 'openid', - columnWidth: 0.5 + columnWidth: 0.5, }], [{ xtype: 'tinerecordpickercombobox', fieldLabel: this.app.i18n.gettext('Primary group'), @@ -1240,10 +1308,10 @@ Tine.Admin.UserEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, { validateLoginName: function (value) { return value.match(/^[a-z\d._-]+$/i) !== null; }, - + getSaveInAddessbookFields(scope, hidden) { this.app = Tine.Tinebase.appMgr.get('Admin'); - + return [{ xtype: 'combo', fieldLabel: this.app.i18n.gettext('Visibility'), @@ -1259,11 +1327,18 @@ Tine.Admin.UserEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, { scope: scope, select: function (combo, record) { // disable container_id combo if hidden - var addressbookContainerCombo = scope.getForm().findField('container_id'); - addressbookContainerCombo.setDisabled(record.data.field1 === 'hidden'); + const hidden = record.data.field1 === 'hidden'; + const addressbookContainerCombo = scope.getForm().findField('container_id'); + addressbookContainerCombo.setDisabled(hidden); if (addressbookContainerCombo.getValue() === '') { addressbookContainerCombo.setValue(null); } + // disable container_id combo if hidden + const addressbookContactCombo = scope.getForm().findField('contact_id'); + addressbookContactCombo.setDisabled(hidden); + if (addressbookContactCombo.getValue() === '') { + addressbookContactCombo.setValue(null); + } } } }, { @@ -1279,6 +1354,18 @@ Tine.Admin.UserEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, { hidden: hidden ?? false, recordProxy: Tine.Admin.sharedAddressbookBackend, listeners: { + beforeselect: (combo, status, index) => { + Ext.MessageBox.confirm( + scope.app.i18n._('Confirm'), + scope.app.i18n._('Do you want to move the contact to this addressbook?'), + (btn) => { + if (btn === 'yes') { + combo.setValue(status.id); + } + }, + ); + return false; + }, specialkey: function(combo, e) { if (e.getKey() == e.TAB && ! e.shiftKey) { // move cursor to first input field (skip display fields) @@ -1290,7 +1377,43 @@ Tine.Admin.UserEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, { }, scope: scope } - }]; + }, { + xtype: 'addressbookcontactpicker', + disabled: scope.record.get('visibility') === 'hidden', + hidden: hidden ?? false, + name: 'contact_id', + filter: [{field: 'type', operator: 'equals', value: 'contact'}], + listeners: { + scope: scope, + beforeselect: (combo, status, index) => { + const addressbookContainerCombo = scope.getForm().findField('container_id'); + const selectedContainer = addressbookContainerCombo.selectedRecord; + + let msg = scope.app.i18n._('The selected contact will be updated') + ` :

${status.data.n_fileas}
` + + '
' + this.app.i18n.gettext('Saved in Addressbook') + ` : ${selectedContainer.data.name}
`; + + Ext.MessageBox.confirm( + scope.app.i18n._('Confirm'), + scope.app.i18n._(msg), + (btn) => { + if (btn === 'yes') { + combo.setValue(status.id); + if (scope.hasSmsAdapters) { + scope.loadSMSContactPhoneNumbers(status.data); + } + } + }, + ); + return false; + }, + select: (combo, status, index) => { + if (scope.hasSmsAdapters && status === '') { + scope.loadSMSContactPhoneNumbers(''); + } + }, + } + } + ]; } }); diff --git a/tine20/Tinebase/Application.php b/tine20/Tinebase/Application.php index 109508c599..a2ba4d49ff 100644 --- a/tine20/Tinebase/Application.php +++ b/tine20/Tinebase/Application.php @@ -685,6 +685,8 @@ public function removeApplicationAuxiliaryData(Tinebase_Model_Application $_appl ); foreach ($dataToDelete as $dataType => $info) { + Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Deleting ' . $dataType . ' for app' + . $_application->name . ' ...'); switch ($dataType) { case 'container': $count = Tinebase_Container::getInstance()->dropContainerByApplicationId($_application->getId()); diff --git a/tine20/Tinebase/Config.php b/tine20/Tinebase/Config.php index 4f1d281e80..9afca1562c 100644 --- a/tine20/Tinebase/Config.php +++ b/tine20/Tinebase/Config.php @@ -743,6 +743,8 @@ class Tinebase_Config extends Tinebase_Config_Abstract public const SMS = 'sms'; public const SMS_ADAPTERS = 'sms_adapters'; + public const SMS_MESSAGE_TEMPLATES = 'sms_message_templates'; + public const SMS_TEMPLATE_NEW_PASSWORD = 'sms_template_new_password'; /** * max username length @@ -3606,6 +3608,27 @@ class Tinebase_Config extends Tinebase_Config_Abstract self::CONTENT_CLASS => Tinebase_Model_Sms_AdapterConfigs::class, self::DEFAULT_STR => [], ], + self::SMS_MESSAGE_TEMPLATES => [ + self::LABEL => 'SMS Message Templates', //_('SMS Message Templates') + self::DESCRIPTION => 'SMS Message Templates', //_('SMS Message Templates') + self::TYPE => self::TYPE_OBJECT, + self::CLASSNAME => Tinebase_Config_Struct::class, + self::CONTENT => [ + self::SMS_TEMPLATE_NEW_PASSWORD => [ + //_('Template for SMS New Password') + self::LABEL => 'Template for SMS New Password', + //_('Template for SMS New Password with parameters: instanceName, password') + self::DESCRIPTION => 'Template for SMS New Password with parameters: instanceName, password', + self::TYPE => self::TYPE_STRING, + //_('Instance: {{ instanceName }} , new password: {{ password }}') + self::DEFAULT_STR => 'Instance: {{ instanceName }} , new password: {{ password }}', + self::CLIENTREGISTRYINCLUDE => true, + self::SETBYADMINMODULE => true, + self::SETBYSETUPMODULE => false, + ] + ], + self::DEFAULT_STR => [], + ], ], self::DEFAULT_STR => [], ], diff --git a/tine20/Tinebase/Controller.php b/tine20/Tinebase/Controller.php index 20657bf940..8115289b8a 100644 --- a/tine20/Tinebase/Controller.php +++ b/tine20/Tinebase/Controller.php @@ -150,11 +150,6 @@ public function login($loginName, $password, \Zend\Http\PhpEnvironment\Request $ Tinebase_Controller::getInstance()->changeUserAccount($roleChangeUserName); } - $loginEvent = new Tinebase_Event_User_Login(); - $loginEvent->password = $password; - $loginEvent->user = $user; - Tinebase_Event::fireEvent($loginEvent); - return true; } @@ -772,7 +767,6 @@ protected function _handleEvent(Tinebase_Event_Abstract $_eventObject) } } break; - case Tinebase_Event_User_Login::class: if (($userCtrl = Tinebase_User::getInstance()) instanceof Tinebase_User_Interface_SyncAble && Tinebase_Config::getInstance()->{Tinebase_Config::USERBACKEND}->{Tinebase_Config::SYNCOPTIONS}->{Tinebase_Config::SYNC_USER_OF_GROUPS} diff --git a/tine20/Tinebase/Core.php b/tine20/Tinebase/Core.php index 87cd9dc5c4..10198c4978 100644 --- a/tine20/Tinebase/Core.php +++ b/tine20/Tinebase/Core.php @@ -2363,9 +2363,16 @@ public static function getHttpClient($uri = null, $config = null) } } + $resolvedConfig = $config; + if (isset($config['adapter']) && is_callable($config['adapter']->writeBodyCallBack)) { + $adapterConfig = clone $config['adapter']; + unset($adapterConfig->writeBodyCallBack); + $resolvedConfig['adapter'] = $adapterConfig; + } + if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug( __METHOD__ . '::' . __LINE__ . ' Creating Zend_Http_Client for ' . $uri . ' with config: ' - . print_r($config, true)); + . print_r($resolvedConfig, true)); return new Zend_Http_Client($uri, $config); } diff --git a/tine20/Tinebase/Frontend/Json.php b/tine20/Tinebase/Frontend/Json.php index 91c6e639fb..0db6eb2a33 100644 --- a/tine20/Tinebase/Frontend/Json.php +++ b/tine20/Tinebase/Frontend/Json.php @@ -839,7 +839,10 @@ protected function _getUserRegistryData() $smtpConfig = $manageSmtpEmailUser ? Tinebase_EmailUser::getConfig(Tinebase_Config::SMTP, true) : []; - + $smsAdapterConfig = Tinebase_Config::getInstance()->{Tinebase_Config::SMS}->{Tinebase_Config::SMS_ADAPTERS}->{Tinebase_Model_Sms_AdapterConfigs::FLD_ADAPTER_CONFIGS} ?? []; + if ($smsAdapterConfig instanceof Tinebase_Record_RecordSet) { + $smsAdapterConfig = $smsAdapterConfig->toArray(); + } // be license class for setting some license registry data $license = Tinebase_License::getInstance(); @@ -866,6 +869,7 @@ protected function _getUserRegistryData() 'additionaldomains' => isset($smtpConfig['additionaldomains']) ? $smtpConfig['additionaldomains'] : '', 'allowExternalEmail' => ! $manageImapEmailUser || Tinebase_Config::getInstance()->get(Tinebase_Config::IMAP)->allowExternalEmail, 'smtpAliasesDispatchFlag' => Tinebase_EmailUser::smtpAliasesDispatchFlag(), + 'hasSmsAdapters' => count($smsAdapterConfig) > 0, ); if (Tinebase_Core::get(Tinebase_Core::SESSION)->encourage_mfa) { diff --git a/tine20/Tinebase/Model/FullUser.php b/tine20/Tinebase/Model/FullUser.php index 015cbfde1c..cac85c4eff 100644 --- a/tine20/Tinebase/Model/FullUser.php +++ b/tine20/Tinebase/Model/FullUser.php @@ -147,6 +147,11 @@ class Tinebase_Model_FullUser extends Tinebase_Model_User self::MODEL_NAME => Addressbook_Model_Contact::MODEL_NAME_PART, ], 'validators' => [Zend_Filter_Input::ALLOW_EMPTY => true], + self::UI_CONFIG => [ + 'recordEditPluginConfig' => [ + 'allowCreateNew' => true, + ], + ], ], 'accountLastLogin' => [ 'type' => 'datetime', diff --git a/tine20/Tinebase/Model/Sms/GenericHttpAdapter.php b/tine20/Tinebase/Model/Sms/GenericHttpAdapter.php index 821e4ca620..5cd8281b3d 100644 --- a/tine20/Tinebase/Model/Sms/GenericHttpAdapter.php +++ b/tine20/Tinebase/Model/Sms/GenericHttpAdapter.php @@ -58,6 +58,11 @@ class Tinebase_Model_Sms_GenericHttpAdapter extends Tinebase_Record_NewAbstract protected array $_httpClientConfig = []; + public function getHttpClientConfig() + { + return $this->_httpClientConfig; + } + public function setHttpClientConfig(array $_config): void { $this->_httpClientConfig = $_config; diff --git a/tine20/Tinebase/Sms.php b/tine20/Tinebase/Sms.php index 36e402c9e1..be0ffd380c 100644 --- a/tine20/Tinebase/Sms.php +++ b/tine20/Tinebase/Sms.php @@ -20,7 +20,7 @@ class Tinebase_Sms public static function send(Tinebase_Model_Sms_SendConfig $config): bool { if (!$config->{Tinebase_Model_Sms_SendConfig::FLD_ADAPTER_CONFIG}) { - // TODO get default sms adapter config or throw + throw new Tinebase_Exception_UnexpectedValue('sms send config needs to be set'); } return $config->{Tinebase_Model_Sms_SendConfig::FLD_ADAPTER_CONFIG}->send($config); diff --git a/tine20/Tinebase/User.php b/tine20/Tinebase/User.php index 4f7d73c36a..959ad2c0d0 100644 --- a/tine20/Tinebase/User.php +++ b/tine20/Tinebase/User.php @@ -919,6 +919,7 @@ public static function user2Contact($user, $contact = null) } $contact->account_id = $user->getId(); + $contact->type = Addressbook_Model_Contact::CONTACTTYPE_USER; return $contact; } diff --git a/tine20/Tinebase/js/widgets/ActivitiesPanel.js b/tine20/Tinebase/js/widgets/ActivitiesPanel.js index eb0bf0bc63..00eac8378c 100644 --- a/tine20/Tinebase/js/widgets/ActivitiesPanel.js +++ b/tine20/Tinebase/js/widgets/ActivitiesPanel.js @@ -129,7 +129,7 @@ Tine.widgets.activities.ActivitiesTabPanel = Ext.extend(Ext.Panel, { }, noteRenderer: function(note) { - var editDialog = this.findParentBy(function(c){return !!c.record}), + const editDialog = this.findParentBy(function(c){return !!c.record}), record = editDialog ? editDialog.record : {}, recordClass = Tine.Tinebase.data.RecordMgr.get(this.record_model) || Tine.Tinebase.data.RecordMgr.get(this.app + '_Model_' + this.record_model), appName = recordClass.getMeta('appName'), @@ -142,14 +142,13 @@ Tine.widgets.activities.ActivitiesTabPanel = Ext.extend(Ext.Panel, { if (recordClass) { Ext.each(recordClass.getFieldDefinitions(), function(field) { - var _ = window.lodash, - i18nLabel = field.label ? i18n._hidden(field.label) : field.name, - regexp = new RegExp(' (' + _.escapeRegExp(field.name) +'|' + _.escapeRegExp(i18nLabel) + `) \\((.*?) (->) ([^)]*)\\)`), - struct = regexp.exec(note), - label = struct && struct.length == 5 ? struct[1] : null, - oldValue = label ? struct[2] : null, - newValue = label ? struct[4] : null, - renderer = Tine.widgets.grid.RendererManager.get(appName, recordClass, field.name, Tine.widgets.grid.RendererManager.CATEGORY_GRIDPANEL); + const i18nLabel = field.label ? i18n._hidden(field.label) : field.name; + const renderer = Tine.widgets.grid.RendererManager.get(appName, recordClass, field.name, Tine.widgets.grid.RendererManager.CATEGORY_GRIDPANEL); + let regexp = new RegExp(' (' + _.escapeRegExp(field.name) +'|' + _.escapeRegExp(i18nLabel) + `) \\((.*?) (->) ([^)]*)\\)`); + let struct = regexp.exec(note); + let label = struct && struct.length === 5 ? struct[1] : null; + let oldValue = label ? struct[2] : null; + let newValue = label ? struct[4] : null; if (label) { if (['record', 'records'].indexOf(_.get(field, 'fieldDefinition.type')) < 0) { @@ -163,8 +162,8 @@ Tine.widgets.activities.ActivitiesTabPanel = Ext.extend(Ext.Panel, { regexp = new RegExp(' (' + _.escapeRegExp(field.name) +'|' + _.escapeRegExp(i18nLabel) + ') \\((.[^)])\\)'); struct = regexp.exec(note); - label = struct && struct.length == 3 ? struct[1] : null; - var value = label ? struct[2] : null; + label = struct && struct.length === 3 ? struct[1] : null; + const value = label ? struct[2] : null; note = note.replace(regexp, '
\u00A0\u2022\u00A0 ' + i18nLabel + ' (' + value + ')'); } diff --git a/tine20/Tinebase/translations/de.po b/tine20/Tinebase/translations/de.po index ad5f8e8814..63816e0960 100644 --- a/tine20/Tinebase/translations/de.po +++ b/tine20/Tinebase/translations/de.po @@ -1056,6 +1056,18 @@ msgstr "" msgid "SMS Adapter Configs" msgstr "" +msgid "SMS Message Templates" +msgstr "" + +msgid "Template for SMS New Password" +msgstr "Schablone für SMS Neues Passwort" + +msgid "Template for SMS New Password with parameters: instanceName, password" +msgstr "Schablone für SMS Neues Passwort mit Parameter: instanceName, password " + +msgid "Instance: {{ instanceName }} , new password: {{ password }}" +msgstr "Instanz: {{ instanceName }} , neues Passwort: {{ password }}" + msgid "User type" msgstr "Nutzer*innen Typ" diff --git a/tine20/Tinebase/translations/en.po b/tine20/Tinebase/translations/en.po index 3629ba333f..fe9bbf3dca 100644 --- a/tine20/Tinebase/translations/en.po +++ b/tine20/Tinebase/translations/en.po @@ -5,7 +5,6 @@ msgstr "" "PO-Revision-Date: 2008-07-29 21:14+0100\n" "Last-Translator: Cornelius Weiss \n" "Language-Team: Tine 2.0 Translators\n" -"Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -1040,6 +1039,18 @@ msgstr "SMS Config" msgid "SMS Adapter Configs" msgstr "SMS Adapter Configs" +msgid "SMS Message Templates" +msgstr "SMS Message Templates" + +msgid "Template for SMS New Password" +msgstr "Template for SMS New Password" + +msgid "Template for SMS New Password with parameters: instanceName, password" +msgstr "Template for SMS New Password with parameters: instanceName, password" + +msgid "Instance: {{ instanceName }} , new password: {{ password }}" +msgstr "Instance: {{ instanceName }} , new password: {{ password }}" + msgid "User type" msgstr "User type" diff --git a/tine20/Tinebase/translations/template.pot b/tine20/Tinebase/translations/template.pot index d891c2d118..abfbf2e927 100644 --- a/tine20/Tinebase/translations/template.pot +++ b/tine20/Tinebase/translations/template.pot @@ -1039,6 +1039,18 @@ msgstr "" msgid "SMS Adapter Configs" msgstr "" +msgid "SMS Message Templates" +msgstr "" + +msgid "Template for SMS New Password" +msgstr "" + +msgid "Template for SMS New Password with parameters: instanceName, password" +msgstr "" + +msgid "Instance: {{ instanceName }} , new password: {{ password }}" +msgstr "" + msgid "User type" msgstr ""