I am currently working on a project where Moderation is the requirement.
There are multiple groups of users:
Content Creator
Content Reviewer
Admins
The business requirement is that once CREATOR has submitted items for REVIEW, they should not be able to edit it anymore.
How can we disable editing on certain items by checking if STATUS of the item is UNDER_REVIEW?
The standard way would be to use ACLs, as described in the Sonata Admin Security docs.
In summary:
Integrate with FOSUserBundle, conveniently by using SonataUserBundle (I'm sure there are other options for managing users)
Configure ACLs in Sonata
Set up your Roles as required, and then create a Custom Voter which can check the Role of the User, and the state of the object, and vote to grant access only if appropriate, something like this:
Voter
public function vote(TokenInterface $token, $object, array $attributes)
{
//...
foreach ($attributes as $attribute) {
if ($this->supportsAttribute($attribute) && $object instanceof Item) {
if $attribute == 'EDIT' && ($token->getUser()->hasRole('ROLE_CREATOR') && $object->getStatus() == 'UNDER_REVIEW')
return self::ACCESS_DENIED;
}
if (($token->getUser()->hasRole('ROLE_ADMIN') {
return self::ACCESS_GRANTED;
}
//etc etc
}
}
//...
}
Related
I have this situation:
Symfony 4.4.8, in the controller, for some users, I change some properties of an entity before displaying it:
public function viewAction(string $id)
{
$em = $this->getDoctrine()->getManager();
/** #var $offer Offer */
$offer = $em->getRepository(Offer::class)->find($id);
// For this user the payout is different, set the new payout
// (For displaying purposes only, not intended to be stored in the db)
$offer->setPayout($newPayout);
return $this->render('offers/view.html.twig', ['offer' => $offer]);
}
Then, I have a onKernelTerminate listener that updates the user language if they changed it:
public function onKernelTerminate(TerminateEvent $event)
{
$request = $event->getRequest();
if ($request->isXmlHttpRequest()) {
// Don't do this for ajax requests
return;
}
if (is_object($this->user)) {
// Check if language has changed. If so, persist the change for the next login
if ($this->user->getLang() && ($this->user->getLang() != $request->getLocale())) {
$this->user->setLang($request->getLocale());
$this->em->persist($this->user);
$this->em->flush();
}
}
}
public static function getSubscribedEvents()
{
return [
KernelEvents::TERMINATE => [['onKernelTerminate', 15]],
];
}
Now, there is something very weird happening here, if the user changes language, the offer is flushed to the db with the new payout, even if I never persisted it!
Any idea how to fix or debug this?
PS: this is happening even if I remove $this->em->persist($this->user);, I was thinking maybe it's because of some relationship between the user and the offer... but it's not the case.
I'm sure the offer is persisted because I've added a dd('beforeUpdate'); in the Offer::beforeUpdate() method and it gets printed at the bottom of the page.
alright, so by design, when you call flush on the entity manager, doctrine will commit all the changes done to managed entities to the database.
Changing values "just for display" on an entity that represents a record in database ("managed entity") is really really bad design in that case. It begs the question what the value on your entity actually means, too.
Depending on your use case, I see a few options:
create a display object/array/"dto" just for your rendering:
$display = [
'payout' => $offer->getPayout(),
// ...
];
$display['payout'] = $newPayout;
return $this->render('offers/view.html.twig', ['offer' => $display]);
or create a new non-persisted entity
use override-style rendering logic
return $this->render('offers/view.html.twig', [
'offer' => $offer,
'override' => ['payout' => $newPayout],
]);
in your template, select the override when it exists
{{ override.payout ?? offer.payout }}
add a virtual field (meaning it's not stored in a column!) to your entity, maybe call it "displayPayout" and use the content of that if it exists
I have a list view in sonata admin. I want to add a column that will allow me to click on a link to send an email. The link action will know variables from that row in the table so that it can fill in the email. I was able to add the column and can visualize a twig template. I've added the following function to the Admin Class:
public function sendEmail( \Swift_Mailer $mailer)
{
$message = (new \Swift_Message('some email'))
->setFrom('contact#example.com')
->setTo('contact#example.com')
->setBody(
$this->renderView(
'emails/response.html.twig',
array('manufacturer' => $manuvalue, 'modelnum' => $modelnumvalue, 'email' => $email)
),
'text/html');
$mailer->send($message);
}
I'm stuck on how to connect these pieces together so that when I click on the link the email is sent and includes the params from the row. I have email working on form submit in other areas of the site, but need help figuring out the way to do this manually.
As you mentioned in the comments, what you want to do is typically a Custom Action
In order to ensure that this action can not be accessed via direct request and can only be performed by admin, you could do use a template like this for your customAction :
...
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
...
//AuthorizationCheckerInterface injected as authorizationChecker
public function customAction($id){
$object = $this->admin->getSubject();
if (!$object) {
throw new NotFoundHttpException(sprintf('unable to find the object with id: %s', $id));
}
// Check access with role of your choice
$access = $this->authorizationChecker->isGranted('ROLE_ADMIN');
if($access){
//Do your action
} else {
throw $this->createAccessDeniedException("You don't have the rights to do that!");
}
}
I ended up doing a custom route and protected it with security settings that #dirk mentioned.
I'm working with symfony 2.3. How I can show the user roles without (ROLE_). I want to change the view, but in the database are intact.
When I display the roles in the view I have this
ROLE_ADMIN
ROLE_CONSULTOR
and I want
Admin
Consultor
Role Entities just need to implement the RoleInterface. You can add your own custom fields such as the names you want.
http://api.symfony.com/2.3/Symfony/Component/Security/Core/Role/RoleInterface.html
Not clear what you want to achieve, but if there are few roles, may be 2-3, (e.g. ROLE_ADMIN, ROLE_CONSULTOR, ROLE_USER), just define a method which receives the system role, returns the human readable one. May be like this:
public function convertToHumanreadable($role)
{
$return = null;
switch ($role) {
case 'ROLE_ADMIN':
$return = 'Admin';
break;
...
}
return $return;
}
Or even like this:
public function convertToHumanreadable($role)
{
$roleParts = explode('_', $role)
return ucfirst(strtolower($roleParts[1]));
}
Or you can create your own role entity by implementing the RoleInterface as #DerickF mentioned.
I have a module that creates (and updates) Drupal 7 nodes programmatically.
Since the content of the body these nodes is changed by a program at random intervals I do not want anyone, including the administrator, to be able to edit them. Is there a way way to completely "turn-off" the interface that allows a administrator to edit a node?
If it's a standard user with an administrator role you can implement hook_node_access() in your custom module:
function MYMODULE_node_access($node, $op, $account) {
$type = is_string($node) ? $node : $node->type;
if ($type == 'the_type' && $op == 'update') {
return NODE_ACCESS_DENY;
}
return NODE_ACCESS_IGNORE;
}
If it's the 'super user' (user 1) you need to get a bit more creative as a lot of access checks are bypassed for that user.
You can implement hook_menu_alter() to override the access callback for the node edit page, and provide your own instead:
function MYMODULE_menu_alter(&$items) {
$items['node/%node/edit']['access callback'] = 'MYMODULE_node_edit_form_access';
}
function MYMODULE_node_edit_form_access($node) {
$type = is_string($node) ? $node : $node->type;
if ($type == 'my_type') {
return FALSE;
}
return node_access('update', $node);
}
I like both of Clive's suggestions, but one more option is to simply disable the fields using HOOK_form_alter. This will work for the User 1 account too. I've used this recently to disable a specific field that I don't want anyone modifying.
function YOURMODULE_form_alter(&$form, &$form_state, $form_id) {
if ($form_id == 'your_form_id') {
$form['body_field']['body']['#disabled'] = TRUE;
$form['body_field']['body']['#value'] = $form['body_field']['body']['#default_value'];
}
}
Admittedly, this solution isn't ideal if you're using the built-in body field because of the teaser. But it works great if you want to disable editing of certain fields while leaving other aspects of the node editable and the page intact.
I'm not sure why you need them to be an administrator. That role, honestly, should be reserved for people with absolute control -- and even those people should not use it as their "main" account due to the potential for destroying things. Why not just make an "editor" role or something similar, and give all the permissions you need?
I've installed the module - http://drupal.org/project/formblock (Enables the presentation of node creation forms in blocks)
I've enabled it for a particular content type and then exposed that block, to show when an Organic Group node is being viewed.
What I would like to do is hide that block if the current logged in user, is not the author of the Organic Group node being viewed. i.o.w I only want the organic group author to see that block.
thanks in advance :)
You can use 'PHP block visibility settings' to achieve what you want here. Using PHP you can query the database, and check whether the logged in user is the same user that created the node in the organic group.
There is an example already on drupal.org that I have adapted (you will probably need to customise this further) -
<?php
// check og module exists
if (module_exists('og')){
// check user is logged in
global $user;
if ($user->uid) {
// check we've got a group, rights to view the group,
// and of type "group_type" - change this to whichever group you want to restrict the block to
// or remove the condition entirely
if (($group = og_get_group_context()) && node_access('view', $group) && ($group->type == 'group_type') ) {
// check current user is a team admin as they should get access
if (og_is_node_admin($group)) {
return TRUE;
}
// check to see if the current user is the node author
if (arg(0) == 'node' && is_numeric(arg(1))) {
$nid = arg(1);
$node = node_load(array('nid' => $nid));
if ($node->uid == $user->uid) {
return TRUE;
}
}
}
}
}
return FALSE;
?>