Symfony2 simple file upload edit without entity - file

Please help me out here because I can't belive my eyes.
I refuse to use some 3rd party plugin for file uploads, and refuse to creat a separate entity for a file/document. I just want simple file upload that I would do in Zend/Laravel and so on.
I have a invoice table with the last collumb name "attachment", I want to store here its sanitized name (eg: 123421_filename.jpg ), the addition form and upload went well.
code here:
//AddAction
$file=$form['attachment']->getData();
$fileName=$file->getClientOriginalName();
$sanitizedFilename=rand(1, 99999).'_'.$fileName;
$dir='files/'.$userId.'/';
$file->move($dir, $sanitizedFilename);
...
$invoice->setAttachment($sanitizedFilename);
$em = $this->getDoctrine()->getManager();
$em->persist($invoice);
First problem I have and don't know how to solve is using the edit form.. I have a formbuilder
$builder->add('attachment', 'file',array('data_class'=>null))
Because I don't have a "Object" for my file and because in my invoice table I store the name as a string, I get an error here and I need to force the data_class => null .. which is bad, because if I edit a Invoice in the frontend for the upload field I get NULL, instead of the filename of the current file linked to the invoice.
With:
$builder->add('attachment', 'text')
I don't get the file input just a silly textbox BUT THIS TIME --with the name in it-- .. so how do I solve this? File Input widget and file name in it without a Document object ?!
And the second question ( I am merely at the point of throwing out all my application developed until now and move to laravel because I had a lot of "these kind of problems" ..doing simple things in symfony is almost always more complicated than in other frameworks..or maybe its my fault that I don't want to folow the guidelines and create a Document Entity?! What if I want flexibility and want to manage my database in another manner that doesn't fit in all these guidelines?)
So I am on the edit form action, I upload a new file (don't forget that I have the file set to $builder->add('attachment',file',array('data_class'=>null))
I can't get my attachment string name from the current invoice I am editing? !
public function editAction($id)
....
$invoice = $em->getRepository('AppBundle:Invoice')->find($id);
..
if ($form->isValid()) {
$oldFileName=$invoice->getAttachment(); //this is null
$oldFileName=$invoice->getId(); //this returns the invoice id
$oldFileName=$invoice->getValue(); //returns invoice value
echo 'old: '.$oldFileName.'<br/>';
exit();
}
So someone please tell me why I can't access my invoices property? Which is a string ?
I tried making a new instance I though that somehow if I create the form with the $invoice object it somehow links him to the attachment from the edit form
if ($form->isValid()) {
$sameInvoice= $em->getRepository('AppBundle:Invoice')->find(20); //hardcoded ID
$oldFileName=$sameInvoice->getAttachment(); //still null
$oldFileName=$sameInvoice->getId(); //returns 20
echo 'old: '.$oldFileName.'<br/>';
exit();
The only thing I wanted was, have a filename string in my invoice table, and test myself if the file exists in the path with that filename and if it exists, then delete it and upload the new one and so on.. why does it have to be so hard ??
Why do I have to create a entity ? Why do I have to alter my database structure (its not the case here..but what if the client doesn't want the database to be changed..so I can't insert this "Document" table).. Why can't the form builder show the data from invoice->attachment, I get it that because he needs a file data and he can't accept a string, but why aren't there guidelines for these simple tasks?!

Alright I finally managed to get it working:
So the problem why it was NULL was because it did bind the $invoice instance to the form:
$invoice = $em->getRepository('AppBundle:Invoice')->find($id);
$form = $this->createForm(new InvoiceType($id), $invoice);
After this block of code, all refferences like : $invoice->getAttachment();
Are reffered to as the form invoice object not the actual object, so basically after this I can only call things like: "getClientOriginalName()" and would show me the new file that I uploaded in the edit form.
To counter this I created a variable that stores that name initially before the $invoice object to get bind to the form
$invoice = $em->getRepository('AppBundle:Invoice')->find($id);
$oldFileName=$invoice->getAttachment(); //this is stil the object here I can see the record from the database
$form = $this->createForm(new InvoiceType($id), $invoice);
//here the $invoice->getAttachment(); returns null
So now I have the old name and I can test it like:
if ($form->isValid()) {
$fs=new Filesystem();
$newFile=$form['attachment']->getData();
$newFileName=$newFile->getClientOriginalName();
if($newFileName != $oldFileName)
{
// if($fs->exists('files/'.$this->getUser()->getId().'/'.$oldFileName))
// $fs->remove('files/'.$this->getUser()->getId().'/'.$oldFileName);
$sanitizedFilename=rand(1, 99999).'_'.$newFileName;
$dir='files/'.$this->getUser()->getId().'/';
$newFile->move($dir, $sanitizedFilename);
$invoice->setAttachment($sanitizedFilename);
}
$em->persist($invoice);
$em->flush();
return $this->redirect($this->generateUrl('my-invoices'));
}
For the other problem with the filename of the old file not appearing in the Edit Invoice Page I sent my variable to the view :
return $this->render('AppBundle:Invoices:edit.html.twig', array(
'oldFileName' => $oldFileName)
And put that value to the label of the input file widget using twig

Remain calm. You are making this much harder then it needs to be.
myControllerAction()
{
// No need for an object, an array works fine
$model = array(
'invoice' => $invoice,
'attachment' => null
);
$builder = $this->createFormBuilder($model);
$builder->setAction($this->generateUrl('cerad_game_schedule_import'));
$builder->setMethod('POST');
$builder->add('attachment', 'file');
$builder->add('invoice', new InvoiceType());
$builder->add('import', 'submit', array(
'label' => 'Import From File',
'attr' => array('class' => 'import'),
));
$form = $builder->getForm();
$form->handleRequest($request);
if ($form->isValid())
{
$model = $form->getData();
$file = $model['attachment']; // The file object is built by the form processor
if (!$file->isValid())
{
$model['results'] = sprintf("Max file size %d %d Valid: %d, Error: %d<br />\n",
$file->getMaxFilesize(), // Returns null?
$file->getClientSize(),
$file->isValid(),
$file->getError());
die($model['results'];
}
$importFilePath = $file->getPathname();
$clientFileName = $file->getClientOriginalName();
// Xfer info to the invoice object and update
}

Related

Undefined index to empty variables (not required inputs)

im learning about angularjs and laravel. Basically i use angular to fetch my data in the forms and than send it to laravel to grab my variables and than create the record, the problem is with one input field (mobile), if i fill the mobile input it doesnt give me any problems, but if i leave it empty (since is not required input) it fails to create the record and give me a undefined index.
How can i make in Laravel to create anyway the record if one variable in my case is empty or not created?
php code:
public function registerUser($inputData)
{
$user = \DB::transaction(function () use ($inputData)
{
$user = User::create([
'email' => $inputData['user']['email'],
'name' => $inputData['user']['name'],
'surname' => $inputData['user']['surname'],
'mobilephone' => $inputData['user']['mobilephone'],
'birth_date' => Carbon::createFromFormat('d-m-Y', $inputData['user']['birth_date']),
]);
$user->save();
//Return the User
return $user;
});
//Return the User instance
return $user;
}
From what you are describing, when you don't fill in the mobile number the front end doesn't send an object which includes "$inputData['user']['mobilephone']". Which would cause an error in the script and prevent it from running. Please post the angular code responsible for transmitting the user information.
If my assumption is correct, you either need to check that these properties all exist in the $inputData before referencing them or have the front end add a dummy value when no value is provided.
OR What ever is holding the record (MySQL or whatever) may not allow this field to be null which would again prevent the insertion. But your description suggests this isn't it.

Creating muliple users in a loop from array

How does one go about creating multiple new users, say in a loop, from an array in a different controller?
I have an issue where attempting to create multiple users in a form submit fails, but creating a single new user works as designed. It appears the issue may be when saving the new user and then bringing the new user_id back in the return statement. Although the new id comes back, subsequent users (2nd, 3rd, etc) all get the same id value, and it appears that the subsequent $this->save calls modify the first created user rather than create add'l ones. Any none of the new users appear in the database. (again, the problem only happens when more than one new users will be created.)
My one small clue is that if I var_dump($user) in my importPublicLDAPUser() function (user.php) just after the $user = $this->save(array('User' => array( ... ))); then for the first element I see both 'modified' and 'created', whereas I see only 'modified' for the rest. This leads me to believe there's a step missing, like the user needs to be saved or commit (??) before the next user can be be created.
I tried changing this to $user = $this->User->save(array('group_id' => 3, ... and adding a 'create' before: $this->User->create(); but these produce errors 'Call to a member function save() on a non-object' and 'Call to a member function create() on a non-object'.
My application manages documents. Each document can have many authors, so it has controllers for: docs, doc-types, users, groups, and authors.
When a new document is entered, the form allows selection of multiple users to create 'Author' records. In addition to the local users table, it also searches our LDAP server (both via auto-sugggest) and also allows input into a text field. So, Authors are selected from
the existing table of users
via the LDAP helper
free text entry.
This result is two arrays: $Authors (from local users tables), and $badAuthors (LDAP and text-input) which the app then tries to add to the local users table when the form is submitted.
The form works just fine if:
one or more authors are added from local users table;
a single author is added from LDAP (succeeds in creating a new entry in users table), and zero or more local users
a single author is added from text input (also succeeds), and zero or more local users
However if two or more non-local users are added ($badAuthors has more than one element) then the form fails. "fails" means that either the Author or User save routine failed, and so it skips the Document commit $this->Docu->commit(); and I spit out an error via ajaxResponse. Thus, the app works as designed, but only with one new User entry at a time, even though the form is designed to allow Authors/badAuthors to be >1.
What I don't understand is why when I loop through bad authors why it doesn't correctly add the users if $badAuthors has more than one element.
As the user enters each name (which is checked against the users table and LDAP via ajax helpers, etc) and then selected, an author_name_list array is built. And then:
foreach($this->params['form']['author_name_list'] as $author_name){
$user_id = $this->Docu->User->field('id',array('User.name' => $author_name));
if($user_id){
$Authors['Author'][]=array(
'docu_id'=>$this->Docu->id
,'user_id'=>$user_id
);
}else{
$badAuthors[] = array('name'=>$author_name);
}
}
So $badAuthors is now those found in LDAP or entered manually.
So now I try to create/save all badAuthors...
docu controller (docu_controller.php):
if(count($badAuthors)){
foreach($badAuthors as $key => $author){
$this->Memo->User->create(); // ** <-- this was missing!! **
if($ldap_author = $this->Docu->User->importPublicLDAPUser($author['name'])){
unset($badAuthors[$key]);
$Authors['Author'] []= array(
'docu_id'=>$this->Docu->id
,'user_id'=>$ldap_author['User']['id']
,'precedence' => $author['precedence']
);
} elseif ($new_author = $this->Docu->User->newReadonlyUser($author['name'])) {
unset($badAuthors[$key]);
$Authors['Author'] []= array(
'docu_id'=>$this->Docu->id
,'user_id'=>$new_author['User']['id']
,'precedence' => $author['precedence']
);
}
}
}
if(!count($badAuthors)){
$authors_saved = true;
foreach($Authors['Author'] as $author_arr){
$this->Docu->Author->create();
if(!$this->Docu->Author->save(array('Author' => $author_arr))){
$authors_saved = false;
break;
}
}
}
user model (user.php)
function afterSave($created) {
if (!$created) {
$parent = $this->parentNode();
$parent = $this->node($parent);
$node = $this->node();
$aro = $node[0];
$aro['Aro']['parent_id'] = $parent[0]['Aro']['id'];
$this->Aro->save($aro);
}
}
function importPublicLDAPUser($cn){
App::import('Vendor','adLDAP',array('file'=>'adLDAP.php'));
$oLDAP = new adLDAP(Configure::read('LDAP_options.email'));
$oLDAP->authenticate(NULL, NULL);
$filter = '(&(cn='.$oLDAP->ldap_escape($cn).'))';
$ldap_res = #$oLDAP->search($filter, array('cn', 'uid','profitcenter'),1);
if(isset($ldap_res['count']) && ($ldap_res['count'] > 0)){//found it
$user = $this->save(array('User' => array(
'group_id' => 3,
'name' => $ldap_res[0]['cn'][0],
'username' => $ldap_res[0]['uid'][0],
'grpnum' => pc2grpnum($ldap_res[0]['profitcenter'][0])
)));
if($user){
$user['User']['id'] = $this->id;
}
return ($user ? $user : false);
}else{
return false;
}
}
Any suggestions? Thanks!!
It turns out that in my docu_controller.php I was missing a create() call. It seems that without a create, an object can still be saved/created when the other controller does a commit(). So before adding the create(), prior to the commit, in later loop iterations I was still modifying the original object, not any new ones. By adding a create in the controller, the save in the method function acts on the new user for each loop iteration.
in controller:
if(count($badAuthors)){
foreach($badAuthors as $key => $author){
$this->Memo->User->create();
if($ldap_author = $this->Memo->User->importPublicLDAPUser($author['name'])){
in method:
function importPublicLDAPUser($cn){
....
$user = $this->save(array('User' => array(...

how to edit a cakephp record without deleting the path to an uploaded file

I want to edit a record in CakePhp without deleting the saved path of an uploaded jpg file. Right now when I edit the record it deletes the saved file path.
The approach I think would work is to do a logic check in the form to check if the field is empty or not. Display the saved file path if it's not empty or show an empty path if it is. Would this be the best way to update the record?
I'm using Miles Johnson's uploader plugin for uploading files.
Here's the code within the form:
<?php
if (!empty ($slider ['Slider']['bgImg']));
echo $slider ['bgImg'];
else
echo $form->input('file', array('type' => 'file'));
?>
This logic should be in the controller, strictly speaking.
In my apps with files and edit capabilities, I will show a file field and a link/thumbnail of the image in question on the edit form.
my approach uses my own uploader, so your results may vary, but essentially:
if (!empty($this->data)) {
$file_object = $this->data['Listing']['featured_image'];
$image_data=$this->Upload->do_upload($file_object, 'img/uploads/');
if($image_data){
$this->data['Listing']['featured_image'] = $image_data['name'];
} else {
unset($this->data['Listing']['featured_image']);
}
$this->Listing->save($this->data)
and in my upload component, I have this:
public function do_upload($file_object, $location='img/uploads/') {
/**
* No file was uploaded
*/
if($file_object['error']==4) {
return false;
}
// carry on uploading
So essentially; I pass my upload component the $file_object, which is from the form. I then do a simple test, using the default set of error codes to see if the file is empty (4). If it's empty, I return false. (you could return errors etc, but this was my approach).
after the upload call, I check the return value, and if the file was uploaded successfully, I can then set the field in my model. (the file name, in my case) - you may want to store the path as well, for instance.
If it's false - it means there was no file; so I unset the value from the array.
Because the field does not exist in the array, cake will not attempt to overwrite existing data - it simply ignores it; allowing the old value to stay untouched.

Using existing field name as different name

i have existing website.
and i write the new back-end (in cakephp) without changing front-end programm
the discomfort that
db table has field names as
id
news_date
news_title
news_content
is it possiable to do something in cakephp model file (reindentify the field names)
so i can use model in controller as
News.date
News.title
News.content
What you need to do is setup some very basic virtual fields in your news model. Something like this should suit your needs.
public $virtualFields = array(
'title' => 'news_title',
'date' => 'news_date',
'content' => 'news_content'
);
Also do yourself a favour by checking out the other model attributes that could help you out, you'll want to set displayType as new_title I'd imagine.
Is said by Dunhamzz, virtualFields are a good solution until you want to work with these new field-names.
Since I assume your frontend needs to use the old names from the database I would go with the afterFind-callback in your model.
Let's say you've got the model news.php:
# /app/model/news.php
function afterFind($results) {
foreach ($results as $key => $val) {
if (isset($val['News']['title'])) {
$results[$key]['News']['news_title'] = $val['News']['title']);
# unset($results[$key]['News']['title']); //use this if you don't want the "new" fields in your array
}
if (isset($val['News']['date'])) {
$results[$key]['News']['news_date'] = $val['News']['date']);
# unset($results[$key]['News']['date']); //use this if you don't want the "new" fields in your array
}
if (isset($val['News']['content'])) {
$results[$key]['News']['news_content'] = $val['News']['content']);
# unset($results[$key]['News']['content']); //use this if you don't want the "new" fields in your array
}
}
return $results;
}
You need to rename the database-fields to your new wanted value. You then can use these within conditions like every other field.
Only difference is, that you get back an array where all your fields have been renamed to your frontend-fields.
For more information about the available callback-methods have a look here: Callback Methods

CakePHP user session not updating but database yes

I'm developing with cakePhP and I have the following problem:
When a user logs in with his name and password to the account system that I've created, he can save items (images) as favorites. This is saved in a text field into the database. What is saved is the image ID.
The saving process works perfectly, the user clicks on the images and they're added to that field (it actually saves all the IDs as a text array that I process later).
The problem comes when removing images. When the user does it (I'll post the code below), the images is removed correctly from the database (I go to PHP MyAdmin and I see it). This means that the array that holds the favorite images IDs is updated instantly. However, when I reload that array from the website, it hasn't been updated. It's like it's stored in the caché or something. Then, if the user logs out and logs in again, then he can see the correct one. The thing is that I have other things in my website that work in a similar way and they all get updated instantly, so I can't see why this doesn't.
This is the code that I use to remove the ID from the database:
function remove_favorite($pictureID) {
$this->User->id = $this->Auth->User('id'); //We get the ID of the current user
$favoritesArray = $this->User->deleteFavoritePicture($this->User->id, $pictureID); //This function retrieves the array (string) of pictures from the user's table, and deletes all the images with the ID passed as parameter, returning the updated array (string)
$fields = array('images_favorites' => $favoritesArray, 'modified' => true); //We indicate the field that we're going to update in the users table
//We save the new string that doesn't contain the deleted image anymore
if($this->User->save($fields, false, array('images_favorites'))) {
$this->Session->setFlash(__('The image has been removed from your favorites', true));
} else {
$this->Session->setFlash(__('Error removing image from favorites, please try again', true));
}
$this->redirect(array('action' => 'manage_favorites',$this->User->id));
}
This is how the deleteFavoritePicture function looks like:
function deleteFavoritePicture($userID, $pictureID) {
$userInfo = $this->find("id = $userID");
$favoritePicturesString = $userInfo['User']['images_favorites'];
$favoritePicturesArray = explode(",", $favoritePicturesString); //Array
$i = 0;
while ($i < count($favoritePicturesArray)) {
//We remove from the array the images which ID is the one we receive to delete
if ($favoritePicturesArray[$i] == $pictureID) unset($favoritePicturesArray[$i]);
$i++;
}
$favoritePicturesString = implode(",", $favoritePicturesArray); //String
return ($favoritePicturesString);
}
That's it. Does anyone now what can be going on? Thanks so much in advance for any clue!
EDIT
Ok, I think I found something that may give a clue of what's going on here:
This is the code for the manage_favorites action:
function manage_favorites($id) {
//$user = $this->User->find("id = $id");
$user = $this->Auth->user();
$this->set('user', $user);
}
That is the action that is called for the page when a user wants to modify his favorites. The same action is called once the user removes a favorite. Here's the thing:
If I use the $id parameter in the manage_favorites function and the $user = $this->User->find("id = $id"); line (the one quoted now), then the problem does not exist! This is how I used to have it. HOWEVER, I had to change it because it was a big security flaw, since the user id ($id) was a visible parameter who anyone could change, and then access other users accounts. What I did was changing the way I obtain the user array of favorite images, using the following line: $user = $this->Auth->user();. This is how I have it now (well, and also without the $id parameter in the function header), so the user information (including the favorites array) comes from the Auth component, instead directly from the database.
So, the problem is clear: when the user deletes a favorite, it's doing it on the array in the database. WHen I show the result of that operation, the array I'm retrieving is not the one in the DB, it's the one in the session. That's why it's not showing the changes.
How can I avoid this without using a non-secure method like the one I had before?
When you save, the array passed to the save method of the model should look like this:
[User] => array(
[field] => value,
[field2] => value2,
...
)
In your example, you clearly haven't added the [User] key.
Also, is your modified field actually the default Cake modified field? That is, the DATETIME field which changes to the current time when the row is updated?
Lastly, maybe you have debugging set to 2 in config.php. try changing this to 0 (as in production) and see if caching persists.
Hope some of the points I have mentioned above will solve your problem. Please let me know!
There could be two things wrong with this.
What does your deleteFavoritePicture method look like? There could be something being done wrong there.
You're passing false as the second parameter to the User::save method, which means that you don't want to validate. Unless there is a SQL error, then this will return true even if it doesn't validate properly, I believe. Try changing this false to true and see if your results differ.

Resources