dynamic mail configuration using Laravel - laravel

In my Laravel application, I am trying to send mail notification based on the company_id of the logged in user:
I have this:
$mail=DB::table('mail_settings')->first();
$config = array(
'driver' => $mail->driver,
'host' => $mail->host,
'port' => $mail->port,
'from' => array('address' => $mail->from_address, 'name' => $mail->from_name),
'encryption' => $mail->encryption,
'username' => $mail->username,
'password' => $mail->password,
'sendmail' => '/usr/sbin/sendmail -bs',
'pretend' => false
);
Config::set('mail',$config);
Models
class Company extends Model
{
protected $table = 'companies';
protected $fillable = [
'id',
'organization_name'
];
}
class User extends Authenticatable
{
protected $fillable = [
'name',
'company_id',
'email',
];
}
Is there any way to override default mail configuration (in app/config/mail.php) on-the-fly (e.g. configuration is stored in database) before mailer transport is created?
Thanks
Is there any way to recreate laravel swiftmailer transport so it can pick up updated config values?

The Mailer class is created in the Illuminate\Mail\MailManager class's resolve() method. If you want to dynamically create a mailer, you need to adapt this function in your Controller to use your $config array and return a Mailer from which you could chain the usual methods.
protected function resolve($name)
{
$config = $this->getConfig($name);
if (is_null($config)) {
throw new InvalidArgumentException("Mailer [{$name}] is not defined.");
}
// Once we have created the mailer instance we will set a container instance
// on the mailer. This allows us to resolve mailer classes via containers
// for maximum testability on said classes instead of passing Closures.
$mailer = new Mailer(
$name,
$this->app['view'],
$this->createSwiftMailer($config),
$this->app['events']
);
if ($this->app->bound('queue')) {
$mailer->setQueue($this->app['queue']);
}
// Next we will set all of the global addresses on this mailer, which allows
// for easy unification of all "from" addresses as well as easy debugging
// of sent messages since these will be sent to a single email address.
foreach (['from', 'reply_to', 'to', 'return_path'] as $type) {
$this->setGlobalAddress($mailer, $config, $type);
}
return $mailer;
}

Related

full access log in laravel 5.8 with session (error bag + flash) data

I'm trying to implement a full access log in a Laravel 5.8 project and that's what I've come up so far: a table named access_logs in a secondary database so than non-relevant log data won't make the db bulky during backup, and custom primary key with no AUTO_INCREMET so can be purged old data without worrying about resetting incremental id while new data is being created. the model is following:
class AccessLog extends Model
{
public $timestamps = true;
public $incrementing = false; // custom id, purgeable
protected $connection= 'mysql2';
protected $fillable = [
'id', // custom id, purgeable
'auth_guard',
'auth_id',
'url',
'method',
'referer',
'request',
'session',
'additional_data',
'ip',
];
}
and the DB writing part in the middleware is following:
$logData = [
'id' => join('_', [
time(),
Auth::user()->id ?? 0,
rand(100000, 999999),
]),
'ip' => json_encode(request()->ips() ?? ''),
'auth_guard' => auth()->getDefaultDriver() ?? null,
'auth_id' => auth()->user()->id ?? null,
'url' => url()->current(),
'method' => request()->method(),
'referer' => url()->previous(),
'request' => json_encode([
$this->removeKeys(request()->all(), [
'password',
]),
$_FILES ?? [],
request()->headers->all(),
]),
'session' => json_encode(session()->all()),
];
AccessLog::create($logData);
the middleware is registered in the kernel file under protected $middlewareGroups['web'].
now my question is I'm trying to log form validation data (ErrorBag?) as well as flash messages, but it seems those do not exists or saved with the session data (session()->all()), how to include/save those with the log?

Understand a concept, is Service Provider the correct use case?

I have created a service provider that takes data from GITHUB API and stores it in a table in a database. Quite a simple thing but I'm wondering if is this how I'm should be using Service Providers?
The second question is about extending this, in reality I want to add more platforms and API's that do this (I currently have one other platform working) but I currently have it set up as a separate service provider. There's a lot of similarities, only some differences in what the API data returns - I feel like I should be abiding by DRY but think this could get complicated when more and more API's get added (def not KISS).
I really want to confirm this before I spend more time extending this platform so appreciate any advice!
<?php
namespace App\Providers;
use GuzzleHttp\Client;
use App\Projects;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Auth;
class GithubServiceProvider extends ServiceProvider
{
/**
* Register services.
*
* #return void
*/
public function register()
{
//
$this->app->singleton('github_client', function ($app) {
try {
$client = new Client();
$code = $_GET['code'] ?? false;
$res = $client->post('https://github.com/login/oauth/access_token', [
'headers' => [
'Accept' => 'application/json',
],
'query' => [
'client_id' => env('GITHUB_ID'),
'client_secret' => env('GITHUB_SECRET'),
'code'=>$code
]
]
);
} catch(\GuzzleHttp\Exception\RequestException $e) {
$response = $e->getResponse();
throw new \App\Exceptions\CustomException($response->getStatusCode());
}
$username = Auth::user()->username;
$user_id = Auth::user()->id;
if($res->getBody() && !isset(json_decode($res->getBody())->error)):
$access_token = json_decode($res->getBody())->access_token;
$projects = json_decode($client->get('https://api.github.com/user/repos', [
'headers' => [
'Authorization'=> 'token ' . $access_token
]
])->getBody());
$i = 0;
foreach($projects as $project):
$i++;
// dd($project->images);
Projects::updateOrCreate(
[
'platform' => 'github',
'user_id' => $user_id,
'project_id' => $project->id
],
[
'platform' => 'github',
'sorting' => $i,
'user_id' => $user_id,
'title' => $project->name,
'description' => strip_tags($project->description),
'images' => '',
'url' => $project->html_url,
'project_id' => $project->id
]
);
endforeach;
endif;
});
}
/**
* Bootstrap services.
*
* #return void
*/
public function boot()
{
//
}
}
A service provider adds services to the service (IoC) container.
In your case, it seems you want code that gets data from third-party providers in a consistent manner. So you probably want to define an interface for this, and then have individual classes that implement that interface (i.e. GithubProjectRepository, BitbucketProjectRepository, etc).
You’d probably use a service provider to register configured instances of GitHub, Bitbucket’s, etc SDKs. This would be instantiated and configured from your config files. You can then type-hint these SDKs in your class’s constructors and the container will give you configured instances instead of having to new-up all over your application.

Laravel avoid duplicate entry from model

I'm building a Laravel API. I have a models called Reservations. I want to avoid that a user creates two reservations for the same product and time period.
I have the following:
$reservation = Reservation::firstOrCreate([
'listing_id' => $request->listing_id,
'user_id_from' => $request->user_id_from,
'start_date' => $request->start_date,
'end_date' => $request->end_date,
]);
Edit after comments:
I'm also using validation
$validator = Validator::make($request->all(), [
'listing_id' => 'required|exists:listings,id',
'user_id_from' => 'required|exists:users,id',
'start_date' => 'required|date_format:"Y-m-d"|after:today',
'end_date' => 'required|date_format:"Y-m-d"|after:start_date'
]);
if ($validator->fails()) {
return response()->json(['error' => 'Validation failed'], 403);
}
Validation is working properly.
End of Edit
In my model I have casted the start_date and end_date as dates.
class Reservation extends Model
{
protected $fillable = ['listing_id', 'start_date', 'end_date'];
protected $dates = [
'start_date',
'end_date'
];
....
....
Documentation says:
The firstOrCreate method will attempt to locate a database record
using the given column / value pairs
However I notice that I'm still able to insert entries with the same attributes.
Any idea what I'm doing wrong or suggestions to fix it?
Probably there's a better way than this, but you can create an static method on Reservation to do this, like:
public static function createWithRules($data) {
$exists = $this->where('product_id', $data['product_id'])->whereBetween(*date logic that i don't remember right now*)->first();
if(!$exists) {
* insert logic *
} else {
* product with date exists *
}
}
So you can call Reservation::createWithRules($data)
You can achieve this using Laravel's built in ValidateRequest class. The most simple use-case for this validation, is to call it directly in your store() method like this:
public function store(){
$this->validate($request, [
'listing_id' => 'required|unique,
'start_date' => 'required|unique,
//... and so on
], $this->messages);
$reservation = Reservation::firstOrCreate([
'listing_id' => $request->listing_id,
'user_id_from' => $request->user_id_from,
'start_date' => $request->start_date,
'end_date' => $request->end_date,
]);
}
With this, you're validating users $request with by saying that specified columns are required and that they need to be unique, in order for validation to pass.
In your controller, you can also create messages function to display error messages, if the condition isn't met.
private $messages = [
'listing_id.required' => 'Listing_id is required',
'title.unique' => 'Listing_id already exists',
//... and so on
];
You can also achieve this by creating a new custom validation class:
php artisan make:request StoreReservation
The generated class will be placed in the app/Http/Requests directory. Now, you can add a few validation rules to the rules method:
public function rules()
{
return [
'listing_id' => 'required|unique,
'start_date' => 'required|unique,
//... and so on
];
}
All you need to do now is type-hint the request on your controller method. The incoming form request is validated before the controller method is called, meaning you do not need to clutter your controller with any validation logic:
public function store(StoreReservation $request)
{
// The incoming request is valid...
// Retrieve the validated input data...
$validated = $request->validated();
}
If you have any additional question about this, feel free to ask. Source: Laravel official documentation.

Laravel Error: Object of class Torann\GeoIP\Location could not be converted to string

I am getting error on send Location Data To Database Using Laravel GeoIP::getLocation('2405:204:970a:d9b3:10a3:5280:9064:3f31'),
Error:
Object of class Torann\GeoIP\Location could not be converted to string
This Is My Auth LoginController. How to Insert GeoIP Loacation data into database. Please Help me
If i remove this code 'current_location' => GeoIP::getLocation('2405:204:970a:d9b3:10a3:5280:9064:3f31'), i am no longer getting this error, every data inserted into database but i add this code i am getting this error
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use Jenssegers\Agent\Agent;
use Carbon\Carbon;
use App\User;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Closure;
use GeoIP;
use Location;
class LoginController extends Controller
{
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/
use AuthenticatesUsers;
function authenticated(Request $request, $user)
{
// Chrome, IE, Safari, Firefox, ...
$agent = new Agent();
$browser = $agent->browser();
// Ubuntu, Windows, OS X, ...
$platform = $agent->platform();
$user->update([
'last_signin' => Carbon::now()->toDateTimeString(),
'ip_address' => $request->getClientIp(),
'browser_login' => $agent->browser(),
'browser_version' => $agent->version($browser),
'device_login' => $agent->platform(),
'device_version' => $agent->version($platform),
'current_location' => GeoIP::getLocation('2405:204:970a:d9b3:10a3:5280:9064:3f31'),
'language' => $agent->languages(),
'root' => $agent->robot(),
'https' => $request->server('HTTP_USER_AGENT'),
]);
}
/**
* Where to redirect users after login.
*
* #var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('guest',['except'=>['logout', 'userLogout', 'profile']]);
}
public function userLogout()
{
Auth::guard('web')->logout();
return redirect('/');
}
}
Auth Route :
//User Auth Route Function
Auth::routes();
This is happending because GeoIP::getLocation('2405:204:970a:d9b3:10a3:5280:9064:3f31') returns an instance of Torann\GeoIP\Location and you are trying to save it as a String.
Checking the documentation of this object it has this shape:
\Torann\GeoIP\Location {
#attributes:array [
'ip' => '232.223.11.11',
'iso_code' => 'US',
'country' => 'United States',
'city' => 'New Haven',
'state' => 'CT',
'state_name' => 'Connecticut',
'postal_code' => '06510',
'lat' => 41.28,
'lon' => -72.88,
'timezone' => 'America/New_York',
'continent' => 'NA',
'currency' => 'USD',
'default' => false,
]
}
You have to choose a way to represent this location as a String, a possible way can be to save the latitude and the longitude separately.
If you need to use only one column at the DB, you can check some GeoHashing implementations skthon/geogash.
You Might be trying to use getLocation method from wrong instance.
1.) Try as below way :
"use Torann\GeoIP\GeoIPFacade as GeoIP"
$location = GeoIP::getLocation();
2.) Or try as Geoip package documentation suggest here (http://lyften.com/projects/laravel-geoip/doc/methods.html)
from this instance \Torann\GeoIP\GeoIP and then use geoip()->getLocation('27.974.399.65');
This seems to be an issue in the current_location field and how it is typed in your database. From what I read, I guess your field is defined a string, and when trying to save your record to the database, it fails since the data you're trying to save is an Location object.
I would recommend changing your current_location column in your database to make it a json type.
Then you'd be able to insert your data as:
$user->update([
'last_signin' => Carbon::now()->toDateTimeString(),
'ip_address' => $request->getClientIp(),
'browser_login' => $agent->browser(),
'browser_version' => $agent->version($browser),
'device_login' => $agent->platform(),
'device_version' => $agent->version($platform),
'current_location' => json_encode(GeoIP::getLocation('2405:204:970a:d9b3:10a3:5280:9064:3f31')->toArray()),
'language' => $agent->languages(),
'root' => $agent->robot(),
'https' => $request->server('HTTP_USER_AGENT'),
]);

Laravel Email Verification in custom registeration controller not working

I have made a registration form in my frontend ( Not a laravel default registration form ) . I have used Laravel Email Verification
I have implements MustVerifyEmail in User Model
But In that custom registraion form in my frontend when i hit submit it redirects the page to /admin/home but email is not been sending when i register but If I click on resend email again it sends the email . I want to fix that
Does anyone know how ?
Do I have to implements MustVerifyEmail to that controller too or what ?
IGNORE THAT CITY AND ROOM IN THE FUNCTION !!!!!
class QuickRegisterController extends Controller
{
public function quickList(Request $request)
{
$this->validate($request ,[
'features' => 'required',
'rommies' => 'required',
'price' => 'required',
'avaiability' => 'required',
'utility' => 'required',
'owner_working_email' => 'required',
'address' => 'required',
'exact_address' => 'required',
'owner_of_the_room' => 'required',
]);
$user = User::firstOrCreate([
'name' => $request->owner_of_the_room,
'email' => $request->owner_working_email,
'password' => bcrypt($request->password),
'role_id' => config('quickadmin.default_role_id'),
]);
\Auth::loginUsingId($user->id);
if (\Auth::check()) {
$city = TotalCity::firstOrCreate([
'name' => $request->city,
'created_by_id' => \Auth::user()->id,
]);
if ($city) {
$room = new MyRoom;
$room->location_id = $city->id;
$room->features = $request->features;
$room->rommies = $request->rommies;
$room->price = $request->price;
$room->utility = $request->utility;
$room->avaiability = $request->avaiability;
$room->owner_woring_email = $request->owner_working_email;
$room->address = $request->address;
$room->exact_address = $request->exact_address;
$room->owner_of_the_room = $request->owner_of_the_room;
$room->save();
}
return redirect('/admin/home');
}
else {
return redirect()->back()->with('Form Submission Failed . Try Again Later');
}
}
}
If you look into the RegisterController that Laravel provides with its auth scaffolding, not sure if you are using that or not, it implements the RegistersUsers trait. That trait implements an event that is triggered upon registration. You can use the RegistersUsers trait in your class or create your own custom event.
I'll show you how to use the trait.
At the top of your file:
use Illuminate\Foundation\Auth\RegistersUsers;
Right inside your class:
use RegistersUsers;
For Example:
use Illuminate\Foundation\Auth\RegistersUsers;
class QuickRegisterController extends Controller
{
use RegistersUsers;
// ....
}
You'll need to set up the route as well.
// The register method is coming from the trait
Route::post('/register', 'QuickRegisterController#register');
Also,
You'll want to update your method name to create, the trait calls a create method from the implementor, which is where the user gets created and then the event is triggered, and in that create a method just return the new user, instead of redirecting back.
This might not be all you need to do to get this working, but it will get you started. If you are interested in creating your own event:
https://laravel.com/docs/5.8/events
Or, as #Bipin Regmi pointed out you can just use the event that is being used in the trait
event(new \Illuminate\Auth\Events\Registered($user = $this->create($request->all())));

Resources