Skip to content

Commit

Permalink
Merge branch 'release/3.7.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
rhukster committed Mar 28, 2022
2 parents b9280a5 + cb5190d commit 11251d1
Show file tree
Hide file tree
Showing 41 changed files with 1,823 additions and 740 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
# v3.7.0
## 03/28/2022

1. [](#new)
* Require **Grav 1.7.32**, **Form 6.0.0** and **Email 3.1.6**
* Added support for fully customizable email templates
* Added `Grav\PluginsLogin\Email` class to simplify sending emails
* Added `PageAuthorizeEvent` event for customizing page access
1. [](#bugfix)
* Removed ACL checks for page modules as they caused unexpected behavior

# v3.6.3
## 03/14/2022

Expand Down
8 changes: 4 additions & 4 deletions blueprints.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: Login
slug: login
type: plugin
version: 3.6.3
version: 3.7.0
testing: false
description: Enables user authentication and login screen.
icon: sign-in
Expand All @@ -15,9 +15,9 @@ bugs: https://github.com/getgrav/grav-plugin-login/issues
license: MIT

dependencies:
- { name: grav, version: '>=1.7.27' }
- { name: form, version: '>=5.1.0' }
- { name: email, version: '>=3.1.0' }
- { name: grav, version: '>=1.7.32' }
- { name: form, version: '>=6.0.0' }
- { name: email, version: '>=3.1.6' }

form:
validation: loose
Expand Down
30 changes: 4 additions & 26 deletions classes/Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,6 @@ protected function taskForgot()
{
/** @var Config $config */
$config = $this->grav['config'];
$param_sep = $config->get('system.param_sep', ':');
$data = $this->post;

/** @var UserCollectionInterface $users */
Expand Down Expand Up @@ -414,33 +413,12 @@ protected function taskForgot()
$user->reset = $token . '::' . $expire;
$user->save();

$author = $config->get('site.author.name', '');
$fullname = $user->fullname ?: $user->username;

$resetRoute = $this->login->getRoute('reset');
if (!$resetRoute) {
throw new \RuntimeException('Password reset route does not exist!');
}

/** @var Pages $pages */
$pages = $this->grav['pages'];
$route = $pages->url($resetRoute, null, true);

$reset_link = $route . '/task' . $param_sep . 'login.reset/token' . $param_sep . $token . '/user' . $param_sep . $user->username . '/nonce' . $param_sep . Utils::getNonce('reset-form');

$sitename = $config->get('site.title', 'Website');

$to = $user->email;

$subject = $language->translate(['PLUGIN_LOGIN.FORGOT_EMAIL_SUBJECT', $sitename]);
$content = $language->translate(['PLUGIN_LOGIN.FORGOT_EMAIL_BODY', $fullname, $reset_link, $author, $sitename]);

$sent = EmailUtils::sendEmail($subject, $content, $to);
try {
Email::sendResetPasswordEmail($user);

if ($sent < 1) {
$messages->add($language->translate('PLUGIN_LOGIN.FORGOT_FAILED_TO_EMAIL'), 'error');
} else {
$messages->add($language->translate('PLUGIN_LOGIN.FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL'), 'info');
} catch (\Exception $e) {
$messages->add($language->translate('PLUGIN_LOGIN.FORGOT_FAILED_TO_EMAIL'), 'error');
}

$this->setRedirect($this->login->getRoute('login') ?? '/');
Expand Down
294 changes: 294 additions & 0 deletions classes/Email.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,294 @@
<?php declare(strict_types=1);

namespace Grav\Plugin\Login;

use Grav\Common\Config\Config;
use Grav\Common\Grav;
use Grav\Common\Language\Language;
use Grav\Common\Page\Pages;
use Grav\Common\User\Interfaces\UserInterface;
use Grav\Common\Utils;
use Grav\Plugin\Login\Invitations\Invitation;
use Psr\Log\LoggerInterface;

class Email
{
/**
* @param UserInterface $user
* @param UserInterface|null $actor
* @return void
* @throws \Exception
*/
public static function sendActivationEmail(UserInterface $user, UserInterface $actor = null): void
{
$email = $user->email;
$token = (string)$user->get('activation_token', '');

if (!$email || !str_contains($token, '::')) {
return;
}

[$token, $expire] = explode('::', $token, 2);

try {
$config = static::getConfig();

$param_sep = $config->get('system.param_sep', ':');
$activationRoute = static::getLogin()->getRoute('activate');
if (!$activationRoute) {
throw new \RuntimeException('User activation route does not exist!');
}

/** @var Pages $pages */
$pages = Grav::instance()['pages'];
$activationLink = $pages->url(
$activationRoute . '/token' . $param_sep . $token . '/username' . $param_sep . $user->username,
null,
true
);

$context = [
'activation_link' => $activationLink,
'expire' => $expire,
];

$params = [
'to' => $user->email,
];

static::sendEmail('activate', $context, $params, $user, $actor);
} catch (\Exception $e) {
static::getLogger()->error($e->getMessage());

throw $e;
}
}


/**
* @param UserInterface $user
* @param UserInterface|null $actor
* @return void
* @throws \Exception
*/
public static function sendResetPasswordEmail(UserInterface $user, UserInterface $actor = null): void
{
$email = $user->email;
$token = (string)$user->get('reset', '');

if (!$email || !str_contains($token, '::')) {
return;
}

[$token, $expire] = explode('::', $token, 2);

try {
$param_sep = static::getConfig()->get('system.param_sep', ':');
$resetRoute = static::getLogin()->getRoute('reset');
if (!$resetRoute) {
throw new \RuntimeException('Password reset route does not exist!');
}

/** @var Pages $pages */
$pages = Grav::instance()['pages'];
$resetLink = $pages->url(
"{$resetRoute}/task{$param_sep}login.reset/token{$param_sep}{$token}/user{$param_sep}{$user->username}/nonce{$param_sep}" . Utils::getNonce('reset-form'),
null,
true
);

$context = [
'reset_link' => $resetLink,
'expire' => $expire,
];

$params = [
'to' => $user->email,
];

static::sendEmail('reset-password', $context, $params, $user, $actor);
} catch (\Exception $e) {
static::getLogger()->error($e->getMessage());

throw $e;
}
}

/**
* @param UserInterface $user
* @param UserInterface|null $actor
* @return void
* @throws \Exception
*/
public static function sendWelcomeEmail(UserInterface $user, UserInterface $actor = null): void
{
if (!$user->email) {
return;
}

try {
$context = [];

$params = [
'to' => $user->email,
];

static::sendEmail('welcome', $context, $params, $user, $actor);
} catch (\Exception $e) {
static::getLogger()->error($e->getMessage());

throw $e;
}
}

/**
* @param UserInterface $user
* @param UserInterface|null $actor
* @return void
* @throws \Exception
*/
public static function sendNotificationEmail(UserInterface $user, UserInterface $actor = null): void
{
try {
$to = static::getConfig()->get('plugins.email.to');
if (!$to) {
throw new \RuntimeException(static::getLanguage()->translate('PLUGIN_LOGIN.EMAIL_NOT_CONFIGURED'));
}

$context = [];

$params = [
'to' => $to,
];

static::sendEmail('notification', $context, $params, $user, $actor);
} catch (\Exception $e) {
static::getLogger()->error($e->getMessage());

throw $e;
}
}

/**
* @param Invitation $invitation
* @param string|null $message
* @param UserInterface|null $actor
* @return void
* @throws \Exception
*/
public static function sendInvitationEmail(Invitation $invitation, string $message = null, UserInterface $actor = null): void
{
if (!$invitation->email) {
return;
}

try {
$config = static::getConfig();
$param_sep = $config->get('system.param_sep', ':');
$inviteRoute = static::getLogin()->getRoute('register', true);
if (!$inviteRoute) {
throw new \RuntimeException('User registration route does not exist!');
}

/** @var Pages $pages */
$pages = Grav::instance()['pages'];
$invitationLink = $pages->url("{$inviteRoute}/{$param_sep}{$invitation->token}", null, true);

$context = [
'invitation_link' => $invitationLink,
'invitation' => $invitation,
'message' => $message,
];

$params = [
'to' => $invitation->email,
];

static::sendEmail('invite', $context, $params, null, $actor);
} catch (\Exception $e) {
static::getLogger()->error($e->getMessage());

throw $e;
}
}

protected static function sendEmail(string $template, array $context, array $params, UserInterface $user = null, UserInterface $actor = null): void
{
$actor = $actor ?? static::getUser();

$config = static::getConfig();

// Twig context.
$context += [
'actor' => $actor,
'user' => $user,
'site_name' => $config->get('site.title', 'Website'),
'author' => $config->get('site.author.name', ''),
];

$params += [
'body' => '',
'template' => "emails/login/{$template}.html.twig",
];

$email = static::getEmail();

$message = $email->buildMessage($params, $context);

$failedRecipients = null;
$email->send($message, $failedRecipients);
if ($failedRecipients) {
$language = static::getLanguage();

throw new \RuntimeException($language->translate(['PLUGIN_LOGIN.FAILED_TO_SEND_EMAILS', implode(', ', $failedRecipients)]));
}
}

/**
* @return Login
*/
protected static function getLogin(): Login
{
return Grav::instance()['login'];
}

/**
* @return LoggerInterface
*/
protected static function getLogger(): LoggerInterface
{
return Grav::instance()['log'];
}

/**
* @return UserInterface
*/
protected static function getUser(): UserInterface
{
return Grav::instance()['user'];
}

/**
* @return \Grav\Plugin\Email\Email
*/
protected static function getEmail(): \Grav\Plugin\Email\Email
{
return Grav::instance()['Email'];
}

/**
* @return Config
*/
protected static function getConfig(): Config
{
return Grav::instance()['config'];
}

/**
* @return Language
*/
protected static function getLanguage(): Language
{
return Grav::instance()['language'];
}
}
Loading

0 comments on commit 11251d1

Please sign in to comment.