diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 06d2d75f57..0372313d7a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -91,6 +91,8 @@ git clone git@github.com:[YOUR_FORK_HERE]/appwrite.git cd appwrite +git submodule update --init + docker compose build docker compose up -d ``` diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index af5c8e4f53..f9425f690b 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -713,11 +713,33 @@ App::post('/v1/account/sessions/magic-url') $url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $user->getId(), 'secret' => $loginSecret, 'expire' => $expire, 'project' => $project->getId()]); $url = Template::unParseURL($url); + $from = $project->isEmpty() || $project->getId() === 'console' ? '' : \sprintf($locale->getText('emails.sender'), $project->getAttribute('name')); + + $body = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-base.tpl'); + $subject = $locale->getText("emails.magicSession.subject"); + + $body + ->setParam('{{subject}}', $subject) + ->setParam('{{hello}}', $locale->getText("emails.magicSession.hello")) + ->setParam('{{name}}', '') + ->setParam('{{body}}', $locale->getText("emails.magicSession.body")) + ->setParam('{{redirect}}', $url) + ->setParam('{{footer}}', $locale->getText("emails.magicSession.footer")) + ->setParam('{{thanks}}', $locale->getText("emails.magicSession.thanks")) + ->setParam('{{signature}}', $locale->getText("emails.magicSession.signature")) + ->setParam('{{project}}', $project->getAttribute('name')) + ->setParam('{{direction}}', $locale->getText('settings.direction')) + ->setParam('{{bg-body}}', '#f7f7f7') + ->setParam('{{bg-content}}', '#ffffff') + ->setParam('{{text-content}}', '#000000'); + + $body = $body->render(); + $mails - ->setType(MAIL_TYPE_MAGIC_SESSION) + ->setSubject($subject) + ->setBody($body) + ->setFrom($from) ->setRecipient($user->getAttribute('email')) - ->setUrl($url) - ->setLocale($locale->default) ->trigger() ; @@ -1991,12 +2013,35 @@ App::post('/v1/account/recovery') $url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $profile->getId(), 'secret' => $secret, 'expire' => $expire]); $url = Template::unParseURL($url); + $projectName = $project->isEmpty() ? 'Console' : $project->getAttribute('name', '[APP-NAME]'); + $from = $project->isEmpty() || $project->getId() === 'console' ? '' : \sprintf($locale->getText('emails.sender'), $projectName); + $body = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-base.tpl'); + $subject = $locale->getText("emails.recovery.subject"); + + $body + ->setParam('{{subject}}', $subject) + ->setParam('{{hello}}', $locale->getText("emails.recovery.hello")) + ->setParam('{{name}}', $profile->getAttribute('name')) + ->setParam('{{body}}', $locale->getText("emails.recovery.body")) + ->setParam('{{redirect}}', $url) + ->setParam('{{footer}}', $locale->getText("emails.recovery.footer")) + ->setParam('{{thanks}}', $locale->getText("emails.recovery.thanks")) + ->setParam('{{signature}}', $locale->getText("emails.recovery.signature")) + ->setParam('{{project}}', $projectName) + ->setParam('{{direction}}', $locale->getText('settings.direction')) + ->setParam('{{bg-body}}', '#f7f7f7') + ->setParam('{{bg-content}}', '#ffffff') + ->setParam('{{text-content}}', '#000000'); + + $body = $body->render(); + + $mails - ->setType(MAIL_TYPE_RECOVERY) ->setRecipient($profile->getAttribute('email', '')) - ->setUrl($url) - ->setLocale($locale->default) ->setName($profile->getAttribute('name')) + ->setBody($body) + ->setFrom($from) + ->setSubject($subject) ->trigger(); ; @@ -2151,11 +2196,32 @@ App::post('/v1/account/verification') $url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $user->getId(), 'secret' => $verificationSecret, 'expire' => $expire]); $url = Template::unParseURL($url); + $projectName = $project->isEmpty() ? 'Console' : $project->getAttribute('name', '[APP-NAME]'); + $from = $project->isEmpty() || $project->getId() === 'console' ? '' : \sprintf($locale->getText('emails.sender'), $projectName); + $body = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-base.tpl'); + $subject = $locale->getText("emails.verification.subject"); + $body + ->setParam('{{subject}}', $subject) + ->setParam('{{hello}}', $locale->getText("emails.verification.hello")) + ->setParam('{{name}}', $user->getAttribute('name')) + ->setParam('{{body}}', $locale->getText("emails.verification.body")) + ->setParam('{{redirect}}', $url) + ->setParam('{{footer}}', $locale->getText("emails.verification.footer")) + ->setParam('{{thanks}}', $locale->getText("emails.verification.thanks")) + ->setParam('{{signature}}', $locale->getText("emails.verification.signature")) + ->setParam('{{project}}', $projectName) + ->setParam('{{direction}}', $locale->getText('settings.direction')) + ->setParam('{{bg-body}}', '#f7f7f7') + ->setParam('{{bg-content}}', '#ffffff') + ->setParam('{{text-content}}', '#000000'); + + $body = $body->render(); + $mails - ->setType(MAIL_TYPE_VERIFICATION) + ->setSubject($subject) + ->setBody($body) + ->setFrom($from) ->setRecipient($user->getAttribute('email')) - ->setUrl($url) - ->setLocale($locale->default) ->setName($user->getAttribute('name')) ->trigger() ; diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 26f8499998..d84be2181e 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -459,28 +459,44 @@ App::post('/v1/teams/:teamId/memberships') } catch (Duplicate $th) { throw new Exception(Exception::TEAM_INVITE_ALREADY_EXISTS); } + $url = Template::parseURL($url); $url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['membershipId' => $membership->getId(), 'userId' => $invitee->getId(), 'secret' => $secret, 'teamId' => $teamId]); $url = Template::unParseURL($url); - // No need of confirmation when in admin or app mode - if (!empty($email)) { - $mails - ->setType(MAIL_TYPE_INVITATION) - ->setRecipient($email) - ->setUrl($url) - ->setName($name) - ->setLocale($locale->default) - ->setTeam($team) - ->setUser($user) - ->trigger() - ; - } elseif (!empty($phone)) { - $messaging - ->setRecipient($phone) - ->setMessage($url) - ->trigger(); - } + $projectName = $project->isEmpty() ? 'Console' : $project->getAttribute('name', '[APP-NAME]'); + + $from = $project->isEmpty() || $project->getId() === 'console' ? '' : \sprintf($locale->getText('emails.sender'), $projectName); + $body = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-base.tpl'); + $subject = \sprintf($locale->getText("emails.invitation.subject"), $team->getAttribute('name'), $projectName); + $body->setParam('{{owner}}', $user->getAttribute('name')); + $body->setParam('{{team}}', $team->getAttribute('name')); + + $body + ->setParam('{{subject}}', $subject) + ->setParam('{{hello}}', $locale->getText("emails.invitation.hello")) + ->setParam('{{name}}', $user->getAttribute('name')) + ->setParam('{{body}}', $locale->getText("emails.invitation.body")) + ->setParam('{{redirect}}', $url) + ->setParam('{{footer}}', $locale->getText("emails.invitation.footer")) + ->setParam('{{thanks}}', $locale->getText("emails.invitation.thanks")) + ->setParam('{{signature}}', $locale->getText("emails.invitation.signature")) + ->setParam('{{project}}', $projectName) + ->setParam('{{direction}}', $locale->getText('settings.direction')) + ->setParam('{{bg-body}}', '#f7f7f7') + ->setParam('{{bg-content}}', '#ffffff') + ->setParam('{{text-content}}', '#000000'); + + $body = $body->render(); + + $mails + ->setSubject($subject) + ->setBody($body) + ->setFrom($from) + ->setRecipient($invitee->getAttribute('email')) + ->setName($invitee->getAttribute('name')) + ->trigger() + ; } $events diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index fa247b8e78..5655874c65 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -98,13 +98,12 @@ App::init() ->inject('user') ->inject('events') ->inject('audits') - ->inject('mails') ->inject('usage') ->inject('deletes') ->inject('database') ->inject('dbForProject') ->inject('mode') - ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $events, Audit $audits, Mail $mails, Stats $usage, Delete $deletes, EventDatabase $database, Database $dbForProject, string $mode) use ($databaseListener) { + ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $events, Audit $audits, Stats $usage, Delete $deletes, EventDatabase $database, Database $dbForProject, string $mode) use ($databaseListener) { $route = $utopia->match($request); @@ -178,10 +177,6 @@ App::init() ->setProject($project) ->setUser($user); - $mails - ->setProject($project) - ->setUser($user); - $audits ->setMode($mode) ->setUserAgent($request->getUserAgent('')) diff --git a/app/workers/certificates.php b/app/workers/certificates.php index e7885bc669..11f3439408 100644 --- a/app/workers/certificates.php +++ b/app/workers/certificates.php @@ -4,6 +4,7 @@ use Appwrite\Event\Event; use Appwrite\Event\Mail; use Appwrite\Network\Validator\CNAME; use Appwrite\Resque\Worker; +use Appwrite\Template\Template; use Utopia\App; use Utopia\CLI\Console; use Utopia\Database\Database; @@ -12,6 +13,7 @@ use Utopia\Database\DateTime; use Utopia\Database\ID; use Utopia\Database\Query; use Utopia\Domains\Domain; +use Utopia\Locale\Locale; require_once __DIR__ . '/../init.php'; @@ -375,18 +377,38 @@ class CertificatesV1 extends Worker Console::warning('Cannot renew domain (' . $domain . ') on attempt no. ' . $attempt . ' certificate: ' . $errorMessage); // Send mail to administratore mail + + $locale = new Locale(App::getEnv('_APP_LOCALE', 'en')); + if (!$locale->getText('emails.sender') || !$locale->getText("emails.certificate.hello") || !$locale->getText("emails.certificate.subject") || !$locale->getText("emails.certificate.body") || !$locale->getText("emails.certificate.footer") || !$locale->getText("emails.certificate.thanks") || !$locale->getText("emails.certificate.signature")) { + $locale->setDefault('en'); + } + + $body = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-base.tpl'); + + $subject = \sprintf($locale->getText("emails.certificate.subject"), $domain); + $body->setParam('{{domain}}', $domain); + $body->setParam('{{error}}', $errorMessage); + $body->setParam('{{attempt}}', $attempt); + + $body + ->setParam('{{subject}}', $subject) + ->setParam('{{hello}}', $locale->getText("emails.certificate.hello")) + ->setParam('{{body}}', $locale->getText("emails.certificate.body")) + ->setParam('{{redirect}}', 'https://' . $domain) + ->setParam('{{footer}}', $locale->getText("emails.certificate.footer")) + ->setParam('{{thanks}}', $locale->getText("emails.certificate.thanks")) + ->setParam('{{signature}}', $locale->getText("emails.certificate.signature")) + ->setParam('{{project}}', 'Console') + ->setParam('{{direction}}', $locale->getText('settings.direction')) + ->setParam('{{bg-body}}', '#f7f7f7') + ->setParam('{{bg-content}}', '#ffffff') + ->setParam('{{text-content}}', '#000000'); + + $body = $body->render(); $mail = new Mail(); $mail - ->setType(MAIL_TYPE_CERTIFICATE) ->setRecipient(App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS')) - ->setUrl('https://' . $domain) - ->setLocale(App::getEnv('_APP_LOCALE', 'en')) ->setName('Appwrite Administrator') - ->setPayload([ - 'domain' => $domain, - 'error' => $errorMessage, - 'attempt' => $attempt - ]) ->trigger(); } diff --git a/app/workers/mails.php b/app/workers/mails.php index 90e9e9a660..e850885ddc 100644 --- a/app/workers/mails.php +++ b/app/workers/mails.php @@ -1,11 +1,8 @@ args['project'] ?? []); - $user = new Document($this->args['user'] ?? []); - $team = new Document($this->args['team'] ?? []); - $payload = $this->args['payload'] ?? []; $recipient = $this->args['recipient']; - $url = $this->args['url']; + $subject = $this->args['subject']; $name = $this->args['name']; - $type = $this->args['type']; - $prefix = $this->getPrefix($type); - $locale = new Locale($this->args['locale']); - $projectName = $project->isEmpty() ? 'Console' : $project->getAttribute('name', '[APP-NAME]'); - - if (!$this->doesLocaleExist($locale, $prefix)) { - $locale->setDefault('en'); - } - - $from = $project->isEmpty() || $project->getId() === 'console' ? '' : \sprintf($locale->getText('emails.sender'), $projectName); - $body = Template::fromFile(__DIR__ . '/../config/locale/templates/email-base.tpl'); - $subject = ''; - switch ($type) { - case MAIL_TYPE_CERTIFICATE: - $domain = $payload['domain']; - $error = $payload['error']; - $attempt = $payload['attempt']; - - $subject = \sprintf($locale->getText("$prefix.subject"), $domain); - $body->setParam('{{domain}}', $domain); - $body->setParam('{{error}}', $error); - $body->setParam('{{attempt}}', $attempt); - break; - case MAIL_TYPE_INVITATION: - $subject = \sprintf($locale->getText("$prefix.subject"), $team->getAttribute('name'), $projectName); - $body->setParam('{{owner}}', $user->getAttribute('name')); - $body->setParam('{{team}}', $team->getAttribute('name')); - break; - case MAIL_TYPE_RECOVERY: - case MAIL_TYPE_VERIFICATION: - case MAIL_TYPE_MAGIC_SESSION: - $subject = $locale->getText("$prefix.subject"); - break; - default: - throw new Exception('Undefined Mail Type : ' . $type, 500); - } - - $body - ->setParam('{{subject}}', $subject) - ->setParam('{{hello}}', $locale->getText("$prefix.hello")) - ->setParam('{{name}}', $name) - ->setParam('{{body}}', $locale->getText("$prefix.body")) - ->setParam('{{redirect}}', $url) - ->setParam('{{footer}}', $locale->getText("$prefix.footer")) - ->setParam('{{thanks}}', $locale->getText("$prefix.thanks")) - ->setParam('{{signature}}', $locale->getText("$prefix.signature")) - ->setParam('{{project}}', $projectName) - ->setParam('{{direction}}', $locale->getText('settings.direction')) - ->setParam('{{bg-body}}', '#f7f7f7') - ->setParam('{{bg-content}}', '#ffffff') - ->setParam('{{text-content}}', '#000000'); - - $body = $body->render(); + $body = $this->args['body']; + $from = $this->args['from']; /** @var \PHPMailer\PHPMailer\PHPMailer $mail */ $mail = $register->get('smtp'); @@ -130,47 +72,4 @@ class MailsV1 extends Worker public function shutdown(): void { } - - /** - * Returns a prefix from a mail type - * - * @param $type - * - * @return string - */ - protected function getPrefix(string $type): string - { - switch ($type) { - case MAIL_TYPE_RECOVERY: - return 'emails.recovery'; - case MAIL_TYPE_CERTIFICATE: - return 'emails.certificate'; - case MAIL_TYPE_INVITATION: - return 'emails.invitation'; - case MAIL_TYPE_VERIFICATION: - return 'emails.verification'; - case MAIL_TYPE_MAGIC_SESSION: - return 'emails.magicSession'; - default: - throw new Exception('Undefined Mail Type : ' . $type, 500); - } - } - - /** - * Returns true if all the required terms in a locale exist. False otherwise - * - * @param $locale - * @param $prefix - * - * @return bool - */ - protected function doesLocaleExist(Locale $locale, string $prefix): bool - { - - if (!$locale->getText('emails.sender') || !$locale->getText("$prefix.hello") || !$locale->getText("$prefix.subject") || !$locale->getText("$prefix.body") || !$locale->getText("$prefix.footer") || !$locale->getText("$prefix.thanks") || !$locale->getText("$prefix.signature")) { - return false; - } - - return true; - } } diff --git a/composer.json b/composer.json index 97f4657bea..b2008b5cc0 100644 --- a/composer.json +++ b/composer.json @@ -59,7 +59,7 @@ "utopia-php/orchestration": "0.6.*", "utopia-php/preloader": "0.2.*", "utopia-php/registry": "0.5.*", - "utopia-php/storage": "0.11.*", + "utopia-php/storage": "0.13.*", "utopia-php/swoole": "0.5.*", "utopia-php/websocket": "0.1.*", "resque/php-resque": "1.3.6", diff --git a/composer.lock b/composer.lock index 5f7b291615..d09230b398 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "60bbe8610441d8b6e119bd322e0f5a13", + "content-hash": "dbc502462e4afa550b1a1bc3898020b3", "packages": [ { "name": "adhocore/jwt", @@ -2373,16 +2373,16 @@ }, { "name": "utopia-php/storage", - "version": "0.11.0", + "version": "0.13.2", "source": { "type": "git", "url": "https://github.com/utopia-php/storage.git", - "reference": "59802cf281d1976560cf6e353f250a9b870efddc" + "reference": "ad1c00f24ca56e73888acc2af3deee4919b1194b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/storage/zipball/59802cf281d1976560cf6e353f250a9b870efddc", - "reference": "59802cf281d1976560cf6e353f250a9b870efddc", + "url": "https://api.github.com/repos/utopia-php/storage/zipball/ad1c00f24ca56e73888acc2af3deee4919b1194b", + "reference": "ad1c00f24ca56e73888acc2af3deee4919b1194b", "shasum": "" }, "require": { @@ -2422,9 +2422,9 @@ ], "support": { "issues": "https://github.com/utopia-php/storage/issues", - "source": "https://github.com/utopia-php/storage/tree/0.11.0" + "source": "https://github.com/utopia-php/storage/tree/0.13.2" }, - "time": "2022-08-31T09:17:31+00:00" + "time": "2022-12-20T11:11:35+00:00" }, { "name": "utopia-php/swoole", diff --git a/src/Appwrite/Event/Mail.php b/src/Appwrite/Event/Mail.php index b3325c00be..1b7e218d69 100644 --- a/src/Appwrite/Event/Mail.php +++ b/src/Appwrite/Event/Mail.php @@ -8,11 +8,10 @@ use Utopia\Database\Document; class Mail extends Event { protected string $recipient = ''; - protected string $url = ''; - protected string $type = ''; + protected string $from = ''; protected string $name = ''; - protected string $locale = ''; - protected ?Document $team = null; + protected string $subject = ''; + protected string $body = ''; public function __construct() { @@ -20,14 +19,14 @@ class Mail extends Event } /** - * Sets team for the mail event. + * Sets subject for the mail event. * - * @param Document $team + * @param string $subject * @return self */ - public function setTeam(Document $team): self + public function setSubject(string $subject): self { - $this->team = $team; + $this->subject = $subject; return $this; } @@ -35,11 +34,11 @@ class Mail extends Event /** * Returns set team for the mail event. * - * @return null|Document + * @return string */ - public function getTeam(): ?Document + public function getSubject(): string { - return $this->team; + return $this->subject; } /** @@ -66,49 +65,49 @@ class Mail extends Event } /** - * Sets url for the mail event. + * Sets from for the mail event. * - * @param string $url + * @param string $from * @return self */ - public function setUrl(string $url): self + public function setFrom(string $from): self { - $this->url = $url; + $this->from = $from; return $this; } /** - * Returns set url for the mail event. + * Returns from for mail event. * * @return string */ - public function getURL(): string + public function getFrom(): string { - return $this->url; + return $this->from; } /** - * Sets type for the mail event (use the constants starting with MAIL_TYPE_*). + * Sets body for the mail event. * - * @param string $type + * @param string $body * @return self */ - public function setType(string $type): self + public function setBody(string $body): self { - $this->type = $type; + $this->body = $body; return $this; } /** - * Returns set type for the mail event. + * Returns body for the mail event. * * @return string */ - public function getType(): string + public function getBody(): string { - return $this->type; + return $this->body; } /** @@ -134,29 +133,6 @@ class Mail extends Event return $this->name; } - /** - * Sets locale for the mail event. - * - * @param string $locale - * @return self - */ - public function setLocale(string $locale): self - { - $this->locale = $locale; - - return $this; - } - - /** - * Returns set locale for the mail event. - * - * @return string - */ - public function getLocale(): string - { - return $this->locale; - } - /** * Executes the event and sends it to the mails worker. * @@ -166,15 +142,11 @@ class Mail extends Event public function trigger(): string|bool { return Resque::enqueue($this->queue, $this->class, [ - 'project' => $this->project, - 'user' => $this->user, - 'payload' => $this->payload, + 'from' => $this->from, 'recipient' => $this->recipient, - 'url' => $this->url, - 'locale' => $this->locale, - 'type' => $this->type, 'name' => $this->name, - 'team' => $this->team, + 'subject' => $this->subject, + 'body' => $this->body, 'events' => Event::generateEvents($this->getEvent(), $this->getParams()) ]); } diff --git a/src/Appwrite/Utopia/Response/Model/Func.php b/src/Appwrite/Utopia/Response/Model/Func.php index c7e69fff88..74cfb1e844 100644 --- a/src/Appwrite/Utopia/Response/Model/Func.php +++ b/src/Appwrite/Utopia/Response/Model/Func.php @@ -97,7 +97,7 @@ class Func extends Model 'type' => self::TYPE_INTEGER, 'description' => 'Function execution timeout in seconds.', 'default' => 15, - 'example' => 1592981237, + 'example' => 15, ]) ; }