mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Merge pull request #12318 from appwrite/feat-email-template-reset-default
feat: add email template reset and get-default endpoints
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Console\Http\Templates\Email;
|
||||
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Template\Template;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'getConsoleEmailTemplate';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/console/templates/email/:templateId')
|
||||
->desc('Get email template')
|
||||
->groups(['api'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'console',
|
||||
group: null,
|
||||
name: 'getEmailTemplate',
|
||||
description: <<<EOT
|
||||
Get the Appwrite built-in default email template for the specified type and locale. Always returns the unmodified default, ignoring any custom project overrides.
|
||||
EOT,
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_EMAIL_TEMPLATE,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('templateId', '', new WhiteList(Config::getParam('locale-templates')['email'] ?? [], true), 'Email template type. Can be one of: ' . \implode(', ', Config::getParam('locale-templates')['email'] ?? []))
|
||||
->param('locale', '', fn ($localeCodes) => new WhiteList($localeCodes), 'Template locale. If left empty, the fallback locale (en) will be used.', optional: true, injections: ['localeCodes'])
|
||||
->inject('response')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(
|
||||
string $templateId,
|
||||
string $locale,
|
||||
Response $response,
|
||||
): void {
|
||||
$locale = $locale ?: System::getEnv('_APP_LOCALE', 'en');
|
||||
|
||||
$localeObj = new Locale($locale);
|
||||
$localeObj->setFallback(System::getEnv('_APP_LOCALE', 'en'));
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'templateId' => $templateId,
|
||||
'locale' => $locale,
|
||||
'subject' => $localeObj->getText('emails.' . $templateId . '.subject'),
|
||||
'message' => $this->getDefaultMessage($templateId, $localeObj),
|
||||
'senderName' => '',
|
||||
'senderEmail' => '',
|
||||
'replyToEmail' => '',
|
||||
'replyToName' => '',
|
||||
]), Response::MODEL_EMAIL_TEMPLATE);
|
||||
}
|
||||
|
||||
private function getDefaultMessage(string $templateId, Locale $localeObj): string
|
||||
{
|
||||
$templateConfigs = [
|
||||
'magicSession' => [
|
||||
'file' => 'email-magic-url.tpl',
|
||||
'placeholders' => ['optionButton', 'buttonText', 'optionUrl', 'clientInfo', 'securityPhrase']
|
||||
],
|
||||
'mfaChallenge' => [
|
||||
'file' => 'email-mfa-challenge.tpl',
|
||||
'placeholders' => ['description', 'clientInfo']
|
||||
],
|
||||
'otpSession' => [
|
||||
'file' => 'email-otp.tpl',
|
||||
'placeholders' => ['description', 'clientInfo', 'securityPhrase']
|
||||
],
|
||||
'sessionAlert' => [
|
||||
'file' => 'email-session-alert.tpl',
|
||||
'placeholders' => ['body', 'listDevice', 'listIpAddress', 'listCountry', 'footer']
|
||||
],
|
||||
];
|
||||
|
||||
$config = $templateConfigs[$templateId] ?? [
|
||||
'file' => 'email-inner-base.tpl',
|
||||
'placeholders' => ['buttonText', 'body', 'footer']
|
||||
];
|
||||
|
||||
$templateString = file_get_contents(APP_CE_CONFIG_DIR . '/locale/templates/' . $config['file']);
|
||||
$message = Template::fromString($templateString);
|
||||
|
||||
foreach ($config['placeholders'] as $param) {
|
||||
$escapeHtml = !in_array($param, ['clientInfo', 'body', 'footer', 'description']);
|
||||
if ($templateId === 'magicSession' && $param === 'securityPhrase') {
|
||||
$message->setParam('{{securityPhrase}}', '');
|
||||
continue;
|
||||
}
|
||||
|
||||
$message->setParam("{{{$param}}}", $localeObj->getText("emails.{$templateId}.{$param}"), escapeHtml: $escapeHtml);
|
||||
}
|
||||
|
||||
$message
|
||||
->setParam('{{hello}}', $localeObj->getText("emails.{$templateId}.hello"))
|
||||
->setParam('{{thanks}}', $localeObj->getText("emails.{$templateId}.thanks"))
|
||||
->setParam('{{signature}}', $localeObj->getText("emails.{$templateId}.signature"));
|
||||
|
||||
return $message->render(useContent: true);
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ use Appwrite\Platform\Modules\Console\Http\Redirects\Root\Get as RedirectRoot;
|
||||
use Appwrite\Platform\Modules\Console\Http\Resources\Get as GetResourceAvailability;
|
||||
use Appwrite\Platform\Modules\Console\Http\Scopes\Organization\XList as ListOrganizationScopes;
|
||||
use Appwrite\Platform\Modules\Console\Http\Scopes\Project\XList as ListKeyScopes;
|
||||
use Appwrite\Platform\Modules\Console\Http\Templates\Email\Get as GetEmailTemplate;
|
||||
use Appwrite\Platform\Modules\Console\Http\Variables\Get as GetVariables;
|
||||
use Utopia\Platform\Service;
|
||||
|
||||
@@ -31,6 +32,7 @@ class Http extends Service
|
||||
$this->addAction(Web::getName(), new Web());
|
||||
|
||||
$this->addAction(GetVariables::getName(), new GetVariables());
|
||||
$this->addAction(GetEmailTemplate::getName(), new GetEmailTemplate());
|
||||
$this->addAction(ListOAuth2Providers::getName(), new ListOAuth2Providers());
|
||||
$this->addAction(ListKeyScopes::getName(), new ListKeyScopes());
|
||||
$this->addAction(ListOrganizationScopes::getName(), new ListOrganizationScopes());
|
||||
|
||||
@@ -466,6 +466,14 @@ abstract class Format
|
||||
return 'ConsoleResourceValue';
|
||||
}
|
||||
break;
|
||||
case 'getEmailTemplate':
|
||||
switch ($param) {
|
||||
case 'templateId':
|
||||
return 'ProjectEmailTemplateId';
|
||||
case 'locale':
|
||||
return 'ProjectEmailTemplateLocale';
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'account':
|
||||
|
||||
@@ -1147,6 +1147,115 @@ trait TemplatesBase
|
||||
return $this->client->call(Client::METHOD_PATCH, '/project/templates/email', $headers, $params);
|
||||
}
|
||||
|
||||
// Console email template (default) tests
|
||||
|
||||
public function testGetConsoleEmailTemplate(): void
|
||||
{
|
||||
$response = $this->getConsoleEmailTemplate('verification', 'en');
|
||||
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame('verification', $response['body']['templateId']);
|
||||
$this->assertSame('en', $response['body']['locale']);
|
||||
$this->assertNotEmpty($response['body']['subject']);
|
||||
$this->assertNotEmpty($response['body']['message']);
|
||||
$this->assertSame('', $response['body']['senderName']);
|
||||
$this->assertSame('', $response['body']['senderEmail']);
|
||||
$this->assertSame('', $response['body']['replyToEmail']);
|
||||
$this->assertSame('', $response['body']['replyToName']);
|
||||
}
|
||||
|
||||
public function testGetConsoleEmailTemplateIgnoresCustomOverride(): void
|
||||
{
|
||||
$this->ensureSMTPEnabled();
|
||||
|
||||
// Set a custom override on the project template.
|
||||
$this->updateEmailTemplate(
|
||||
templateId: 'recovery',
|
||||
locale: 'en',
|
||||
subject: 'Custom subject',
|
||||
message: 'Custom message',
|
||||
senderName: 'Custom Sender',
|
||||
senderEmail: 'custom@appwrite.io',
|
||||
);
|
||||
|
||||
// Console endpoint must always return the built-in default, not the override.
|
||||
$response = $this->getConsoleEmailTemplate('recovery', 'en');
|
||||
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame('recovery', $response['body']['templateId']);
|
||||
$this->assertNotSame('Custom subject', $response['body']['subject']);
|
||||
$this->assertSame('', $response['body']['senderName']);
|
||||
$this->assertSame('', $response['body']['senderEmail']);
|
||||
}
|
||||
|
||||
public function testGetConsoleEmailTemplateDefaultLocale(): void
|
||||
{
|
||||
$response = $this->getConsoleEmailTemplate('magicSession');
|
||||
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame('en', $response['body']['locale']);
|
||||
$this->assertNotEmpty($response['body']['subject']);
|
||||
}
|
||||
|
||||
public function testGetConsoleEmailTemplateNonDefaultLocale(): void
|
||||
{
|
||||
$response = $this->getConsoleEmailTemplate('verification', 'fr');
|
||||
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame('verification', $response['body']['templateId']);
|
||||
$this->assertSame('fr', $response['body']['locale']);
|
||||
$this->assertNotEmpty($response['body']['subject']);
|
||||
$this->assertNotEmpty($response['body']['message']);
|
||||
}
|
||||
|
||||
public function testGetConsoleEmailTemplateAllTypes(): void
|
||||
{
|
||||
$types = [
|
||||
'verification',
|
||||
'magicSession',
|
||||
'recovery',
|
||||
'invitation',
|
||||
'mfaChallenge',
|
||||
'sessionAlert',
|
||||
'otpSession',
|
||||
];
|
||||
|
||||
foreach ($types as $type) {
|
||||
$response = $this->getConsoleEmailTemplate($type, 'en');
|
||||
$this->assertSame(200, $response['headers']['status-code'], "type={$type}");
|
||||
$this->assertNotEmpty($response['body']['subject'], "type={$type} must have subject");
|
||||
$this->assertNotEmpty($response['body']['message'], "type={$type} must have message");
|
||||
}
|
||||
}
|
||||
|
||||
public function testGetConsoleEmailTemplateInvalidTemplateId(): void
|
||||
{
|
||||
$response = $this->getConsoleEmailTemplate('invalidTemplate', 'en');
|
||||
|
||||
$this->assertSame(400, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testGetConsoleEmailTemplateInvalidLocale(): void
|
||||
{
|
||||
$response = $this->getConsoleEmailTemplate('recovery', 'not-a-locale');
|
||||
|
||||
$this->assertSame(400, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
protected function getConsoleEmailTemplate(string $templateId, ?string $locale = null): mixed
|
||||
{
|
||||
$params = [];
|
||||
if ($locale !== null) {
|
||||
$params['locale'] = $locale;
|
||||
}
|
||||
|
||||
return $this->client->call(Client::METHOD_GET, '/console/templates/email/' . $templateId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => 'console',
|
||||
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
|
||||
], $params);
|
||||
}
|
||||
|
||||
protected function ensureSMTPEnabled(): void
|
||||
{
|
||||
$this->client->call(
|
||||
|
||||
Reference in New Issue
Block a user