Using Laravel 5, I need 2 different views for password reset email. The default path to the email view is emails.password. But upon some conditions, I want to send emails.password_alternative.
How can I do this? (with PasswordBroker from Laravel)
This is my current code:
public function __construct(Guard $auth, PasswordBroker $passwords)
{
$this->auth = $auth;
$this->passwords = $passwords;
}
public function sendReset(PasswordResetRequest $request)
{
//HERE : If something, use another email view instead of the default one from the config file
$response = $this->passwords->sendResetLink($request->only('email'), function($m)
{
$m->subject($this->getEmailSubject());
});
}
For anyone interested in Laravel 5.2 you can set a custom html and text email view for password reset by adding
config(['auth.passwords.users.email' => ['auth.emails.password.html', 'auth.emails.password.text']]);
to the PasswordController.php in the constructor before the middleware call.
This overrides the app/config/auth.php setup for the PasswordBroker.
Blade Template for the password reset email is then located at:
yourprojectname/resources/views/auth/emails/password/html.blade.php
yourprojectname/resources/views/auth/emails/password/text.blade.php
Took me long enough.
Credits:
http://ericlbarnes.com/2015/10/14/how-to-send-both-html-and-plain-text-password-reset-emails-in-laravel-5-1/
http://academe.co.uk/2014/01/laravel-multipart-registration-and-reminder-emails/
Using PasswordBroker and based on the Illuminate/Auth/Passwords/PasswordBroker.php class, the $emailView is a protected variable, so you can't change the value once the class is instantiated.
However, you have a couple of solutions:
You can create your own class that extends PasswordBroker and use that.
class MyPasswordBroker extends PasswordBroker {
public function setEmailView($view) {
$this->emailView = $view;
}
}
// (...)
public function __construct(Guard $auth, MyPasswordBroker $passwords)
{
$this->auth = $auth;
$this->passwords = $passwords;
}
public function sendReset(PasswordResetRequest $request)
{
if ($someConditionHere) {
$this->passwords->setEmailView('emails.password_alternative');
}
$response = $this->passwords->sendResetLink($request->only('email'), function($m)
{
$m->subject($this->getEmailSubject());
});
}
You could create the PasswordBroker within your method, without using Dependency Injection.
public function sendReset(PasswordResetRequest $request)
{
$emailView = 'emails.password';
if ($someConditionHere) {
$emailView = 'emails.password_alternative';
}
$passwords = new PasswordBroker(
App::make('TokenRepositoryInterface'),
App::make('UserProvider'),
App::make('MailerContract'),
$emailView
);
$response = $passwords->sendResetLink($request->only('email'), function($m)
{
$m->subject($this->getEmailSubject());
});
}
This is an uglier solution and if you have automated tests this will be a pain to work with.
Disclaimer: I've not tested any of this code.
Related
Why is it that whenever I redirect something through the constructor of my Codeigniter 4 controller is not working?
<?php namespace App\Controllers\Web\Auth;
class Register extends \App\Controllers\BaseController
{
function __construct()
{
if(session('username')){
return redirect()->to('/dashboard');
}
}
public function index()
{
// return view('welcome_message');
}
}
But if I put it inside index it's working as expected.
public function index()
{
if(session('username')){
return redirect()->to('/dashboard');
}
}
The thing is, I do not want to use it directly inside index because I it need on the other method of the same file.
As per the Codeigniter forum, you can no longer use the redirect method in the constructor to redirect to any of the controllers.
Please refer the below link for more information
https://forum.codeigniter.com/thread-74537.html
It clearly states that redirect() will return a class instance instead of setting a header and you cannot return an instance of another class while instantiating a different class in PHP.
So that's why you can't use redirect method in constructor.
Instead, what I can suggest to you is that use the header method and redirect to your desired controller.
<?php namespace App\Controllers\Web\Auth;
class Register extends \App\Controllers\BaseController
{
function __construct()
{
if(session('username')){
header('Location: /dashboard');
}
}
}
If that's not feasible or difficult to achieve you can follow the below code
<?php namespace App\Controllers\Web\Auth;
class Register extends \App\Controllers\BaseController
{
function __construct()
{
//call to session exists method
$this->is_session_available();
}
private function is_session_available(){
if(session('username')){
return redirect()->to('/dashboard');
}else{
return redirect()->to('/login');
}
}
}
The 2nd solution will be more interactive than the first one. And make sure the method is private. So that it should not be called from other class instances.
The community team has also given a solution to look into the controller filter.
https://codeigniter4.github.io/CodeIgniter4/incoming/filters.html
Please refer to the thread. I hope it may help you in finding a better solution.
In this case you shouldn't even be doing this kind of logic in your controllers. This should be done in a filter and not your controllers.
So you have your controller Register.
You should create a filter in your app/filters folder something like checkLogin.php
That filter should have the following structure:
<?php
namespace App\Filters;
use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
class CheckLogin implements FilterInterface
{
/**
* Check loggedIn to redirect page
*/
public function before(RequestInterface $request, $arguments = null)
{
$session = \Config\Services::session();
if (session('username')) {
return redirect()->to('/dashboard');
}
}
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
{
// Do something here
}
}
Then in your app/config/Filters.php your should add the filter to the desired controller.
public $aliases = [
'csrf' => \CodeIgniter\Filters\CSRF::class,
'toolbar' => \CodeIgniter\Filters\DebugToolbar::class,
'honeypot' => \CodeIgniter\Filters\Honeypot::class,
'checkLogin' => \App\Filters\CheckLogin::class,
];
// List filter aliases and any before/after uri patterns
public $filters = [
'checkLogin' => ['before' => ['Register']],
];
For more information on filters and how to use then please check the documentation.
https://codeigniter.com/user_guide/incoming/filters.html?highlight=filters
You can then even create filters to your other controllers that would redirect to this one in case the user is not logged in.
Codeigniter 4 using initController() to create constructor.
You can't use redirect() inside __construct() or initController() function.
But you can use $response parameter or $this->response to call redirect in initController() before call another function in controller;
<?php namespace App\Controllers\Web\Auth;
class Register extends \App\Controllers\BaseController
{
public function initController(\CodeIgniter\HTTP\RequestInterface $request, \CodeIgniter\HTTP\ResponseInterface $response, \Psr\Log\LoggerInterface $logger)
{
// Do Not Edit This Line
parent::initController($request, $response, $logger);
if(session('username')){
$response->redirect(base_url('dashboard')); // or use $this->response->redirect(base_url('dashboard'));
}
}
public function index()
{
// return view('welcome_message');
}
}
I just got into CI 4 and i had the same issue as you did, since i've been approaching the login system as with CI 3.
So here is the proper way of doing it right. Enjoy.
Codeigniter 4 do not has any return on Constructor, but you can use like this
public function __construct()
{
if (!session()->has('user_id')) {
header('location:/home');
exit();
}
}
Don't forget to use exit()
But, you better use Filters
I want to check if a user is logged in. Therefor I have an Class witch returns true or false. Now I want a middleware which checks if the user is logged in.
$app->get('/login', '\Controller\AccountController:loginGet')->add(Auth::class)->setName('login');
$app->post('/login', '\Controller\AccountController:loginPost')->add(Auth::class);
Auth Class
class Auth {
protected $ci;
private $account;
//Constructor
public function __construct(ContainerInterface $ci) {
$this->ci = $ci;
$this->account = new \Account($this->ci);
}
public function __invoke($request, \Slim\Http\Response $response, $next) {
if($this->account->login_check()) {
$response = $next($request, $response);
return $response;
} else {
//Redirect to Homepage
}
}
}
So when the user is logged in the page will render correctly. But when the user is not autoriesed I want to redirect to the homepage. But how?!
$response->withRedirect($router->pathFor('home');
This doesn't work!
You need to return the response. Don't forget that the request and response objects are immutable.
return $response = $response->withRedirect(...);
I have a similar auth middleware and this is how I do it which also adds a 403 (unauthorized) header.
$uri = $request->getUri()->withPath($this->router->pathFor('home'));
return $response = $response->withRedirect($uri, 403);
Building off of tflight's answer, you will need to do the following to make everything work as intended. I tried to submit this as a revision, given that the code provided in tflight's answer would not work on the framework out of the box, but it was declined, so providing it in a separate answer:
You will need the following addition to your middleware:
protected $router;
public function __construct($router)
{
$this->router = $router;
}
Additionally, when declaring the middleware, you would need to add the following the constructor:
$app->getContainer()->get('router')
Something similar to:
$app->add(new YourMiddleware($app->getContainer()->get('router')));
Without these changes, the solution will not work and you will get an error that $this->router does not exist.
With those changes in place you can then utilize the code provided by tflight
$uri = $request->getUri()->withPath($this->router->pathFor('home'));
return $response = $response->withRedirect($uri, 403);
make basic Middleware and inject $container into it so all your middleware can extends it.
Class Middleware
{
protected $container;
public function __construct($container)
{
$this->container = $container;
}
public function __get($property)
{
if (isset($this->container->{$property})) {
return $this->container->{$property};
}
// error
}
}
make sure your Auth middleware on the same folder with basic middleware or you can use namespacing.
class Auth extends Middleware
{
public function __invoke($request, $response, $next)
{
if (!$this->account->login_check()) {
return $response->withRedirect($this->router->pathFor('home'));
}
return $next($request, $response);
}
}
Use:
http_response_code(303);
header('Location: ' . $url);
exit;
I have made the following custom guard:
<?php
namespace App\Auth;
use Illuminate\Http\Request;
use Illuminate\Auth\GuardHelpers;
use Illuminate\Contracts\Auth\Guard;
class LicenseGuard implements Guard
{
use GuardHelpers;
protected $request;
public function __construct(LicenseUserProvider $provider, Request $request)
{
$this->provider = $provider;
$this->request = $request;
}
public function user ()
{
// If we've already retrieved the user for the current request we can just
// return it back immediately. We do not want to fetch the user data on
// every call to this method because that would be tremendously slow.
if (!is_null($this->user))
return $this->user;
$user = null;
$licenseKey = $this->request->json('license_key');
if (!empty($licenseKey)) {
$user = $this->provider->retrieveByLicense($licenseKey);
}
return $this->user = $user;
}
public function validate (Array $credentials = [])
{
/* Validate code */
}
}
?>
In my middleware i have defined the following:
<?php
if($this->auth->guard($guard)->quest())
return response('You have entered an unknown license key', 401);
The error that i am getting is:
Fatal error: Call to undefined method App\Auth\LicenseGuard::quest()
I am using the default GuardHelper trait which has the "quest" method, i just can't find out why this is happening.
I am using PHP7 and Lumen 5.2
Not sure what you are doing there my friend, but I assume quest "isn't the droids you are looking for".
I had modified a vendor file of Laravel placed at
/vendor/laravel/framework/src/Illuminate/Auth/Guard.php
but it will be overwritten upon updating Laravel.
I'm looking for a way to put the code somewhere in my /app to prevent the overwrite.
The function modified is
public function UpdateSession() {
$this->session->set('type', $type); //==> Set Client Type
}
Also there's a new function on the file:
public function type() {
return $this->session->get('type'); //==> Get Client Type
}
Codes above are called in many places in my application.
Any idea?
Steps:
1- create myGuard.php
class myGuard extends Guard
{
public function login(Authenticatable $user, $remember = false)
{
$this->updateSession($user->getAuthIdentifier(), $user->type);
if ($remember) {
$this->createRememberTokenIfDoesntExist($user);
$this->queueRecallerCookie($user);
}
$this->fireLoginEvent($user, $remember);
$this->setUser($user);
}
protected function updateSession($id, $type = null)
{
$this->session->set($this->getName(), $id);
$this->session->set('type', $type);
$this->session->migrate(true);
}
public function type()
{
return $this->session->get('type');
}
}
2- in AppServiceProvider or new service provider or routes.php:
public function boot()
{
Auth::extend(
'customAuth',
function ($app) {
$model = $app['config']['auth.model'];
$provider = new EloquentUserProvider($app['hash'], $model);
return new myGuard($provider, App::make('session.store'));
}
);
}
3- in config/auth.php
'driver' => 'customAuth',
4- now you can use this
Auth::type();
This doesn't look like you need to update the Guard at all. As far as I can see you are only trying to retrieve data from the session. And that's definitely no business for the Guard itself.
You have multiple ways of accessing the session yourself:
// via Session-Facade
$type = Session::get('type');
Session::put('type', $type);
// via Laravels helper function
$type = session('type'); // get
session()->put('type', $type); // set
session(['type' => $type']); // alternative
I am not sure if I am using this correctly, but I am utilising the requests in Laravel 5, to check if the user is logged in and if he is the owner of an object. To do this I need to get the actual object in the request class, but then I need to get the same object in the controller?
So instead of fetching it twice, I thought, why not just set the object as a variable on the request class, making it accessible to the controller?
It works, but I feel dirty? Is there a more appropriate way to handle this?
Ex.
Request Class
class DeleteCommentRequest extends Request {
var $comment = null;
public function authorize() {
$this->comment = comment::find(Input::get('comment_id'));
$user = Auth::user();
if($this->comment->user == $user)
return true;
return false;
}
public function rules() {
return [
'comment_id' => 'required|exists:recipes_comments,id'
];
}
}
Ex. Controller:
public function postDeleteComment(DeleteCommentRequest $request) {
$comment = $request->comment;
$comment->delete();
return $comment;
}
So what is my question? How do I best handle having to use the object twice when using the new Laravel 5 requests? Am I possibly overextending the functionality of the application? Is it ok to store the object in the application class so I can reach it later in my controller?
I would require ownership on the query itself and then check if the collection is empty.
class DeleteCommentRequest extends Request {
var $comment = null;
public function authorize() {
$this->comment = comment::where('id',Input::get('comment_id'))->where('user_id',Auth::id())->first();
if($this->comment->is_empty())
return false;
return true;
}
public function rules() {
return [
'comment_id' => 'required|exists:recipes_comments,id'
];
}
}
Since you're wanting to use the Model in two different places, but only query it once I would recommenced you use route-model binding.
In your RouteServiceProvider class (or any relevant provider) you'll want to bind the comment query from inside the boot method. The first parameter of bind() will be value that matches the wildcard in your route.
public function boot()
{
app()->router->bind( 'comment_id', function ($comment_id) {
return comment::where('id',$comment_id)->where('user_id',Auth::id())->first();
} );
}
Once that's set up you can access the Model from your DeleteCommentRequest like so
$this->comment_id
Note: The variable is Comment_id because that's what matches your route, but it will contain the actual model.
From your controller you just inject it like so
public function postDeleteComment(Comment $comment, DeleteCommentRequest $request) {
$comment->delete();
return $comment;
}