How in SonataAdminBundle
get the current admin class without using AdminPool?
Now I'm trying to do it like this
$entityClass = get_class($entity);
$adminClass = $this->adminPool->getAdminByClass($entityClass);
But this method has a problem. If the entity is associated with several classes of the admin, an exception will be thrown.
Is there a way to find out what exactly the admin service should handle the current route?
Thanks!
If you have multiple admins registered for this entity's class, nothing can choose the correct one for you.
You can still get a specific admin with the method Pool::getAdminByAdminCode(string $code).
For example, an usage for you could be:
if ($entityClass === MultipleAdminRegisteredEntity::class) {
$admin = $this->adminPool->getAdminById('specific_admin_id');
} else {
$entityClass = get_class($entity);
$admin = $this->adminPool->getAdminByClass($entityClass);
}
Please pay attention to the fact that the Pool::getAdminByClass(string $class) returns an Admin and not a class string: you named your variable $adminClass which suggests you made this confusion.
Also note that there is an open issue on Github here: https://github.com/sonata-project/SonataAdminBundle/issues/3908 to determine a way to be able define default admins when there are more than one admin for an entity, so that the Pool:getAdminByClass() method doesn't throw an exception. Nobody seems to have care enough about this to implement it, feel free to contribute there if you want.
Related
On SS 4.0.3 following this guide and the official doc, I successfully created a custom permission role.
Now I would create a custom group and add the default admin to it, as action performed by default in order to maintain those user/group/role settings either if the DB being dropped. I googled around many times but I didn't found any detailed tutorial to achieve this (using the Group class, the right place to implement this logic and so on).
Could anyone show me the way?
Thanks in advance.
If you want to enforce a data structure you can use DataObject::requireDefaultRecords. See Group::requireDefaultRecords for an example of this, it runs on dev/build every time.
You will want to check whether the data you're creating exists before doing so though, to ensure you don't create the group every time.
I successfully setted up the panorama described on my question, thanks to #robbie and other sources that I combined all in one, in order to achieve my goal (I'll list them here below). I wanna share my approach to those could be face this logic in the future.
In the first place I created a brand new standard/global permission with providePermissions() in the PageController (see balbuss.com source).
Next, as #robbie suggested, I created a Group DataExtension in order to build up a new group and setted my previously created permission on it (see #Barry's solution source).
To add the default admin to this group, I had to create a new Permission DataExtension in which I assigned the group to the proper member (see #StefGuev suggestion source):
// Definizione Namespace
use SilverStripe\ORM\DataExtension;
use SilverStripe\Security\DefaultAdminService;
use SilverStripe\Security\Member;
use SilverStripe\Security\Group;
class PermessoExtension extends DataExtension
{
/**
* Metodo gestione inizializzazione records di default
* Setter
* #return void
*/
public function requireDefaultRecords()
{
parent::requireDefaultRecords();
$userAdmin = DefaultAdminService::getDefaultAdminUsername();
$admin = Member::get()->filter('Email', $userAdmin)->first();
$gruppo = Group::get()->filter('Code', 'negozianti')->first();
// Controllo gruppo
if (!$admin->inGroup($gruppo->ID)) {
$admin->Groups()->add($gruppo);
$admin->write();
}
}
}
I made this choice because during dev/build, Permission being built after Member table, so I could get the default admin without errors.
Special thanks to these sources:
balbuss.com
Bereusei's solution
Barry's solution
StefGuev suggestion
I will use a simple situation for explain :
I have "news" entity
I have "new categories" entities
In administration, I want to check if I can delete news category.
If you dont have "ROLE_SUPERADMIN", you can't ;
If news category is linked (= used in category), you can't.
When control that ?
If I use Symfony Voters :
class NewsCategoryVoter extends Voter {
....
private function canDelete(NewsCategory $newsCategory, User $user)
{
// Check ROLE and Count in NewsRepository if $newsCategory is used. I have not yet coded this.
return false;
}
I have a problem :
I can't get the reason why he can not remove. In twig and after is_granted('delete', category), idealy :
You can't delete because ...
Can you help me ?
Please, keep in mind that this situation is very simple. In my situation, I have many reasons (> 10) to reject a deletion or modification, almost always because of a relationship in database
Because the Voter is just another service, you can add whatever properties, or other classes/services you want or need to be able to store some sort of reason as to why something did, or did not happen.
public static $reason;
// in the voter, make grant/deny/abstain choices...
if ($user->hasRole('ROLE_SUPER_ADMIN')) {
self::$reason = "is super-admin";
$this->log->debug("FeatureVoter | {$user} is super-admin");
return VoterInterface::ACCESS_GRANTED;
}
// after is_granted()
echo VoterClass::$reason;
I already had logging in the voter, and so some other notification mechanism would be just as easy. Here, I've just added a static variable in the Voter, and can read it out externally. You can trivially make that an array that could be added to, (and cleared before voting started), or noting a reason why something did, or didn't happen in an external service that can be retrieved.
i was trying to setup a general service which handles common function i use very often everywhere in my project. For example if a user wants to purchase something for virtual currency there would be a function which checks, if the user has enough virtual currency in his account.
If the user doesnt have enough virtual currency I want this function to make a JSOn Response, but of cource, only controllers are allowed to response. But this means i have to check in every action I use this function, whether the purchase is valid or not.
Here is the function call in my Controller:
$purchaes= $this->get('global_functions')->payVirtualCurrency($user_id, $currency_amount);
if($change instanceof JsonResponse){
return $change;
}
And the function:
public function payVirtualCurrency($user_id, $currency_amount){
$user = $this->dm->getRepository('LoginBundle:User')->findOneById($user_id);
if($user->getVirtualCurrency() < $currency_amount){
return new JsonResponse(array('error' => $this->trans->trans('Insufficient amount of virtual Currency')));
}
return true;
}
Is there a better way to do this? I really want to avoid doing the same thing in the controller over and over again.
Thanks in advance!
Two options come to my mind, both are quite elegant solutions but both require little work:
1. Create custom exception listener
Create custom exception, let's call it InsufficientMoneyException. Then, your sevice can be as it is, but instead of returning response it throws your custom exception (in case user does not have enough money). Then, you create custom exception listener which listenes to InsufficientMoneyException custom exception and returns your desired JsonResponse.
2. Create custom annotation
You can create custom annotation and flag a controller action with this annotation. It would look something like this
/**
* #MinimumMoneyRequired("50")
*/
public function buyAction()
{
(...)
}
This option is really nice and decoupled but it require quite a lot of configuration. This is nice blog post with detailed description how to create custom annotations
This is a fairly basic question about CakePHP, but since my knowledge of this framework is rather rusty, it is making me lose a lot of time.
I have a ManyToMany relation between Guest and Present. Whenever a new Guest is created and associated with a present, I would like to mark the Present as taken. If the present is already taken, some error should arise. The reason why I am not just declaring that a Guest hasMany Presents is because in the future things may change and more than one guest could associate to a present, so I prefer to avoid a Db migration.
My Guest::add() action looks like follows. It is called with a POST with the data of a new Guest and the id of an existing Present.
public function add() {
if ($this->request->is('post')) {
$id = $this->request->data['Present']['id'];
$this->Guest->create();
$present = $this->Guest->Present->findById($id);
if ($present['Present']['taken']) {
throw new ForbiddenException();
}
if ($this->Guest->save($this->request->data)) {
if ($this->Guest->Present->saveField('taken', true)) {
// Give the guest a uuid and proceed with a welcome message
$this->Guest->read();
$this->set('uuid', $this->Guest->data['Guest']['uuid']);
}
}
}
else {
throw new ForbiddenException();
}
}
What happens is that a new Guest is created (correct) and associated with the given present (correct) but when I save the taken field a new present is created instead of modifying the given one.
What is the correct way to proceed to update the current Present?
If it is of any help, I am using CakePHP 2.0
For obtaining the model data by the primary key it's better to use theIn addition read method:
$present = $this->Guest->Present->read(null, $id);
The read method sets the model's id attribute so that further calls to other methods affect the same data record, rather than creating a new one. This should solve the problem you are having.
Model callbacks tend to be better suited for these situations. You could add a beforeSave callback to the Guest class to checks if the present is already taken, and not allow the creation if it is. This way the model logic is left in the model layer and you don't need to do any extra work e.g. if the constraint has to be enforced also when existing Guests are saved, or created from different controllers or actions.
It sounds like the ID of the model you are trying to save is losing scope. You should be able to resolve your issue by updating your code:
...
if ($this->Guest->save($this->request->data)) {
$this->Guest->Present->id = $id;
if ($this->Guest->Present->saveField('taken', true)) {
...
I can't get my Zend_Navigation to work properly,
When logging in user with AUth/Doctrine, I am pulling out the roles assigned to the user (usually it's a few of them) from a Many-to-many table,
Then in the bootstrap.php on line:
$view->navigation($navContainer)->setAcl($this->_acl)->setRole($this->_role);
I get error:
'$role must be a string, null, or an instance of Zend_Acl_Role_Interface; array given'
However if I loop through the roles with foreach - the previous roles are being overwritten by the following ones and I get the nav only for last role,
Does anyone have any logical solution for this ?
Really appreciate,
Adam
I had the same problem but approached the solution from a slightly different angle. Instead of modifying the Zend_Navigation object to accept two or more roles, I extended Zend_Acl and modified the isAllowed() method to check against all those roles. The Zend_Navigation objects use the isAllowed() method, so overriding this solved the issue.
My_Acl.php
<pre><code>
class My_Acl extends Zend_Acl
{
public function isAllowed($role = null, $resource = null, $privilege = null)
{
// Get all the roles to check against
$userRoles = Zend_Registry::get('aclUserRoles');
$isAllowed = false;
// Loop through them one by one and check if they're allowed
foreach ($userRoles as $role)
{
// Using the actual ACL isAllowed method here
if (parent::isAllowed($role->code, $resource))
{
$isAllowed = true;
}
}
return $isAllowed;
}
}
</code></pre>
Then, instead of creating an instance of Zend_Acl, use My_Acl, pass that to your navigation object and it should work.
You should really never, ever override isAllowed(), and yes there is a solution. Create a class that implements Zend_Acl_Role_Interface and if memory serves it requires defining a single method getRole(), this could, in fact, be your model that you use to authenticate a user against and allow that class to handle determining the role. A user should only have a single role. If access to the resource should be granted to users of multiple roles but only under certain conditions, then you should use an assertion, thats why they are there.