by eggsurplus

Control what your users can access and save time, money, and frustrations. Lock down sensitive data in SuiteCRM to specific groups or teams. Supports unlimited assigned users, unlimited group assignments to records, custom layouts for each group, login/sudo capabilities and much more.

Free 30 day trial
Try it Now

#773 - Is it possible to dynamically remove ability to edit sets of records based on a value in the record itself?

Closed General Question created by Armin 6 years ago

Current Environment:

  • SuiteCRM v7.8.2
  • Apache 2.4
  • PHP 5.6
  • MariaDB 10.2
  • Ubuntu 16.04

I have a custom Module in SuiteCRM that has another custom Module as a relationship (One to Many). In the UI it is displayed as a sub-panel. In this subpanel there are obviously rows and columns, and the last column is the EDIT button. I'm aware that this layout can be changed using a number of methods, such as within the Studio, but those changes are not dynamic based on a value of a record.

I have a field in my parent Module that can be one of 4 values. Let's say it's 1, 2, 3, and 4. If the parent Module that is being displayed in detail view has a value of 1, for example, then all the records in its child module should not be able to be edited. If the value is 2, 3, or 4, then the edit button should appear in the subpanel for each record.

Is this possible with SecuritySuite?

Please let me know if I can elaborate for further understanding.

  1. eggsurplus member avatar

    eggsurplus Provider Affiliate

    6 years ago

    Hello,

    SecuritySuite wouldn't be involved here. What I would suggest is altering the view.edit.php logic of the custom module for the subpanel module. Then pull the data from the parent record to see if the value is 1. If so, redirect the user to the detail view or handle however you would like. Using a similar concept it may be possible to remove the Edit button dynamically, but it's best to first get the view.edit.php portion out of the way as well as the save() function in the custom module bean to ensure no one bypasses and attempts to save with that condition is true.

    Hope this helps! -Jason

    • Armin member avatar

      Armin

      6 years ago

      Jason, Thanks so much for the quick reply! I'm not so sure I explained the situation accurately. You are probably still right in assessing that SecuritySuite might not be involved. I'll give a better description.

      Parent Module - has a field called "total", and values for "total" can only be either 1, 2, 3, or 4. Child Module - has a many-to-one relationship with the parent module, meaning if I look at the detail view of the Parent Module, I will see a subpanel with many records of the Child Module.

      When looking at the detail view of a Parent Module record, and if that Parent Module record's "total" value is 1, then all the Child Module records in the subpanel should not have an EDIT button at the end of each row.

    • eggsurplus member avatar

      eggsurplus Provider Affiliate

      6 years ago

      That's exactly how I understood it. How I tackle this requirement is to first ensure that an actual edit and save cannot happen if the parent module total = 1. That requires editing the child module bean's save() to check for that parent module total value to prevent a save. Then to prevent an edit alter the view.edit.php view in the child module to check for that total value then redirect to the detail view if total = 1.

      With those mechanisms in place the next step is to remove the edit button. This I don't have a solution for off of the top of my head, but where there is a will there is a way with SuiteCRM. Could simply be a js solution which would be acceptable if you have the safety mechanisms above done.

    • Armin member avatar

      Armin

      6 years ago

      Thank you so much for your valuable insight, Jason. I will tackle this issue using your approach which makes total sense to me. I will follow up with you as soon as I figure things out more completely. Thank you!

    • eggsurplus member avatar

      eggsurplus Provider Affiliate

      6 years ago

      You're welcome! I'm looking forward to hearing what you came up with. I'll close this case out, but feel free to follow up here.

  2. Armin member avatar

    Armin

    6 years ago

    Hi eggsurplus,

    Can you provide some additional direction on your suggestion?

    That's exactly how I understood it. How I tackle this requirement is to first ensure that an actual edit and save cannot happen if the parent module total = 1. That requires editing the child module bean's save() to check for that parent module total value to prevent a save. Then to prevent an edit alter the view.edit.php view in the child module to check for that total value then redirect to the detail view if total = 1.

    I'm currently trying to find the code responsible for populating the list view. There must be some sort of loop somewhere. I also found that it may be possible to hook into this using logic hooks process_record event.

    Can you provide some guidance?

    • eggsurplus member avatar

      eggsurplus Provider Affiliate

      6 years ago

      For the list view use the get_list_view_data() function in your bean class. Only the data shown in your list view will be included here. However, you can add query only fields that don't display on your list view, but will return in the data passed to that function by adding the field in your listviewdefs.php similar to:

        'MY_CUSTOM_FIELD' => 
        array (
          'width' => '0',
          'default' => true,
          'sortable' => false,
          'label' => '',
          'customCode' => ' ',
        ),
      

      Or if this is for your subpanel add the field to your subpanel def similar to:

              'my_custom_field'=>array(
                  'name'=>'my_custom_field',
                  'usage' => 'query_only',
              ),
      

      Or just the ID passed to the function and retrieve the bean, but that will often require another db query behind the scenes.

      What you can do in get_list_view_data():

          function get_list_view_data(){
              global $db, $system_config, $current_user;
              $temp_array = $this->get_list_view_array();
      
              if($temp_array['TOTAL'] == '1') {
                  $temp_array['SOME_OTHER_FIELD'] = '{removed}';
              }
              return $temp_array;
          }
      

      Hope this helps.

    • Armin member avatar

      Armin

      6 years ago

      I honestly have no idea which files to place this code in.

    • eggsurplus member avatar

      eggsurplus Provider Affiliate

      6 years ago

      The get_list_view_data() function is part of SugarBean. Your custom module will have a class that extends this. The function would go in that class.

      The query_only thing above it goes into your /modules/YOURMODULE/metadata/subpanels/default.php or whichever subpanel layout you wish. This is only to get that data into your get_list_view_data() function.

  3. Armin member avatar

    Armin

    6 years ago

    I think the issue I'm running into is that the EDIT button is not a field in the module's database, it's part of the UI alone. I can find some logic that modifies each record as it is pulled from the database, but I cannot see where the code for the EDIT button is stored, and furthermore I'm not sure how to differentiate the List View for the module itself, or the List View for the Module when it's in a Subpanel. There seems to be some code that changes both, and some code that changes only one. I'm still not entirely sure what to do for this issue.

    • eggsurplus member avatar

      eggsurplus Provider Affiliate

      6 years ago

      This is why it's important to make sure that editing isn't possible even if they click on the Edit button. Once that is in place then you can look at using javascript to remove the Edit buttons or start working backwards through the code to find where in /include/ListView or /include/SubPanel the Edit button gets added and how to override that functionality.

  4. Armin member avatar

    Armin

    6 years ago

    I think the best way to do this is in addition to your suggestions by removing the ability to edit by modifying view.edit.list (among other files), is to have a subpanel list view .tpl file, and put templating logic in there that removes the edit button row-by-row if a field in that row has a specific value. Although, I'm not sure how to do that.

  5. Armin member avatar

    Armin

    6 years ago

    Hi eggsurplus,

    I am almost there! There's been a slight change in the requirements for this enhancement. There is no longer a need to pull data from the related ParentModule. The field value to inspect is a part of the ChildModule itself.

    Here's how I've got it to work so far:

    <?php
    
    // File Location: custom/modules/ChildModule/views/view.edit.php
    
    require_once(&#039;include/MVC/View/views/view.edit.php&#039;);
    
    class ChildModuleViewEdit extends ViewEdit {
    
        function __construct(){
            parent::__construct();
        }
    
        function display(){
          if ($this->bean->my_field > 1) {
            $queryParams = array(
                &#039;module&#039; => &#039;ChildModule&#039;,
              &#039;return_module&#039; => &#039;ChildModule&#039;,
                &#039;action&#039; => &#039;DetailView&#039;,
              &#039;record&#039; => $this->bean->id
            );
            SugarApplication::redirect(&#039;index.php?&#039; . http_build_query($queryParams));
          }
          else {
            parent::display();
          }
        }
    }
    
    <?php
    
    // File Location: custom/modules/ChildModule/logic_hooks.php
    
        $hook_version = 1;
        $hook_array = Array();
    
        $hook_array[&#039;process_record&#039;] = Array();
        $hook_array[&#039;process_record&#039;][] = Array(
            //Processing index. For sorting the array.
            1,
    
            //Label. A string value to identify the hook.
            &#039;process_record example&#039;,
    
            //The PHP file where your class is located.
            &#039;custom/modules/ChildModule/logic_hooks_class.php&#039;,
    
            //The class the method is in.
            &#039;logic_hooks_class&#039;,
    
            //The method to call.
            &#039;process_record_method&#039;
        );
    
        $hook_array[&#039;before_delete&#039;] = Array();
        $hook_array[&#039;before_delete&#039;][] = Array(
            //Processing index. For sorting the array.
            1,
    
            //Label. A string value to identify the hook.
            &#039;before_delete example&#039;,
    
            //The PHP file where your class is located.
            &#039;custom/modules/ChildModule/logic_hooks_class.php&#039;,
    
            //The class the method is in.
            &#039;logic_hooks_class&#039;,
    
            //The method to call.
            &#039;before_delete_method&#039;
        );
    
    <?php
    
        if (!defined(&#039;sugarEntry&#039;) || !sugarEntry) die(&#039;Not A Valid Entry Point&#039;);
    
        class logic_hooks_class
        {
          function process_record_method($bean, $event, $arguments)
          {
            if ($bean->my_field > 1) {
              // inject javascript code here to remove edit/delete/checkbox buttons on page
              print &#039;
              <script type="text/javascript">
              $(document).ready(function() {
                $(":checkbox[value=&#039;. $bean->id .&#039;]").remove();
                $(".edit-link[id=edit-&#039;. $bean->id .&#039;]").remove();
                $("ul#&#039;. $bean->id .&#039;]").remove();
              });
              </script>
              &#039;;
            }
          }
    
          function before_delete_method($bean, $event, $arguments)
          {
            if ($bean->my_field > 1) {
              $queryParams = array(
                &#039;module&#039; => &#039;ChildModule&#039;,
                &#039;action&#039; => &#039;DetailView&#039;,
                &#039;record&#039; => $bean->id,
              );
              SugarApplication::redirect(&#039;index.php?&#039; . http_build_query($queryParams));
            }
          }
    
        }
    

    The first block of code is guided by your direction. It is a custom view.edit.php file that detects a specific field value for the record it is about to display, and if the value condition is met, redirect the user to that records detail view.

    The next two blocks go together, they are logic hooks. The process_record_method injects jQuery to remove edit/delete elements from the UI. And the before_delete_method redirects a user to the record's detail view if they try to delete the record. This, however, has some unexpected results if the record is a part of a bulk-change using checkboxes. But that has been circumvented because the checkboxes are removed from the list-view.

    I have one more issue to figure out and that is how to inject javascript for the subpanel when ChildModule's list-view is populated there. Can you help me out in this regard?

    • eggsurplus member avatar

      eggsurplus Provider Affiliate

      6 years ago

      Good job making it this far! It's painful now, but it'll feel easier to do down the road.

      For injecting javascript I suggest just echoing it out in your view.detail.php for the parent module and do an on ready. I'm not able to help with the actual js for your specific needs, but this will get it out on the page at least.

      Alternatively, use an after_ui_frame global hook, check for the current module, and if it is the parent module and detail view then output the js, but that may have problems in ajaxui mode.

    • Armin member avatar

      Armin

      6 years ago

      That's a really good idea to use the ParentModule's detail-view file to inject the javascript that handles the subpanel UI. I hadn't considered that option.

      Ideally I'd like to keep all the code/logic within the ChildModule directory. There's got to be a way to handle the javascript injection directly into the subpanel view as it loops through the records.

    • Armin member avatar

      Armin

      6 years ago

      Hi eggsurplus,

      I greatly appreciate all of your time and guidance in this effort! It has helped me immensely to figure out how to implement these changes in an upgrade-safe manner.

      You were correct and I am able to inject jQuery code to the ParentModule detail view which handles modifying the UI of its subpanel for the ChildModule. The only issue I have now is intelligently detecting when the EDIT button appears as a user navigates through list pages within the subpanel using left/right arrows within the subpanel.

      For future reference, it looks like the subpanel list is populated with javascript, and it is populated using this file: include/SubPanel/SubPanelTiles.js. I will need to add some jQuery to detect when new EDIT buttons appear, and then remove them immediately from the UI. I will report back here when that is done with the code. Here is the code I have so far:

      <?php
      
      // File Location: custom/modules/ParentModule/views/view.detail.php
      
      if(!defined(&#039;sugarEntry&#039;) || !sugarEntry) die(&#039;Not A Valid Entry Point&#039;);
      
      require_once(&#039;include/MVC/View/views/view.detail.php&#039;);
      
      class ParentModuleViewDetail extends ViewDetail {
      
          function __construct(){
              parent::__construct();
          }
      
        function _displaySubPanels(){
          global $db;
          require_once (&#039;include/SubPanel/SubPanelTiles.php&#039;);
          $subpanel = new SubPanelTiles($this->bean, $this->module);
      
          // disable inline edit if criteria is met
          if ($this->bean->my_field > 1) {
            print &#039;
            <script type="text/javascript">
            $(document).ready(function() {
              $("table.subpanel-table tbody tr td ul.clickMenu").remove();
            });
            </script>
            &#039;;
          }
      
          echo $subpanel->display();
        }
      
      }
      

      This code works for the initial list that is populated in the subpanel, but when a user clicks the right/left arrow buttons within the subpanel, the EDIT buttons re-appear for the new populated list. I will post my code as soon as I get it working to remove all EDIT buttons from each row within the subpanel.

      Thanks again for all your help eggsurplus. Your insight has proven invaluable.

    • Armin member avatar

      Armin

      6 years ago

      I got it to work by locating a jQuery plugin that detects when a specific DOM element changes the contents within it, whatever it may be. It's called initialize.js located here: https://github.com/pie6k/jquery.initialize I included the initialize.js file before my own jQuery.

      I wrapped the logic to remove the EDIT button within the initialize function and it works great!! Here's the code below:

      <?php
      if(!defined(&#039;sugarEntry&#039;) || !sugarEntry) die(&#039;Not A Valid Entry Point&#039;);
      
      // File Location: custom/modules/ParentModule/views/view.detail.php
      
      require_once(&#039;include/MVC/View/views/view.detail.php&#039;);
      
      class ParentModuleViewDetail extends ViewDetail {
      
          function __construct(){
              parent::__construct();
          }
      
        function _displaySubPanels(){
          global $db;
          require_once (&#039;include/SubPanel/SubPanelTiles.php&#039;);
          $subpanel = new SubPanelTiles($this->bean, $this->module);
      
          // disable inline edit if criteria is met
          if ($this->bean->my_field > 1) {
            // clickMenu  SugarActionMenu
            print &#039;
            <script src="custom/modules/ParentModule/js/initialize.js"></script>
            <script type="text/javascript">
            $(document).ready(function() {
              $.initialize("table.subpanel-table tbody", function() {
                $("table.subpanel-table tbody tr td ul.clickMenu").remove();
              });
            });
            </script>
            &#039;;
          }
      
          echo $subpanel->display();
        }
      
      }
      

      The only caveat so far is that this jQuery also removes the CREATE button within the subpanel, which is not what I want to do. But the only thing I need to do to fix that is change the jQuery selector table.subpanel-table tbody tr td ul.clickMenu to either more intelligently target the EDIT buttons only, or to remove the CREATE button from the selector. Other than that small change, the code works perfect!

      I will consider adding more jQuery on other pages to handle the "Recently viewed" EDIT buttons that sometimes appear in the primary navigation dropdown menus.

      Thanks again eggsurplus!

    • eggsurplus member avatar

      eggsurplus Provider Affiliate

      6 years ago

      Fantastic work!

This case is public. Please leave out any sensitive information such as URLs, passwords, etc.
Saving Comment Saving Comment...
Rating
  • "Thank you so much. Highly recommend the module to work in teams."

    Read More Reviews