NOTE: This is easily the most popular post from my blog, but I think it's time to just set this sucker free. If someone wants to post it to the Cake site for posterity you have my permission. I cribbed part of the process from someone else, and I hope someone will find it useful past me. I just didn't want to see this die so it's here and if someone posts it again that's cool too! ----------------------------------------------------------------------------------------------------------------------------------------------------------------
I was unable to locate a simple file upload tutorial for CakePHP 1.2. Once I figured it out with the help of Aditya Mooley's filePost.tar.gz from August 9th 2005. Unfortunately his tutorial and demo are no longer active, so I've resurrected the code, updated it for CakePHP 1.2 and will walk you through it. Getting Started Set up a new 1.2 project. If you need help with that there are plenty of great tutorials out there including mine (if you're an Apple OS X Leopard user). Create your database with the following SQL: CREATE TABLE `attachments` ( `id` int(10) NOT NULL auto_increment, `name` varchar(100) NOT NULL, `description` text NOT NULL, `filename` varchar(255) NOT NULL, `date_added` datetime NOT NULL, PRIMARY KEY (`id`) ) Now that we've got our database in place and configured our database.php (you did remember to do that during your project setup right?) we can begin using the great cake console commands that come with CakePHP 1.2. cake bake Select to set up the model (m) and just let it configure everything. We won't worry about setting up the data validations for the purposes of this tutorial since there are other excellent data validation tutorials available. Continue by setting up the controller and then the view. Make sure you do them in that order as the controller is needed by the console prior to setting up the views. You do not need to set up any of the admin routing and views. Setting up the view Cake will treat your filename column as a regular input field like name in this example. You need to tell the form that you want it to process multipart form data and you need to change the form field to a file field type. Change views/add.ctp : create('Attachment');?> You'll want to make it look like this: create('Attachment', array('type' => 'file')); ?> This sets the form option type to file so you can perform a file upload. When you make these changes the way CakePHP handles your upload has changed. You're filename field is no longer: $this->data['filename'] It now is an array as described by the php manual. We will be accessing the filename field in the following ways: $this->data['Attachment']['filename']['tmp_name'] $this->data['Attachment']['filename']['name'] ... Setting up the model We need to accomplish 4 things in the model (for this basic tutorial): -Create some validation for the file we are uploading -Ensure we are uploading a unique filename -Process the upload -Delete the temporary file created on the server if the file is not valid Creating the validation rule Put the following at top of your Attachments_Controller.php file: var $validateFile = array( 'size' => 204800, 'type' => 'jpg,jpeg,png,gif' ); We've set the maximum size of the file in bytes and we've set the mime types that will be accepted. In this case we're only accepting images, but these can be customized to fit whatever use you may need. You can change to suit your needs. Remember though that these are the mime types so you don't want to add extensions like .doc. You'll want to add msword to accept .doc and .docx. Now let's set up a method to ensure that we only write unique filenames to the application. function generateUniqueFilename($fileName, $path='') { $path = empty($path) ? WWW_ROOT.'/files/' : $path; $no = 1; $newFileName = $fileName; while (file_exists("$path/".$newFileName)) { $no++; $newFileName = substr_replace($fileName, "_$no.", strrpos ($fileName, "."), 1); } return $newFileName; } So what we've got going on in this method is just a simple check in the directory we're writing to (../webroot/files/), if the filename already exists. If the file does exist we're going to add an underscore and a unique number to the end of it. We should now create the method that will handle our file upload. function handleFileUpload($fileData, $fileName) { $error = false; //Get file type $typeArr = explode('/', $fileData['type']); //If size is provided for validation check with that size. Else compare the size with INI file if (($this->validateFile['size'] && $fileData['size'] > $this- >validateFile['size']) || $fileData['error'] == UPLOAD_ERR_INI_SIZE) { $error = 'File is too large to upload'; } elseif ($this->validateFile['type'] && (strpos($this->validateFile ['type'], strtolower($typeArr[1])) === false)) { //File type is not the one we are going to accept. Error!! $error = 'Invalid file type'; } else { //Data looks OK at this stage. Let's proceed. if ($fileData['error'] == UPLOAD_ERR_OK) { //Oops!! File size is zero. Error! if ($fileData['size'] == 0) { $error = 'Zero size file found.'; } else { if (is_uploaded_file($fileData['tmp_name'])) { //Finally we can upload file now. Let's do it and return without errors if success in moving. if (!move_uploaded_file($fileData['tmp_name'], WWW_ROOT.'/ files/'.$fileName)) { $error = true; } } else { $error = true; } } } } return $error; } Here are a few highlights of this method: We evalute the size and isolate the mime type to evaluate it as well. Once validation has completed successfully the temp file is moved to the directory we configured Please note that you can customize where you upload the file by changing: if (!move_uploaded_file($fileData['tmp_name'], WWW_ROOT.'/ files/'."$fileName")) Finally, let's create a method to clean up the temporary file. function deleteMovedFile($fileName) { if (!$fileName || !is_file($fileName)) { return true; } if(unlink($fileName)) { return true; } return false; } Putting it all together in the controller Now that we've got the majority of the worker methods completed in our model we can tie them together in the add method of the controller. Our new add method should look like this: function add() { if (empty($this->data)) { $this->Document->create(); } else { $err = false; if (!empty($this->data['Attachment']['filename']['tmp_name'])) { $fileName = $this->generateUniqueFilename($this->data ['Attachment']['filename']['name']); echo 'The filename is:'. $fileName; $error = $this->handleFileUpload($this->data['Attachment'] ['filename'], $fileName); echo 'The error is: '. $error; } else { print_r($this->data); } if (!$error) { $this->data['Attachment']['filename'] = $fileName; if ($this->Attachment->save($this->data)) { $this->Session->setFlash(__('The Attachment has been saved', true)); $this->redirect(array('action'=>'index')); } else { $err = true; } } else { $this->Attachment->set($this->data); } if ($error || $err) { //Something failed. Remove the image uploaded if any. $this->deleteMovedFile(WWW_ROOT.IMAGES_URL.$fileName); $this->set('error', $error); $this->set('data', $this->data); $this->validateErrors($this->Attachment); $this->render(); } } } This is a pretty standard add method. We verify that data was filled in on the form and then set up the file on the server. Once the upload has been completed we can proceed by entering all the other information. There is, however, one problem. In a standard save the model just looks for the fields and saves them. In this case, cakePHP will be looking for: $this->data['Attachment']['filename'] This object does not exist. It is an array. So we'll need to set it... That's why we've got the following line. $this->data['Attachment']['filename'] = $fileName; Once that is taken care of, you'll be all set! Go ahead and try it out! Tying up some loose ends There are still 2 things that would be nice to have. -A link to download the file in the index view -Protecting the directory we uploaded to so people won't have access to all the files Adding the download link is as easy as changing the following line in our view/index.ctp: You'll want to change it to: link($attachment['Attachment']['filename'], '/files/'.$attachment ['Attachment']['filename']); ?> Voila, one problem solved.... Now on the more pressing security concern for our little demo. Create an .htaccess file that looks like this: Deny from All Save this file to the directory in which you chose to upload your files. Congratulations. You should now have a working simple upload in cakePHP 1.2! Once again, I'd like to extend my thanks to Aditya Mooley's original code that formed the basis for this tutorial. I hope that you'll find this lowers the barrier for creating simple uploads within your next cakePHP 1.2 application. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "CakePHP" group. To post to this group, send email to cake-php@googlegroups.com To unsubscribe from this group, send email to cake-php+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/cake-php?hl=en -~----------~----~----~----~------~----~------~--~---