OneupUploaderBundle and jQuery-File-Upload on symfony 2 - symfony

I've been trying this but I'm confused since I'm new to Symfony Events and stuff.
This is where I've got so far:
composer bundle installation
AppKernel.php, routing.yml, services.yml, config.yml, UploadListener.php file modifications
And it works, the file I put is actually being uploaded to the folder, and I got the status bar filling... but I need something else:
somehow I need to post (and read) an item id (integer) along with the file (or to be able to set the filename when the file is being copied to the output folder)
if something goes wrong with the upload, how do I send an error message back?
In the example (jQuery-File-Uploader), the code returns the filename of the file that was uploaded, my code doesn't do that, I mean the code is there, but it doesn't work
I'm posting the code I have.
HTML code (here is the piece where I call the jQuery-File-Upload
<tr>
<td>{{ clienteCorporativo.nombreComercial|upper }}</td>
<td>{% if clienteCorporativo.afiliadosUploads|length == 0 %}Nunca{% else %}{{ (clienteCorporativo.afiliadosUploads|last).fecha|date('d/mmm/y') }}{% endif %}</td>
<td>
{% if clienteCorporativo.formatoExcelAfiliados == null %}
<span class="btn btn-success fileinput-button">
<i class="glyphicon glyphicon-upload"></i>
<span>Seleccionar Excel</span>
<input id="fileupload_{{ clienteCorporativo.id }}" class="fileupload" data-id="{{ clienteCorporativo.id }}" type="file" name="files[]" multiple>
</span>
{#<span style="color: #8c8c8c"><span class="glyphicon glyphicon-upload"></span> Seleccionar Excel </span>#}
{% else %}
<input id="fileupload_{{ clienteCorporativo.id }}" class="fileupload" type="file" name="files[]" data-url="{{ oneup_uploader_endpoint('gallery') }}" />
{#<a role="button" data-toggle="modal" data-target="#myModal" data-operation="loadOne" data-id="{{ clienteCorporativo.id }}"><span class="glyphicon glyphicon-upload"></span> Seleccionar Excel</a> #}
{% endif %}
<a role="button" data-toggle="modal" data-target="#myModal" data-operation="defineFormat" data-id="{{ clienteCorporativo.id }}"><span class="glyphicon glyphicon-list-alt"></span> Definir Formato</a>
{% if clienteCorporativo.afiliadosUploads|length == 0 %}
<span style="color: #8c8c8c"><span class="glyphicon glyphicon-repeat"></span> Revertir Última Carga </span>
{% else %}
<a role="button" data-toggle="modal" data-target="#myModal" data-operation="undoLast" data-id="{{ clienteCorporativo.id }}"><span class="glyphicon glyphicon-repeat"></span> Revertir Última Carga</a>
{% endif %}
</td>
<td>
<div id="progress_{{ clienteCorporativo.id }}" class="progress text-center">
<div class="progress-bar progress-bar-success">
<span id="files_{{ clienteCorporativo.id }}"></span>
</div>
</div>
</td>
</tr>
js script (the "each" sentence does nothing)
<script>
/*jslint unparam: true */
/*global window, $ */
var idFile = 0;
$(function () {
'use strict';
// Change this to the location of your server-side upload handler:
$('.fileupload').fileupload({
url: '{{ oneup_uploader_endpoint('xlsfile') }}',
dataType: 'json',
done: function (e, data) {
var eventTrigger = $(this)
idFile = eventTrigger.data('id')
$.each(data.result.files, function (index, file) {
$('#files_'+idFile).html(file.name);
});
},
progressall: function (e, data) {
var eventTrigger = $(this)
idFile = eventTrigger.data('id')
var progress = parseInt(data.loaded / data.total * 100, 10);
$('#progress_'+idFile+' .progress-bar').css(
'width',
progress + '%'
);
},
formData: [ {"id":idFile} ]
}).prop('disabled', !$.support.fileInput)
.parent().addClass($.support.fileInput ? undefined : 'disabled');
//
});
</script>
The other files (AppKernel.php, routing.yml, services.yml, config.yml, UploadListener.php) are just like the OneupUploaderBundle documentation says (I've changed things and then rolled back since I didn't got the results I expected). I think I chew more that I could swallow on this one...

Your form fields will be posted with the file upload. Or you can use the form_data: {} option in your $('#myFile').fileUploader() constructor. But it will submit your form fields by default and you can handle those in the usual way.
$('#myFile').fileupload({
dataType: 'json',
formData: {
'file_id': 1
},
You have to craft your own return response in your UploadListener. Then parse the results on the front-end (Javascript).
$response = $event->getResponse();
$response['success'] = true;
$response['files'] = [
'file' => [
'name' => $event->getFile()->getPathname()
]
];
return $response;

Related

Flask-WTForms .validate_on_submit() never executes

So basically I want to have a Updateform() form that allows users to update their account details. All my other forms (register, etc.) work perfectly fine but this specific form never validates on submit. Hence when I press the submit button the page just refreshes but the .validate_on_submit() code doesn't execute.
I've looked through the forums and a common issue I found is the .CSRF token missing, but I'm using form.hidden_tag() which I read should work perfectly fine. So it seems that my issue is unique.
I've been looking on the forums for hours but haven't found a solution.
Here is my form code:
class Updateform(FlaskForm):
email = StringField('Email:', validators=[DataRequired(), Email()])
picture = FileField('Update Profile Picture:', validators=[FileAllowed(['jpg', 'png'])])
submit = SubmitField("Update")
def validate_email(self, email):
if email.data != User.email:
if User.query.filter_by(email=email.data).first():
raise ValidationError('Email has already been registered')
Here is the route:
#users.route('/account', methods=['GET', 'POST'])
#login_required
def account():
form = Updateform()
print("hello")
if form.validate_on_submit():
print(form)
print("YES!!!")
name = current_user.name
pic = add_profile_pic(form.picture.data, name)
current_user.profile_image = pic
current_user.email = form.email.data
db.session.commit()
flash("Account Updated")
# elif request.method == "GET":
# form.email = current_user.email
profile_image = url_for('static', filename='profile_pics/'+current_user.profile_image)
return render_template('account.html', profile_image=profile_image, form=form)
And here is the html code:
{% extends "base.html" %}
{% block content %}
<div align="center">
Hi {{ current_user.name }}<br>
<img align="center" src="{{ url_for('static', filename='profile_pics/'+current_user.profile_image) }}">
</div>
<div class="container">
<form method="post">
{{ form.hidden_tag() }}
<div class="form-group">
{{ form.email.label(class='form-group') }}
{{ form.email(class='form-control') }}
</div>
<div class="form-group">
{{ form.picture.label(class='form-group') }}
{{ form.picture(class='form-control') }}
</div>
<div class="form-group">
{{ form.submit() }}
</div>
</form>
</div>
{% endblock %}
The extra classes you see are from the bootstrap library incase anyone is wondering.
Hello you could find out what the problem is by adding a else for you if form.validate_on_submit(): and have it do this
for error in form.email.errors:
print(error)
for error in form.picture.errors:
print(error)
this should tell you what is not working hope this helps and I have not tested this so it could have typos

Multiple file upload constraint

I'm followed the tutorial to manage the upload of multiple files http://growingcookies.com/easy-multiple-file-upload-in-symfony-using-the-collectiontype-field/
The system for uploading multiple files works fine.
However, I would like to add a constraint to allow only certain types of files and to set a maximum size.
For that, I add #Assert\File:
/**
* #Assert\File(
* maxSize = "300k",
* mimeTypes = {"application/pdf", "application/x-pdf", "text/plain", "application/msword",
* "application/vnd.ms-excel", "image/jpeg", "image/x-citrix-jpeg", "image/png", "image/x-citrix-png", "image/x-png", "image/gif",
* "application/zip"},
* mimeTypesMessage = "Liste des formats de fichiers acceptés : PDF, TXT, DOC, XLS, JPG, PNG, GIF"
* )
*
* #ORM\OneToMany(targetEntity="Maps_red\TicketingBundle\Entity\TicketDocument", mappedBy="ticket", cascade={"persist"}, orphanRemoval=true)
*/
protected $documents;
By adding that, the code does not work anymore. I get the following error :
Impossible to access an attribute ("name") on a null variable.
This error appears on the Add Files page when I send the form. The line corresponding to the error is:
<div class="col col-xs-11" id="jsPreview{{ pos }}">{{ doc.vars.value.name }}</div>
The constraint violation message is : "The file could not be found."
Do you know where my error is ?
Edit for #Denis Alimov:
I tried the answer of Denis Alimov with #Assert\All but it returns the same error.
I then tried to put the constraint in the BuildForm. Now, the .txt files pass without error, but all the other extensions always return me the same error
Impossible to access an attribute ("name") on a null variable.
Edit for #Jakumi:
My twig :
{% extends '#Ticketing/base.html.twig' %}
{% block title %}{{ 'New Ticket'|trans({}, 'TicketingBundle') }}{% endblock %}
{% block header %}<h1>{{ 'New Ticket'|trans({}, 'TicketingBundle') }}</h1>{% endblock %}
{% block form_group_class -%}
col-sm-8
{%- endblock form_group_class %}
{% block main %}
<script type="text/javascript" src="http://code.jquery.com/jquery-latest.min.js"></script>
{% form_theme form 'bootstrap_4_layout.html.twig' _self %}
<div class="box box-danger">
<div class="box-header with-border">
<h3 class="box-title">{{ 'Create a new ticket'|trans({}, 'TicketingBundle') }}</h3>
</div>
{% form_theme form 'bootstrap_3_horizontal_layout.html.twig' _self %}
{{ form_start(form, {'attr': {'class': 'form-horizontal'} }) }}
<div class="box-body">
<div class="hr-line-dashed"></div>
<div id="filesProto" data-prototype="{{ form_widget(form.documents.vars.prototype)|e }}"></div>
<div class="form-group">
<label class="col-sm-2 control-label" for="ticket_form_documents">Pièce-jointe</label>
<div class="col-sm-8" id="filesBox">
{% set pos = 0 %}
{% for doc in form.documents %}
<div class="row">
<div class="col col-xs-1" id="jsRemove{{ pos }}" style="">
<button type="button" class="btn btn-danger" onclick="removeFile($(this));"><i class="fa fa-times" aria-hidden="true"></i></button>
</div>
<div class="col col-xs-11" id="jsPreview{{ pos }}">{{ doc.vars.value.name }}</div>
<div style="display:none">
{{ form_widget(doc) }}
</div>
</div>
{% set pos = pos + 1 %}
{% endfor %}
</div>
</div>
</div>
<!-- /.box-body -->
<div class="box-footer">
<div class="col-md-offset-2 col-sm-8">
<button id="dropbutton" class="btn bg-ticketing btn-flat form-control" type="submit">
{{ 'Submit the ticket'|trans({}, 'TicketingBundle') }}
</button>
</div>
</div>
<!-- /.box-footer -->
{{ form_end(form) }}
</div>
<script>
var fileCount = '{{ form.documents|length }}';
var removeButton = "<button type='button' class='btn btn-danger btn-xs' onclick='removeFile($(this));'><i class='fa fa-times' aria-hidden='true'></i></button>";
function removeFile(ob)
{
ob.parent().parent().remove();
}
function createAddFile(fileCount)
{
// grab the prototype template
var newWidget = $("#filesProto").attr('data-prototype');
// replace the "__name__" used in the id and name of the prototype
newWidget = newWidget.replace(/__name__/g, fileCount);
newWidget = "<div style='display:none'>" + newWidget + "</div>";
hideStuff = "";
hideStuff += "<div class='col col-xs-1' id='jsRemove" + fileCount + "' style='display: none;'>";
hideStuff += removeButton;
hideStuff += "</div>";
hideStuff += "<div class='col col-xs-11' id='jsPreview" + fileCount + "'>";
hideStuff += "</div>";
hideStuff += "<div class='col-sm-8'>";
hideStuff += "<button type='button' id='jsBtnUpload" + fileCount + "' class='btn btn-default'>";
hideStuff += "<i class='fa fa-plus'></i> {{ 'Pièce-jointe' | trans }}";
hideStuff += "</button>";
hideStuff += "</div>";
$("#filesBox").append("<div class='form-group'>" + hideStuff + newWidget + "</div>");
// On click => Simulate file behaviour
$("#jsBtnUpload" + fileCount).on('click', function(e){
$('#ticket_form_documents_' + fileCount + '_file').trigger('click');
});
// Once the file is added
$('#ticket_form_documents_' + fileCount + '_file').on('change', function() {
// Show its name
fileName = $(this).prop('files')[0].name;
$("#jsPreview" + fileCount).append(fileName);
// Hide the add file button
$("#jsBtnUpload" + fileCount).hide();
// Show the remove file button
$("#jsRemove" + fileCount).show();
// Create another instance of add file button and company
createAddFile(parseInt(fileCount)+1);
});
}
$(document).ready(function(){
createAddFile(fileCount);
fileCount++;
});
</script>
{% endblock %}
Edit :
With {{ doc.vars.value.name ?? '' }} I do not have the error anymore !!
However, the file deletion buttons remain displayed:
<div class="col col-xs-1" id="jsRemove1" style="">
<button type="button" class="btn btn-danger" onclick="removeFile($(this));"><i class="fa fa-times" aria-hidden="true"></i></button>
</div>
How to make these buttons disappear when a file type is not good?
As already mentioned use All constraint like this
/**
* #Assert\All({
* #Assert\File(
* maxSize = "300k",
* mimeTypes = {"application/pdf", "application/x-pdf", "text/plain", "application/msword", "application/vnd.ms-excel", "image/jpeg", "image/x-citrix-jpeg", "image/png", "image/x-citrix-png", "image/x-png", "image/gif","application/zip"},
* mimeTypesMessage = "Liste des formats de fichiers acceptés : PDF, TXT, DOC, XLS, JPG, PNG, GIF"
* )
* })
*/
private $documents;
Also you can add constraints to your DocumentType in the buildForm method

Symfony 3 : password validation

I use this code to validate user password :
$encoderService = $this->container->get('security.password_encoder');
$match = $encoderService->isPasswordValid($user, $request->query->get('password'));
but it always returns 'false' even the password is correct
You're using the password encoder in the wrong way... Check Symfony doc on security
Here is an example on how to use it.
app/config/security.yml
security:
encoders:
AppBundle\Entity\Security:
algorithm: bcrypt
src/AppBundle/Controller/SecurityController.php
class SecurityController extends Controller {
private $tokenManager;
private $encoder;
public function __construct(CsrfTokenManagerInterface $tokenManager=null, UserPasswordEncoderInterface $encoder) {
$this->tokenManager=$tokenManager;
$this->encoder=$encoder;
}
**[...]**
public function editAction(Security $user) {
if($user->getOldPassword() !== null && $user->getPlainPassword() !== null && $this->encoder->isPasswordValid($user, $user->getOldPassword())) {
$user->setPassword($this->encoder->encodePassword($user, $user->getPlainPassword()));
$em->flush();
}
}
}
Form view
<form action="{{ form.vars.action }}" method="{{ form.vars.method }}" onsubmit="event.preventDefault(); ajaxSubmit($(this));">
<div class="form-section-title">Change password</div>
<div class="form-col-2">
<div class="input-field">
{{ form_label(form.oldPassword, 'Current password') }}
{{ form_widget(form.oldPassword, { 'attr': {'autocomplete': 'off' }}) }}
</div>
<div class="input-field">
</div>
</div>
<div class="form-col-2">
<div class="input-field">
{{ form_label(form.plainPassword.first, 'New password') }}
{{ form_widget(form.plainPassword.first, { 'attr': {'autocomplete': 'off' }}) }}
</div>
<div class="input-field">
{{ form_label(form.plainPassword.second, 'Repeat new password) }}
{{ form_widget(form.plainPassword.second, { 'attr': {'autocomplete': 'off' }}) }}
</div>
</div>
<input name="{{ form._token.vars.full_name }}" type="hidden" value="{{ form._token.vars.value }}">
<div class="input-field submit-container">
<button class="waves-effect waves-light btn btn-2 close_action">Annuler</button>
<button class="waves-effect waves-light btn btn-1" type="submit">Valider</button>
</div>
</form>
Javascript submit function
function ajaxSubmit(node) {
$.ajax({
type: node.attr("method"),
url: node.attr("action"),
enctype: 'multipart/form-data',
data: new FormData(node[0]),
processData: false,
contentType: false,
cache: false
}).done(function(response, status, xhr) {
//Your code here
}).fail(function(request, status, error) {
console.error(request);
console.error(status);
console.error(error);
});
}
Side note: Should be obvious, but you should use POST and not GET with your AJAX query.
First because GET parameters are as clear as day... It's quite dangerous for a password to be changed like that.
Second because, as soon as your site will be in HTTPS, your POST parameters will be encrypted. Making it really hard to read the content for anyone sniffing around.
And last, avoid to post code in comments, edit your question instead... ;)

Get selected option and all request data in twig form

I have this form in twig, and I want to get the selected option and the value of the input field from a simple html form:
(PS: Don't say 'You'd better generate a form using the controller!' I know that, but I have a reason why I want to create a form in twig: because that allows me to create as many forms as I want using a for loop.)
I tried to pass arguments in the action path, but that didn't work.
<form action="{{ path('changeProf') }}" method="post" id="form">
<section class="col-lg-9 col-md-9 col-sm-9 col-xs-9" style="position: relative; left: 35%;top: 6vmin">
<label style="display:inline-table;">
<span> <input type="text" value="{{ user.id }}" disabled="true" id="iduser"style="max-width: 18vmin;"/></span>
</label>
</section>
<section class="col-lg-9 col-md-9 col-sm-9 col-xs-9"style="position: relative; left: 35%;top: 6vmin">
<label style="display:inline-table;">
<span>{% set k=1 %}
<select id="profil">
{% for prof in profArr %}
<option value="{{ prof }}"> {{ prof }} </option>
{% endfor %}
</select>
</span>
</label>
</section>
<input type="submit" class="btn btn-info btn-xs" style="position: relative;top:18vmin;left:-28%">
</form>
This is the action that handles the form :
/**
* #Route("/profile/chProf",name="changeProf")
* Method ("POST")
* #Template()
*/
public function changeProfAction(Request $request)
{
$session = new Session();
$session->start();
$search = $session->get('user');
$gestAcces = $session->get('acces');
$gestEtat = $session->get('etatUser');
$gestCont = $session->get('contenu');
$repMsg = $session->get('repMsg');
$gestRec = $session->get('Reclam');
$gestMess = $session->get('gestMess');
$gestMp = $session->get('gestMp');
if ($search == Null) {
return $this->redirectToRoute('empty', array('search' => $search,
'contenu' => $gestCont,
'gestAcces' => $gestAcces,
'gestEtat' => $gestEtat,
'repMsg' => $repMsg,
'gestRec' => $gestRec,
'gestMess' => $gestMess,
'gestMp' => $gestMp,
));
}
$em = $this
->getDoctrine()
->getManager();
$reposit = $em->getRepository("CNAMCMSBundle:userprof");
$rep = $em->getRepository("CNAMCMSBundle:profil");
$userprof=new userprof();
$libprof=$request->request->get('profil');
$iduser=$request->request->get('iduser');
$idprof=$rep->findOneBy(array('libelle'=>$libprof));
$userprof->setIdUser($iduser);
$userprof->setIdProfil($idprof);
$em->persist($userprof);
$em->flush();
return $this->render('CNAMCMSBundle:Default:userProf.html.twig', array(
'search'=>$search,
'contenu'=>$gestCont,
'gestAcces'=>$gestAcces,
'gestEtat'=>$gestEtat,
'repMsg'=>$repMsg,
'gestRec'=>$gestRec,
'gestMess'=>$gestMess,
'gestMp'=>$gestMp,
));
}
I think I found out what caused the error you receive.
$request->request->get('profil'); returns NULL.
This means, the form did not send a profil entry.
Look where is the profil in the form:
<input type="text" value="{{ user.id }}" disabled="true" id="iduser"style="max-width: 18vmin;"/>
There is no name attribute! Which is actually what is sent with the request. The name attribute, not the id. The id is used only for local styles and javascripts.
The solution:
<input type="text" value="{{ user.id }}" disabled="true" id="iduser" name="iduser" style="max-width: 18vmin;"/>
Do the same for profil
Hope this helps.

Overlapping forms

I have a problem with overlapping forms in Symfony 2. I'm trying to create an application with photos albums.
To do so, I need to create an album and adding him photos during this process.
Photos are link with onl one album.
I think that my code is right on backend but the photo's form never shown inside the album's form. I think the problem is in the form himself.
A photo have to attribute: a file and a name.
This is my form.
<script type="text/javascript">
$(document).ready(function() {
var $container = $('#thelink_albumbundle_albumtype_photos');
function add_photo() {
index = $container.children().length;
$container.append(
$($container.attr('data-prototype').replace(/\$\$picture\$\$/g, index))
);
}
if($container.children().length == 0)
add_tag();
$('#add_photo').click(function() {
add_photo();
});
});
<form method="post" enctype="multipart/form-data">
{{ form_widget(form) }}
<div id="thelink_albumbundle_albumtype_photos" data-prototype="<div>
<label class=" required">$$photo$$</label>
<div id="thelink_albumbundle_albumtype_photos$$photo$$">
<div><label for="thelink_albumbundle_albumtype_photos$$photo$$_photo"
class=" required">Photo</label>
<input type="file" id="thelink_albumbundle_albumtype_photos$$photo$$_photo"
name="thelink_albumbundle_albumtype[photos][$$photo$$][picture]" required="required"
maxlength="255" value="" /></div></div></div>">
</div>
<br />
<a href="{{ path('TheLinkAlbumBundle_index') }}" title="Retourner sur la liste des Albums">
Annuler la {% if news is defined %}Modification{% else %}Création{% endif %}
</a>
<input type="submit" value="{% if album is defined %}Modifier{% else %}Créer{% endif %} l'Album" />
Do you have any idea ? Or the problem is else where ?
It looks like you're manually entering the data prototype? Assuming "photos" is a collection type. Make sure you have the "allow_add", "allow_delete", and "prototype" options set to true:
->add('photos', 'collection', array(
'type' => new PhotoType(),
'allow_add' => true,
'allow_delete' => true,
'prototype' => true
))
Then you can just do {{ form_row(form.photos) }} and it should render the prototype automatically.
It would help more if you could post your form type as well.

Resources