Error generating custom url that shows a phpcr document version in SonataAdmin - symfony

So i'm trying to extend the SonataAdmin show view to have a list of the document's versions (phpcr checkpoint or checkin).
I've got the list of saved versions showing correctly, and now i need to make them into links that display that version of the content, but i'm getting the following error when trying to add a custom Route:
An exception has been thrown during the rendering of a template
("Parameter "id" for route "admin_cmsbundle_product_show_version" must
match "[^/]++" ("mstr/product/product-253562" given) to generate a
corresponding URL.") in CmsBundle:product:show.html.twig at line 18.
This is my configureRoutes in my admin class:
protected function configureRoutes(RouteCollection $collection)
{
$collection->add('show_version', $this->getRouterIdParameter() . '/show/{version}');
}
This is my overriden template:
{% block show %}
<ul>
{% for version in versions %}
<li>Version: {{ version.name }} </li>
{% endfor %}
</ul>
{{ parent() }}
{% endblock %}
This is my edited show action (to include the versions list):
public function showAction($id = null)
{
...
return $this->render($this->admin->getTemplate('show'), array(
'action' => 'show',
'object' => $object,
'elements' => $this->admin->getShow(),
'versions' => $this->getVersionHistory($object)
));
}
And this is my showVersion action in the controller:
public function showVersionAction($id = null, $version = "1.0")
{
...
return $this->render($this->admin->getTemplate('show'), array(
'action' => 'show',
'object' => $this->getVersion($object, $version),
'elements' => $this->admin->getShow(),
'versions' => $this->getVersionHistory($object)
));
}
Note, generateUrl gives the same error:
Version: {{ version.name }}
What am i doing wrong?
Any help on fixing this would be greatly appreciated :)

According to the error message there is some problem with $object. So maybe use generateUrl instead of generateObjectUrl() and pass your id in array:
{% block show %}
<ul>
{% for version in versions %}
<li>Version: {{ version.name }} </li>
{% endfor %}
</ul>
{{ parent() }}
{% endblock %}

Bit of digging around and the answer was simple, just had to override the pattern for id to be .+
protected function configureRoutes(RouteCollection $collection)
{
$collection->add('show_version', $this->getRouterIdParameter() . '/show/{version}', array(), array('id' => '.+'));
}

Related

Symfony form builder: How to iterate/render dynamic amount of text fields in twig? (Sylius - exta checkout step)

I added an extra checkout step to the Sylius checkout procedure and I am trying to add 1 text field per instance of an ordered item. Thus having put 3x itemA and 1x itemB in cart should spawn 4 text fields.
I have this code so far in the form builder (based on code from here: https://sf.khepin.com/2011/08/basic-usage-of-the-symfony2-collectiontype-form-field/)
$builder->add('myCollection', CollectionType::class, $formOptions);
$totalCounter = 0;
/** #var OrderItem $item */
foreach($order->getItems() as $item) {
for($itemCounter = 0 ; $itemCounter < $item->getQuantity(); $itemCounter++ ) {
$totalCounter++;
$builder->get('myCollection')->add('item_' . $totalCounter, TextType::class, $formOptions);
}
}
First Question: Is this the right approach for the form builder for my particular scenario, and secondly: If yes, how do I read from this in the twig template?
form.children.myCollection does exist but does not seem to contain these children, so that I could use them with the form_row function. What I would have expected is something like this:
{% set totalCounter = 0 %}
{% for item in order.items %}
{% for itemCounter in 1..item.quantity %}
{% set totalCounter = totalCounter + 1 %}
{{ form_row(form.children.myCollection['item_' ~ totalCounter ]) }}
{% endfor %}
{% endfor %}
Any idea how to do this? Thanks for any hint in advance!
Just saw my exact case is described in the beginning of the documentation of CollectionType https://symfony.com/doc/current/reference/forms/types/collection.html
The form builder part..
$builder->add('emails', CollectionType::class, [
// each entry in the array will be an "email" field
'entry_type' => EmailType::class,
// these options are passed to each "email" type
'entry_options' => [
'attr' => ['class' => 'email-box'],
],
]);
..and the twig part:
{{ form_label(form.emails) }}
{{ form_errors(form.emails) }}
<ul>
{% for emailField in form.emails %}
<li>
{{ form_errors(emailField) }}
{{ form_widget(emailField) }}
</li>
{% endfor %}

Sonata Admin modify data in list view

I am trying to show/edit postgis point type. I am using creof/doctrine2-spatial package which provides some neat functions to get X and Y values for a point. The following works fine in edit/new form so the point is listed as 'Y X' (in this case "latitude longitude").
I am not sure if this is the correct way to accomplish what I need, but it works.
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('name', 'text')
->add('coords', 'text', array(
'data'=>
$this->getSubject()->getCoords()->getLatitude() . ' ' .
$this->getSubject()->getCoords()->getLongitude()
));
}
However the problem is the list views. Because point is converted to string as "X Y" it prints the latitude and longitude in wrong order in list view. It prints as "longitude latitude" I am very new to sonata so I am not exactly sure how to solve the issue in list view.
Any ideas?
Update: Thanks to #kunicmarko20 I have resolved the issue:
So the file goes to app/Resources/views/SonataAdmin/CRUD/geography_point_list.html.twig
I decided to put the file to a reasonable folder.
The contents of the template is:
{% extends 'SonataAdminBundle:CRUD:base_list_field.html.twig' %}
{% block field %}
<div>
{{ object.coords.getLatitude }} {{ object.coords.getLongitude }}
</div>
{% endblock %}
The code for using the template was:
->add('coords', null, ['template' => 'SonataAdmin/CRUD/geography_point_list.html.twig']);
For some reason I couldn't get the : type path to work?
For your list view field, you can create custom template as explained here.
Example:
protected function configureListFields(ListMapper $listMapper)
{
$listMapper
->add('yourfiled', null, ['template' => 'AppBundle:Admin:your_field_list.html.twig'])
;
}
And your template would look like:
{% extends 'SonataAdminBundle:CRUD:base_list_field.html.twig' %}
{% block field %}
<div>
{{ object.longitude }} {{ object.latitude }}
</div>
{% endblock %}
The object here is your entity with all values.

Unable to override KnpMenuBundle template

With ...MyBundle\Resources\views\Menu\knp_menu.html.twig, deleting the </li> has no effect on the rendered menu. (Removing the tag is done to remove the space between inline list elements.) I have followed the advice provided in this answer, including the {% import 'knp_menu.html.twig' as knp_menu %} mentioned toward the bottom of that post. Is this because knp_menu.html.twig already extends knp_menu_base.html.twig? Or what?
layout.html.twig:
...
{{ render(controller('VolVolBundle:Default:userMenu')) }}
...
userMenuAction:
$user = $this->getUser();
$tool = $this->container->get('vol.toolbox');
$type = $tool->getUserType($user);
return $this->render(
'VolVolBundle:Default:userMenu.html.twig', array('type' => $type)
);
userMenu.html.twig
...
{% if type is not null %}
{% set menu = "VolVolBundle:Builder:"~type~"Menu" %}
{{ knp_menu_render(menu) }}
{% endif %}
The answer was found deep in here. All that's required to do a global override of the template is to modify config.yml.
config.yml:
...
knp_menu:
twig: # use "twig: false" to disable the Twig extension and the TwigRenderer
template: VolVolBundle:Menu:knp_menu.html.twig
...

FormComponent - Pass Entity with Form to Template

Lets say i have a one to many relation:
A company can own several pictures
I have company entity and image entity
Now, I want to display all pictures of a company within a template. And i also want to make theese pictures directly editable. I thought of adding a form to each Entity of the DoctrineArrayCollection and pass them to the template. In Template if somebody clicks on a picture the corresponding should be fade in, the should be able to edit the pictures description and pass it through ajax to a controller.
In my entity I added a field without annotations:
private $form;
public function setForm(MyPictureForm $form)
{
$this->form = $form;
}
public function getForm()
{
return $this->form;
}
Now in my controller I add a form instance to every picture of the company:
//office images with forms
$officeImages = array();
foreach($company->getOfficeImages() as $image)
{
$form = $this->get('companybundle.imagedescription.form.factory')->createForm();
$form->setData($image);
$image->setForm($form->createView());
array_push($officeImages, $image);
}
return $this->render('CompanyBundle:Company:Show/show.html.twig', array(
'company' => $company,
'officeImages' => $officeImages
));
}
And in my template i render it like this way:
{% for image in officeImages %}
<a href="#" title="{% if image.description %}{{ company.description }}{% else %}CLICK HERE FOR EDIT{% endif %}">
{% if image.image %}
<img src="{{ vich_uploader_asset(image, 'image') | imagine_filter('company_office_image_thumb') }}"
alt="{% if image.description %}{{ company.description }}{% endif %}"/>
{% else %}
{% image '#UserBundle/Resources/public/img/nopic_logo.jpg' output='/images/nopic_logo.jpg' %}
<img src="{{ asset_url }}" alt="Joblogo"/>
{% endimage %}
{% endif %}
</a>
{{ form(image.form) }}
{% else %}
<p>Es sind noch keine Images vorhanden</p>
{% endfor %}
At the end there is a lot if javascript stuff which handels the fade in / fade out of the form and their submitting.
Is this the correct way to handle my case? I think not cause passing a form for every picture seems like overhead?
The reason why I am working with forms instead of simply passing data out of a manual added input field is csrf protection and the smart usage of the form component.
As you said keeping form object in entity isn't a good idea. It should represents model data.
I've got two solutions for this:
I. Pass array of forms with image id as a key
$officeImages = array();
$imageForms = array();
foreach($company->getOfficeImages() as $image)
{
$form = $this->get('companybundle.imagedescription.form.factory')->createForm();
$form->setData($image);
$imageForms[$image->getId()] = $form->createView();
}
return $this->render('CompanyBundle:Company:Show/show.html.twig', array(
'company' => $company,
'officeImages' => $officeImages,
'imageForms' => imageForms
));
and in show.html.twig
{% for image in officeImages %}
{# your image display code #}
{{ form(imageForms[image.id]) }}
{% endfor %}
II. render partial for single image
in controller
public function showAndEditImageAction(Image $image)
{
$form = $this->get('companybundle.imagedescription.form.factory')->createForm();
$form->setData($image);
return $this->render(
'CompanyBundle:Company:Show/showAndEditImage.html.twig', array(
'image' => $image,
'imageForm' => $form->createView()
));
}
in twigs
{# CompanyBundle:Company:Show/showAndEditImage.html.twig #}
{# your image display code #}
{{ form(imageform) }}
{# CompanyBundle:Company:Show/show.html.twig #}
{% for image in officeImages %}
{{ render(controller('CompanyBundle:Company:showAndEditImage',
{ 'image': image })) }}
{% endfor %}

Directly access a form field's value when overriding widget in a twig template

What I want to do is get variables stored in form view.
{% form_theme edit_form _self %}
{% block field_widget %}
{% spaceless %}
{% set type = type|default('text') %}
<input type="{{ type }}" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value }}" {% endif %}/>
{# MY CODE #}
{% if type == "file" %}
<a class="BOpreview" href="{# NEED TO REPLACE VAR HERE #}">Aperçu</a>
{% endif %}
{# MY ATTEMPT #}
{{ form.title.get('value') }}
{{ form.vars.value.url }}
{% endspaceless %}
{% endblock field_widget %}
My form has properties like url, title, etc and I am trying to access them here to use it in the field widget block.
I searched for it and came on https://groups.google.com/forum/?fromgroups=#!topic/symfony2/onor9uFte9E that suggested:
{{ form.title.get('value') }}
{{ form.vars.value.url }}
which didn't work for me.
Note: If I do a var_dump on $form->createView() in my controller, I get:
object(Symfony\Component\Form\FormView)[331]
private 'vars' =>
array (size=15)
'value' =>
object(Panasonic\TestEtAvisBundle\Entity\Product)[168]
protected 'reviewArray' =>
object(Doctrine\ORM\PersistentCollection)[234]
...
protected 'testArray' =>
object(Doctrine\ORM\PersistentCollection)[221]
...
protected 'fbshareArray' =>
object(Doctrine\ORM\PersistentCollection)[317]
...
private 'id' => int 2
private 'name' => string 'Nom du produit' (length=14)
private 'title' => string '<span>Titre </span>' (length=19)
private 'image' => string 'bundles/testetavis/uploads/product/0d9d9550.png' (length=47)
private 'fbImage' => string 'bundles/testetavis/uploads/product/facebook//product_e928cd96.jpg' (length=65)
private 'description' => string '<span>Descriptif </span>' (length=24)
private 'url' => string 'http://www.google.com' (length=21)
private 'creationDate' =>
object(DateTime)[210]
...
private 'modificationDate' =>
object(DateTime)[209]
...
private 'isDeleted' => int 0
'attr' =>
array (size=0)
empty
'form' =>
&object(Symfony\Component\Form\FormView)[331]
'id' => string 'panasonic_testetavisbundle_producttype' (length=38)
'name' => string 'panasonic_testetavisbundle_producttype' (length=38)
'full_name' => string 'panasonic_testetavisbundle_producttype' (length=38)
I want to access that url for instance but can't seem to be able to do it after many variations. Including use of {{ value }}, {{ value.url }}
But inspite of vars, I can do {{ full_name }} and get panasonic_testetavisbundle_producttype.
Any ideas?
Edit2: I found out the real problem...
Edit3: Seeing that this question is quite popular I decided to clarify on what I attempted to do in case it helps someone in the same situation. If you rely strictly on what the question asks, as I stated from my research and that Besnik supported are indeed correct.
Now what I wanted to do is for every input type file, get url from object used to render form and append a preview link, using retrieved url, beside the input type file.
If you try to get the form var of an input type "file" like this "{{ form.vars.value.url }}" in my code, this doesn't work since, if I recall correctly, you receive a token instead of the url stored inside the object.
You can access the current data of your form via form.vars.value:
{{ form.vars.value.title }}
See Symfony2 Forms documentation: http://symfony.com/doc/current/book/forms.html#rendering-a-form-in-a-template
Dump vars by using dump function:
{{ dump(form.vars.value) }}
If you are using subforms or want to have a value of a specific field:
{{ form.FIELD.vars.VALUE }}
You can access values of the parent parent from a widget block using form.parent.vars
For example, we want to render the value from a type text field called primerNombre we will need
{{ form.vars.value.primerNombre }}
If we wanted to render the name of one of the children we will need
{% for hijo in form.hijos %}
<td><div align="left">{{ form_widget(hijo.vars.value.primerNombre) }}</div></td>
{% endfor %}
Good luck!
In Symfony > 3 you may use:
form.vars.value.Entity.someValue
Edit2:
Finally, I was indeed getting the value of the current row in {{ value }} here:
{% block field_widget %}
{% spaceless %}
{% set type = type|default('text') %}
<input type="{{ type }}" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ **value** }}" {% endif %}/>
{# MY CODE #}
{% if type == "file" %}
<a class="BOpreview" href="{{ value }}">Aperçu</a>
{% endif %}
{% endspaceless %}
{% endblock field_widget %}
But in my case I get a token instead of the value since I am using input type file. This is due to a security measure in Symfony2.

Resources