diff --git a/docs/apis/plugintypes/sms/index.md b/docs/apis/plugintypes/sms/index.md new file mode 100644 index 0000000000..62869357d4 --- /dev/null +++ b/docs/apis/plugintypes/sms/index.md @@ -0,0 +1,145 @@ +--- +title: SMS gateway +tags: + - SMS + - Gateway + - Notification +--- + + + +SMS gateway plugins allow you to create SMS gateway providers. +Providers are an interface between the [SMS API](/apis/subsystems/sms/index.md) and an external SMS provider (for example Amazon Web Services). +This allows for the sending of SMS notifications to users from your Moodle instance. + +:::info Example + +You can set up Multi-Factor Authentication (MFA)) in Moodle and choose 'AWS' as your SMS gateway provider. + +This enables users to receive SMS notifications as part of the authentication process. + +::: + +## File structure + +SMS gateway plugins are located in the `/sms/gateway` directory. A plugin should not include any custom files outside its own plugin folder. + +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. + +
+ The directory layout for the `smsgateway` plugin. + +```console +sms/gateway/example +├── classes +│   ├── gateway.php +│   ├── hook_listener.php +│   └── privacy +│   └── provider.php +├── db +│ └── hooks.php +├── lang +│   └── en +│   └── smsgateway_example.php +├── settings.php +└── version.php +``` + +
+ +## Key files + +There are a number of key files within the SMS gateway plugin which will need to be configured for correct functionality. + +- `gateway.php` +- `hook_listener.php` + +### gateway.php + +Each plugin must create a class called `gateway` which extends the `\core_sms\gateway` class. +The SMS API will use the extended methods from this class. + +```php title="Implementing the base SMS gateway" +get_gateway_service($config); + $recipientnumber = manager::format_number( + phonenumber: $message->recipientnumber, + countrycode: isset($config->countrycode) ?? null, + ); + + $status = call_user_func( + [$class, 'send_sms_message'], + $message->content, + $recipientnumber, + $config, + ); + + return $message->with( + status: $status, + ); + } + + private function get_gateway_service(\stdClass $config): string { + return match ($config->gateway) { + 'aws_sns' => aws_sns::class, + default => throw new moodle_exception("Unknown Message Handler {$config->gateway}"), + }; + } + + #[\Override] + public function get_send_priority(message $message): int { + return 50; + } +} + +``` + +### hook_listener.php + +[Hooks](/apis/core/hooks/index.md) can be dispatched from the SMS API which the plugin can then listened to. +It is necessary for plugins developers to assess these hooks and implement accordingly. + +#### after_sms_gateway_form_hook + +This hook will allow plugins to add required form fields to assist users in configuring their SMS gateway. + +```php title="Listener method for after_sms_gateway_form_hook" +public static function set_form_definition_for_aws_sms_gateway(after_sms_gateway_form_hook $hook): void { + if ($hook->plugin !== 'smsgateway_example') { + return; + } + + $gateways = [ + 'smsgateway_example' => get_string('list', 'smsgateway_example'), + ]; + $mform->addElement( + 'select', + 'gateway', + get_string('gateway', 'smsgateway_example'), + $gateways, + ); +} + +``` + +:::info + +For an example of a production plugin example, see the [AWS SMS Gateway plugin](https://github.com/moodle/moodle/tree/main/sms/gateway/aws). + +::: diff --git a/docs/apis/subsystems/sms/index.md b/docs/apis/subsystems/sms/index.md index c8680cbedd..7537c6edeb 100644 --- a/docs/apis/subsystems/sms/index.md +++ b/docs/apis/subsystems/sms/index.md @@ -1,10 +1,24 @@ --- title: SMS API +tags: + - SMS --- -The SMS API lets you send SMS messages using configured gateways, fetch messages that were previously sent, and check on their status. +The SMS API allows developers to implement SMS-related features into their plugins. +The subsystem contains an SMS Manager class `\core_sms\manager` which facilitates the actions performed by the API. + +Some of the actions made possible are: + +- Sending messages +- Fetching messages +- Checking the status of a message +- Getting SMS gateways. + +Currently, the design of the SMS API features the following plugin types: + +- [SMS gateway](/apis/plugintypes/sms/index.md) ## Sending an SMS @@ -25,12 +39,24 @@ $message = \core\di::get(\core_sms\manager::class) :::info Message lengths -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. +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. Any message longer than the maximum length will be immediately rejected. ::: +### Parameter consideration while sending messages + +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. + +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. + +:::info + +In future reporting will be available for messages status. See MDL-80963 for further information + +::: + ### Sending messages containing sensitive information 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 The `send()` method return an instance of `\core_sms\message` which can be used to check on the message status. +:::warning + +Messages containing sensitive information cannot be sent asynchronously. + +Sensitive content is not persisted to the database and is therefore not available in a separate PHP process. + +::: + +:::info Availability of asynchronous message handling + +The ability to send messages asynchronously has not yet been implemented. The parameter is included for future compatibility. + +See MDL-81015 for more information on this feature. + +::: + ## Fetching messages -Every sent message is stored in the database for subsequent reporting, and to check statuses. +Every sent message is stored in the database to support status checks, and for subsequent reporting. 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. @@ -122,3 +164,47 @@ graph TD GQ --> |Gateway failed to send the message| GF end ``` + +## Getting SMS gateways + +[SMS gateways](/apis/plugintypes/sms/index.md) are plugins that provide a way to interface with external SMS providers. +Once a gateway is configured, any component implementing the SMS API can get a list of gateways. + +```php title="Getting the list of enabled gateways" +$manager = \core\di::get(\core_sms\manager::class); +$gatewayrecords = $manager->get_gateway_records(); + +// It is also possible to filter the request. +$gatewayrecords = $manager->get_gateway_records(['id' => $id]); + +// To get all the enabled gateway instances. +$gatewayrecords = $manager->get_enabled_gateway_instances(); +``` + +## Important hooks + +The SMS API dispatches some [hooks](/apis/core/hooks/index.md) which should be considered when implemented by a plugin/component. + +- before_gateway_deleted +- before_gateway_disabled + +Before deleting or disabling an [SMS gateways](/apis/plugintypes/sms/index.md), these two hooks are dispatched from the SMS API. +This allows components that are actively using that gateway to stop the action, or do necessary cleanup. +Listening to these hooks is crucial to avoid data loss or accidental deletion when disabling an active gateway. + +```php title="Implement the hooks to check for usage before deletion or deactivation" + +public static function check_gateway_usage_in_example_plugin( + before_gateway_deleted|before_gateway_disabled $hook, +): void { + try { + $smsgatewayid = (int)get_config('example_plugin', 'smsgateway'); + if ($smsgatewayid && $smsgatewayid === (int)$hook->gateway->id) { + $hook->stop_propagation(); + } + } catch (\dml_exception $exception) { + $hook->stop_propagation(); + } +} + +``` diff --git a/versioned_docs/version-4.5/apis/plugintypes/sms/index.md b/versioned_docs/version-4.5/apis/plugintypes/sms/index.md new file mode 100644 index 0000000000..62869357d4 --- /dev/null +++ b/versioned_docs/version-4.5/apis/plugintypes/sms/index.md @@ -0,0 +1,145 @@ +--- +title: SMS gateway +tags: + - SMS + - Gateway + - Notification +--- + + + +SMS gateway plugins allow you to create SMS gateway providers. +Providers are an interface between the [SMS API](/apis/subsystems/sms/index.md) and an external SMS provider (for example Amazon Web Services). +This allows for the sending of SMS notifications to users from your Moodle instance. + +:::info Example + +You can set up Multi-Factor Authentication (MFA)) in Moodle and choose 'AWS' as your SMS gateway provider. + +This enables users to receive SMS notifications as part of the authentication process. + +::: + +## File structure + +SMS gateway plugins are located in the `/sms/gateway` directory. A plugin should not include any custom files outside its own plugin folder. + +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. + +
+ The directory layout for the `smsgateway` plugin. + +```console +sms/gateway/example +├── classes +│   ├── gateway.php +│   ├── hook_listener.php +│   └── privacy +│   └── provider.php +├── db +│ └── hooks.php +├── lang +│   └── en +│   └── smsgateway_example.php +├── settings.php +└── version.php +``` + +
+ +## Key files + +There are a number of key files within the SMS gateway plugin which will need to be configured for correct functionality. + +- `gateway.php` +- `hook_listener.php` + +### gateway.php + +Each plugin must create a class called `gateway` which extends the `\core_sms\gateway` class. +The SMS API will use the extended methods from this class. + +```php title="Implementing the base SMS gateway" +get_gateway_service($config); + $recipientnumber = manager::format_number( + phonenumber: $message->recipientnumber, + countrycode: isset($config->countrycode) ?? null, + ); + + $status = call_user_func( + [$class, 'send_sms_message'], + $message->content, + $recipientnumber, + $config, + ); + + return $message->with( + status: $status, + ); + } + + private function get_gateway_service(\stdClass $config): string { + return match ($config->gateway) { + 'aws_sns' => aws_sns::class, + default => throw new moodle_exception("Unknown Message Handler {$config->gateway}"), + }; + } + + #[\Override] + public function get_send_priority(message $message): int { + return 50; + } +} + +``` + +### hook_listener.php + +[Hooks](/apis/core/hooks/index.md) can be dispatched from the SMS API which the plugin can then listened to. +It is necessary for plugins developers to assess these hooks and implement accordingly. + +#### after_sms_gateway_form_hook + +This hook will allow plugins to add required form fields to assist users in configuring their SMS gateway. + +```php title="Listener method for after_sms_gateway_form_hook" +public static function set_form_definition_for_aws_sms_gateway(after_sms_gateway_form_hook $hook): void { + if ($hook->plugin !== 'smsgateway_example') { + return; + } + + $gateways = [ + 'smsgateway_example' => get_string('list', 'smsgateway_example'), + ]; + $mform->addElement( + 'select', + 'gateway', + get_string('gateway', 'smsgateway_example'), + $gateways, + ); +} + +``` + +:::info + +For an example of a production plugin example, see the [AWS SMS Gateway plugin](https://github.com/moodle/moodle/tree/main/sms/gateway/aws). + +::: diff --git a/versioned_docs/version-4.5/apis/subsystems/sms/index.md b/versioned_docs/version-4.5/apis/subsystems/sms/index.md index c8680cbedd..7537c6edeb 100644 --- a/versioned_docs/version-4.5/apis/subsystems/sms/index.md +++ b/versioned_docs/version-4.5/apis/subsystems/sms/index.md @@ -1,10 +1,24 @@ --- title: SMS API +tags: + - SMS --- -The SMS API lets you send SMS messages using configured gateways, fetch messages that were previously sent, and check on their status. +The SMS API allows developers to implement SMS-related features into their plugins. +The subsystem contains an SMS Manager class `\core_sms\manager` which facilitates the actions performed by the API. + +Some of the actions made possible are: + +- Sending messages +- Fetching messages +- Checking the status of a message +- Getting SMS gateways. + +Currently, the design of the SMS API features the following plugin types: + +- [SMS gateway](/apis/plugintypes/sms/index.md) ## Sending an SMS @@ -25,12 +39,24 @@ $message = \core\di::get(\core_sms\manager::class) :::info Message lengths -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. +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. Any message longer than the maximum length will be immediately rejected. ::: +### Parameter consideration while sending messages + +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. + +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. + +:::info + +In future reporting will be available for messages status. See MDL-80963 for further information + +::: + ### Sending messages containing sensitive information 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 The `send()` method return an instance of `\core_sms\message` which can be used to check on the message status. +:::warning + +Messages containing sensitive information cannot be sent asynchronously. + +Sensitive content is not persisted to the database and is therefore not available in a separate PHP process. + +::: + +:::info Availability of asynchronous message handling + +The ability to send messages asynchronously has not yet been implemented. The parameter is included for future compatibility. + +See MDL-81015 for more information on this feature. + +::: + ## Fetching messages -Every sent message is stored in the database for subsequent reporting, and to check statuses. +Every sent message is stored in the database to support status checks, and for subsequent reporting. 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. @@ -122,3 +164,47 @@ graph TD GQ --> |Gateway failed to send the message| GF end ``` + +## Getting SMS gateways + +[SMS gateways](/apis/plugintypes/sms/index.md) are plugins that provide a way to interface with external SMS providers. +Once a gateway is configured, any component implementing the SMS API can get a list of gateways. + +```php title="Getting the list of enabled gateways" +$manager = \core\di::get(\core_sms\manager::class); +$gatewayrecords = $manager->get_gateway_records(); + +// It is also possible to filter the request. +$gatewayrecords = $manager->get_gateway_records(['id' => $id]); + +// To get all the enabled gateway instances. +$gatewayrecords = $manager->get_enabled_gateway_instances(); +``` + +## Important hooks + +The SMS API dispatches some [hooks](/apis/core/hooks/index.md) which should be considered when implemented by a plugin/component. + +- before_gateway_deleted +- before_gateway_disabled + +Before deleting or disabling an [SMS gateways](/apis/plugintypes/sms/index.md), these two hooks are dispatched from the SMS API. +This allows components that are actively using that gateway to stop the action, or do necessary cleanup. +Listening to these hooks is crucial to avoid data loss or accidental deletion when disabling an active gateway. + +```php title="Implement the hooks to check for usage before deletion or deactivation" + +public static function check_gateway_usage_in_example_plugin( + before_gateway_deleted|before_gateway_disabled $hook, +): void { + try { + $smsgatewayid = (int)get_config('example_plugin', 'smsgateway'); + if ($smsgatewayid && $smsgatewayid === (int)$hook->gateway->id) { + $hook->stop_propagation(); + } + } catch (\dml_exception $exception) { + $hook->stop_propagation(); + } +} + +```