Example
The example below will create a new view that extends the ProductBundlesQuoteDataGroupList view and append a row action to the Group List's vertical elipsis action list.
First, create your custom view type that will extend the ProductBundlesQuoteDataGroupList view. This will contain the JavaScript for your action.
./custom/modules/ProductBundles/clients/base/views/quote-data-group-list/quote-data-group-list.js
({
    extendsFrom: 'ProductBundlesQuoteDataGroupListView',
    initialize: function(options) {
        this.events = _.extend({}, this.events, options.def.events, {
            'click [name="gl-custom-action"]': 'actionClicked'
        });
        this._super('initialize', [options]);
    },
    /**


this._super('initialize', [options]);
    },
    /**
     * Click event
     */
    actionClicked: function() {
        app.alert.show('success_alert', {
            level: 'success',
            title: 'List Header Row Action was clicked!'
        });
    },
})
This code will append a click event to our field named gl-custom-action and trigger the actionClicked method. Once completed, add your new row action to the group list in the $viewdefs['ProductBundles']['base']['view']['quote-data-group-list']['selection']['actions'] index.
./custom/modules/ProductBundles/clients/base/views/quote-data-group-list/quote-data-group-list.php
<?php


<?php
$viewdefs['ProductBundles']['base']['view']['quote-data-group-list'] = array(
    'selection' => array(
        'type' => 'multi',
        'actions' => array(
            array(
                'type' => 'rowaction',
                'name' => 'edit_row_button',
                'label' => 'LBL_EDIT_BUTTON',
                'tooltip' => 'LBL_EDIT_BUTTON',
                'acl_action' => 'edit',
            ),
            array(
                'type' => 'rowaction',
                'name' => 'delete_row_button',
                'label' => 'LBL_DELETE_BUTTON',
                'tooltip' => 'LBL_DELETE_BUTTON',


'tooltip' => 'LBL_DELETE_BUTTON',
                'acl_action' => 'delete',
            ),
            array(
                'type' => 'rowaction',
                'name' => 'gl-custom-action',
                'label' => 'LBL_GL_CUSTOM_ACTION',
                'tooltip' => 'LBL_GL_CUSTOM_ACTION_TOOLTIP',
                'acl_action' => 'edit',
            ),
        ),
    ),
);
Finally, create labels under Quotes for the label and tooltip indexes. To accomplish this, create a language extension:
./custom/Extension/modules/Quotes/Ext/Language/en_us.gh-custom-action.php
<?php
$mod_strings['LBL_GL_CUSTOM_ACTION'] = 'Custom Action';


$mod_strings['LBL_GL_CUSTOM_ACTION_TOOLTIP'] = 'Custom Action Tooltip';
Once the files are in place, navigate to Admin > Repair > Quick Repair and Rebuild. Your changes will now be reflected in the system.
Group Footer
The Group Footer contains the total for each grouping of quoted line items.
Modifying Fields in the Group Footer


Modifying Fields in the Group Footer
To modify the GroupFooter fields, you can copy ./modules/ProductBundles/clients/base/views/quote-data-group-footer/quote-data-group-footer.php to ./custom/modules/ProductBundles/clients/base/views/quote-data-group-footer/quote-data-group-footer.php or you may create a metadata extension in ./custom/Extension/modules/ProductBundles/clients/base/views/quote-data-group-footer/. Next, modify the $viewdefs['ProductBundles']['base']['view']['quote-data-group-footer'] index to add or remove your preferred fields.
Example


Example
The example below will append the bundle stage (product_bundles.bundle_stage) field to the Group Footer. It's important to note that when adding additional fields, that changes to the corresponding .hbs file may be necessary to correct any formatting issues.
./custom/modules/ProductBundles/clients/base/views/quote-data-group-header/quote-data-group-footer.php
<?php
$viewdefs['ProductBundles']['base']['view']['quote-data-group-footer'] = array(
    'panels' => array(
        array(
            'name' => 'panel_quote_data_group_footer',
            'label' => 'LBL_QUOTE_DATA_GROUP_FOOTER',
            'fields' => array(
                'bundle_stage',


'fields' => array(
                'bundle_stage',
                array(
                    'name' => 'new_sub',
                    'label' => 'LBL_GROUP_TOTAL',
                    'type' => 'currency',
                ),
            ),
        ),
    ),
);
If adding custom fields from the Product Bundles module to the view, you will also need to add that field to the related_fields array as outlined in the Record View documentation below. Once the files are in place, navigate to Admin > Repair > Quick Repair and Rebuild. Your changes will now be reflected in the system.
Grand Totals Footer
The Grand Total Footer contains the calculated totals for the quote. It mimics the information found in the Grand Total Header.
Modifying Fields in the Grand Totals Footer


Modifying Fields in the Grand Totals Footer
To modify the Grand Totals Footer fields, you can copy ./modules/Quotes/clients/base/views/quote-data-grand-totals-footer/quote-data-grand-totals-footer.php to ./custom/modules/Quotes/clients/base/views/quote-data-grand-totals-footer/quote-data-grand-totals-footer.php or you may create a metadata extension in ./custom/Extension/modules/Quotes/clients/base/views/quote-data-grand-totals-footer/. Next, modify the $viewdefs['Quotes']['base']['view']['quote-data-grand-totals-footer']['panels'][0]['fields'] index to add or remove your preferred fields.
Example


Example
The example below will remove the "Shipping" field (quotes.shipping) field from the layout.
./custom/modules/Quotes/clients/base/views/quote-data-grand-totals-footer/quote-data-grand-totals-footer.php
<?php
$viewdefs['Quotes']['base']['view']['quote-data-grand-totals-footer'] = array(
    'panels' => array(
        array(
            'name' => 'panel_quote_data_grand_totals_footer',
            'label' => 'LBL_QUOTE_DATA_GRAND_TOTALS_FOOTER',
            'fields' => array(
                'quote_num',
                array(
                    'name' => 'new_sub',


array(
                    'name' => 'new_sub',
                    'type' => 'currency',
                ),
                array(
                    'name' => 'tax',
                    'type' => 'currency',
                    'related_fields' => array(
                        'taxrate_value',
                    ),
                ),
                array(
                    'name' => 'total',
                    'label' => 'LBL_LIST_GRAND_TOTAL',
                    'type' => 'currency',
                    'css_class' => 'grand-total',
                ),
            ),
        ),
    ),
);


),
            ),
        ),
    ),
);
If adding custom fields from the QuotesÂ module to the view, you will also need to add that field to the related_fields array as outlined in the Record View documentation below. Once the files are in place, navigate to Admin > Repair > Quick Repair and Rebuild. Your changes will now be reflected in the system.
Record View
The record view for Quotes is updated and used like the standard Record View for all modules. The one major difference to note is that the JavaScript models used by the previous views above, are derived from the Model defined in the record view metadata.
Adding Custom Related Fields to Model


Adding Custom Related Fields to Model
In the Record View metadata, the firstÂ panel panel_header containing the picture and name fields for the Quote record, contains a related_fields property in the name definition that defines the related Product Bundles and Products data that is pulled into the JavaScript model. When custom fields are added to the above mentioned views you will need to add them to the related_fields property in their respective related module so that they are properly loaded during the page load.
The following example adds the custom_product_bundle_field_c field from the Product Bundles module, and the custom_product_field_c from the Products module into the retrieved Model.
./custom/modules/Quotes/clients/base/views/record/record.php
<?php
$viewdefs['Quotes']['base']['view']['record'] = array(
    ...
    'panels' => array(


...
    'panels' => array(
        array(
            'name' => 'panel_header',
            'label' => 'LBL_PANEL_HEADER',
            'header' => true,
            'fields' => array(
                array(
                    'name' => 'picture',
                    'type' => 'avatar',
                    'size' => 'large',
                    'dismiss_label' => true,
                    'readonly' => true,
                ),
                array(
                   'name' => 'name',
                   'events' => array(
                       'keyup' => 'update:quote',
                   ),
                   'related_fields' => array(
                       array(
                           'name' => 'bundles',
                           'fields' => array(
                              'id',


'fields' => array(
                              'id',
                              'bundle_stage',
                              'currency_id',
                              'base_rate',
                              'currencies',
                              'name',
                              'deal_tot',
                              'deal_tot_usdollar',
                              'deal_tot_discount_percentage',
                              'new_sub',
                              'new_sub_usdollar',
                              'position',
                              'related_records',
                              'shipping',
                              'shipping_usdollar',
                              'subtotal',
                              'subtotal_usdollar',
                              'tax',
                              'tax_usdollar',
                              'taxrate_id',
                              'team_count',
                              'team_count_link',


'team_count',
                              'team_count_link',
                              'team_name',
                              'taxable_subtotal',
                              'total',
                              'total_usdollar',
                              'default_group',
                              'custom_product_bundle_field_c',
                              array(
                                  'name' => 'product_bundle_items',
                                  'fields' => array(
                                      'name',
                                      'quote_id',
                                      'description',
                                      'quantity',
                                      'product_template_name',
                                      'product_template_id',
                                      'deal_calc',
                                      'mft_part_num',
                                      'discount_price',
                                      'discount_amount',
                                      'tax',


'discount_amount',
                                      'tax',
                                      'tax_class',
                                      'subtotal',
                                      'position',
                                      'currency_id',
                                      'base_rate',
                                      'discount_select',
                                      'custom_product_field_c',
                                  ),
                                  'max_num' => -1,
                              ),
                          ),
                          'max_num' => -1,
                          'order_by' => 'position:asc',
                      ),
                  ),
              ),
          ),
      ),
      ...
  ),
);
Quote PDFs
Ungrouped Quoted Line Items and Notes


),
);
Quote PDFs
Ungrouped Quoted Line Items and Notes
As of Sugar 7.9, the quotes module allows users to add quoted line items and notes without first adding a group. Adding at least one group to your quote was mandatory in earlier versions. This document will cover how to update your custom PDF templates to support ungrouped QLIs and notes. Ungrouped items are technically added to a default group. The goal is to exclude this group when no items exist in it.
PDF Manager Templates
In your PDF Manager templates, you may have code similar to what is shown below to iterate over the groups in a quote:
{foreach from=$product_bundles item="bundle"}
    ....
{/foreach}


....
{/foreach}
To correct this for 7.9, we will need to add an if statement to check to see if the group is empty. If it is empty, it will be ignored in your PDF. The benefit is that it will also exclude any other empty groups in your quote and clean the generated PDF.
{foreach from=$product_bundles item="bundle"}
    {if $bundle.products|@count}
        ....
    {/if}
{/foreach}
Smarty Templates
In Smarty templates, you may have code similar to what is shown below to iterate over the groups in a quote:
{literal}{foreach from=$product_bundles item="bundle"}{/literal}
    ...
{literal}{/foreach}{/literal}


...
{literal}{/foreach}{/literal}
To correct this for 7.9, we will need to add an if statement to check to see if the group is empty. If it is empty, it will be ignored in your PDF. The benefit is that it will also exclude any other empty groups in your quote and clean the generated PDF.
{literal}{foreach from=$product_bundles item="bundle"}{/literal}
    {literal}{if $bundle.products|@count}{/literal}
        ...
    {literal}{/if}{/literal}
{literal}{/foreach}{/literal}
Creating Custom PDF Templates


{literal}{/foreach}{/literal}
Creating Custom PDF Templates
With Sugar, there are generally two routes that can be taken to create custom PDF templates. The first and most recommended route is to use the PDF Manager found in the Admin > PDF Manager. The second route is to extend the Sugarpdf class and write your own template using PHP and the TCPDF library. This method should only be used if you are unable to accomplish your business needs through the PDF Manager.
Creating the TCPDF Template


Creating the TCPDF Template
The first step is to create your custom TCPDF template in ./custom/modules/Quotes/sugarpdf/. For our example, we will extend QuotesSugarpdfStandard, found at ./modules/Quotes/sugarpdf/sugarpdf.standard.php, to a new file in ./custom/modules/Quotes/sugarpdf/ to create a custom invoice. The QuotesSugarpdfStandard class sets up our general quote requirements and extends the Sugarpdf class. Technical documentation on templating can be found in the Sugar PDF documentation. Our class will be named QuotesSugarpdfCustomInvoice as the naming must be in the format of <module>Sugarpdf<pdf view>.
./custom/modules/Quotes/sugarpdf/sugarpdf.custominvoice.php
<?php


<?php
if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
require_once('modules/Quotes/sugarpdf/sugarpdf.standard.php');
class QuotesSugarpdfCustomInvoice extends QuotesSugarpdfStandard
{
    function preDisplay()
    {
        global $mod_strings, $timedate;
        parent::preDisplay();
        $quote[0]['TITLE'] = $mod_strings['LBL_PDF_INVOICE_NUMBER'];
        $quote[1]['TITLE'] = $mod_strings['LBL_PDF_QUOTE_DATE'];
        $quote[2]['TITLE'] = $mod_strings['LBL_PURCHASE_ORDER_NUM'];


$quote[3]['TITLE'] = $mod_strings['LBL_PAYMENT_TERMS'];
        $quote[0]['VALUE']['value'] = format_number_display($this->bean->quote_num, $this->bean->system_id);
        $quote[1]['VALUE']['value'] = $timedate->nowDate();
        $quote[2]['VALUE']['value'] = $this->bean->purchase_order_num;
        $quote[3]['VALUE']['value'] = $this->bean->payment_terms;
        // these options override the params of the $options array.
        $quote[0]['VALUE']['options'] = array();
        $quote[1]['VALUE']['options'] = array();


$quote[1]['VALUE']['options'] = array();
        $quote[2]['VALUE']['options'] = array();
        $quote[3]['VALUE']['options'] = array();
        $html = $this->writeHTMLTable($quote, true, $this->headerOptions);
        $this->SetHeaderData(PDF_HEADER_LOGO, PDF_HEADER_LOGO_WIDTH, $mod_strings['LBL_PDF_INVOICE_TITLE'], $html);
    }
    /**
     * This method build the name of the PDF file to output.
     */
    function buildFileName()
    {
        global $mod_strings;


function buildFileName()
    {
        global $mod_strings;
        $fileName = preg_replace("#[^A-Z0-9\-_\.]#i", "_", $this->bean->shipping_account_name);
        if (!empty($this->bean->quote_num)) {
            $fileName .= "_{$this->bean->quote_num}";
        }
        $fileName = $mod_strings['LBL_INVOICE'] . "_{$fileName}.pdf";
        if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match("/MSIE/", $_SERVER['HTTP_USER_AGENT'])) {
            //$fileName = $locale->translateCharset($fileName, $locale->getExportCharset());


$fileName = urlencode($fileName);
        }
        $this->fileName = $fileName;
    }
}
Next, register the layout. This is a two step process. The first step is to create ./custom/modules/Quotes/Layouts.php which will map our view action to the physical PHP file.
./custom/modules/Quotes/Layouts.php
<?php
$layouts['CustomInvoice'] = 'custom/modules/Quotes/sugarpdf/sugarpdf.custominvoice.php';


The second step is to update the layouts_dom dropdown list by navigating to Admin > Dropdown Editor. Once there, create a new entry in the list with an Item Name of "CustomInvoice" and a Display Label of your choice. It's also recommended to remove the Quote and Invoice Templates if they are not being used to avoid confusion. This will create a ./custom/Extension/application/Ext/Language/en_us.sugar_layouts_dom.php file that should look similar to:
./custom/Extension/application/Ext/Language/en_us.sugar_layouts_dom.php
<?php
$app_list_strings['layouts_dom']=array (
  'CustomInvoice' => 'Custom Invoice',
);


'CustomInvoice' => 'Custom Invoice',
);
Once the layout is registered, we need to extendÂ the pdfaction field for the Quotes module to render our custom pdf templates in the record view. An example of this is shown below:
./custom/modules/Quotes/clients/base/fields/pdfaction/pdfaction.js
/**
 * @class View.Fields.Base.Quotes.PdfactionField
 * @alias SUGAR.App.view.fields.BaseQuotesPdfactionField
 * @extends View.Fields.Base.PdfactionField
 */
({
    extendsFrom: 'PdfactionField',
    /**
     * @inheritdoc
     * Create PDF Template collection in order to get available template list.
     */
    initialize: function(options) {
        this._super('initialize', [options]);


this._super('initialize', [options]);
    },
    /**
     * Define proper filter for PDF template list.
     * Fetch the collection to get available template list.
     * @private
     */
    _fetchTemplate: function() {
        this.fetchCalled = true;
        var collection = this.templateCollection;
        collection.filterDef = {'$and': [{
            'base_module': this.module
        }, {
            'published': 'yes'
        }]};
        collection.fetch({
            success: function(){
                var models = [];
                _.each(app.lang.getAppListStrings('layouts_dom'), function(template, id){
                    var model = new Backbone.Model({
                        id: id,
                        name: template
                    });


id: id,
                        name: template
                    });
                    models.push(model);
                });
                collection.add(models);
            }
        });
    },
    /**
     * Build download link url.
     *
     * @param {String} templateId PDF Template id.
     * @return {string} Link url.
     * @private
     */
    _buildDownloadLink: function(templateId) {
        var sugarpdf = this._isUUID(templateId)? 'pdfmanager' : templateId;
        var urlParams = $.param({
            'action': 'sugarpdf',
            'module': this.module,
            'sugarpdf': sugarpdf,
            'record': this.model.id,
            'pdf_template_id': templateId
        });


'pdf_template_id': templateId
        });
        return '?' + urlParams;
    },
    /**
     * Build email pdf link url.
     *
     * @param {String} templateId PDF Template id.
     * @return {string} Email pdf url.
     * @private
     */
    _buildEmailLink: function(templateId) {
        var sugarpdf = this._isUUID(templateId)? 'pdfmanager' : templateId;
        return '#' + app.bwc.buildRoute(this.module, null, 'sugarpdf', {
                'sugarpdf': sugarpdf,
                'record': this.model.id,
                'pdf_template_id': templateId,
                'to_email': '1'
            });
    },
    /**


'to_email': '1'
            });
    },
    /**
     * tests to see if a templateId is a uuid or template name.
     *
     * @param {String} templateId PDF Template id
     * @return {boolean} true if uuid, false if not
     * @private
     */
    _isUUID: function(templateId) {
        var regex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
        return regex.test(templateId);
    }
})


return regex.test(templateId);
    }
})
Once the files are in place, navigate to Admin > Repair > Quick Repair and Rebuild. Your changes will now be reflected in the system and navigating to a url in the format of index.php?module=Quotes&record=<record id>&action=sugarpdf&sugarpdf=CustomInvoice will generate the pdf document.
Hiding PDF Buttons


Hiding PDF Buttons
In some circumstances, users may not have a need to generate PDFs from Quotes. If this should occur, a developer can copy ./modules/Quotes/clients/base/views/record/record.phpÂ to ./custom/modules/Quotes/clients/base/views/record/record.phpÂ if it doesn't already exist. Next, find the array with a name of main_dropwdown inÂ the $viewdefs['Quotes']['base']['view']['record']['buttons']Â index. Once found, you will then need to locate the buttons index within thatÂ array. You will then need to remove the following arrays from that index:
array(
    'type' => 'pdfaction',
    'name' => 'download-pdf',
    'label' => 'LBL_PDF_VIEW',


'label' => 'LBL_PDF_VIEW',
    'action' => 'download',
    'acl_action' => 'view',
),
array(
    'type' => 'pdfaction',
    'name' => 'email-pdf',
    'label' => 'LBL_PDF_EMAIL',
    'action' => 'email',
    'acl_action' => 'view',
),
Your file should look similar to what is shown below:
custom/modules/Quotes/clients/base/views/record/record.php
<?php
$viewdefs['Quotes']['base']['view']['record'] = array(
    'buttons' => array(
        array(
            'type' => 'button',
            'name' => 'cancel_button',


'name' => 'cancel_button',
            'label' => 'LBL_CANCEL_BUTTON_LABEL',
            'css_class' => 'btn-invisible btn-link',
            'showOn' => 'edit',
            'events' => array(
                'click' => 'button:cancel_button:click',
            ),
        ),
        array(
            'type' => 'rowaction',
            'event' => 'button:save_button:click',
            'name' => 'save_button',
            'label' => 'LBL_SAVE_BUTTON_LABEL',
            'css_class' => 'btn btn-primary',
            'showOn' => 'edit',
            'acl_action' => 'edit',
        ),
        array(


'acl_action' => 'edit',
        ),
        array(
            'type' => 'actiondropdown',
            'name' => 'main_dropdown',
            'primary' => true,
            'showOn' => 'view',
            'buttons' => array(
                array(
                    'type' => 'rowaction',
                    'event' => 'button:edit_button:click',
                    'name' => 'edit_button',
                    'label' => 'LBL_EDIT_BUTTON_LABEL',
                    'acl_action' => 'edit',
                ),
                array(
                    'type' => 'shareaction',
                    'name' => 'share',
                    'label' => 'LBL_RECORD_SHARE_BUTTON',
                    'acl_action' => 'view',
                ),
                array(


'acl_action' => 'view',
                ),
                array(
                    'type' => 'divider',
                ),
                array(
                    'type' => 'convert-to-opportunity',
                    'event' => 'button:convert_to_opportunity:click',
                    'name' => 'convert_to_opportunity_button',
                    'label' => 'LBL_QUOTE_TO_OPPORTUNITY_LABEL',
                    'acl_module' => 'Opportunities',
                    'acl_action' => 'create',
                ),
                array(
                    'type' => 'divider',
                ),
                array(
                    'type' => 'rowaction',
                    'event' => 'button:historical_summary_button:click',


'event' => 'button:historical_summary_button:click',
                    'name' => 'historical_summary_button',
                    'label' => 'LBL_HISTORICAL_SUMMARY',
                    'acl_action' => 'view',
                ),
                array(
                    'type' => 'rowaction',
                    'event' => 'button:audit_button:click',
                    'name' => 'audit_button',
                    'label' => 'LNK_VIEW_CHANGE_LOG',
                    'acl_action' => 'view',
                ),
                array(
                    'type' => 'rowaction',
                    'event' => 'button:find_duplicates_button:click',
                    'name' => 'find_duplicates_button',


'name' => 'find_duplicates_button',
                    'label' => 'LBL_DUP_MERGE',
                    'acl_action' => 'edit',
                ),
                array(
                    'type' => 'divider',
                ),
                array(
                    'type' => 'rowaction',
                    'event' => 'button:delete_button:click',
                    'name' => 'delete_button',
                    'label' => 'LBL_DELETE_BUTTON_LABEL',
                    'acl_action' => 'delete',
                ),
            ),
        ),
        array(
            'name' => 'sidebar_toggle',
            'type' => 'sidebartoggle',
        ),
    ),
    ...
);


),
    ),
    ...
);
Once the files are in place, navigate to Admin > Repair > Quick Repair and Rebuild. Your changes will now be reflected in the system.
  	Last modified: 2023-02-03 21:04:03


Logging
Overview
There 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.
PSR-3
PSR-3 compliant logging solution has been implemented based on PHP Monolog.Â
Log Levels
Log Level
Description
Debug
Logs events that help in debugging the application
Info
Logs informational messages and database queries
Warning
Logs potentially harmful events
Notice
Logs messages for deprecated methods that are still in use.
Error
Logs error events in the application
Alert
Logs severe error events that may cause the application to abort. This is the default and recommended level.
Critical
Logs events that may compromise the security of the application
Off
Turns off all logging


Logs events that may compromise the security of the application
Off
Turns off all logging
When you specify a logging level, the system will record messages for the specified level as well as all higher levels. For example, if you specify "Error", the system records all Error, Fatal, and Security messages. More information on logging levels can be found in theÂ logger levelÂ configuration documentation.
Considerations
When you are not troubleshooting Sugar, the log level should be setÂ toÂ FatalÂ inÂ Admin > System Settings > Logger Settings toÂ ensure that your environment is not wasting unnecessaryÂ resources to write to the Sugar log.
Logging Messages


Logging Messages
The PSR-3 implementation in Sugar can also be used to log messages to the Sugar Log file. You can utilize the implementation to log to the Sugar log file using the default channel or you can specify your own custom channel if you want further control over when your custom logs should be displayed.Â
use \Sugarcrm\Sugarcrm\Logger\Factory;
//Get the default Logger
$Logger = Factory::getLogger('default');
$Logger->debug('Debug level message');
$Logger->info('Info level message');
$Logger->notice('Notice level message');
$Logger->warning('Warning level message');
$Logger->error('Error level message');
$Logger->critical('Critical level message');
$Logger->alert('Alert level message');
$Logger->emergency('Emergency level message');


$Logger->emergency('Emergency level message');
//Get a custom Log Channel
$Logger = Factory::getLogger('my_logger');
Note: For more information on using custom channels, adding custom log handlers and processors see the PSR-3 LoggerÂ documentation.
Â
SugarLogger
The SugarLogger class, located in ./include/SugarLogger/SugarLogger.php, allows for developers and system administrators to log system events to a log file. Sugar then determines which events to write to the log based on the system's Log Level. This can be set in Admin > System Settings.
Log Levels
Log Level
Description
Debug
Logs events that help in debugging the application
Info
Logs informational messages and database queries
Warn
Logs potentially harmful events
Deprecated
Logs messages for deprecated methods that are still in use.
Error


Deprecated
Logs messages for deprecated methods that are still in use.
Error
Logs error events in the application
Fatal
Logs severe error events that may cause the application to abort. This is the default and recommended level.
Security
Logs events that may compromise the security of the application
Off
Logging is turned off
When you specify a logging level, the system will record messages for the specified level as well as all higher levels. For example, if you specify "Error", the system records all Error, Fatal, and Security messages. More information on logging levels can be found in the logger level documentation.
Considerations
When you are not troubleshooting Sugar, the log level should be set to FatalÂ in Admin > System Settings > Logger Settings toÂ ensure that your environment is not wasting unnecessaryÂ resources to write to the Sugar log.
Logging Messages
Using $GLOBALS['log']


Logging Messages
Using $GLOBALS['log']
How to log messages using $GLOBALS['log'] in the system.
$GLOBALS['log']->debug('Debug level message');
$GLOBALS['log']->info('Info level message');
$GLOBALS['log']->warn('Warn level message');
$GLOBALS['log']->deprecated('Deprecated level message');
$GLOBALS['log']->error('Error level message');
$GLOBALS['log']->fatal('Fatal level message');
$GLOBALS['log']->security('Security level message');
For more information on the implementation, please refer toÂ theÂ SugarLogger documentation.Â
Using LoggerManager
How to log messages using the LoggerManager.Â
$Logger = \LoggerManager::getLogger();


$Logger = \LoggerManager::getLogger();
$Logger->debug('Debug level message');
$Logger->info('Info level message');
$Logger->warn('Warn level message');
$Logger->deprecated('Deprecated level message');
$Logger->error('Error level message');
$Logger->fatal('Fatal level message');
$Logger->security('Security level message');
For more information on the implementation, please refer toÂ theÂ SugarLoggerÂ documentation.Â
Log Rotation


Log Rotation
TheÂ SugarLoggerÂ willÂ automatically rotate the logs when theÂ logger.file.maxSizeÂ configuration setting has been met or exceeded. When this happens, the Sugar log will be renamed with an integer. For example, if the Sugar log wasÂ named "sugarcrm.log, it will then be renamed "sugarcrm_1.log". The next log rotation after that would create "sugarcrm_2.log". This will occur until theÂ logger.file.maxLogsÂ configuration setting has been met. Once met, theÂ log rollover will start over.
Debugging Messages with _ppl()


Debugging Messages with _ppl()
When developing, it may be beneficial for a developer to use _ppl()Â method to log a message to the Sugar log. The _ppl() method handles converting Objects, so you can quickly dump an entire object to the log while testing during development.Â
_ppl('Debugging message');
This will write a message to the Sugar log that defines the message and file location. An example is shown below:
------------------------------ _ppLogger() output start -----------------------------
Debugging message
------------------------------ _ppLogger() output end -----------------------------
------------------------------ _ppLogger() file: myFile.php line#: 5-----------------------------
Â Note: It is important that you remove _ppl() from your code for production use as it will affect system performance.


TopicsCreating Custom LoggersCustom loggers, defined in ./custom/include/SugarLogger/, can be used to write log entries to a centralized application management tool or to write messages to a developer tool such as FirePHP.PSR-3 LoggerMonolog\Handler\HandlerInterfaceSugarLoggerThe SugarLogger is used for log reporting by the application. The following article outlines the LoggerTemplate interface as well as the LoggerManager object and explains how to programmatically use the SugarLogger.
  	Last modified: 2023-02-03 21:04:03


Creating Custom Loggers
Custom Loggers
Custom loggers,Â defined in ./custom/include/SugarLogger/, can be used to write log entries to a centralized application management tool or to write messages to a developer tool such as FirePHP.
To do this, you can create a new instance class that implements the LoggerTemplate interface. The following is an example of how to create a FirePHP logger.
./custom/include/SugarLogger/FirePHPLogger.php.
<?php
// change the path below to the path to your FirePHP install
require_once('/path/to/fb.php');
class FirePHPLogger implements LoggerTemplate
{
    /** Constructor */
    public function __construct()
    {
        if (
            isset($GLOBALS['sugar_config']['logger']['default'])


&& $GLOBALS['sugar_config']['logger']['default'] == 'FirePHP'
        )
        {
            LoggerManager::setLogger('default','FirePHPLogger');
        }
    }
    /** see LoggerTemplate::log() */
    public function log($level, $message)
    {
        // change to a string if there is just one entry
        if ( is_array($message) && count($message) == 1 )
        {
            $message = array_shift($message);
        }
        switch ($level)
        {
            case 'debug':
                FB::log($message);
                break;
            case 'info':
                FB::info($message);
                break;
            case 'deprecated':
            case 'warn':
                FB::warn($message);


case 'warn':
                FB::warn($message);
                break;
            case 'error':
            case 'fatal':
            case 'security':
                FB::error($message);
                break;
        }
    }
}
The only method that needs to be implemented by default is the log() method, which writes the log message to the backend. You can specify which log levels this backend can use in the constructor by calling the LoggerManager::setLogger() method and specifying the level to use for this logger in the first parameter; passing 'default' makes it the logger for all logging levels.
You will then specify your default logger as 'FirePHP' in your ./config_override.php file.
$sugar_config['logger']['default'] = 'FirePHP';
  	Last modified: 2023-02-03 21:04:03


PSR-3 Logger
Monolog\Handler\HandlerInterface
Overview
Sugar'sÂ PSR-3Â compliant logging solution has been implemented based on PHP Monolog. Accessing the PSR-3 Logger Objects can be done by via the \Sugarcrm\Sugarcrm\Logger namespace. Currently, this logging implementation is only used in a few areas of the system to allow for more in-depth logging in those areas, see the Usage section for more details.
Architecture
The PSR-3 Logging solution is found in ./src/Logger directory, which is mapped to the \Sugarcrm\Sugarcrm\Logger namespace in the application. The following outlines the architecture and current implementations of those core objects.
Factory


Factory
The Logger Factory Object, namespaced asÂ \Sugarcrm\Sugarcrm\Logger\Factory, is a singleton factory that creates and manages all Loggers currently being used by the system. The Logger Factory uses 'channels' to allow for multiple Loggers to be utilized and configured independently of default log level set for the system.Â It also allows for each channel to use different handlers, formatters, and processors. Check out the Usage section below for further details on configuration and usage.
Methods
getLogger($channel)
Returns the requested channels \Psr\Log\LoggerInterface implementation
Arguments
Name
Type
Description
$channel
String
The channel for which you are logging against
Example
use \Sugarcrm\Sugarcrm\Logger\Factory;
$Logger = Factory::getLogger('default');
Handlers


$Logger = Factory::getLogger('default');
Handlers
Handlers are the primary object used to manage the logs. They control how logs are submitted and where the logs will go. Since the PSR-3 Logging solution is based on Monolog, there are a number of default Handlers included in Sugar, however, they are not set up in the Sugar architecture to be utilized right off the bat. Sugar utilizes a Factory implementation for the handlers so that theÂ LoggerÂ Factory Object can build out specific Handlers for each channel based on the Sugar Config.
Factory Interface


Factory Interface
The Factory Interface for Handlers is used to implement a Logging Handler, so that the Logger Factory can build out the configured handler for a channel, without a lot of work from external developers. The Handler Factory Interface is located in ./src/Logger/Handlers/Factory.php or in code atÂ theÂ \Sugarcrm\Sugarcrm\Logger\Handler\Factory namespace.
Methods
There is only one method to implement forÂ aÂ Handler Factory, which is the create() method. This will contain the necessary Logic to set up the Logger Handler
create($level, $config)
Arguments
Name
Type
Description
$level
String
The log level the Logger Handler will operate atÂ
$config
Array
The config parameters set for the handler in the Sugar config file
Returns
The Monolog\Handler\HandlerInterface implementation
Implementations


Returns
The Monolog\Handler\HandlerInterface implementation
Implementations
By default, Sugar only comes with a single Handler implementation, which is the File Handler implementation. This is used to log directly to the ./sugarcrm.log file to mirror the functionality of the previous logging framework. For information on adding custom handlers, or implementing the built-in Monolog handlers inside of Sugar PSR-3 Logging framework, see the Customization section below.
Configuration
To configure the default logging handler for Sugar the following configuration setting is used:
$sugar_config['logger']['handler'] = '<handler>';
You can also configure a different Logging Handler or Handlers for a specific channel:
//Single Handler
$sugar_config['logger']['channels']['test_channel']['handlers'] = '<handler>';
//Multiple Channels


//Multiple Channels
$sugar_config['logger']['channels']['test_channel']['handlers'] = array('<handler1>','<handler2>');
To pass configuration properties to a Handler your configuration will need to look as follows:
//For system handler
$sugar_config['logger']['handlers']['<handler>']['host'] = '127.0.0.1';
$sugar_config['logger']['handlers']['<handler>']['port'] = 12201;
//For channel handlers
$sugar_config['logger']['channels']['test_channel']['handlers']['<handler>']['host'] = '127.0.0.1';


$sugar_config['logger']['channels']['test_channel']['handlers']['<handler>']['port'] = 12201;
$sugar_config['logger']['channels']['test_channel']['handlers']['<handler>']['level'] = 'debug';
Note: For more information, please refer to the logger.channels.channel.handlers documentation.
Formatters


Formatters
Formatters are a component of the Handler Object. By default Sugar only comes with a single Formatter, which is the BackwardCompatibleFormatter used by the File Handler, which simply assures that Log messages are consistent with the legacy Logging functionality. Formatters are used and built out in accordance with the Monolog framework, and under the majority of circumstances, building out a custom formatter is not necessary. For more information on Formatters, you can review the Monolog repository.
Processors
Processors provide a way to add more information to Log messages, without having to hard code this information inside the Handler, so that it can be used only when necessary. For example, Sugar's PSR-3 Logging implementation provides two default Processors that can be enabled for a given channel or handler via configuration. These Processors provide functionality such as adding a stack trace or the web request informationÂ to the log message to provide further debugging context.


Factory Interface
The Factory Interface for processorsÂ is used to implement a Logging Processor, so that the Logger Factory can build out the configured handler for a channel, without a lot of work from external developers. TheÂ ProcessorÂ Factory Interface is located in ./src/Logger/Processor/Factory.phpÂ or in code atÂ \Sugarcrm\Sugarcrm\Logger\Handler\FactoryÂ namespace.
Methods
There is only one method to implement forÂ aÂ Processor Factory, which is theÂ create()Â method. This method will contain the necessary Logic to set up the Processor object.Â
create($config)
Arguments
Name
Type
Description
$config
Array
The config parameters set for theÂ processorÂ in the Sugar config file
Returns


The config parameters set for theÂ processorÂ in the Sugar config file
Returns
Callable - See Customization section for an example of implementation, otherwise review the included Processor Factory implementations in code in ./src/Logger/Processor/Factory/.
Implementations
By default, Sugar comes with twoÂ ProcessorÂ implementations, \Sugarcrm\Sugarcrm\Logger\Processor\BacktraceProcessor andÂ \Sugarcrm\Sugarcrm\Logger\Processor\RequestProcessor.Â
BacktraceProcessor


BacktraceProcessor
As the name implies, the BacktraceProcessor appends a backtrace or stack trace to the log message output, to easily trace where and how the log message came from. The Factory implementation for this Processor lives inÂ ./src/Logger/Processor/Factory/Backtrace.php, and is referenced in the sugar config as backtrace.
The following shows an example of the output that occurs when utilizing this processor:


Fri Mar 23 09:24:19 2018 [90627][1][FATAL] <log message>; Call Trace: \n0: /var/www/Ent/71110/custom/SugarQueryLogger.php:43 - Sugarcrm\Sugarcrm\Logger\BackwardCompatibleAdapter::fatal()\n2: /var/www/Ent/71110/include/utils/LogicHook.php:270\n3: /var/www/Ent/71110/include/utils/LogicHook.php:160 - LogicHook::process_hooks()\n4: /var/www/Ent/71110/data/SugarBean.php:6684 - LogicHook::call_custom_logic()\n5:


- LogicHook::call_custom_logic()\n5: /var/www/Ent/71110/data/SugarBean.php:3317 - SugarBean::call_custom_logic()\n6: /var/www/Ent/71110/clients/base/api/FilterApi.php:632 - SugarBean::fetchFromQuery()\n7: /var/www/Ent/71110/clients/base/api/FilterApi.php:397 - FilterApi::runQuery()\n8: /var/www/Ent/71110/include/api/RestService.php:257 - FilterApi::filterList()\n9: /var/www/Ent/71110/api/rest.php:23 - RestService::execute()


Note: when viewing complex logs, you can use the following to print log entries in a more human readable format:
cat sugarcrm.log | sed s/\\n/\n/g
RequestProcessor
The RequestProcessor appendsÂ Session properties to the Log message that would help identify the request that caused the log message. The Factory implementation for this Processor lives in ./src/Logger/Processor/Factory/Request.php, and is referenced in the sugar config as request.
The following list of session properties are appended to the log:
User ID
Client ID (OAuth2 Client)
Platform
The following shows an example of the output that occurs when utilizing this processor:
Mon Mar 26 09:48:58 2018 [5930][1][FATAL] <log message>; User ID=1; Client ID=sugar; Platform=base
Configuration


Configuration
To configure the default Logging Handler for Sugar to utilize the built-in processors:
//Configure a single Processor
$sugar_config['logger']['channels']['default']['processors'] = 'backtrace';
//Configure multiple Processors
$sugar_config['logger']['channels']['default']['processors'] = array('backtrace','request');
You can also configure different channels to utilize the processors:
//Single Processors
$sugar_config['logger']['channels']['<channel>']['processors'] = 'backtrace';
//Multiple Channels
$sugar_config['logger']['channels']['<channel>']['processors'] = array('backtrace','request');


To pass configuration properties to aÂ ProcessorÂ your configuration will need to look as follows:
$sugar_config['logger']['channels']['<channel>']['processors']['<processor>']['config_key'] = 'test_value';
Note: For more information, please refer to the logger.channels.channel.processors documentation.
Usage
As previously mentioned the Logger Factory allows for multiple channels to be configured independently of each other. The following examples will showcase how to use the Logger Factory to get a channel's Logger and use it in code, as well as how to configure the system to use multiple channels with different configurations.
Basic Usage
use \Sugarcrm\Sugarcrm\Logger\Factory;
//Retrieve the default Logger
$DefaultLogger = Factory::getLogger('default');


$DefaultLogger = Factory::getLogger('default');
$DefaultLogger->alert('This is a log message');
Configuring Channels
The following is an example of the ./config_override.php file that would configure two different channels at different log levels, using the logger.channel.channel.level configuration setting. These two channels would allow for portions of the code to Log messages at Debug (and higher) levels, and other portions to only log Info (and higher) levels.
$config['logger']['channels']['default'] = array(
    'level' => 'alert'
);
$config['logger']['channels']['channel1'] = array(
    'level' => 'debug'
);
The following code example shows how toÂ retrieve the above-configured channels and use the Logger for each channel.Â


use \Sugarcrm\Sugarcrm\Logger\Factory;
//Retrieve the default Logger
$DefaultLogger = Factory::getLogger('default');
$DefaultLogger->info("This message will not display");
//Channel1 Logger
$Channel1Logger = Factory::getLogger('channel1');
$Channel1Logger->info("This message will display");
In the example above, assuming a stock system with the previously mentioned config values set in config_override.php, the default channel logger would be set to Alert level logging, and therefore would not add the info level log to the Log file, however, the channel11 Logger would add the log message to the Sugar log file, since it is configured at the info log level.
Default Channels


Default Channels
By default, Sugar has a few areas of the system that utilize a different channel than the default. The usage of these channels means that you can configure the log level and processors differently for those areas, without inundating the log file with lower level logs from other areas of the system.
Channel
Description
authentication
This logger channel is utilized by the AuthenticationController, and can show useful information about failed login attempts.Â
input_validation
This logger channel is utilized by the Input Validation framework and can display information regarding failed validations.
metadata
This logger channel is utilized by the MetadataManager and mainly provides information on when rebuilds occur.
rest
This channel is utilized by theÂ RestService object.
db
This channel is utilized by the databse for query logging.
Customization


db
This channel is utilized by the databse for query logging.
Customization
You can use custom channels where ever you would like in your customizations, and configure them as outlined above, but if you need to send logs somewhere other than the Sugar log file, you will need to build out your own Handler. The following will walk through adding a custom Logging Handler that utilizes Monologs built-in Chrome Logger.
Adding a Custom Handler
Create the following file in ./custom/src/Logger/Handler/Factory/ folder.
./custom/src/Logger/Handler/Factory/Chrome.php
<?php
namespace Sugarcrm\Sugarcrm\custom\Logger\Handler\Factory;
use Monolog\Handler\ChromePHPHandler;
use Sugarcrm\Sugarcrm\Logger\Handler\Factory;
class Chrome implements Factory
{


class Chrome implements Factory
{
    public function create($level, array $config)
    {
        return new ChromePHPHandler($level);
    }
}
Once you have the new class in place, you will need to run a Quick Repair and Rebuild so that the new class is auto-loaded correctly. Then you can configure the handler for use in the Sugar config:
$sugar_config['logger']['channels']['<channel>']['handlers'] = 'Chrome';
Â Note: For more information, please refer to theÂ logger.channels.channel.handlersÂ documentation.
  	Last modified: 2023-02-03 21:04:03


SugarLogger
Overview
The SugarLoggerÂ is used for log reporting by the application. The following article outlines theÂ LoggerTemplateÂ interfaceÂ as well as theÂ LoggerManager object and explains how to programmatically use the SugarLogger.
LoggerTemplate
The LoggerManager manages those objects that implement the LoggerTemplate interface found in ./include/SugarLogger/LoggerTemplate.php.Â
Methods
log($method, $message)
The LoggerTemplate has a single method that should be implemented, which is the log() method.
Arguments
Name
Type
Description
$method
String
The method or level which the Logger should handle the provided message
$message
String
The logged message
Implementations


$message
String
The logged message
Implementations
Sugar comes with two stock LoggerTemplate implementations that can be utilized for different scenarios depending on your needs. You can extend from these implementations or create your own class that implements the template as outlined in Creating Custom LoggersÂ section.
SugarLogger
The SugarLoggerÂ class, found in ./include/SugarLogger/SugarLogger.php, is the default system logging class utilized throughout the majority of Sugar.Â
SugarPsrLogger
The SugarPsrLogger was added in Sugar 7.9 to accommodate the PSR-3Â compliant logging implementation. This logger works just like the SugarLogger object, however it uses the Monolog implementation of Logger.
To configure the SugarPsrLogger as the default system logger, you can add the following to your configuration:


$sugar_config['logger']['default'] = 'SugarPsrLogger';
Log Level Mappings
PSR-3 defines the set of log levels that should be implemented for all PHP Applications, however, theseÂ are different from the log levels defined by theÂ SugarLogger. Below is the list ofÂ SugarLoggerÂ log levels and theirÂ SugarPsrLoggerÂ compatible mapping. All calls to the SugarLogger log levels are mapped according to the table below. For example, when using theÂ SugarPsrLoggerÂ class, all calls to theÂ Â fatal()Â method will map to theÂ AlertÂ log level.
SugarLogger Level
SugarPsrLogger LevelÂ (PSR-3)
Debug
Debug
Info
Info
Warn
Warning
Deprecated
Notice


Debug
Debug
Info
Info
Warn
Warning
Deprecated
Notice
Error
Error
Fatal
Alert
Security
Critical
LoggerManager
The LoggerManager Object acts as a singleton factory that sets up the configured logging object for use throughout the system. This is the object stored in $GLOBALS['log'] in the majority of use cases in the system.
Methods
getLogger()
This method is used to get the currently configured Logger class.
setLevel($level)
You may find that you want to define the log level while testing your code without modifying the configuration. This can be done by using the setLevel() method.
Arguments
Name
Type
Description
$level
String
The method or level which the Logger should handle the provided message
Example
\LoggerManager::getLogger()->setLevel('debug');


Example
\LoggerManager::getLogger()->setLevel('debug');
Note: The use of setLevel should be removed from your code for production and it is important to note that it is restricted by package scanner as defined by theÂ Module Loader Restrictions.
assert($message, $condition)
In your custom code you may want to submit a debug level log, should a condition not meet your expectations. You could do this with an if statement, otherwise you could just use the assert() method as it allows you to pass the debug message, and the condition to check, and if that condition is FALSE a debug level message is logged.
Arguments
Name
Type
Description
$message
String
The log message
$condition
Boolean
The condition to check
Example
$x = 1;
\LoggerManager::getLogger()->assert('X was not equal to 0!', $x==0)


wouldLog($level)
If in your customization you find that extra debugging is needed for particular area of code, and that extra work might have performance impacts on standard log levels, you can use the wouldLog() method to check the current log level before doing the extra work.
Arguments
Name
Type
Description
$level
String
The levelÂ to check againstÂ
Example
if (\LoggerManager::getLogger()->wouldLog('debug')) {
   //Do extra debugging
}
setLogger($level, $logger)
This method allows you to setup a second logger for a specific log level, rather than just using the same logger for all levels. Passing defaultÂ as the level, will set the default Logger used by all levels in the system.
Arguments
Name
Type
Description
$level
String
The levelÂ to check againstÂ
$logger


$level
String
The levelÂ to check againstÂ
$logger
String
The class name of an installed Logger
Example
//Set the debug level to a custom Logger
\LoggerManager::getLogger()->setLogger('debug', 'CustomLogger');
//Set all other levels to SugarPsrLogger
\LoggerManager::getLogger()->setLogger('default', 'SugarPsrLogger');
getAvailableLoggers()
Returns a list of the names of Loggers found/installed in the system.
Arguments
None
Example
$loggers = \LoggerManager::getLogger()->getAvailableLoggers();
getLoggerLevels()
Returns a list of the names of Loggers found/installed in the system.
Arguments
None
Example


Arguments
None
Example
$levels = \LoggerManager::getLogger()->getLoggerLevels();
Adding a Custom SugarLogger
Custom loggers are defined in ./custom/include/SugarLogger/, and can be used to write log entries to a centralized application management tool, to a developer tool such as FirePHP or even to a secondary log file inside the Sugar application.
The following is an example of how to create a FirePHP logger.
./custom/include/SugarLogger/FirePHPLogger.php.
<?php
// change the path below to the path to your FirePHP install
require_once('/path/to/fb.php');
class FirePHPLogger implements LoggerTemplate
{
    /** Constructor */
    public function __construct()
    {
        if (


/** Constructor */
    public function __construct()
    {
        if (
            isset($GLOBALS['sugar_config']['logger']['default'])
            && $GLOBALS['sugar_config']['logger']['default'] == 'FirePHP'
        )
        {
            LoggerManager::setLogger('default','FirePHPLogger');
        }
    }
    /** see LoggerTemplate::log() */
    public function log($level, $message)
    {
        // change to a string if there is just one entry
        if ( is_array($message) && count($message) == 1 ) {
            $message = array_shift($message);
        }
        switch ($level) {
            case 'debug':
                FB::log($message);
                break;


case 'debug':
                FB::log($message);
                break;
            case 'info':
                FB::info($message);
                break;
            case 'deprecated':
            case 'warn':
                FB::warn($message);
                break;
            case 'error':
            case 'fatal':
            case 'security':
                FB::error($message);
                break;
        }
    }
}
You will then specify your default logger as 'FirePHP' in your ./config_override.php file.
$sugar_config['logger']['default'] = 'FirePHP';
  	Last modified: 2023-02-03 21:04:03


Elastic Search
Overview
The focus of this document is to guide developers on how Sugar integrates with Elastic Search.
Resources
We recommend the following resources to get started with Elasticsearch:
Elasticsearch: The Definitive Guide
Elasticsearch Reference
Deployment
The following figure shows a typical topology of Elasticsearch with a Sugar system. All the communication with Elasticsearch goes through Sugar via REST APIs.
Framework
The following figure shows the overall architecture with main components in the Elasticsearch framework and related components in Sugar framework.
Main Components
This section describes how to use each of the main components in detail.
Mapping Manager
This component builds the mappings for providers in the system. The mappings are used for Elastic to interpret data stored there, which is similar to a database schema.
Define the full_text_searchÂ property in a module's vardefÂ file for a given field:


enabledÂ : sets the field to be enabled for global search;
searchableÂ : sets the field to be searchable or not;
boostÂ : sets the boost value so that the field can be ranked differently at search time.
type : overrides the sugar type originally defined;
aggregations : sets the properties specific related to aggregations;
Define what analyzers to use for a given sugar type:
The analyzers for both indexing and searching are specified. They may be different for the same field. The index analyzer is used at indexing time, while the search analyzer is used at query time.
Each of the providers generates its own mapping.
Generates mappings for each field and sends it over to Elasticsearch.
Example Mapping


Generates mappings for each field and sends it over to Elasticsearch.
Example Mapping
This section outlines how the mapping is created for Account module's name field. The Account module's name field is defined as "searchable" in the "full_text_search" index of the vardefs. The Sugar type "name" uses the regular and ngram string analyzers for indexing and search.
./modules/Accounts/vardefs.php
...
'name' => array(
    'name' => 'name',
    'type' => 'name',
    'dbType' => 'varchar',
    'vname' => 'LBL_NAME',
    'len' => 150,
    'comment' => 'Name of the Company',
    'unified_search' => true,
    'full_text_search' => array(


'full_text_search' => array(
        'enabled' => true,
        'searchable' => true,
        'boost' => 1.9099999999999999,
    ),
    'audited' => true,
    'required' => true,
    'importable' => 'required',
    'duplicate_on_record_copy' => 'always',
    'merge_filter' => 'selected',
),
...


'merge_filter' => 'selected',
),
...
The field handler, located in ./src/Elasticsearch/Provider/GlobalSearch/Handler/Implement/MultiFieldHandler.php, defines the mappings used by Elasticsearch. The class property $typesMultiFieldÂ in the MultiFieldHandler class defines the relationship between types andÂ mappings. For the "name" type, we use the gs_string and gs_string_wildcardÂ definitions to define our mappings and analyzers.
./src/Elasticsearch/Provider/GlobalSearch/Handler/Implement/MultiFieldHandler.php
...
protected $typesMultiField = array(
    ...
    'name' => array(
        'gs_string',
        'gs_string_wildcard',
    ),
    ...
);
...


'gs_string_wildcard',
    ),
    ...
);
...
The class property $multiFieldDefsÂ in the MultiFieldHandler class defines the actual Elasticsearch mapping to be used.
...
protected $multiFieldDefs = [
    ...
    /*
     * Default string analyzer with full word matching base ond
     * the standard analyzer. This will generate hits on the full
     * words tokenized by the standard analyzer.
     */
    'gs_string' => [
        'type' => 'text',
        'index' => true,
        'analyzer' => 'gs_analyzer_string',
        'store' => true,
    ],
    /*
     * String analyzer using ngrams for wildcard matching. The
     * weighting of the hits on this mapping are less than full


* weighting of the hits on this mapping are less than full
     * matches using the default string mapping.
     */
    'gs_string_wildcard' => [
        'type' => 'text',
        'index' => true,
        'analyzer' => 'gs_analyzer_string_ngram',
        'search_analyzer' => 'gs_analyzer_string',
        'store' => true,
    ],
    ...
];
...
The mapping is created from the definitions in Elasticsearch. A sample mapping is shown below:
"Accounts__name": {
	"include_in_all": false,
	"index": "not_analyzed",
	"type": "string",
	"fields":{
		"gs_string_wildcard":{


"fields":{
		"gs_string_wildcard":{
			"index_analyzer": "gs_analyzer_string_ngram",
			"store": true,
			"search_analyzer": "gs_analyzer_string",
			"type": "string"
		},
		"gs_string":{
			"store": true,
			"analyzer": "gs_analyzer_string",
			"type": "string"
		}
	}
}
Index Manager
An index includes types, similar to a database including tables. In our system, a type is based on a module consisting of mappings.


As shown in the following figure, the Index Manager combines the analysis built from Analysis Builder and the mappings from Mapping Manager from different providers and organizes them by modules.
Indexer & Queue Manager
After creating the index, data needs to be moved from the Sugar system to Elasticsearch. The queue manager uses theÂ Â fts_queue and job_queueÂ tables to queue the information. The indexer then processes data from Sugar bean into the format of ElasticsearchÂ documents.
The whole process is as follows:
Each Sugar bean is added to fts_queue table.
Elasticsearch queue scheduler is added to job_queue table.
When the job starts, the scheduler generates an ElasticsearchÂ queue consumer for each module.
Each consumer queries theÂ fts_queue table fo find the corresponding sugar beans.
The indexer gets fields from each bean and processes them into individual documents.


The indexer gets fields from each bean and processes them into individual documents.
The indexer uses the bulk APIs to send documents to Elasticsearch in batches.
Global Search Provider
The provider supplies information to build analysis, mapping, query, etc. It is done by using handlers. Currently, there are two providers in the system: global search and visibility. To extend the functionalities, new providers and handlers may be added.
The following figure shows the existing handler interfaces and some of the handlers implementing them.
Query Builder
The Query builder composes the query string for search. A structure of a typical multi-match query is shown as follows:
{
    "query": {
        "filtered": {
            "query": {
                "bool": {
                    "must": [{
                        "bool": {
                            "should": [{
                                "multi_match": {


"should": [{
                                "multi_match": {
                                    "type": "cross_fields",
                                    "query": "test",
                                    "fields": [
                                        "Cases__name.gs_string^1.53",
                                        "Cases__name.gs_string_wildcard^0.69",
                                        "Cases__description.gs_string^0.66",
                                        "Cases__description.gs_text_wildcard^0.23",
                                        "Bugs__name.gs_string^1.51",
                                        "Bugs__name.gs_string_wildcard^0.68",
                                        "Bugs__description.gs_string^0.68",
                                        "Bugs__description.gs_text_wildcard^0.24"
                                    ],


],
                                    "tie_breaker": 1
                                }
                            }]
                        }
                    },
                    {
                        "bool": {
                            "should": [{
                                "multi_match": {
                                    "type": "cross_fields",
                                    "query": "query",
                                    "fields": [
                                        "Cases__name.gs_string^1.53",
                                        "Cases__name.gs_string_wildcard^0.69",
                                        "Cases__description.gs_string^0.66",
                                        "Cases__description.gs_text_wildcard^0.23",
                                        "Bugs__name.gs_string^1.51",
                                        "Bugs__name.gs_string_wildcard^0.68",


"Bugs__name.gs_string_wildcard^0.68",
                                        "Bugs__description.gs_string^0.68",
                                        "Bugs__description.gs_text_wildcard^0.24"
                                    ],
                                    "tie_breaker": 1
                                }
                            }]
                        }
                    }]
                }
            },
            "filter": {
                "bool": {
                    "must": [{
                        "bool": {
                            "should": [{
                                "bool": {
                                    "must": [{
                                        "term": {
                                            "_type": "Bugs"
                                        }
                                    }]
                                }
                            },
                            {
                                "bool": {
                                    "must": [{


{
                                "bool": {
                                    "must": [{
                                        "term": {
                                            "_type": "Cases"
                                        }
                                    }]
                                }
                            }]
                        }
                    }]
                }
            }
        }
    },
    "highlight": {
        "pre_tags": [
            "<strong>"
        ],
        "post_tags": [
            "<\/strong>"
        ],
        "require_field_match": 1,
        "number_of_fragments": 3,
        "fragment_size": 255,
        "encoder": "html",
        "order": "score",
        "fields": {
            "*.gs_string": {
                "type": "plain",
                "force_source": false
            },


"type": "plain",
                "force_source": false
            },
            "*.gs_string_exact": {
                "type": "plain",
                "force_source": false
            },
            "*.gs_string_html": {
                "type": "plain",
                "force_source": false
            },
            "*.gs_string_wildcard": {
                "type": "plain",
                "force_source": false
            },
            "*.gs_text_wildcard": {
                "type": "plain",
                "force_source": false
            },
            "*.gs_phone_wildcard": {
                "type": "plain",
                "force_source": false
            },
            "*.gs_email": {
                "type": "plain",
                "force_source": false,


"type": "plain",
                "force_source": false,
                "number_of_fragments": 0
            },
            "*.gs_email_wildcard": {
                "type": "plain",
                "force_source": false,
                "number_of_fragments": 0
            }
        }
    }
}
Figure 7: Multi-match query with aggregations.
A query may include multiple filters, post filters, queries, and settings, which can get complicated sometimes. To add new queries, we recommend adding new classes implementing QueryInterface, instead of modifying Query Builder or even calling Elastica APIs directly.
ACL
ACL control is done in the Query Builder. The search fields are filtered based on their ACL access levels when being added to the query string in a specific query. For instance, MultiMatchQuery used for global search may include the following filterings:


If a field is owner read (i.e., SugarACL::ACL_OWNER_READ_WRITE), it will be added to a sub-query with a filter of ownerId.
If a field is not owner read, its access level must not equal to SugarACL::ACL_NO_ACCESS, in order to be added as one of the search fields in the query.
The corresponding function is MultiMatchQuery.php::isFieldAccessible(). Potentially this function can be shared by different queries that require ACL control.
Visibility
The visibility model is applied when building the query too. It is done by adding filters built by individual visibility strategies, defined in ./data/visibility/ directory. These strategies implement StrategyInterface including:
building analysis,
building mapping,
processing document before being indexed,
getting bean index fields,
adding visibility filters.


processing document before being indexed,
getting bean index fields,
adding visibility filters.
The functions are similar to the global search provider's handler interfaces. The query builder only uses adding the visibility filters, while others provide information to Analysis builder, Mapping Manager, Indexer, etc. Hence the visibility provider is separated as an independent provider in the framework.
  	Last modified: 2023-02-03 21:04:03


Module Builder
Overview
The 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.
ForÂ this example, a custom module to track media inquiries will be created to track public relations requests within a CRM system. This use case is an often requested enhancement for CRM systems that apply across industries.
Creating New Modules
Module Builder functionality is managed within the 'Developer Tools' section of Sugar's administration console.


Module Builder functionality is managed within the 'Developer Tools' section of Sugar's administration console.
Upon selecting 'Module Builder', the user has the option of creating a "New Package". Packages are a collection of custom modules, objects, and fields that can be published within the application instance or shared across instances of Sugar. Once the user selects "New Package", the usernames and describes the type of Custom Module to be created. A package key, usually based on the organization or name of the package creator is required to prevent conflicts between two packages with the same name from different authors. In this case, the package will be named "MediaTracking" to explain its purpose, and a key based on the author name will be used.
Once the new package is created and saved, the user is presented with a screen to create a Custom Module. Upon selecting the "New Module" icon, a screen appears showing six different object templates.
Understanding Object Templates


Understanding Object Templates
Five of the six object templates contain pre-built CRM functionality for key CRM use cases. These objects are:"basic", "company", "file", "issue", "person", and "sale". The "basic" template provides fields such as Name, Assigned to, Team, Date Created, and Description. As their title denotes, the rest of these templates contain fields and application logic to describe entities similar to "Accounts", "Documents, "Cases", "Contacts", and "Opportunities", respectively. Thus, to create a Custom Module to track a type of account, you would select the "Company" template. Similarly, to track human interactions, you would select "People".


For the media tracking use case, the user will use the object template "Issue" because inbound media requests have similarities to incoming support cases. In both examples, there is an inquiry, a recipient of the issue, assignment of the issue and resolution. The final object template is named "Basic" which is the default base object type. This allows the administrator to create their own custom fields to define the object.
Upon naming and selecting the Custom Module template named "Issue", the user can further customize the module by changing the fields and layout of the application and creating relationships between this new module and existing standard or custom modules. This Edit functionality allows a user to construct a module that meets the specific data requirements of the Custom Module.
Editing Module Fields


Editing Module Fields
Fields can be edited and created using the field editor. Fields inherited from the custom module's templates can be relabeled while new fields are fully editable. New fields are added using the Add Field button. This displays a tab where you can select the type of field to add as well as any properties that field-type requires.
Editing Module Layouts


Editing Module Layouts
The layout editor can be used to change the appearance of the screens within the new module, including the EditView, DetailView and ListView screens. When editing the Edit View or the Detail View, new panels and rows can be dragged from the toolbox on the left side to the layout area on the right. Fields can then be dragged between the layout area and the toolbox. Fields are removed from the layout by dragging them from the layout area to the recycling icon. Fields can be expanded or collapsed to take up one or two columns on the layout using the plus and minus icons. TheÂ List, Search, Dashlet, and Subpanel views can be edited by dragging fields between hidden/visible/available columns.
Building Relationships


Building Relationships
Once the fields and layout of the Custom Module have been defined, the user then defines relationships between this new module and existing CRM data by clicking "View Relationships". The "Add Relationship" button allows the user to associate the new module to an existing or new custom module in the same package. In the case of the Media Tracker, the user can associate the Custom Module with the existing, standard 'Contacts' module that is available in every Sugar installation using a many-to-many relationship. By creating this relationship, end-users will see the Contacts associated with each Media Inquiry. We will also add a relationship to the activities module so that a Media Inquiry can be related to calls, meetings, tasks, and emails.
Publishing and Uploading Packages


After the user has created the appropriate fields, layouts, and relationships for the custom modules, this new CRM functionality can be deployed. Click the "Deploy" button to deploy the package to the current instance. This is the recommended way to test your package while developing. If you wish to make further changes to your package or custom modules, you should make those changes in Module Builder, and click the Deploy button again. Clicking the Publish button generates a zip file with the Custom Module definitions. This is the mechanism for moving the package to a test environment and then ultimately to the production environment. The Export button will produce a module loadable zip file, similar to the Publish functionality, except that when the zip file is installed, it will load the custom package into Module Builder for further editing. This is a good method for storing the custom package in case you would like to make changes to it in the future on another Sugar instance. Once your module has been deployed in a production


changes to it in the future on another Sugar instance. Once your module has been deployed in a production environment, we highly recommend that you do not redeploy the module in Module Builder but modify the module using Studio as outlined in our Best Practices When Building Custom Modules.


After the new package has been published, the administrator must commit the package to the Sugar system through the Module Loader. The administrator uploads the files and commits the new functionality to the live application instance.
Note: Sugar Sell Essentials customers do not have the ability to upload custom file packages to Sugar using Module Loader.
Adding Custom Logic Using Code
While the key benefit of the Module Builder is that the Administrator user is able to create entirely new modules without the need to write code, there are still some tasks that require writing PHP code. For instance, adding custom logic or making a call to an external system through a Web Service. This can be done in one of two methods.
Logic Hooks


Logic Hooks
One way is by writing PHP code that leverages the event handlers, or "logic hooks", available in Sugar. In order to accomplish this, the developer must create the custom code and then add it to the manifest file for the "Media Inquiry" package. More information on creating logic hooks can be found in the Logic Hooks section. Information on adding a hook to your installer package can be found in theÂ Creating an Installable Package for a Logic Hook example.
Custom Bean files
Another method is to add code directly to the custom bean. This is a more complicated approach because it requires understanding the SugarBean class. However, it is a far more flexible and powerful approach.


First, you must "build" your module. This can be done by either deploying your module or clicking the Publish button. Module Builder will then generate a folder for your package in ./custom/modulebuilder/builds/. Inside that folder is where Sugar will have placed the bean files for your new module(s). In this case, we want ./custom/modulebuilder/builds/MediaTracking/SugarModules/modules/jsche_mediarequest/


Inside you will find two files of interest. The first one is {module_name}sugar.php. This file is generated by Module Builder and may be erased by further changes in module builder or upgrades to the Sugar application. You should not make any changes to this file. The second is {module_name}.php. This is the file where you make any changes you would like to the logic of your module. To add our timestamp, we would add the following code to jsche_mediarequest.php
function save($check_notify = FALSE)
{
     global $current_user;
     $this->description .= "Saved on " . date("Y-m-d g:i a"). " by ". $current_user->user_name;
     parent::save($check_notify);
}


parent::save($check_notify);
}
The call to the parent::save function is critical as this will call on the out of box SugarBean to handle the regular Save functionality. To finish, re-deploy or re-publish your package from Module Builder.
You can now upload this module, extended with custom code logic, into your Sugar application using the Module Loader as described earlier.
Using the New Module


Using the New Module
After you upload the new module, the new custom module appears in the Sugar instance. In this example, the new module, named "Media" uses the object template "Issue" to track incoming media inquiries. This new module is associated with the standard "Contacts" modules to show which journalist has expressed interest. In this example, the journalist has requested a product briefing. On one page, users can see the nature of the inquiry, the journalist who requested the briefing, who the inquiry was assigned to, the status, and the description.
TopicsBest PracticesSugar provides two tools for building and maintaining custom module configurations: Module Builder and Studio. As an administrator of Sugar, it is important to understand the strengths of both tools so that you have a sound development process as you look to build on Sugar's framework.
  	Last modified: 2023-02-03 21:04:03


Best Practices
Overview
Sugar provides two tools for building and maintaining custom module configurations: Module Builder and Studio. As an administrator of Sugar, it is important to understand the strengths of both tools so that you have a sound development process as you look to build on Sugar's framework.
Goal
This guide will provide you with all the necessary steps from initial definition of your module to the configuration and customization of the module functionality. Follow these tips to ensure your development process will be sound:
Build Your Module in Module Builder
Build the initial framework for your module in Module Builder. Add all the fields you feel will be necessary for the module and construct the layouts with those fields. If possible, it is even better to create your custom modules in a development environment that mirrors your production environment.
Never Redeploy a Package


Never Redeploy a Package
Once a module has been promoted to a production environment, we recommend only making additional changes in Studio.Â Redeploying a package will remove all customizations related to your module in the following directories:
./modules/
./custom/modules/
./custom/Extension/modules/
This includes workflows, code customizations, changes through Studio, and much more. It is imperative that this directive is followed to ensure any desired configurations remain intact. When working in Studio, you can make the following types of changes:
adding a new field
updating the properties of a field that has been deployed with the module
changing field layouts
creating, modifying and removing relationships
Every Module in Module Builder Gets Its Very Own Package


creating, modifying and removing relationships
Every Module in Module Builder Gets Its Very Own Package
While it is possible to create multiple modules in a package, this can also cause design headaches down the road. If you end up wanting to uninstall a module and it is part of a larger package, all modules in that package would need to be uninstalled. Keeping modules isolated to their own packages allows greater flexibility in the future if a module is no longer needed.
Create Relationships in Studio After the Module Is Deployed
This part is critical for success as relationships created in Module Builder cannot be removed after the module is deployed unless the package is updated and redeployed from Module Builder. Redeploying from Module Builder is what we are trying to avoid as mentioned above. If you deploy the module and then create the relationships in Studio, you can update or remove the relationships via Studio at any future point in time.
Delete the Package from Module Builder Once It Is Deployed


Delete the Package from Module Builder Once It Is Deployed
Once the package is deployed, delete it from Module Builder so that it will not accidentally be redeployed. The only exception to this rule is in a development environment as you may want to continue working and testing until you are ready to move the module to your production environment. If you ever want to uninstall the module at a later date, you can do so under Admin > Module Loader.
  	Last modified: 2023-02-03 21:04:03


Access Control Lists
Overview
Access 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.
Actions


Actions
Actions are the use cases in which a User interacts with a particular module in the system. ACLs control access by using different logic to determine if a user can do an action. Below is a list of actions that are utilized by Sugar and will be requested actions against a custom ACL strategy implementation. You can add your own actions to be checked against in a custom strategy, but the following list should always be considered:
index, list, listview - for module ListView access
detail, detailview, view - module DetailView access
popupeditview, editview - module EditView access
edit, save - editing module data
import - import into the module
export - export from the module
delete - delete module record
team_security - should this module have team security enabled?
field - access field ACLs. The field action is specified via context array, see below


field - access field ACLs. The field action is specified via context array, see below
subpanel - checks if subpanel should be displayed. Should have "subpanel" context option.
Note: Action names are not case sensitive, although the standard practice is to use them in all lowercase
Field Actions
When using the field action, the action for the particular field that is being checked is passed via Context. The field actions that are used in Sugar are:
access - any access to field data
read, detail - read access
write, edit - write access
Context
The context array is used throughout Sugar's ACL architecture as a way to pass extra contextual data that is used by an ACL Strategy to determine the access request.Â The following context parameters are used in stock contexts, and should be used as a guideline for custom ACL implementations as parameters that should be accommodated or used:


bean (SugarBean) - the current SugarBean object
user (User) - the user to check access for, otherwise defaults to the current user.
user_id (String) - the current user ID (also overrides global current user). If the current user object is available, use the user context, since it has precedent.
owner_override (Bool) - if set to true, apply ACLs as if the current user were the owner of the object.
subpanel (aSubPanel) - used in subpanel action to provide aSubPanel object describing the panel.
field, action (String) - used to specify field name and action name for field ACL requests.
massupdate (Bool) - true if save operation is a mass update
SugarACL


SugarACL
The SugarACL Class, ./data/SugarACL.php, is the static API for checking access for an action against a module.Â
checkAccess()
The checkAccess() method is used to check a users access for a given action against a module.Â
SugarACL::checkAccess('Accounts','edit');
Arguments
Name
Type
Required
Description
$module
String
true
The name of the module
$action
String
true
The name of the action. See Actions section.
$context
Array
false
The associative array of context data. See Context section.
Returns
Boolean
checkField()
The checkField() method is used to check a users access for a given field against a module.Â
SugarACL::checkField('Accounts','account_type','view');
Arguments
Name
Type
Required


Arguments
Name
Type
Required
Description
$module
String
true
The name of the module
$field
String
true
The name of the field
$action
String
true
The name of the action.Â See Actions section.
$context
Array
false
The associative array of context data. See Context section.
Returns
Boolean
getFieldAccess()
The getFieldAccess() method is used to get the access level for a specific
$access = SugarACL::getFieldAccess('Accounts','account_type');
Arguments
Name
Type
Required
Description
$module
String
true
The name of the module
$field
String
true
The name of the field
$context
Array
false
The associative array of context data. See Context section.
Returns
Integer


false
The associative array of context data. See Context section.
Returns
Integer
The integers are represented as constants in the SugarACL class, and correspond as follows:
SugarACL::ACL_NO_ACCESS = 0 - access denied
SugarACL::ACL_READ_ONLY = 1 - read only access
SugarACL::ACL_READ_WRITE = 4 - full access
filterModuleList()
The filterModuleList() method is used to filter the list of modules available for a given action. For example, if you wanted to get all the modules the current user had edit access to, you might call the following in code:
global $app_list_strings;
$modules = $app_list_strings['module_list'];
$editableModules = SugarACL::filterModuleList($modules,'edit');
Arguments
Name
Type
Required


Arguments
Name
Type
Required
Description
$modules
Array
true
The list of modules you want to filter
$action
String
false
The action to check for,Â See Actions section.Â Defaults to 'access' action
$use_value
Boolean
false
Whether to the module name in the $modules array is in the Key or the Value of the array.Â Defaults to false
For example, if filtering an array of modules, where the modules are defined in the values of the array:
$modules = array(
   'Accounts',
   'Cases',
   'Administration'
);
$accessModules = SugarACL::filterModuleList($modules,'access',true);
Returns
Array
The filtered list of modules.
disabledModuleList()


Returns
Array
The filtered list of modules.
disabledModuleList()
Similar to the previous method, the disableModuleList() method filters a list of modules to those that the user does not have access to.
global $app_list_strings;
$modules = $app_list_strings['module_list'];
$disabledModules = SugarACL::disabledModuleList($modules,'access');
Arguments
Name
Type
Required
Description
$modules
Array
true
The list of modules you want to filter
$action
String
false
The action to check for,Â See Actions section.Â Defaults to 'access' action
$use_value
Boolean
false
Whether to the module name in the $modules array is in the Key or the Value of the array.Â Defaults to false


For example, if filtering an array of modules, where the modules are defined in the values of the array:
$modules = array(
   'Accounts',
   'Cases',
   'Administration'
);
$accessModules = SugarACL::filterModuleList($modules,'access',true);
Returns
Array
The filtered list of modules.
moduleSupportsACL()
The moduleSupportsACL() method is used to check if a module has ACLs defined on the vardefs.Â A good use case for this method is to not run any ACL checks if module has no ACL definitions.
SugarACL::moduleSupportsACL('Accounts');
Arguments
Name
Type
Required
Description
$module
String
true
The name of the module
Returns
Boolean
listFilter()


String
true
The name of the module
Returns
Boolean
listFilter()
The listFilter() method allows for filtering an array of fields for a module to remove those which the user does not have access to. The removal can be done in a couple of different ways, provided by the $options argument. The filtering of the array is done in place, rather than returning the filtered list.
$Account = BeanFactory::getBean('Accounts','12345');
$fields = array(
  'account_type' => 'foobar',
  'status' => 'New',
  'name' => 'Customer'
);
$context = array(
   'bean' => $Account
);
$options = array(
    'blank_value' => true
);
SugarACL::listFilter('Accounts',$fields,$context,$options);


echo
json_encode($fields)
;
Should the user not have access to the 'status' field, the above might output:
{
  "account_type": "foobar",
  "status": "",
  "name": "Customer"
}
Arguments
Name
Type
Required
Description
$module
String
true
The name of the module
$list
Array
true
The array, as a reference, which will be filtered
$context
Array
false
The associative array of context data. See Context section.
$options
Array
false
An associative array containing some of the following options:
blank_value (Bool) - instead of removing inaccessible field put '' there
add_acl (Bool) - instead of removing fields add 'acl' value with access level
suffix (String) - strip suffix from field names


suffix (String) - strip suffix from field names
min_access (Int) - require this level of access for the field.Â Defaults toÂ SugarACL::ACL_READ_ONLYÂ or 0
use_value (Bool) - look for field name in value, not in the key of the list
Returns
Void
SugarBean Usage
The SugarBean class also comes with some methods for working with the ACLs in regards to the current SugarBean context. These methods automatically provide the 'bean' context parameter and provide a wrapper that utilizes the SugarACL class
getACLCategory()


getACLCategory()
The getACLCategory() method is used by all of the ACL helper methods that exist on the SugarBean and is used to determine the module name that should be checked against for the current Bean. By default, it will return the acl_category property set on the Bean, otherwise if that is not set, it will use the module_dir property that is configured on the Bean. This method can be overridden by a custom Bean implementation, but the following code shows which properties on a Bean implementation should be set for usage with ACLs.
<?php
class testBean extends SugarBean
{
    public $module_dir = 'testBean';
    //all the other SugarBean class logic...
}
class test_SubmoduleBean extends SugarBean
{
    public $module_dir = 'testBean/submodule';


{
    public $module_dir = 'testBean/submodule';
    public $acl_category = 'test_SubmoduleBean';
    ///all the other SugarBean class logic...
}
$test = new testBean();
//echoes testBean
echo $test->getACLCategory();
$submodule = new test_SubmoduleBean();
//echoes test_SubmoduleBean
echo $submodule->getACLCategory();
Arguments
None
Returns
String
ACLAccess()
The ACLAccess() method is a wrapper for SugarACL::checkField() method, which provides the Bean context as the current SugarBean.
$Account = BeanFactory::getBean('Accounts','12345');
if ($Account->ACLFieldAccess('account_type','edit')){


if ($Account->ACLFieldAccess('account_type','edit')){
   //do something if User has account_type edit access
}
Arguments
Name
Type
Required
Description
$action
String
false
The name of the action to check.Â See Actions section.
$context
Array
false
The associative array of context data. See Context section.
Returns
Boolean
ACLFieldAccess()
The ACLFieldAccess() method is a wrapper for SugarACL::checkAccess() method, which provides the Bean context as the current SugarBean.
$Account = BeanFactory::getBean('Accounts','12345');
if ($Account->ACLACcess('edit')){
   //do something if User has edit access
}
Arguments
Name
Type
Required
Description
$field
String
true
The name of the field
$action


Required
Description
$field
String
true
The name of the field
$action
String
false
The name of the action to check,Â see Field Actions section. Defaults to 'access'
$context
Array
false
The associative array of context data. See Context section.
Returns
Boolean
ACLFieldGet()
The ACLFieldGet() method is a wrapper for SugarACL::getFieldAccess() method, which provides the Bean context as the current SugarBean.
$Account = BeanFactory::getBean('Accounts','12345');
$accountTypeAccess = $Account->ACLFieldGet('account_type');
Arguments
Name
Type
Required
Description
$field
String
true
The name of the field
$context
Array
false
The associative array of context data. See Context section.
Returns
Boolean


false
The associative array of context data. See Context section.
Returns
Boolean
ACLFilterFields()
The ACLFilterFields()Â method uses theÂ SugarACL::checkField() method to blank out those fields which a user doesn't have access to on the current SugarBean.
$Account = BeanFactory::getBean('Accounts','12345');
$Account->status = 'Old';
$Account->ACLFilterFields('edit');
echo $Account->status;
Should the user not have 'edit' access to the 'status' field, the above wouldn't output any data since the status field would be blank.
Arguments
Name
Type
Required
Description
$action
String
true
The name of the action to check.Â See Actions section.
$context
Array
false
The associative array of context data. See Context section.


$context
Array
false
The associative array of context data. See Context section.
Returns
Void
ACLFilterFieldList()
The ACLFilterFieldList() method is a wrapper for SugarACL::listFilter() method, which provides the Bean context as the current SugarBean.
$Account = BeanFactory::getBean('Accounts','12345');
$fields = array(
  'account_type' => 'foobar',
  'status' => 'New',
  'name' => 'Customer'
);
$options = array(
    'blank_value' => true
);
$Account->ACLFilterFieldList($fields,array(),$options);
echo "<pre>".json_encode($fields)."</pre>";
Should the user not have access to the 'status' field, the above might output:
{


{
  "account_type": "foobar",
  "status": "",
  "name": "Customer"
}
Arguments
Name
Type
Required
Description
$list
Array
true
The array, as a reference, which will be filtered
$context
Array
false
The associative array of context data. See Context section.
$options
Array
false
An associative array containing some of the following options:
blank_value (Bool) - instead of removing inaccessible field put '' there
add_acl (Bool) - instead of removing fields add 'acl' value with access level
suffix (String) - strip suffix from field names
min_access (Int) - require this level of access for the field.Â Defaults toÂ SugarACL::ACL_READ_ONLYÂ or 0


use_value (Bool) - look for field name in value, not in the key of the list
Returns
Void
Legacy Methods
When working in the Sugar codebase there might be areas that still utilize the following legacy ACLController class. The following gives a brief overview of a few methods that might still be used but should be avoided in future custom development.
ACLController::checkAcess()
The ACLController::checkAccess() method is just a wrapper for SugarACL::checkAccess() method and has been left in place for backward compatibility.
ACLController::moduleSupportsACL()
The ACLController::moduleSupportsACL() method is just a wrapper for SugarACL::moduleSupportsACL() method and has been left in place for backward compatibility.
ACLController::displayNoAccess()
This method does an echo to display a "no access" message for pages.Â


This method does an echo to display a "no access" message for pages.Â
  	Last modified: 2023-02-03 21:04:03


TinyMCE
Overview
This article demonstrates how to work with the TinyMCE rich-editor field and customize its settings.Â
TinyMCE Field Type (htmleditable_tinymce)
Sugar provides a rich-text editor field using TinyMCE. This editor is used available when customizing templates and composing emails. As Sugar does not provide a way to create and use these field in Studio, we will demonstrate how to convert a text area field into a TinyMCE editor in the following sections.
Converting a Text Area to a TinyMCE Editor
As an example, we will use the Accounts' description field. The description fields type is by default text area. First of all, you will need to change the type of the field to htmleditable_tinymce using theÂ displayParamsÂ vardefÂ key.


$dictionary['Account']['fields']['description']['displayParams']['type'] = 'htmleditable_tinymce';
Additionally, having a rich-text editor will require you to have a longer database field to store the HTML content. You will need to change the dbType to longtext and its length to 4294967295.
$dictionary['Account']['fields']['description']['dbType'] = 'longtext';
$dictionary['Account']['fields']['description']['len'] = '4294967295';
After making these changes, the vardef definition will look as follows:
./custom/Extension/modules/Accounts/Ext/Vardefs/tinymce_description.php
<?php


<?php
$dictionary['Account']['fields']['description']['displayParams']['type'] = 'htmleditable_tinymce';
$dictionary['Account']['fields']['description']['dbType'] = 'longtext';
$dictionary['Account']['fields']['description']['len'] = '4294967295';
Last, you will need to navigate Admin > Repairs and Perform Quick Repair and Rebuild.Â
Configuring the TinyMCE Editor