MDL-77917 communication_matrix: Support server API versions
This commit brings in support for multiple versions of the Matrix
specification.
A Matrix server is compromised of a number of individually versioned API
endpoints, for example:
/_matrix/client/v3/createRoom
/_matrix/client/v3/rooms/:roomid/joined_members
/_matrix/media/v1/create
The combination of a large number of these individually versioned
endpoints forms a Matrix Specification version.
For example:
* the /_matrix/media/v1/create endpoint was created for version 1.7 of the
specification, and does not exist in earlier versions.
* in the future a new behaviour or parameter may be created for the
`createRoom` endpoint and a new endpoint created at:
/_matrix/client/v4/createRoom
A single server can support multiple versions of the Matrix
specification. The server declares the versions of the specification
that it supports using a non-versioned endpoint at
`/_matrix/client/versions`.
As a Matrix client, Moodle should:
* query the server version endpoint
* determine the combination of mutually supported Matrix specification
versions
* create a client instance of the highest-supported version of the
specification.
For example, if Moodle (Matrix client) and a remote server have the
following support:
```
Moodle: 1.1 1.2 1.3 1.4 1.5 1.6 1.7
Server: r0 1.1 1.2 1.3 1.4 1.5 1.6
```
The versions in common are 1.1 through 1.6, and version 1.6 would be
chosen.
To avoid duplication and allow for support of future features more
easily, the Moodle client is written as:
* a set of classes named `v1p1` through `v1p7` (currently) which extend
the `matrix_client` abstract class; and
* a set if PHP traits which provide the implementation for individual
versioned endpoints.
Each client version then imports any relevant traits which are present
in that version of the Matrix Specification. For example versions 1.1 to
1.6 do _not_ have the `/_matrix/media/v1/create` endpoint so they do not
import this trait. This trait was introduced in version 1.7, so the
trait is included from that version onwards.
In the future, if an endpoint is created which conflicts with an
existing endpoint, then it would be easy to create a new client version
which uses the existing common traits, and adds the new trait.
Each endpoint is written using a `command` class which extends the
Guzzle implementation of the PSR-7 Request interface. This command
class adds support for easy creation of:
* path parameters within the URI
* query parameters
* body parameters
This is done to avoid complex patterns of Request creation which are
repeated for every client endpoint.
This commit is contained in:
+108
-15
@@ -84,6 +84,62 @@ class api {
|
||||
return new self($component, $instancetype, $instanceid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload in the internal instance data.
|
||||
*/
|
||||
public function reload(): void {
|
||||
$this->communication = processor::load_by_instance(
|
||||
$this->component,
|
||||
$this->instancetype,
|
||||
$this->instanceid,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the underlying communication processor object.
|
||||
*
|
||||
* @return processor
|
||||
*/
|
||||
public function get_processor(): processor {
|
||||
return $this->communication;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the room provider.
|
||||
*
|
||||
* @return \core_communication\room_chat_provider
|
||||
*/
|
||||
public function get_room_provider(): \core_communication\room_chat_provider {
|
||||
return $this->communication->get_room_provider();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the user provider.
|
||||
*
|
||||
* @return \core_communication\user_provider
|
||||
*/
|
||||
public function get_user_provider(): \core_communication\user_provider {
|
||||
return $this->communication->get_user_provider();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the room user provider.
|
||||
*
|
||||
* @return \core_communication\room_user_provider
|
||||
*/
|
||||
public function get_room_user_provider(): \core_communication\room_user_provider {
|
||||
return $this->communication->get_room_user_provider();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the form provider.
|
||||
*
|
||||
* @return \core_communication\form_provider
|
||||
*/
|
||||
public function get_form_provider(): \core_communication\form_provider {
|
||||
return $this->communication->get_form_provider();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the communication api is enabled.
|
||||
*/
|
||||
@@ -196,25 +252,39 @@ class api {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the avatar file.
|
||||
*
|
||||
* @return null|\stored_file
|
||||
*/
|
||||
public function get_avatar(): ?\stored_file {
|
||||
$filename = $this->communication->get_avatar_filename();
|
||||
if ($filename === null) {
|
||||
return null;
|
||||
}
|
||||
$fs = get_file_storage();
|
||||
$args = (array) $this->get_avatar_filerecord($filename);
|
||||
return $fs->get_file(...$args) ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the avatar file record for the avatar for filesystem.
|
||||
*
|
||||
* @param string $filename The filename of the avatar
|
||||
* @return stdClass
|
||||
*/
|
||||
public function get_avatar_filerecord(string $filename): stdClass {
|
||||
protected function get_avatar_filerecord(string $filename): stdClass {
|
||||
return (object) [
|
||||
'contextid' => \context_system::instance()->id,
|
||||
'contextid' => \core\context\system::instance()->id,
|
||||
'component' => 'core_communication',
|
||||
'filearea' => 'avatar',
|
||||
'filename' => $filename,
|
||||
'filepath' => '/',
|
||||
'itemid' => $this->communication->get_id(),
|
||||
'filepath' => '/',
|
||||
'filename' => $filename,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Get the avatar file.
|
||||
*
|
||||
* If null is set, then delete the old area file and set the avatarfilename to null.
|
||||
@@ -229,8 +299,8 @@ class api {
|
||||
return false;
|
||||
}
|
||||
|
||||
$currentfilerecord = $this->communication->get_avatar();
|
||||
if ($avatar && !empty($currentfilerecord)) {
|
||||
$currentfilerecord = $this->get_avatar();
|
||||
if ($avatar && $currentfilerecord) {
|
||||
$currentfilehash = $currentfilerecord->get_contenthash();
|
||||
$updatedfilehash = $avatar->get_contenthash();
|
||||
|
||||
@@ -240,7 +310,7 @@ class api {
|
||||
}
|
||||
}
|
||||
|
||||
$context = \context_system::instance();
|
||||
$context = \core\context\system::instance();
|
||||
|
||||
$fs = get_file_storage();
|
||||
$fs->delete_area_files(
|
||||
@@ -266,6 +336,15 @@ class api {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper to fetch the room name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_room_name(): string {
|
||||
return $this->communication->get_room_name();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the form data if the data is already available.
|
||||
*
|
||||
@@ -344,20 +423,33 @@ class api {
|
||||
* @param \stdClass|null $instance The actual instance object
|
||||
*/
|
||||
public function update_room(
|
||||
string $selectedprovider,
|
||||
string $communicationroomname,
|
||||
?string $selectedprovider = null,
|
||||
?string $communicationroomname = null,
|
||||
?\stored_file $avatar = null,
|
||||
?\stdClass $instance = null,
|
||||
): void {
|
||||
|
||||
// Existing object found, let's update the communication record and associated actions.
|
||||
if ($this->communication !== null) {
|
||||
// Get the previous data to compare for update.
|
||||
$previousroomname = $this->communication->get_room_name();
|
||||
$previousprovider = $this->communication->get_provider();
|
||||
if ($previousprovider === $selectedprovider) {
|
||||
// If the provider is the same, unset it.
|
||||
$selectedprovider = null;
|
||||
}
|
||||
|
||||
// Update communication record.
|
||||
$this->communication->update_instance($selectedprovider, $communicationroomname);
|
||||
$previousroomname = $this->communication->get_room_name();
|
||||
if ($previousroomname === $communicationroomname) {
|
||||
// If the room name is the same, we don't need to update the room.
|
||||
$communicationroomname = null;
|
||||
}
|
||||
|
||||
if ($selectedprovider !== null || $communicationroomname !== null) {
|
||||
// Something to update. Update communication record.
|
||||
$this->communication->update_instance(
|
||||
provider: $selectedprovider,
|
||||
roomname: $communicationroomname,
|
||||
);
|
||||
}
|
||||
|
||||
// Update provider record from form data.
|
||||
if ($instance !== null) {
|
||||
@@ -365,7 +457,8 @@ class api {
|
||||
}
|
||||
|
||||
// Update the avatar.
|
||||
$imageupdaterequired = $this->set_avatar($avatar);
|
||||
// If the value is `null`, then unset the avatar.
|
||||
$this->set_avatar($avatar);
|
||||
|
||||
// If the provider is none, we don't need to do anything from room point of view.
|
||||
if ($this->communication->get_provider() === processor::PROVIDER_NONE) {
|
||||
|
||||
Reference in New Issue
Block a user