Laravel 5.3 - Attach Multiple Files To Mailables - laravel

How does one go about attaching multiple files to laravel 5.3 mailable?
I can attach a single file easily enough using ->attach($form->filePath) on my mailable build method. However, soon as I change the form field to array I get the following error:
basename() expects parameter 1 to be string, array given
I've searched the docs and also various search terms here on stack to no avail. Any help would be greatly appreciated.
Build Method:
public function build()
{
return $this->subject('Employment Application')
->attach($this->employment['portfolio_samples'])
->view('emails.employment_mailview');
}
Mail Call From Controller:
Mail::to(config('mail.from.address'))->send(new Employment($employment));

You should store your generated email as a variable, then you can just add multiple attachments like this:
public function build()
{
$email = $this->view('emails.employment_mailview')->subject('Employment Application');
// $attachments is an array with file paths of attachments
foreach ($attachments as $filePath) {
$email->attach($filePath);
}
return $email;
}
In this case your $attachments variable should be an array with paths to files:
$attachments = [
// first attachment
'/path/to/file1',
// second attachment
'/path/to/file2',
...
];
Also you can attach files not only by file paths, but with MIME type and desired filename, see documentation about second case of use for the `attachment` method: https://laravel.com/docs/master/mail#attachments
For example, your $attachments array can be something like this:
$attachments = [
// first attachment
'path/to/file1' => [
'as' => 'file1.pdf',
'mime' => 'application/pdf',
],
// second attachment
'path/to/file12' => [
'as' => 'file2.pdf',
'mime' => 'application/pdf',
],
...
];
After you can attach files from this array:
// $attachments is an array with file paths of attachments
foreach ($attachments as $filePath => $fileParameters) {
$email->attach($filePath, $fileParameters);
}

Best solution work for me
$email = $this->from($from)->subject("Employment Application")
->view('emails.employment_mailview');
foreach ($files as $file) {
$path = '/path/to/'.$file->file;
$attachments->push($path);
}
$attachments = collect([]);
foreach ($attachments as $filePath) {
$email->attach($filePath);
}
return $email;

This worked for me, maybe it will work for more people.
As I am getting the files via $request this was the solution works for me.
obs: sorry I'm using translate. From Portuguese(Brazil) to English.
thanks #AlexanderReznikov, it helped me a lot.
class SendMailextends Mailable
{
protected $inputs;
protected $files;
public function __construct(array $inputs, array $files)
{
$this->inputs = $inputs;
$this->files = $files;
}
public function build()
{
if (isset($this->files)){
$email = $this->from('you#mail')->view('email.email-contact')->subject('Email teste mulptiple files');
for ($indice = 0; $indice < count($this->files['files']); $indice++) {
$files = $this->files[$indice];
$pathName = $files->getPathname();
$attachments = $pathName;
$fileName = $files->getClientOriginalName();
$email->attach($attachments,['as' => "$fileName"]);
}
return $email;
}else{
// file empty, send email
return $this->from('you#mail','name')->subject('Email teste mulptiple files')
->view('email.email-contact');
}
}
}

Related

Laravel 8: File Upload with Original File Name and Extension

I currently have an API where it saves the uploaded image but it hashes it, turns it into strings but what I want to do now is to retain the original name of the image in the database, not the string-type.
Image Controller:
namespace App\Http\Controllers;
use App\Models\LessonIMG;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class LessonIMGController extends Controller
{
public function FileUpload(Request $request, $id)
{
$rules = [
'file' => 'required',
];
$validator = Validator::make($request->all(), $rules);
if ($validator->fails()) {
return response()->json($validator->errors(), 400);
}
$uploaded_files = $request->file->store('public/uploads/');
$lesson = LessonIMG::find($id);
$lesson->lesson_image = $request->file->hashName();
$results = $lesson->save();
if ($results) {
return ["result" => "Image Added"];
} else {
return ["result" => "Image Not Added"];
}
return ["result" => $uploaded_files];
}
public function DeleteIMG($id)
{
$lesson = LessonIMG::find($id);
if (is_null($lesson)) {
return response()->json('Record not found!', 401);
}
$lesson->update(['lesson_image' => null]);
return response('Image Deleted', 200);
}
}
Any help/suggestion would be appreciated. Thank you!
You can get any file attribute from the request in your controller as it is documented in here. A full reference of file methods is available in here
$file_extension = $request->file->extension();
$file_mime_type = $request->file->getClientMimeType();
$original_file_name = $request->file->getClientOriginalName();
$uploaded_files = $request->file->store('public/uploads/');
You can get the original file by using getClientOriginalName() method:
$filenameWithExt = $files->getClientOriginalName();

Vue / Laravel: How to validate files uploaded from the frontend?

I have an image uploader on my Vue app that takes multiple files. I want to ensure they are images and of a certain size and if not, obviously don't upload the files and have the frontend display the error. Right now, the route it hits in the controller loos like this:
public function uploadAssets(UploadAssetsFormRequest $request)
{
if ($request->hasFile('file')) {
$files = $request->file('file');
$stack = [];
foreach ($files as $file) {
$fileName = Storage::put('/check/', file_get_contents($file->getRealPath()), ['visibility' => 'public']);
array_push($stack, $fileName);
}
return response()->json($stack);
}
}
My Form Request is below and has the validation but I don't know how to apply that in the controller.
UploadAssetsFormRequest
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class UploadAssetsFormRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'files.*' => 'required|image|max:1000',
];
}
public function messages()
{
return [
'files.*.max' => 'The image is too large',
'files.*.image' => 'Only image files are allowed.',
];
}
}
You need to check files extension :
$extension = $file->extension();
$allowed_file_types = ['jpg','png','gif'];
if (in_array($extension, $allowed_file_types)){
//do upload
}else{
Continue;
}
for file sizes check this thread
You can use laravel image validation
$this->validate ($input, [
'files.*.image' => 'image|max:200',
]):
Note: max(size) is in Kilobytes
You can also use dimension rule
$this->validate ($input, [
'files.*.image' => 'dimensions:min_width=100,min_height=200'
]):
Laravel Image Validation
Laravel Image Dimensions Validation
You can set the following rule in your validation -
'file' => 'required|max:100|mimes:jpg,png,bmp' // 100kb, mimes must have image extensions

Laravel testing file upload and resize

I'm trying to develop something using the TDD way and I'm facing this problem I can't solve.
I'm uploading an image which is resized and store in database.
When I'm doing it manually (using my browser like a regular user) it works, but the test fail.
Here is my controller:
public function store(Product $product)
{
//$name = request('image')->store('products', 'public');
$name = str_random(50).'.jpg';
$path = storage_path('app/public/products/'.$name);
$image = Image::make(request('image'));
$image->fit(400, 400);
$image->save($path);
$product->photos()->save(new Photo([
'name' => $name
]));
return redirect($product->adminPath());
}
and here is my test
public function a_user_can_add_photos_to_the_product()
{
$this->withoutExceptionHandling();
$this->signIn();
$product = ProductFactory::create();
Storage::fake('public');
$this->post($product->adminPath('/photos'), [
'image' => UploadedFile::fake()->image('photo.jpg')
])->assertRedirect($product->adminPath());
$product->load(['photos']);
tap($product->photos->first(), function ($photo) {
$this->assertInstanceOf('App\Photo', $photo);
Storage::disk('public')->assertExists($photo->name);
});
$this->assertEquals(1, $product->photos->count());
}
At this point the test will fail, but If i comment the resize part and use the store method on the request like this:
Tests\Feature\ManageProductPhotoTest::a_user_can_add_photos_to_the_product
Unable to find a file at path [pJ1Zd19XT0aF9fc5W1famc3n7WwxBt3L9MDB0yRJNlVYcWkMwk.jpg].
Failed asserting that false is true.
public function store(Product $product)
{
$name = request('image')->store('products', 'public');
/*$name = str_random(50).'.jpg';
$path = storage_path('app/public/products/'.$name);
$image = Image::make(request('image'));
$image->fit(400, 400);
$image->save($path);*/
$product->photos()->save(new Photo([
'name' => $name
]));
return redirect($product->adminPath());
}
The test passes.
How can I solve this issue ?

Create Relationship inside the create function

I have a model that has a one to many relationship to the versions of the description.
In my Controller
$tag = Tags::create([
'name' => $request->get('name'),
'user_id' => \Auth::id(),
]);
$tag->update([
'content' => $request->get('description')
]);
In my Model:
public function setContentAttribute(string $value)
{
$this->versions()->create([
'user_id' => \Auth::id(),
'value' => $value
]);
}
So I can't put content directly as an attribute in the create method because there is no Model right now.
But is it possible to overwrite the create Method?
When I try to overwrite something like this in my Model it will do an infinity loop
public static function create($attr) {
return parent::create($attr);
}
So my question is if it is possible to have something like this:
$tag = Tags::create([
'name' => $request->get('name'),
'user_id' => \Auth::id(),
'content' => $request->get('content')
]);
and in the Model:
public static function create($attr) {
$value = $attr['content'];
$attr['content'] = null;
$object = parent::create($attr);
$object->content = $value;
$object->save();
return $object;
}
Update
I didn't overwrite the create method but called it customCreate. So there is no infinity loop anymore and I can pass all variables to the customCreate function that handles the relationships for me.
Solution
After reading the changes from 5.3 to 5.4 it turns out that the create method was moved so you don't have to call parent::create() anymore.
The final solution is:
public static function create($attr) {
$content = $attr['content'];
unset($attr['content']);
$element = static::query()->create($attr);
$element->content = $content;
$element->save();
return $element;
}
I don't see why not and you could probably implement a more general approach? Eg. checking if set{property}Attribute() method exists, if it does - use it to assign a value, if it doesn't - use mass assigning.
Something like:
public static function create($attr) {
$indirect = collect($attr)->filter(function($value, $property) {
return method_exists(self::class, 'set' . camel_case($property) . 'Attribute');
});
$entity = parent::create(array_diff_key($attr, $indirect->toArray()));
$indirect->each(function($value, $property) use ($entity) {
$entity->{$property} = $value;
});
$entity->save();
return $entity;
}
I haven't really tested it but it should work. I use something like this in one of my Symfony apps.

CodeIgniter - How to add strings to pagination link?

I would like to add some strings/values to the end of the generated pagination link.
For example, I get this
http://localhost/products/lists/5
I would like to have
http://localhost/products/lists/5/value/anothervalue
So, I need to send those values somehow... :)
Thank u all.
The pagination class has an undocumented configuration option called suffix that you can use. Here's how I use it in one of my apps:
// get the current url segments and remove the ones before the suffix
// http://localhost/products/lists/5/value/anothervalue
$args = $this->uri->segment_array();
unset($args[1], $args[2], $args[3]);
$args = implode('/', $args);
// $args is now 'value/anothervalue'
$base_url = 'http://localhost/products/lists';
$this->pagination->initialize(array(
'base_url' => $base_url,
'suffix' => '/'.$args,
'first_url' => $base_url.'/1/'.$args,
'uri_segment' => 3
));
The application/config/routes.php
$route['products/lists/(:num)/value/(:any)'] = "products/lists/$1/$2";
The controller code application/controllers/products.php
class Products extends CI_Controller {
public function index() {
$this->load->view('welcome_message');
}
public function lists($page = 1, $value = null) {
$this->load->view('product_lists', array('page' => $page, 'value' => $value));
}
}
In this way if your url is like http://localhost/products/lists/5/value/anothervalue
in function lists will be $page = 5 and $value = 'anothervalue' and they will be available in template product_lists ($page, $value)

Resources