Users from the backend application can upload files and publish them to the frontend. Using sfWidgetFormInputFile and sfValidatorFile, I would like to keep the original filename instead of the default functionality of a random string (i.e. Meaningful_filename.docx instead of a4b25e9f48cfb6268f34b691fc18cd76fefe96b5.docx - numbers can be appended onto duplicate names). This can be useful in scenarios where the user downloads several files and would not be able to tell them apart from the file name.
$this->widgetSchema['file_name'] = new sfWidgetFormInputFile(array('label' => 'File'));
$this->validatorSchema['file_name'] = new sfValidatorFile(array(
'required' => true,
'path' => sfConfig::get('sf_upload_dir').DIRECTORY_SEPARATOR.sfConfig::get('app_dir_file_sharing').DIRECTORY_SEPARATOR,
'mime_types' => array('application/msword',
'application/vnd.ms-word',
'application/msword',
'application/msword; charset=binary')
), array(
'invalid' => 'Invalid file.',
'required' => 'Select a file to upload.',
'mime_types' => 'The file must be a supported type.'
));
Is there native functionality in the sfWidgetFormInputFile widget or is there another solution to this?
You get the file by calling $form["file_name"]->getValue(). This gives you an object of class sfValidatedFile where you can call the method getOriginalName().
To define how the file should be save you can do this:
The sfValidatorFile class accepts an option which sfValidatedFile class to use:
validated_file_class: Name of the class that manages the cleaned uploaded file (optional)
The sfValidatedFile class has a method save that calls a method generateFileName. Subclass this class and overwrite this method:
class CustomValidatedFile extends sfValidatedFile {
/**
* Generates a random filename for the current file.
*
* #return string A random name to represent the current file
*/
public function generateFilename()
{
return 'foo bar'// your custom generated file name;
}
}
Here is the function from the original class:
public function generateFilename()
{
return sha1($this->getOriginalName().rand(11111, 99999)).$this->getExtension($this->getOriginalExtension());
}
Then you set up the validator this way:
$this->validatorSchema['file_name'] = new sfValidatorFile(array(
'required' => true,
'path' => 'yourpath',
'validated_file_class' => 'CustomValidatedFile',
'mime_types' => array('application/msword',
'application/vnd.ms-word',
'application/msword',
'application/msword; charset=binary')
),
array('invalid' => 'Invalid file.',
'required' => 'Select a file to upload.',
'mime_types' => 'The file must be a supported type.')
);
Hope that helps!
After some research:
While you can extend sfValidatedFile and override generateFilename I found out that sfFormPropel checks for the existence of a method based on the column name for the model to name the file.
From symfony/plugins/sfPropelPlugin/lib/form line 292:
$method = sprintf('generate%sFilename', $column);
if (null !== $filename)
{
return $file->save($filename);
}
else if (method_exists($this, $method))
{
return $file->save($this->$method($file));
}
Therefore, if your column is called file_name, the method looks for the existence of generateFileNameFilename in the form class. This way you only have to add one method to your form class, rather than extending the sfValidatedFile widget. For instance, my function uses the original name if it is not taken, otherwise appends a sequential number (one method is to recursively check the generated filename):
public function generateFileNameFilename($file = null)
{
if (null === $file) {
// use a random filename instead
return null;
}
if (file_exists($file->getpath().$file->getOriginalName())) {
return $this->appendToName($file);
}
return $file->getOriginalName();
}
public function appendToName($file, $index = 0)
{
$newname = pathinfo($file->getOriginalName(), PATHINFO_FILENAME).$index.$file->getExtension();
if (file_exists($file->getpath().$newname)) {
return $this->appendToName($file, ++$index);
} else {
return $newname;
}
}
I can't find this documented in the symfony API anywhere which is why it took some searching the code base to find. If you are using this method in many places, extending sfValidatedFile might be a good option too.
According to the Symfony documentation "The sfValidatorFile validator validates an uploaded file. The validator converts the uploaded file to an instance of the sfValidatedFile class, or of the validated_file_class option if it is set." (Source: http://www.symfony-project.org/forms/1_4/en/B-Validators#chapter_b_sub_sfvalidatorfile)
Although the sfValidatedFile class renames files right out of the box, you can override this function by setting the validated_file_class to a custom class, and extending sfValidatedFile.
In your custom validated file class, pass your custom filename to the save() method. "If you don't pass a file name, it will be generated by the generateFilename method." (Source:
http://www.symfony-project.org/api/1_4/sfValidatedFile#method_save)
Here's one way you could do it (Source: http://forum.symfony-project.org/index.php/m/90887/#msg_90887)...
A custom validated file class:
// lib/validator/myValidatedFile.php
class myValidatedFile extends sfValidatedFile {
private $savedFilename = null;
// Override sfValidatedFile's save method
public function save($file = null, $fileMode = 0666, $create = true, $dirMode = 0777) {
// This makes sure we use only one savedFilename (it will be the first)
if ($this->savedFilename === null) $this->savedFilename = $file;
// Let the original save method do its magic :)
return parent::save($this->savedFilename, $fileMode, $create, $dirMode);
}
}
Make sure to set 'validated_file_class' => 'myValidatedFile' for the sfWidgetFormInputFile. And to set the logic for what the file name is going to be in Form's save method.
Related
I am using Symfony 3.4.8 and I try to create a form for uploading a file. I followed exact the Symfony document steps but got the error:
Controller "AppBundle\Report::uploadReport()" requires that you provide a value for the "$fileUploader" argument. Either the argument is nullable and no null value has been provided, no default value has been provided or because there is a non optional argument after this one.
Here is part of my code, the rest are the same from the document except I changed the class name. Clearly when the function get called, there is no FileUploader argument passed into the function. If I remove the argument FileUploader $fileUploader, the page can load without throwing exception but it won't get the file. I am new to Symfony, how can I solve this problem?
/**
* #Route("/report/create-report/upload/", name="report_create")
*/
public function uploadReport(Request $request, FileUploader $fileUploader)
{
$report = new Report();
$form = $this->createForm(ReportType::class, $report);
$form->add('submit', SubmitType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// $file stores the uploaded PDF file
/** #var Symfony\Component\HttpFoundation\File\UploadedFile $file */
$file = $report->getReport();
$fileName = $fileUploader->upload($file);
$report->setBrochure($fileName);
//$fileName = $this->generateUniqueFileName().'.'.$file->guessExtension();
// moves the file to the directory where brochures are stored
//$file->move(
// $this->getParameter('reports_directory'),
// $fileName
//);
// updates the 'brochure' property to store the PDF file name
// instead of its contents
//$report->setReport($fileName);
// ... persist the $product variable or any other work
}
return $this->render('report/createReport.html.twig', array(
'form' => $form->createView(),
));
}
I have seen the post but I cannot get that answer to work on my end as there is no such variable $container.
Last update: I gave up trying implement upload from scratch. I used the recommended bundle to make it work with minimum amount of coding.
the argument brochures_directory of your FileUploader.php service seems to be emtpy.
Did you specify it in service.yml?
Did you also add it in your config.yml ?
And then did you clear symfony cache after change ?
I am using "wForm" extension for forms. I want to add "captcha" in my form.
I have tried "myCaptcha" component for "cForm".
http://www.yiiframework.com/wiki/733/how-to-show-a-captcha-in-cform/
but i am getting the following error
"WForm" and its behaviors do not have a method or closure named "MyCaptcha".
how can i use the "cform captcha in wForm"?
Download the extension cCaptcha extension
1) Unzip CaptchaExtended.zip files into ../protected/extensions/captchaExtended/.
2) Register class paths to CaptchaExtendedAction and CaptchaExtendedValidator, e.g. in components/controller.php:
public function init(){
// register class paths for extension captcha extended
Yii::$classMap = array_merge( Yii::$classMap, array(
'CaptchaExtendedAction' => Yii::getPathOfAlias('ext.captchaExtended').DIRECTORY_SEPARATOR.'CaptchaExtendedAction.php',
'CaptchaExtendedValidator' => Yii::getPathOfAlias('ext.captchaExtended').DIRECTORY_SEPARATOR.'CaptchaExtendedValidator.php'
));
}
3) Define action in controller, e.g. SiteController:
public function actions(){
return array(
'captcha'=>array(
'class'=>'CaptchaExtendedAction',
// if needed, modify settings
'mode'=>CaptchaExtendedAction::MODE_MATH,
),
);
}
4) Define client validation in model::rules():
public function rules(){
return array(
array('verifyCode', 'CaptchaExtendedValidator', 'allowEmpty'=>!CCaptcha::checkRequirements()),
);
}
5) add the following in your view file (in your form)
$this->widget('CCaptcha'); //for captch image
echo CHtml::activeTextField($model,'verifyCode'); //text field to enter captcha text
I'm trying to get an extension of a file during upload but I get an error that path info requires a string:
I have tried:
$path_parts = pathinfo($_FILES['evidence']['name']);
echo $path_parts['extension'];
How can extract file extension, for example jpeg, doc, pdf, etc.
If you are using yii2 kartik file input you can get the instance of yii\web\uploadedFile this way to:
$file = UploadedFile::getInstanceByName('evidence'); // Get File Object byName
// Then you can get extension by this:
$file->getExtension()
If you want to validate file as well then you can use FileValidator using adhoc role:
$validator = new FileValidator(['extensions' => ['png','jpg']]);
if( $validator->validate($file, $errors) ) {
// Validation success now you can save file using $file->saveAs method
} else {
// ToDO with error: print_r($errors);
}
It's better not use $_FILES directly in Yii2 since framework provides abstraction with a class yii\web\UploadedFile. There is also separate page in guide describing working with uploaded files.
There is an example with model.
namespace app\models;
use yii\base\Model;
use yii\web\UploadedFile;
class UploadForm extends Model
{
/**
* #var UploadedFile
*/
public $imageFile;
public function rules()
{
return [
[['imageFile'], 'file', 'skipOnEmpty' => false, 'extensions' => 'png, jpg'],
];
}
public function upload()
{
if ($this->validate()) {
$this->imageFile->saveAs('uploads/' . $this->imageFile->baseName . '.' . $this->imageFile->extension);
return true;
} else {
return false;
}
}
}
As you can see, extension is extracted using extension property ($this->imageFile->extension).
There are more info about form settings, handling in controller, uploading multiple files. All this can be found by the link mentioned above.
I've tried to test move_uploaded_file and is_uploaded_file with PHPUnit and vfsStream. They always return false.
public function testShouldUploadAZipFileAndMoveIt()
{
$_FILES = array('fieldName' => array(
'name' => 'file.zip',
'type' => 'application/zip',
'tmp_name' => 'vfs://root/file.zip',
'error' => 0,
'size' => 0,
));
vfsStream::setup();
$vfsStreamFile = vfsStream::newFile('file.zip');
vfsStreamWrapper::getRoot()
->addChild($vfsStreamFile);
$vfsStreamDirectory = vfsStream::newDirectory('/destination');
vfsStreamWrapper::getRoot()
->addChild($vfsStreamDirectory);
$fileUpload = new File_Upload();
$fileUpload->upload(
vfsStream::url('root/file.zip'),
vfsStream::url('root/destination/file.zip')
);
$this->assertFileExists(vfsStream::url('root/destination/file.zip'));
}
Is it possible? How do I do that?
Can I post a vfsStreamFile (or any data) without a form, just using PHP code?
Thank you.
No. move_uploaded_file and is_uploaded_file are specifically designed to handle uploaded files. They include extra security checks to ensure that the file's not been tampered with in the time between the upload completing and the controlling script accessing the file.
Note: changing the file from within the script counts as tampering.
Assuming you're using classes you can create a parent class.
// this is the class you want to test
class File {
public function verify($file) {
return $this->isUploadedFile($file);
}
public function isUploadedFile($file) {
return is_uploaded_file($file);
}
}
// for the unit test create a wrapper that overrides the isUploadedFile method
class FileWrapper extends File {
public function isUploadedFile($file) {
return true;
}
}
// write your unit test using the wrapper class
class FileTest extends PHPUnit_Framework_TestCase {
public function setup() {
$this->fileObj = new FileWrapper;
}
public function testFile() {
$result = $this->fileObj->verify('/some/random/path/to/file');
$this->assertTrue($result);
}
}
I have a file upload form in the frontend.
At the moment, when a new record is created, the file is uploaded to
%sf_data_dir%/files/
but due to some business logic I need the file to be uploaded to
%sf_data_dir%/files/%record_id%/
Therefore the uploaded file should be saved AFTER the record is created.
How can I achieve that?
If you use file upload, your form certainly make use of the sfValidatorFile (if not, that's wrong):
$this->validatorSchema['image'] = new sfValidatorFile(array(
'required' => true,
'mime_types' => 'web_images',
));
This validator return a sfValidatedFile instance that can be saved anywhere you want (it's safer than move_uploaded_file, there is checks on the directory, filename...).
In your action (or in the form, as you want/need), you can now do this:
protected function processForm(sfWebRequest $request, sfForm $form)
{
$form->bind(
$request->getParameter($form->getName()),
$request->getFiles($form->getName())
);
if ($form->isValid())
{
$job = $form->save();
// Saving the file to filesystem
$file = $form->getValue('my_upload_field');
$file->save('/path/to/save/'.$job->getId().'/myimage.'.$file->getExtension());
$this->redirect('job_show', $job);
}
}
Don't hesitate to open sfValidatedFile to see how it work.