We have moved our forum to GitHub Discussions. For questions about Phalcon v3/v4/v5 you can visit here and for Phalcon v6 here.

File upload / validation issue

Hello,

I have a form in which a user can upload an image via a file input field. The user is allowed to leave the file field empty so it remains empty upon creation or doesnt get updated in the case its left empty on the edit screen.

When left empty the file type validation is somehow triggered

When i use the below check, file uploading works, but the validation triggers even if left blank

if($this->request->getPost("image") !== '') {
        //
}

if i change this to the below, the validation is skipped but the image/file upload doesnt work anymore.

if($this->request->getPost("image") !== NULL) {
        //
}

This is the full controller (above is in the save- and create action)

<?php
use PhalconTime\Forms\UserForm;
use PhalconTime\Models\User;
use Phalcon\Security\Random;

class UserController extends ControllerBase
{

    /**
     * The start action, it shows the "search" view
     */
    public function indexAction()
    {
        $users = User::find();

        $this->view->users = $users;
    }

    /**
     * Execute the "search" based on the criteria sent from the "index"
     * Returning a paginator for the results
     */
    public function searchAction()
    {
        // ...
    }

    /**
     * Shows the view to create a "new" user
     */
    public function newAction()
    {
        $this->view->setVar('form', new UserForm(null, ['edit' => false]));
    }

    /**
     * Shows the view to "edit" an existing user
     */
    public function editAction($id)
    {
        if (!$this->request->isPost()) {
            $user = User::findFirstById($id);
            if (!$user) {
                $this->flash->error('User not found');
                return $this->dispatcher->forward(["controller" => "user", "action" => "index" ]);
            }

            $user->setPassword('');
            $this->view->setVar('form', new UserForm($user, ['edit' => true]));
        }
    }

    /**
     * Creates a user based on the data entered in the "new" action
     */
    public function createAction()
    {
        if (!$this->request->isPost()) {
            return $this->dispatcher->forward(["controller" => "user", "action" => "index" ]);
        }

        $form   = new UserForm;
        $user   = new User;
        $data   = $this->request->getPost();

        if (!$form->isValid($data, $user)) {
            foreach ($form->getMessages() as $message) {
                $this->flash->error($message);
            }
            return $this->dispatcher->forward(["controller" => "user", "action" => "new" ]);
        }

        $password       = $this->request->getPost("password");
        $user->password = $this->security->hash($password);

        // @TODO The check always triggers since it has to be !== NULL but if changed uploading doesnt work anymore
        if($this->request->getPost("image") !== '') {
            if($this->request->hasFiles()) {
                foreach ($this->request->getUploadedFiles() as $file) {
                    if ($this->extensionCheck($file->getRealType())) {
                        $random      = new Random();
                        $uuid        = $random->uuid();
                        $user->image = $uuid.'_'.$file->getName();

                        $file->moveTo('img/uploads/'.$uuid.'_'.$file->getName());
                    }
                    else {
                        $this->flash->error('This typ of file is not supported');

                        return $this->dispatcher->forward(["controller" => "user", "action" => "new" ]);
                    }
                }
            }
        }

        if ($user->save() == false) {
            foreach ($user->getMessages() as $message) {
                $this->flash->error($message);
            }
            return $this->dispatcher->forward(["controller" => "user", "action" => "new" ]);
        }

        $form->clear();
        $this->flash->success("User created");

        return $this->dispatcher->forward(["controller" => "user", "action" => "index" ]);
    }

    /**
     * Updates a user based on the data entered in the "edit" action
     */
    public function saveAction()
    {

        if (!$this->request->isPost()) {
            return $this->dispatcher->forward(["controller" => "user", "action" => "index" ]);
        }

        $id     = $this->request->getPost("id", "int");
        $user   = User::findFirstById($id);

        if (!$user) {
            $this->flash->error("User not found");
            return $this->dispatcher->forward(["controller" => "user", "action" => "index" ]);
        }
        $currentPassword = $user->getPassword();

        $form = new UserForm;
        $this->view->setVar('form', $form);

        if($this->request->getPost("password") !== '') {
            $data = $this->request->getPost();

            // @TODO on edit screen validation doesn't work
            // if (!$form->isValid($data, $user)) {
            //     foreach ($form->getMessages() as $message) {
            //         $this->flash->error($message);
            //     }
            //     return $this->dispatcher->forward(["controller" => "user", "action" => "edit" , "params" => $id]);
            // }

            $password       = $this->request->getPost("password");
            $user->password = $this->security->hash($password);
        }
        else {
            $user->password = $currentPassword;
        }

        if($this->request->getPost("image") !== '') {
            if($this->request->hasFiles()) {
                foreach ($this->request->getUploadedFiles() as $file) {
                    if ($this->extensionCheck($file->getRealType())) {
                        $random      = new Random();
                        $uuid        = $random->uuid();
                        $user->image = $uuid.'_'.$file->getName();

                        $file->moveTo('img/uploads/'.$uuid.'_'.$file->getName());
                    }
                    else {
                        $this->flash->error('This typ of file is not supported');

                        return $this->dispatcher->forward(["controller" => "user", "action" => "new" ]);
                    }
                }
            }
        }

        if ($user->save() == false) {
            foreach ($user->getMessages() as $message) {
                $this->flash->error($message);
            }
            return $this->dispatcher->forward(["controller" => "user", "action" => "edit" , "params" => $id]);
        }

        $form->clear();
        $this->flash->success('User updated');

        return $this->dispatcher->forward(["controller" => "user", "action" => "index" ]);
    }

    /**
     * Deletes an existing user
     */
    public function deleteAction($id)
    {
        $user = User::findFirstById($id);

        if (!$user) {
            $this->flash->error("User not found");

            return $this->dispatcher->forward(["controller" => "user", "action" => "index" ]);
        }
        if (!$user->delete()) {
            foreach ($user->getMessages() as $message) {
                $this->flash->error($message);
            }
            return $this->dispatcher->forward(["controller" => "user", "action" => "index" ]);
        }

        $this->flash->success("User deleted");
        return $this->dispatcher->forward(["controller" => "user", "action" => "index" ]);
    }

    /**
     * Delete only the image
     */
    public function deleteImageAction($id)
    {
        $user   = User::findFirstById($id);

        if (!$user) {
            $this->flash->error("User not found");
            return $this->dispatcher->forward(["controller" => "user", "action" => "index" ]);
        }

        $user->image = NULL;

        $form = new UserForm;
        $this->view->setVar('form', $form);

        if ($user->save() == false) {
            foreach ($user->getMessages() as $message) {
                $this->flash->error($message);
            }
            return $this->dispatcher->forward(["controller" => "user", "action" => "edit" , "params" => $id]);
        }

        $form->clear();
        $this->flash->success('Image removed');

        return $this->dispatcher->forward(["controller" => "user", "action" => "edit" ]);

    }

    /**
     * Confirm before deleting records
     */
    public function confirmAction($id)
    {
        if(!$id) {
            $this->flash->error("User not found");

            return $this->dispatcher->forward(["controller" => "user", "action" => "index" ]);
        }

        $this->view->setVar('id', $id);
    }

    /**
     * Attempt to determine the real file type of a file.
     *
     * @param  string $extension Extension (eg 'jpg')
     * @return boolean
     *
     * @TODO move to form object, https://docs.phalcon.io/en/3.0.0/api/Phalcon_Validation_Validator_File.html
     */
    private function extensionCheck($extension)
    {
        $allowedTypes = [
            'image/gif',
            'image/jpg',
            'image/png',
            'image/bmp',
            'image/jpeg'
        ];

        return in_array($extension, $allowedTypes);
    }

}

And below is the form

<?php

namespace PhalconTime\Forms;

use Phalcon\Forms\Form;
use Phalcon\Forms\Element\Text;
use Phalcon\Forms\Element\Hidden;
use Phalcon\Forms\Element\Password;
use Phalcon\Forms\Element\Check;
use Phalcon\Forms\Element\File;
use Phalcon\Validation\Validator\File as FileValidator;
use Phalcon\Validation\Validator\PresenceOf;
use Phalcon\Validation\Validator\Email;
use Phalcon\Validation\Validator\Identical;
use Phalcon\Validation\Validator\StringLength;
use Phalcon\Validation\Validator\Confirmation;

class UserForm extends Form
{
    /**
     * Initialize the project status form
     *
     * @param mixed $entity
     * @param array $options
     */
    public function initialize($entity = null, $options = [])
    {

        if (isset($options['edit']) && $options['edit'] === TRUE) {
            $id = new Hidden(
                "id", [
                    "class" => "hidden",
                ]
            );
            $this->add($id);
        }

        // Name
        $name = new Text(
            "name",
            [
                "placeholder" => "Name",
                "class"       => "form-control",
            ]
        );
        $name->setLabel("Name");
        $name->setFilters(
            [
                "striptags",
                "string",
            ]
        );
        $name->addValidators(
            [
                new PresenceOf(
                    [
                        "message" => "Name is required",
                    ]
                )
            ]
        );
        $this->add($name);

        // Email
        $email = new Text(
            "email",
            [
                "placeholder" => "E-mailaddress",
                "class"       => "form-control",
            ]
        );
        $email->setLabel("E-mail");
        $email->setFilters(
            [
                "striptags",
                "string",
            ]
        );
        $email->addValidators(
            [
                new PresenceOf(
                    [
                        "message" => "E-mailaddress is required",
                    ]
                ),
                new Email(
                    [
                        'message' => 'E-mailaddress is not valid'
                    ]
                )
            ]
        );
        $this->add($email);

        // Password
        $password = new Password(
            "password",
            [
                "placeholder" => "Password",
                "class"       => "form-control",
            ]
        );
        $password->setLabel("Password");
        $password->setFilters(
            [
                "striptags",
                "string",
            ]
        );
        $password->addValidators(
            [
                new PresenceOf(
                    [
                        "message" => "Password is required",
                    ]
                ),
                new StringLength(
                    [
                        'min' => 8,
                        'messageMinimum' => 'Password is too short. Minimum 8 characters'
                    ]
                ),
                new Confirmation(
                    [
                        'message' => 'Password doesn\'t match confirmation',
                        'with' => 'passwordRepeat'
                    ]
                )
            ]
        );
        $this->add($password);

        // Password repeat
        $passwordRepeat = new Password(
            "passwordRepeat",
            [
                "placeholder" => "Password repeat",
                "class"       => "form-control",
            ]
        );
        $passwordRepeat->setLabel("Password repeat");
        $passwordRepeat->setFilters(
            [
                "striptags",
                "string",
            ]
        );
        $passwordRepeat->addValidators(
            [
                new PresenceOf(
                    [
                        "message" => "Password repeat is required",
                    ]
                )
            ]
        );
        $this->add($passwordRepeat);

        // File
        $file = new File(
            "image",
            [
                "placeholder" => ""
            ]
        );
        $file->setLabel("Profile picture");
        $file->addValidators(
            [
                new FileValidator(
                    [
                        'allowEmpty' => true
                    ]
                )
            ]
        );
        $this->add($file);

        // Active
        $active = new Check(
            "active",
            [
                "value" => 1,
                "checked" => "checked"
            ]
        );
        $active->setLabel("Active");
        $active->setFilters(
            [
                "striptags",
                "int",
            ]
        );
        $this->add($active);

    }

}

Why not move extension check to the validator?

        $file->setLabel("Profile picture");
        $file->addValidators(
            [
                new FileValidator(
                    [
                        'allowEmpty' => true,
                        "allowedTypes" => [
                            "image/jpeg",
                            "image/png",
                        ],
                        "messageType" => "Allowed file types are :types",
                    ]
                )
            ]
        );

https://docs.phalcon.io/en/3.0.0/api/Phalcon_Validation_Validator_File.html



6.0k
Accepted
answer

Hi, try this:


                                     if($this->request->hasFiles() == true)
                                            {

                                                foreach ($this->request->getUploadedFiles() as $upload)
                                                {
                                                     if($upload->getName() != '')
                                                      {
                                                            //do validation and save image or not
                                                      }
                                                      //there is no image uploading

                                                }
                                            }


13.8k

Thank a lot for your help this worked!



13.8k
edited May '17

Why not move extension check to the validator?

        $file->setLabel("Profile picture");
        $file->addValidators(
            [
                new FileValidator(
                    [
                        'allowEmpty' => true,
                        "allowedTypes" => [
                            "image/jpeg",
                            "image/png",
                        ],
                    "messageType" => "Allowed file types are :types",
                    ]
                )
            ]
        );

https://docs.phalcon.io/en/3.0.0/api/Phalcon_Validation_Validator_File.html


I tried this solution but didnt manage to get it to work. It would even allow me to upload a zip file and on update it would make the field empty. I'll look into this solution again later when I'm more familiar with the form objects.

Thanks a lot though!

if($this->request->hasFiles() == true) { foreach ($this->request->getUploadedFiles() as $upload) { if($upload->getName() != '') { //do validation and save image or not } //there is no image uploading } }

Its ok , how to validate for image uploaded or not, befor foreach() loop