upload a file in cakePHP - cakephp

Hi I want to upload a file using cake php, i got the file and i am using
move_uploaded_file() to move to a specific location but it is not moving my simple logic is
shown below
if (move_uploaded_file($this->data['Add']['upload']['tmp_name'], APP . 'views' . DS .
'static' . DS.'uploads'.DS.'Rajaram'.DS )) {
LogUtil::$logger->debug('KMP File upload Url :
'.var_export($this->data, true));
}
Thanks in Advance.

File uploading is something CakePHP doesn’t do out of the box, which is one of the only thing that annoys me about the framework.
I tackled this by adding file handling to a model using callback methods. I upload the actual file with beforeSave(), and delete the file from the file system with beforeDelete(). A sample model looks like this:
<?php
App::uses('File', 'Utility');
class Image extends AppModel {
public $name = 'Image';
public function beforeSave($options = array()) {
$fieldName = 'filename';
$field = $this->data[$this->alias][$fieldName];
if (!is_array($field)) {
$this->validationErrors[$fieldName][] = 'No file detected';
return false;
}
switch ($field['error']) {
case UPLOAD_ERR_OK:
$newFilename = time() . '.jpg';
$uploadDir = WWW_ROOT . 'files/';
$source = $field['tmp_name'];
$destination = $uploadDir . $newFilename;
if (move_uploaded_file($source, $destination)) {
$this->data[$this->alias][$fieldName] = $newFilename;
return true;
}
else {
$this->validationErrors[$fieldName][] = 'No file detected';
return false;
}
break;
default:
$this->validationErrors[$fieldName][] = 'No file detected';
return false;
break;
}
}
public function beforeDelete($cascade = true) {
$image = $this->findById($this->id);
$file = new File(WWW_ROOT . 'files/' . $image['Image']['filename']);
return $file->delete();
}
}
Obviously this isn’t a perfect implementation, so feel free to take from it, learn from it, adapt it.
This was written off-the-cuff for a project recently where there’s only one model that has images attached, but on a larger project I’d more than likely wrap it up into a nice model behavior.

In your model you can use the afterSave callback method to handle your file upload:-
public function afterSave($created) {
if (isset($_FILES['data']['name'][$this->alias]['filename'])) {
$filename = $_FILES['data']['name'][$this->alias]['filename'];
$fileInfo = pathinfo($filename);
$fileExt = isset($fileInfo['extension']) ? $fileInfo['extension'] : '';
$filename = $fileInfo['filename'];
$newFilename = "$filename.$fileExt";
$dir = WWW_ROOT . 'files' . DS . 'uploads';
$target = $dir . DS . $newFilename;
move_uploaded_file($source, $target);
}
}
You can use $newFilename to change the filename to something appropriate if need be (I tend to check if a file with the same name already exists and rename the new one to avoid overwriting it.

Related

Upload multiple pictures renaming files in Codeigniter 3

I'm not able to figure out what's wrong in my code.
In my project the user is allowed to upload multiple pictures for each aircraft registration number; I want to rename each uploaded files with the following rules: registration id, minus sign, progressive number; so if the user upload a new image file for the registration id xxx, the uploaded filename becomes xxx-1.jpg
The code to upload the multiple files is the following; it works fine so far...
// Count uploaded files
$countfiles = count($_FILES['files']['name']);
// Define new image name
$image = $id . '-1.jpg';
for($i=0;$i<$countfiles;$i++)
{
if(!empty($_FILES['files']['name'][$i]))
{
// Define new $_FILES array - $_FILES['file']
$_FILES['file']['name'] = $_FILES['files']['name'][$i];
$_FILES['file']['type'] = $_FILES['files']['type'][$i];
$_FILES['file']['tmp_name'] = $_FILES['files']['tmp_name'][$i];
$_FILES['file']['error'] = $_FILES['files']['error'][$i];
$_FILES['file']['size'] = $_FILES['files']['size'][$i];
// Check if the image file exist and modify name in case
$filename = $this->_file_newname($uploaddir,$image);
// Set preference
$config['upload_path'] = $upload_path;
$config['allowed_types'] = 'jpg|jpeg|png|gif';
$config['max_size'] = '5000';
$config['file_name'] = $filename;
//Load upload library
$this->load->library('upload',$config);
$arr = array('msg' => 'something went wrong', 'success' => false);
// File upload
if($this->upload->do_upload('file'))
{
$data = $this->upload->data();
}
}
}
The _file_newname() function does the file renaming jog, here you are the code:
private function _file_newname($path, $filename)
{
if ($pos = strrpos($filename, '.')) {
$name = substr($filename, 0, $pos);
$ext = substr($filename, $pos);
} else {
$name = $filename;
}
$newpath = $path.$filename;
$newname = $filename;
if(file_exists($newpath)){
$counter = 1;
if($pos = strrpos($name, '-')) {
$oldcounter = substr($name, $pos + 1);
if(is_numeric($oldcounter)){
$counter = $oldcounter++;
$name = substr($name, 0, $pos);
}
}
$newname = $name . '-' . $counter . $ext;
$newpath = $path . $newname;
while (file_exists($newpath)) {
$newname = $name .'-'. $counter . $ext;
$newpath = $path.$newname;
$counter++;
}
}
return $newname;
}
Now...the issue....the renaming function works fine if the user upload one file each time...so the first upload set the file name xxx-1.jpg, the second upload set the filename to xxx-2.jpg and so on....but....if the user upload more then one file at time...the second file become xxx-1x.jpg.
If already exists one file on server ( for example xxx-1.jpg ) and the user upload two more files..they are renamed as xxx-2.jpg ( correct) and xxx-21.jpg (wrong...should be xxx-3.jpg).
Any hint or suggestion to fix the issue?
Thanks a lot
Your new file name is out of the loop for the uploaded photos.
So it becomes static.
Put that line inside the loop:
// Define new image name
$image = $id . '-' . $i . '.jpg';
This way you are gonna keep the $id and rename the files according to the iteration of the loop - $i.

Declaration of Upload::beforeSave() should be compatible with Model::beforeSave($options = Array) [APP/Model/Upload.php, line 5]

I am being shown the following error on top of my page when using beforeSave method in my Upload model.
Strict (2048): Declaration of Upload::beforeSave() should be
compatible with Model::beforeSave($options = Array)
[APP/Model/Upload.php, line 5]
Could someone point out what I'm doing wrong?
Here is my model:
<?php
App::uses('AppModel', 'Model');
class Upload extends AppModel {
protected function _processFile() {
$file = $this->data['Upload']['file'];
if ($file['error'] === UPLOAD_ERR_OK) {
$name = md5($file['name']);
$path = WWW_ROOT . 'files' . DS . $name;
if (is_uploaded_file($file['tmp_name'])
&& move_uploaded_file($file['tmp_name'], $path) ) {
$this->data['Upload']['name'] = $file['name'];
$this->data['Upload']['size'] = $file['size'];
$this->data['Upload']['mime'] = $file['type'];
$this->data['Upload']['path'] = '/files/' . $name;
unset($this->data['Upload']['file']);
return true;
}
}
return false;
}
public function beforeSave() {
if (!parent::beforeSave($options)) {
return false;
}
return $this->_processFile();
}
}
?>
Just change this line
public function beforeSave() {
to this, so you have correct method declaration
public function beforeSave($options = array()) {
The beforeSave() function executes immediately after model data has been successfully validated, but just before the data is saved. This function should also return true if you want the save operation to continue.
This callback is especially handy for any data-massaging logic that needs to happen before your data is stored. If your storage engine needs dates in a specific format, access it at $this->data and modify it.
Below is an example of how beforeSave can be used for date conversion. The code in the example is used for an application with a begindate formatted like YYYY-MM-DD in the database and is displayed like DD-MM-YYYY in the application. Of course this can be changed very easily. Use the code below in the appropriate model.
public function beforeSave($options = array()) {
if (!empty($this->data['Event']['begindate']) &&
!empty($this->data['Event']['enddate'])
) {
$this->data['Event']['begindate'] = $this->dateFormatBeforeSave(
$this->data['Event']['begindate']
);
$this->data['Event']['enddate'] = $this->dateFormatBeforeSave(
$this->data['Event']['enddate']
);
}
return true;
}
public function dateFormatBeforeSave($dateString) {
return date('Y-m-d', strtotime($dateString));
}
Make sure that beforeSave() returns true, or your save is going to fail.

Find all in CakePHP is dumping a semicolon in my view

I don't know what exactly is happening with my CakePHP app. It worked last week and I have literally changed nothing with that particular file.
When I use find 'all' in my controller it dumps a semi-colon in my view even if there is nothing in the view file.
Here is my code
$evts = $this->Event->find('all');
There is nothing in my view file. I don't know if it makes a difference but I'm using a json view.
As requested here is the complete code for the action
public function search(){
$this->Event->recursive = 2;
$conditions = array();
if(!empty($this->request->query['name'])){
$conditions = array('Event.name LIKE ' => "%" . str_replace(" ","%", $this->request->query['name']) . "%");
}
if(!empty($this->request->query['home'])){
$conditions = array('Event.home_team_id' => $this->request->query['home']);
}
if(!empty($this->request->query['away'])){
$conditions = array('Event.away_team_id' => $this->request->query['away']);
}
$limit = 25;
if(!empty($this->request->query['limit'])){
$limit = $this->request->query['limit'];
}
//$evts = $this->Event->find('all',array('conditions'=>array($conditions),'order' => array('Event.start_time'),'limit'=>$limit));
$evts = $this->Event->find('all');
$this->set('events',$evts);
}
Everything in the view has been commented out ... but here is the code anyway
$results = array();
$i = 0;
foreach ($events as $event) {
$results[$i]['id'] = $event['Event']['id'];
$results[$i]['label'] = $event['Event']['name'] . "(" . date_format(date_create($event['Event']['start_time']), 'D, jS M Y') . ")";
$results[$i]['value'] = $event['Event']['name'];
$results[$i]['home_team_name'] = $event['HomeTeam']['name'];
$results[$i]['away_team_name'] = $event['AwayTeam']['name'];
$results[$i]['sport_name'] = $event['Sport']['name'];
$results[$i]['tournament_name'] = $event['Tournament']['name'];
$results[$i]['start_time'] = $event['Event']['start_time'];
$results[$i]['img'] = $event['Event']['img_path'];
$results[$i]['listener_count'] = 0; //TODO Get the follower count
$i++;
}
echo json_encode($results);
Display
If you changed nothing with that particular file then perhaps you have installed callbacks (either in the controller or in the model) that echo that ";". Look for 'afterFind' calls...
Search your model and your controller folders for "echo statement". In fact you should search your entire app folder except for the Views folder.

cakephp - Save file syncronized and send as attachment

Hi have a strange problem with File::write() in cakephp.
I write this code to generate a pdf view and save it on a file.
private function generate( $id ){
$order = $this->Order->read( null, $id );
$this->set('order', $order);
$view = new View($this);
$viewdata = $view->render('pdf');
//set the file name to save the View's output
$path = WWW_ROOT . 'ordini/' . $id . '.pdf';
$file = new File($path, true);
//write the content to the file
$file->write( $viewdata );
//return the path
return $path;
}
I need generate this pdf and send it as attacchment so, this is my code
public function publish ($id) {
$pdf_file = $this->generate( (int)$id );
$Email = new CakeEmail();
$Email->from(array('info#.....com' => '......'));
$Email->to('....#gmail.com');
$Email->subject('Nuovo ordine');
$filepath = WWW_ROOT . 'ordini/' . $id . '.pdf';
$Email->attachments(array('Ordine.pdf' => $filepath));
$Email->send('......');
}
The first time i run this code the email doesn't works, the email works nice only if the pdf is alredy in the folder.
I think that File::write perform some kind of async writing and when system execute Email::attachments the file ins't ready.
How can i insert a while() in this code to check file avability?
thanks a lot.
if($file->write( $viewdata ))
{
return $path;
}
using the return value wasn't enough.
I changed this line
$file = new File($path, false);
Opening file without forcing worked for me.

Model Callback beforeDelete

I'm trying to delete images when deleting the container of those images with a cascading model::delete
The cascading works fine, but I can't get the model call back afterDelete to work properly so I can delete the actual image files when doing the delete.
function beforeDelete() {
$containerId = $this->id;
$numberOfImages = $this->RelatedImage->find('count', array('conditions' => array('RelatedImage.container_id' => 'containerId')));
if ($numberOfImages > 0){
$relatedImages = $this->RelatedImage->find('all', array('conditions' => array('RelatedImage.container_id' => 'containerId')));
foreach ($relatedImages as $image) {
$myFile = WWW_ROOT . 'image' . $containerId . '_i' . $image['RelatedImage']['id'] . '.jpg';
unlink($myFile);
$myThumb = WWW_ROOT . 'img/' . $image['RelatedImage']['thumbnail'];
unlink($myThumb);
}
return true;
} else{
return false;
}
}
The if statement fails each time, even though I know there are images in the table. If I can get the if statement to at least execute i will add further validation on the unlink.
I would do it in this way:
in beforeDelete get the images data
function beforeDelete(){
$relatedImages = $this->RelatedImage->find('all', array('conditions' => array('RelatedImage.container_id' => 'containerId')));
$this->relatedImages = $relatedImages;
$this->currentId = $this->id; //I am not sure if this is necessary
return true;
}
then in the afterDelete() as Oscar suggest do the actual delete of the image:
function afterDelete(){
$relatedImages = $this->relatedImages;
$containerId = $this->currentId; //probably this could be just $this->id;
foreach ($relatedImages as $image) {
$myFile = WWW_ROOT . 'image' . $containerId . '_i' . $image['RelatedImage']['id'] . '.jpg';
unlink($myFile);
$myThumb = WWW_ROOT . 'img/' . $image['RelatedImage']['thumbnail'];
unlink($myThumb);
}
}
this way you are save, even if the model fail to delete the record you will delete images only if the delete was actually happen.
HTH
If the models are related with hasMany/hasOne, both RelatedImage::beforeDelete() and RelatedImage::afterDelete() should be called when they are removed. Try putting the delete logic there instead?

Resources