<?php
$viewdefs['Accounts']['base']['layout']['create'] = array(
    'components' =>
        array(
            array(
                'layout' =>
                    array(
                        'components' =>
                            array(
                                array(
                                    'layout' =>
                                        array(
                                            'components' =>
                                                array(
                                                    array(
                                                        'view' => 'create',
                                                    ),
                                                    array(
                                                        'view' => 'validate-account',
                                                    ),
                                                ),
                                            'type' => 'simple',
                                            'name' => 'main-pane',
                                            'span' => 8,
                                        ),
                                ),
                                array(


),
                                ),
                                array(
                                    'layout' =>
                                        array(
                                            'components' =>
                                                array(),
                                            'type' => 'simple',
                                            'name' => 'side-pane',
                                            'span' => 4,
                                        ),
                                ),
                                array(
                                    'layout' =>
                                        array(
                                            'components' =>
                                                array(
                                                    array(
                                                        'view' =>
                                                            array (
                                                                'name' => 'dnb-account-create',
                                                                'label' => 'DNB Account Create',
                                                            ),
                                                        'width' => 12,
                                                    ),
                                                ),


'width' => 12,
                                                    ),
                                                ),
                                            'type' => 'simple',
                                            'name' => 'dashboard-pane',
                                            'span' => 4,
                                        ),
                                ),
                                array(
                                    'layout' =>
                                        array(
                                            'components' =>
                                                array(
                                                    array(
                                                        'layout' => 'preview',
                                                    ),
                                                ),
                                            'type' => 'simple',
                                            'name' => 'preview-pane',
                                            'span' => 8,
                                        ),
                                ),
                            ),
                        'type' => 'default',
                        'name' => 'sidebar',
                        'span' => 12,


'name' => 'sidebar',
                        'span' => 12,
                    ),
            ),
        ),
    'type' => 'simple',
    'name' => 'base',
    'span' => 12,
);
Lastly, depending on the selected module, duplicate its record layout to the modulesÂ custom folder to handle editing a record. In the accounts example, we do not have an existing ./modules/Accounts/clients/base/layouts/record/record.php file so we duplicated the core ./clients/base/layouts/record/record.php toÂ ./custom/modules/Accounts/clients/base/layouts/record/record.php. Since we are copying from the ./clients/ core directory, modify:
$viewdefs['base']['layout']['record'] = array(
    ...
);
To:


...
);
To:
$viewdefs['Accounts']['base']['layout']['record'] = array(
    ...
);
After this has been completed, append the new custom view to the components:
array(
    'view' => 'validate-account',
),
The resulting file is shown below:
./custom/modules/Accounts/clients/base/layouts/record/record.php
<?php
$viewdefs['Accounts']['base']['layout']['record'] = array(
    'components' => array(
        array(
            'layout' => array(
                'components' => array(
                    array(
                        'layout' => array(
                            'components' => array(
                                array(
                                    'view' => 'record',


array(
                                    'view' => 'record',
                                    'primary' => true,
                                ),
                                array(
                                    'view' => 'validate-account',
                                ),
                                array(
                                    'layout' => 'extra-info',
                                ),
                                array(
                                    'layout' => array(
                                        'name' => 'filterpanel',
                                        'span' => 12,
                                        'last_state' => array(
                                            'id' => 'record-filterpanel',
                                            'defaults' => array(
                                                'toggle-view' => 'subpanels',
                                            ),
                                        ),
                                        'availableToggles' => array(
                                            array(
                                                'name' => 'subpanels',


array(
                                                'name' => 'subpanels',
                                                'icon' => 'icon-table',
                                                'label' => 'LBL_DATA_VIEW',
                                            ),
                                            array(
                                                'name' => 'list',
                                                'icon' => 'icon-table',
                                                'label' => 'LBL_LISTVIEW',
                                            ),
                                            array(
                                                'name' => 'activitystream',
                                                'icon' => 'icon-th-list',
                                                'label' => 'LBL_ACTIVITY_STREAM',
                                            ),
                                        ),
                                        'components' => array(
                                            array(
                                                'layout' => 'filter',


array(
                                                'layout' => 'filter',
                                                'targetEl' => '.filter',
                                                'position' => 'prepend'
                                            ),
                                            array(
                                                'view' => 'filter-rows',
                                                "targetEl" => '.filter-options'
                                            ),
                                            array(
                                                'view' => 'filter-actions',
                                                "targetEl" => '.filter-options'
                                            ),
                                            array(
                                                'layout' => 'activitystream',
                                                'context' =>
                                                array(
                                                    'module' => 'Activities',
                                                ),
                                            ),
                                            array(
                                                'layout' => 'subpanels',
                                            ),


'layout' => 'subpanels',
                                            ),
                                        ),
                                    ),
                                ),
                            ),
                            'type' => 'simple',
                            'name' => 'main-pane',
                            'span' => 8,
                        ),
                    ),
                    array(
                        'layout' => array(
                            'components' => array(
                                array(
                                    'layout' => 'sidebar',
                                ),
                            ),
                            'type' => 'simple',
                            'name' => 'side-pane',
                            'span' => 4,
                        ),
                    ),
                    array(
                        'layout' => array(
                            'components' => array(
                                array(
                                    'layout' => array(


array(
                                    'layout' => array(
                                        'type' => 'dashboard',
                                        'last_state' => array(
                                            'id' => 'last-visit',
                                        )
                                    ),
                                    'context' => array(
                                        'forceNew' => true,
                                        'module' => 'Home',
                                    ),
                                ),
                            ),
                            'type' => 'simple',
                            'name' => 'dashboard-pane',
                            'span' => 4,
                        ),
                    ),
                    array(
                        'layout' => array(
                            'components' => array(
                                array(
                                    'layout' => 'preview',
                                ),
                            ),
                            'type' => 'simple',


),
                            ),
                            'type' => 'simple',
                            'name' => 'preview-pane',
                            'span' => 8,
                        ),
                    ),
                ),
                'type' => 'default',
                'name' => 'sidebar',
                'span' => 12,
            ),
        ),
    ),
    'type' => 'simple',
    'name' => 'base',
    'span' => 12,
);
Once the files are in place, navigate to Admin > Repair > Quick Repair and Rebuild.Â
  	Last modified: 2023-02-03 21:04:03


Logic Hooks
These pages demonstrateÂ some common examples of working with logic hooks in Sugar.
TopicsComparing Bean Properties Between Logic HooksHow to compare the properties of a bean between the before_save and after_save logic hooksPreventing Infinite Loops with Logic HooksHow to add a check to a logic hook to eliminate perpetual looping
  	Last modified: 2023-02-03 21:04:03


Preventing Infinite Loops with Logic Hooks
Overview
Infinite loops often happen when a logic hook calls save on a bean in a scenario that triggers the same hook again. This example showsÂ how to add a check to a logic hook to eliminate perpetual looping.
Saving in an After Save Hook
Infinite loops can sometimes happen when you have a need to update a record after it has been run through the workflow process in the after_save hook. Here is an example of a looping hook:
./custom/modules/Accounts/logic_hooks.php
<?php
$hook_version = 1;
$hook_array = Array();
$hook_array['after_save'] = Array();
$hook_array['after_save'][] = Array(
    1,
    'Update Account Record',
    'custom/modules/Accounts/Accounts_Hook.php',


'custom/modules/Accounts/Accounts_Hook.php',
    'Accounts_Hook',
    'update_self'
);
./custom/modules/Accounts/Accounts_Hook.php
<?php
if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
class Accounts_Hook
{
    function update_self($bean, $event, $arguments)
    {
        //generic condition
        if ($bean->account_type == 'Analyst')
        {
            //update
            $bean->industry = 'Banking';
            $bean->save();
        }
    }
}


$bean->save();
        }
    }
}
In the example above, there is a condition that, when met, will trigger the update of a field on the record. This will cause an infinite loop because calling the save() method will trigger once during the before_save hook and then again during the after_save hook. The solution to this problem is to add a new property on the bean to ignore any unneeded saves. For this example, we will name the property "ignore_update_c" and add a check to the logic hook to eliminate the perpetual loop. An example of this is shown below:
./custom/modules/Accounts/Accounts_Hook.php
<?php
if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
class Accounts_Hook
{
    function update_self($bean, $event, $arguments)
    {


function update_self($bean, $event, $arguments)
    {
        //loop prevention check
        if (!isset($bean->ignore_update_c) || $bean->ignore_update_c === false)
        {
            //generic condition
            if ($bean->account_type == 'Analyst')
            {
                //update
                $bean->ignore_update_c = true;
                $bean->industry = 'Banking';
                $bean->save();
            }
        }
    }
}
Related Record Save Loops
Sometimes logic hooks on two separate but related modules can cause an infinite loop. The problem arises when the two modules have logic hooks that update one another. This is often done when wanting to keep a field in sync between two modules. The example below will demonstrate this behavior on the accounts:contacts relationship:


./custom/modules/Accounts/logic_hooks.php
<?php
$hook_version = 1;
$hook_array = Array();
$hook_array['before_save'] = Array();
$hook_array['before_save'][] = Array(
    1,
    'Handling associated Contacts records',
    'custom/modules/Accounts/Accounts_Hook.php',
    'Accounts_Hook',
    'update_contacts'
);
./custom/modules/Accounts/Accounts_Hook.php
<?php
if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
class Accounts_Hook
{
    function update_contacts($bean, $event, $arguments)
    {
        //if relationship is loaded


{
        //if relationship is loaded
        if ($bean->load_relationship('contacts'))
        {
            //fetch related beans
            $relatedContacts = $bean->contacts->getBeans();
            foreach ($relatedContacts as $relatedContact)
            {
                //perform any other contact logic
                $relatedContact->save();
            }
        }
    }
}
The above example will loop through all contacts related to the account and save them. At this point, the save will then trigger our contacts logic hook shown below:
./custom/modules/Contacts/logic_hooks.php
<?php
$hook_version = 1;
$hook_array = Array();
$hook_array['before_save'] = Array();
$hook_array['before_save'][] = Array(
    1,


$hook_array['before_save'][] = Array(
    1,
    'Handling associated Accounts records',
    'custom/modules/Contacts/Contacts_Hook.php',
    'Contacts_Hook',
    'update_account'
);
./custom/modules/Contacts/Contacts_Hook.php
<?php
if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
class Contacts_Hook
{
    function update_account($bean, $event, $arguments)
    {
        //if relationship is loaded
        if ($bean->load_relationship('accounts'))
        {
            //fetch related beans
            $relatedAccounts = $bean->accounts->getBeans();
            $parentAccount = false;


$parentAccount = false;
            if (!empty($relatedAccounts))
            {
                //order the results
                reset($relatedAccounts);
                //first record in the list is the parent
                $parentAccount = current($relatedAccounts);
            }
            if ($parentAccount !== false && is_object($parentAccount))
            {
                //perform any other account logic
                $parentAccount->save();
            }
        }
    }
}


$parentAccount->save();
            }
        }
    }
}
These two logic hooks will continuously trigger one another in this scenario until you run into a max_execution or memory_limit error. The solution to this problem is to add a new property on the bean to ignore any unneeded saves. In our example, we will name this property "ignore_update_c" and add a check to our logic hook to eliminate the perpetual loop. The following code snippets are copies of the two classes with the ignore_update_c property added.
./custom/modules/Accounts/Accounts_Hook.php
<?php
if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
class Accounts_Hook
{
    function update_contacts($bean, $event, $arguments)
    {
        //if relationship is loaded


{
        //if relationship is loaded
        if ($bean->load_relationship('contacts'))
        {
            //fetch related beans
            $relatedContacts = $bean->contacts->getBeans();
            foreach ($relatedContacts as $relatedContact)
            {
                //check if the bean's ignore_update_c attribute is not set
                if (!isset($bean->ignore_update_c) || $bean->ignore_update_c === false)
                {
                    //set the ignore update attribute
                    $relatedContact->ignore_update_c = true;
                    //perform any other contact logic
                    $relatedContact->save();
                }
            }
        }
    }
}
./custom/modules/Contacts/Contacts_Hook.php
<?php


./custom/modules/Contacts/Contacts_Hook.php
<?php
if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
class Contacts_Hook
{
    function update_account($bean, $event, $arguments)
    {
        //if relationship is loaded
        if ($bean->load_relationship('accounts'))
        {
            //fetch related beans
            $relatedAccounts = $bean->accounts->getBeans();
            $parentAccount = false;
            if (!empty($relatedAccounts))
            {
                //order the results
                reset($relatedAccounts);
                //first record in the list is the parent
                $parentAccount = current($relatedAccounts);
            }
            if ($parentAccount !== false && is_object($parentAccount))
            {


if ($parentAccount !== false && is_object($parentAccount))
            {
                //check if the bean's ignore_update_c element is set
                if (!isset($bean->ignore_update_c) || $bean->ignore_update_c === false)
                {
                    //set the ignore update attribute
                    $parentAccount->ignore_update_c = true;
                    //perform any other account logic
                    $parentAccount->save();
                }
            }
        }
    }
}
  	Last modified: 2023-02-03 21:04:03


Comparing Bean Properties Between Logic Hooks
Overview
How to compare the properties of a bean between the before_save and after_save logic hooks
Storing and Comparing Values
When working with a bean in the after_save logic hook, you may notice that the after_save fetched rows no longer match the before_save fetched rows. If your after_save logic needs to be able to compare values that were in the before_save, you can use the following example to help you store and use the values.
./custom/modules/<module>/logic_hooks.php
<?php
$hook_version = 1;
$hook_array = Array();
$hook_array['before_save'] = Array();
$hook_array['before_save'][] = Array(
    1,
    'Store values',


1,
    'Store values',
    'custom/modules/<module>/My_Logic_Hooks.php',
    'My_Logic_Hooks',
    'before_save_method'
);
$hook_array['after_save'] = Array();
$hook_array['after_save'][] = Array(
    1,
    'Retrieve and compare values',
    'custom/modules/<module>/My_Logic_Hooks.php',
    'My_Logic_Hooks',
    'after_save_method'
);
?>
./custom/modules/<module>/My_Logic_Hooks.php
<?php
if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
class My_Logic_Hooks
{


class My_Logic_Hooks
{
    function before_save_method($bean, $event, $arguments)
    {
        //store as a new bean property
        $bean->stored_fetched_row_c = $bean->fetched_row;
    }
    function after_save_method($bean, $event, $arguments)
    {
        //check if a fields value has changed
        if (
            isset($bean->stored_fetched_row_c)
            && $bean->stored_fetched_row_c['field'] != $bean->field
           )
        {
            //execute logic
        }
    }
}
?>
  	Last modified: 2023-02-03 21:04:03


Creating Custom Field Types
Overview
In thisÂ example, we create a custom field type calledÂ "Highlightfield", whichÂ will mimic the base text field type with the added feature that the displayed text for the field will be highlighted in a color chosen when the field is created in Studio.
This example requires the following steps, which are covered in the sections and subsections below:
Creating the JavaScript Controller
Defining the Handlebar Templates
Adding the Field Type to Studio
Enabling Search and Filtering
Note: Custom field types are not currently functional for use in Reports and will break Report Wizard.
Naming a Custom Field Type in Sugar
When choosing a name for a custom field type, keep in mind the following rules:
The first letter of a custom field type's name must be capitalized.
All subsequentÂ letters in the field type's name must be lowercase.


All subsequentÂ letters in the field type's name must be lowercase.
A field type's name cannot contain non-letter characters such as 0-9, hyphens, or dashes.
Therefore, inÂ thisÂ example, the field type "Highlightfield" cannot be called HighLightField or highlightfield.
Creating the JavaScript Controller
First,Â create aÂ controller file. Since we are starting from scratch, we needÂ to extend the base field template. To accomplish this, createÂ ./custom/clients/base/fields/Highlightfield/Highlightfield.js. This file will contain the JavaScript needed to render the field and format the values. By default, all fields extend the base template and do not require you to add the extendsFrom property. AnÂ exampleÂ template is shown below:


./custom/clients/base/fields/Highlightfield/Highlightfield.js
({
    /**
     * Called when initializing the field
     * @param options
     */
    initialize: function(options) {
        this._super('initialize', [options]);
    },
    /**
     * Called when rendering the field
     * @private
     */
    _render: function() {
        this._super('_render');
    },
    /**
     * Called when formatting the value for display
     * @param value
     */
    format: function(value) {
        return this._super('format', [value]);
    },
    /**
     * Called when unformatting the value for storage
     * @param value
     */
    unformat: function(value) {
        return this._super('unformat', [value]);


return this._super('unformat', [value]);
    }
})
Note:Â This controller file example contains methods for initialize, _render, format, and unformat. These are shown for your ease of use but are not necessarily needed for this customization. For example, you could choose to create an empty controller consisting of nothing other thanÂ ({}) and the field would render as expected in this example.
Defining the Handlebar Templates
Next,Â define theÂ handlebar templates. The templates will nearly match the base templateÂ found in ./clients/base/fields/base/ with the minor difference of an additional attributeÂ of style="background:{{def.backcolor}}; color:{{def.textcolor}}"Â for the detail and list templates.
Detail View


Detail View
The first templateÂ toÂ define is the Sidecar detail view. This template handles the display of data on the recordÂ view.
./custom/clients/base/fields/Highlightfield/detail.hbs
{{!
    The data for field colors are passed into the handlebars template through the def array. For this example, the def.backcolor and def.textcolor properties. These indexes are defined in:
    ./custom/modules/DynamicFields/templates/Fields/TemplateHighlightfield.php
}}
{{#if value}}
    <div class="ellipsis_inline" data-placement="bottom" style="background:{{def.backcolor}}; color:{{def.textcolor}}">
        {{value}}
    </div>
{{/if}}
Edit View


{{value}}
    </div>
{{/if}}
Edit View
Now define the Sidecar edit view. This template handles the display of theÂ field in the edit view.
./custom/clients/base/fields/Highlightfield/edit.hbs.
{{!
    We have not made any edits to this file that differ from stock, however,
    we could add styling here just as we did for the detail and list templates.
}}
<input type="text"
    name="{{name}}"
    value="{{value}}"
    {{#if def.len}}maxlength="{{def.len}}"{{/if}}
    {{#if def.placeholder}}placeholder="{{str def.placeholder this.model.module}}"{{/if}}
    class="inherit-width">
<p class="help-block">
List View


List View
Finally, define the list view. This template handles the display of the custom field in list views.
./custom/clients/base/fields/Highlightfield/list.hbsÂ
{{!
    The data for field colors are passed into the handlebars template through the def array. Our case
    being the def.backcolor and def.textcolor properties.  These indexes are defined in:
    ./custom/modules/DynamicFields/templates/Fields/TemplateHighlightfield.php
}}
<div class="ellipsis_inline" data-placement="bottom" data-original-title="{{#unless value}}
    {{#if def.placeholder}}{{str def.placeholder this.model.module}}{{/if}}{{/unless}}{{value}}"


style="background:{{def.backcolor}}; color:{{def.textcolor}}">
    {{#if def.link}}
       {{value}}{{else}}{{value}}
    {{/if}}

Adding the Field Type to Studio
To enable the newÂ field type for use in Studio, define the Studio field template. This will also allow us to map any additional properties we need for theÂ templates. For this example,Â map the ext1 and ext2 fields from the fields_meta_data table to the backcolor and textcolor definitions. Also, setÂ the dbfield definition toÂ varcharÂ so that the correct database field type is created.


Note: We are setting "reportable" to false to avoid issues with Report Wizard.
./custom/modules/DynamicFields/templates/Fields/TemplateHighlightfield.php
<?php
if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
require_once 'modules/DynamicFields/templates/Fields/TemplateField.php';
class TemplateHighlightfield extends TemplateField
{
    var $type = 'varchar';
    var $supports_unified_search = true;
    /**
     * TemplateAutoincrement Constructor: Map the ext attribute fields to the relevant color properties
     *
     * References:      get_field_def function below
     *
     * @returns field_type
     */
    function __construct()
    {


*/
    function __construct()
    {
        $this->vardef_map['ext1'] = 'backcolor';
        $this->vardef_map['ext2'] = 'textcolor';
        $this->vardef_map['backcolor'] = 'ext1';
        $this->vardef_map['textcolor'] = 'ext2';
    }
    //BEGIN BACKWARD COMPATIBILITY
    // AS 7.x does not have EditViews and DetailViews anymore these are here
    // for any modules in backwards compatibility mode.
    function get_xtpl_edit()
    {
        $name = $this->name;
        $returnXTPL = array();
        if (!empty($this->help)) {


$returnXTPL = array();
        if (!empty($this->help)) {
            $returnXTPL[strtoupper($this->name . '_help')] = translate($this->help, $this->bean->module_dir);
        }
        if (isset($this->bean->$name)) {
            $returnXTPL[$this->name] = $this->bean->$name;
        } else {
            if (empty($this->bean->id)) {
                $returnXTPL[$this->name] = $this->default_value;
            }
        }
        return $returnXTPL;
    }
    function get_xtpl_search()
    {
        if (!empty($_REQUEST[$this->name])) {
            return $_REQUEST[$this->name];
        }
    }


return $_REQUEST[$this->name];
        }
    }
    function get_xtpl_detail()
    {
        $name = $this->name;
        if (isset($this->bean->$name)) {
            return $this->bean->$name;
        }
        return '';
    }
    //END BACKWARD COMPATIBILITY
    /**
     * Function:        get_field_def
     * Description:        Get the field definition attributes that are required for the Highlightfield Field
     *                      the primary reason this function is here is to set the dbType to 'varchar',
     *                      otherwise 'Highlightfield' would be used by default.
     * References:      __construct function above
     *
     * @return            Field Definition
     */
    function get_field_def()
    {


*/
    function get_field_def()
    {
        $def = parent::get_field_def();
        //set our fields database type
        $def['dbType'] = 'varchar';
        //set our fields to false to avoid issues with Report Wizard.
        $def['reportable'] = false;
        //set our field as custom type
        $def['custom_type'] = 'varchar';
        //map our extension fields for colorizing the field
        $def['backcolor'] = !empty($this->backcolor) ? $this->backcolor : $this->ext1;
        $def['textcolor'] = !empty($this->textcolor) ? $this->textcolor : $this->ext2;
        return $def;
    }
}


return $def;
    }
}
Note: For theÂ custom field type, theÂ ext1, ext2, ext3, and ext4Â database fields in the fields_meta_data tableÂ offerÂ additional property storage.
Creating the Form Controller
Next, set up the field's form controller. This controller will handle the field's form template in Admin > Studio > Fields and allow us to assign color values to the Smarty template.
./custom/modules/DynamicFields/templates/Fields/Forms/Highlightfield.php
<?php
if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
require_once 'custom/modules/DynamicFields/templates/Fields/TemplateHighlightfield.php';
/**
 * Implement get_body function to correctly populate the template for the ModuleBuilder/Studio


/**
 * Implement get_body function to correctly populate the template for the ModuleBuilder/Studio
 * Add field page.
 *
 * @param Sugar_Smarty $ss
 * @param array $vardef
 *
 */
function get_body(&$ss, $vardef)
{
    global $app_list_strings, $mod_strings;
    $vars = $ss->get_template_vars();
    $fields = $vars['module']->mbvardefs->vardefs['fields'];
    $fieldOptions = array();
    foreach ($fields as $id => $def) {
        $fieldOptions[$id] = $def['name'];
    }
    $ss->assign('fieldOpts', $fieldOptions);
    //If there are no colors defined, use black text on
    // a white background


//If there are no colors defined, use black text on
    // a white background
    if (isset($vardef['backcolor'])) {
        $backcolor = $vardef['backcolor'];
    } else {
        $backcolor = '#ffffff';
    }
    if (isset($vardef['textcolor'])) {
        $textcolor = $vardef['textcolor'];
    } else {
        $textcolor = '#000000';
    }
    $ss->assign('BACKCOLOR', $backcolor);
    $ss->assign('TEXTCOLOR', $textcolor);
    $colorArray = $app_list_strings['highlightColors'];
    asort($colorArray);
    $ss->assign('highlightColors', $colorArray);


$ss->assign('highlightColors', $colorArray);
    $ss->assign('textColors', $colorArray);
    $ss->assign('BACKCOLORNAME', $app_list_strings['highlightColors'][$backcolor]);
    $ss->assign('TEXTCOLORNAME', $app_list_strings['highlightColors'][$textcolor]);
    return $ss->fetch('custom/modules/DynamicFields/templates/Fields/Forms/Highlightfield.tpl');
}
?>
Creating the Smarty Template


}
?>
Creating the Smarty Template
Once theÂ form controller is in place, create the Smarty template. The .tpl below will define the center content of the field's Studio edit view.Â The template includesÂ a coreTop.tpl and coreBottom.tpl file. These filesÂ add the baseÂ field properties such asÂ "Name" and "Display Label" that are common across all field types. This allows us to focus onÂ the fields that are specific to our new field type.
./custom/modules/DynamicFields/templates/Fields/Forms/Highlightfield.tpl
{include file="modules/DynamicFields/templates/Fields/Forms/coreTop.tpl"}
<tr>


    {sugar_translate module="DynamicFields" label="COLUMN_TITLE_DEFAULT_VALUE"}:

        {if $hideLevel < 5}
            <input type='text' name='default' id='default' value='{$vardef.default}'
                   maxlength='{$vardef.len|default:50}'>
        {else}
            <input type='hidden' id='default' name='default' value='{$vardef.default}'>{$vardef.default}
        {/if}

    {sugar_translate module="DynamicFields" label="COLUMN_TITLE_MAX_SIZE"}:


<td>
        {if $hideLevel < 5}
            <input type='text' name='len' id='field_len' value='{$vardef.len|default:25}'
                  onchange="forceRange(this,1,255);changeMaxLength(document.getElementById('default'),this.value);">
            <input type='hidden' id="orig_len" name='orig_len' value='{$vardef.len}'>
            {if $action=="saveSugarField"}
                <input type='hidden' name='customTypeValidate' id='customTypeValidate' value='{$vardef.len|default:25}'


onchange="if (parseInt(document.getElementById('field_len').value) < parseInt(document.getElementById('orig_len').value)) return confirm(SUGAR.language.get('ModuleBuilder', 'LBL_CONFIRM_LOWER_LENGTH')); return true;">
            {/if}
        {literal}
            <script>
                function forceRange(field, min, max) {
                    field.value = parseInt(field.value);
                    if (field.value == 'NaN')field.value = max;
                    if (field.value > max) field.value = max;
                    if (field.value < min) field.value = min;
                }
                function changeMaxLength(field, length) {
                    field.maxLength = parseInt(length);


field.maxLength = parseInt(length);
                    field.value = field.value.substr(0, field.maxLength);
                }
            </script>
        {/literal}
        {else}
            <input type='hidden' name='len' value='{$vardef.len}'>{$vardef.len}
        {/if}

    {sugar_translate module="DynamicFields" label="LBL_HIGHLIGHTFIELD_BACKCOLOR"}:

        {if $hideLevel < 5}
            {html_options name="ext1" id="ext1" selected=$BACKCOLOR options=$highlightColors}
        {else}


{else}
            <input type='hidden' id='ext1' name='ext1' value='{$BACKCOLOR}'>
            {$BACKCOLORNAME}
        {/if}

    {sugar_translate module="DynamicFields" label="LBL_HIGHLIGHTFIELD_TEXTCOLOR"}:

        {if $hideLevel < 5}
            {html_options name="ext2" id="ext2" selected=$TEXTCOLOR options=$highlightColors}
        {else}
            <input type='hidden' id='ext2' name='ext2' value='{$TEXTCOLOR}'>
            {$TEXTCOLORNAME}
        {/if}


{$TEXTCOLORNAME}
        {/if}
    </td>
</tr>
{include file="modules/DynamicFields/templates/Fields/Forms/coreBottom.tpl"}
Registering the Field Type
Once youÂ have defined theÂ Studio template, register theÂ field as a valid field type. For this example, theÂ fieldÂ will inherit the base field typeÂ as it is aÂ text field. In doing this, we areÂ ableÂ to override the coreÂ functions. The most used override is the save function shown below.
./custom/include/SugarFields/Fields/Highlightfield/SugarFieldHighlightfield.php
<?php
require_once 'include/SugarFields/Fields/Base/SugarFieldBase.php';


require_once 'data/SugarBean.php';
class SugarFieldHighlightfield extends SugarFieldBase
{
    //this function is called to format the field before saving.  For example we could put code in here
    // to check spelling or to change the case of all the letters
    public function save(&$bean, $params, $field, $properties, $prefix = '')
    {
        $GLOBALS['log']->debug("SugarFieldHighlightfield::save() function called.");
        parent::save($bean, $params, $field, $properties, $prefix);
    }
}
?>
Creating Language Definitions
For the newÂ field,Â you mustÂ define several language extensions to ensure everything is displayed correctly. This section includesÂ three steps:
Adding the Custom Field to the Type List
Creating Labels for Studio


Adding the Custom Field to the Type List
Creating Labels for Studio
Defining Dropdown Controls
Adding the Custom Field to the Type List
Define theÂ new field type in theÂ field types list. This will allow an Administrator to select the HighlightfieldÂ field type in Studio when creating a new field.
./custom/Extension/modules/ModuleBuilder/Ext/Language/en_us.Highlightfield.php
<?php
$mod_strings['fieldTypes']['Highlightfield'] = 'Highlighted Text';
Creating Labels for Studio
Once the fieldÂ type is defined, add the labels for theÂ Studio template.
./custom/Extension/modules/DynamicFields/Ext/Language/en_us.Highlightfield.php
<?php
$mod_strings['LBL_HIGHLIGHTFIELD'] = 'Highlighted Text';


$mod_strings['LBL_HIGHLIGHTFIELD_FORMAT_HELP'] = '';
$mod_strings['LBL_HIGHLIGHTFIELD_BACKCOLOR'] = 'Background Color';
$mod_strings['LBL_HIGHLIGHTFIELD_TEXTCOLOR'] = 'Text Color';
Defining Dropdown Controls
For this example, we must define dropdown lists, which will be available in Studio for an administrator to add or remove color options for the field type.
./custom/Extension/application/Ext/Language/en_us.Highlightfield.php
<?php
$app_strings['LBL_HIGHLIGHTFIELD_OPERATOR_CONTAINS'] = 'contains';
$app_strings['LBL_HIGHLIGHTFIELD_OPERATOR_NOT_CONTAINS'] = 'does not contain';


$app_strings['LBL_HIGHLIGHTFIELD_OPERATOR_STARTS_WITH'] = 'starts with';
$app_list_strings['highlightColors'] = array(
    '#0000FF' => 'Blue',
    '#00ffff' => 'Aqua',
    '#FF00FF' => 'Fuchsia',
    '#808080' => 'Gray',
    '#ffff00' => 'Olive',
    '#000000' => 'Black',
    '#800000' => 'Maroon',
    '#ff0000' => 'Red',
    '#ffA500' => 'Orange',
    '#ffff00' => 'Yellow',
    '#800080' => 'Purple',
    '#ffffff' => 'White',
    '#00ff00' => 'Lime',


'#00ff00' => 'Lime',
    '#008000' => 'Green',
    '#008080' => 'Teal',
    '#c0c0c0' => 'Silver',
    '#000080' => 'Navy'
);
Enabling Search and Filtering
To enable HighlightfieldÂ for searching and filtering, define the filter operators and the Sugar widget.
Defining the Filter Operators
TheÂ filter operators, defined inÂ ./custom/clients/base/filters/operators/operators.php, Â allow the custom field be usedÂ for searching inÂ Sidecar listviews.Â
./custom/clients/base/filters/operators/operators.php
<?php
require 'clients/base/filters/operators/operators.php';


require 'clients/base/filters/operators/operators.php';
$viewdefs['base']['filter']['operators']['Highlightfield'] = array(
    '$contains' => 'LBL_HIGHLIGHTFIELD_OPERATOR_CONTAINS',
    '$not_contains' => 'LBL_HIGHLIGHTFIELD_OPERATOR_NOT_CONTAINS',
    '$starts' => 'LBL_HIGHLIGHTFIELD_OPERATOR_STARTS_WITH',
);


);
Note: The labels for theÂ filters in this example are defined in Â ./custom/Extension/application/Ext/Language/en_us.Highlightfield.php. For the full list of filters in the system, you can look at ./clients/base/filters/operators/operators.php.
Defining the Sugar Widget
Finally, define the Sugar widget. The widget will help our field for display on Reports and subpanels in backward compatibility. It also controls how search filters are appliedÂ to our field.
./custom/include/generic/SugarWidgets/SugarWidgetFieldHighlightfield.php
<?php
if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
require_once 'include/generic/SugarWidgets/SugarWidgetFieldvarchar.php';


class SugarWidgetFieldHighlightfield extends SugarWidgetFieldVarchar
{
    function SugarWidgetFieldText(&$layout_manager)
    {
        parent::SugarWidgetFieldVarchar($layout_manager);
    }
    function queryFilterEquals($layout_def)
    {
        return $this->reporter->db->convert($this->_get_column_select($layout_def), "text2char") .
        " = " . $this->reporter->db->quoted($layout_def['input_name0']);
    }
    function queryFilterNot_Equals_Str($layout_def)
    {
        $column = $this->_get_column_select($layout_def);


$column = $this->_get_column_select($layout_def);
        return "($column IS NULL OR " . $this->reporter->db->convert($column, "text2char") . " != " .
        $this->reporter->db->quoted($layout_def['input_name0']) . ")";
    }
    function queryFilterNot_Empty($layout_def)
    {
        $column = $this->_get_column_select($layout_def);
        return "($column IS NOT NULL AND " . $this->reporter->db->convert($column, "length") . " > 0)";
    }
    function queryFilterEmpty($layout_def)
    {
        $column = $this->_get_column_select($layout_def);


$column = $this->_get_column_select($layout_def);
        return "($column IS NULL OR " . $this->reporter->db->convert($column, "length") . " = 0)";
    }
    function displayList($layout_def)
    {
        return nl2br(parent::displayListPlain($layout_def));
    }
}
?>
Once the files are in place, navigate to Admin > Repair > Quick Repair and Rebuild. It is also best practiceÂ to clear your browser cache before using the newÂ field type in Studio.
  	Last modified: 2023-03-09 22:43:32


Modifying Layouts to Display Additional Columns
Overview
By default, the record view layout for each module displays two columns of fields. The number of columns to display can be customized on a per-module basis with the following steps.
Resolution
First, you will want to ensure your layouts are deployed in the custom directory. If you have not previously customized your layouts via Studio, go to Admin > Studio > {Module Name} > Layouts. From there, select each layout you wish to add additional columns to and click 'Save & Deploy'. This action will create a corresponding layout file under the ./custom/modules/{Module Name}/clients/base/views/record/ directory. The file will be named record.php.
In your custom record.php file, locate the maxColumnsÂ value under templateMeta array, and change it to the number of columns you would like to have on screen:


'maxColumns' => '3',
Once that is updated, locate the widths array to define the spacing for your each column(s). You should have a label and field entry for each column in your layout:
'widths' => array (
    0 => array (
        'label' => '10',
        'field' => '30',
    ),
    1 => array (
        'label' => '10',
        'field' => '30',
    ),
    2 => array (
        'label' => '10',
        'field' => '30',
    ),
),
Next, under the existingÂ panelsÂ array, each entry will already have a columns property, with a value of 2. Update each of these to the number of columns you set forÂ maxColumnsÂ :
'columns' => 3,


'columns' => 3,
Once the file has been modified, you can navigate to Studio and add fields to your new column in the layout. For any rows that already contain two fields, the second field will automatically span the second and third column. Simply click the minus (-) icon to contract the field to one column and expose the new column space:
After you have added the desired fields in Studio, click 'Save & Deploy' to finalize your changes
Column Widths
Studio does not support expanding a field beyond two column-widths. If you want a field to span the full layout (all three columns), you would need to manually update this in the custom record.php like so:
array (
    'name' => 'description',
    'span' => 12,
)


'name' => 'description',
    'span' => 12,
)
Changing the span value from 8 to 12. After saving the changes on the file, you would then run a Quick Repair and Rebuild. However, any updates in Studio will revert the span back to 8.
Â
  	Last modified: 2023-02-03 21:04:03


Module Loadable Packages
Examples working with the module loadable packages.


TopicsCreating an Installable Package for a Logic HookThese examples will demonstrate how to create module loadable packages for logic hooks based on different scenarios.Creating an Installable Package That Copies FilesThis is an overview of how to create a module loadable package that will copy files into your instance of Sugar. This is most helpful when your instance is hosted in Sugar's cloud environment or by a third party. For more details on the $manifest or $installdef options, you can visit the Introduction to the Manifest. This example package can be downloaded here.Creating an Installable Package that Creates New FieldsThis is an overview of how to create a Module Loader package that will install custom fields to a module. This example will install a set of fields to the accounts module. The full package is downloadable here for your reference. For more details on the $manifest or $installdef options, you can visit the Introduction to the Manifest File.Removing Files with


options, you can visit the Introduction to the Manifest File.Removing Files with an Installable PackageThis is an overview of how to create a module loadable package that will remove files from the custom directory that may have been left over from a previous package installation or legacy versions of your code customization.


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


Creating an Installable Package for a Logic Hook
Overview
These examples will demonstrate how to create module loadable packages for logic hooks based on different scenarios.
Logic Hook File vs. Logic Hook Definition
No matter the deployment method, all logic hooks require being registered (meaning they are attached to the $hook_array array), defining the logic hook event, module, file to load, and class and function to run for that logic hook event. For all deployment options, the logic hook definition points to a file where the class and function to run is located, and this file must be copied to the instance via the copyÂ installdefs of the manifest. The bulk of this article will focus on the various options for registering the logic hook definition via the package manifest. But all deployment strategies will assume the same logic hook definition, the same logic hook file, and will use the sameÂ copy array to move the logic hook file into the instance.


Note: More information on the $manifest or $installdefÂ can be found inÂ the Introduction to the Manifest .
The Logic Hook
This logic hook will install a before_save hook to the accounts module that will default the account name to 'My New Account Name ({time stamp})'. This various ways of installing this hook will be shown in the following sections.
Logic Hook Definition
<basepath>/Files/custom/Extension/Accounts/Ext/LogicHooks/account_name_hook.php
<?php
$hook_array['before_save'][] = Array(
    99,
    'Example Logic Hook - Updates account name',
    'custom/modules/Accounts/accounts_save.php',
    'Accounts_Save',
    'updateAccountName'
);
Logic Hook File


'updateAccountName'
);
Logic Hook File
<basepath>/Files/custom/modules/Accounts/accounts_save.php
<?php
if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
class Accounts_Save
{
	function updateAccountName($bean, $event, $arguments)
	{
		$bean->name = "My New Account Name (" . time() . ")";
	}
}
Distributed Plugin


}
}
Distributed Plugin
If the package being built is intended to be distributed to various Sugar instances as part of a distributed plugin, your logic hook definition should be created as part of the extension framework using the LogicHooks Extension, which can be done usingÂ $installdefs['logic_hooks'] in the package manifest. When using this extension, the logic hook will be installed and uninstalled along with the plugin.
Note: More information on the $installdefs['logic_hooks']Â can be found inÂ LogicHooks Extensions.
Package Example
The following files are relative to the root of the .zip archive which is referred to at the basepath.Â This example package can be downloaded here.
<basepath>/manifest.php
<?php
$manifest = array(


<?php
$manifest = array(
    'acceptable_sugar_flavors' => array(
        'PRO',
        'ENT',
        'ULT'
    ),
    'acceptable_sugar_versions' => array(
        'exact_matches' => array(),
        'regex_matches' => array(
            '13.0.*
        '),
    ),
    'author' => 'SugarCRM',
    'description' => 'Installs a sample logic hook - using extension framework',
    'icon' => '',
    'is_uninstallable' => true,
    'name' => 'Example Logic Hook Installer - Using Extension Framework',
    'published_date' => '2018-01-01 00:00:00',
    'type' => 'module',


'type' => 'module',
    'version' => '1.0.0',
);
$installdefs = array(
	'id' => 'package_123',
	'copy' => array(
		array(
			'from' => '<basepath>/Files/custom/modules/Accounts/accounts_save.php',
			'to' => 'custom/modules/Accounts/accounts_save.php',
		),
	),
	'hookdefs' => array(
		array(
			'from' => '<basepath>/Files/custom/Extension/Accounts/Ext/LogicHooks/account_name_hook.php',
			'to_module' => 'Accounts',
		)
	),
);


)
	),
);
<basepath>/Files/custom/Extension/Accounts/Ext/LogicHooks/account_name_hook.php
<?php
$hook_array['before_save'][] = Array(
    99,
    'Example Logic Hook - Updates account name',
    'custom/modules/Accounts/accounts_save.php',
    'Accounts_Save',
    'updateAccountName'
);
<basepath>/Files/custom/modules/Accounts/accounts_save.php
<?php
if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
class Accounts_Save
{
	function updateAccountName($bean, $event, $arguments)
	{


function updateAccountName($bean, $event, $arguments)
	{
		$bean->name = "My New Account Name (" . time() . ")";
	}
}
Promotion Process
If the package being built is intended to be part of a code promotion process, you are welcome to use theÂ LogicHooks Extension as mentioned above orÂ you may find it easier to update the ./custom/modules/<module>/logic_hooks.php file, which can be done using the $installdefs['logic_hooks'] in the package manifest.
Package Example
The following files are relative to the root of the .zip archive which is referred to at the basepath.Â This example package can be downloaded here.
<basepath>/manifest.php
<?php
$manifest = array(


<?php
$manifest = array(
        'acceptable_sugar_flavors' => array(
            'PRO',
            'ENT',
            'ULT'
        ),
        'acceptable_sugar_versions' => array(
            'exact_matches' => array(),
            'regex_matches' => array(
                '13.0.*
            '),
        ),
	'author' => 'SugarCRM',
	'description' => 'Installs a sample logic hook - using logic_hooks installdefs',
	'icon' => '',
	'is_uninstallable' => true,
	'name' => 'Example Logic Hook Installer - Using logic_hooks installdefs',
	'published_date' => '2018-01-01 00:00:00',


'published_date' => '2018-01-01 00:00:00',
	'type' => 'module',
	'version' => '1.0.0',
);
$installdefs = array(
	'id' => 'package_456',
	'copy' => array(
		array(
			'from' => '<basepath>/Files/custom/modules/Accounts/accounts_save.php',
			'to' => 'custom/modules/Accounts/accounts_save.php',
		),
	),
	'logic_hooks' => array(
		array(
			'module' => 'Accounts',
			'hook' => 'before_save',
			'order' => 99,


'order' => 99,
			'description' => 'Example Logic Hook - Updates account name',
			'file' => 'custom/modules/Accounts/accounts_save.php',
			'class' => 'Accounts_Save',
			'function' => 'updateAccountName',
		),
	),
);
<basepath>/Files/custom/modules/Accounts/accounts_save.php
<?php
if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
class Accounts_Save
{
	function updateAccountName($bean, $event, $arguments)
	{
		$bean->name = "My New Account Name (" . time() . ")";
	}
}
Conditionally Add or Remove Logic Hook via post_execute script


}
}
Conditionally Add or Remove Logic Hook via post_execute script
A developer may want to be able to trigger the addition or removal of logic hooks from a customization. This can be done using a post execute script with the check_logic_hook_file and remove_logic_hook_file functions.
It is recommended to be cautious when doing this and limit any updates to off-hours as not to affect system behavior for users.
Note:Â remove_logic_hook_fileÂ only removes hooks defined in the given module's logic_hooks.php file.Â check_logic_hook_fileÂ only checks hooks defined in the given module'sÂ logic_hooks.phpÂ file. Neither check or remove hooks defined via the Logic Hook Extension framework. Thus this deployment option should be considered a subset of the Promotion Process deployment option listed earlier.
Package Example


Package Example
Note: For demonstration purposes, in the post_execute script, the Accounts_Save logic hook from the earlier example package is unregistered (if it exists) and a second logic hook Accounts_Save_EnterpriseÂ is registered. The second logic hook points to a different logic hook file, which is moved to the instance using copyÂ in the manifest.
The following files are relative to the root of the .zip archive which is referred to at the basepath.Â This example package can be downloaded here.
<basepath>/manifest.php
<?php
$manifest = array(
        'acceptable_sugar_flavors' => array(
            'PRO',
            'ENT',
            'ULT'
        ),
        'acceptable_sugar_versions' => array(
            'exact_matches' => array(),


'exact_matches' => array(),
            'regex_matches' => array(
                '13.0.*
            '),
        ),
	'author' => 'SugarCRM',
	'description' => 'Installs a sample logic hook - using post_execute check_logic_hook_file',
	'icon' => '',
	'is_uninstallable' => true,
	'name' => 'Example Logic Hook Installer - Using post_execute check_logic_hook_file',
	'published_date' => '2018-01-01 00:00:00',
	'type' => 'module',
	'version' => '1.0.0',
);
$installdefs = array(
	'id' => 'package_789',
	'copy' => array(


'id' => 'package_789',
	'copy' => array(
		array(
			'from' => '<basepath>/Files/custom/modules/Accounts/accounts_save_enterprise.php',
			'to' => 'custom/modules/Accounts/accounts_save_enterprise.php',
		),
	),
	'post_execute' => array(
		 '<basepath>/change_hooks.php',
	),
);
<basepath>/Files/custom/modules/Accounts/accounts_save_enterprise.php
<?php
if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
class Accounts_Save_Enterprise
{
	function updateAccountName($bean, $event, $arguments)


{
	function updateAccountName($bean, $event, $arguments)
	{
		$bean->name = "My New Account Name (" . time() . ") -- Enterprise";
	}
}
<basepath>/change_hooks.php
<?php
if(!defined('sugarEntry')) define('sugarEntry', true);
require_once 'include/entryPoint.php';
$accounts_hook_pro = array(
	99,
	'Example Logic Hook - Updates account name',
	'custom/modules/Accounts/accounts_save.php',
	'Accounts_Save',
	'updateAccountName'
);
$accounts_hook_ent = array(
	99,
	'Example Logic Hook - Updates account name - Enterprise',


99,
	'Example Logic Hook - Updates account name - Enterprise',
	'custom/modules/Accounts/accounts_save_enterprise.php',
	'Accounts_Save_Enterprise',
	'updateAccountName'
);
if( $GLOBALS['sugar_flavor'] == 'ENT' ) {
	//unregister Accounts_Save logichook
	remove_logic_hook("Accounts", "before_save", $accounts_hook_pro);
	//register Accounts_Save_Enterprise logichook
	check_logic_hook_file("Accounts", "before_save", $accounts_hook_ent);
}
  	Last modified: 2023-02-03 21:04:03


Creating an Installable Package That Copies Files
Overview
This is an overview of how to create a module loadable package that will copy files into your instance of Sugar. This is most helpful when your instance is hosted in Sugar's cloudÂ environment or by a third party. For more details on the $manifest or $installdef options, you can visit the Introduction to the Manifest.Â This example package can be downloaded here.
Manifest Example
<?php
$manifest = array(
    'acceptable_sugar_flavors' => array('PRO','ENT','ULT'),
    'acceptable_sugar_versions' => array(
        'exact_matches' => array(),
        'regex_matches' => array('(.*?)\\.(.*?)\\.(.*?)$'),
    ),
    'author' => 'SugarCRM',


),
    'author' => 'SugarCRM',
    'description' => 'Copies my files to custom/src/',
    'icon' => '',
    'is_uninstallable' => true,
    'name' => 'Example File Installer',
    'published_date' => '2018-06-29 00:00:00',
    'type' => 'module',
    'version' => '1.0.0',
);
$installdefs = array(
    'id' => 'package_1530305622',
    'copy' => array(
        0 => array(
            'from' => '<basepath>/Files/custom/src/MyLibrary/MyCustomClass.php',
            'to' => 'custom/src/MyLibrary/MyCustomClass.php',
        ),


),
    ),
);
Copied File Example
This example copies a file toÂ Â ./custom/src/MyLibrary/MyCustomClass.php, which adds the custom namespacedÂ classÂ Sugarcrm\Sugarcrm\custom\MyLibrary\MyCustomClassÂ to the application.
<?php
namespace Sugarcrm\Sugarcrm\custom\MyLibrary;
use \Sugarcrm\Sugarcrm\Logger\Factory;
class MyCustomClass
{
    public function MyCustomMethod($message = 'relax')
    {
        $logger = Factory::getLogger('default');
        $logger->info('MyCustomClass says' . "'{$message}'");
    }
}
  	Last modified: 2023-02-03 21:04:03


Removing Files with an Installable Package
Overview
This is an overview of how to create a module loadable package that will remove files from the custom directory that may have been left over from a previous package installation or legacy versions of your code customization.Â
Note: For SugarCloud customers, this method will not work because removing files is prohibited by Package Scanner. To have files removed, you can submitÂ a case to Sugar Support with a list of files to be removed or request a package approval and provide your module loadable package.


This example will install a custom file and use a pre-execute script to remove files from a previously installed customization. Typically, uninstalling a package would remove the files of the customization, however, in some scenarios, it is desired to install a new package on top of a previous version, and removing files with the newÂ package might be necessary. The full package for this example is downloadableÂ hereÂ for your reference.
For more details on the $manifest or $installdef options, you can visit the Introduction to the ManifestÂ documentation.Â
Manifest Example
<basepath>/manifest.php
<?php
    $manifest = array(
        'acceptable_sugar_flavors' => array(
            'PRO',
            'ENT',
            'ULT'
        ),
        'acceptable_sugar_versions' => array(


),
        'acceptable_sugar_versions' => array(
            'exact_matches' => array(),
            'regex_matches' => array(
                '13.0.*
            '),
        ),
        'author' => 'SugarCRM',
        'description' => 'Installs a custom class file',
        'icon' => '',
        'is_uninstallable' => true,
        'name' => 'Example Removing File Installer',
        'published_date' => '2018-12-06 00:00:00',
        'type' => 'module',
        'version' => '1.2.0'
    );
    $installdefs = array(
        'id' => 'package_1341607504',
        'copy' => array(
            array(


'copy' => array(
            array(
                'from' => '<basepath>/Files/custom/src/CustomClass.php',
                'to' => 'custom/src/CustomClass.php',
            ),
        ),
        'pre_execute' => array(
            '<basepath>/removeLegacyFiles.php',
        ),
        'remove_files' => array(
            'custom/include/CustomClass.php'
        )
    );
<basepath>/removeLegacyFiles.phpÂ
<?php
if (isset($this->installdefs['remove_files'])) {
    foreach($this->installdefs['remove_files'] as $relpath){
        if (SugarAutoloader::fileExists($relpath)) {


if (SugarAutoloader::fileExists($relpath)) {
            SugarAutoloader::unlink($relpath);
        }
    }
}
The full package is downloadableÂ hereÂ for your reference.
  	Last modified: 2023-02-03 21:04:03


Creating an Installable Package that Creates New Fields
Overview
This is an overview of how to create a Module Loader package that will install custom fields to a module. This example will install a set of fields to the accounts module. The full package is downloadable hereÂ for your reference. For more details on the $manifest or $installdef options, you can visit the Introduction to the Manifest File.
Note: Sugar Sell Essentials customers do not have the ability to upload custom file packages to Sugar using Module Loader.
Manifest Example
<basepath>/manifest.php
<?php
$manifest = array(
    'acceptable_sugar_flavors' => array('CE', 'PRO', 'CORP', 'ENT', 'ULT'),
    'acceptable_sugar_versions' => array(
        'exact_matches' => array(),


'exact_matches' => array(),
        'regex_matches' => array(
            0 => '6\\.5\\.(.*?)',
            1 => '7\\.8\\.(.*?)\\.(.*?)',
            2 => '7\\.9\\.(.*?)\\.(.*?)',
            3 => '7\\.10\\.(.*?)\\.(.*?)'
        ),
    'author' => 'SugarCRM',
    'description' => 'Installs a sample set of custom fields to the accounts module',
    'icon' => '',
    'is_uninstallable' => true,
    'name' => 'Example Custom Field Installer',
    'published_date' => '2015-05-11 20:45:04',
    'type' => 'module',


'type' => 'module',
    'version' => '1.0.0',
);
$installdefs = array(
    'id' => 'package_1341607504',
    'language' => array(
        array(
            'from' => '<basepath>/Files/Language/Accounts/en_us.lang.php',
            'to_module' => 'Accounts',
            'language' => 'en_us'
        ),
    ),
    'custom_fields' => array(
        //Text
        array(
            'name' => 'text_field_example_c',
            'label' => 'LBL_TEXT_FIELD_EXAMPLE',
            'type' => 'varchar',
            'module' => 'Accounts',


'module' => 'Accounts',
            'help' => 'Text Field Help Text',
            'comment' => 'Text Field Comment Text',
            'default_value' => '',
            'max_size' => 255,
            'required' => false, // true or false
            'reportable' => true, // true or false
            'audited' => false, // true or false
            'importable' => 'true', // 'true', 'false', 'required'
            'duplicate_merge' => false, // true or false
        ),
        //DropDown
        array(
            'name' => 'dropdown_field_example_c',
            'label' => 'LBL_DROPDOWN_FIELD_EXAMPLE',
            'type' => 'enum',
            'module' => 'Accounts',


'type' => 'enum',
            'module' => 'Accounts',
            'help' => 'Enum Field Help Text',
            'comment' => 'Enum Field Comment Text',
            'ext1' => 'account_type_dom', //maps to options - specify list name
            'default_value' => 'Analyst', //key of entry in specified list
            'mass_update' => false, // true or false
            'required' => false, // true or false
            'reportable' => true, // true or false
            'audited' => false, // true or false
            'importable' => 'true', // 'true', 'false' or 'required'
            'duplicate_merge' => false, // true or false
        ),
        //MultiSelect
        array(


),
        //MultiSelect
        array(
            'name' => 'multiselect_field_example_c',
            'label' => 'LBL_MULTISELECT_FIELD_EXAMPLE',
            'type' => 'multienum',
            'module' => 'Accounts',
            'help' => 'Multi-Enum Field Help Text',
            'comment' => 'Multi-Enum Field Comment Text',
            'ext1' => 'account_type_dom', //maps to options - specify list name
            'default_value' => 'Analyst', //key of entry in specified list
            'mass_update' => false, // true or false
            'required' => false, // true or false
            'reportable' => true, // true or false
            'audited' => false, // true or false


'audited' => false, // true or false
            'importable' => 'true', // 'true', 'false' or 'required'
            'duplicate_merge' => false, // true or false
        ),
        //Checkbox
        array(
            'name' => 'checkbox_field_example_c',
            'label' => 'LBL_CHECKBOX_FIELD_EXAMPLE',
            'type' => 'bool',
            'module' => 'Accounts',
            'default_value' => true, // true or false
            'help' => 'Bool Field Help Text',
            'comment' => 'Bool Field Comment',
            'audited' => false, // true or false
            'mass_update' => false, // true or false
            'duplicate_merge' => false, // true or false


'duplicate_merge' => false, // true or false
            'reportable' => true, // true or false
            'importable' => 'true', // 'true', 'false' or 'required'
        ),
        //Date
        array(
            'name' => 'date_field_example_c',
            'label' => 'LBL_DATE_FIELD_EXAMPLE',
            'type' => 'date',
            'module' => 'Accounts',
            'default_value' => '',
            'help' => 'Date Field Help Text',
            'comment' => 'Date Field Comment',
            'mass_update' => false, // true or false
            'required' => false, // true or false
            'reportable' => true, // true or false
            'audited' => false, // true or false


'audited' => false, // true or false
            'duplicate_merge' => false, // true or false
            'importable' => 'true', // 'true', 'false' or 'required'
        ),
        //DateTime
        array(
            'name' => 'datetime_field_example_c',
            'label' => 'LBL_DATETIME_FIELD_EXAMPLE',
            'type' => 'datetime',
            'module' => 'Accounts',
            'default_value' => '',
            'help' => 'DateTime Field Help Text',
            'comment' => 'DateTime Field Comment',
            'mass_update' => false, // true or false
            'enable_range_search' => false, // true or false
            'required' => false, // true or false


'required' => false, // true or false
            'reportable' => true, // true or false
            'audited' => false, // true or false
            'duplicate_merge' => false, // true or false
            'importable' => 'true', // 'true', 'false' or 'required'
        ),
        //Encrypt
        array(
            'name' => 'encrypt_field_example_c',
            'label' => 'LBL_ENCRYPT_FIELD_EXAMPLE',
            'type' => 'encrypt',
            'module' => 'Accounts',
            'default_value' => '',
            'help' => 'Encrypt Field Help Text',
            'comment' => 'Encrypt Field Comment',
            'reportable' => true, // true or false


'reportable' => true, // true or false
            'audited' => false, // true or false
            'duplicate_merge' => false, // true or false
            'importable' => 'true', // 'true', 'false' or 'required'
        ),
    ),
);
?>
Language Example
<basepath>/Files/Language/Accounts/en_us.lang.php
<?php
$mod_strings['LBL_TEXT_FIELD_EXAMPLE'] = 'Text Field Example';
$mod_strings['LBL_DROPDOWN_FIELD_EXAMPLE'] = 'DropDown Field Example';
$mod_strings['LBL_CHECKBOX_FIELD_EXAMPLE'] = 'Checkbox Field Example';


$mod_strings['LBL_MULTISELECT_FIELD_EXAMPLE'] = 'Multi-Select Field Example';
$mod_strings['LBL_DATE_FIELD_EXAMPLE'] = 'Date Field Example';
$mod_strings['LBL_DATETIME_FIELD_EXAMPLE'] = 'DateTime Field Example';
$mod_strings['LBL_ENCRYPT_FIELD_EXAMPLE'] = 'Encrypt Field Example';
  	Last modified: 2023-02-03 21:04:03


Creating an Auto-Incrementing Field
Overview
This article will cover the two approaches to creating an auto-incrementing field in Sugar.
Case 1: Auto-Incrementing a Field Using a Logic Hook
The benefits of having an auto-incrementing field populated from a logic hook are that it can be incremented based on additional logic. It also allows for multiple auto-incrementing fields, while a database-level auto-incrementing field is only allowed once per table. Auto-incremented fields handled in a logic hook can also be formatted as needed, such as adding leading zeros or alpha characters. You can also control the auto-increment value based on another field, for instance, if you wanted to increase a version_number based on other records related to the same parent. For this example, we will create a simple auto-incrementing field that goes up by 1 whenever the record is saved for the first time.


First, create an integer field incrementing_number_c in Studio.
Next, create a custom class and method that the logic hook will call:
./custom/modules/Accounts/Accounts_Save.php
<?php
class Accounts_Save
{
    function auto_increment($bean, $event, $arguments)
    {
        if (!$arguments['isUpdate']) {
            $sq = new \SugarQuery();
            $sq->from(BeanFactory::newBean('Accounts'), ['team_security' => false]);
            $sq->select->fieldRaw('MAX(' . $sq->getFromAlias() . '_cstm.incrementing_number_c)', 'current_max_id');
            $current_max_id = $sq->getOne();


$current_max_id = $sq->getOne();
            $max_id = (empty($current_max_id)) ? 1 : $current_max_id + 1;
            $bean->incrementing_number_c = $max_id;
        }
    }
}
Next, register the custom logic hook via the LogicHooks Extension framework by creating a file at ./custom/Extension/modules/Accounts/Ext/LogicHooks. An example is shown below:
./custom/Extension/modules/Accounts/Ext/LogicHooks/autoIncrementOnSave.php
<?php
$hook_array['before_save'][] = array(
    2,
    'Custom Logic to Auto-Increment integer_field_c',
    'custom/modules/Accounts/Accounts_Save.php',


'custom/modules/Accounts/Accounts_Save.php',
    'Accounts_Save',
    'auto_increment'
);
After creating these files, run a Quick Repair and Rebuild.
Case 2: Creating an Auto-Incrementing Field on the Database
Creating an auto-incrementing field at the database level has the benefit of being unique and easy to manage. One downside to having an auto-incrementing field at the database level is that it means only being allowed one such field per module, as databases only allow one auto-incrementing field per table. A custom auto-incrementing field also relies on updating a module's core table, which is less upgrade safe. For this reason, a logic hook based auto-incrementing field is recommended over a database-driven auto-incrementing field.


To create an integer field in Sugar which auto-increments at the database level, we will create a custom field via the VarDefs extension as shown below:
./custom/Extension/modules/Accounts/Ext/Vardefs/autoIncrement.php
<?php
$dictionary['Account']['fields']['account_number_auto'] = array (
    'name' => 'account_number_auto',
    'vname' => 'LBL_NUMBER_AUTO',
    'type' => 'int',
    'readonly' => true,
    'len' => 11,
    'required' => true,
    'auto_increment' => true,
    'unified_search' => true,
    'full_text_search' => array(
        'enabled' => true,
        'searchable' => true,


'enabled' => true,
        'searchable' => true,
        'boost' => 1.25
    ),
    'comment' => 'Visual unique identifier',
    'duplicate_merge' => 'disabled',
    'disable_num_format' => true,
    'studio' => array('quickcreate' => false),
    'duplicate_on_record_copy' => 'no',
);
$dictionary['Account']['indices']['account_number_auto'] = array(
    'name' =>'accountsnumk_cstm',
    'type' =>'unique',
    'fields'=>array('account_number_auto'),
);
Navigate to Admin > Studio > Repair > Quick Repair and Rebuild. After the repair, you will see a vardef comparison as shown below:


/*COLUMNS*/
/*MISSING IN DATABASE - account_number_auto - ROW*/
ALTER TABLE accounts add COLUMN account_number_auto int(11) NOT NULL auto_increment;
/* INDEXES */
/*MISSING INDEX IN DATABASE - accountsnumk_cstm - unique ROW */
ALTER TABLE accounts ADD CONSTRAINT UNIQUE accountsnumk_cstm (account_number_auto);;
Alter this to be the following and execute the changes:
ALTER TABLE accounts add COLUMN account_number_auto int(11) UNIQUE NOT NULL auto_increment;


Note: Since auto-incremented fields are populated at the database level, all existing records in the database will have this field populated as soon as the field is created on the database table. The value assigned will not be based on any logic (such as the record creation date) that can be controlled or predicted. Therefore when sorting by this field, records created after the field was added will be in order that they were created, but records created before will not.
  	Last modified: 2023-02-03 21:04:03


Disabling RLI Alerts on Opportunities
Overview
How to disable Revenue Line Item (RLI) alerts on Opportunities using a custom JavaScript controller.
Overriding the Record View
First, we must overrideÂ the stock opportunities record view. This will handle the RLI alerts when navigating to an existing record. This can be done by creating:
./custom/modules/Opportunities/clients/base/views/record/record.js
({
    extendsFrom: 'OpportunitiesRecordView',
    initialize: function (options) {
        this._super('initialize', [options]);
    },
    /**
     * Hide the warning message about missing RLIs
     * @param string module     The module that we are currently on.
     */
    showRLIWarningMessage: function(module) {
        //here we create an empty override function
    },
    /**


//here we create an empty override function
    },
    /**
     * @inheritdoc
     */
    _dispose: function() {
        this._super('_dispose', []);
    }
})
As you can see, this file extends from 'OpportunitiesRecordView' which points our code to extend the stock ./modules/Opportunities/clients/base/views/record/record.js file.
  	Last modified: 2023-02-03 21:04:03


Passing Data to Templates
Overview
This page explains how to create a custom view component that passes data to the Handlebars template.
Steps to Complete
The view component will render the actual content on the page. The view below will display data passed to it from the controller.
./custom/clients/base/views/my-dataview/my-dataview.js
({
    className: 'tcenter',
    loadData: function (options) {
        //populate your data
        myData=new Object();
        myData.myProperty = "My Value";
        this.myData = myData;

        /*
            //alternatively, you can pass in a JSON array to populate your data
            myData = $.parseJSON( '{"myData":{"myProperty":"My Value"}}' );
            _.extend(this, myData);


_.extend(this, myData);
        */
        this.render();
        //reset flags on reload
        if (options && _.isFunction(options.complete)) {
            options.complete();
        }

        // Another Alternative you can get the date from an end point
        let self = this;
        app.api.call("read", app.api.buildURL("Accounts/4bbd3b4e-755d-11e9-9a7b-f45c89a8598f"), null, {
            success: function (accountResponse) {
                self.myData['account'] = accountResponse;
                self.render();
            },
        })
    },
})
Next, add the Handlebars template to render the data. An example is shown below:


Next, add the Handlebars template to render the data. An example is shown below:
./custom/clients/base/views/my-dataview/my-dataview.hbs
{{#with myData}}
 {{account.name}} - {{account.assigned_user_name}} <br>
 {{myProperty}}
{{/with}}
Once the files are in place, add theÂ view to a layout and then navigate to Admin > Repair > Quick Repair and Rebuild.
  	Last modified: 2023-02-03 21:04:03


Converting Address' Country Field to a Dropdown
Overview
Address fields in SugarÂ® are normally text fields, which allow users to enter in the appropriate information (e.g. street, city, and country) for the record. However, withÂ multiple users working in Sugar, it is possible for data (e.g. country) to be entered in a variety of different ways (e.g. USA, U.S.A, and United States) when creating or editing the record. This can cause some issues when creating a report grouped by the Billing Country field, for example, as records with the same country will be grouped separately based on the different ways the country was entered. Â


This article will cover how to change the address' Billing Country field to a dropdown list which will allow users to select a singleÂ value (e.g. USA) and maintain consistency in data throughout the system. Â  Â
Note: This article pertains to Sugar versions 6.x and 7.x.
Use Case
In this example, we will convert the Billing Country field in the Accounts module to a dropdown-type field to allow values to be selected from a dropdown list.
Prerequisites


Prerequisites
A part of making this change involves mappingÂ the "countries_dom" dropdown list to your existingÂ Billing Country field's values. This dropdown list can be accessed and modified via Admin > Dropdown Editor. For more information on editing dropdown lists, please refer to theÂ Developer ToolsÂ documentation. It is very important that the existing values in the Billing Country field exactlyÂ match the Item Name values in the "countries_dom" dropdown list in order for the values to convert properly. If the existing value (e.g. United States) does not match one of the country options (e.g. USA) in the dropdown list, then the new dropdown version of the Billing Country field will most likely default to a blank value for that record record.


To avoid such issues, please review and update all of your existing Billing Country text field values prior to making this change. For more information on updating many records at once via import, please refer to the Updating Records Via ImportÂ article.Â
Steps to Complete
Converting Field to Dropdown
Use the following steps to change the Billing Country field to a dropdown list:
First, we willÂ associate the Billing Country field in the Accounts module with the "countries_dom" dropdown. Locate the following directory in your Sugar file system: ./custom/Extension/modules/Accounts/Ext/Vardefs/


Locate a file calledÂ sugarfield_billing_address_country.phpÂ which controls the billing_address_country field and add the following lines to the file. This will set the field type to 'enum' and define the dropdown (countries_dom) to use for the field.Â Note: If this file or location does not exist, then you will need to first create this path and file.Your file should look like this:
<?php
$dictionary['Account']['fields']['billing_address_country']['type']='enum';
$dictionary['Account']['fields']['billing_address_country']['options']='countries_dom';


Now, since the address block is handled uniquely in Sugar, we will also need to modify the template file in order to display the field as a dropdown anywhere the layout is being used in backward compatibility mode. To do this in an upgrade safe way, you will need to copy the file located inÂ ./include/SugarFields/Fields/Address/en_us.EditView.tpl and place it in the following directory: ./custom/include/SugarFields/Fields/Address/.Â If you are using multiple languages in Sugar, you will need to make this change for each language-type in your system. For our example, we will be focusing on the "en_us" language.Note: You will most likely need to create this directory as it likelyÂ does not already exist.


Once the en_us.EditView.tpl file is in the custom directory,Â locate the line for the input html element for the country field. The line of code should look similar to this:<input type="text" name="{{$country}}" id="{{$country}}" size="{{$displayParams.size|default:30}}" {{if !empty($vardef.len)}}maxlength='{{$vardef.len}}'{{/if}} value='{$fields.{{$country}}.value}' tabindex="{{$tabindex}}">
Directly between the list shown above and the <td> appearing before it, add the following lines to display the field as a dropdown:{if (!isset($config.enable_autocomplete) || $config.enable_autocomplete==false) && isset($fields.{{$country}}.options)}


<select name="{{$country}}" id="{{$country}}" title=''>
{if isset($fields.{{$country}}.value) && $fields.{{$country}}.value != ''}
{html_options options=$fields.{{$country}}.options selected=$fields.{{$country}}.value}
{else}
{html_options options=$fields.{{$country}}.options selected=$fields.{{$country}}.default}
{/if}
</select>
{else}


{/if}
</select>
{else}
<input type="text" name="{{$country}}" id="{{$country}}" size="{{$displayParams.size|default:30}}" {{if !empty($vardef.len)}}maxlength='{{$vardef.len}}'{{/if}} value='{$fields.{{$country}}.value}' tabindex="{{$tabindex}}">
{/if}
Save the changes to the file.
Once the necessary change has been made, please navigate to Admin > Repair and perform a Quick Repair and Rebuild in order for the change to take effect. Your Billing Country field will now display as a dropdown field in the Accounts module.


Please note that only the Account module's Billing Country field will be converted to a dropdown field once this change is applied. If you wish to have the Shipping Country field converted to a dropdown as well, please follow the stepsÂ above, but be sure to make the change specific to the "shipping_address_country" field. In addition, you can make this change for other modules (e.g. Contacts and Leads) as well by specifying the appropriate module name (e.g. Contacts) and field name (e.g. Primary Address Country) when going through the steps.
Updating the Field Type for List View Filter
Once the appropriate changes have been made per the section above, use the following steps to get the list view filter to recognize the change:
Navigate to Admin > Studio > Accounts > Layouts > Search.


Navigate to Admin > Studio > Accounts > Layouts > Search.
Drag the Billing Country field from the Default column to the Hidden column and click "Save & Deploy".
Now drag the Billing Country field back to the Default column and click "Save & Deploy" again.
This will retrieve the Billing Country field in its new state and add it to the account's list view filter.
Application
Once the Billing Country field is converted successfully to a dropdown, all records that had an existing value in the field should have the matching country automatically selected. Going forward, users will simply need to select the appropriate country when entering address information in Sugar.Â
Please note that administrators can add additional country values as necessary to theÂ "countries_dom" dropdown via Admin > Dropdown Editor. For more information on editing dropdown lists, please refer to the Developer Tools documentation. Â


The Billing Country field will now appear as follows:
For Accounts record view:Â
For Accounts list view filter:
Â
  	Last modified: 2023-02-03 21:04:03


Customizing the Email Editor Buttons
Overview
The Emails module in SugarÂ® displays commonly-used buttons in the HTML editor. This article explains how to modify the buttons on the editor's toolbar.
Modifying The View
The following sections outline how to customize the button toolbar presented within the TinyMCE editor. In the example below, we will be adding buttons for Cut, Copy, and Paste to the toolbar.
Using Custom Module Metadata


Using Custom Module Metadata
To override the Emails moduleÂ compose-emailÂ view, we will need to copyÂ ./modules/Emails/clients/base/views/compose-email/compose-email.phpÂ toÂ ./custom/modules/Emails/clients/base/views/compose-email/compose-email.php. Once completed, we will then edit the ['tinyConfig']['toolbar']Â definitionÂ to includeÂ | cut copy pasteÂ .Â An example of this is shown below:
./custom/modules/Emails/clients/base/views/compose-email/compose-email.php
$viewdefs['Emails']['base']['view']['compose-email'] = array(
    ...
    'panels' => array(
        array(


...
    'panels' => array(
        array(
            ...
            'fields' => array(
            	...
                array(
                    'name' => 'description_html',
                    'dismiss_label' => true,
                    'span' => 12,
                    'tinyConfig' => array(
                        'toolbar' => 'code | bold italic underline strikethrough | bullist numlist | ' .
                            'alignleft aligncenter alignright alignjustify | forecolor backcolor | ' .
                            'fontsizeselect | formatselect | fontselect | sugarattachment sugarsignature sugartemplate | cut copy paste',
                    ),
                ),
            ),
        ),
        ...
    ),
);


),
                ),
            ),
        ),
        ...
    ),
);
Note:Â We recommend modifying the modules customÂ metadata for local deployments when the customization is not part of a distributed package.
Once this file is in place, navigate to Admin > RepairÂ > Quick Repair and Rebuild. Once completed, your changes will be reflected in the system.
Using The Module Extension Framework
To extend the Emails moduleÂ compose-emailÂ viewÂ using the extension framework, we will need to loop through the existing views array definition to find and modify the description_htmlÂ field and update the relevant sub-arrayÂ ['tinyConfig']['toolbar']Â with the additional toolbar buttons we want to display. An example of this is shown below:


./custom/Extension/modules/Emails/Ext/clients/base/views/compose-email/add_toolbar_buttons.php
<?php
$target_view = 'compose-email';
$target_fieldname = 'description_html';
$default_toolbar =  'code | bold italic underline strikethrough | bullist numlist | ' .
                            'alignleft aligncenter alignright alignjustify | forecolor backcolor | ' .
                            'fontsizeselect | formatselect | fontselect | sugarattachment sugarsignature sugartemplate';
$custom_toolbar = ' | cut copy paste';
if (!empty($viewdefs['Emails']['base']['view'][$target_view]['panels'])) {


$panels = $viewdefs['Emails']['base']['view'][$target_view]['panels'];
	foreach ($panels as $i => $panel) {
		if (!empty($panel['fields'])) {
			foreach ($panel['fields'] as $j => $field) {
				if ($field['name'] == $target_fieldname) {
					if(!isset($field['tinyConfig'])){
						$field['tinyConfig'] = array();
					}
					/* If toolbar already exists, add our custom buttons to the end,
					otherwise set it to the default and append that. */


otherwise set it to the default and append that. */
					if(!isset($field['tinyConfig']['toolbar'])) {
						$field['tinyConfig']['toolbar'] = $default_toolbar;
					}
					$viewdefs['Emails']['base']['view'][$target_view]['panels'][$i]['fields'][$j]['tinyConfig']['toolbar'] = $field['tinyConfig']['toolbar'] . $custom_toolbar;
				}
			}
		}
	}
}
Note:Â We recommend using the extension framework when the code will be installed as part of a distributed package.Â


Once this file is in place, navigate to Admin > RepairÂ > Quick Repair and Rebuild. Once completed, your changes will be reflected in the system.
Toolbar Format
As demonstrated in the examples above, theÂ toolbarÂ value is a string with the button keywords space-separated. TheÂ |Â indicates a divider should be shown in the editor. If the goal were to replace the toolbar buttons with only text-modifier buttons, the value for toolbarÂ would be:
'bold italic | underline strikethrough'
The toolbar button keywords are documented atÂ TinyMCE Editor Control Identifiers. Note that at this time, only control keywords listed as "Core" are supported by SugarÂ®.
  	Last modified: 2023-02-03 21:04:03


Removing the Account Requirement on Opportunities
Overview
This article covers how to remove the Account field from being required on the Opportunities module.Â
Removing the Requirement in Configuration
By default, Sugar requires the accounts field to be populated on several modules. These modules include
Opportunities
Cases
Contracts
In order to remove this dependency, you will need to modify theÂ require_accountsÂ configuration inÂ yourÂ  ./config_override.php.
$sugar_config['require_accounts'] = false;
The resulting file should look similar to:
Removing the Requirement in Vardefs


The resulting file should look similar to:
Removing the Requirement in Vardefs
Once you have updated your configuration, you may find the field is still required. This may be due to the module's vardefs array having the field's required attribute set to true. This will also need to be updated. UsingÂ the Opportunities vardefs as an example, we will need to do the following:
Create a custom file in the Opportunities Extension directory like ./custom/Extension/modules/Opportunities/Ext/Vardefs/no_account_required.phpÂ
Set the content of this file so that theÂ account_name field has required set to false
<?php
$dictionary['Opportunity']['fields']['account_name']['required'] = false;


Once completed, you will need to navigate to Admin > Repairs and run a Quick Repair & Rebuild
Removing the Requirement in View Metadata
If the Account field is still required after making these changes, this may be due to the views metadata having the field's required attribute set to true. By default, account_nameÂ should not be set to required, but if you have created custom views, these may need to be updated. UsingÂ the Opportunities record view as an example, we will need to do the following:
EditÂ Â ./custom/modules/Opportunities/clients/base/views/record/record.phpÂ with a text editor application.
Search for theÂ account_name field in panel definitions of your record view.


Search for theÂ account_name field in panel definitions of your record view.
RemoveÂ 'required' => true,Â fromÂ the account_nameÂ definition. If it doesn't already exist, you don't need to modify anything.
...
  'fields' =>
    array (
      0 =>
        array (
          'name' => 'account_name',
          'required' => false,
          ...
Your resulting file should be:
...
  'fields' =>
    array (
      0 =>
        array (
          'name' => 'account_name',
          ...
These changes will only affectÂ theÂ record view for Opportunities.Â You may need to modify additional module layouts based on your use case.Â AÂ list of other views you many need to modify for your module areÂ shown below.


./custom/modules/<module>/clients/base/views/list/list.php
./custom/modules/<module>/clients/base/views/record/record.php
./custom/modules/<module>/clients/base/views/dupecheck-list/dupecheck-list.php
./custom/modules/<module>/clients/base/views/resolve-conflicts-list/resolve-conflicts-list.php
./custom/modules/<module>/clients/base/views/selection-list/selection-list.php
./custom/modules/<module>/clients/base/views/subpanel-list/subpanel-list.php
Once completed, you will need to navigate to Admin > Repairs and run a Quick Repair & Rebuild
Application


Application
Now that the appropriate changes have been made, navigate to the Opportunities module and create a new opportunity record. You will notice that the Account Name field no longer indicates "Required" in the field. In addition, the Account Name field will no longer be marked as required when importing records into the Opportunities module.
  	Last modified: 2023-02-03 21:04:03


Modifying Subpanel Buttons
Overview
Several buttons exist on each subpanel by default such as "Create" (+ Plus symbol) and "Link Existing Record". These buttons are controlled by the panel-top view for each module. To make changes to the buttons included in a subpanel layout, you will need to create an override for the panel-top view inÂ ./custom/Extension/modules/<module>/Ext/clients/base/views/panel-top/ and update the correspondingÂ buttons property to include or exclude a button.
If you need to make changes to a specific relationships subpanel, then you will need to modify the subpanels layout definition for the relationship to point to a custom panel-top view that you define.
Steps to Complete
Editing Subpanel Buttons


Steps to Complete
Editing Subpanel Buttons
In this example, we will remove the "Create" button from the Contacts subpanel for all modules. This code snippet uses the same button definition that the create button uses.Â It has been changed to trigger the "Link Existing Record" actionÂ by setting the button type to link-action. The blank array after the initial button is just to assure that the drop-down button displays next to it as disabled for placement purposes.
Create the file custom/Extension/modules/Contacts/Ext/clients/base/views/panel-top/panel-top.php as follows: <?php
$viewdefs['Contacts']['base']['view']['panel-top']['buttons'] = array(
    array(
        'type' => 'actiondropdown',
        'name' => 'panel_dropdown',


'name' => 'panel_dropdown',
        'css_class' => 'pull-right',
        'buttons' => array(
            array(
                'type' => 'link-action',
                'icon' => 'fa-link',
                'name' => 'select_button',
                'label' => ' ',
                'tooltip' => 'LBL_ASSOC_RELATED_RECORD',
            ),
            array(
            ),
        ),
    ),
);
Navigate to Admin > Repair and click "Quick Repair and Rebuild".
The subpanel will appear as shown below for all Contacts subpanels.
Using a Custom Panel-Top View for a Specific Relationship


Using a Custom Panel-Top View for a Specific Relationship
In this example, we will only remove the "Create" action from a specific relationships subpanel. Specifically, the example will look at Contacts subpanel on the Accounts module and implement the above changes explicitly for that relationship.
Create the custom panel-top view in ./custom/modules/Contacts/clients/base/views/panel-top-for-accounts/panel-top-for-accounts.php as follows: <?php
$viewdefs['Contacts']['base']['view']['panel-top-for-accounts'] = array(
    'type' => 'panel-top',
    'template' => 'panel-top',
    'buttons' => array(
        array(
            'type' => 'actiondropdown',
            'name' => 'panel_dropdown',


'name' => 'panel_dropdown',
            'css_class' => 'pull-right',
            'buttons' => array(
                array(
                    'type' => 'link-action',
                    'icon' => 'fa-link',
                    'name' => 'select_button',
                    'label' => ' ',
                    'tooltip' => 'LBL_ASSOC_RELATED_RECORD',
                ),
                array(
                ),
            ),
        ),
    ),
);
Create the subpanel panel-top override definition for Accounts in ./custom/modules/Accounts/clients/base/layouts/subpanels/subpanels.php as follows:Â
<?php
require 'modules/Accounts/clients/base/layouts/subpanels/subpanels.php';


foreach($viewdefs['Accounts']['base']['layout']['subpanels']['components'] as $key => $component){
    if ($component['context']['link'] == 'contacts'){
        $viewdefs['Accounts']['base']['layout']['subpanels']['components'][$key]['override_paneltop_view']  = 'panel-top-for-accounts';
        break;
    }
}
Navigate to Admin > Repair and click "Quick Repair and Rebuild".
Your changes will now beÂ reflected in the system.
  	Last modified: 2023-02-03 21:04:03


Increasing the Number of Dashboards Displayed in the Home Menu
Overview
By default, the "Home Dashboard" comes out-of-the-box with SugarÂ® to display on the home page. In addition, Sugar users with a Sugar Serve and/or Sugar Sell license typeÂ will have access to specialized Home page dashboards called "Service Console" and "Renewals Console".Â  Users have the ability to create new dashboards on their home page and build out its layout and dashlet set. Currently, Sugar imposes a limit of 50 dashboards thatÂ can be displayed under the Home module tab.Â So, when creating multiple dashboards, keep in mind that having more than 50 in the list of available dashboards will cause the ones in the beginning to be dropped off the list.Â This article coversÂ how to increase the number of dashboards allowed in the Home menu via a code-level customization.
Use Case


Use Case
In this example, we will make a code-level change to setÂ the number of dashboards allowed in the Home menu to "80".
Prerequisites
This change requires code-level customizations. You will need direct access to the server as well as administrator access in Sugar in order to perform the necessary actions. If you needÂ assistance making these changes and already have a relationship with a Sugar partner, you can work with them to make this change.Â If not, please refer to the Partner Page to find a reselling partner to help with your development needs.Â
You will also need the following capabilities prior to making this code-level customization:
You should have a working knowledge of PHP developmentÂ and arrays.
You should have access toÂ a text editor that can be used for programming.


You should have access toÂ a text editor that can be used for programming.
If your Sugar instance is hosted on Sugar's cloud service or hosted on a server to which you do not have direct file access, you will need to know how to create and deploy Module Loader packages. For more information, please refer to the Module Loader section of the Developer Guide.
Note: Sugar Sell Essentials customers do not have the ability to upload custom file packages to Sugar using Module Loader.
Steps to Complete
The following steps cover setting the number of dashboards allowed on the Home menu to "80" as an example:


Navigate to ./modules/Home/clients/base/views/module-menu/module-menu.php and copy the file contents to a new file at ./custom/modules/Home/clients/base/views/module-menu/module-menu.php. The contents of the file should look similar to this:
<?php
$viewdefs['Home']['base']['view']['module-menu'] = array(
  'settings' => array(
     'favorites' => 0,
     'recently_viewed' => 10,
     'recently_viewed_toggle' => 3,
  ),
);
?>


),
);
?>
Modify the ./custom/modules/Home/clients/base/views/module-menu/module-menu.php file and add theÂ dashboards => 80, element to the array. The modified file should look similar to this:<?php
$viewdefs['Home']['base']['view']['module-menu'] = array(
  'settings' => array(
     'dashboards' => 80,
     'favorites' => 0,
     'recently_viewed' => 10,
     'recently_viewed_toggle' => 3,
  ),
);
?>
Save the changes to the file and update the ownership and permissions of the files that were created.
For more information on Linux-based stacks, please refer to the Required File System Permissions on Linux article.


For more information on Windows-based stacks, please refer to the Required File System Permissions on Windows With IIS article.Â
Finally, logÂ into Sugar as an administrator, navigate to Admin > Repair, and perform a "Quick Repair and Rebuild". This will rebuild the cached files to fully implement the changes to your Sugar instance.
Application
Once the appropriate changes haveÂ been made, users should be able to create and view up to 80 dashboards on their home page and any previously missing dashboards should now display in the Home menu as well.
  	Last modified: 2023-02-03 21:04:03