Disable action for specific entries - symfony

In Sonata Admin, in one of my section, I would like to disable the delete action for 4 specific entries/lines.
I know I can do it for the entire listing with configureRoutes() but couldn't find anything for specific entries.
I tried the following but getSubject() is always empty.
protected function configureRoutes(RouteCollection $collection)
{
$product = $this->getSubject();
if ($product && $product->getIsBase())
{
$collection->clearExcept(array('list', 'edit'));
}
}
Thanks

You have to override the isGranted method in your admin class:
public function isGranted($name, $object = null)
{
if (in_array($name, array('LIST', 'EDIT')) && $object && $object->getIsBase()) {
return false;
}
return parent::isGranted($name, $object);
}

Related

Symfony - ArrayCollection - Update or Create

Symfony 4.3
Goal : import a CSV listing employees/company with doctrine and save it into the DB.
I have the Entity company :
<?php
// src/Entity/Company.php
class Company
{
/**
* #ORM\OneToMany(targetEntity="App\Entity\Employee", mappedBy="company")
*/
private $employees;
public function __construct()
{
$this->employees = new ArrayCollection();
}
public function getEmployees()
{
return $this->employees;
}
and the Employee entity :
<?php
// src/Entity/Employee.php
class Employee
{
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Company", inversedBy="employees")
*/
private $company;
public function getCompany()
{
return $this->company;
}
public function setCompany(?Company $company)
{
$this->company = $company;
return $this;
}
Below is my loop for each line of the CSV being imported :
<?php
// $csv = array made from the CSV file
// e.g. $csv[0]['employee_name'] = "John Doe"
// e.g. $csv[0]['employee_mail'] = "john.doe#bmw.com"
// e.g. $csv[0]['employee_company_name'] = "BMW"
// e.g. $csv[0]['employee_company_id'] = 77
foreach($csv as $key => $value)
{
if($company = $this->em->getRepository(Company::class)->find($value['employee_company_id']))
{
// if the employee doest not exist, create it
// IN MY TESTS, HERE IS MY PROBLEM
// DON'T KNOW HOW TO LOOP INSIDE THE EMPLOYEES LIST USING the MAIL
if ($company->getEmployees()->contains($value['employee_mail']))
{
// This employee for this company exists, let's update it
}
else
{
// This employee for this company does not exist, let's create it
}
}
else
{
// Create the company
}
I don't know how to loop inside the company employees list, in order to decide if I have to edit (employee already exists) or create a new employee. Maybe I should not use the ArrayCollection::contains method ?
As Employees is an Doctrine ArrayCollection, you can use the exists method on it. This method accepts a closure as argument which loops over all elements in the collection and returns true when the condition matches.
if ($company->getEmployees()->exists(function ($key, Employee $employee) use ($value) {
return $employee->getEmail() === $value['employee_mail'];
})) {
// Employee exists, update
} else {
// Employee does not exist
}
Alternatively, if you want to create/update the record right away, you can do the following. This returns the Employee if it exists, or create a new Employee object if it doesn't
$employee = $company
->getEmployees()
->filter(function (Employee $employee) use ($value) {
return $employee->getEmail() === $value['employee_mail'];
})
->first() ?? new Employee();
the naive approach would be to just look in the employee repository.
$employee = $em->getRepository(Employee::class)->findOneBy([
'company' => $company,
'email' => $value['employee_mail'],
]);
if($employee) {
// exists -> update
} else {
// create
}
Depending on the company-employee-ratio, it might be better to cycle through the company's employees instead:
$employee = null;
foreach($company->getEmployees() as $_employee) {
if($_employee->getEmail() == $value['employee_mail']) {
$employee = $_employee;
break; // short circuit the loop
}
}
// rest is the same
If there are is very large number of employees in your csv and/or the database, it might be even better to skip the ORM alltogether and go straight to the database instead. Otherwise, you might want to clear the entity manager once in a while, if you have a huge database / csv.
You should store first your list of employees :
$employees = $company->getEmployees()
then you loop inside $employees :
foreach($employees as $employee ) //supposedly you have 'email' property
{
if ($employee->getEmail() == $value['employee_mail'])
{
//your code here
}
}
don't forget to add this :
* #ORM\OneToMany(targetEntity="App\Entity\Employee", mappedBy="company", fetch="EAGER")
Update :
$qb = $repository->createQueryBuilder('a')
->where(':employee_mail OF a.emplyee_email')
->setParameter('employee_mail ', $value['employee_mail']);
->andWhere(':comapanyId MEMBER OF a.company_id');
->setParameter('comapanyId ', $value['employee_company_id']);
->getQuery()
->execute()
this will return null if the employee does not exist in the company, you should implement this as you need in your code.

Adding a button to the CMS in SilverStripe

How do I add a button to the backend of the CMS that fires an action? I can display the button where I want using:
public function getCMSFields()
{
$fields = parent::getCMSFields();
$fields->addFieldsToTab("Root.ButtonTest", array(
FormAction::create('doAction', 'Action button')
)
);
return $fields;
}
public function doAction()
{
//Do something
}
However the button added does nothing when clicked.
I've seen one example of how to put a button on the main action bar (next to save/publish) but that's not what I'm trying to do.
Looking at the only page of documentation I can find, do I need to do something within:
public function getCMSActions()
{
$actions = parent::getCMSActions();
//Something here?
}
It isn't very clear how to create the action that the button calls.
You'll have to extend/decorate LeftAndMain with your own extension and the action you want to call. Here's an example:
<?php
class MyExtension extends LeftAndMainExtension
{
private static $allowed_actions = array(
'doAction'
);
public function doAction($data, $form){
$className = $this->owner->stat('tree_class');
$SQL_id = Convert::raw2sql($data['ID']);
$record = DataObject::get_by_id($className, $SQL_id);
if(!$record || !$record->ID){
throw new SS_HTTPResponse_Exception(
"Bad record ID #" . (int)$data['ID'], 404);
}
// at this point you have a $record,
// which is your page you can work with!
// this generates a message that will show up in the CMS
$this->owner->response->addHeader(
'X-Status',
rawurlencode('Success message!')
);
return $this->owner->getResponseNegotiator()
->respond($this->owner->request);
}
}
Once you have written an extension like this, you'll have to apply it to LeftAndMain by adding the following to your mysite/_config/config.yml:
LeftAndMain:
extensions:
- MyExtension
That's it. Your doAction button should now actually do something!
Not sure if this is helpful, but here's how you can add action-buttons to a ModelAdmin.
(does reload the page)
...in the admin class:
public function getEditForm($id = null, $fields = null)
{
$form = parent::getEditForm($id, $fields);
$form
->Fields()
->fieldByName($this->sanitiseClassName($this->modelClass))
->getConfig()
->getComponentByType('GridFieldDetailForm')
->setItemRequestClass('MyGridFieldDetailForm_ItemRequest');
return $form;
}
MyGridFieldDetailForm_ItemRequest.php
class MyGridFieldDetailForm_ItemRequest extends GridFieldDetailForm_ItemRequest
{
function ItemEditForm()
{
$form = parent::ItemEditForm();
$formActions = $form->Actions();
$button = FormAction::create('myAction');
$button->setTitle('button label');
$button->addExtraClass('ss-ui-action-constructive');
$formActions->push($button);
$form->setActions($formActions);
return $form;
}
public function myAction(){ //do things }
}

Twig extension for sorting Doctrine ArrayCollection

I'm trying to write a Twig filter to be able to sort a Doctrine ArrayCollection, but the returned array is not sorted :( Can you please help me to fix this:
class SortExtension extends \Twig_Extension
{
public function getFilters()
{
return array(
new \Twig_SimpleFilter('sortby', array($this, 'sortByFilter')),
);
}
public function sortbyname( $a, $b )
{
if ($a->getName() === $b->getName()) {
return 0;
}
if ( $a->getName() < $b->getName() ) {
return 1;
}
return -1;
}
public function sortByFilter($collection)
{
$iterator = $collection->getIterator();
$iterator->uasort(array($this, 'sortbyname'));
return $collection;
}
I'm not quite sure if the returned collection in sortByFilter is changed.
This is because you are getting the iterator and sorting it.
The method getIterator creates a new ArrayIterator which makes a copy of the array.
Then, you are returning the collection, which is not sorted.
Here is a little sample of what happens.
You just have to replace
return $collection;
By
return $iterator;

Is there any way to determine current action (create or edit) in Sonata\AdminBundle\Admin\Admin::configureFormFields()?

I'd like to create different fields configuration for create and edit actions in Sonata Admin Bundle.
Is there any way to determine it except checking $this->getSubject()->getId() in Sonata\AdminBundle\Admin\Admin::configureFormFields()?
You can also do this:
protected function configureFormFields(FormMapper $formMapper) {
if ($this->isCurrentRoute('create')) {
// CREATE
}
else {
// EDIT
}
}
with:
if($this->getRequest()->get($this->getIdParameter()) == null){
// create
} else {
// edit
}
I use this :
$creationMode = ($this->id($this->getSubject()))?(false):(true);
if ($creationMode){
//Ok
}
In sonata admin from version 3.x
if ($this->isCurrentRoute('create')) {
// CREATE
}
else {
// EDIT
}
In sonata admin before version 3.x use:
$subject = $this->getSubject();
if ($subject->isNew()) {
// CREATE
}
else {
// EDIT
}
You can also do this:
protected function configureFormFields(FormMapper $formMapper) {
if ($this->isCurrentRoute('create')) {
// CREATE
}
else {
// EDIT
}
}
public function getAction(): ?string
{
if (! $this->getRequest()) {
return null;
}
$pathArray = \explode('/', $this->request->getPathInfo());
return \end($pathArray);
}

Setting body classes in Zend

I am working on a Zend 1.12 application and trying to get style classes assigned to layout body tag.
I found this sample Bootstrap.php file that seems to handle the task:
https://gist.github.com/fideloper/1302688
It seems to integrate nicely, but the body's class always comes out blank.
Can someone please point me in the right direction here on how to get classes assigned to body?
Thanks.
Made a small change to the snippet I referenced above.
class AppName_Helper_BodyClass extends Zend_View_Helper_Placeholder_Container_Standalone {
private $_classes = array();
public function __construct($classes = null) {
if(is_array($classes)) {
$this->addClass($classes);
}
}
public function addClass($class) {
if(is_array($class)) {
foreach($class as $k => $c) {
if(is_string($c)) {
if(is_string($k)) {
$this->addClass($k.'-'.$c); //recursion
} else {
$this->addClass($c);
}
} else {
throw new Zend_Exception('Class must be a string - is type: '.gettype($c));
}
}
return $this;
}
if(is_string($class)) {
$this->_classes[] = $class;
return $this;
} else {
throw new Zend_Exception('Class must be a string - is type: '.gettype($class));
}
return $this;
}
public function removeClass($class) {
$key = array_search($class, $this->_classes);
if($key !== false) {
unset($this->_classes[$key]);
}
return $this;
}
public function bodyClass() {
return $this;
}
public function toString() {
return implode(' ', $this->_classes);
}
}
This snippet goes into my layout:
$uri = Zend_Controller_Front::getInstance()->getRequest()->getParams();
$this->bodyClass()->addClass($uri);
The results is such (for module - "default", controller - "auth", action - "signin"):
<body class="controller-auth action-signin module-default">
I am grabbing current request's parameters and mapping them to the body class. Hope this helps someone dealing with this.
From the look of that helper you need to call one of it's methods to add a class. From one of your controllers:
$this->view->bodyClass()->addClass('something');
is that how you are using it?

Resources