How to do file uploads with PHP and the Zend Framework? - php

I am using Zend Framework 1.9.6. I think I've got it pretty much figured out except for the end. This is what I have so far:
Form:
<?php
class Default_Form_UploadFile extends Zend_Form
{
public function init()
{
$this->setAttrib('enctype', 'multipart/form-data');
$this->setMethod('post');
$description = new Zend_Form_Element_Text('description');
$description->setLabel('Description')
->setRequired(true)
->addValidator('NotEmpty');
$this->addElement($description);
$file = new Zend_Form_Element_File('file');
$file->setLabel('File to upload:')
->setRequired(true)
->addValidator('NotEmpty')
->addValidator('Count', false, 1);
$this->addElement($file);
$this->addElement('submit', 'submit', array(
'label' => 'Upload',
'ignore' => true
));
}
}
Controller:
public function uploadfileAction()
{
$form = new Default_Form_UploadFile();
$form->setAction($this->view->url());
$request = $this->getRequest();
if (!$request->isPost()) {
$this->view->form = $form;
return;
}
if (!$form->isValid($request->getPost())) {
$this->view->form = $form;
return;
}
try {
$form->file->receive();
//upload complete!
//...what now?
$location = $form->file->getFileName();
var_dump($form->file->getFileInfo());
} catch (Exception $exception) {
//error uploading file
$this->view->form = $form;
}
}
Now what do I do with the file? It has been uploaded to my /tmp directory by default. Obviously that's not where I want to keep it. I want users of my application to be able to download it. So, I'm thinking that means I need to move the uploaded file to the public directory of my application and store the file name in the database so I can display it as a url.
Or set this as the upload directory in the first place (though I was running into errors while trying to do that earlier).
Have you worked with uploaded files before? What is the next step I should take?
Solution:
I decided to put the uploaded files into data/uploads (which is a sym link to a directory outside of my application, in order to make it accessible to all versions of my application).
# /public/index.php
# Define path to uploads directory
defined('APPLICATION_UPLOADS_DIR')
|| define('APPLICATION_UPLOADS_DIR', realpath(dirname(__FILE__) . '/../data/uploads'));
# /application/forms/UploadFile.php
# Set the file destination on the element in the form
$file = new Zend_Form_Element_File('file');
$file->setDestination(APPLICATION_UPLOADS_DIR);
# /application/controllers/MyController.php
# After the form has been validated...
# Rename the file to something unique so it cannot be overwritten with a file of the same name
$originalFilename = pathinfo($form->file->getFileName());
$newFilename = 'file-' . uniqid() . '.' . $originalFilename['extension'];
$form->file->addFilter('Rename', $newFilename);
try {
$form->file->receive();
//upload complete!
# Save a display filename (the original) and the actual filename, so it can be retrieved later
$file = new Default_Model_File();
$file->setDisplayFilename($originalFilename['basename'])
->setActualFilename($newFilename)
->setMimeType($form->file->getMimeType())
->setDescription($form->description->getValue());
$file->save();
} catch (Exception $e) {
//error
}

By default, files are uploaded to the system temporary directory, which means you'll to either :
use move_uploaded_file to move the files somewhere else,
or configure the directory to which Zend Framework should move the files ; your form element should have a setDestination method that can be used for that.
For the second point, there is an example in the manual :
$element = new Zend_Form_Element_File('foo');
$element->setLabel('Upload an image:')
->setDestination('/var/www/upload')
->setValueDisabled(true);
(But read that page : there are other usefull informations)

If you were to move the file to a public directory, anyone would be able to send a link to that file to anyone else and you have no control over who has access to the file.
Instead, you could store the file in the DB as a longblob and then use the Zend Framework to provide users access the file through a controller/action. This would let you wrap your own authentication and user permission logic around access to the files.
You'll need to get the file from the /tmp directory in order to save it to the db:
// I think you get the file name and path like this:
$data = $form->getValues(); // this makes it so you don't have to call receive()
$fileName = $data->file->tmp_name; // includes path
$file = file_get_contents($fileName);
// now save it to the database. you can get the mime type and other
// data about the file from $data->file. Debug or dump $data to see
// what else is in there
Your action in the controller for viewing would have your authorization logic and then load the row from the db:
// is user allowed to continue?
if (!AuthenticationUtil::isAllowed()) {
$this->_redirect("/error");
}
// load from db
$fileRow = FileUtil::getFileFromDb($id); // don't know what your db implementation is
$this->view->fileName = $fileRow->name;
$this->view->fileNameSuffix = $fileRow->suffix;
$this->view->fileMimeType = $fileRow->mime_type;
$this->view->file = $fileRow->file;
Then in the view:
<?php
header("Content-Disposition: attachment; filename=".$this->fileName.".".$this->fileNameSuffix);
header('Content-type: ".$this->fileMimeType."');
echo $this->file;
?>

$this->setAction('/example/upload')->setEnctype('multipart/form-data');
$photo = new Zend_Form_Element_File('photo');
$photo->setLabel('Photo:')->setDestination(APPLICATION_PATH ."/../public/tmp/upload");
$this->addElement($photo);

Related

Zend Framework 3: create download link for generated file

I'm writing an app that lets the user upload an Excel file. It checks the file for errors, then, if no errors are found, it uploads the contents to a database. If it does find errors, the cells containing errors are colored red, then the file is saved. I then want to create a download link to this file so the user can check where they made mistakes.
The problem is that I am not sure how to create this link and where to store the file. I modify the file like this:
foreach ($badCells as $bcell) {
$sheet->getStyle($bcell)->applyFromArray(array(
'fill' => array(
'type' => \PHPExcel_Style_Fill::FILL_SOLID,
'color' => array('rgb' => 'FF4444')
)
));
}
And then save it with
$objWriter->save($dldir . $formData['upload']['name']);
$dldir is created with
$dldir = "/download/";
if (file_exists($dldir)) {
if (!is_dir($dldir)) {
unlink($dldir);
mkdir($dldir);
}
} else {
if(!is_dir($dldir)) {
mkdir($dldir);
}
}
Is this even the right way to do this? Can I store the files in any old folder or do they have go somewhere specific? How do I create the link to the specific file in the view for the user and make it accessible so they can download it?
I may be wrong .
For file upload i use this library Gargron/fileupload
The function bellow help to upload file to a specific folder and return full link of the file. You can save the link in DB
function uploadFile ($file){
$validator = new \FileUpload\Validator\Simple('5M');
$pathresolver = new \FileUpload\PathResolver\Simple($_SERVER['DOCUMENT_ROOT'].'/upload_folder');
$filesystem = new \FileUpload\FileSystem\Simple();
$fileupload = new \FileUpload\FileUpload(file, $_SERVER);
$fileupload->setPathResolver($pathresolver);
$fileupload->setFileSystem($filesystem);
$fileupload->addValidator($validator);
$md5Generator = new \FileUpload\FileNameGenerator\MD5 (true);
$fileupload->setFileNameGenerator($md5Generator);
list($files) = $fileupload->processAll();
foreach($files as $file){
if ($file->completed) {
return $_SERVER['DOCUMENT_ROOT'].'/upload_folder/'.$file->getFileName()) ;
}
}
}
Thanks for the help, I managed to figure it out over the weekend. Here's how I did
it:
$filename = join(DIRECTORY_SEPARATOR, array($dldir, $formData['upload']['name']));
$objWriter->save(str_replace(__FILE__,$filename,__FILE__));
This is how I save the file, using DIRECTORY_SEPARATOR so it will work correctly on both Windows and Linux.
return $this->redirect()->toRoute('import', ['action' => 'reject'],['query' => ['q' => 'file', 'name' => $filename]]);
In the controller, I redirect the route to the correct action and pass the file name on to it via a query URL.
public function rejectAction()
{
$filename = $this->getRequest()->getQuery('name', null);
return new ViewModel(array('filename' => $filename));
}
There, I obtain said file name through getRequest()->getQuery() and pass it on to the viewmodel.
Right-click here and choose 'Save As' to download your file.
And finally, this is how it shows the link in reject.phtml.
Downloading only works with right click and save as, I suspect I will have to write some sort of file handler to make ZF produce the correct headers for a normal left click download.

Symfony2 Filesystem - Save a stream of data in an existent directory

I have a data stream that I need to save in a PDF file and then store that file in an already existent directory, which is Documents/pdf. This directory is at the same level of src, app, web directories, and has all the permissions to write in it.
With my solution I always have the file saved under the web directory. I want the file to be under Documents/pdf. This is my controller:
/**
* #Route("/api/savePdf", name = "save_pdf")
* #Method("POST")
**/
public function savePdfAction(Request $request) {
$pdfPath = $this->get("kernel")->getRootDir() . '/../Documents/pdf/';
$data = $request->getContent();
$name = 'file.pdf';
$dateNow = new \DateTime('now');
$date = $dateNow->format('Y-m-d H-i-s');
$fileName = $date.$name;
try {
$fs = new Filesystem();
$fs->dumpFile($fileName, $data);
move_uploaded_file($fileName, $pdfPath.$fileName);
return new Response ("File saved correctly", 200, array('Content-Type' => 'application/json') );
}
catch(IOException $e) {
return new Response ("Error!", 500, array('Content-Type' => 'application/json'));
}
return new Response();
}
Don't use
move_uploaded_file
http://php.net/manual/en/function.move-uploaded-file.php
This function checks to ensure that the file designated by filename is a valid upload file (meaning that it was uploaded via PHP's HTTP POST upload mechanism).
I'm just guessing here but since you dump the content to a file yourself i don't think it does satisfy the move_uploaded_file usage condition.
Why don't you dump the content directly into the targetfolder and get rid of a manual move?
$fs->dumpFile($pdfPath.$fileName, $data);
should do the trick since your path is absolute anyways.

Silverstripe: User-specific files module

I've got an idea for a project and wondering if there's an already existing module that can achieve something similar. Basically I want to allow the users to upload/download files from their member profile. Users will only be able to view/download their own files. I have member profiles set up already, it's the file handling that I need to get working now.
I imagine it will be something like a gridfield displayed on the users profile. When they use an upload field it will save files in to assets/ folder. Which can only be viewed by that member (admins can view all in the /admin section of the CMS.
Are there any current modules that support something like this?
If I create an extension of the Member profile that allows uploading files, how could I implement the security for only users with the currentID to view files?
EDIT: OK So I've made some progress with this. The upload file function works. It saves it in to a folder which is named after the user. However, it saves the uploaded file in that directory, but also a blank file under the "Files" directory. Here is my code...
//User file upload function
public function UploadUserFile() {
$fields = new FieldList($field = new UploadField('UserFiles', 'Upload files'));
$field->setCanAttachExisting(false); // Block access to SilverStripe assets library
$field->setCanPreviewFolder(false); // Don't show target filesystem folder on upload field
$field->setFolderName('user-files/user-'.Member::currentUserID()); //Upload to a user specific folder
$actions = new FieldList(new FormAction('submit', 'Save Images'));
$form = new Form($this, 'UploadUserFile', $fields, $actions, null);
return $form;
}
public function submit($data, $form) {
$file = new File();
$form->saveInto($file);
$file->write();
return $this;
}
EDIT 2:
Turns out that the submit function was causing the extra file. Disregard. Still experiencing other problems now but will see if I can fix it first.
EDIT 3:
OK, I've had a go and I am able to display files on the front end. Here is my code...
//User file upload function
public function UploadUserFile() {
$fields = new FieldList($field = new UploadField('UserFiles', 'Upload files'));
$field->setCanAttachExisting(false); // Block access to SilverStripe assets library
$field->setCanPreviewFolder(false); // Don't show target filesystem folder on upload field
$field->setFolderName('User-Files/User-'.Member::currentUserID().'-'.Member::currentUser()->Surname.Member::currentUser()->FirstName); //Upload to a user specific folder
$field->setAutoUpload(false);
$actions = new FieldList(new FormAction('submit', 'Save Images'));
$form = new Form($this, 'UploadUserFile', $fields, $actions, null);
return $form;
}
//Refresh files function
public function submit($data, $form) {
return $this->redirectBack();
}
//Display users files
public function DisplayFiles() {
$arrayList = ArrayList::create();
$files = File::get()->filter(array(
"OwnerID" => Member::currentUserID(),
"ClassName:not" => 'Folder'
));
foreach($files as $file) {
if($file->canView()) {
$arrayList->push($file);
}
}
return $arrayList;
}
I'm trying to allow users to delete their file from the front end. What's the best way to handle this?
Thanks
Yes. Use the secureassets module! https://github.com/silverstripe-labs/silverstripe-secureassets

Creating a controller that handles file uploads

I would like to create a controller that handles uploading files to user specific folders. I currently have a from that allows users to upload a file which sends the post data to the controller.
What I would like the controller to do is take the uploaded file, and place it in a folder e.g. /public/{username}/files
But I am not too sure how to approach it using symfony.
As Mahok commented, the Symfony2 docs are useful here.
I would follow them with the added additions. When you save the document, pass the username:
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
//get the user and pass the username to the upload method
$user = $this->get('security.context')->getToken()->getUser();
$document->upload($user->getUsername());
$em->persist($document);
$em->flush();
$this->redirect(...);
}
When you upload the file, use the username:
public function upload($username)
{
if (null === $this->file) {
return;
}
//use the username for the route
$this->file->move(
"/public/$username/files/",
$this->file->getClientOriginalName()
);
// set the path property to the filename where you've saved the file
$this->path = $this->file->getClientOriginalName();
// clean up the file property as you won't need it anymore
$this->file = null;
}
Saving it this way you wont actually need to use the extra entity methods like "getAbsolutePath" etc
Note that you may have to slugify the username if you accept spaces etc.
Edit:
You will need to set up a oneToMany relationship for users to files so that you can locate the file later on.
This might help you---
$upload_dir = "your upload directory/{username}";
if (!is_dir($upload_dir)) {
#mkdir($upload_dir, "755", true);
}
move_uploaded_file($source,$destination);

php, data not updating after form post in zend?

this might be a bit of a novice question and here is my situation:
i have a upload form for uploading images. and in my editAction i do:
if ($request->isPost()) {
if (isset($_POST['upload_picture']) && $formImageUpload->isValid($_POST)) {
//here i will add the picture name to my database and save the file to the disk.
}
}
$picVal = $this->getmainPic(); // here i do a simple fetch all and get the picture that was just uploaded
$this->view->imagepath = $picVal;
what happens is that the newly uploaded picture doesn't show. I checked the database and the dick and the file is there.
im thinking the problem might be the order of the requests or something similar.
any ideas?
edit: another thing is that in order to make the new image come up i have to do a SHIFT+F5 and not only press the browser refresh button
edit2: more code
i first call the upload to disk function then if that returns success addthe file to the database
$x = $this->uploadToDiskMulty($talentFolderPath, $filename)
if($x == 'success'){
$model->create($data);
}
the upload function
public function uploadToDiskMulty($talentFolderPath, $filename)
{
// create the transfer adapter
// note that setDestiation is deprecated, instead use the Rename filter
$adapter = new Zend_File_Transfer_Adapter_Http();
$adapter->addFilter('Rename', array(
'target' => $filename,
'overwrite' => true
));
// try to receive one file
if ($adapter->receive($talentFolderPath)) {
$message = "success";
} else {
$message = "fail";
}
return $message;
}
If the picture only appears when you do SHIFT+F5 that means it's a caching problem. Your browser doesn't fetch the image when you upload it. Do you use the same file name?

Categories