0

I am fairly new to CodeIgniter and I am trying to convert a working DropZone multi-file uploader to the MVC CodeIgniter format. I have seen some other examples here but need to get recommendations for my specific code.

I know how to place the upload/form page in a view and load via a controller. However, the form handler code is where I need help.

Upload/Form Page:

<head>
<script type="text/javascript" src="js/jquery-2.1.1.js"></script>
<!-- Add Dropzone -->
<link rel="stylesheet" type="text/css" href="css/dropzone.css" />
<script type="text/javascript" src="js/dropzone.js"></script>
</head>
<body>
<div class="image_upload_div">
<form action="upload_thumbnails.php" class="dropzone">
</form>
</div>  

<script type="text/javascript">
//Disabling autoDiscover
Dropzone.autoDiscover = false;
$(function() {
    //Dropzone class
    var myDropzone = new Dropzone(".dropzone");
    myDropzone.on("queuecomplete", function() {
        //Redirect URL
        //window.location.href = 'http://php.net';
    });
});
</script>
</body>

What parts of the form handler code should go in the model vs controller? This is the section I am struggling with.

Form Handler Code:

if(!empty($_FILES)){    

  function createThumbnail($filename) {
    $final_width_of_image = 200;
    $path_to_image_directory = 'uploads/';
    $path_to_thumbs_directory = 'uploads/thumbs/';

    if(preg_match('/[.](jpg)$/', $filename)) {
        $im = imagecreatefromjpeg($path_to_image_directory . $filename);
    } else if (preg_match('/[.](gif)$/', $filename)) {
        $im = imagecreatefromgif($path_to_image_directory . $filename);
    } else if (preg_match('/[.](png)$/', $filename)) {
        $im = imagecreatefrompng($path_to_image_directory . $filename);
    }

    $ox = imagesx($im);
    $oy = imagesy($im);
    $nx = $final_width_of_image;
    $ny = floor($oy * ($final_width_of_image / $ox));
    $nm = imagecreatetruecolor($nx, $ny);
    if(!imagecopyresized($nm, $im, 0,0,0,0,$nx,$ny,$ox,$oy)){
        header("HTTP/1.0 500 Internal Server Error");
        echo 'Thumbnail Not created';
        exit(); 
    }
    if(!file_exists($path_to_thumbs_directory)) {
        if(!mkdir($path_to_thumbs_directory)) {
            header("HTTP/1.0 500 Internal Server Error");
            echo 'Thumbnail Not Created';
            exit();
        } 
    }
    // Save new thumbnail image
    imagejpeg($nm, $path_to_thumbs_directory . $filename);

  }

//database configuration
$dbHost = 'localhost';
$dbUsername = 'insertnamehere';
$dbPassword = 'insertpwhere';
$dbName = 'ci_local';
//connect with the database
$conn = new mysqli($dbHost, $dbUsername, $dbPassword, $dbName);
if($mysqli->connect_errno){
    echo "Failed to connect to MySQL: (" . $mysqli->connect_errno . ") " . $mysqli->connect_error;
}

$targetDir = "uploads/";
$fileName = $_FILES['file']['name'];
$targetFile = $targetDir.$fileName;

if(move_uploaded_file($_FILES['file']['tmp_name'],$targetFile)){
    // NEW
    //createThumbnail($file['file']['name']);
    createThumbnail($fileName);
    // NEW

    //insert file information into db table
    $conn->query("INSERT INTO files (file_name, uploaded) VALUES('".$fileName."','".date("Y-m-d H:i:s")."')");
} else {
    echo "Ooops";
}

}

Any recommendations will be greatly appreciated.

12AX7
  • 310
  • 4
  • 14
  • Why aren't you using codeigniter upload library and database system? That's what a framework is for! – Alex Dec 11 '17 at 21:35
  • The CI upload library is for single file uploads. I am using DropZone for drag and drop multi-file uploads and simply listed the conventional HTML example seeking in particular how to get the same code wkg in CI (MVC) format. – 12AX7 Dec 11 '17 at 21:48
  • 1
    DropZone does this asynchronously using different instances of the script. You don't have to handle multiple files at once (and you aren't doing that here either)! Even if you had to, you could still use a foreach loop and CI's upload library. And also, I could easily mess up your database with a sql statement as a filename as you aren't escaping your strings. That's one more reason to use the framework as intended with query builder, it does it for you! Not to mention CI's upload library will make filenames safe as well. – Alex Dec 11 '17 at 21:58
  • Point well taken regarding escaping strings in query - I just missed that when using my code example. I had concluded that it was not possible to perform anything but single-file uploads with the CI framework without using some custom code. Can you show an example of using a foreach loop and CI's upload library to perform multi-file uploads? – 12AX7 Dec 11 '17 at 22:07
  • 1
    Please reread the first part of my previous comment. Dropzone is Ajax, you just upload one at a time but it does so doing multiple instances hence you can process x amount of files at once. It doesn't post it all to one instance. To upload multiple files the regular way even you in the code above would have to use a foreach loop to go through the files array. The fact that you don't proves the point I'm trying to make. – Alex Dec 11 '17 at 22:24
  • 1
    That being said there is no real issue in not using CI's library (just make those filenames safe!). It's just a pref of mine to use all the tools a framework affords me so I don't feel like I'm in the dark ages of procedural. – Alex Dec 11 '17 at 22:30
  • Thanks for your responses as they have been very helpful. I definitely agree that is a better to use the CI framework since it affords pre-wriiten code so there is no need to reinvent the wheel. – 12AX7 Dec 11 '17 at 22:48

1 Answers1

1

Your createThumbnail function is a good example of something that should go either in a model or a library or a helper. A helper just contains functions, whereas a library or model are class based and therefore can access class properties and have constructors. I would lean towards making it either a library or a helper as it isn't coupled (thus reusable) to anything specific or other functions.

Generally you only want to return or throw Exceptions in any function that is not in a controller.

  1. This allows you to set your output in one file rather than having to search through your models, libraries, .etc. if you want to change your output down the road (for example if you plan on using AJAX).
  2. Also allows for code bases that aren't tightly coupled and can be easily ported to other installations that may be different.
  3. Good for readability.

Models

Anything database related should go in a model as models represent the data logic in or the actual data used by your application. You should really use Codeigniter's in-built database functions instead of initializing the database and running the query yourself. Your insert or update statements should move into a model related to the controller.

Controllers

Your controller is just used for loading models, and any variables the view may require. It also should be used to set the response, whether that be a flash message or json encoded. In many examples it is also used for form_validation or data checks before performing CRUD operations that exist within the related model.

tl;dr anything database related go in the model, anything reusable and not tightly coupled goes in a library, or if it has no specific relation to oop goes in a helper (like an array sorting algorithm), anything form/input/view/response related goes in a controller.


Roughly you can do something like this (code may not work out of the box; I use my own implementation that differs from the default dz behavior as I employing jquerys sortable as well... I also am unsure about the error handling as I do that using another technique):

// byte_helper.php

/**
 * 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);
    $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;
    }
    return fix_integer_overflow($number);
}

/**
 * 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;
}

class Dropzone_controller extends CI_Controller {

    public function upload() {
        if (!$this->input->is_ajax_request()) {
            exit('No direct script access allowed');
        }
        $this->load->helper('byte');
        $config['upload_path'] = ''; // upload directory realpath
        $config['file_name'] = md5(time()); // I like to set my own filename
        $config['overwrite'] = true;
        $config['allowed_types'] = 'jpg|jpeg|png|gif';
        $config['max_size'] = file_upload_max_size();
        $config['file_ext_tolower'] = true;
        $this->load->library('upload', $config);
        if (!$this->upload->do_upload('file')) {
            $this->dz_error($this->upload->display_errors('', '<br>'));
        }
        $data = $this->upload->data();
        $full_path = realpath($data['full_path']); // CI uses weird slashes
        // here you can use imagelib to resize if you want, I personally
        // like to use verot's class upload for that
        $this->load->model('dropzone_model');
        if (!$this->dropzone_model->add_image($full_path)) {
            @unlink($full_path); // remove orphaned image
            $this->dz_error('Failed to add image to the database!');
        }
        $this->dz_success();
    }

    // https://stackoverflow.com/questions/27030652/how-to-display-error-message-of-jquery-dropzone
    private function dz_error($error) {
        $this->output
                ->set_header("HTTP/1.0 400 Bad Request")
                ->set_output($error)
                ->_display();
        exit;
    }

    private function dz_success() {
        // don't really need to do anything here?
    }

}

class Dropzone_model extends CI_Model {

    public function add_image($full_path) {
        return $this->db->insert('sometable', array('image' => $full_path));
    }

}
Alex
  • 9,215
  • 8
  • 39
  • 82