I'm using SonataAdminBundle for managing entities in my application. The admins of the site can add videos, and some of them first need to be approved by their speakers. There is an authorization system working already - I have working code which will generate a special link and notify the speaker, who can approve or disapprove the video, and notify back the admins automatically.
I'd like to customize my admin section, so there will be a button ask for authorization next to the videos. I'm okay having it either in the list action ( /admin/acme/videos/list ) or in the edit action somewhere in the right-nav ( /admin/acme/videos/x/edit/ )
What's the best approach to do this? The documentation says very little about blocks customization, but I found this example which may be the thing I'm looking for, but I couldn't figure out how to use it.
One option is to use the preUpdate hook, and add a checkbox to the edit action, but a button would be much nicer.
To add an action for edit form
Add to your admin class:
protected function configureSideMenu(MenuItemInterface $menu, $action, Admin $childAdmin = null)
{
if (!$childAdmin && !in_array($action, array('edit'))) {
return;
}
$admin = $this->isChild() ? $this->getParent() : $this;
$id = $admin->getRequest()->get('id');
$menu->addChild('My action', array('uri' => 'http://google.com?id=' . $id));
}
It will create left side menu for actions like /admin/acme/videos/x/edit/. Having id for current item allows you to build any custom URL.
To add an action for list:
In your admin file add
protected function configureListFields(ListMapper $listMapper)
{
$listMapper
->add('_action', 'actions', array(
'actions' => array(
'act' => array('template' => 'AcmeBundle:Video:my_temp.html.twig'),
)
))
;
}
It will add a column with links, then you need to create a template for your column, something like
<a href="{{ admin.generateObjectUrl('delete', object) }}" class="delete_link" title="{% trans from 'SonataAdminBundle' %}action_delete{% endtrans %}">
<img src="{{ asset('bundles/sonataadmin/famfamfam/delete.png') }}" alt="{% trans from 'SonataAdminBundle' %}action_delete{% endtrans %}" />
</a>
All examples are taken from link that you provided. Hope it helps
Related
Is it possible to set a series of global properties (such as social media usernames) that are available to all page views in OctoberCMS rather than having them associated to one CMS page or Static Page at a time?
For example, being able to use {{ twitter_username }} in any template, but it wouldn't show up as a field in any page form on the backend.
UPDATE: this can be achieved by registering a Twig function using registerMarkupTags in your plugin:
use System\Classes\PluginBase;
class Plugin extends PluginBase
{
public function registerMarkupTags()
{
return [
'functions' => [
'globals' => function($var) {
switch ($var) {
case 'twitter_username':
return 'mytwitterusername';
}
return null;
},
],
];
}
}
In this case, calling {{ globals('twitter_username') }} from any template prints mytwitterusername.
Hmm yes better you need to add code to life-cycle method in layouts, so now page which are using that layout will have this info already loaded.
In layout code block you can use something like this
use RainLab\Pages\Classes\Page as StaticPage;
function onStart() {
$pageName = 'static-test'; // this will be static page name/filename/title
$staticPage = StaticPage::load($this->controller->getTheme(), $pageName);
$this['my_title'] = $staticPage->viewBag['title'];
$this['twitter_username'] = $staticPage->viewBag['twitter_username'];
}
now inside your cms page you can use this variable
<h1>{{ my_title }} </h1>
<h3>{{ twitter_username }} </h3>
let me know if it you find any issues
You could also use theme config file which gives you more flexibility rather than hardcoding the values in to the code block.
https://octobercms.com/docs/themes/development#customization
easy simple question.
I made a register form. When you press the submit button the Controller handles the request and renders.
if($form->isSubmitted && $form->isValid()) {
return $this->render('x/register.html.twig', array(
'options' => 'userCreated',
'userName' => $user->getUsername()); }
in the .html.twig
{% if options=="userCreated" %}
user {{userName}} was successfully inserted into the database
{% else %}
//show the form
{% endif %}
This worked perfect but my "teacher" told me, that I have to do this more dynamic.
I did a new "Info.html.twig" now with 2 parameters (redirectUrl, redirectText) e.g.
$redirectUrl="/home"; $redirectText="Go to the home page"
in HTML
{{redirectText}}
and if there is a message at the top I use the flashMessage method ($this->addFlash).
Is that a good method or is there a way to get way more dynamical than this?
I am wondering how to solve this problem:
I have form with 4 fields. I want 4th field to be dependent on user status (logged or unlogged). For logged user I will get ID from session but unlogged user should provide username manually.
I dont know which option should i use. Inherit_data, two form types (two much duplicated code) or validation groups based on the submitted data. Any ideas?
Ok, There are several ways to achive that.
Take a Look at FormEvents. In your case it would be FormEvents::PRE_SET_DATA and then read about dynamic forms
I personly prefer to do following
public function buildForm(FormBuilderInterface $builder, array $options)
{
//$builder->add( ... )
//$builder->add( ... )
//$builder->add( ... )
//each-event with own method, but of cource it can be a callback like in a tutorial above
$builder->addEventListener(FormEvents::PRE_SET_DATA, array(this, 'onPreSetData');
}
and in the same class there is a method onPreSetData
public function onPreSetData ( FormEvent $formEvent )
{
$form = $formEvent->getForm();
$user = $formEvent->getData();
//pseudo-code
if( $user->isLoggedIn() )
{
$form->add('user', HiddenType::class, array(
));
}
else
{
$form->add('user', TextType::class, array(
'label' => 'Username',
'required' => true,
'constraints' => array(
new NotBlank(array('message' => 'please enter username...')),
// new YourCustomValidator(array('message' => 'not enough minerals...')),
)
));
}
}
I personally think a more elegant solution is to pass the user to the form builder from your controller. It's been covered in this answer here:
how to check the user role inside form builder in Symfony2?
you can create form by individual fields like
{{ form_row(form.username) }}
{{ form_row(form.email) }}
{{ form_row(form.phone) }}
{% if(app.user) %}
//your conditional field
{% endif%}
By this way, you have to create submit button as well as csrf token if there
I hope this will quite helpful :)
The Scenario
I have two Entities linked together with manyToMany relation.
Entities
User
Interest
So on a user's profile form there is a field called Interests which is rendered using select2.
Now a user can select as many Interests as they want and upon saving doctrine is doing the nice job of saving the selected Interests in the linked table. When I reload the profile page I can see that interests that I already selected.
The Problem
Although the form field is linked with an Interest entity
$form->add('interest', EntityType::class, array(
'class' => 'AppBundle\Entity\Interest',
'multiple' => true,
'expanded' => false,
'by_reference' => false)
A user can also add interests of their own which do not exist in Interest table with the help of Tagging Support on front end and on backend to save this information I have Form Event Subscriber in place that checks if any of the Interests submitted by the user does not exist in Interest table add them and it is here where I get the following exception
Message
This value is not valid.
Origin
interest
Cause
Symfony\Component\Validator\ConstraintViolation
Object(Symfony\Component\Form\Form).children[interest] = [0 => 1, 1 => 4, 2 => 7, 3 => www]
Caused by:
Symfony\Component\Form\Exception\TransformationFailedException
Unable to reverse value for property path "interest": Could not find all matching choices for the given values
Caused by:
Symfony\Component\Form\Exception\TransformationFailedException
Could not find all matching choices for the given values
Here is the Event Subscriber code
namespace AppBundle\Form\EventListener;
use AppBundle\Entity\Interest;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;
class AddProfileFieldSubscriber implements EventSubscriberInterface
{
protected $authorizationChecker;
protected $em;
function __construct(AuthorizationChecker $authorizationChecker, EntityManager $em)
{
$this->authorizationChecker = $authorizationChecker;
$this->em = $em;
}
public static function getSubscribedEvents()
{
// Tells the dispatcher that you want to listen on the form.pre_set_data
// event and that the preSetData method should be called.
return array(
FormEvents::PRE_SUBMIT => 'onPreSubmit'
);
}
/**
* #param FormEvent $event
*/
public function onPreSubmit(FormEvent $event){
$interestTags = $event->getData();
$interestTags = $interestTags['interest'];
foreach($interestTags as $interestTag){
$interest = $this->em->getRepository('AppBundle:Interest')->findOneBy(array('id' => $interestTag));
if(!$interest){
$newInterest = new Interest();
$newInterest->setName($interestTag);
$this->em->persist($newInterest);
$this->em->flush();
}
}
}
}
The Attempt
I updated the form code to as following by adding choice_value
$form->add('interest', EntityType::class, array(
'class' => 'AppBundle\Entity\Interest',
'multiple' => true,
'expanded' => false,
'by_reference' => false,
'choice_value' => 'name'
)
);
and I changed the query inside the Event Subscriber from
$interest = $this->em->getRepository('AppBundle:Interest')->findOneBy(array('id' => $interestTag));
to
$interest = $this->em->getRepository('AppBundle:Interest')->findOneBy(array('name' => $interestTag));
This worked perfectly at first but when I reload the profile page my interest field appears empty
The reason why it appears empty is because (my assumption) of id="select2-user_interest-result-5z18-Education" I think that needs to look something like id="select2-user_interest-result-5z18-66"
<ul class="select2-results__options" role="tree" aria-multiselectable="true" id="select2-user_interest-results"
aria-expanded="true" aria-hidden="false">
<li class="select2-results__option" id="select2-user_interest-result-5z18-Education" role="treeitem"
aria-selected="false">Education
</li>
<li class="select2-results__option" id="select2-user_interest-result-rdka-History" role="treeitem"
aria-selected="false">History
</li>
<li class="select2-results__option select2-results__option--highlighted"
id="select2-user_interest-result-lfq4-Architecture" role="treeitem" aria-selected="false">Architecture
</li>
<li class="select2-results__option" id="select2-user_interest-result-qqiq-Entrepreneurship" role="treeitem"
aria-selected="false">Entrepreneurship
</li>
<li class="select2-results__option" id="select2-user_interest-result-qutx-Technology" role="treeitem"
aria-selected="false">Technology
</li>
<li class="select2-results__option" id="select2-user_interest-result-sfx4-Engineering" role="treeitem"
aria-selected="false">Engineering
</li>
I crossed check the data in Interest table and I can see the new interests added by the user which did not exist before, so its working but on front end its not being displayed. Out of curiosity i removed the choice_value and then reloaded the profile page and I could see the new interests
I will really appreciate if anything can push me in right direction and let me know what am I missing and how can i get this to work.
You can't do that as EntityType extend ChoiceType which doesn't allow adding new values in the choice list.
You should use CollectionType like in the cookbook http://symfony.com/doc/current/cookbook/form/form_collections.html
To make a proper select in your code, you'll also have to insert in the view, along with your form, the full list of all interests.
Then your view should looks like :
<select name='{{ form.interest.vars.full_name }}' id="{{ form.interest.vars.id }}" class='select2'>
{% for interestList as interestItem %}
<option value='{{ interestItem.id }}' {% if some_logic_to_check_whether_the_item_is_selected %}selected='selected'{% endif %} >{{ interestItem.name }}</option>
{% endfor %}
</select>
I am new to Symfony & trying to list images in sonata admin bundle. But i am bit confuse how to get an object in twig file so that i can get my exact path for image source.
here is my code
protected function configureListFields(ListMapper $listMapper)
{
// $image = $this->getSubject();
$listMapper
->addIdentifier('caption')
->add('image','string', array('template' => 'swaamImageUploaderBundle:Admin:list_image.html.twig'))
;
}
and here is my list_image.html.twig file
{% extends 'SonataAdminBundle:CRUD:base_list_field.html.twig' %}
{% block field%}
<img src="{{ 'uploads/images/822b23a922f43bb664cb58ca57de6cccccc962e5.jpeg'}}">
{#<img src="{{ asset(image.getthumbWebPath) }}">#}
{% endblock %}
in my image source tag i have given a hard code path for my testing. but don't know how to get path from db.
plus, when i write only ->add('image') in controller i get my exact path from db displayed in back end.
I have an entity image.
any one who can help me ?
I suppose you're using Sonata Media Bundle
you must use the entity and you can find needed helpers here: http://sonata-project.org/bundles/media/2-2/doc/reference/helpers.html
If you need to get the full path in controller (can happen, very rarely but can happen) you have to use the media service
$mm = $this->container->get('sonata.media.manager.media');
$pr = $this->container->get('sonata.media.provider.image');
$media = $mm->findOneBy(array('id' => $idImage));
$format = $pr->getFormatName($media, 'default');
$path = $pr->generatePublicUrl($media, $format);
or use the twig helper inside the controller
$format = 'default'; //the format you want to show
$path=$this->get('sonata.media.twig.extension')->path($image, $format);
if you want to add the image to the list field of Sonata Admin (your case) you can use this code
{% block field %}
{% thumbnail object.image, 'thumb' %}
{% endblock %}
where image is the image getter method of your entity (es getImage() )