Search code examples
phpformscodeignitervalidation

Codeigniter form validation returns all errors if file is too big


I have a controller method that checks for post data and parses this through form_validation run method. There is also an image upload field in my view where an image can be uploaded. The problem is that when a file gets uploaded which is way too big, let's say over 10MB, the form validation will return every single error defined in my validation rules. If i upload a file which is still too big, but not too much, i will get the correct error message back.

My code looks like this:

public function addVacancy()
{
    $this->is_logged_in();

    //Check if user has correct permission to access this page
    $aclConfig = array('userID' => $this->auth_user_id);
    $this->load->library('acl', $aclConfig);
    $redirect_protocol = USE_SSL ? 'https' : NULL;

    if (!empty($this->auth_user_id)) {
        if ( $this->acl->hasPermission("create_vacancy") ) {
            //If the value is filled in we know it is an admin creating a vacancy for an organisation, we also set a boolean so we show a bit different message in confirmation.php
            $orgId = $this->input->post('orgid');
            if (empty($orgId)) {
                $orgId = $this->userRoles_model->getOrgUserId($this->auth_user_id);
            } else {
                $adminEditing = TRUE;
            }
            $vacancyId = null;
            $data = null;

            if (strlen($this->input->post('occupancy')) != 1) {
                $occupancy = 3;
            } else {
                $occupancy = $this->input->post('occupancy');
            }

            $vacancy_data = [
                'org_id' => $orgId,
                'offer' => $this->input->post('offer'),
                'name' => $this->input->post('title'),
                'website' => $this->input->post('website'),
                'description' => $this->input->post('vacdescription'),
                'address_line_1' => $this->input->post('route'),
                'address_line_2' => $this->input->post('street_number'),
                'address_postal_code' => $this->input->post('postalcode'),
                'address_city' => $this->input->post('city'),
                'address_country' => $this->input->post('country'),
                'number_required' => $this->input->post('count'),
                'engagement' => $this->input->post('time'),
                'occupancy_kind' => $occupancy,
                'create_time' => date('Y-m-d H:i:s'),
                'accessibility' => $this->input->post('vacaccessibility'),
                'status' => 0
            ];

            $dates_data = [
                'fullvacancydate' => $this->input->post('daterange-vacancy')
            ];

            if (!empty($dates_data['fullvacancydate'])) {
                $splitfromandto = explode(" - ", $dates_data['fullvacancydate']);
                $vacancy_data['vacancy_start_date'] = date( 'Y-m-d', strtotime($splitfromandto[0]));
                $vacancy_data['vacancy_end_date'] = date( 'Y-m-d', strtotime($splitfromandto[1]));
            } else {
                $today = date('Y-m-d');
                $todayPlus6Months = date('Y-m-d', strtotime("+6 months", strtotime($today)));
                $vacancy_data['vacancy_start_date'] = $today;
                $vacancy_data['vacancy_end_date'] = $todayPlus6Months;
            }

            $contactId = $this->input->post('contactpersons');
            if ($contactId == 0) {
                $contact_data = [
                    'org_id' => $orgId,
                    'family_name' => $this->input->post('family_name'),
                    'first_name' => $this->input->post('first_name'),
                    'email' => $this->input->post('email'),
                    'phone' => $this->input->post('phone'),
                    'function' => $this->input->post('function')
                ];
            }else{
                $contact_data = (array) $this->orgContacts_model->get($contactId);
            }

            $this->load->library('form_validation');
            $validation_data = array_merge($vacancy_data, $contact_data);
            $this->form_validation->set_data($validation_data);
            $validation_rules = [
                [
                    'field' => 'offer',
                    'label' => 'offer',
                    'rules' => 'trim|required',
                    'errors' => ['required' => 'Wat heb je te bieden is verplicht.']
                ],
                [
                    'field' => 'name',
                    'label' => 'name',
                    'rules' => 'trim|required',
                    'errors' => ['required' => 'Naam is verplicht.']
                ],
                [
                    'field' => 'description',
                    'label' => 'description',
                    'rules' => 'trim|required',
                    'errors' => ['required' => 'Beschrijving is verplicht.']
                ],
                [
                    'field' => 'address_postal_code',
                    'label' => 'address_postal_code',
                    'rules' => 'trim|required',
                    'errors' => ['required' => 'Postcode is verplicht.']
                ],
                [
                    'field' => 'address_city',
                    'label' => 'address_city',
                    'rules' => 'trim|required',
                    'errors' => ['required' => 'Stad of gemeente is verplicht.']
                ],
                [
                    'field' => 'address_country',
                    'label' => 'address_country',
                    'rules' => 'trim|required',
                    'errors' => ['required' => 'Land is verplicht.']
                ],
                [
                    'field' => 'number_required',
                    'label' => 'number_required',
                    'rules' => 'trim|required',
                    'errors' => ['required' => 'Aantal vrijwilligers is verplicht.']
                ],
                [
                    'field' => 'occupancy_kind',
                    'label' => 'occupancy_kind',
                    'rules' => 'trim|required',
                    'errors' => ['required' => 'Bezetting is verplicht.']
                ],
                [
                    'field' => 'family_name',
                    'label' => 'family_name',
                    'rules' => 'trim|required',
                    'errors' => ['required' => 'Familie naam van de contactpersoon is verplicht.']
                ],
                [
                    'field' => 'first_name',
                    'label' => 'first_name',
                    'rules' => 'trim|required',
                    'errors' => ['required' => 'Voornaam van de contactpersoon is verplicht.']
                ],
                [
                    'field' => 'email',
                    'label' => 'email',
                    'rules' => 'trim|required',
                    'errors' => ['required' => 'Email van de contactpersoon is verplicht.']
                ],
                [
                    'field' => 'phone',
                    'label' => 'phone',
                    'rules' => 'trim|required',
                    'errors' => ['required' => 'Telefoonnummer van de contactpersoon is verplicht.']
                ],
                [
                    'field' => 'function',
                    'label' => 'function',
                    'rules' => 'trim|required',
                    'errors' => ['required' => 'Functie van de contactpersoon is verplicht.']
                ],
            ];
            $this->form_validation->set_rules($validation_rules);

            if ($this->form_validation->run()) {

                // Create new contact
                if ($contactId == 0) {
                    $contactId = $this->orgContacts_model->add($contact_data);
                }

                // Create vacancy
                $this->load->model('vacancy/vacancy_model');

                // Var to check
                $check = false;
                $isNoImageChosen = false;

                if(isset($_FILES['userfile']['name']) && is_uploaded_file($_FILES['userfile']['tmp_name'])) {
                    // Banner Upload
                    $config['upload_path'] = VACANCY_IMAGES;
                    $config['allowed_types'] = 'jpg|png|PNG|JPG|jpeg|bmp';
                    $config['min_width'] = 1024;
                    $config['min_height'] = 768;
                    $config['max_size'] = 3072;
                    $config['quality'] = 60;

                    $this->load->library('upload', $config);

                    if ($this->upload->do_upload('userfile')) {
                        $uploadData = $this->upload->data();

                        if (isset($uploadData)) {
                            $vacancy_data['banner'] = $uploadData['file_name'];
                            $check = true;
                        }
                    } else {
                        $imageOk = array('error' => $this->upload->display_errors());

                        // Passing Variables
                        $data['title'] = 'Give a Day - Error';
                        $data['class'] = 'vacancy';
                        $data['validationErrors'] = validation_errors();
                        $data['imgErrors'] = $imageOk['error'];

                        $data['feedback'] = array(
                            'type'  => 'alert-danger',
                            'icon'     => 'fa-times',
                            'title' => 'Oops!',
                            'text' => 'Uploaden van de foto is niet gelukt'
                        );

                        $content = 'dashboard/vacancy/error';
                    }
                } else {
                    //check on true cause there is no image selected
                    $check = true;
                    $isNoImageChosen = true;
                }

                //Check if image upload went without errors
                if($check) {
                    //Serialize the accessibility (because more than 1 value can be entered)
                    $vacancy_data['accessibility'] = serialize($vacancy_data['accessibility']);

                    $vacancyId = $this->vacancy_model->add($vacancy_data);

                    // Create Vacancy Contact
                    $vacancyContact['contact_id'] = $contactId;
                    $vacancyContact['vacancy_id'] = $vacancyId;
                    $this->vacancyContacts_model->add($vacancyContact);

                    // Set Interests
                    $this->load->model('vacancy/vacancyInterests_model');
                    $interests = $this->input->post('interests');
                    if ($interests != '') {
                        $interests = explode(",", $interests);
                        foreach ($interests as $id) {
                            $this->vacancyInterests_model->add($vacancyId, $id);
                        }
                    }

                    if ($isNoImageChosen) {
                        $k = array_rand($interests);
                        $randomInterestId = $interests[$k];
                        $randomBannerImageLocation = return_random_interest_banner($randomInterestId);
                        //Because the vacancy has already been created we need to do an update for setting the banner image
                        $bannerData = array (
                            'banner' => $randomBannerImageLocation
                        );
                        $this->vacancies_model->update($vacancyId, $bannerData);
                    }

                    // Set Skills
                    $this->load->model('vacancy/vacancySkills_model');
                    $skills = $this->input->post('skills');
                    if ($skills != '') {
                        $skills = explode(",", $skills);
                        foreach ($skills as $id) {
                            $this->vacancySkills_model->add($vacancyId, $id);
                        }
                    }

                    //Get organisation information for the name
                    $orgdata = $this->organization_model->get($orgId);
                    $orgname = $orgdata->name;
                    $orguserid = $this->userRoles_model->getAdminUserId($orgId);
                    $orguserdata = $this->users_model->get($orguserid[0]->user_id);
                    $orgemail = $orguserdata->email;

                    $mail_data['vacid'] = $vacancyId;
                    $mail_data['vacname'] = $vacancy_data['name'];
                    $mail_data['org_id'] = $orgId;
                    $mail_data['orgname'] = $orgname;
                    $mail_data['contactpersonfirstname'] = $contact_data['first_name'];

                    $this->email->from(GENERAL_MAIL, 'Give a Day');
                    $this->email->to($contact_data['email']);
                    $this->email->cc($orgemail);
                    $this->email->bcc(GENERAL_MAIL);
                    $this->email->subject('Je vrijwilligersactiviteit is aangemaakt');
                    $message = $this->load->view('mail/vacancy/creation_confirmation', $mail_data, TRUE);
                    $this->email->message($message);

                    $this->email->send();

                    // Passing Variables
                    $data['title'] = 'Give a Day - Succes!';
                    $data['class'] = 'vacancy';
                    $data['vacancyId'] = $vacancyId;
                    $data['vacancyName'] = $vacancy_data['name'];
                    if ($adminEditing) {
                        $data['adminedit'] = TRUE;
                        $data['orgid'] = $orgId;
                    }
                    $content = 'dashboard/vacancy/confirmation';
                } else {
                    // Passing Variables
                    $data['title'] = 'Give a Day - Error';
                    $data['class'] = 'vacancy';

                    $data['validationErrors'] = validation_errors();
                    $imageOk = array('error' => $this->upload->display_errors());
                    $data['imgErrors'] = $imageOk['error'];
                    $content = 'dashboard/vacancy/error';
                }
            } else {
                // Passing Variables
                $data['title'] = 'Give a Day - Error';
                $data['class'] = 'vacancy';

                $data['validationErrors'] = validation_errors();
                //$imageOk = array('error' => $this->upload->display_errors());
                //$data['imgErrors'] = $imageOk['error'];
                $content = 'dashboard/vacancy/error';
            }

            // Template declaration
            $partials = array('head' => '_master/header/head', 'navigation' => '_master/header/navigation_dashboard', 'content' => $content, 'footer' => '_master/footer/footer');
            $this->template->load('_master/master', $partials, $data);
        } else {
            //User does not have correct permissions --> Go to 404
            redirect(site_url( 'error/my404', $redirect_protocol));
        }
    } else {
        //User is anonymous -> go to login page (and remember the current URL for redirecting again after log in)
        $this->session->set_flashdata('referred_from', uri_string());
        redirect(site_url( 'login?redirect=user', $redirect_protocol));
    }
}

Note that when I upload an image of 19 MB, for some reason I end up in the else clause all the way at the bottom and I get a page showing all the validation errors saying "Wat heb je te bieden is verplicht.", "naam is verplicht", ... and all of them literally. All data is filled in correctly, and when i perform the exact same steps, but take an image which is less than 3MB, it works perfect.

So 0-3MB - works perfect. 3-10MB - gives perfect error and >10 MB - gives all validation errors.


Solution

  • You may find this useful, this is a helper I've created for form uploads to determine the maximum size for uploads based on post_max_size and upload_max_size

    Usage:

    $config['max_size'] = convert_bytes_to_type(file_upload_max_size(), 'KB');

    Helper:

    /**
     * Gets max upload size from post_max_size and upload_max_filesize
     * Returns whichever is smaller
     *
     * @return int
     */
    function file_upload_max_size() {
        $max_size = convert_to_bytes(ini_get('post_max_size'));
        $upload_max = convert_to_bytes(ini_get('upload_max_filesize'));
        if ($upload_max > 0 && $upload_max < $max_size) {
            $max_size = $upload_max;
        }
        return $max_size;
    }
    
    /**
     * Converts KB (K) through GB (G) to bytes
     *
     * @param string $from
     * @return int bytes
     */
    function convert_to_bytes($from) {
        $number = filter_var($from, FILTER_SANITIZE_NUMBER_INT);
        if (empty($number)) {
            return 0;
        }
        $type = strtoupper(str_replace($number, '', $from));
        switch ($type) {
            case "KB":
            case "K":
                $number = $number * 1024;
                break;
            case "MB":
            case "M":
                $number = $number * pow(1024, 2);
                break;
            case "GB":
            case "G":
                $number = $number * pow(1024, 3);
                break;
            default:
                return 0;
        }
        return fix_integer_overflow($number);
    }
    
    /**
     * Converts bytes to KB (K) through GB (G)
     *
     * @param int $bytes
     * @param string $type Type to convert to
     *                     KB (K) through GB (G)
     * @return int bytes
     */
    function convert_bytes_to_type($bytes, $type = 'MB') {
        $number = filter_var($bytes, FILTER_SANITIZE_NUMBER_INT);
        if (empty($number)) {
            return 0;
        }
        $type = strtoupper($type);
        switch ($type) {
            case "KB":
            case "K":
                $divisor = 1024;
                break;
            case "MB":
            case "M":
                $divisor = pow(1024, 2);
                break;
            case "GB":
            case "G":
                $divisor = pow(1024, 3);
                break;
            default:
                return 0;
        }
        return $bytes / $divisor;
    }
    
    /**
     * Converts bytes into human readable form
     *
     * @param string || File
     * @param int precision round precision
     * @return int rounded bytes in units
     */
    function human_readable_bytes($bytes, $precision = 2) {
        $CI = & get_instance();
        $CI->lang->load('number');
        if (is_file($bytes)) {
            $bytes = fix_integer_overflow(filesize($bytes));
        }
        $units = array(
            $CI->lang->line('bytes'),
            $CI->lang->line('kilobyte_abbr'),
            $CI->lang->line('megabyte_abbr'),
            $CI->lang->line('gigabyte_abbr'),
            $CI->lang->line('terabyte_abbr')
        );
        $bytes = max($bytes, 0);
        $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
        $pow = min($pow, count($units) - 1);
        $bytes /= pow(1024, $pow);
        return round($bytes, $precision) . ' ' . $units[$pow];
    }
    
    /**
     * Fixes integer overflow
     *
     * @param int $size
     * @return int
     */
    function fix_integer_overflow($size) {
        if ($size < 0) {
            $size += 2.0 * (PHP_INT_MAX + 1);
        }
        return $size;
    }
    

    https://pastebin.com/2zDJ90Qr