How do I HIDE the DELETE item in the LIST ACTIONS dropdown?

Tried adding a custom ACL to disallow DELETE as per

https://enricosimonetti.com/powerful-customisations-with-sugars-acl/

but this does NOT seem to work when I try it on Sugar 13.0.3

custom\Extension\modules\Contacts\Ext\Vardefs\acl.php

$dictionary['Contacts']['acls']['SugarACLDenyDelete'] = true;

custom\data\acl\SugarACLDenyDelete.php

class SugarACLDenyDelete extends SugarACLStrategy {
	// allowed user ids
    protected $user_ids_to_allow = array(
    );

    // denied actions: example was READ-ONLY, we want to deny only DELETE
    protected $denied_actions = array(
        //'edit',
        'delete',
        //'massupdate',
        //'import',
    );

    // our custom method to check permissions
    protected function _canUserWrite($context)
    {
        // retrieve user from context
        $user = $this->getCurrentUser($context);

        // allow only admin users or special users access
        if(/*$user->isAdmin() || */in_array($user->id, $this->user_ids_to_allow)) { //we DENY to ADMINS too
            return true;
        } else {
            return false;
        }
    }

    // runtime access check
    public function checkAccess($module, $view, $context)
    {
        $view = SugarACLStrategy::fixUpActionName($view);
        // if it is not a blocked action, or there is no bean, allow it
        if(!in_array($view, $this->denied_actions) || !isset($context['bean'])) {
            return true;
        }

        // can user write?
        if($this->_canUserWrite($context)) return true;

        // everyone else for everything else is denied
        return false;
    }

    // mostly for front-end access checks (cached on the application, per user)
    public function getUserAccess($module, $access_list = array(), $context = array())
    {
        // retrieve original ACL
        $acl = parent::getUserAccess($module, $access_list, $context);

        // if user can't write
        if(!$this->_canUserWrite($context)) {
            // override access, disable access where required if not admin and not special user
            foreach($acl as $access => $value) {
                if(in_array($access, $this->denied_actions)) {
                    $acl[$access] = 0;
                }
            }
        }

        // return modified acl
        return $acl;
    }
}

Can anyone point me to a way that works in v13+?

Parents Reply Children
  • I would prefer a code only method, so we can easily implement this on STAGING and PRODUCTION by having the same code in place in both and running sugar_repair script.

  • Can you give it a try with these files: 

    custom/Extension/modules/Contacts/Ext/Vardefs/noDelete.php

    <?php
    $dictionary['Contact']['acls']['SugarACLRestrictDelete'] = true;
    


    custom/data/acl/SugarACLRestrictDelete.php

    <?php
    
    if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
    
    /**
     * Custom ACL to restrict delete capabilities for Contacts module to admins only
     */
    require_once('data/SugarACLStrategy.php');
    
    class SugarACLRestrictDelete extends SugarACLStrategy
    {
        /**
         * Check access method
         *
         * @param string $module
         * @param string $action
         * @param array $context
         * @return boolean
         */
        public function checkAccess($module, $action, $context)
        {
            global $current_user;
    
            // Apply this ACL only to the Contacts module
            if ($module !== 'Contacts') {
                return true;
            }
    
            // Allow all actions for admin users
            if ($current_user->isAdmin()) {
                return true;
            }
    
            // Restrict delete action for non-admin users
            if (strtolower($action) === 'delete') {
                return false;
            }
    
            // Allow other actions by default
            return true;
        }
    }
    ?>
    

    Seems to work for me locally. 

    I hope this helps 

    Cheers, 

  • Thanks, yes that code also stops the DELETE from working but the DELETE option still appears and trying to DELETE gives the same ERROR popups.

    So I still need a custom RecordList definition without the DELETE item.

  • Hello  , 

    That is weird.

    You can check the video bellow that shows how it works on a stock installation, by default if permissions for deletion are removed the Delete button will not appear (List View, Record View ). 
    Would you mind running a QRR and clear the cache/ folder just to double check

     

    If the button still shows, perhaps you are referencing a custom button that was added to the instance.? 

    Would you mind sharing a print screen of the button that you are referring to? 

    Many thanks

    André 




  • I tried deleting the CACHE folder and doing another repair.

    I still see the DELETE option in the dropdown on the LIST page.

    I also see the DELETE option on the dropdown on the DETAIL page.

    We have a custom API which handles the DELETE by throwing an EXCEPTION (instead of just doing the delete!) which then shows a message to the user saying "don't delete, HIDE the Contact instead"

    So we're not worried about that DELETE option still appearing. though it sounds like that should not be showing either.

    With the custom RECORDLIST without the DELETE button at all we get

    so no DELETE option here

  • Hello  , 

    I still believe that the buttons shouldn't be showing up after restricting the ACL, especially given this parameter in the button definition:

    'acl_action' => 'delete',

    I'm not sure why or how it's appearing in the screenshot you shared—are you using an admin user or perhaps a user with admin access to Contacts?

    If you'd like, feel free to send me a message, and I'd be happy to help you figure out why this might not be working as expected.

    By the way, just a quick tip: if you restrict DELETE via ACL, you don’t need to add a custom exception. Sugar will automatically prevent deletions through the API endpoint as well.

    Hope this helps!

    Cheers,
    André