The classes responsible for displaying the Detail View and SearchForm also extend and use the EditView class. The ViewEdit, ViewDetail and ViewSidequickcreate classes use the EditView class to process and display their contents. Even the file that renders the quick create form display (SubpanelQuickCreate.php) uses the EditView class. DetailView (in DetailView2.php) and SearchForm (in SearchForm2.php) extend the EditView class while SubpanelQuickCreate.php uses an instance of the EditView class. The following diagram highlights these relationships.
The following diagram highlights the EditView class's main responsibilities and their relationships with other classes in the system. We will use the example of a DetailView request although the sequence will be similar for other views that use the EditView class.


One thing to note is the EditView class's interaction with the TemplateHandler class. The TemplateHandler class is responsible for generating a Smarty template in the cache/modules/<module> directory. For example, for the Accounts module, the TemplateHandler will create the Smarty file, cache/modules/Accounts/DetailView.tpl, based on the Metadata file definition and other supplementary information from the EditView class. The TemplateHandler class actually uses Smarty itself to generate the resulting template that is placed in the aforementioned cache directory.


Some of the modules that are available in the SugarCRM application also extend the ViewDetail class. One example of this is the DetailView for the Projects module. As mentioned in the MVC section, it is possible to extend the view classes by placing a file in the modules/<module>/views directory. In this case, a view.detail.php file exists in the modules/Projects/views folder. This may serve as a useful example in studying how to extend a view and apply additional field/layout settings not provided by the EditView class.
The following diagram shows the files involved with the DetailView example in more detail:
A high level processing summary of the components for DetailViews follows:
The MVC framework receives a request to process the DetaiView.php (A) action for a module. For example, a record is selected from the list view shown on the browser with URL:


At this point the new MVC framework checks to see if there is a DetailView.php (A2) file in the modules/Opportunity directory that will override the default DetailView.php implementation. The presence of a DetailView.php file will trigger the "classic" MVC view. If there is no DetailView.php (A2) file in the directory, the MVC will also check if you have defined a custom view to handle the DetailView rendering in MVC (that is, checks if there is a file modules/Opportunity/views/view.detail.php). See the documentation for the MVC architecture for more information. Finally, if neither the DetailView.php (A2) nor the view.detail.php exists, then the MVC will invoke include/DetailView/DetailView.php (A).


The MVC framework (see views.detail.php in include/MVC/View/views folder) creates an instance of the generic DetailView (A)
// Call DetailView2 constructor
$dv = new DetailView2();
// Assign by reference the Sugar_Smarty object created from MVC
// We have to explicitly assign by reference to back support PHP 4.x
$dv->ss =& $this->ss;
// Call the setup function
$dv->setup($this->module, $this->bean, $metadataFile, 'include/DetailView/DetailView.tpl');
// Process this view
$dv->process();
// Return contents to the buffer
echo $dv->display();


echo $dv->display();
When the setup method is invoked, a TemplateHandler instance (D) is created. A check is performed to determine which detailviewdefs.php metadata file to used in creating the resulting DetailView. The first check is performed to see if a metadata file was passed in as a parameter. The second check is performed against the custom/studio/modules/[Module] directory to see if a metadata file exists. For the final option, the DetailView constructor will use the module's default detailviewdefs.php metadata file located under the modules/[Module]/metadata directory. If there is no detailviewdefs.php file in the modules/[Module]/metadata directory, but a DetailView.html exists, then a "best guess" version is created using the metadata parser file in include/SugarFields/Parsers/DetailViewMetaParser.php (not shown in diagram).


The TemplateHandler also handles creating the quick search (Ajax code to do look ahead typing) as well as generating the JavaScript validation rules for the module. Both the quick search and JavaScript code should remain static based on the definitions of the current definition of the metadata file. When fields are added or removed from the file through Studio, this template and the resulting updated quick search and JavaScript code will be rebuilt.
It should be noted that the generic DetailView (A) defaults to using the generic DetailView.tpl smarty template file (F). This may also be overridden through the constructor parameters. The generic DetailView (A) constructor also retrieves the record according to the record id and populates the $focus bean variable.
The process() method is invoked on the generic DetailView.php instance:
function process()
{
    //Format fields first
    if($this->formatFields)
    {


//Format fields first
    if($this->formatFields)
    {
        $this->focus->format_all_fields();
    }
    parent::process();
}
This, in turn, calls the EditView->process() method since DetailView extends from EditView. The EditView->process() method will eventually call the EditView->render() method to calculate the width spacing for the DetailView labels and values. The number of columns and the percentage of width to allocate to each column may be defined in the metadata file. The actual values are rounded as a total percentage of 100%. For example, given the templateMeta section's maxColumns and widths values:
'templateMeta' => array(
    'maxColumns' => '2',
    'widths' => array(
        array(
            'label' => '10',
            'field' => '30'


'label' => '10',
            'field' => '30'
        ),
        array(
            'label' => '10',
            'field' => '30'
        )
    ),
),
We can see that the labels and fields are mapped as a 1-to-3 ratio. The sum of the widths only equals a total of 80 (10 + 30 x 2) so the actual resulting values written to the Smarty template will be at a percentage ratio of 12.5-to-37.5. The resulting fields defined in the metadata file will be rendered as a table with the column widths as defined:
The actual metadata layout will allow for variable column lengths throughout the displayed table. For example, the metadata portion defined as:
'panels' => array(
    'default' => array(
        array(
            'name',
            array(


array(
            'name',
            array(
                'name' => 'amount',
                'label' => '{$MOD.LBL_AMOUNT} ({$CURRENCY})',
            ),
        ),
        array(
            'account_name',
        ),
        array(
            '',
            'opportunity_type',
        )
    )
)
This specifies a default panel under the panels section with three rows. The first row has two fields (name and amount). The amount field has some special formatting using the label override option. The second row contains the account_name field and the third row contains the opportunity_type column.


Next, the process() method populates the $fieldDefs array variable with the vardefs.php file (G) definition and the $focus bean's value. This is done by calling the toArray () method on the $focus bean instance and combining these values with the field definition specified in the vardefs.php file (G).
The display() method is then invoked on the generic DetailView instance for the final step. When the display() method is invoked, variables to the DetailView.tpl Smarty template are assigned and the module's HTML code is sent to the output buffer.


Before HTML code is sent back, the TemplateHandler (D) first performs a check to see if an existing DetailView template already exists in the cache respository (H). In this case, it will look for file cache/modules/Opportunity/DetailView.tpl. The operation of creating the Smarty template is expensive so this operation ensures that the work will not have to be redone. As a side note, edits made to the DetailView or EditView through the Studio application will clear the cache file and force the template to be rewritten so that the new changes are reflected.
If the cache file does not exist, the TemplateHandler (D) will create the template file and store it in the cache directory. When the fetch() method is invoked on the Sugar_Smarty class (E) to create the template, the DetailView.tpl file is parsed.
TopicsExamplesLegacy MVC metadata examples.


TopicsExamplesLegacy MVC metadata examples.
  	Last modified: 2023-02-03 21:04:03


Examples
Legacy MVC metadata examples.
TopicsHiding the Quotes Module PDF ButtonsHow to hide the PDF buttons on a Quote.Manipulating Buttons on Legacy MVC LayoutsHow to add custom buttons to the EditView and DetailView layouts.Manipulating Layouts ProgrammaticallyHow to manipulate and merge layouts programmatically.Modifying Layouts to Display Additional ColumnsHow to add additional columns to layouts.
  	Last modified: 2023-02-03 21:04:03


Manipulating Layouts Programmatically
Overview
How to manipulate and merge layouts programmatically.
Note: This customization is only applicable for modules in backward compatibility mode.
The ParserFactory
The ParserFactory can be used to manipulate layouts such as editviewdefs or detailviewdefs. This is a handy when creating custom plugins and needing to merge changes into an existing layout. The following example will demonstrate how to add a button to the detail view:
<?php
//Instantiate the parser factory for the Accounts DetailView.
require_once('modules/ModuleBuilder/parsers/ParserFactory.php');
$parser = ParserFactory::getParser('detailview', 'Accounts');
//Button to add
$button = array(
    'customCode'=>'<input type="button" name="customButton" value="Custom Button">'
);
//Add button into the parsed layout


);
//Add button into the parsed layout
array_push($parser->_viewdefs['templateMeta']['form']['buttons'], $button);
//Save the layout
$parser->handleSave(false);
Â
  	Last modified: 2023-02-03 21:04:03


Modifying Layouts to Display Additional Columns
Overview
How to add additional columns to layouts.
By default, the editview, detailview, and quickcreate layouts for each module display two columns of fields. The number of columns to display can be customized on a per-module basis with the following steps.
Note: This customization is only applicable for modules in backward compatibility mode.
Resolution
SugarCloud


Resolution
SugarCloud
First, you will want to ensure your layouts are deployed in the custom directory. If you have not previously customized your layouts via Studio, go to Admin > Studio > {Module Name} > Layouts. From there, select each layout you wish to add additional columns to and click 'Save & Deploy'. This action will create a corresponding layout file under the ./custom/modules/{Module Name}/metadata/ directory. The files will be named editviewdefs.php, detailviewdefs.php, and quickcreatedefs.php depending on the layouts deployed.


To access your custom files, go to Admin > Diagnostic Tool, uncheck all the boxes except for "SugarCRM Custom directory" and then click "Execute Diagnostic". This will generate an archive of your instance's custom directory to download, and you will find the layout files in the above path. Open the custom layout file, locate the 'maxColumns' value, and change it to the number of columns you would like to have on screen:
'maxColumns' => '3',
Once that is updated, locate the 'widths' array to define the spacing for your new column(s). You should have a label and field entry for each column in your layout:
'widths' => array (
    0 => array (
        'label' => '10',
        'field' => '30',
    ),
    1 => array (
        'label' => '10',


),
    1 => array (
        'label' => '10',
        'field' => '30',
    ),
    2 => array (
        'label' => '10',
        'field' => '30',
    ),
),
After this is completed, you will need to create a module-loadable package to install the changes on your SugarCloud instance. More information on creating this package can be found inÂ Creating an Installable Package that Creates New Fields. To upload and install the package, go to Admin > Module Loader.
Note: Sugar Sell Essentials customers do not have the ability to upload custom file packages to Sugar using Module Loader.


Once the installation completes, you can navigate to Studio and add fields to your new column in the layout. For any rows that already contain two fields, the second field will automatically span the second and third column. Simply click the minus (-) icon to contract the field to one column and expose the new column space:
After you have added the desired fields in Studio, click 'Save & Deploy', and you are ready to go!
On-Site


On-Site
First, you will want to ensure your layouts are deployed in the custom directory. If you have not previously customized your layouts via Studio, go to Admin > Studio > {Module Name} > Layouts. From there, select each layout you wish to add additional columns to and click 'Save & Deploy'. This action will create a corresponding layout file under the ./custom/modules/{Module Name}/metadata/ directory. The files will be named editviewdefs.php, detailviewdefs.php, and quickcreatedefs.php depending on the layouts deployed.
Next, open the custom layout file, locate the 'maxColumns' value, and change it to the number of columns you would like to have on screen:
'maxColumns' => '3',


'maxColumns' => '3',
Once that is updated, locate the 'widths' array to define the spacing for your new column(s). You should have a label and field entry for each column in your layout:
'widths' => array (
    0 => array (
        'label' => '10',
        'field' => '30',
    ),
    1 => array (
        'label' => '10',
        'field' => '30',
    ),
    2 => array (
        'label' => '10',
        'field' => '30',
    ),
),


'field' => '30',
    ),
),
Once this is completed, you can navigate to Studio and add fields to your new column in the layout. For any rows that already contain two fields, the second field will automatically span the second and third column. Simply click the minus (-) icon to contract the field to one column and expose the new column space:
After you have added the desired fields in Studio, click 'Save & Deploy', and you are ready to go!
  	Last modified: 2023-02-03 21:04:03


Hiding the Quotes Module PDF Buttons
Overview
How to hide the PDF buttons on a Quote.
The PDF buttons on quotes are rendered differently than the standard buttons on most layouts. Since these buttons can't be removed directly from the DetailView in the detailviewdefs, the best approach is using jQuery to hide the buttons.
Note: This customization is only applicable for the quotes module as it is in backward compatibility mode.
Hidding the PDF Buttons
This approach involves modifying the detailviewdefs.php in the custom/modules/Quotes/metadata directory to include a custom JavaScript file. If a custom detailviewdefs.php file doesn't exist, you will need to create it through Studio or by manually
        coping the detailviewdefs.php from the Quotes stock module metadata directory.


coping the detailviewdefs.php from the Quotes stock module metadata directory.
First, we will create a javascript file, say removePdfBtns.js, in the ./custom/modules/Quotes directory. This javascript file will contain the jQuery statements to hide the Quotes "Download PDF" and "Email PDF" buttons on the DetailView of the Quote.

./custom/modules/Quotes/removePdfBtns.js
SUGAR.util.doWhen("typeof $ != 'undefined'", function(){
    YAHOO.util.Event.onDOMReady(function(){
        $("#pdfview_button").hide();
        $("#pdfemail_button").hide();
    });
});
Next, we will modify the custom detailviewdefs.php file to contain the 'includes' array element in the templateMeta array as follows:


./custom/modules/Quotes/metadata/detailviewdefs.php
$viewdefs['Quotes'] = array (
  'DetailView' =>
  array (
    'templateMeta' =>
    array (
      'form' =>
      array (
        'closeFormBeforeCustomButtons' => true,
        'buttons' =>
        array (
          0 => 'EDIT',
          1 => 'SHARE',
          2 => 'DUPLICATE',
          3 => 'DELETE',
          4 =>
          array (
            'customCode' => '<form action="index.php" method="POST" name="Quote2Opp" id="form">
                    <input type="hidden" name="module" value="Quotes">
                    <input type="hidden" name="record" value="{$fields.id.value}">


<input type="hidden" name="user_id" value="{$current_user->id}">
                    <input type="hidden" name="team_id" value="{$fields.team_id.value}">
                    <input type="hidden" name="user_name" value="{$current_user->user_name}">
                    <input type="hidden" name="action" value="QuoteToOpportunity">
                    <input type="hidden" name="opportunity_subject" value="{$fields.name.value}">
                    <input type="hidden" name="opportunity_name" value="{$fields.name.value}">
                    <input type="hidden" name="opportunity_id" value="{$fields.billing_account_id.value}">


<input type="hidden" name="amount" value="{$fields.total.value}">
                    <input type="hidden" name="valid_until" value="{$fields.date_quote_expected_closed.value}">
                    <input type="hidden" name="currency_id" value="{$fields.currency_id.value}">
                    <input id="create_opp_from_quote_button" title="{$APP.LBL_QUOTE_TO_OPPORTUNITY_TITLE}"
                        class="button" type="submit" name="opp_to_quote_button"
                        value="{$APP.LBL_QUOTE_TO_OPPORTUNITY_LABEL}" {$DISABLE_CONVERT}></form>',
          ),
        ),


),
        ),
        'footerTpl' => 'modules/Quotes/tpls/DetailViewFooter.tpl',
      ),
      'maxColumns' => '2',
      'widths' =>
      array (
        0 =>
        array (
          'label' => '10',
          'field' => '30',
        ),
        1 =>
        array (
          'label' => '10',
          'field' => '30',
        ),
      ),
      'includes' =>
      array (
        0 =>
        array (
          'file' => 'custom/modules/Quotes/removePdfBtns.js',
        ),
      ),
      'useTabs' => false,
      'tabDefs' =>
      array (
        'LBL_QUOTE_INFORMATION' =>


array (
        'LBL_QUOTE_INFORMATION' =>
        array (
          'newTab' => false,
          'panelDefault' => 'expanded',
        ),
        'LBL_PANEL_ASSIGNMENT' =>
        array (
          'newTab' => false,
          'panelDefault' => 'expanded',
        ),
      ),
    ),
    ...
Finally, navigate to: Admin > Repair > Quick Repair and Rebuild
The buttons will then be removed from the DetailView layouts.
  	Last modified: 2023-02-03 21:04:03


Manipulating Buttons on Legacy MVC Layouts
Overview
How to add custom buttons to the EditView and DetailView layouts.
Note: This customization is only applicable for modules in backward compatibility mode.
Metadata
Before adding buttons to your layouts, you will need to understand how the metadata framework is used. Detailed information on the metadata framework can be found in the Legacy Metadata section.
Custom Layouts
Before you can add a button to your layout, you will first need to make sure you have a custom layout present. The stock layouts are located in ./modules/<module>/metadata/ and must be recreated in ./custom/modules/<module>/metadata/.
There are two ways to recreate a layout in the custom directory if it does not already exist. The first is to navigate to:
Studio > {Module} > Layouts > {View}


Studio > {Module} > Layouts > {View}
Once there, you can click the "Save & Deploy" button. This will create the layoutdef for you. Alternatively, you can also manually copy the layoutdef from the stock folder to the custom folder.
Editing Layouts
When editing layouts you have three options in having your changes reflected in the UI.
Developer Mode
You can turn on Developer Mode:
Admin > System Settings
Developer Mode will remove the caching of the metadata framework. This will cause your changes to be reflected when the page is refreshed. Make sure this setting is deactivated when you are finished with your customization.
Quick Repair and Rebuild
You can run a Quick Repair and Rebuild:
Admin > Repair > Quick Repair and Rebuild
Doing this will rebuild the cache for the metadata.
Saving & Deploying the Layout in Studio


Saving & Deploying the Layout in Studio
You may also choose to load the layout in studio and then save & deploy it:
Admin > Studio > {Module} > Layouts > {View}
This process can be a bit confusing, however, once a layout is changed, you can then choose to load the layout in studio and then click "Save & Deploy" . This will rebuild the cache for that specific layout. Please note that any time you change the layout, you will have to reload the Studio layout view before deploying in order for this to work correctly.
Adding Custom Buttons
When adding buttons, there are several things to consider when determining how the button should be rendered. The following sections will outline these scenarios when working with the accounts editviewdefs located in ./custom/modules/Accounts/metadata/editviewdefs.php.
JavaScript Actions


JavaScript Actions
If you are adding a button solely to execute JavaScript (no form submissions), you can do so by adding the button HTML to:
$viewdefs['<Module>']['<View>']['templateMeta']['form']['buttons']
Example
<?php
$viewdefs['Accounts'] =
array (
  'DetailView' =>
  array (
    'templateMeta' =>
    array (
      'form' =>
      array (
        'buttons' =>
        array (
          0 => 'EDIT',
          1 => 'DUPLICATE',
          2 => 'DELETE',
          3 => 'FIND_DUPLICATES',
          4 => 'CONNECTOR',
          5 =>
          array (


4 => 'CONNECTOR',
          5 =>
          array (
            'customCode' => '<input id="JavaScriptButton" title="JavaScript Button" class="button" type="button" name="JavaScriptButton" value="JavaScript Button" onclick="alert(\'Button JavaScript\')">',
          ),
        ),
      ),
Submitting the Stock View Form
If you intend to submit the stock layout form ('formDetailView' or 'formEditView') to a new action, you can do so by adding a the button HTML with an onclick event as shown below to:
$viewdefs['<Module>']['<View>']['templateMeta']['form']['buttons']
Example
<?php
$viewdefs['Accounts'] =
array (
  'DetailView' =>
  array (


array (
  'DetailView' =>
  array (
    'templateMeta' =>
    array (
      'form' =>
      array (
        'hidden' =>
        array (
          0 => '<input type="hidden" id="customFormField" name="customFormField" value="">',
        ),
        'buttons' =>
        array (
          0 => 'EDIT',
          1 => 'DUPLICATE',
          2 => 'DELETE',
          3 => 'FIND_DUPLICATES',
          4 => 'CONNECTOR',
          5 =>
          array (


4 => 'CONNECTOR',
          5 =>
          array (
            'customCode' => '<input id="SubmitStockFormButton" title="Submit Stock Form Button" class="button" type="button" name="SubmitStockFormButton" value="Submit Stock Form Button" onclick="var _form = document.getElementById(\'formDetailView\'); _form.customFormField.value = \'CustomValue\'; _form.action.value = \'CustomAction\'; SUGAR.ajaxUI.submitForm(_form);">',
          ),
        ),
      ),
You should note in this example that there is also a 'hidden' index. This is where you should add any custom hidden inputs:
  $viewdefs['<Module>'][
  '<View>'][
  'templateMeta'][
  'form'][
  'hidden']


'templateMeta'][
  'form'][
  'hidden']
Submitting Custom Forms
If you intend to submit a custom form, you will first need to set 'closeFormBeforeCustomButtons' to true. This will close out the current views form and allow you to create your own.
$viewdefs['<Module>']['<View>']['templateMeta']['form']['closeFormBeforeCustomButtons']
Next, you will add the form and button HTML as shown below to:
$viewdefs['<Module>']['<View>']['templateMeta']['form']['buttons']
Example
<?php
$viewdefs['Accounts'] =
array (
  'DetailView' =>
  array (
    'templateMeta' =>
    array (
      'form' =>
      array (


array (
      'form' =>
      array (
        'closeFormBeforeCustomButtons' => true,
        'buttons' =>
        array (
          0 => 'EDIT',
          1 => 'DUPLICATE',
          2 => 'DELETE',
          3 => 'FIND_DUPLICATES',
          4 => 'CONNECTOR',
          5 =>
          array (
            'customCode' => '<form action="index.php" method="POST" name="CustomForm" id="form"><input type="hidden" name="customFormField" name="customFormField" value="CustomValue"><input id="SubmitCustomFormButton" title="Submit Custom Form Button" class="button" type="submit" name="SubmitCustomFormButton" value="Submit Custom Form Button"></form>',
          ),
        ),
      ),
Removing Buttons


),
        ),
      ),
Removing Buttons
To remove a button from the detail view will require modifying the ./modules/<module>/metadata/detailviewdefs.php.
The code is originally defined as:
$viewdefs[$module_name] = array (
    'DetailView' => array (
        'templateMeta' => array (
            'form' => array (
                'buttons' => array (
                    'EDIT',
                    'DUPLICATE',
                    'DELETE',
                    'FIND_DUPLICATES'
                ),
            ),
To remove one or more buttons, simply remove the 'buttons' attribute(s) that you do not want on the view.
$viewdefs[$module_name] = array (
    'DetailView' => array (
        'templateMeta' => array (


'DetailView' => array (
        'templateMeta' => array (
            'form' => array (
                'buttons' => array (
                    'DELETE',
                ),
            ),
Â
  	Last modified: 2023-02-03 21:04:03


View
Overview
Displaying information to the browser.
What are Views?
Views, otherwise known as actions, are typically used to render views or to process logic. Views are not just limited to HTML data. You can send JSON encoded data as part of a view or any other structure you wish. As with the controllers, there is a default class called SugarView which implements much of the basic logic for views, such as handling of headers and footers.
There are five main actions for a module:
Display Actions


There are five main actions for a module:
Display Actions
Detail View: A detail view displays a read-only view of a particular record. Usually, this is accessed via the list view. The detail view displays the details of the object itself and related items (subpanels). Subpanels are miniature list views of items that are related to the parent object. For example, Tasks assigned to a Project, or Contacts for an Opportunity will appear in subpanels in the Project or Opportunity detail view. The file ./<module>/metadata/detailviewdefs.php defines a module's detail view page layout. The file ./<module>/metadata/subpaneldefs.php defines the subpanels that are displayed in the module's detail view page.


Edit View: The edit view page is accessed when a user creates a new record or edits details of an existing one. Edit view can also be accessed directly from the list view. The file ./<module>/metadata/editviewdefs.php defines a module's edit view page layout.
List View: This Controller action enables the search form and search results for a module. Users can perform actions such as delete, export, update multiple records (mass update), and drill into a specific record to view and edit the details. Users can see this view by default when they click one of the module tabs at the top of the page. Files in each module describe the contents of the list and search view.
Process Actions
Save: This Controller action is processed when the user clicks Save in the record's edit view.


Save: This Controller action is processed when the user clicks Save in the record's edit view.
Delete: This action is processed when the user clicks "Delete" in the detail view of a record or in the detail view of a record listed in a subpanel.
Implementation
Class File Structure
./include/MVC/Views/SugarView.php
./include/MVC/Views/view.<view>.php
./custom/include/MVC/Views/view.<view>.php
./modules/<module>/views/view.<view>.php
./custom/modules/<module>/views/view.<view>.php
Class Loading
The ViewFactory class loads the view based off the the following sequence loading the first file it finds:
./custom/modules/<module>/views/view.<view>.php
./modules/<module>/views/view.<view>.php


./modules/<module>/views/view.<view>.php
./custom/include/MVC/View/view.<view>.php
./include/MVC/Views/view.<view>.php
Methods
There are two main methods to override within a view:
preDisplay(): This performs pre-processing within a view. This method is relevant only for extending existing views. For example, the include/MVC/View/views/view.edit.php file uses it, and enables developers who wishes to extend this view to leverage all of the logic done in preDisplay() and either override the display() method completely or within your own display() method call parent::display().
display(): This method displays the data to the screen. Place the logic to display output to the screen here.
Creating Views


Creating Views
Creating a new/view action consists of a controller action and a view file. The first step is to define your controller action. If the module does not contain a controller.php file in ./modules/<module>/ you will create the following file:
./custom/modules/<module>/controller.php
<?php
class <module>Controller extends SugarController
{
    function action_MyView()
    {
        $this->view = 'myview';
    }
}
More information on controllers can be found in the Controller section.
The next step is to define your view file. This example extends the ViewDetail class but you can extend any of the classes you choose in ./include/MVC/View/views/.
./custom/modules/<module>/views/view.newview.php
<?php


<?php
require_once 'include/MVC/View/views/view.detail.php' ;
class <module>ViewMyView extends ViewDetail
{
    function display()
    {
        echo 'This is my new view<br>';
    }
}
Overriding Views
The following section will demonstrate how to extend and override a view. When overriding existing actions and views, you won't need to make any changes to the controller. This approach will be very similar for any view you may choose to modify. If the module you are extending the view for does not contain an existing view in its modules views directory ( ./modules/<module>/views/ ), you will need to extend the views base class. Otherwise, you will extend the view class found within the file.


In the case of a detail view, you would check for the file ./modules/<module>/views/view.detail.php. If this file does not exist, you will create ./custom/modules/<module>/views/view.detail.php and extend the base ViewDetail class with the name <module>ViewDetail.
./custom/modules/<module>/views/view.detail.php
<?php
require_once('include/MVC/View/views/view.detail.php');
class <module>ViewDetail extends ViewDetail
{
    function display()
    {
        echo 'This is my addition to the DetailView<br>';
        //call parent display method
        parent::display();
    }
}


//call parent display method
        parent::display();
    }
}
If ./modules/<module>/views/view.detail.php does exist, you would create ./custom/modules/<module>/views/view.detail.php and extend the base <module>ViewDetail class with the name Custom<module>ViewDetail.
./custom/modules/<module>/views/view.detail.php
<?php
require_once('modules/<module>/views/view.detail.php');
class Custom<module>ViewDetail extends <module>ViewDetail
{
    function display()
    {
        echo 'This is my addition to the DetailView<br>';
        //call parent display method
        parent::display();
    }
}
Display Options for Views


parent::display();
    }
}
Display Options for Views
The Sugar MVC provides developers with granular control over how the screen looks when a view is rendered. Each view can have a config file associated with it. In the case of an edit view, the developer would create the file ./customs/modules/<module>/views/view.edit.config.php . When the edit view is rendered, this config file will be picked up. When loading the view, ViewFactory class will merge the view config files from the following possible locations with precedence order (high to low):
./customs/modules/<module>/views/view.<view>.config.php
./modules/<module>/views/view.<view>.config.php
./custom/include/MVC/View/views/view.<view>.config.php
./include/MVC/View/views/view.<view>.config.php


./include/MVC/View/views/view.<view>.config.php
Implementation
The format of these files is as follows:
$view_config = array(
    'actions' =>
        array(
            'popup' => array(
            'show_header' => false,
            'show_subpanels' => false,
            'show_search' => false,
            'show_footer' => false,
            'show_JavaScript' => true,
        ),
    ),
    'req_params' => array(
        'to_pdf' => array(
            'param_value' => true,
            'config' => array(
                'show_all' => false
            ),
        ),
    ),
);


'show_all' => false
            ),
        ),
    ),
);
To illustrate this process, let us take a look at how the 'popup' action is processed. In this case, the system will go to the actions entry within the view_config and determine the proper configuration. If the request contains the parameter to_pdf, and is set to be true, then it will automatically cause the show_all configuration parameter to be set false, which means none of the options will be displayed.
  	Last modified: 2023-02-03 21:04:03


Dashlets
Overview
Dashlets are special view-component plugins that render data from a context and make use of the Dashlet plugin. They are typically made up of a controller JavaScript file (.js) and at least one Handlebars template (.hbs).
Hierarchy Diagram
Sugar loads the dashlet view components in the following manner:
Note: The Sugar application's client type is "base". For more information on the various client types, please refer toÂ the User Interface page.
Dashlet Views
The are three views when working with dashlets: Preview, Dashlet View, and Configuration View. The following sections discuss the differences between views.
Preview
The preview view is used when selecting dashlets to add to your homepage. Preview variables in the metadata will be assigned to the custom model variables.
'preview' => array(
    'key1' => 'value1',
),


'key1' => 'value1',
),
The values in the preview metadata can be retrieved using:
this.model.get("key1");
Dashlet View
The dashlet view will render the content for the dashlet. It will also contain the settings for editing, removing, and refreshing the dashlet.
Configuration View
The configuration view is displayed when a user clicks the 'edit' option on the dashlet frame's drop-down menu. Config variables in the metadata will be assigned to the custom model variables
'config' => array(
    //key value pairs of attributes
    'key1' => 'value1',
),
The values in the config metadata can be retrieved using:
this.model.get("key1");
Dashlet Example


this.model.get("key1");
Dashlet Example
The RSSÂ feedÂ dashlet, located in ./clients/base/views/rssfeed/, handles the display of RSSÂ feeds to the user. The sections below outline the various files that render this dashlet.
Metadata
The Dashlet view contains the 'dashlets' metadata:
Parameters
Type
Required
Description
label
String
yes
The name of the dashlet
description
String
no
A description of the dashlet
config
Object
yes
Pre-populated variables in the configuration viewNote:Â Config variables in the metadata areÂ assigned to the custom model variables.
preview
Object
yesÂ
Pre-populated variables in the preview
filter
Object
no
Filter for display
TheÂ RSS feedÂ dashlets metadata is located in:


Filter for display
TheÂ RSS feedÂ dashlets metadata is located in:
./clients/base/views/rssfeed/rssfeed.php
<?php
/*
 * Your installation or use of this SugarCRM file is subject to the applicable
 * terms available at
 * http://support.sugarcrm.com/Resources/Master_Subscription_Agreements/.
 * If you do not agree to all of the applicable terms or do not have the
 * authority to bind the entity as an authorized representative, then do not
 * install or use this SugarCRM file.
 *
 * Copyright (C) SugarCRM Inc. All rights reserved.
 */
$viewdefs['base']['view']['rssfeed'] = array(
    'dashlets' => array(
        array(


'dashlets' => array(
        array(
            'label' => 'LBL_RSS_FEED_DASHLET',
            'description' => 'LBL_RSS_FEED_DASHLET_DESCRIPTION',
            'config' => array(
                'limit' => 5,
                'auto_refresh' => 0,
            ),
            'preview' => array(
                'limit' => 5,
                'auto_refresh' => 0,
                'feed_url' => 'http://blog.sugarcrm.com/feed/',
            ),
        ),
    ),
    'panels' => array(
        array(
            'name' => 'panel_body',
            'columns' => 2,
            'labelsOnTop' => true,
            'placeholders' => true,


'labelsOnTop' => true,
            'placeholders' => true,
            'fields' => array(
                array(
                    'name' => 'feed_url',
                    'label' => 'LBL_RSS_FEED_URL',
                    'type' => 'text',
                    'span' => 12,
                    'required' => true,
                ),
                array(
                    'name' => 'limit',
                    'label' => 'LBL_RSS_FEED_ENTRIES_COUNT',
                    'type' => 'enum',
                    'options' => 'tasks_limit_options',
                ),
                array(
                    'name' => 'auto_refresh',
                    'label' => 'LBL_DASHLET_REFRESH_LABEL',
                    'type' => 'enum',


'type' => 'enum',
                    'options' => 'sugar7_dashlet_reports_auto_refresh_options',
                ),
            ),
        ),
    ),
);
Controller
The rssfeed.js controller file, shown below, contains the JavaScript to render the news articles on the dashlet. The Dashlet view must include 'Dashlet' plugin and can override initDashlet to add additional custom process while it is initializing.
./clients/base/views/rssfeed/rssfeed.js
/*
 * Your installation or use of this SugarCRM file is subject to the applicable
 * terms available at
 * http://support.sugarcrm.com/Resources/Master_Subscription_Agreements/.
 * If you do not agree to all of the applicable terms or do not have the
 * authority to bind the entity as an authorized representative, then do not


* authority to bind the entity as an authorized representative, then do not
 * install or use this SugarCRM file.
 *
 * Copyright (C) SugarCRM Inc. All rights reserved.
 */
/**
 * RSS Feed dashlet consumes an RSS Feed URL and displays it's content as a list
 * of entries.
 *
 * The following items are configurable.
 *
 * - {number} limit Limit imposed to the number of records pulled.
 * - {number} refresh How often (minutes) should refresh the data collection.
 *
 * @class View.Views.Base.RssfeedView
 * @alias SUGAR.App.view.views.BaseRssfeedView
 * @extends View.View
 */
({
    plugins: ['Dashlet'],
    /**
     * Default options used when none are supplied through metadata.
     *


/**
     * Default options used when none are supplied through metadata.
     *
     * Supported options:
     * - timer: How often (minutes) should refresh the data collection.
     * - limit: Limit imposed to the number of records pulled.
     *
     * @property {Object}
     * @protected
     */
    _defaultOptions: {
        limit: 5,
        auto_refresh: 0
    },
    /**
     * @inheritdoc
     */
    initialize: function(options) {
        options.meta = options.meta || {};
        this._super('initialize', [options]);
        this.loadData(options.meta);
    },
    /**
     * Init dashlet settings
     */
    initDashlet: function() {


* Init dashlet settings
     */
    initDashlet: function() {
        // We only need to handle this if we are NOT in the configure screen
        if (!this.meta.config) {
            var options = {};
            var self = this;
            var refreshRate;
            // Get and set values for limits and refresh
            options.limit = this.settings.get('limit') || this._defaultOptions.limit;
            this.settings.set('limit', options.limit);
            options.auto_refresh = this.settings.get('auto_refresh') || this._defaultOptions.auto_refresh;
            this.settings.set('auto_refresh', options.auto_refresh);
            // There is no default for this so there's no pointing in setting from it


// There is no default for this so there's no pointing in setting from it
            options.feed_url = this.settings.get('feed_url');
            // Set the refresh rate for setInterval so it can be checked ahead
            // of time. 60000 is 1000 miliseconds times 60 seconds in a minute.
            refreshRate = options.auto_refresh * 60000;
            // Only set up the interval handler if there is a refreshRate higher
            // than 0
            if (refreshRate > 0) {
                if (this.timerId) {
                    clearInterval(this.timerId);
                }
                this.timerId = setInterval(_.bind(function() {
                    if (self.context) {
                        self.context.resetLoadFlag();
                        self.loadData(options);
                    }
                }, this), refreshRate);


self.loadData(options);
                    }
                }, this), refreshRate);
            }
        }
        // Validation handling for individual fields on the config
        this.layout.before('dashletconfig:save', function() {
            // Fields on the metadata
            var fields = _.flatten(_.pluck(this.meta.panels, 'fields'));
            // Grab all non-valid fields from the model
            var notValid = _.filter(fields, function(field) {
                return field.required && !this.dashModel.get(field.name);
            }, this);
            // If there no invalid fields we are good to go
            if (notValid.length === 0) {
                return true;
            }
            // Otherwise handle notification of invalidation
            _.each(notValid, function(field) {


_.each(notValid, function(field) {
                 var fieldOnView = _.find(this.fields, function(comp, cid) {
                    return comp.name === field.name;
                 });
                 fieldOnView.model.trigger('error:validation:' + field.name, {required: true});
            }, this);
            // False return tells the drawer that it shouldn't close
            return false;
        }, this);
    },
    /**
     * Handles the response of the feed consumption request and sets data from
     * the result
     *
     * @param {Object} data Response from the rssfeed API call
     */
    handleFeed: function (data) {
        if (this.disposed) {
            return;
        }
        // Load up the template


return;
        }
        // Load up the template
        _.extend(this, data);
        this.render();
    },
    /**
     * Loads an RSS feed from the RSS Feed endpoint.
     *
     * @param {Object} options The metadata that drives this request
     */
    loadData: function(options) {
        if (options && options.feed_url) {
            var callbacks = {success: _.bind(this.handleFeed, this), error: _.bind(this.handleFeed, this)},
                limit = options.limit || this._defaultOptions.limit,
                params = {feed_url: options.feed_url, limit: limit},
                apiUrl = app.api.buildURL('rssfeed', 'read', '', params);


app.api.call('read', apiUrl, {}, callbacks);
        }
    },
    /**
     * @inheritdoc
     *
     * New model related properties are injected into each model:
     *
     * - {Boolean} overdue True if record is prior to now.
     */
    _renderHtml: function() {
        if (this.meta.config) {
            this._super('_renderHtml');
            return;
        }
        this._super('_renderHtml');
    }
})
Workflow
When triggered, the following procedure will render the view area:
Retrieving Data
Use the following commands to retrieve the corresponding data:
Data Location
Element
Command
Main pane
Record View
this.model
Record View
this.context.parent.get("model")
List View


Record View
this.context.parent.get("model")
List View
this.context.parent.get("collection")
Metadata
Â
this.dashletConfig['metadata_key']
Module vardefs
Â
app.metadata.getModule("ModuleName")
Remote data
Bean
new app.data.createBean("Module")new app.data.createBeanCollection("Module")
RestAPIÂ
app.api.call(method, url, data, callbacks, options)
Ajax CallÂ
$.ajax()
User inputs
Â
this.settings.get("custom_key")
Handlebar Template
The rssfeed.hbs template file defines the content of the view. This view is used for rendering the markup rendering in the dashlet content.
./clients/base/views/rssfeed/rssfeed.hbs


./clients/base/views/rssfeed/rssfeed.hbs
{{!--
/*
 * Your installation or use of this SugarCRM file is subject to the applicable
 * terms available at
 * http://support.sugarcrm.com/Resources/Master_Subscription_Agreements/.
 * If you do not agree to all of the applicable terms or do not have the
 * authority to bind the entity as an authorized representative, then do not
 * install or use this SugarCRM file.
 *
 * Copyright (C) SugarCRM Inc. All rights reserved.
 */
--}}
{{#if feed}}

            {{#if feed.link}}{{/if}}
            {{feed.title}}


{{feed.title}}
            {{#if feed.link}}{{/if}}

        {{#each feed.entries}}

                Dashlets
                {{#if author}} - {{str "LBL_RSS_FEED_AUTHOR"}} {{author}}{{/if}}

        {{/each}}

{{else}}

        {{#if errorThrown}}
        {{str "LBL_NO_DATA_AVAILABLE"}}
        {{else}}
        {{loading 'LBL_ALERT_TITLE_LOADING'}}
        {{/if}}


{{/if}}
    </div>
{{/if}}
  	Last modified: 2023-02-03 21:04:03


Alerts
Overview
The alert view widget, located in ./clients/base/views/alert/, displays helpful information such as loading messages, notices, and confirmation messages to the user.
Methods
app.alert.show(id, options)
The app.alert.show(id, options) method displays an alert message to the user with the options provided.
Parameters
Name
Description
id
The id of the alert message. Used for dismissing specific messages.
options.level
The alert level
options.title
The alert's title, which corresponds to the alert's level
options.messages
The message that the user sees Note: Process alerts do not display messages.
options.autoClose
Whether or not to auto-close the alert popup
options.onClose
Callback handler for closing confirmation alerts
options.onCancel
Callback handler for canceling confirmation alerts
options.onLinkClick


options.onCancel
Callback handler for canceling confirmation alerts
options.onLinkClick
Callback handler for click actions on a link inside of the alert
Default Alert Values
Alert Level
Alert Appearance
Alert Title
info
blue
"Notice"
success
green
"Success"
warning
yellow
"Warning!"
error
red
"Error"
process
loading message
"Loading..."
confirmation
confirmation dialog
"Warning"
Alert Examples
Standard Alert
app.alert.show('message-id', {
    level: 'success',
    messages: 'Task completed!',
    autoClose: true
});
Confirmation Alert
app.alert.show('message-id', {
    level: 'confirmation',
    messages: 'Confirm?',
    autoClose: false,
    onConfirm: function(){
         alert("Confirmed!");
    },


onConfirm: function(){
         alert("Confirmed!");
    },
    onCancel: function(){
        alert("Cancelled!");
    }
});
Process Alert
app.alert.show('message-id', {
    level: 'process',
    title: 'In Process...' //change title to modify display from 'Loading...'
});
app.alert.dismiss(id)
The app.alert.dismiss(id) method dismisses an alert message from view based on the message id.
Parameters
Name
Description
id
The id of the alert message to dismiss.
Example
app.alert.dismiss('message-id');
app.alert.dismissAll
The app.alert.dismissAll dismisses all alert messages from view.
Example
app.alert.dismissAll();
Testing in Console


Example
app.alert.dismissAll();
Testing in Console
To test alerts, you can trigger them in your browser's developer tools by using the global App variable as shown below:
App.alert.show('message-id', {
    level: 'success',
    messages: 'Successful!',
    autoClose: false
});
  	Last modified: 2023-02-03 21:04:03


Layouts
Overview
Layouts are component plugins that define the overall layout and positioning of the page. Layouts replace the previous concept of MVC views and are used system-wide to generate rows, columns, bootstrap fluid layouts, and pop-ups by wrapping and placing multiple views or nested layouts on a page.
Layout components are typically made up of a controller JavaScript file (.js) and a PHP file (.php), however, layout types vary and are not dependent on having both files.
Hierarchy Diagram
The layout components are loaded in the following manner:
Note: The Sugar application client type is "base". For more information on the various client types, please refer toÂ the User Interface page.
Sidecar Layout Routing
Sidecar uses routing to determine where to direct the user. To route the user to a specific page in Sugar, refer to the following default URL formats:
Behavior
URL Format


Behavior
URL Format
Route the user to the list layout for a module
http://{site url}/#<module>/
Route the user to the record layout for a specific record
http://{site url}/#<module>/f82d09cb-48cd-a1fb-beae-521cf39247b5
Route the user to a custom layout for the module
http://{site url}/#<module>/layout/<layout>
Layout Example
The list layout, located in ./clients/base/layouts/list/, handles the layout for the list view. The sections below outline the various files that render this view.
JavaScript
The file list.js, shown below, contains the JavaScript used to place the layout content.
./clients/base/layouts/list/list.js
/**
* Layout that places components using bootstrap fluid layout divs


/**
* Layout that places components using bootstrap fluid layout divs
* @class View.Layouts.ListLayout
* @extends View.FluidLayout
*/
({
    /**
     * Places a view's element on the page.
     * @param {View.View} comp
     * @protected
     * @method
     */
    _placeComponent: function(comp, def) {
        var size = def.size || 12;
        // Helper to create boiler plate layout containers
        function createLayoutContainers(self) {
            // Only creates the containers once
            if (!self.$el.children()[0]) {
                comp.$el.addClass('list');
            }
        }
        createLayoutContainers(this);
        // All components of this layout will be placed within the
        // innermost container div.


// innermost container div.
        this.$el.append(comp.el);
    }
})
Layout Definition
The layout definition is contained as an array in list.php. This layout definition contains four views:
massupdate
massaddtolist
recordlist
list-bottom
./clients/base/layouts/list/list.php
<?php
$viewdefs['base']['layout']['list'] = array(
    'components' =>
    array(
        array(
            'view' => 'massupdate',
        ),
        array(
            'view' => 'massaddtolist',
        ),
        array(
            'view' => 'recordlist',
            'primary' => true,
        ),
        array(
            'view' => 'list-bottom',
        ),
    ),


'view' => 'list-bottom',
        ),
    ),
    'type' => 'simple',
    'name' => 'list',
    'span' => 12,
);
Application
For information on working with layouts, please refer to the Creating Layouts and Overriding Layouts pages for practical examples.
TopicsCreating LayoutsThis example explains how to create a custom layout to define the various components that load on a page.Overriding LayoutsThis page explains how to override a stock layout component. For this example, we will extend the stock record view and create a custom view named "my-record" that will be used in our record layout's override. This example involves two steps:
  	Last modified: 2023-02-03 21:04:03


Creating Layouts
Overview
This exampleÂ explains howÂ to create a custom layout to define the various components that load on aÂ page.Â
Creating the Layout
This example creates a component named "my-layout", whichÂ will render a custom view named "my-view".
./custom/clients/base/layouts/my-layout/my-layout.php
<?php
    $viewdefs['base']['layout']['my-layout'] = array(
        'type' => 'simple',
        'components' => array(
            array(
                'view' => 'my-view',
            ),
        ),
    );
Creating a View
The view component will render the actual content we want to see on the page. The view below will display a clickable cube icon that will spin when clicked by the user.


./custom/clients/base/views/my-view/my-view.js
({
    className: 'my-view tcenter',
    cubeOptions: {
        spin: false
    },
    events: {
        'click .sugar-cube': 'spinCube'
    },
    spinCube: function() {
        this.cubeOptions.spin = !this.cubeOptions.spin;
        this.render();
    }
})
./custom/clients/base/views/my-view/my-view.hbs
<style>
    div.my-view
    {
        padding-top: 5%;
    }
    div.my-view .sugar-cube
    {
        fill:#bbbbbb;
        height:200px;
        width:200px;
        display: inline;
    }


width:200px;
        display: inline;
    }
</style>

My View

{{{subFieldTemplate 'sugar-cube' 'detail' cubeOptions}}}

Click to spin the cube!

Once the files are in place, navigate to Admin > Repair and perform a Quick Repair and Rebuild.
Navigating to the Layout
To see this newÂ layout and view, navigate to http://{site url}/#<module>/layout/my-layout.
  	Last modified: 2023-02-03 21:04:03


Overriding Layouts
Overview
This page explains how to override a stock layout component. For this example, we will extend the stock record view and create a custom view named "my-record" that will be used in our record layout's override. This example involves twoÂ steps:
Override the Layout
Extend the View
These steps are explained in the following sections.
Overriding the Layout
First,Â copy ./clients/base/layouts/record/record.php to ./custom/clients/base/layouts/record/record.php. Once copied, modify the following line from:
'view' => 'record',
To:
'view' => 'my-record',


To:
'view' => 'my-record',
That line will change the record layout from using the base record.js view, ./clients/base/views/record/record.js, to instead use a custom view that we will create in ./custom/clients/base/views/my-record/my-record.js. At this point, theÂ custom layout override should be very similar to the example below:
./custom/clients/base/layouts/record/record.php
<?php
$viewdefs['base']['layout']['record'] = array(
    'components' => array(
        array(
            'layout' => array(
                'type' => 'default',
                'name' => 'sidebar',
                'components' => array(
                    array(
                        'layout' => array(


array(
                        'layout' => array(
                            'type' => 'base',
                            'name' => 'main-pane',
                            'css_class' => 'main-pane span8',
                            'components' => array(
                                array(
                                    'view' => 'my-record',
                                    'primary' => true,
                                ),
                                array(
                                    'layout' => 'extra-info',
                                ),
                                array(
                                    'layout' => array(
                                        'type' => 'filterpanel',
                                        'last_state' => array(
                                            'id' => 'record-filterpanel',
                                            'defaults' => array(
                                                'toggle-view' => 'subpanels',
                                            ),
                                        ),


),
                                        ),
                                        'refresh_button' => true,
                                        'availableToggles' => array(
                                            array(
                                                'name' => 'subpanels',
                                                'icon' => 'fa-table',
                                                'label' => 'LBL_DATA_VIEW',
                                            ),
                                            array(
                                                'name' => 'list',
                                                'icon' => 'fa-table',
                                                'label' => 'LBL_LISTVIEW',
                                            ),
                                            array(
                                                'name' => 'activitystream',
                                                'icon' => 'fa-clock-o',
                                                'label' => 'LBL_ACTIVITY_STREAM',
                                            ),
                                        ),


),
                                        ),
                                        'components' => array(
                                            array(
                                                'layout' => 'filter',
                                                'xmeta' => array(
                                                    'layoutType' => '',
                                                ),
                                                'loadModule' => 'Filters',
                                            ),
                                            array(
                                                'view' => 'filter-rows',
                                            ),
                                            array(
                                                'view' => 'filter-actions',
                                            ),
                                            array(
                                                'layout' => 'activitystream',
                                                'context' =>
                                                array(
                                                    'module' => 'Activities',
                                                ),
                                            ),
                                            array(


),
                                            ),
                                            array(
                                                'layout' => 'subpanels',
                                            ),
                                        ),
                                    ),
                                ),
                            ),
                        ),
                    ),
                    array(
                        'layout' => array(
                            'type' => 'base',
                            'name' => 'dashboard-pane',
                            'css_class' => 'dashboard-pane',
                            'components' => array(
                                array(
                                    'layout' => array(
                                        'type' => 'dashboard',
                                        'last_state' => array(
                                            'id' => 'last-visit',
                                        )
                                    ),
                                    'context' => array(
                                        'forceNew' => true,


'context' => array(
                                        'forceNew' => true,
                                        'module' => 'Home',
                                    ),
                                    'loadModule' => 'Dashboards',
                                ),
                            ),
                        ),
                    ),
                    array(
                        'layout' => array(
                            'type' => 'base',
                            'name' => 'preview-pane',
                            'css_class' => 'preview-pane',
                            'components' => array(
                                array(
                                    'layout' => 'preview',
                                ),
                            ),
                        ),
                    ),
                ),
            ),
        ),
    ),
);
Extending the View


),
            ),
        ),
    ),
);
Extending the View
For this example, we will extend the stock record view and create a custom view named my-record that will be used in our record layouts override.
./custom/clients/base/views/my-record/my-record.js
({
    extendsFrom: 'RecordView',
    initialize: function (options) {
        this._super("initialize", [options]);
        //log this point
        console.log("**** Override called");
    }
})
Once the files are in place, navigate to Admin > Repair > Quick Repair and Rebuild.
  	Last modified: 2023-02-03 21:04:03


Architecture
Overview
This section of Sugar's Developer Guide begins with a high-level overview of the Sugar platform's architecture and contains documentation on granular concepts in Sugar such as logic hooks, caching, logging, extensions, job queue, and more.
Please continue to the bottom of this page or use the navigation on the left to exploreÂ the related content.
Platform
SugarÂ® is built on open standards and technology such as HTML5, PHP, and JavaScript, and runs on a variety of free and open-source technology like Linux, MySQL, and Elasticsearch. The Sugar platform also supports common proprietary databases such as Oracle, IBM DB2, and Microsoft SQL Server.
All of Sugar's customers and partners have access to source code that they can choose to deploy on-premise or utilize Sugar's cloud service for a SaaS deployment.


Out of the box, Sugar uses a consistent platform across all clients and devices (e.g. mobile, web, plug-ins, etc.).
Front-End Framework
Our clients are based on a front-end framework called Sidecar. Sidecar is built on open source technology: Backbone.js, jQuery, Handlebars.js, and Bootstrap. The Sidecar framework provides a responsive UI (to support a variety of form factors) and uses modern, single-page client architecture. Sugar clients connect to Sugar server application via our client REST API. The REST API is implemented in PHP and drives server-side business logic and interacts with a database. If it can be accomplished via one of our clients, then its equivalent functionality can be accomplished using our REST API.


The Sugar platform uses modules. Modules are a vertically integrated application component that is traditionally organized around a single feature or record type (or underlying database table). For example, contact records are managed via a Contacts module that contains all the business logic, front-end interface definitions, REST APIs, data schema,Â andÂ relationships with other modules.
Custom modules can be created and deployed as needed in order to add new features to a Sugar application instance.Â
Metadata
Sugar's modules are defined primarily using Metadata. There are two types of metadata definitions within Sugar: Vardefs, which define the data model for Sugar modules; and Viewdefs, which define the user interface components that are used with a module.
Sugar Metadata is implemented as PHP files that can be modified directly by a Sugar Developer making filesystem changes, or indirectly through the use of Sugar Studio and Module Builder by a Sugar Administrator.


Metadata allows you to configure solutions instead of having to write countless lines of custom code in order to implement common customizations such as adding custom fields, calculated values, and changing user interface layouts.
Extensions
Beyond metadata, Sugar is highly customizable and includes an extensive Extensions Framework that provides Sugar Developers the capability to contribute to pre-defined extension points within the application in a way that is upgrade-safe and will not conflict with other customizations that exist in the system.


TopicsAutoloaderThe autoloader is an API that allows the unified handling of customizations and customizable metadata while reducing the number of filesystem accesses and improving performance.CachingMuch of Sugar's user interface is built dynamically using a combination of templates, metadata and language files. A file caching mechanism improves the performance of the system by reducing the number of static metadata and language files that need to be resolved at runtime. This cache directory stores the compiled files for JavaScript files, Handlebars templates, and language files.UploadsThe upload directory is used to store files uploaded for imports, attachments, documents, and module loadable packages.EmailOutlines the relationships between emails, email addresses, and bean records.Email AddressesRecommended approaches when working with email addresses in Sugar.LoggingThere are two logging systems implemented in the Sugar application: SugarLogger and PSR-3. PSR-3 is Sugar's preferred logger solution and should be used going forward.Logic


PSR-3 is Sugar's preferred logger solution and should be used going forward.Logic HooksThe Logic Hook framework allows you to append actions to system events such as when creating, editing, and deleting records.LanguagesSugar as an application platform is internationalized and localizable. Data is stored and presented in the UTF-8 codepage, allowing for all character sets to be used. Sugar provides a language-pack framework that allows developers to build support for any language in the display of user interface labels. Each language pack has its own set of display strings which is the basis of language localization. You can add or modify languages using the information in this guide.ExtensionsThe extension framework, defined in ./ModuleInstall/extensions.php, provides the capability to modify Sugar metadata such as vardefs and layouts in a safe way that supports installing, uninstalling, enabling, and disabling without interfering with other customizations.FiltersFilters are a way to


enabling, and disabling without interfering with other customizations.FiltersFilters are a way to predefine searches on views that render a list of records such as list views, pop-up searches, and lookups. This page explains how to implement the various types of filters for record list views.Duplicate CheckThe duplicate-check framework provides the capability to alter how the system searches for duplicate records in the database when creating records. For information on duplicate checking during imports, please refer to the index documentation.Elastic SearchThe focus of this document is to guide developers on how Sugar integrates with Elastic Search.Global SearchHow to customize the global search results.Sugar LogicSugar Logic, a feature in all Sugar products, enables custom business logic that is easy to create, manage, and reuse on both the server and client.AdministrationThe Administration class is used to manage settings stored in the database config table.ConfiguratorThe Configurator class, located in


manage settings stored in the database config table.ConfiguratorThe Configurator class, located in ./modules/Configurator/Configurator.php, handles the config settings found in ./config.php and ./config_override.php.Module LoaderModule Loader is used when installing customizations, plugins,language packs, and hotfixes, and other customizations into a Sugar instance in the form of a Module Loadable Package. This documentation covers the basics and best practices for creating module loadable packages for a Sugar installation.Module BuilderThe Module Builder tool allows programmers to create custom modules without writing code and to create relationships between new and existing CRM modules. To illustrate how to use Module Builder, this article will show how to create and deploy a custom module.QuotesAs of Sugar 7.9.0.0, the quotes module has been moved out of Backward Compatibility mode into Sidecar. Customizations to the Quotes interface will need to be rewritten for


Compatibility mode into Sidecar. Customizations to the Quotes interface will need to be rewritten for the new Sidecar framework as an automated migration of these customizations is not possible. This document will outline common customizations and implementation methods.SugarBPMSugarBPMâ¢ automation suite is the next-generation workflow management tool for Sugar. Introduced in 7.6, SugarBPM is loosely based on BPMN process notation standards and provides a simple drag-and-drop user interface along with an intelligent flow designer to allow for the creation of easy yet powerful process definitions.Entry PointsEntry points, defined in ./include/MVC/Controller/entry_point_registry.php, were used to ensure that proper security and authentication steps are applied consistently across the entire application. While they are still used in some areas of Sugar, any developers using custom entry points should adjust their customizations to use the latest REST API endpoints instead.Job QueueThe Job Queue


their customizations to use the latest REST API endpoints instead.Job QueueThe Job Queue executes automated tasks in Sugar through a scheduler, which integrates with external UNIX systems and Windows systems to run jobs that are scheduled through those systems. Jobs are the individual runs of the specified function from a scheduler.Access Control ListsAccess Control Lists (ACLs) are used to control access to the modules, data, and actions available to users in Sugar. By default, Sugar comes with a default ACL Strategy that is configurable by an Admin through the Roles module via Admin > Role Management. Sugar also comes with other ACL strategies that are located in the ./data/acl/ folder, and are used in other parts of the system such as the Administration module, Emails module, Locked Fields functionality, and many others. They can also be leveraged by customizations and custom ACL strategies can be implemented to control your own customizations.TeamsTeams provide the ability to limit access


can be implemented to control your own customizations.TeamsTeams provide the ability to limit access at the record level, allowing sharing flexibility across functional groups. Developers can manipulate teams programmatically provided they understand Sugar's visibility framework.TagsThe tagging feature allows a user to apply as many "tags" as they choose on any record they want to categorize. This allows the user to effectively group records together from any source within the application and relate them together. Please note that the tag field and Tags module do not support customization at this time.TinyMCEThis section contains information about working with the TinyMCE editor in Sugar.SugarPDFThis section contains information on generating PDFs and configuring PDF settings and fonts in Sugar.DateTimeThe SugarDateTime class, located in, ./include/SugarDateTime.php, extends PHP's built in DateTime class and can be used to perform common operations on date and time values.ShortcutsShortcuts is a framework to


be used to perform common operations on date and time values.ShortcutsShortcuts is a framework to add shortcut keys to the application. When shortcut keys are registered, they are registered to a particular shortcut session. Shortcut sessions group shortcut keys, and they can be activated, deactivated, saved, and restored. Global shortcut keys can be registered, but they are not tied to a particular shortcut session. They are rather applied everywhere in the application and can only be applied once.ThemesHow to customize the Sugar theme.Web AccessibilityLearn about the Sugar Accessibility Plugin for Sidecar.Validation ConstraintsThis article will cover how to add validation constraints to your custom code.CLISugar on-premise deployments include a command line interface tool built using the Symfony Console Framework. Sugar's CLI is intended to be an administrator or developer level power tool to execute PHP code in the context of Sugar's code base. These commands are version specific and can be executed on a


in the context of Sugar's code base. These commands are version specific and can be executed on a preinstalled Sugar instance or on an installed Sugar instance. Sugar's CLI is not intended to be used as a tool to interact remotely with an instance nor is it designed to interact with multiple instances.Performance TuningThe Performance Tuning section contains information that will help maximize system performance for your Sugar instance.Backward CompatibilityAs of SugarÂ® 7, modules are built using the Sidecar framework. Any modules that still use the MVC architecture and have not yet migrated to the Sidecar framework are set in what Sugar refers to as "backward compatibility" (BWC) mode.


Last modified: 2023-02-03 21:04:03


Autoloader
Overview
The autoloader is an API that allows the unified handling of customizations and customizable metadata while reducing the number of filesystem accesses and improving performance.
SugarAutoLoader
The SugarAutoLoader class, located in ./include/utils/autoloader.php, keeps a map of files within the Sugar directory that may be loaded.
Included File Extensions
The autoloader will only map files with the following extensions:
bmp
css
gif
hbs
html
ico
jpg
js
less
override
php
png
tif
tpl
xml
*All other file extensions are excluded from the mapping.
Class Loading Directories
The autoloader will scan and autoload classes in the following directories:
./clients/base/api/
./data/duplicatecheck/
./data/Relationships/
./data/visibility/


./data/Relationships/
./data/visibility/
./include/
./include/api/
./include/CalendarEvents/
./include/SugarSearchEngine/
./modules/Calendar/
./modules/Mailer/
Ignored Directories
The following directories in Sugar are ignored by the autoloader mapping:
./.idea/
./cache/
./custom/backup/
./custom/blowfish/
./custom/Extension/
./custom/history/
./custom/modulebuilder/
./docs/
./examples/
./portal/
./tests/
./upload/
./vendor/bin/
./vendor/HTMLPurifier/
./vendor/log4php/
./vendor/nusoap/
./vendor/pclzip/
./vendor/reCaptcha/


./vendor/pclzip/
./vendor/reCaptcha/
./vendor/ytree/
Â
TopicsConfiguration APIMethods to configure loading paths for the AutoLoader API.File Check APIFile check methods for use with the AutoLoader API.File Map Modification APIMethods to modify files in the AutoLoader API. All the functions below return true on success and false on failure.Metadata APIMethods to load metadata for the AutoLoader API.
  	Last modified: 2023-02-03 21:04:03


Configuration API
Overview
Methods to configure loading paths for the AutoLoader API.
addDirectory($dir)
Adds a directory to the directory map for loading classes. Directories added should include a trailing "/".
SugarAutoloader::addDirectory('relative/file/path/');
addPrefixDirectory($prefix, $dir)
Adds a prefix and directory to the $prefixMap for loading classes by prefix.
SugarAutoloader::addPrefixDirectory('myPrefix', 'relative/file/path/');
  	Last modified: 2023-02-03 21:04:03


Metadata API
Overview
Methods to load metadata for the AutoLoader API.
Metadata Loading
For the specific sets of metadata, such as detailviewdefs, editviewefs, listviewdefs, searchdefs, popupdefs, and searchfields, a special process is used to load the correct metadata file. You should note that the variable name for the defs, e.g. "detailviewdefs", is usually the same as variable name, except in the case of "searchfields" where it is "SearchFields".
The process is described below:
If ./custom/modules/{$module}/metadata/{$varname}.php exists, it is used as the data file.


If ./modules/{$module}/metadata/metafiles.php or ./custom/modules/{$module}/metadata/metafiles.php exists, it is loaded with the custom file being preferred. If the variable name exists in the data specified by the metafile, the corresponding filename is assumed to be the defs file name.
If the defs file name or its custom/ override exists, it is used as the data file (custom one is preferred).
If no file has been found yet, ./modules/{$module}/metadata/{$varname}.php is checked and if existing, it is used as the data file.
Otherwise, no metadata file is used.
loadWithMetafiles($module, $varname)


loadWithMetafiles($module, $varname)
Returns the specified metadata file for a specific module. You should note that due to the scope nature of include(), this function does not load the actual metadata file but will return the file name that should be loaded by the caller.
$metadataPath = SugarAutoloader::loadWithMetafiles('Accounts', 'editviewdefs');
loadPopupMeta($module, $metadata = null)
Loads popup metadata for either specified $metadata variable or "popupdefs" variable via loadWithMetafiles() and returns it. If no metadata found returns empty array.
$popupMetadata = SugarAutoloader::loadPopupMeta('Accounts');
loadExtension($extname, $module = "application")


loadExtension($extname, $module = "application")
Returns the extension path given the extension name and module. For global extensions, the module should be "application" and may be omitted. If the extension has its own module, such as schedulers, it will be used instead of the $module parameter. You should note that due to the scope nature of include(), this function does not load the actual metadata file but return the file name that should be loaded by the caller. If no extension file exists it will return false.
//The list of extensions can be found in ./ModuleInstall/extensions.php
$extensionPath = SugarAutoloader::loadExtension('logichooks');
  	Last modified: 2023-02-03 21:04:03


File Map Modification API
Overview
Methods to modify files in the AutoLoader API. All the functions below return true on success and false on failure.
addToMap($filename, $save = true)
Adds an existing file to the file map. If $save is true, the new map will be saved to the disk file map, otherwise, it will persist only until the end of the request. This method does not create the file on the filesystem.
SugarAutoloader::addToMap('custom/myFile.php');
delFromMap($filename, $save = true)
Removes a file from the file map. If $filename points to a directory, this directory and all files under it are removed from the map. If $save is true, the new map will be saved to the disk file map, otherwise, it will persist only until the end of the request. This method does not delete the file from the filesystem.


SugarAutoloader::delFromMap('custom/myFile.php');
put($filename, $data, $save = false)
Saves data to a file on the filesystem and adds it to the file map. If $save is true, the new map will be saved to the disk file map, otherwise, it will persist only until the end of the request.
$file = 'custom/myFile.php';
SugarAutoloader::touch($file, true);
SugarAutoloader::put($file, '<?php /*file content*/ ?>', true);
touch($filename, $save = false)
Creates the specified file on the filesystem and adds it to the file map. If $save is true, the new map will be saved to the disk file map, otherwise, it will persist only until the end of the request.


SugarAutoloader::touch('custom/myFile.php', true);
unlink($filename, $save = false)
Removes the specified file from the filesystem and from theÂ current file map. If $save is true, the new map will be saved to the disk file map, otherwise, it will persist only until the end of the request.
SugarAutoloader::unlink('custom/myFile.php', true);
  	Last modified: 2023-02-03 21:04:03


File Check API
Overview
File check methods for use with the AutoLoader API.
existing(...)
Returns an array of filenames that exist in the file map. Accepts any number of arguments of which can be filename or array of filenames. If no files exist, empty array is returned.
$files = SugarAutoloader::existing('include/utils.php', 'include/TimeDate.php');
existingCustom(...)
This method accepts any number of arguments, each of which can be filename or array of filenames. It will return an array of filenames that exist in the file map, adding also files that exist when custom/ is prepended to them. If the original


filename already had custom/ prefix, it is not prepended again. custom/ files are added to the list after the root directory files so that if included in order, they will override the data of the root file. If no files exist, empty array is
            returned.
$files = SugarAutoloader::existingCustom('include/utils.php', 'include/TimeDate.php');
existingCustomOne(...)
Returns the last file of the result returned by existingCustom(), or null if none exist. Accepts any number of arguments of which can be filename or array of filenames. Since customized files are placed after the root files, it will return customized file if exists, otherwise root file.

$files = SugarAutoloader::existingCustomOne('include/utils.php');
You should note that the existingCustomOne() method can be used for loading inline PHP files. An example is shown below:


foreach(SugarAutoLoader::existingCustomOne('custom/myFile.php') as $file)
{
    include $file;
}
Alternative to including inline PHP files, loading class files should be done using requireWithCustom() .
fileExists($filename)
Checks if a file exists in the file map. You should note that ".." is not supported by this function and any paths including ".." will return false. The path components should be separated by /.
            You should also note that multiple slashes are compressed and treated as single slash.
$file = 'include/utils.php';
if (SugarAutoloader::fileExists($file))
{
    require_once($file);
}
getDirFiles($dir, $get_dirs = false, $extension = null)


Retrieves the list of files existing in the file map under the specified directory. If no files are found, the method will return an empty array. By default, the method will return file paths, however, If $get_dirs is set to true, the method will
            return only directories. If $extension is set, it would return only files having that specific extension.

$files = SugarAutoloader::getDirFiles('include');
getFilesCustom($dir, $get_dirs = false, $extension = null)
Retrieves the list of files existing in the file map under the specified directory and under it's custom/ path. If no files are found it will return empty array. By default, the method will return file paths, however, If $get_dirs is set to true,


the method will return only directories. If $extension is set, it would return only files having that specific extension.

$files = SugarAutoloader::getFilesCustom('include');
lookupFile($paths, $file)
Looks up a file in the list of given paths, including with and without custom/ prefix, and return the first match found. The custom/ directory is checked before root files. If no file is found, the method will return false.
$paths = array(
  'include',
  'modules',
);
$files = SugarAutoloader::lookupFile($paths, 'utils.php');
requireWithCustom($file, $both = false)


requireWithCustom($file, $both = false)
If a custom/ override of the file or the file exist, require_once it and return true, otherwise return false. If $both is set to true, both files are required with the root file being first and custom/ file being second. Unlike other functions,
            this function will actually include the file.
$file = SugarAutoloader::requireWithCustom('include/utils.php');
You should note that the requireWithCustom() method should be used for loading class files and not inline PHP files. Inline PHP files should be loaded using the existingCustomOne()
            method.
  	Last modified: 2023-02-03 21:04:03


Teams
Overview
Teams provide the ability to limit access at the record level, allowing sharing flexibility across functional groups.Â Developers can manipulate teams programmaticallyÂ providedÂ they understand Sugar'sÂ visibility framework.
For more information on teams, please refer to the Team Management documentation.
Database Tables
Table
Description
teams
Each record in this table represents a team in the system.
team_sets
Each record in this table represents a unique team combination. For example, each user's private team will have a corresponding team set entry in this table. A team set may also be comprised of one or more teams.
team_sets_teams
The team_sets_teams table maintains the relationships to determine which teams belong to a team set. Each table that previously used the team_id column to maintain team security now uses the team_set_id column's value to associate the record to team(s).


team_sets_modules
This table is used to manage team sets and keep track of which modules have records that are or were associated to a particular team set.
NeverÂ modify these tables without going through the PHP object methods. Manipuating the team sets with direct SQLÂ may cause undesired side effects in the system due to how the validations and security methods work.
Module Team Fields
In addition to the team tables, each module table also contains a team_id and team_set_id column. The team_set_id contains the id of a record in the team_sets table that contains the unique combination of teams associated with the record. The team_id will contain the id of the primary team designated for a record.
Team Sets (team_set_id)


Team Sets (team_set_id)
As mentioned above, Sugar implemented this feature not as a many-to-many relationship but as a one-to-many relationship. On each table that had a team_id field we added a 'team_set_id' field. We have also added a team_sets table, which maintains the team_set_id, and a team_sets_teams table, which relates a team set to the teams. When performing a team based query we use the 'team_set_id' field on the module table to join to team_sets_teams.team_set_id and then join all of the teams associated with that set. Given the list of teams from team_memberships we can then decide if the user has access to the record.
Primary Team (team_id)


Primary Team (team_id)
The team_id is still being used, not only to support backwards compatibility with workflow and reports, but also to provide some additional features. When displaying a list, we use the team set to determine whether the user has access to the record. When displaying the data, we show the team from team id in the list. When the user performs a mouseover on that team, Sugar performs an Ajax call to display all of the teams associated with the record. This team_id field is designated as the Primary Team because it is the first team shown in the list, and for sales territory management purposes, can designate the team that actually owns the record and can report on it.
Team Security
The team_sets_teams table allows the system to check for permissions on multiple teams. The following diagram illustrates table relationships in SugarBean's add_team_security_where_clause method.


Using the team_sets_teams table the system will determine which teams are associated with the team_set_id and then look in the team_memberships table for users that belong to the team(s).
TeamSetLink
Typically, any relationship in a class is handled by the data/Link2.php class. As a part of dynamic teams, we introduced the ability to provide your own custom Link class to handle some of the functionality related to managing relationships. The team_security parent vardefs in the Sugar objects contain the following in the 'teams' field definition:
'link_class' => 'TeamSetLink',
'link_file' => 'modules/Teams/TeamSetLink.php',


'link_file' => 'modules/Teams/TeamSetLink.php',
The link_class entry defines the class name we are using, and the link_file tells us where that class file is located. This class extends the legacy Link.php and overrides some of the methods used to handle relationships such as 'add' and 'delete'.
TopicsManipulating Teams ProgrammaticallyHow to manipulate team relationships.Visibility FrameworkThe visibility framework provides the capability to alter the queries Sugar uses to retrieve records from the database. This framework can allow for additional restrictions or specific logic to meet business requirements of hiding or showing only specific records. Visibility classes only apply to certain aspects of Sugar record retrieval, e.g. List Views, Dashlets, and Filter Lookups.
  	Last modified: 2023-02-03 21:04:03


Visibility Framework
Overview
The visibility framework provides the capability to alter the queries Sugar uses to retrieve records from the database. This framework can allow for additional restrictions or specific logic to meet business requirements of hiding or showing only specific records. Visibility classes only apply to certain aspects of Sugar record retrieval, e.g. List Views, Dashlets, and Filter Lookups.
Custom Row Visibility
Custom visibility class files are stored under ./custom/data/visibility/. The files in this directory can be enabled or disabled by modifying a module's visibility property located inÂ ./custom/Extension/modules/<module>/Ext/Vardefs/. Every enabled visibility class is merged into the module's definition, allowing multiple layers of logic to be added to a module.
Visibility Class


Visibility Class
To add custom row visibility, you must create a visibility class that will extend the coreÂ SugarVisibility class ./data/SugarVisibility.php .Â The visibility class has the ability to override the following methods:
Name
Description
addVisibilityQuery
Add visibility clauses to a SugarQuery object.
addVisibilityFrom
[Deprecated] Add visibility clauses to the FROM part of the query. This method shouldÂ still be implemented, as not all objects have been switched over to use addVisibilityQuery() method.
addVisibilityFromQuery
[Deprecated] Add visibility clauses to the FROM part of SugarQuery.Â This method shouldÂ still be implemented, as not all objects have been switched over to use addVisibilityQuery() method.
addVisibilityWhere


addVisibilityWhere
[Deprecated] Add visibility clauses to the WHERE part of the query.Â This method shouldÂ still be implemented, as not all objects have been switched over to useÂ addVisibilityQuery() method.
addVisibilityWhereQuery
[Deprecated] Add visibility clauses to the WHERE part of SugarQuery.Â This method shouldÂ still be implemented, as not all objects have been switched over to use addVisibilityQuery() method.
The visibility class should also implementÂ Sugarcrm\Sugarcrm\Elasticsearch\Provider\Visibility\StrategyInterfaceÂ so that the visibility rules are also applied to the global search.Â Â StrategyInterfaceÂ has the following functions that should be implemented:
Name
Description
elasticBuildAnalysis
Build Elasticsearch analysis settings.Â  This function can be empty if you do not need any special analyzers.


elasticBuildMapping
Build Elasticsearch mapping.Â  This function should contain a mapping of fields that should be analyzed.
elasticProcessDocumentPreIndex
Process document before it's being indexed. This function should perform any actions to the document that needs to be completed beforeÂ it is indexed.Â
elasticGetBeanIndexFields
Bean index fields to be indexed.Â  This function should return an array of the fields that need to be indexed as part of yourÂ custom visibility.
elasticAddFilters
Add visibility filters.Â  This function should apply the Elastic filters.
Example
The following example creates a custom visibility filter that determines whether Opportunity records should be displayed based on their Sales Stage.Â  Opportunity records with Sales Stage set to Closed Won or Closed Lost will not be displayed in the Opportunities module or global search for users with the Demo Visibility role.Â Â


/custom/data/visibility/FilterOpportunities.php:
<?php
use Sugarcrm\Sugarcrm\Elasticsearch\Provider\Visibility\StrategyInterface as ElasticStrategyInterface;
use Sugarcrm\Sugarcrm\Elasticsearch\Provider\Visibility\Visibility;
use Sugarcrm\Sugarcrm\Elasticsearch\Analysis\AnalysisBuilder;
use Sugarcrm\Sugarcrm\Elasticsearch\Mapping\Mapping;
use Sugarcrm\Sugarcrm\Elasticsearch\Adapter\Document;
/**
 *
 * Custom visibility class for Opportunities module:
 *
 * This demo allows to restrict access to opportunity records based on the
 * user's role and configured filtered sales stages.
 *
 * The following $sugar_config parameters are available:
 *


*
 * The following $sugar_config parameters are available:
 *
 * $sugar_config['custom']['visibility']['opportunities']['target_role']
 * This parameter takes a string containing the role name for which
 * the filtering should apply.
 *
 * $sugar_config['custom']['visibility']['opportunities']['filter_sales_stages']
 * This parameters takes an array of filtered sales stages. If current user is
 * member of the above configured role, then the opportunities with the sale
 * stages as configured in this array will be inaccessible.
 *
 *
 * Example configuration given that 'Demo Visibility' role exists (config_override.php):
 *


*
 * $sugar_config['custom']['visibility']['opportunities']['target_role'] = 'Demo Visibility';
 * $sugar_config['custom']['visibility']['opportunities']['filter_sales_stages'] = array('Closed Won', 'Closed Lost');
 *
 */
class FilterOpportunities extends SugarVisibility implements ElasticStrategyInterface
{
    /**
     * The target role name
     * @var string
     */
    protected $targetRole = '';
    /**
     * Filtered sales stages
     * @var array
     */
    protected $filterSalesStages = array();
    /**
     * {@inheritdoc}
     */
    public function __construct(SugarBean $bean, $params = null)
    {


{
        parent::__construct($bean, $params);
        $config = SugarConfig::getInstance();
        $this->targetRole = $config->get(
            'custom.visibility.opportunities.target_role',
            $this->targetRole
        );
        $this->filterSalesStages = $config->get(
            'custom.visibility.opportunities.filter_sales_stages',
            $this->filterSalesStages
        );
    }
    /**
     * {@inheritdoc}
     */
    public function addVisibilityWhere(&$query)
    {
        if (!$this->isSecurityApplicable()) {
            return $query;
        }
        $whereClause = sprintf(
            "%s.sales_stage NOT IN (%s)",


"%s.sales_stage NOT IN (%s)",
            $this->getTableAlias(),
            implode(',', array_map(array($this->bean->db, 'quoted'), $this->filterSalesStages))
        );
        if (!empty($query)) {
            $query .= " AND $whereClause ";
        } else {
            $query = $whereClause;
        }
        return $query;
    }
    /**
     * {@inheritdoc}
     */
    public function addVisibilityWhereQuery(SugarQuery $sugarQuery, $options = array())
    {
        $where = null;
        $this->addVisibilityWhere($where, $options);
        if (!empty($where)) {
            $sugarQuery->where()->addRaw($where);
        }


$sugarQuery->where()->addRaw($where);
        }
        return $sugarQuery;
    }
    /**
     * Check if we can apply our security model
     * @param User $user
     * @return false|User
     */
    protected function isSecurityApplicable(User $user = null)
    {
        $user = $user ?: $this->getCurrentUser();
        if (!$user) {
            return false;
        }
        if (empty($this->targetRole) || empty($this->filterSalesStages)) {
            return false;
        }
        if (!is_string($this->targetRole) || !is_array($this->filterSalesStages)) {
            return false;
        }
        if (!$this->isUserMemberOfRole($this->targetRole, $user)) {


return false;
        }
        if ($user->isAdminForModule("Opportunities")) {
            return false;
        }
        return $user;
    }
    /**
     * Get current user
     * @return false|User
     */
    protected function getCurrentUser()
    {
        return empty($GLOBALS['current_user']) ? false : $GLOBALS['current_user'];
    }
    /**
     * Check if given user has a given role assigned
     * @param string $targetRole Name of the role
     * @param User $user
     * @return boolean
     */
    protected function isUserMemberOfRole($targetRole, User $user)
    {
        $roles = ACLRole::getUserRoleNames($user->id);


$roles = ACLRole::getUserRoleNames($user->id);
        return in_array($targetRole, $roles) ? true : false;
    }
    /**
     * Get table alias
     * @return string
     */
    protected function getTableAlias()
    {
        $tableAlias = $this->getOption('table_alias');
        if (empty($tableAlias)) {
            $tableAlias = $this->bean->table_name;
        }
        return $tableAlias;
    }
    /**
     * {@inheritdoc}
     */
    public function elasticBuildAnalysis(AnalysisBuilder $analysisBuilder, Visibility $provider)
    {
        // no special analyzers needed
    }
    /**
     * {@inheritdoc}
     */


}
    /**
     * {@inheritdoc}
     */
    public function elasticBuildMapping(Mapping $mapping, Visibility $provider)
    {
        $mapping->addNotAnalyzedField('visibility_sales_stage');
    }
    /**
     * {@inheritdoc}
     */
    public function elasticProcessDocumentPreIndex(Document $document, \SugarBean $bean, Visibility $provider)
    {
        // populate the sales_stage into our explicit filter field
        $sales_stage = isset($bean->sales_stage) ? $bean->sales_stage : '';
        $document->setDataField('visibility_sales_stage', $sales_stage);
    }
    /**
     * {@inheritdoc}
     */


}
    /**
     * {@inheritdoc}
     */
    public function elasticGetBeanIndexFields($module, Visibility $provider)
    {
        // make sure to pull sales_stage regardless of search
        return array('sales_stage');
    }
    /**
     * {@inheritdoc}
     */
    public function elasticAddFilters(User $user, Elastica\Query\BoolQuery $filter,
                                      Sugarcrm\Sugarcrm\Elasticsearch\Provider\Visibility\Visibility $provider)
    {
        if (!$this->isSecurityApplicable($user)) {
            return;
        }
        // apply elastic filter to exclude the given sales stages
        $filter->addMustNot($provider->createFilter(
            'OpportunitySalesStages',


'OpportunitySalesStages',
            array(
                'filter_sales_stages' => $this->filterSalesStages,
            )
        ));
    }
}
./custom/Extension/modules/Opportunities/Ext/Vardefs/opp_visibility.php
<?php
if (!defined('sugarEntry') || !sugarEntry) {
    die('Not A Valid Entry Point');
}
$dictionary['Opportunity']['visibility']['FilterOpportunities'] = true;
./custom/src/Elasticsearch/Provider/Visibility/Filter/OpportunitySalesStagesFilter.php
<?php
namespace Sugarcrm\Sugarcrm\custom\Elasticsearch\Provider\Visibility\Filter;