I'm using SonataAdminBundle with Symfony 2.2 and want to display the dashboard blocks depending on what user is logged in.
e.g.
Users with Group Superadmin will see blocks 'UserManagement' and 'Messages'
Users with Group Staff will see only block 'Messages'
I read the whole documentation especially the security doc but found no info on how to restrict the dashboard view. I already implemented a filter which will will show no entries in the list view of an entity class if the user's permissions are not enough.
But it would be way better to not show him the blocks at all.
Any ideas on how to do this ?
Well, for anyone running into this, I solved it by simply returning an empty Response in execute(). My use-case was one where I wanted to show a block to users with a specific role:
First I defined my service for using the security.context service like so:
sonata.block.service.foo:
class: Acme\DemoBundle\Block\FooBlockService
arguments: [ "sonata.block.service.foo", "#templating", "#doctrine", "#security.context" ]
tags:
- { name: sonata.block }
Then I defined the block service __construct() as:
//Acme\DemoBundle\Block\FooBlockService
public function __construct($name, EngineInterface $templating, $doctrine, $securityContext)
{
parent::__construct($name, $templating);
$this->doctrine = $doctrine;
$this->securityContext = $securityContext;
}
Finally, in the execute function the first thing I did was check for a specific role on the user like so:
//Acme\DemoBundle\Block\FooBlockService
public function execute(BlockContextInterface $blockContext, Response $response = null)
{
if( $this->securityContext->isGranted("ROLE_ACME_FOO") === false )
{
return new Response();
}
//... more code
This works, but it feels like a hack. Mainly, because the Block goes through all of its stages, and only on the output it return nothing, meaning overhead. A better solution would be to somehow prevent the entire Block from being loaded, based on some custom code.
In conclusion, this isn't the best way, but it worked for me!
You can restrict access to block using roles, like this:
sonata_admin:
dashboard:
blocks:
- { position: top, type: your_service_block_name, class: 'col-xs-4', roles: [ROLE_SOME_NAME_HERE_, ROLE_SOME_NAME_HERE_2] }
You can check under SonataAdminBundle:Core:dashboard.html.twig how this roles works:
{% if block.roles|length == 0 or is_granted(block.roles) %}
<div class="{{ block.class }}">
{{ sonata_block_render({ 'type': block.type, 'settings': block.settings}) }}
</div>{% endif %}
Related
I need to check if a user is using a mobile device upon connecting to the website.
I need to make an eventListener further on.
in the Symfony doc there is a passage where actually you can check this $request->headers->get('User-Agent')
Is there a simple way to do that?
--- EDIT ---
this is the code I wrote so far. I'm missing maybe on how to pass it to the controller?
service
template.loader:
class: ST\BackofficeBundle\EventListener\DeviceListener
tags:
- { name: kernel.event_listener, event: kernel.view, method: onKernelView }
listener
class DeviceListener
{
public function onKernelView(getResponseEvent $event)
{
$event->getRequest()->getSession()->set('mobile', true);
$response = new Response();
$response->setContent($event);
$event->setResponse($response);
}
}
Am I on the right track ?
You could have a look at https://github.com/kbond/ZenstruckMobileBundle code
It creates an EventListener here:
https://github.com/kbond/ZenstruckMobileBundle/blob/master/EventListener/RequestListener.php
And based on that, overwrites the twig render here:
https://github.com/kbond/ZenstruckMobileBundle/blob/master/Twig/TwigEngine.php
Both things get wired up through
https://github.com/kbond/ZenstruckMobileBundle/blob/master/Manager/MobileManager.php
Default forgot password form is an Email form field. I have a licence number attached to each user. As a user, I want to get the reset password link by entering my licence number instead of email.
I've found a way around to override the functionality side but not the form. What I've done till now
Filename: _config.php
Object::useCustomClass('MemberLoginForm', 'ExtendLoginForm');
Filename: ExtendLoginForm.php
Just as a test - override forgotPassword method
class ExtendLoginForm extends MemberLoginForm
{
public function forgotPassword($data) {
// Ensure password is given
$this->sessionMessage('This works', 'bad');
$this->controller->redirect('Security/lostpassword');
return;
}
}
Session message is successfully printed with a redirect to the same page.
How can I override the form to make it a Text field instead of Email field.
First, just a general tip, you should avoid using Object::useCustomClass() in favour of Injector (https://docs.silverstripe.org/en/3.0/reference/injector/). Object::useCustomClass() is more of a 2.4 thing.
The forgot password form is generated in Security. You can try using a custom implementation of that class as well, and overloading the ForgotPasswordForm method.
config.yml
Injector:
Security:
class: MyCustomSecurity
MyCustomSecurity.php
class MyCustomSecurity extends Security
{
public function LostPasswordForm()
{
$form = parent::LostPasswordForm();
$form->Fields()->replaceField('Email', TextField::create('Email'));
return $form;
}
}
Q1.I want to count the unread messages before every page rendered ,and add the number into twig template.i don't know how to make it
Q2.i have read the symfony tutorial,it seems that i will make a service ,but i don't know how to do it.is the following code right? and i don't know what to write at the seconde argument
namespace App\RepairBundle\Service;
use Symfony\Component\DependencyInjection\ContainerInterface;
use App\RepairBundle\Entity\RepairMessageRepository;
class CountUnReadMessage
{
protected $container;
protected $messageRepository;
public function __construct(ContainerInterface $container,RepairMessageRepository $messageRepository)
{
$this->container = $container;
$this->messageRepository = $messageRepository;
}
public function countUnRead()
{
$number = $this->messageRepository->countByUnread($this->container->get('security.token_storage')->getToken()->getUser()->getId());
return $number;
}
}
parameters:
app.repair.count_unread_message: App\RepairBundle\Service\CountUnReadMessage
services:
app.repair.count_unread_message:
class: %app.repair.count_unread_message%
arguments:
- #service_container
- #
If piece of the twig template containing message counter similar in all templates, you can move it to separate template and call service inside this template. You steps to achieve this might look like this:
Write service for getting message counter (you almost got it, but try to avoid injecting whole container in servce since it is a very bad practice. In this case, i think you could inject only security.token_storage)
Make this service visible in twig templates by declare it in config file.
config.yml
twig:
globals:
count_read_message: #app.repair.count_unread_message
In your separate twig file call this service
message_block.html.twig
{{ count_read_message.countUnRead() }}
And include this twig file to needed template (better idea would be keep main template for most of templates and include you file in this template, but this dependenced of template structure)
I hope you got the main idea =)
P.S. Answering for Q2 - it is #doctrine.orm.entity_manager
If you want to inject repository make another service with your repository:
app.message_repository:
class: Doctrine\ORM\EntityRepository
factory: ["#doctrine.orm.default_entity_manager", getRepository]
arguments:
- App\RepairBundle\Entity\RepairMessage
Then in your service:
app.repair.count_unread_message:
class: %app.repair.count_unread_message%
arguments:
- #service_container
- #app.message_repository
BTW you don't need container, inject only security.token_storage instead of container.
Probably I have a problem with a loop in template.
services:
twig_menu:
class: Cms\PageBundle\Twig\Menu
arguments: ['#doctrine.orm.entity_manager', "#templating"]
Code php:
namespace Cms\PageBundle\Twig;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Templating\EngineInterface;
class Menu {
protected $em, $templating;
public function __construct(EntityManager $em, EngineInterface $templating){
$this->em = $em;
$this->templating=$templating;
}
public function show($typ){
$menu=$this->em->getRepository("CmsAdminBundle:Menu")->findBy(array('type_id'=>$typ));
return $this->templating->render("menu.html.twig", array('links'=>$menu));
}
}
Template:
<ul>
{% for link in links %}
<li>{{ link.name }}</li>
{% endfor %}
</ul>
When I cleared cache on the first refresh it is ok, next I get this error:
Circular reference detected for service "templating", path:
"templating -> twig -> twig_menu".
templating needs twig, twig needs twig_menu and twig_menu needs templating. Hence your circular reference problem. It might be because you're in dev mode, where Twig has a lot more dependencies, because of the profiler.
Fabien Potencier himself has answered this problem on GitHub by saying "Just inject the service container and get Twig from that". It's a quick and dirty solution, but it should work without any serious penalties.
But because injecting the service container is a code smell, you might want to avoid it. The deeper (more correct) solution is to refactor so that twig doesn't depend on twig_menu, but without knowing your entire project, it's hard to say how you could do that.
Inject the twig service, rather than the templating service. #twig is the service name.
Instead of injecting the templating service within the twig_menu service's constructor you could provide it using a setter method. For example:
public function setTemplating(TwigEngine $templating)
{
$this->templating = $templating;
}
Then in your controller use:
$this->get('twig_menu')->setTemplating($this->get('templating'));
I am working with Symfony2. I have two fields in a form builder, when I select a choice in the first field, information in the second field should be reloaded dynamically according the first choice.
How can I do it ?
What do we need?
Let's say for the sake of simplicity you need when the user type in 'knock knock' the next field is filled with 'who's there'.
Assuming that the first field (which the user will fill) has an id of #input_filled_by_user and the other field's id is #input_filled_with_php.
How do we solve this?
As long as the user will be typing after the script is executed, we need something to tell us the he wrote the specific word in or case 'knock knock' (which is jQuery), and send it to a controller (a fancy name for rest API).
And then in the controller we process and send back result, and with jQuery (again) we output the result to the user.
The code
First we need a router:
my_door_keeper_router:
pattern: /request/{string}
defaults: { _controller: AcmeDemoBundle:door:keeper, _format: ~ }
requirements:
_method: GET
id: "\d+"
And here is the action controller:
public function keeperAction($string)
{
if ($string == 'knock knock') {
echo "hello"; // please dont do it with echo use symfony way , i just dont have enough time
}
return;
}
Now with jQuery:
$('#input_filled_by_user').change(
function(){
$.get('/request', { string: $(this).val() } ,function(data) {
$('#input_filled_with_php').val(data);
alert('Load was performed.');
});
}
)
And that's it. Hope this makes sense (please dig deeper, I tried to explain it the dummy way, and some of the things i mentioned are half correct, but for whatever you're trying to do now you will be fine doing so).