Yii validation with file upload failing - validation

I'm having what looks to me some strange behaviour within Yii.
I have a simple file upload, that takes a name and the file itself.
If I just submit the form with no name or file I can bypass the validation (i.e - My controller action is called and is trying to process the uploaded file) I think my rules() are setup accordingly to stop this. These are my relevant rules:
public function rules() {
return array(
array('name file', 'required', 'message' => 'This field is required'),
array('file', 'file', 'on' => 'insert', 'allowEmpty' => false, 'safe'=>true,
'maxSize'=> 512000,
'maxFiles'=> 1,
'mimeTypes' => 'application/msword, text/plain',
'tooLarge'=> 'file cannot be larger than 500KB.',
'wrongMimeType'=> 'Format must be: .doc .txt'
),
I specified that the file is required and also within the file array that allowEmpty should be false. So what am I doing wrong here?
Thanks in advance for any help
Controller
public function actionCreate() {
$model = new File;
if (isset($_POST['File'])) {
$model->setAttributes($_POST['File']);
// Set file
$model->file = CUploadedFile::getInstance($model,'file');
// Set directory
$dest = Yii::getPathOfAlias('application.uploads');
$model->tmp_name = time();
$model->date_added = new CDbExpression('NOW()');
$model->file_type = $model->file->type;
$model->file_size = $model->file->size;
$model->extension = $model->file->extensionName;
if ($model->save()) {
$model->file->saveAs(($dest . '/' . $model->tmp_name . '.' . $model->file->extensionName));
Yii::app()->user->setFlash('success','<strong>Success!</strong> Your file has been uploaded');
}
}
$this->render('create', array( 'model' => $model));
}

For one you're missing a , in your first rule between name and file. Then you say:
I can bypass the validation (i.e - My controller action is called ...
From that i assume you use AJAX validation and expect the upload to fail. But you can't do AJAX validation on file uploads with CActiveForm.
So if you fix the typo above, you'll at least get AJAX validation for the name attribute.
You should maybe also remove the 'on'=>'insert' scenario. And you don't need the 'safe'=>true because you don't do massive assignment with the $model->file attribute.

For me I found that if I validate before I process the uploaded file it worked. Wasn't quite sure why I had to do that as I thought the save() method automatically called the validate() method
Validation will be performed before saving the record. If the validation fails, the record will not be saved. You can call getErrors() to retrieve the validation errors.
Updated code
public function actionCreate() {
$model = new File;
if (isset($_POST['File'])) {
$model->setAttributes($_POST['File']);
if($model->validate()){ // add in validation call
// Set file
$model->file = CUploadedFile::getInstance($model,'file');
// Set directory
$dest = Yii::getPathOfAlias('application.uploads');
$model->tmp_name = time();
$model->date_added = new CDbExpression('NOW()');
$model->file_type = $model->file->type;
$model->file_size = $model->file->size;
$model->extension = $model->file->extensionName;
if ($model->save()) {
$model->file->saveAs(($dest . '/' . $model->tmp_name . '.' . $model->file->extensionName));
Yii::app()->user->setFlash('success','<strong>Success!</strong> Your file has been uploaded');
}
}
}
$this->render('create', array( 'model' => $model));
}
Hope that helps anyone, thanks to #Michael Härtl too

Related

Laravel validation couldn't store value after validate and give error 500

I have a form that using ajax for update data client. In that form there is an input file. Everything is going fine except for updating the file. File is sent, it changed on storage too, but it gives error on validation and didn't change data on database.
Here is the code on the controller :
public function update(Request $request, Client $client)
{
$validatedData = Validator::make($request->all(), [
'name' => 'required|max:255',
'logo'=> 'image|file|max:100',
'level' => 'required|max:1'
]);
$validatedData['user_id'] = auth()->user()->id;
if ($validatedData->fails()){
return response()->json($validatedData->errors());
} else {
if($request->file('logo')){
if($request->oldLogo){
Storage::delete($request->oldLogo);
}
$validatedData['logo'] = $request->file('logo')->store('logo-clients');
}
$validateFix = $validatedData->validate();
Client::where('id', $client->id)->update($validateFix);
return response()->json([
'success' => 'Success!'
]);
}
}
It gives error on line :
$validatedData['logo'] = $request->file('logo')->store('logo-clients');
With message :
"Cannot use object of type Illuminate\Validation\Validator as array"
I use the same code that works on another case, the difference is the other not using ajax or I didn't use Validator::make on file input. I guess it's just wrong syntax but I don't really know where and what it is.
To retrieve the validated input of a Validator, use the validated() function like so:
$validated = $validator->validated();
Docs:
https://laravel.com/docs/9.x/validation#manually-creating-validators
https://laravel.com/api/9.x/Illuminate/Contracts/Validation/Validator.html
$validatedData is an object of type Illuminate\Validation\Validator.
I would say the error is earlier there as well as this line should give an error also:
$validatedData['user_id'] = auth()->user()->id;
As ericmp said, you first need to retrieve the validateddata to an array and then work with it.

laravel livewire uploaded file create object from path and save to s3

I'm uploading a file to a file upload component, and then passing the temporary file path to the parent component with an event. In the parent component I need to save the file to s3.
I need to pass the path or a file object or something back to the parent component, and then save it, but I can't seem to get it to work.
I've tried sending over a File object, as well as an UploadedFile object, my latest iteration is to try with a File object, and I'm getting the following error:
Unresolvable dependency resolving [Parameter #0 [ <required> string $path ]] in class Symfony\Component\HttpFoundation\File\File
So in my child component I have this code:
public function updatedFile()
{
$fileObj = new File($this->file->path());
$this->emitUp('fileUploaded', $fileObj);
}
In my parent component I'm listening for the fileUploaded event, which calls the save method:
public function save(File $uploadedFile)
{
if ($path = Storage::putFileAs(env('APP_ENV') . '/statements', $uploadedFile->name, 's3')) {
$this->statement = new Statement([
'location_id' => $this->location->id,
'file_name' => $uploadedFile->name,
'path' => $path,
'uploaded_by' => Auth::user()->id,
]);
$this->statement->save();
}
}
I've also tried using $uploadedFile->storeAs() and I get the same result. It seems like the $uploadedFile object is not the right type. I don't know if I need a Storage object or what and I can't seem to find a good answer in the docs.
The path I have available after uploading the file in my livewire component is the temporary file name that livewire saves the file as in local storage. I also need the original file name as well, like what was uploaded as I'm saving that to the database.
If I remove the type hint on the save() method I get Attempt to read property "name" on array. Why is $uploadedFile an array and not an object? I guess if I remove the type hint it just gets sent over as an array. I dunno..
Here's the solution I came up with:
child component:
public function updatedFile()
{
$this->validate([
'file' => 'required|max:12288'
]);
$this->emitUp('fileUploaded', [$this->file->path(), $this->file->getClientOriginalName()]);
}
parent component:
public function save($uploadedFile)
{
if ($path = Storage::disk('s3')->put(env('APP_ENV') . '/statements/' . $uploadedFile[1], file_get_contents($uploadedFile[0]))) {
$this->statement = new Statement([
'location_id' => $this->location->id,
'file_name' => $uploadedFile[1],
'path' => $path,
'uploaded_by' => Auth::user()->id,
]);
$this->statement->save();
}
}

Laravel test unable to find a file at path using Storage::fake()

I have created my own 'disk' in config/filesystems.php which looks like so;
'uploads' => [
'driver' => 'local',
'root' => storage_path('app/public') . '/uploads'
],
This seems to work fine when in my controller, it uploads the file and returns a response. my code is as follows;
public function store(Request $request)
{
if ($request->hasFile('filename')) {
foreach ($request->file('filename') as $image) {
$fileName = md5($image . microtime()) . '.' . $image->getClientOriginalExtension();
$image->storeAs('', $fileName, 'uploads');
}
}
// return goes here
}
but when I go to test my store method using the following code;
public function testUserCanSuccessfullySubmitSingleImage(): void
{
Storage::fake('uploads');
$this->postJson('/upload', [
'filename' => UploadedFile::fake()->image('image1.jpg')
]);
Storage::disk('uploads')->assertExists('image1.jpg');
Storage::disk('uploads')->assertMissing('missing.jpg');
}
I get the following error;
Unable to find a file at path [image1.jpg].
Failed asserting that false is true.
I have followed a few tutorials, but they all say the same thing and im really lost.
Any help would be greatly appreciated.
Cheers
The problem is that you are renaming your file in the controller with md5($image . microtime()) so you cannot assert that image1.jpg exists since you changed the name.
What you could do is let laravel name the file and then check that in your test:
In your Controller:
Replace storeAs with store, store will generate a unique ID to serve as the file name.
public function store(Request $request)
{
if ($request->hasFile('filename')) {
foreach ($request->file('filename') as $image) {
$image->store('', 'uploads');
}
}
}
In your test:
To assert if the image exists we will use the same method to generate the unique ID as laravel does when saving the image. Replace 'image1.jpg' with $image->hashName() in your assertion.
public function testUserCanSuccessfullySubmitSingleImage(): void
{
Storage::fake('uploads');
$this->postJson('/upload', [
'filename' => $image = UploadedFile::fake()->image('image1.jpg')
]);
Storage::disk('uploads')->assertExists($image->hashName());
Storage::disk('uploads')->assertMissing('missing.jpg');
}
From the docs:
In web applications, one of the most common use-cases for storing
files is storing user uploaded files such as profile pictures, photos,
and documents. Laravel makes it very easy to store uploaded files
using the store method on an uploaded file instance. Call the store
method with the path at which you wish to store the uploaded file:
public function update(Request $request)
{
$path = $request->file('avatar')->store('avatars');
return $path;
}
There are a few important things to note about this example. Note that
we only specified a directory name, not a file name. By default, the
store method will generate a unique ID to serve as the file name. The
file's extension will be determined by examining the file's MIME type.
The path to the file will be returned by the store method so you can
store the path, including the generated file name, in your database.

Image validation not working If using ajaxForm

I tried googling and saw other questions posted at this forum but could not find any solution for my issue. I am using Jquery ajaxForm method to submit form. My form contains one file field too in the form that can be used to upload a picture. I have defined the validation in my model. But the issue is even i am uploading a correct jpg file, still i am getting error message that
Argument 1 passed to Illuminate\\Validation\\Factory::make() must be of the type array, object given.
Javascript Code
$('#create_form').ajaxForm({
dataType:'JSON',
success: function(response){
alert(response);
}
}).submit();
Controllder Code
if ($file = Input::file('picture')) {
$validator = Validator::make($file, User::$file_rules);
if ($validator->fails()) {
$messages = $validator->messages();
foreach ($messages->all(':message') as $message) {
echo $message; exit;
}
return Response::json(array('message'=>$response, 'status'=>'failure'));
} else {
// do rest
}
}
Model Code
public static $file_rules = array(
'picture' => 'required|max:2048|mimes:jpeg,jpg,bmp,png,gif'
);
POST Request
I know that my validation defined in the model expects an array. But by passing $file in the validator, an object is passed. Then i changed the code like:
$validator = Validator::make(array('picture' => $file->getClientOriginalName()), User::$file_rules);
Now i am getting error:
The picture must be a file of type: jpg, JPEG, png,gif.
The problem is you pass file object directly to validate. Validator::make() method takes all four parameters as array. Moreover, you need to pass the whole file object as value so that Validator can validate mime type, size, etc. That's why your code should be like that.
$input = array('picture' => Input::file('picture'));
$validator = Validator::make($input, User::$file_rules);
if ($validator->fails()) {
$messages = $validator->messages();
foreach ($messages->all(':message') as $message) {
echo $message; exit;
}
return Response::json(array('message'=>$response, 'status'=>'failure'));
} else {
// do rest
}
Hope it will be useful for you.
Try rule like this.
$rules = array(
'picture' => 'image|mimes:jpeg,jpg,bmp,png,gif'
);
or try removing 'mimes'

One-shot laravel validator

I have a form where someone searches for something. Based on this form, I validate if the input is correct:
$validator = Validator::make(Input::all() , array(
'address' =>'required',
));
if($validator->fails()) {
return Redirect::to('/')->withErrors($validator);
}
After this, I want to validate something else (that a result object isn't empty), which is completely unrelated to the search. In other words, it's NOT input from a form.
1) Do I create another validator to validate this? Or
2) Is there a better way to simply check this value and spawn an object that can be returned with "withErrors"?
UPDATE
This isn't working for me:
$validator = Validator::make(
array(
'searches' => sizeof($search)
) ,
array(
'searches' => 'required|min:1'
)
);
if($validator->fails()) {
return Redirect::to('/')->withErrors($validator);
}
It's not working because for some reason it's picking up that the "searches" item should only be validated "sometimes"
you have two ways. one is custom validator
or there is a simpler way,
suppose,
private function foo()
{
$data = ''; //retrieved the data error here with whatever call you want to make
return !empty($data) ? true : false;
}
in the controller,
public function bar()
{
if(!$this->foo())
{
$messages = new \Illuminate\Support\MessageBag;
// you should use interface here. i directly made the object call for the sake of simplicity.
$messages->add('custom', 'custom error');
return Redirect::back()->withErrors($messages)->withInput();
}
}
in the view:
#if($errors->has('custom'))
<p>custom error output.</p>
#endif
it is just the outline to give you the idea.

Resources