From 2d0a93100ce6911146cd593b36ece27589c041c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Sch=C3=BCle?= Date: Wed, 19 Feb 2025 12:50:15 +0100 Subject: [PATCH 1/2] feature(MatrixSynapseIntegrator/UserEditDialog): add matrix ID and active fields --- tine20/Admin/js/user/EditDialog.js | 6 +- .../Admin/userEditDialogRecordFormPlugin.js | 93 +++++++++++++++++++ .../js/elementIntegrator.js | 9 ++ .../MatrixSynapseIntegrator/js/package.json | 17 ++++ 4 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 tine20/MatrixSynapseIntegrator/js/Admin/userEditDialogRecordFormPlugin.js create mode 100644 tine20/MatrixSynapseIntegrator/js/elementIntegrator.js create mode 100644 tine20/MatrixSynapseIntegrator/js/package.json diff --git a/tine20/Admin/js/user/EditDialog.js b/tine20/Admin/js/user/EditDialog.js index 4082b9dabc0..b9091faa571 100644 --- a/tine20/Admin/js/user/EditDialog.js +++ b/tine20/Admin/js/user/EditDialog.js @@ -972,7 +972,7 @@ Tine.Admin.UserEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, { name: 'password_must_change', plugins: [this.mustChangeTriggerPlugin] }); - + var config = { xtype: 'tabpanel', deferredRender: false, @@ -985,6 +985,10 @@ Tine.Admin.UserEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, { border: false, frame: true, layout: 'hfit', + plugins: [{ + ptype: 'ux.itemregistry', + key: 'Admin-UserEditDialog-RecordForm' + }], items: [{ xtype: 'columnform', labelAlign: 'top', diff --git a/tine20/MatrixSynapseIntegrator/js/Admin/userEditDialogRecordFormPlugin.js b/tine20/MatrixSynapseIntegrator/js/Admin/userEditDialogRecordFormPlugin.js new file mode 100644 index 00000000000..204c1bace70 --- /dev/null +++ b/tine20/MatrixSynapseIntegrator/js/Admin/userEditDialogRecordFormPlugin.js @@ -0,0 +1,93 @@ +Promise.all([Tine.Tinebase.appMgr.isInitialised('MatrixSynapseIntegrator'), + Tine.Tinebase.ApplicationStarter.isInitialised()]).then(() => { + + if (!Tine.Tinebase.configManager.get('matrixDomain', 'MatrixSynapseIntegrator')) { + Tine.log.debug('MatrixSynapseIntegrator: matrixDomain not configured - skipping admin user edit dialog hook'); + return; + } + + // add panel with matrixid field + const mySubPanel = Ext.extend(Ext.form.FieldSet, { + layout: 'fit', + + initComponent: function() { + this.app = Tine.Tinebase.appMgr.get('MatrixSynapseIntegrator'); + this.title = this.app.i18n._('Matrix Integration'); + this.matrixIdField = new Ext.form.TextField({ + fieldLabel: this.app.i18n.gettext('Matrix ID'), + name: 'matrixId', + }); + this.matrixActiveField = new Ext.form.Checkbox({ + fieldLabel: this.app.i18n.gettext('Matrix Account Active'), + name: 'matrixActive', + listeners: { + 'check': function(checkbox, value) { + if (value) { + if (!this.matrixIdField.getValue()) { + this.setDefaultMatrixId(this.editDialog.record); + } + this.matrixIdField.enable(); + } else { + this.matrixIdField.disable(); + } + }, + scope: this + } + }); + + this.items = [{ + xtype: 'columnform', + labelAlign: 'top', + items: [[this.matrixActiveField, this.matrixIdField]] + }]; + + this.supr().initComponent.apply(this, arguments); + }, + + setDefaultMatrixId: function(record) { + const matrixDomain = Tine.Tinebase.configManager.get('matrixDomain', 'MatrixSynapseIntegrator'); + const userId = record.phantom ? '@{user.id}' : record.id; + this.matrixIdField.setValue(`${userId}:${matrixDomain}`); + }, + + onRecordLoad: async function(editDialog, record) { + if (record.phantom) { + // new record + this.setDefaultMatrixId(record); + this.matrixActiveField.setValue(true); + } else { + // get value from xprops + let xprops = record.get('xprops'); + xprops = Ext.isObject(xprops) ? xprops : {}; + if (xprops.matrixId) { + this.matrixIdField.setValue(xprops.matrixId); + } + if (xprops.matrixActive) { + this.matrixActiveField.setValue(xprops.matrixActive); + } else { + this.matrixIdField.disable(); + } + } + }, + + onRecordUpdate: function(editDialog, record) { + let xprops = record.get('xprops'); + xprops = Ext.isObject(xprops) ? xprops : {}; + xprops.matrixId = this.matrixIdField.getValue(); + xprops.matrixActive = this.matrixActiveField.getValue(); + }, + + onRender: function() { + this.supr().onRender.apply(this, arguments); + + if (!this.editDialog) { + this.editDialog = this.findParentBy(function (c) { + return c instanceof Tine.widgets.dialog.EditDialog + }); + } + this.editDialog.on('load', this.onRecordLoad, this); + this.editDialog.on('recordUpdate', this.onRecordUpdate, this); + } + }) + Ext.ux.ItemRegistry.registerItem('Admin-UserEditDialog-RecordForm', mySubPanel, 5); +}) diff --git a/tine20/MatrixSynapseIntegrator/js/elementIntegrator.js b/tine20/MatrixSynapseIntegrator/js/elementIntegrator.js new file mode 100644 index 00000000000..5b2de70fedd --- /dev/null +++ b/tine20/MatrixSynapseIntegrator/js/elementIntegrator.js @@ -0,0 +1,9 @@ +/* + * Tine 2.0 + * + * @license http://www.gnu.org/licenses/agpl.html AGPL Version 3 + * @author Cornelius Weiß + * @copyright Copyright (c) 2024 Metaways Infosystems GmbH (http://www.metaways.de) + */ + +import './Admin/userEditDialogRecordFormPlugin' diff --git a/tine20/MatrixSynapseIntegrator/js/package.json b/tine20/MatrixSynapseIntegrator/js/package.json new file mode 100644 index 00000000000..a8a649a1803 --- /dev/null +++ b/tine20/MatrixSynapseIntegrator/js/package.json @@ -0,0 +1,17 @@ +{ + "name": "MatrixSynapseIntegrator", + "version": "0.0.1", + "description": "Tine MatrixSynapseIntegrator extensions", + "main": "elementIntegrator.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "element", + "synapse", + "matrix", + "extension" + ], + "author": "Cornelius Weiß", + "license": "AGPL-3.0" +} From eb738eedf8d06da25d1203d597ffc8fae8f34b29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Sch=C3=BCle?= Date: Thu, 20 Feb 2025 11:49:59 +0100 Subject: [PATCH 2/2] feature(MatrixSynapseIntegrator/Corporal): manage user lifecycle via corporal see https://github.com/devture/matrix-corporal --- .../MatrixSynapseIntegrator/AllTests.php | 5 +- .../Backend/CorporalMock.php | 26 ++++ .../Controller/UserTests.php | 82 ++++++++++++ .../ControllerTests.php | 4 +- .../MatrixSynapseIntegrator | 1 - tine20/Felamimail/Controller.php | 5 +- .../Backend/Corporal.php | 118 ++++++++++++++++++ tine20/MatrixSynapseIntegrator/Config.php | 34 ++++- tine20/MatrixSynapseIntegrator/Controller.php | 24 +++- .../Controller/User.php | 97 ++++++++++++++ 10 files changed, 384 insertions(+), 12 deletions(-) create mode 100644 tests/tine20/MatrixSynapseIntegrator/Backend/CorporalMock.php create mode 100644 tests/tine20/MatrixSynapseIntegrator/Controller/UserTests.php delete mode 120000 tests/tine20/MatrixSynapseIntegrator/MatrixSynapseIntegrator create mode 100644 tine20/MatrixSynapseIntegrator/Backend/Corporal.php create mode 100644 tine20/MatrixSynapseIntegrator/Controller/User.php diff --git a/tests/tine20/MatrixSynapseIntegrator/AllTests.php b/tests/tine20/MatrixSynapseIntegrator/AllTests.php index 179d8325ccc..b3222efda90 100644 --- a/tests/tine20/MatrixSynapseIntegrator/AllTests.php +++ b/tests/tine20/MatrixSynapseIntegrator/AllTests.php @@ -4,7 +4,7 @@ * * @package MatrixSynapseIntegrator * @license http://www.gnu.org/licenses/agpl.html - * @copyright Copyright (c) 2020 Metaways Infosystems GmbH (http://www.metaways.de) + * @copyright Copyright (c) 2020-2025 Metaways Infosystems GmbH (http://www.metaways.de) * @author Paul Mehrer */ @@ -15,13 +15,12 @@ */ class MatrixSynapseIntegrator_AllTests { - - public static function suite () { $suite = new PHPUnit\Framework\TestSuite('All MatrixSynapseIntegrator tests'); $suite->addTestSuite(MatrixSynapseIntegrator_ControllerTests::class); + $suite->addTestSuite(MatrixSynapseIntegrator_Controller_UserTests::class); return $suite; } diff --git a/tests/tine20/MatrixSynapseIntegrator/Backend/CorporalMock.php b/tests/tine20/MatrixSynapseIntegrator/Backend/CorporalMock.php new file mode 100644 index 00000000000..0a7bd00fc28 --- /dev/null +++ b/tests/tine20/MatrixSynapseIntegrator/Backend/CorporalMock.php @@ -0,0 +1,26 @@ +//www.gnu.org/licenses/agpl.html AGPL Version 3 + * @copyright Copyright (c) 2025 Metaways Infosystems GmbH (http://www.metaways.de) + * @author Philipp Schüle + */ + +/** + * MatrixSynapseIntegrator Backend Mock + * + * @package MatrixSynapseIntegrator + * @subpackage Backend + */ +class MatrixSynapseIntegrator_Backend_CorporalMock extends MatrixSynapseIntegrator_Backend_Corporal +{ + public function push(Tinebase_Model_FullUser $user): bool + { + $this->_policy = $this->_getPolicy($user); + return true; + } +} diff --git a/tests/tine20/MatrixSynapseIntegrator/Controller/UserTests.php b/tests/tine20/MatrixSynapseIntegrator/Controller/UserTests.php new file mode 100644 index 00000000000..0ff6115eb13 --- /dev/null +++ b/tests/tine20/MatrixSynapseIntegrator/Controller/UserTests.php @@ -0,0 +1,82 @@ + + */ + +class MatrixSynapseIntegrator_Controller_UserTests extends TestCase +{ + public function setUp(): void + { + parent::setUp(); + + MatrixSynapseIntegrator_Controller_User::getInstance()->setBackend(new MatrixSynapseIntegrator_Backend_CorporalMock()); + } + + public function tearDown(): void + { + parent::tearDown(); + + MatrixSynapseIntegrator_Controller_User::destroyInstance(); + } + + public function testCreateUser(): Tinebase_Model_FullUser + { + $user = $this->_createTestUser([ + 'xprops' => [ + MatrixSynapseIntegrator_Config::USER_XPROP_MATRIX_ID => '@{user.id}:matrix.domain', + MatrixSynapseIntegrator_Config::USER_XPROP_MATRIX_ACTIVE => true, + ] + ]); + + self::assertTrue($user->xprops()[MatrixSynapseIntegrator_Config::USER_XPROP_MATRIX_ACTIVE]); + self::assertEquals('@' . $user->getId() . ':matrix.domain', + $user->xprops()[MatrixSynapseIntegrator_Config::USER_XPROP_MATRIX_ID]); + + // assert corporal policy json + $backend = MatrixSynapseIntegrator_Controller_User::getInstance()->getBackend(); + $policy = $backend->getPushedPolicy(); + self::assertArrayHasKey('users', $policy); + self::assertCount(1, $policy['users']); + self::assertArrayHasKey('authType', $policy['users'][0]); + self::assertEquals('sha1', $policy['users'][0]['authType']); + $userData = $policy['users'][0]; + self::assertEquals($user->xprops()[MatrixSynapseIntegrator_Config::USER_XPROP_MATRIX_ID], $userData['id']); + self::assertEquals($user->accountDisplayName, $userData['displayName']); + return $user; + } + + public function testUpdateUser() + { + $user = $this->testCreateUser(); + $user->xprops()[MatrixSynapseIntegrator_Config::USER_XPROP_MATRIX_ACTIVE] = false; + $updatedUser = Admin_Controller_User::getInstance()->update($user); + self::assertFalse($updatedUser->xprops()[MatrixSynapseIntegrator_Config::USER_XPROP_MATRIX_ACTIVE]); + $this->_assertInactiveUserInPolicy(); + } + + protected function _assertInactiveUserInPolicy() + { + $backend = MatrixSynapseIntegrator_Controller_User::getInstance()->getBackend(); + $policy = $backend->getPushedPolicy(); + self::assertArrayHasKey('users', $policy); + self::assertCount(1, $policy['users']); + self::assertArrayHasKey('active', $policy['users'][0]); + self::assertFalse($policy['users'][0]['active']); + } + + public function testDeleteUser() + { + $user = $this->testCreateUser(); + // user deletion need the confirmation header + Admin_Controller_User::getInstance()->setRequestContext(['confirm' => true]); + Admin_Controller_User::getInstance()->delete([$user->getId()]); + $this->_assertInactiveUserInPolicy(); + } +} diff --git a/tests/tine20/MatrixSynapseIntegrator/ControllerTests.php b/tests/tine20/MatrixSynapseIntegrator/ControllerTests.php index 2d0ec0cd8cb..d42593bb38f 100644 --- a/tests/tine20/MatrixSynapseIntegrator/ControllerTests.php +++ b/tests/tine20/MatrixSynapseIntegrator/ControllerTests.php @@ -31,7 +31,7 @@ class MatrixSynapseIntegrator_ControllerTests extends TestCase public function setUp(): void -{ + { parent::setUp(); $this->_oldRequest = Tinebase_Core::getContainer()->get(RequestInterface::class); @@ -41,7 +41,7 @@ public function setUp(): void } public function tearDown(): void -{ + { Tinebase_Core::getContainer()->set(RequestInterface::class, $this->_oldRequest); if ($this->_originalTestUser) { diff --git a/tests/tine20/MatrixSynapseIntegrator/MatrixSynapseIntegrator b/tests/tine20/MatrixSynapseIntegrator/MatrixSynapseIntegrator deleted file mode 120000 index cc31fc3839a..00000000000 --- a/tests/tine20/MatrixSynapseIntegrator/MatrixSynapseIntegrator +++ /dev/null @@ -1 +0,0 @@ -./../../tine20/vendor/metaways/tine20-matrixsynapseintegrator/tests/MatrixSynapseIntegrator \ No newline at end of file diff --git a/tine20/Felamimail/Controller.php b/tine20/Felamimail/Controller.php index 4f5bf8b1174..a8279009b80 100644 --- a/tine20/Felamimail/Controller.php +++ b/tine20/Felamimail/Controller.php @@ -41,9 +41,10 @@ class Felamimail_Controller extends Tinebase_Controller_Event private static $_instance = NULL; /** - * constructor (get current user) + * constructor */ - private function __construct() { + private function __construct() + { } /** diff --git a/tine20/MatrixSynapseIntegrator/Backend/Corporal.php b/tine20/MatrixSynapseIntegrator/Backend/Corporal.php new file mode 100644 index 00000000000..f0e9df7f407 --- /dev/null +++ b/tine20/MatrixSynapseIntegrator/Backend/Corporal.php @@ -0,0 +1,118 @@ +//www.gnu.org/licenses/agpl.html AGPL Version 3 + * @copyright Copyright (c) 2025 Metaways Infosystems GmbH (http://www.metaways.de) + * @author Philipp Schüle + */ + +/** + * MatrixSynapseIntegrator Backend + * + * @package MatrixSynapseIntegrator + * @subpackage Backend + */ +class MatrixSynapseIntegrator_Backend_Corporal +{ + protected array $_policy = []; + + protected const CORPORAL_ENDPOINT = '_matrix/corporal/policy'; + + public function push(Tinebase_Model_FullUser $user): bool + { + $this->_policy = $this->_getPolicy($user); + $this->pushPolicyToCorporal($this->_policy); + + return true; + } + + protected function _getHttpClient(): Zend_Http_Client + { + $matrixHomeServer = MatrixSynapseIntegrator_Config::getInstance()->get( + MatrixSynapseIntegrator_Config::HOME_SERVER_URL); + $corporalUrl = $matrixHomeServer . '/' . self::CORPORAL_ENDPOINT; + + return Tinebase_Core::getHttpClient($corporalUrl); + } + + /** + * @see https://github.com/devture/matrix-corporal/blob/master/docs/http-api.md#policy-submission-endpoint + * + * @param array $policy + * @return bool + * @throws Zend_Http_Client_Exception + */ + protected function pushPolicyToCorporal(array $policy): bool + { + $client = $this->_getHttpClient(); + $client->setHeaders([ + 'Authorization' => 'Bearer ' . MatrixSynapseIntegrator_Config::getInstance()->get( + MatrixSynapseIntegrator_Config::CORPORAL_SHARED_AUTH_TOKEN), + 'Content-Type' => 'application/json', + ]); + $client->setRawData(json_encode($policy)); + + if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) { + Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ + . ' Pushing policy to ' . $client->getUri()); + } + + $client->request(Zend_Http_Client::PUT); + + $response = $client->getLastResponse(); + if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) { + Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ + . ' Response status: ' . $response->getStatus()); + } + + return $response->isSuccessful(); + } + + protected function _getPolicy(Tinebase_Model_FullUser $user): array + { + // TODO allow to configure defaults/flags + + return [ + "schemaVersion" => 2, + "flags" => [ + "allowCustomUserDisplayNames" => true, + "allowCustomUserAvatars" => true, + "allowCustomPassthroughUserPasswords" => true, + "forbidRoomCreation" => false, + "forbidEncryptedRoomCreation" => false, + "forbidUnencryptedRoomCreation" => false + ], + "users" => [ + $this->_getUserPolicy($user) + ], + ]; + } + + protected function _getUserPolicy(Tinebase_Model_FullUser $user): array + { + + return [ + "id" => $user->xprops()[MatrixSynapseIntegrator_Config::USER_XPROP_MATRIX_ID], + "active" => $user->is_deleted === 0 && $user->xprops()[MatrixSynapseIntegrator_Config::USER_XPROP_MATRIX_ACTIVE], + "displayName" => $user->accountDisplayName, + "forbidRoomCreation" => false, + "authType" => "sha1", + "authCredential" => Tinebase_User::getInstance()->getPasswordHashByLoginname($user->accountLoginName), +// "authType" => "plain", +// "avatarUri" => "https://example.com/john.jpg", +// "joinedRooms" => [ +// {"roomId": "!roomA:example.com", "powerLevel": 0}, +// {"roomId": "!roomB:example.com", "powerLevel": 50} +// ], + ]; + } + + public function getPushedPolicy(): array + { + return $this->_policy; + } +} diff --git a/tine20/MatrixSynapseIntegrator/Config.php b/tine20/MatrixSynapseIntegrator/Config.php index 30c00e3f31a..fb394f5a1e4 100644 --- a/tine20/MatrixSynapseIntegrator/Config.php +++ b/tine20/MatrixSynapseIntegrator/Config.php @@ -1,4 +1,5 @@ - * @copyright Copyright (c) 2020 Metaways Infosystems GmbH (http://www.metaways.de) + * @copyright Copyright (c) 2020-2025 Metaways Infosystems GmbH (http://www.metaways.de) */ /** @@ -18,9 +19,14 @@ */ class MatrixSynapseIntegrator_Config extends Tinebase_Config_Abstract { - const APP_NAME = 'MatrixSynapseIntegrator'; + public const APP_NAME = 'MatrixSynapseIntegrator'; + + public const MATRIX_DOMAIN = 'matrixDomain'; + public const HOME_SERVER_URL = 'homeServerUrl'; + public const CORPORAL_SHARED_AUTH_TOKEN = 'corporalSharedAuthToken'; - const MATRIX_DOMAIN = 'matrixDomain'; + public const USER_XPROP_MATRIX_ID = 'matrixId'; + public const USER_XPROP_MATRIX_ACTIVE = 'matrixActive'; /** * (non-PHPdoc) @@ -43,6 +49,28 @@ class MatrixSynapseIntegrator_Config extends Tinebase_Config_Abstract self::SETBYADMINMODULE => true, self::SETBYSETUPMODULE => true, ], + self::HOME_SERVER_URL => [ + //_('Home Server URL') + self::LABEL => 'Home Server URL', + //_('Home Server URL') + self::DESCRIPTION => 'Home Server URL', + self::TYPE => self::TYPE_STRING, + self::CLIENTREGISTRYINCLUDE => true, + self::SETBYADMINMODULE => true, + self::SETBYSETUPMODULE => true, + self::DEFAULT_STR => 'https://matrix.mydomain', + ], + self::CORPORAL_SHARED_AUTH_TOKEN => [ + //_('Corporal Shared Auth Token') + self::LABEL => 'Corporal Shared Auth Token', + //_('Corporal Shared Auth Token') + self::DESCRIPTION => 'Corporal Shared Auth Token', + self::TYPE => self::TYPE_STRING, + self::CLIENTREGISTRYINCLUDE => false, + self::SETBYADMINMODULE => false, + self::SETBYSETUPMODULE => false, + self::DEFAULT_STR => 'abc', + ], ]; /** diff --git a/tine20/MatrixSynapseIntegrator/Controller.php b/tine20/MatrixSynapseIntegrator/Controller.php index aa6bc636012..7493346335f 100644 --- a/tine20/MatrixSynapseIntegrator/Controller.php +++ b/tine20/MatrixSynapseIntegrator/Controller.php @@ -137,7 +137,9 @@ public function identity() } } - if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' response: ' . print_r($result, true)); + if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) { + Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' response: ' . print_r($result, true)); + } $response = (new \Zend\Diactoros\Response())->withHeader('Content-Type', 'application/json'); $response->getBody()->write(json_encode(isset($bodyMsg['lookup']['medium']) ? @@ -265,4 +267,24 @@ public function getMatrixDomain() return MatrixSynapseIntegrator_Config::getInstance()->{MatrixSynapseIntegrator_Config::MATRIX_DOMAIN} ?: Tinebase_Core::getUrl(Tinebase_Core::GET_URL_HOST); } + + /** + * implement logic for each controller in this function + * + * @param Tinebase_Event_Abstract $_eventObject + */ + protected function _handleEvent(Tinebase_Event_Abstract $_eventObject) + { + switch (get_class($_eventObject)) { + case Admin_Event_AddAccount::class: + MatrixSynapseIntegrator_Controller_User::getInstance()->create($_eventObject->account); + break; + case Admin_Event_UpdateAccount::class: + MatrixSynapseIntegrator_Controller_User::getInstance()->update($_eventObject->account, $_eventObject->oldAccount); + break; + case Tinebase_Event_User_DeleteAccount::class: + MatrixSynapseIntegrator_Controller_User::getInstance()->delete($_eventObject->account); + break; + } + } } diff --git a/tine20/MatrixSynapseIntegrator/Controller/User.php b/tine20/MatrixSynapseIntegrator/Controller/User.php new file mode 100644 index 00000000000..c9c05eaa6b2 --- /dev/null +++ b/tine20/MatrixSynapseIntegrator/Controller/User.php @@ -0,0 +1,97 @@ + + */ + +/** + * MatrixSynapseIntegrator Controller + * + * @package MatrixSynapseIntegrator + * @subpackage Controller + */ +class MatrixSynapseIntegrator_Controller_User extends Tinebase_Controller_Abstract +{ + use Tinebase_Controller_SingletonTrait; + + protected ?MatrixSynapseIntegrator_Backend_Corporal $_backend = null; + + public function setBackend(?MatrixSynapseIntegrator_Backend_Corporal $backend = null): MatrixSynapseIntegrator_Backend_Corporal + { + return $this->_backend = $backend ?: new MatrixSynapseIntegrator_Backend_Corporal(); + } + + public function getBackend(): MatrixSynapseIntegrator_Backend_Corporal + { + return $this->_backend ?: $this->setBackend(); + } + + public function create(Tinebase_Model_FullUser $user): bool + { + if ($this->_inactiveMatrixUser($user)) { + return true; + } + + $this->_replaceUserIdXprop($user); + $this->getBackend()->push($user); + + return true; + } + + protected function _inactiveMatrixUser(Tinebase_Model_FullUser $user): bool + { + $matrixId = $user->xprops()[MatrixSynapseIntegrator_Config::USER_XPROP_MATRIX_ID] ?? null; + $active = $user->xprops()[MatrixSynapseIntegrator_Config::USER_XPROP_MATRIX_ACTIVE] ?? false; + return (! $active || empty($matrixId)); + } + + public function update(Tinebase_Model_FullUser $user, Tinebase_Model_FullUser $olduser): bool + { + $matrixId = $user->xprops()[MatrixSynapseIntegrator_Config::USER_XPROP_MATRIX_ID] ?? null; + $active = $user->xprops()[MatrixSynapseIntegrator_Config::USER_XPROP_MATRIX_ACTIVE] ?? false; + $oldMatrixId = $olduser->xprops()[MatrixSynapseIntegrator_Config::USER_XPROP_MATRIX_ID] ?? null; + $oldActive = $olduser->xprops()[MatrixSynapseIntegrator_Config::USER_XPROP_MATRIX_ACTIVE] ?? false; + + if ($active === $oldActive && $matrixId == $oldMatrixId) { + return true; + } + $this->getBackend()->push($user); + return true; + } + + public function delete(Tinebase_Model_FullUser $user): bool + { + if ($this->_inactiveMatrixUser($user)) { + return true; + } + + $user->xprops()[MatrixSynapseIntegrator_Config::USER_XPROP_MATRIX_ACTIVE] = false; + $this->getBackend()->push($user); + return true; + } + + /** + * update xprop - replace user id + * + * @param Tinebase_Model_FullUser $user + * @return void + * @throws Tinebase_Exception_InvalidArgument + * @throws Tinebase_Exception_Record_Validation + * @throws Tinebase_Exception_SystemGeneric + */ + protected function _replaceUserIdXprop(Tinebase_Model_FullUser $user): void + { + $matrixId = $user->xprops()[MatrixSynapseIntegrator_Config::USER_XPROP_MATRIX_ID]; + if (str_contains($matrixId, '{user.id}')) { + $user->xprops()[MatrixSynapseIntegrator_Config::USER_XPROP_MATRIX_ID] = str_replace('{user.id}', + $user->getId(), $matrixId); + Tinebase_User::getInstance()->updateUserInSqlBackend($user); + } + } +}