I have created Mailable Class in Laravel 5.3 which calls the view. However, I need to pass some variables from my Controller to the Mailable Class and then use these values inside the View. This is my set-up:
Controller:
$mailData = array(
'userId' => $result['user_id'],
'action' => $result['user_action'],
'object' => $result['user_object'],
);
Mail::send(new Notification($mailData));
Mailable:
class Notification extends Mailable
{
use Queueable, SerializesModels;
protected $mailData;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct($mailData)
{
$this->$mailData = $mailData;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
// Array for Blade
$input = array(
'action' => $mailData['action'],
'object' => $mailData['object'],
);
return $this->view('emails.notification')
->with([
'inputs' => $this->input,
]);
}
}
The above gives me the error:
ErrorException in Notification.php line 25:
Array to string conversion
Referring to the construct line in Mailable Class:
$this->$mailData = $mailData;
What have I got wrong here? How do I correctly pass array values from Controller to Mailable and then use with to pass them on to the View?
Try this:
public $mailData;
public function __construct($mailData)
{
$this->mailData = $mailData;
}
public function build()
{
// Array for Blade
$input = array(
'action' => $this->mailData['action'],
'object' => $this->mailData['object'],
);
return $this->view('emails.notification')
->with([
'inputs' => $input,
]);
}
Docs
Related
I would like to know how to include an attachment when sending an email using laravel and markdown.
This is the class InvoiceEmail extends Mailable
protected $data;
public function __construct($data)
{
$this->data = $data;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
return $this->from('tes#test.com')->subject('Order')->markdown('emails.invoiceEmail')->with('data',$this->data);
}
In the controller called OrderController I send the email:
$customerPDF = 'file.pdf';
$data = array(
'name' => $request->vendor_name,
'company' => $request->company,
'vat'=> $request->vat,
'category' => $request->category,
'url' => Route('vendor.reg.enable.account',$enableCode)
);
Mail::to($request->email)->send(new InvoiceEmail($data));
My question is: how can I attach the customerPDF?
Try to add attach method call to InvoiceEmail#build
public function build()
{
return $this->from('tes#test.com')
->subject('Order')
->markdown('emails.invoiceEmail')
->with('data',$this->data)
->attach(asset($this->data->pdf_file), ['mime' => 'application/pdf']);
}
I am working on Laravel passport api in which i am using spatie package for user role's and permission's. I have to perform certain operation ('store','view','update','delete') based on user permission's.
For this purpose i have created a trait and used in controller but it is not working correctly.
On every api request it throw's an exception "This action is unauthorized" either the user has permission or not.
Authorize Trait :
<?php
namespace App;
/*
* A trait to handle authorization based on users permissions for given controller
*/
trait Authorizable
{
/**
* Abilities
*
* #var array
*/
private $abilities = [
'index' => 'view',
'edit' => 'edit',
'show' => 'view',
'update' => 'edit',
'create' => 'add',
'store' => 'add',
'destroy' => 'delete'
];
/**
* Override of callAction to perform the authorization before it calls the action
*
* #param $method
* #param $parameters
* #return mixed
*/
public function callAction($method, $parameters)
{
if( $ability = $this->getAbility($method) ) {
$this->authorize($ability);
}
return parent::callAction($method, $parameters);
}
/**
* Get ability
*
* #param $method
* #return null|string
*/
public function getAbility($method)
{
$routeName = explode('.', \Request::route()->getName());
$action = array_get($this->getAbilities(), $method);
return $action ? $action . '_' . $routeName[0] : null;
}
/**
* #return array
*/
private function getAbilities()
{
return $this->abilities;
}
/**
* #param array $abilities
*/
public function setAbilities($abilities)
{
$this->abilities = $abilities;
}
}
Routes:
Route::middleware('auth:api')->group(function () {
Route::post('user', 'ApiController#user');
Route::post('view_department', 'DepartmentController#index');
Route::post('add_department', 'DepartmentController#store');
Route::post('edit_department', 'DepartmentController#update');
Route::post('delete_department', 'DepartmentController#destroy');
Route::post('/logout', 'ApiController#logout');
}); // auth middleware ends
Controller:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;
use App\User;
use App\Authorizable;
use Illuminate\Support\Facades\Validator;
use App\Department;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
class DepartmentController extends Controller
{
use Authorizable;
//
public function index(Request $request) {
// return response
return response()->json([
'success' => 'You have the permission to view departments!']);
}
//
public function store(Request $request) {
// validate the posted data
$validator = Validator::make($request->all(), [
'name' => 'required|string|unique:departments',
]);
// return errors
if ($validator->fails())
{
return response(['errors'=>$validator->errors()->all()]);
}
$department = new Department;
$department->name = $request->name;
$department->save();
// return response
return response()->json([
'success' => 'Successfully created department!']);
}
}
I am badly stack at it, don't know where i am going wrong. I would highly appreciate if anyone guide me through this.
Thanks,
IMHO, the current Database channel for saving notifications in Laravel is really bad design:
You can't use foreign key cascades on items for cleaning up notifications of a deleted item for example
Searching custom attributes in the data column (casted to Array) is not optimal
How would you go about extending the DatabaseNotification Model in vendor package?
I would like to add columns event_id, question_id, user_id (the user that created the notification) etc... to the default laravel notifications table
How do you override the send function to include more columns?
In:
vendor/laravel/framework/src/Illuminate/Notifications/Channels/DatabaseChannel.php
The code:
class DatabaseChannel
{
/**
* Send the given notification.
*
* #param mixed $notifiable
* #param \Illuminate\Notifications\Notification $notification
* #return \Illuminate\Database\Eloquent\Model
*/
public function send($notifiable, Notification $notification)
{
return $notifiable->routeNotificationFor('database')->create([
'id' => $notification->id,
'type' => get_class($notification),
\\I want to add these
'user_id' => \Auth::user()->id,
'event_id' => $notification->type =='event' ? $notification->id : null,
'question_id' => $notification->type =='question' ? $notification->id : null,
\\End adding new columns
'data' => $this->getData($notifiable, $notification),
'read_at' => null,
]);
}
}
To create a custom Notification Channel:
First, create a Class in App\Notifications for example:
<?php
namespace App\Notifications;
use Illuminate\Notifications\Notification;
class CustomDbChannel
{
public function send($notifiable, Notification $notification)
{
$data = $notification->toDatabase($notifiable);
return $notifiable->routeNotificationFor('database')->create([
'id' => $notification->id,
//customize here
'answer_id' => $data['answer_id'], //<-- comes from toDatabase() Method below
'user_id'=> \Auth::user()->id,
'type' => get_class($notification),
'data' => $data,
'read_at' => null,
]);
}
}
Second, use this channel in the via method in the Notification class:
<?php
namespace App\Notifications;
use Illuminate\Notifications\Notification;
use App\Notifications\CustomDbChannel;
class NewAnswerPosted extends Notification
{
private $answer;
public function __construct($answer)
{
$this->answer = $answer;
}
public function via($notifiable)
{
return [CustomDbChannel::class]; //<-- important custom Channel defined here
}
public function toDatabase($notifiable)
{
return [
'type' => 'some data',
'title' => 'other data',
'url' => 'other data',
'answer_id' => $this->answer->id //<-- send the id here
];
}
}
Create and use your own Notification model and Notifiable trait and then use your own Notifiable trait in your (User) models.
App\Notifiable.php:
namespace App;
use Illuminate\Notifications\Notifiable as BaseNotifiable;
trait Notifiable
{
use BaseNotifiable;
/**
* Get the entity's notifications.
*/
public function notifications()
{
return $this->morphMany(Notification::class, 'notifiable')
->orderBy('created_at', 'desc');
}
}
App\Notification.php:
namespace App;
use Illuminate\Notifications\DatabaseNotification;
class Notification extends DatabaseNotification
{
// ...
}
App\User.php:
namespace App;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use Notifiable;
// ...
}
An example for #cweiske response.
If you really need extends the Illuminate\Notifications\Channels\DatabaseChannel not creating a new Channel you can:
Extends the channel:
<?php
namespace App\Notifications;
use Illuminate\Notifications\Channels\DatabaseChannel as BaseDatabaseChannel;
use Illuminate\Notifications\Notification;
class MyDatabaseChannel extends BaseDatabaseChannel
{
/**
* Send the given notification.
*
* #param mixed $notifiable
* #param \Illuminate\Notifications\Notification $notification
* #return \Illuminate\Database\Eloquent\Model
*/
public function send($notifiable, Notification $notification)
{
$adminNotificationId = null;
if (method_exists($notification, 'getAdminNotificationId')) {
$adminNotificationId = $notification->getAdminNotificationId();
}
return $notifiable->routeNotificationFor('database')->create([
'id' => $notification->id,
'type' => get_class($notification),
'data' => $this->getData($notifiable, $notification),
// ** New custom field **
'admin_notification_id' => $adminNotificationId,
'read_at' => null,
]);
}
}
And register the Illuminate\Notifications\Channels\DatabaseChannel on application container again:
app\Providers\AppServiceProvider.php
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
//
}
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->app->bind(
Illuminate\Notifications\Channels\DatabaseChannel::class,
App\Notifications\MyDatabaseChannel::class
);
}
}
Now when the Illuminate\Notifications\ChannelManager try createDatabaseDriver will return your registered database driver.
More one option to solve this problem!
Unlike "Bassem El Hachem", I wanted to keep the database keyword in the via() methods.
So in addition to a custom DatabaseChannel, I also wrote my own ChannelManager that returns my own DatabaseChannel in the createDatabaseDriver() method.
In my apps' ServiceProvider::register() method, I overwrote the singleton for the original ChannelManager class to return my custom manager.
I solved a similar problem by customizing notification class:
create the class for this action:
artisan make:notification NewQuestion
inside it:
public function __construct($user,$question)
{
$this->user=$user;
$this->question=$question;
}
...
public function toDatabase($notifiable){
$data=[
'question'=>$this->(array)$this->question->getAttributes(),
'user'=>$this->(array)$this->user->getAttributes()
];
return $data;
}
then you can access proper data in view or controller like this:
#if($notification->type=='App\Notifications\UserRegistered')
New question from {{$notification->data['user']['name']}}
#endif
I am using hashid to hash the id parameters in url. I have it set up in my model to automatically hash the id. This is working fine. My problem is decoding the hash in a middleware returns null. I'm not sure if this is a problem with my middleware or because of the hashing.
Model:
public function getIdAttribute($value)
{
$hashids = new \Hashids\Hashids(env('APP_KEY'),10);
return $hashids->encode($value);
}
Middleware:
<?php
namespace App\Http\Middleware;
use Closure;
class HashIdsDecode
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
dd($request->id); //Returns null on show method - example localhost:8000/invoices/afewRfshTl
if($request->has('id'))
{
$hashids = new \Hashids\Hashids(env('APP_KEY'),10);
dd($hashids->decode($request->input('id')));
}
return $next($request);
}
}
Route:
Route::resource('invoices','InvoiceController');
Controller:
public function show($id)
{
$invoice = Invoice::find($id);
return view('invoices.show', [
'invoice' => $invoice,
'page_title' => ' Invoices',
'page_description' => 'View Invoice',
]);
}
NOTE: if I bypass the middleware and do it directly in my controller like this it works but it requires me to repeat myself over and over and probably not the best way to do it.
public function show($id)
{
$hashids = new \Hashids\Hashids(env('APP_KEY'),10);
$invoiceId = $hashids->decode($id)[0];
$invoice = Invoice::find($invoiceId);
return view('invoices.show', [
'invoice' => $invoice,
'page_title' => ' Invoices',
'page_description' => 'View Invoice',
]);
}
Personally, I would be more inclined to write a model trait. You can then use the trait on only the models required, rather than assuming every ID argument in a request is a Hash ID.
E.g.
namespace App\Models\Traits;
use Hashids\Hashids;
use Illuminate\Database\Eloquent\Builder;
trait HashedId
{
public function scopeHashId(Builder $query, $id)
{
$hashIds = new Hashids(env('APP_KEY'), 10);
$id = $hashIds->decode($id)[0];
return $query->where('id', $id);
}
}
Then to use it, you'd use the trait on your Invoice model (edit):
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Invoice extends Model
{
use \App\Models\Traits\HashedId;
// ...
}
And execute the following query in your controller:
public function show($id)
{
$invoice = Invoice::hashId($id)->firstOrFail();
return view('invoices.show', [
'invoice' => $invoice,
'page_title' => ' Invoices',
'page_description' => 'View Invoice',
]);
}
I have created a validation rule that checks to see if a url actually exists. I can make it work fine if I implement it as a custom rule within my tables validators. However I would like to make it reusable... I have tried a few different ways and I either get told that the method does not exist or cannot be found, or that I am calling to a member function on NULL
My current error is:
Error: Call to a member function add() on null
I am fairly new to MVC programming and very new to Cakephp
As per the documentation (and my understanding of it) here is my new validator class:
<?php
// In src/Model/Validation/ContactValidator.php
namespace App\Model\Validation;
use Cake\Validation\Validator;
class ContactValidator extends Validator
{
public function __construct()
{
parent::__construct();
$validator
->add('validDomain','custom',[
'rule' => function($value){
$url = parse_url($value);
$host = $url['host'];
if($host != gethostbyname($host)){
return true;
}
return false;
}
]);
}
}
?>
here is my Table (I have deleted all of the validator rules but the one I am trying to get to work for this example):
<?php
namespace App\Model\Table;
use App\Model\Entity\Agent;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
use Cake\Event\Event, ArrayObject;
/**
* Agents Model
*
* #property \Cake\ORM\Association\BelongsTo $Users
* #property \Cake\ORM\Association\BelongsTo $Agencies
* #property \Cake\ORM\Association\HasMany $Pictures
* #property \Cake\ORM\Association\HasMany $Properties
* #property \Cake\ORM\Association\HasMany $SoldProperties
* #property \Cake\ORM\Association\BelongsToMany $Regions
*/
class AgentsTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$this->table('agents');
$this->displayField('user_id');
$this->primaryKey('user_id');
$this->addBehavior('Timestamp');
$this->belongsTo('Users', [
'foreignKey' => 'user_id',
'joinType' => 'INNER'
]);
$this->belongsTo('Agencies', [
'foreignKey' => 'agency_id'
]);
$this->hasMany('Pictures', [
'foreignKey' => 'agent_id'
]);
$this->hasMany('Properties', [
'foreignKey' => 'agent_id'
]);
$this->hasMany('SoldProperties', [
'foreignKey' => 'agent_id'
]);
$this->belongsToMany('Regions', [
'foreignKey' => 'agent_id',
'targetForeignKey' => 'region_id',
'joinTable' => 'agents_regions'
]);
}
/**
* Default validation rules.
*
* #param \Cake\Validation\Validator $validator Validator instance.
* #return \Cake\Validation\Validator
*/
public function validationDefault(Validator $validator)
{
$validator = new \App\Model\Validation\ContactValidator;
$validator
->add('agency_domain', 'valid',['rule' => 'validDomain', 'provider'=>'ContactValidator', 'message' => 'The url you have supplied does not exist!']);
return $validator;
}
public function isOwnedBy($userId)
{
return $this->exists(['user_id' => $userId]);
}
/**
* Returns a rules checker object that will be used for validating
* application integrity.
*
* #param \Cake\ORM\RulesChecker $rules The rules object to be modified.
* #return \Cake\ORM\RulesChecker
*/
public function beforeMarshal(Event $event, ArrayObject $data, ArrayObject $options)
{
if(isset($data['agency_domain']))
{
$data['agency_domain']=strtolower($data['agency_domain']);
if(strpos($data['agency_domain'],"http")===false){
$data['agency_domain'] = "http://".$data['agency_domain'];
}
}
}
}
if someone could point me in the right direction or even show me a working example of how to do this it would be greatly appreciated.
Just create an object of Validator class.
public function __construct()
{
parent::__construct();
$validator = new Validator();
$validator
->add('validDomain','custom',[
'rule' => function($value){
$url = parse_url($value);
$host = $url['host'];
if($host != gethostbyname($host)){
return true;
}
return false;
}
]);
}