Skip to content

Commit 943738d

Browse files
safatshahindavewoloszyn
authored andcommitted
Plugintype and API docs for SMS subsystem
Co-authored-by: David Woloszyn <[email protected]>
1 parent 0738248 commit 943738d

File tree

4 files changed

+468
-6
lines changed

4 files changed

+468
-6
lines changed

docs/apis/plugintypes/sms/index.md

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
---
2+
title: SMS gateway
3+
tags:
4+
- SMS
5+
- Gateway
6+
- Notification
7+
---
8+
9+
<Since version="4.5" issueNumber="MDL-83406" />
10+
11+
SMS gateway plugins allow you to create SMS gateway providers.
12+
Providers are an interface between the [SMS API](/apis/subsystems/sms/index.md) and an external SMS provider (for example Amazon Web Services).
13+
This allows for the sending of SMS notifications to users from your Moodle instance.
14+
15+
:::info Example
16+
17+
You can set up Multi-Factor Authentication (MFA)) in Moodle and choose 'AWS' as your SMS gateway provider.
18+
19+
This enables users to receive SMS notifications as part of the authentication process.
20+
21+
:::
22+
23+
## File structure
24+
25+
SMS gateway plugins are located in the `/sms/gateway` directory. A plugin should not include any custom files outside its own plugin folder.
26+
27+
Each plugin is placed in a separate subdirectory and consists of a number of mandatory files and any other files the developer is going to use. See the [common plugin files](/apis/commonfiles/index.mdx) documentation for other files which may be useful in your plugin.
28+
29+
<details>
30+
<summary>The directory layout for the `smsgateway` plugin.</summary>
31+
32+
```console
33+
sms/gateway/example
34+
├── classes
35+
│   ├── gateway.php
36+
│   ├── hook_listener.php
37+
│   └── privacy
38+
│   └── provider.php
39+
├── db
40+
│ └── hooks.php
41+
├── lang
42+
│   └── en
43+
│   └── smsgateway_example.php
44+
├── settings.php
45+
└── version.php
46+
```
47+
48+
</details>
49+
50+
## Key files
51+
52+
There are a number of key files within the SMS gateway plugin which will need to be configured for correct functionality.
53+
54+
- `gateway.php`
55+
- `hook_listener.php`
56+
57+
### gateway.php
58+
59+
Each plugin must create a class called `gateway` which extends the `\core_sms\gateway` class.
60+
The SMS API will use the extended methods from this class.
61+
62+
```php title="Implementing the base SMS gateway"
63+
<?php
64+
namespace smsgateway_aws;
65+
66+
use smsgateway_aws\local\service\aws_sns;
67+
68+
class gateway extends \core_sms\gateway {
69+
#[\Override]
70+
public function send(
71+
message $message,
72+
): message {
73+
// Sample code to send an SMS message.
74+
$config = (object) json_decode(
75+
$awsconfig,
76+
true,
77+
512,
78+
JSON_THROW_ON_ERROR,
79+
);
80+
$class = $this->get_gateway_service($config);
81+
$recipientnumber = manager::format_number(
82+
phonenumber: $message->recipientnumber,
83+
countrycode: isset($config->countrycode) ?? null,
84+
);
85+
86+
$status = call_user_func(
87+
[$class, 'send_sms_message'],
88+
$message->content,
89+
$recipientnumber,
90+
$config,
91+
);
92+
93+
return $message->with(
94+
status: $status,
95+
);
96+
}
97+
98+
private function get_gateway_service(\stdClass $config): string {
99+
return match ($config->gateway) {
100+
'aws_sns' => aws_sns::class,
101+
default => throw new moodle_exception("Unknown Message Handler {$config->gateway}"),
102+
};
103+
}
104+
105+
#[\Override]
106+
public function get_send_priority(message $message): int {
107+
return 50;
108+
}
109+
}
110+
111+
```
112+
113+
### hook_listener.php
114+
115+
[Hooks](/apis/core/hooks/index.md) can be dispatched from the SMS API which the plugin can then listened to.
116+
It is necessary for plugins developers to assess these hooks and implement accordingly.
117+
118+
#### after_sms_gateway_form_hook
119+
120+
This hook will allow plugins to add required form fields to assist users in configuring their SMS gateway.
121+
122+
```php title="Listener method for after_sms_gateway_form_hook"
123+
public static function set_form_definition_for_aws_sms_gateway(after_sms_gateway_form_hook $hook): void {
124+
if ($hook->plugin !== 'smsgateway_example') {
125+
return;
126+
}
127+
128+
$gateways = [
129+
'smsgateway_example' => get_string('list', 'smsgateway_example'),
130+
];
131+
$mform->addElement(
132+
'select',
133+
'gateway',
134+
get_string('gateway', 'smsgateway_example'),
135+
$gateways,
136+
);
137+
}
138+
139+
```
140+
141+
:::info
142+
143+
For an example of a production plugin example, see the [AWS SMS Gateway plugin](https://github.com/moodle/moodle/tree/main/sms/gateway/aws).
144+
145+
:::

docs/apis/subsystems/sms/index.md

Lines changed: 89 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,24 @@
11
---
22
title: SMS API
3+
tags:
4+
- SMS
35
---
46

57
<Since version="4.5" issueNumber="MDL-79808" />
68

7-
The SMS API lets you send SMS messages using configured gateways, fetch messages that were previously sent, and check on their status.
9+
The SMS API allows developers to implement SMS-related features into their plugins.
10+
The subsystem contains an SMS Manager class `\core_sms\manager` which facilitates the actions performed by the API.
11+
12+
Some of the actions made possible are:
13+
14+
- Sending messages
15+
- Fetching messages
16+
- Checking the status of a message
17+
- Getting SMS gateways.
18+
19+
Currently, the design of the SMS API features the following plugin types:
20+
21+
- [SMS gateway](/apis/plugintypes/sms/index.md)
822

923
## Sending an SMS
1024

@@ -25,12 +39,24 @@ $message = \core\di::get(\core_sms\manager::class)
2539

2640
:::info Message lengths
2741

28-
A single SMS sent by the API may consist of up to 480 UTF-8 characters. It is up to the message _gateway_ plugin to determine how this message is sent to the recipient.
42+
A single SMS sent by the API may consist of up to 480 UTF-8 characters. It is up to the message _gateway plugin_ to determine how this message is sent to the recipient.
2943

3044
Any message longer than the maximum length will be immediately rejected.
3145

3246
:::
3347

48+
### Parameter consideration while sending messages
49+
50+
When sending a message it's important to add the correct `component` (for example `tool_mfa`) and `messagetype` (for example `mfa code`) for record keeping purposes.
51+
52+
One component can have many different types of messages and those types should be clearly mentioned while sending the messages so that they are clear in reporting and other locations.
53+
54+
:::info
55+
56+
In future reporting will be available for messages status. See MDL-80963 for further information
57+
58+
:::
59+
3460
### Sending messages containing sensitive information
3561

3662
When sending a message containing something like a 2FA login token, you should make use of the `issensitive` flag.
@@ -39,9 +65,25 @@ Passing this flag prevents the SMS subsystem from storing the content of the mes
3965

4066
The `send()` method return an instance of `\core_sms\message` which can be used to check on the message status.
4167

68+
:::warning
69+
70+
Messages containing sensitive information cannot be sent asynchronously.
71+
72+
Sensitive content is not persisted to the database and is therefore not available in a separate PHP process.
73+
74+
:::
75+
76+
:::info Availability of asynchronous message handling
77+
78+
The ability to send messages asynchronously has not yet been implemented. The parameter is included for future compatibility.
79+
80+
See MDL-81015 for more information on this feature.
81+
82+
:::
83+
4284
## Fetching messages
4385

44-
Every sent message is stored in the database for subsequent reporting, and to check statuses.
86+
Every sent message is stored in the database to support status checks, and for subsequent reporting.
4587

4688
Messages can be fetched from the database by calling the `\core_sms\manager::get_message()` and `\core_sms\manager::get_messages()` methods and supplying a filter.
4789

@@ -122,3 +164,47 @@ graph TD
122164
GQ --> |Gateway failed to send the message| GF
123165
end
124166
```
167+
168+
## Getting SMS gateways
169+
170+
[SMS gateways](/apis/plugintypes/sms/index.md) are plugins that provide a way to interface with external SMS providers.
171+
Once a gateway is configured, any component implementing the SMS API can get a list of gateways.
172+
173+
```php title="Getting the list of enabled gateways"
174+
$manager = \core\di::get(\core_sms\manager::class);
175+
$gatewayrecords = $manager->get_gateway_records();
176+
177+
// It is also possible to filter the request.
178+
$gatewayrecords = $manager->get_gateway_records(['id' => $id]);
179+
180+
// To get all the enabled gateway instances.
181+
$gatewayrecords = $manager->get_enabled_gateway_instances();
182+
```
183+
184+
## Important hooks
185+
186+
The SMS API dispatches some [hooks](/apis/core/hooks/index.md) which should be considered when implemented by a plugin/component.
187+
188+
- before_gateway_deleted
189+
- before_gateway_disabled
190+
191+
Before deleting or disabling an [SMS gateways](/apis/plugintypes/sms/index.md), these two hooks are dispatched from the SMS API.
192+
This allows components that are actively using that gateway to stop the action, or do necessary cleanup.
193+
Listening to these hooks is crucial to avoid data loss or accidental deletion when disabling an active gateway.
194+
195+
```php title="Implement the hooks to check for usage before deletion or deactivation"
196+
197+
public static function check_gateway_usage_in_example_plugin(
198+
before_gateway_deleted|before_gateway_disabled $hook,
199+
): void {
200+
try {
201+
$smsgatewayid = (int)get_config('example_plugin', 'smsgateway');
202+
if ($smsgatewayid && $smsgatewayid === (int)$hook->gateway->id) {
203+
$hook->stop_propagation();
204+
}
205+
} catch (\dml_exception $exception) {
206+
$hook->stop_propagation();
207+
}
208+
}
209+
210+
```

0 commit comments

Comments
 (0)