diff --git a/lib/User/Bootstrap.php b/lib/User/Bootstrap.php index b162b91..de207d3 100644 --- a/lib/User/Bootstrap.php +++ b/lib/User/Bootstrap.php @@ -23,6 +23,7 @@ class Bootstrap implements BootstrapInterface $classMap = $this->buildClassMap(); $this->initContainer($classMap); $this->initTranslations($app); + $this->initMailServiceConfiguration($app, $app->getModule('user')); if ($app instanceof WebApplication) { $this->initUrlRoutes($app); @@ -44,9 +45,9 @@ class Bootstrap implements BootstrapInterface $di = Yii::$container; // email change strategy - $di->set('Da\User\Strategy\DefaultEmailChangeStrategy'); - $di->set('Da\User\Strategy\InsecureEmailChangeStrategy'); - $di->set('Da\User\Strategy\SecureEmailChangeStrategy'); + $di->set(Strategy\DefaultEmailChangeStrategy::class); + $di->set(Strategy\InsecureEmailChangeStrategy::class); + $di->set(Strategy\SecureEmailChangeStrategy::class); // models + active query classes $modelClassMap = []; @@ -68,7 +69,7 @@ class Bootstrap implements BootstrapInterface } // helpers - $di->set('Da\User\Helper\AuthHelper'); + $di->set(Helper\AuthHelper::class); $di->setSingleton(ClassMapHelper::class, ClassMapHelper::class, [$modelClassMap]); if (php_sapi_name() !== 'cli') { @@ -82,6 +83,20 @@ class Bootstrap implements BootstrapInterface ] ); } + + // services + $di->set(Service\UserCreateService::class); + + // events + $di->set(Event\FormEvent::class); + $di->set(Event\ProfileEvent::class); + $di->set(Event\ResetPasswordEvent::class); + $di->set(Event\SocialNetworkAuthEvent::class); + $di->set(Event\SocialNetworkConnectEvent::class); + $di->set(Event\UserEvent::class); + + // validators + $di->set(Validator\AjaxRequestModelValidator::class); } /** @@ -94,7 +109,7 @@ class Bootstrap implements BootstrapInterface if (!isset($app->get('i18n')->translations['user*'])) { $app->get('i18n')->translations['user*'] = [ 'class' => PhpMessageSource::class, - 'basePath' => __DIR__ . '/../i18n', + 'basePath' => __DIR__ . '/resources/i18n', 'sourceLanguage' => 'en-US' ]; } @@ -123,6 +138,24 @@ class Bootstrap implements BootstrapInterface $app->getUrlManager()->addRules([$rule], false); } + /** + * Ensures required mail parameters needed for the mail service. + * + * @param Application $app + * @param Module $module + */ + protected function initMailServiceConfiguration(Application $app, Module $module) + { + $defaults = [ + 'welcomeMailSubject' => Yii::t('user', 'Welcome to {0}', $app->name), + 'confirmationMailSubject' => Yii::t('user', 'Confirm account on {0}', $app->name), + 'reconfirmationMailSubject' => Yii::t('user', 'Confirm email change on {0}', $app->name), + 'recoveryMailSubject' => Yii::t('user', 'Complete password reset on {0}', $app->name) + ]; + + $module->mailParams = array_merge($defaults, $module->mailParams); + } + /** * Ensures the authCollection component is configured. * diff --git a/lib/User/Contracts/ValidatorInterface.php b/lib/User/Contracts/ValidatorInterface.php new file mode 100644 index 0000000..9b837c6 --- /dev/null +++ b/lib/User/Contracts/ValidatorInterface.php @@ -0,0 +1,10 @@ + [ + 'class' => VerbFilter::className(), + 'actions' => [ + 'delete' => ['post'], + 'confirm' => ['post'], + 'block' => ['post'], + ], + ], + 'access' => [ + 'class' => AccessControl::className(), + 'ruleConfig' => [ + 'class' => AccessRuleFilter::className(), + ], + 'rules' => [ + [ + 'allow' => true, + 'roles' => ['admin'], + ], + ], + ], + ]; + } + + public function actionCreate() + { + /** @var User $user */ + $user = $this->make(User::class, ['scenario' => 'create']); + + /** @var UserEvent $event */ + $event = $this->make(UserEvent::class, [$user]); + + $this->make(AjaxRequestModelValidator::class, [$user])->validate(); + + $this->trigger(UserEvent::EVENT_BEFORE_CREATE, $event); + + if($user->load(Yii::$app->request->post())) { + /** @var UserCreateService $userCreateService */ + $userCreateService = $this->make(UserCreateService::class, [$user]); + $userCreateService->run(); + + $this->trigger(UserEvent::EVENT_AFTER_CREATE, $event); + + return $this->redirect(['update', 'id' => $user->id]); + } + + return $this->render('create', [ + 'user' => $user, + ]); + } +} diff --git a/lib/User/Event/FormEvent.php b/lib/User/Event/FormEvent.php new file mode 100644 index 0000000..5b30f8b --- /dev/null +++ b/lib/User/Event/FormEvent.php @@ -0,0 +1,30 @@ + + */ +class FormEvent extends Event +{ + protected $form; + + public function __construct(Model $form, array $config = []) + { + $this->form = $form; + parent::__construct($config); + } + + public function getForm() + { + return $this->form; + } +} + diff --git a/lib/User/Event/ProfileEvent.php b/lib/User/Event/ProfileEvent.php new file mode 100644 index 0000000..82b9e06 --- /dev/null +++ b/lib/User/Event/ProfileEvent.php @@ -0,0 +1,24 @@ +profile = $profile; + + return parent::__construct($config); + } + + public function getProfile() + { + return $this->profile; + } +} diff --git a/lib/User/Event/ResetPasswordEvent.php b/lib/User/Event/ResetPasswordEvent.php new file mode 100644 index 0000000..1505216 --- /dev/null +++ b/lib/User/Event/ResetPasswordEvent.php @@ -0,0 +1,31 @@ +form = $form; + $this->token = $token; + + parent::__construct($config); + } + + public function getForm() + { + return $this->form; + } + + public function getToken() + { + return $this->token; + } +} diff --git a/lib/User/Event/SocialNetworkAuthEvent.php b/lib/User/Event/SocialNetworkAuthEvent.php new file mode 100644 index 0000000..1c6a67a --- /dev/null +++ b/lib/User/Event/SocialNetworkAuthEvent.php @@ -0,0 +1,31 @@ +account = $account; + $this->client = $client; + + parent::__construct($config); + } + + public function getClient() + { + return $this->client; + } + + public function getAccount() + { + return $this->account; + } +} diff --git a/lib/User/Event/SocialNetworkConnectEvent.php b/lib/User/Event/SocialNetworkConnectEvent.php new file mode 100644 index 0000000..8f79d1f --- /dev/null +++ b/lib/User/Event/SocialNetworkConnectEvent.php @@ -0,0 +1,31 @@ +user = $user; + $this->account = $account; + + parent::__construct($config); + } + + public function getUser() + { + return $this->user; + } + + public function getAccount() + { + return $this->account; + } +} diff --git a/lib/User/Event/UserEvent.php b/lib/User/Event/UserEvent.php new file mode 100644 index 0000000..6b98f52 --- /dev/null +++ b/lib/User/Event/UserEvent.php @@ -0,0 +1,24 @@ +user = $user; + parent::__construct($config); + } + + public function getUser() + { + return $this->user; + } +} diff --git a/lib/User/Filter/AccessRuleFilter.php b/lib/User/Filter/AccessRuleFilter.php new file mode 100644 index 0000000..15258a1 --- /dev/null +++ b/lib/User/Filter/AccessRuleFilter.php @@ -0,0 +1,44 @@ +roles)) { + return true; + } + + foreach ($this->roles as $role) { + if ($role === '?') { + if ($user->getIsGuest()) { + return true; + } + } elseif ($role === '@') { + if (!$user->getIsGuest()) { + return true; + } + } elseif ($role === 'admin') { + + /** @var User $identity */ + $identity = $user->getIdentity(); + + if (!$user->getIsGuest() && $identity->getIsAdmin()) { + return true; + } + } elseif ($user->can($role)) { + return true; + } + } + + return false; + } +} diff --git a/lib/User/Form/RecoveryForm.php b/lib/User/Form/RecoveryForm.php new file mode 100644 index 0000000..7a1279e --- /dev/null +++ b/lib/User/Form/RecoveryForm.php @@ -0,0 +1,15 @@ + + */ +class RecoveryForm +{ + +} diff --git a/lib/User/Model/Profile.php b/lib/User/Model/Profile.php new file mode 100644 index 0000000..8d3df40 --- /dev/null +++ b/lib/User/Model/Profile.php @@ -0,0 +1,15 @@ + + */ +class Profile +{ + +} diff --git a/lib/User/Model/SocialNetworkAccount.php b/lib/User/Model/SocialNetworkAccount.php new file mode 100644 index 0000000..099f2ab --- /dev/null +++ b/lib/User/Model/SocialNetworkAccount.php @@ -0,0 +1,15 @@ + + */ +class SocialNetworkAccount +{ + +} diff --git a/lib/User/Model/Token.php b/lib/User/Model/Token.php new file mode 100644 index 0000000..4d3d3de --- /dev/null +++ b/lib/User/Model/Token.php @@ -0,0 +1,15 @@ + + */ +class Token +{ + +} diff --git a/lib/User/Module.php b/lib/User/Module.php index fe5cb0f..a89109e 100644 --- a/lib/User/Module.php +++ b/lib/User/Module.php @@ -60,6 +60,10 @@ class Module extends \yii\base\Module * @var string the route prefix */ public $prefix = 'user'; + /** + * @var array MailService configuration + */ + public $mailParams = []; /** * @var array the url rules (routes) diff --git a/lib/User/Service/MailService.php b/lib/User/Service/MailService.php index f561f84..d5187dd 100644 --- a/lib/User/Service/MailService.php +++ b/lib/User/Service/MailService.php @@ -2,11 +2,51 @@ namespace Da\User\Service; use Da\User\Contracts\ServiceInterface; +use yii\swiftmailer\Mailer; +use Yii; class MailService implements ServiceInterface { + protected $viewPath = '@Da/User/resources/views/mail'; + + protected $from; + protected $to; + protected $subject; + protected $view; + protected $params = []; + protected $mailer; + + /** + * MailService constructor. + * + * @param string $from + * @param string $to + * @param string $subject + * @param string $view + * @param array $params + * @param Mailer $mailer + */ + public function __construct($from, $to, $subject, $view, array $params, Mailer $mailer) + { + $this->from = $from; + $this->to = $to; + $this->subject = $subject; + $this->view = $view; + $this->params = $params; + $this->mailer = $mailer; + $this->mailer->setViewPath($this->viewPath); + $this->mailer->getView()->theme = Yii::$app->view->theme; + } + + /** + * @return bool + */ public function run() { - // TODO: Implement run() method. + return $this->mailer->compose(['html' => $this->view, 'text' => "text/{$this->view}"], $this->params) + ->setFrom($this->from) + ->setTo($this->to) + ->setSubject($this->subject) + ->send(); } } diff --git a/lib/User/Service/UserCreateService.php b/lib/User/Service/UserCreateService.php index 7256a99..fa2569a 100644 --- a/lib/User/Service/UserCreateService.php +++ b/lib/User/Service/UserCreateService.php @@ -6,25 +6,20 @@ use Da\User\Helper\SecurityHelper; use Da\User\Model\User; use yii\base\InvalidCallException; use Exception; +use yii\db\ActiveRecord; use yii\log\Logger; -/** - * - * UserCreateService.php - * - * Date: 4/12/16 - * Time: 2:55 - * @author Antonio Ramirez - */ class UserCreateService implements ServiceInterface { protected $model; protected $securityHelper; + protected $mailService; protected $logger; - public function __construct(User $model, SecurityHelper $securityHelper, Logger $logger) + public function __construct(User $model, MailService $mailService, SecurityHelper $securityHelper, Logger $logger) { $this->model = $model; + $this->mailService = $mailService; $this->securityHelper = $securityHelper; $this->logger = $logger; } @@ -48,7 +43,7 @@ class UserCreateService implements ServiceInterface ? $model->password : $this->securityHelper->generatePassword(8); - // TODO: Trigger BEFORE CREATE EVENT + $model->trigger(ActiveRecord::EVENT_BEFORE_INSERT); if (!$model->save()) { $transaction->rollBack(); @@ -56,8 +51,9 @@ class UserCreateService implements ServiceInterface return false; } - // TODO: Send welcome message + $model->trigger(ActiveRecord::EVENT_AFTER_INSERT); + $this->mailService->run(); $transaction->commit(); return true; diff --git a/lib/User/Strategy/InsecureEmailChangeStrategy.php b/lib/User/Strategy/InsecureEmailChangeStrategy.php index ecc9276..77c05c3 100644 --- a/lib/User/Strategy/InsecureEmailChangeStrategy.php +++ b/lib/User/Strategy/InsecureEmailChangeStrategy.php @@ -11,7 +11,7 @@ use Da\User\Contracts\StrategyInterface; * Time: 16:21 * @author Antonio Ramirez */ -class InsecuredEmailChangeStrategy implements StrategyInterface +class InsecureEmailChangeStrategy implements StrategyInterface { } diff --git a/lib/User/Traits/ContainerTrait.php b/lib/User/Traits/ContainerTrait.php index 5d3e156..96f821f 100644 --- a/lib/User/Traits/ContainerTrait.php +++ b/lib/User/Traits/ContainerTrait.php @@ -19,6 +19,20 @@ trait ContainerTrait return Yii::$container; } + /** + * Gets a class from the container. + * + * @param string $class he class name or an alias name (e.g. `foo`) that was previously registered via [[set()]] + * or [[setSingleton()]]. + * @param array $params + * + * @return object + */ + public function make($class, $params = []) + { + return $this->getDi()->get($class, $params); + } + /** * @return \Da\User\Helper\AuthHelper */ diff --git a/lib/User/Validator/AjaxRequestModelValidator.php b/lib/User/Validator/AjaxRequestModelValidator.php new file mode 100644 index 0000000..deed1bf --- /dev/null +++ b/lib/User/Validator/AjaxRequestModelValidator.php @@ -0,0 +1,31 @@ +model = $model; + } + + public function validate() + { + $request = Yii::$app->request; + + if($request->getIsAjax() && !$request->getIsPjax()) { + if($this->model->load($request->post())) { + Yii::$app->response->format = Response::FORMAT_JSON; + echo json_encode(ActiveForm::validate($this->model)); + Yii::$app->end(); + } + } + } +} diff --git a/lib/i18n/message.php b/lib/User/resources/i18n/message.php similarity index 100% rename from lib/i18n/message.php rename to lib/User/resources/i18n/message.php