I am very new to Yii.
I have to apply image validation (.svg and .png allowed) only if user have selected the image.
public function rules() {
return [
[['logo'], 'file', 'extensions'=>'svg, png'],
];
}
When user select image it works fine.
But on update form we have only name of file. Now if we submit the form it will apply validation. I need validation only if user changes the image.
My Controller code
if ($model->load(Yii::$app->request->post())) {
$model->logo = UploadedFile::getInstance($model, 'logo');
if (!empty($model->logo)) {
$model->logo->name = $model->logo->baseName . Yii::$app->formatter->asTimestamp(date('Y-d-m h:i:s')) . '.' . $model->logo->extension;
$logoPath = Yii::getAlias('#common') . '/web/uploads/logo/' . $model->logo->name;
$model->logo->saveAs($logoPath, false);
}
if ($model->updateSite()) {
return $this->redirect(['site-list']);
}
}
Please let me know if you need more clarification.
Thanks.
Using scenarios may be helpful in this case.
Define validation rules to dependend on scenario
public function rules() {
return [
[['logo'], 'file', 'extensions'=>'svg, png', 'on' => 'imageUploaded'],
];
}
Then define scenario for model in controller. Something like this.
if ($model->load(Yii::$app->request->post())) {
$model->logo = UploadedFile::getInstance($model, 'logo');
if (!empty($model->logo)) {
$model->scenario = 'imageUploaded';
...
}
}
It's also possible to define anonymous function for conditional validation.
public function rules() {
return [
[['logo'], 'file', 'extensions'=>'svg, png', 'when' => function ($model) {
//return true to apply the rule
return $model->isImageUploaded();
}],
];
}
More on rules and scenarios can be found here https://github.com/yiisoft/yii2/blob/master/docs/guide/input-validation.md#declaring-rules-
Related
I'm working on validating an image file with two different dimensions rule, it can b Icon or Banner depending on the selection of recently selected field. I'm doing this by adding a custom Rule class, here is my validation rule which works fine on Icon Only OR Banner Only
$validator = Validator::make([
'file' => $value,
], [
'file' => 'mimes:png,webp|dimensions:width=512,height=512|max:2048'
]);
$validator->validate();
now the problem is to validate the banner with a different dimension in the same field. Is there any way to add another Width & Height to this line? I've tried to add another rule under this file with the same name, but it doesn't work. Or is there any other approach to solve this problem? I've read the documentation but could not find the solution there.
Thanks in advance.
Validation rules by default must all pass for the input to be valid. In your case you need one of the two rules to pass which is not possible via built-in validation rules. You can create your own validation rule e.g.:
php artisan make:rule DimensionRule
Then modify the generated rule:
class DimensionRule implements Rule {
public function passes($attribute, $value) {
$validator1 = Validator::make([ $attribute => $value ], [ $attribute => 'dimensions:width=512,height=512' ]);
if ($validator1->passes()) {
return true;
}
$validator2 = Validator::make([ $attribute => $value ], [ $attribute => 'dimensions:width=800,height=30' ]);
return $validator2->passes();
}
public function message()
{
return 'Dimensions must either be 512x512 or 800x30';
}
}
Then you can use this rule:
$validator = Validator::make([
'file' => $value,
], [
'file' => [ 'mimes:png,webp', new DimensionRule(), 'max:2048' ]
]);
$validator->validate();
I would do it this way, buy running few validations in a row, and if it passes one, it should be OK. You can write more validation rules after that of course, I think only the dimensions validation should be here.
class FileUploadController extends Controller
{
/**
* Update the avatar for the user.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function fileUpload(Request $request)
{
foreach ([[100, 50], [200, 150]] as $k => $dim) {
$validators[$k] = Validator::make($request->all(), [
'avatar' => "dimensions:width={$dim[0]},height={$dim[1]}"
]);
}
foreach ($validators as $validator) {
if ($validator->fails()) {
$invalid = true;
} else {
$invalid = false;
break;
}
}
if ($invalid) {
return redirect()->back()
->withErrors($validator)
->withInput();
}
$path = Storage::putFile('public', $request->file('avatar'));
return $path;
}
}
And here is also a feature test:
class FileUploadTest extends TestCase
{
/**
* #dataProvider cases
*/
public function test_avatars_can_be_uploaded($w, $h, $shoudReturnError)
{
Storage::fake('public');
$file = UploadedFile::fake()->image('av1.jpg', $w, $h);
$response = $this->post('/file-upload', [
'avatar' => $file,
]);
if ($shoudReturnError) {
// The image dimensions are not right and it should return error
$response->assertSessionHasErrors(['avatar']);
} else {
// The image dimensions are fine and it should pass
Storage::assertExists('public/' . $file->hashName());
}
}
public function cases()
{
return [
[100, 50, $shoudReturnError = false],
[600, 850, $shoudReturnError = true],
[200, 150, $shoudReturnError = false],
[102, 50, $shoudReturnError = true],
];
}
}
I've provided cases where the validation should pass and where it should fail.
Cheers
In my input form, I have two fields; momentFrom & momentTo. I need to put a validation which gives error message if any of the following criteria fails.
momentFrom is greater than or equal to momentTo.
momentFrom is less than now.
My code for storing the data:
public function store(Request $request, Requisition $requisitionObj) {
$momentFrom = strtotime($request->txtTravelDate . " " . $request->txtTimeFrom);
$momentTo = strtotime($request->txtTravelDate . " " . $request->txtTimeTo);
$timeValidation = $requisitionObj->validateTiming($momentFrom, $momentTo);
if ($timeValidation['error']) {
echo 'ERROR: ' . $timeValidation['message'];
return view('requisitions.create');
} else {
/* store form data into requisition object */
$requisitionObj->travel_date = $request->txtTravelDate;
$requisitionObj->moment_from = $momentFrom;
$requisitionObj->moment_to = $momentTo;
$requisitionObj->save();
return redirect()->route('requisitions.index');
}
}
I have seen laravel custom validation rules where only one field can be validated at a time. But in my scenario I need to check both fields at a time depending on each other. How can I achieve this?
Thanks for any help in advance!
Creating new Rule Class
You can create your custom rule with the artisan command: php artisan make:rule YourRuleNamethis will create a new Rule Class file into the Rules folder.
By default the created file contains a constructor, a passes method and a message method.
Rules Logic
If you have some complicated rules where you need the request or some models, you can pass them via the constructor.
public function __construct(Request $request, User $user, ....)
{
//save them into class variables to access them later
$this->request = $request;
$this->user = $user;
}
Otherwise you can directly put your validation logic into the passes method:
public function passes($attribute, $value){
//some code
return #myCondition
}
Last you are able to specify the message if the validation fails.
public function message()
{
return 'Your message';
}
To use your rule simply add it to your rules array:
$rules = [
'my_attribute' => [new MyCustomRule(),...],
]
At last, I have solved this problem using FormRequest and AppServiceProvider. Thought this would help others who come to this place.
First I have created FormRequest validator using following artisan command.
php artisan make:request StoreRequisition
Then added primary validation rules and messages into it.
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreRequisition extends FormRequest {
public function authorize() {
return true;
}
public function rules() {
$rules = [
'txtTravelDate' => 'required|date_format:Y-m-d|after_or_equal:today',
'txtTimeFrom' => 'required|date_format:H:i|travel_time_validate',
'txtTimeTo' => 'required|date_format:H:i',
];
return $rules;
}
public function messages() {
return [
'txtTravelDate.required' => 'Travel date is required!',
'txtTravelDate.date_format' => 'Invalid format for Travel Date!',
'txtTravelDate.after_or_equal' => 'Travel Date should be today or later!',
'txtTimeFrom.required' => 'Time From is required!',
'txtTimeFrom.date_format' => 'Invalid format for Time From!',
'txtTimeFrom.travel_time_validate' => 'Invalid time selected!',
'txtTimeTo.required' => 'Time To is required!',
'txtTimeTo.date_format' => 'Invalid format for Time To!',
'listFunction.required' => 'Department to be selected!',
'txtPickLoc.required' => 'Pickup Location is required!',
'txtDropLoc.required' => 'Drop Location is required!',
'listPurpose.required' => 'Travel Purpose to be selected!'
];
}
}
Then inside app\Providers\AppServiceProvider, added the extra validation logic.
public function boot() {
Validator::extend(
'travel_time_validate',
function ($attribute, $value, $parameters, $validator) {
$inputs = $validator->getData();
/* convert time to moments */
$momentFrom = strtotime($inputs['txtTravelDate'] . " " . $inputs['txtTimeFrom']);
$momentTo = strtotime($inputs['txtTravelDate'] . " " . $inputs['txtTimeTo']);
$result = true;
if ($momentFrom >= $momentTo) {
$result = false;
}
return $result;
}
);
}
My Controller:
public function store(StoreRequisition $request, Requisition $requisitionObj) {
$validatedData = $request->validated();
/* store form data into requisition object */
$requisitionObj->requester_id = Auth::user()->id;
$requisitionObj->travel_date = $request->txtTravelDate;
$requisitionObj->time_from = $request->txtTimeFrom;
$requisitionObj->time_to = $request->txtTimeTo;
$requisitionObj->purpose_id = $request->listPurpose;
/* Finally save the record into the database */
$requisitionObj->save();
return redirect()->route('requisitions.index');
}
Example how make custom rule for validation in Laravel 8.x / Lumen 8.x.
public static function rules(){
return [
'number' => [
'required', 'min:1', 'max:30', 'string', self::testNumber(),
],
];
}
public static function testNumber(){
return function($attribute, $value, $fail){
if ($value === 'foo'){
$fail('The '.$attribute.' is invalid.');
}
};
}
I'm using Yii2 basic. It doesn't seems like anything's wrong, no error message displayed, but why did my image didn't upload? The rest (title, content etc) get uploaded through the form,though
This is my model's rule and related method:
public $image;
public function init(){
Yii::$app->params['uploadPath'] = Yii::$app->basePath . '/uploads/batam/';
Yii::$app->params['uploadUrl'] = Yii::$app->urlManager->baseUrl . '/uploads/batam/';
}
public function rules()
{
return [
[['title', 'content'], 'required'],
[['content'], 'string'],
[['created_at', 'updated_at','image'], 'safe'],
[['image'], 'file','extensions'=>'jpg,png,jpeg'],
[['title'], 'string', 'max' => 255],
];
}
public function getImageFile()
{
return isset($this->image) ? Yii::$app->params['uploadPath'].$this->image : null;
}
public function uploadImage() {
$image = UploadedFile::getInstance($this, 'image');
if (empty($image)) {
return false;
}
$this->image = $image->name;
return $image;
}
This is my controller
public function actionCreate()
{
$model = new News();
if ($model->load(Yii::$app->request->post()) )
{
// process uploaded image file instance
$image = $model->uploadImage();
if($model->validate())
{
if($model->save())
{
// upload only if valid uploaded file instance found
if ($image !== false)
{
$path = $model->getImageFile();
$image->saveAs($path);
}
return $this->redirect(['view', 'id'=>$model->id]);
}
}
else{echo " validation is failed";}
}
else{
return $this->render('create', [
'model' => $model,
]);
}
}
This is the form
echo $form->field($model, 'image')->widget(FileInput::classname(), [
'options' => ['accept' => 'image/*'],
'pluginOptions' => [['previewFileType' => 'any']]
]);
I had included the enctype also at the beginning of the form
<?php $form = ActiveForm::begin(['options' => ['enctype' => 'multipart/form-data']]);
At this point inside the if ($image !== false) part of the controller , the $image and $path to be saved-as contains a seemingly correct path.
This is my $path : C:\xampp\htdocs\gbia/uploads/batam/test image 1-01.jpg and my $image also contain the object (not null). This is the var_dump of my $image :
object(yii\web\UploadedFile)#179 (5) { ["name"]=> string(19) "test image 1-01.jpg" ["tempName"]=> string(24) "C:\xampp\tmp\php3199.tmp" ["type"]=> string(10) "image/jpeg" ["size"]=> int(925184) ["error"]=> int(0) }
I think something wrong with the saveAs(), but I can't figure it out. Had googled around, look on stackoverflow and tutorials but I still can't find any answer. Can someone help? Thanks
Check your model, you have declared $image as a public variable of the class, and not as a field in the database, if you want to store the data there, it will never work, as the public property that is temporary will have preference over the database column.
public $image;
So delete this field (If it is also in the db) or generate a new column name (I suggest by the name of path).
[['content', 'path'], 'string'],
Then you need to store the path, I don't see where are you doing that in the controller or class. I suggest you to add a field in the database with the "path" name and then do like this in the controller:
$path = $model->getImageFile();
$image->saveAs($path);
$model->path = $path . $image // You must store the full path plus the file name
$model->save(); // then you save the model again
Any doubt is welcome, I have example projects that I can show you if you are unable to see the light.
I have 2 methods in my Controller and I need to validate it but I don't know how.
1st method which should allow all image extensions:
public function testing(Request $request) {
if($request->hasFile('img')) {
$image = Input::file('img');
$filename = time() . '.' . $image->getClientOriginalExtension();
$path = public_path('images/' . $filename);
Image::make($image->getRealPath())->resize(200, 200)->save($path);
$file = $request->file('img');
return ['url' => url('images/' . $filename)];
}
}
2nd method which should only allow 1 word and if there is space, trim it into 1 word:
public function postDB(Request $request) {
$newName = $request->input('newName');
$websites = new Website();
$websites->name = $newName;
$websites->save();
return redirect('template')->with('status', 'Website has been saved successfully!');
}
First write new Request for your data
php artisan make:request ImageRequest
Than write in ImageRequest:
public function authorize()
{
return true;
}
public function rules()
{
return [
'img' => 'file|image',
]
}
If you want to customize error messages:
public function messages()
{
return [
'img.image' => 'Some custom message ...',
];
}
Last inject request to your method (don`t forget about use App\Http\Requests):
public function testing(Requests\ImageRequest $request) {
//for retrieving validation errors use:
$imgErrors = $errors->first('img');
}
More information about Form Request Validation
Or you can use Validator facade (don`t forget about use Validator):
$validator = Validator::make(
$image, [
'img' => 'file|image',
]
);
More information about A Note On Optional Fields
My Model code,
public function rules()
{
return [
[['image'], 'safe'],
['image', 'file', 'types'=>'jpg, gif, png'],
];
}
Use Apartfrom types,validation message not display document save in database
To validate file type you should use property $extensions of FileValidator.
public function rules()
{
return [
[['image'], 'safe'],
[['image'], 'file', 'extensions'=>'jpg, gif, png'],
];
}
Update
Ok, I've got this. Please check one more time the source link, especially the controller section. To validate your model, you have to use validate() function.
Example code:
$model->file = UploadedFile::getInstance($model, 'file');
if ($model->file && $model->validate()) {
$model->file->saveAs('uploads/' . $model->file->baseName . '.' . $model->file->extension);
}
Source.