after_relationship_delete
Overview
The after_relationship_delete hook executes after a relationship between two records has been deleted.
Definition
function after_relationship_delete($bean, $event, $arguments){}
Arguments
Name
Type
Description
bean
Object
The bean object
event
String
The current event
arguments
Array
Additional information related to the event
arguments.id
String
Module ID
arguments.module
String
Module name
arguments.related_id
String
Related module ID
arguments.related_module
String
Related module name
arguments.link
String
Link field name
arguments.relationship
String
Relationship name
Considerations
This hook will be executed for each side of the relationship. For example, if you delete an association between an account and contact, the hook will run for both.
The 'arguments' parameter will have additional information regarding the records being modified. The $bean variable will not contain this information.
Change Log
Version
Note
6.0.0
Added after_relationship_delete hook
Examples
Creating a Logic Hook using the Extension Framework
./custom/Extension/modules/<module>/Ext/LogicHooks/<file>.php
<?php
    $hook_array['after_relationship_delete'][] = Array(
        //Processing index. For sorting the array.
        1,
        //Label. A string value to identify the hook.
        'after_relationship_delete example',
        //The PHP file where your class is located.
        'custom/modules/<module>/after_relationship_delete_class.php',
        //The class the method is in.
        'after_relationship_delete_class',
        //The method to call.
        'after_relationship_delete_method'
    );
?>
./custom/modules/<module>/after_relationship_delete_class.php
<?php
    if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');


class after_relationship_delete_class
    {
        function after_relationship_delete_method($bean, $event, $arguments)
        {
            //logic
        }
    }
?>
Creating a Core Logic Hook
Prior to Sugar 6.3.x, logic hooks could only be created using the following method. Please note that this approach is still valid but is not recommended when building plugins as it may conflict with existing customizations.
./custom/modules/<module>/logic_hooks.php
<?php
   $hook_version = 1;
   $hook_array = Array();
   $hook_array['after_relationship_delete'] = Array();
   $hook_array['after_relationship_delete'][] = Array(
      //Processing index. For sorting the array.
      1,
      //Label. A string value to identify the hook.
      'after_relationship_delete example',
      //The PHP file where your class is located.
      'custom/modules/<module>/after_relationship_delete_class.php',
      //The class the method is in.
      'after_relationship_delete_class',
      //The method to call.
      'after_relationship_delete_method'
   );
?>
./custom/modules/<module>/after_relationship_delete_class.php
<?php
   if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
   class after_relationship_delete_class
   {
      function after_relationship_delete_method($bean, $event, $arguments)
      {
         //logic
      }
   }
?>
  	Last modified: 2023-02-03 21:04:03


before_fetch_query
Overview
The before_fetch_queryÂ logic hook executesÂ beforeÂ a sugar queryÂ has been executed.
Definition
function before_fetch_query($bean, $event, $arguments){}
Arguments
Name
Type
Description
bean
Object
The bean object
event
String
The current event
arguments
Array
Additional information related to the event
arguments.query
Object
The query to be executed
arguments.fields
Array
An arrayÂ of selected fields
Change Log
Version
Note
7.7.0.0
Added before_fetch_queryÂ logic hook
Examples
Creating a Logic Hook using Extension Framework
./custom/Extension/modules/<module>/Ext/LogicHooks/<file>.php
<?php
    $hook_array['before_fetch_query'][] = Array(
        //Processing index. For sorting the array.
        1,
        //Label. A string value to identify the hook.
        'before_fetch_query example',
        //The PHP file where your class is located.
        'custom/modules/<module>/before_fetch_query_class.php',
        //The class the method is in.
        'before_fetch_query_class',
        //The method to call.
        'before_fetch_query_method'
    );
?>
./custom/modules/<module>/before_fetch_query_class.php
<?php
    if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
    class before_fetch_query_class
    {
        function before_fetch_query_method($bean, $event, $arguments)
        {
            //logic
        }
    }
?>
Creating a Core Logic Hook


//logic
        }
    }
?>
Creating a Core Logic Hook
Prior to Sugar 6.3.x, logic hooks could only be created using the following method. Please note that this approach is still valid but is not recommended when building plugins as it may conflict with existing customizations.
./custom/modules/<module>/logic_hooks.php
<?php
   $hook_version = 1;
   $hook_array = Array();
   $hook_array['before_fetch_query'] = Array();
   $hook_array['before_fetch_query'][] = Array(
      //Processing index. For sorting the array.
      1,
      //Label. A string value to identify the hook.
      'before_fetch_query example',
      //The PHP file where your class is located.
      'custom/modules/<module>/before_fetch_query_class.php',
      //The class the method is in.
      'logic_hooks_class',
      //The method to call.
      'before_fetch_query_method'
   );
?>
./custom/modules/<module>/before_fetch_query_class.php
<?php
   if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
   class before_fetch_query_class
   {
      function before_fetch_query_method($bean, $event, $arguments)
      {
         //logic
      }
   }
?>
  	Last modified: 2023-02-03 21:04:03


before_relationship_add
Overview
The before_relationship_add logic hook executes before a relationship has been added between two records.
Definition
function before_relationship_add($bean, $event, $arguments){}
Arguments
Name
Type
Description
bean
Object
The bean object
event
String
The current event
arguments
Array
Additional information related to the event
arguments.id
String
Module ID
arguments.module
String
Module name
arguments.related_id
String
Related module ID
arguments.related_module
String
Related module name
arguments.link
String
Link field name
arguments.relationship
String
Relationship name
Considerations
This hook will be executed for each side of the relationship. For example, if you add an association between an account and a contact, the hook will run for both records.
The arguments parameter will have additional information regarding the records being modified. The $bean variable will not contain this information.
Change Log
Version
Note
6.4.5
Added before_relationship_add hook
Examples
Creating a Logic Hook using the Extension Framework
./custom/Extension/modules/<module>/Ext/LogicHooks/<file>.php
<?php
    $hook_array['before_relationship_add'][] = Array(
        //Processing index. For sorting the array.
        1,
        //Label. A string value to identify the hook.
        'before_relationship_add example',
        //The PHP file where your class is located.
        'custom/modules/<module>/before_relationship_add_class.php',
        //The class the method is in.
        'before_relationship_add_class',
        //The method to call.
        'before_relationship_add_method'
    );
?>
./custom/modules/<module>/before_relationship_add_class.php
<?php
    if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');


class before_relationship_add_class
    {
        function before_relationship_add($bean, $event, $arguments)
        {
            //logic
        }
    }
?>
  	Last modified: 2023-02-03 21:04:03


Application Hooks
Application hooks execute logic when working with the global application.
Topicsafter_entry_pointThe after_entry_point application hook executes at the start of every request.after_load_userThe after_load_user hook executes after the current user is set for the current request. It handles actions that are dependent on the current user, such as setting ACLs or configuring user-dependent parameters.after_session_startThe after_session_start hook executes before the user's session starts, but after the user's visibility rules have been set up and the after_load_user logic hook has executed.after_ui_footerafter_ui_frameThe after_ui_frame hook executes after the frame has been invoked and before the footer has been invoked for modules in backward compatibility mode. This logic hook has been deprecated and will be removed in a future release.entry_point_variables_settingThe entry_point_variables_setting application hook executes at the start of every entry point.handle_exceptionThe handle_exception logic hook executes when an exception is thrown.server_round_tripExecutes at the end of every page.
  	Last modified: 2023-02-03 21:04:03


after_load_user
Overview
The after_load_userÂ hook executes after the current user is set for the current request. It handles actions that are dependent on the current user, such as setting ACLs or configuring user-dependent parameters.
Definition
function after_load_user($event, $arguments){}
Arguments
Name
Type
Description
event
String
The current event
arguments
Array
Additional information related to the event (typically empty)
Change Log
Version
Note
6.6.0
Added after_load_user hook
Example
./custom/modules/logic_hooks.php
<?php
    $hook_version = 1;
    $hook_array = Array();
    $hook_array['after_load_user'] = Array();
    $hook_array['after_load_user'][] = Array(
        //Processing index. For sorting the array.
        1,
        //Label. A string value to identify the hook.
        'after_load_user example',
        //tde PHP file where your class is located.
        'custom/modules/application_hooks_class.php',
        //tde class the method is in.
        'application_hooks_class',
        //tde method to call.
        'after_load_user_method'
    );
?>
./custom/modules/application_hooks_class.php
<?php
    if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
    class application_hooks_class
    {
        function after_load_user_method($event, $arguments)
        {
            //logic
        }
    }
?>
  	Last modified: 2023-02-03 21:04:03


after_ui_frame
Overview
The after_ui_frame hook executes after the frame has been invoked and before the footer has been invoked for modules in backward compatibility mode.Â This logic hook has been deprecated and will be removed in a futureÂ release.Â
Definition
function after_ui_frame($event, $arguments){}
Arguments
Name
Type
Description
event
String
The current event
arguments
Array
Additional information related to the event (typically empty)
Considerations
This hook is only applicable for modules in backward compatibility mode.
This hook is executed on most views such as the DetailView, EditView, and Listview.
Application hooks do not make use of the $bean argument.
This is logic hook can be used as a global reference (./custom/modules/logic_hooks.php) or as a module reference (./custom/modules/<module>/logic_hooks.php).
Change Log
Version
Note
7.10.0.0
Deprecated after_ui_frame hook
5.0.0a
Added after_ui_frame hook
Example
Module-Specific Hook
This hook can be used at the application level for all modules or limited to specific modules. An example limiting the hook for specific modules is shown below:
./custom/modules/<module>/logic_hooks.php
<?php
    $hook_version = 1;
    $hook_array = Array();
    $hook_array['after_ui_frame'] = Array();
    $hook_array['after_ui_frame'][] = Array(
        //Processing index. For sorting the array.
        1,
        //Label. A string value to identify the hook.
        'after_ui_frame example',
        //The PHP file where your class is located.
        'custom/modules/{module}/logic_hooks_class.php',
        //The class the method is in.
        'logic_hooks_class',


//The class the method is in.
        'logic_hooks_class',
        //The method to call.
        'after_ui_frame_method'
    );
?>
./custom/modules/<module>/logic_hooks_class.php
<?php
    if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
    class logic_hooks_class
    {
        function after_ui_frame_method($event, $arguments)
        {
            //display logic
        }
    }
?>
Application Hook
This hook can be used at the application level for all modules or limited to specific modules. An example of executing the hook for all modules is shown below:
./custom/modules/logic_hooks.php
<?php
    $hook_version = 1;
    $hook_array = Array();
    $hook_array['after_ui_frame'] = Array();
    $hook_array['after_ui_frame'][] = Array(
        //Processing index. For sorting the array.
        1,
        //Label. A string value to identify the hook.
        'after_ui_frame example',
        //The PHP file where your class is located.
        'custom/modules/application_hooks_class.php',
        //The class the method is in.
        'application_hooks_class',
        //The method to call.
        'after_ui_frame_method'
    );
?>
./custom/modules/application_hooks_class.php
<?php
    if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
    class application_hooks_class
    {
        function after_ui_frame_method($event, $arguments)
        {
            //display logic
        }
    }
?>
  	Last modified: 2023-02-03 21:04:03


server_round_trip
Overview
Executes at the end of every page.
Definition
function server_round_trip($event, $arguments){}
Arguments
Name
Type
Description
event
String
The current event
arguments
Array
Additional information related to the event (typically empty)
Considerations
This is a global logic hook where the logic hook reference must be placed in ./custom/modules/logic_hooks.php.
If you are intending to write display logic to the screen, you must first wrap the display logic in an if condition to prevent breaking the Ajax page loads:if (!isset($_REQUEST["to_pdf"]) || $_REQUEST["to_pdf"] == false)
{
    //display logic
}
This hook is executed on all page loads.
Application hooks do not make use of the $bean argument.
Change Log
Version
Note
5.0.0a
Added server_round_trip hook.
Example
./custom/modules/logic_hooks.php
<?php
    $hook_version = 1;
    $hook_array = Array();
    $hook_array['server_round_trip'] = Array();
    $hook_array['server_round_trip'][] = Array(
        //Processing index. For sorting the array.
        1,
        //Label. A string value to identify the hook.
        'server_round_trip example',
        //The PHP file where your class is located.
        'custom/modules/application_hooks_class.php',
        //The class the method is in.
        'application_hooks_class',
        //The method to call.
        'server_round_trip_method'
    );
?>
./custom/modules/application_hooks_class.php
<?php
    if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
    class application_hooks_class
    {


class application_hooks_class
    {
        function server_round_trip_method($event, $arguments)
        {
            //display logic should check for $_REQUEST["to_pdf"]
        }
    }
?>
  	Last modified: 2023-02-03 21:04:03


after_session_start
Overview
The after_session_startÂ hook executes before the user's session starts, but after the user's visibility rules have been set up and the after_load_user logic hook has executed.
Definition
function after_session_start($event, $arguments){}
Arguments
Name
Type
Description
event
String
The current event
arguments
Array
Additional information related to the event (typically empty)
Change Log
Version
Note
6.4.3
Added after_session_start hook
Example
./custom/modules/logic_hooks.php
<?php
    $hook_version = 1;
    $hook_array = Array();
    $hook_array['after_session_start'] = Array();
    $hook_array['after_session_start'][] = Array(
        //Processing index. For sorting the array.
        1,
        //Label. A string value to identify the hook.
        'after_session_start example',
        //The PHP file where your class is located.
        'custom/modules/application_hooks_class.php',
        //The class the method is in.
        'application_hooks_class',
        //The method to call.
        'after_session_start_method'
    );
?>
./custom/modules/application_hooks_class.php
<?php
    if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
    class application_hooks_class
    {
        function after_session_start_method($event, $arguments)
        {
            //logic
        }
    }
?>
  	Last modified: 2023-02-03 21:04:03


handle_exception
Overview
The handle_exception logic hook executes when an exception is thrown.
Definition
function handle_exception($event, $exception){}
Arguments
Name
Type
Description
event
String
The current event
exception
Object
The exception object
Considerations
This hook should not be used for any type of display output.
You can retrieve the exception message by using $exception->getMessage(). All exception classes extend the PHP Exceptions class.
Change Log
Version
Note
6.4.4
Added handle_exception hook
Example
./custom/modules/logic_hooks.php
<?php
    $hook_version = 1;
    $hook_array = Array();
    $hook_array['handle_exception'] = Array();
    $hook_array['handle_exception'][] = Array(
        //Processing index. For sorting the array.
        1,
        //Label. A string value to identify the hook.
        'handle_exception example',
        //The PHP file where your class is located.
        'custom/modules/handle_exception_class.php',
        //The class the method is in.
        'handle_exception_class',
        //The method to call.
        'handle_exception_method'
    );
?>
./custom/modules/handle_exception_class.php
<?php
    if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
    class handle_exception_class
    {
        function handle_exception_method($event, $exception)
        {
            //logic with $exception->getMessage()
        }
    }
?>
  	Last modified: 2023-02-03 21:04:03


after_ui_footer
  	Last modified:


entry_point_variables_setting
Overview
The entry_point_variables_settingÂ application hook executes at the start of every entryÂ point.
Definition
function entry_point_variables_setting($event, $arguments){}
Arguments
Name
Type
Description
event
String
The current event
arguments
Array
Additional information related to the eventÂ (typicallyÂ empty)
Considerations
The entry_point_variables_settingÂ hook is a global logic hook where the logic hook reference must be placed in ./custom/modules/logic_hooks.php.
This hook is executed at the start of every request at the end of ./include/entryPoint.php.
This hookÂ doesÂ not make use of the $bean argument.
Change Log
Version
Note
6.4.3
Added entry_point_variables_setting hook
Example
./custom/modules/logic_hooks.php
<?php
    $hook_version = 1;
    $hook_array = Array();
    $hook_array['entry_point_variables_setting'] = Array();
    $hook_array['entry_point_variables_setting'][] = Array(
        //Processing index. For sorting the array.
        1,
        //Label. A string value to identify the hook.
        'entry_point_variables_setting example',
        //The PHP file where your class is located.
        'custom/modules/application_hooks_class.php',
        //The class the method is in.
        'application_hooks_class',
        //The method to call.
        'entry_point_variables_setting_method'
    );
?>
./custom/modules/application_hooks_class.php
<?php
    if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
    class application_hooks_class
    {
        function entry_point_variables_setting_method($event, $arguments)
        {
            //logic
        }


{
            //logic
        }
    }
?>
  	Last modified: 2023-02-03 21:04:03


after_entry_point
Overview
The after_entry_pointÂ application hook executes at the start of every request.
Definition
function after_entry_point($event, $arguments){}
Arguments
Name
Type
Description
event
String
The current event
arguments
Array
Additional information related to the eventÂ (typicallyÂ empty)
Considerations
The after_entry_pointÂ hook is a global logic hook where the logic hook reference must be placed in ./custom/modules/logic_hooks.php.
This hook is executed at the start of every request at the end of ./include/entryPoint.php.
This hook should not be used for any type of display output.
The after_entry_pointÂ hook is ideally used for logging or loading libraries.
Application hooks do not make use of the $bean argument.
Change Log
Version
Note
6.4.3
Added after_entry_point hook
Example
./custom/modules/logic_hooks.php
<?php
    $hook_version = 1;
    $hook_array = Array();
    $hook_array['after_entry_point'] = Array();
    $hook_array['after_entry_point'][] = Array(
        //Processing index. For sorting the array.
        1,
        //Label. A string value to identify the hook.
        'after_entry_point example',
        //The PHP file where your class is located.
        'custom/modules/application_hooks_class.php',
        //The class the method is in.
        'application_hooks_class',
        //The method to call.
        'after_entry_point_method'
    );
?>
./custom/modules/application_hooks_class.php
<?php
    if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
    class application_hooks_class
    {


class application_hooks_class
    {
        function after_entry_point_method($event, $arguments)
        {
            //logic
        }
    }
?>
  	Last modified: 2023-02-03 21:04:03


Entry Points
Overview
Entry points, defined in ./include/MVC/Controller/entry_point_registry.php, wereÂ used to ensure that proper security and authentication steps are applied consistently across the entire application. While they are still used in some areas of Sugar, any developers using custom entry points should adjust their customizations to use the latestÂ REST API endpointsÂ instead.
Accessing Entry Points
Available custom entry points can be accessed using a URL pattern as follows:
http://{sugar url}/index.php?entryPoint={entry point name}
The entry point name will be the specified index in the $entry_point_registry array. Access to this entry point outside of sugar will be dependent on the auth parameter defined in the $entry_point_registry array as well.Â For more information, refer to the Creating Custom Entry Points page.
TopicsCreating Custom Entry PointsAs of 6.3.x, entry points can be created using the extension framework. The entry point extension directory, located at ./custom/Extension/application/Ext/EntryPointRegistry/, is compiled into ./custom/application/Ext/EntryPointRegistry/entry_point_registry.ext.php after a Quick Repair and Rebuild. Additional information can be found in the extensions EntryPointRegistry section.
  	Last modified: 2023-02-03 21:04:03


Creating Custom Entry Points
Overview
As of 6.3.x, entry points can be created using the extension framework. The entry point extension directory, located at ./custom/Extension/application/Ext/EntryPointRegistry/,Â is compiled into ./custom/application/Ext/EntryPointRegistry/entry_point_registry.ext.php after a Quick Repair and Rebuild. Additional information can be found in the extensions EntryPointRegistry section.
Custom Entry Points
Prior to 6.3.x, an entry pointÂ could be addedÂ by creating the file ./custom/include/MVC/Controller/entry_point_registry.php. This method of creating entry points is still compatible but is not recommended from a best practices standpoint as duplicating ./include/MVC/Controller/entry_point_registry.php to ./custom/include/MVC/Controller/entry_point_registry.phpÂ will prevent any upgrader updates toÂ ./include/MVC/Controller/entry_point_registry.php from being reflected in the system. Entry point registries contain two properties:
file - The path to the entry point.
auth - A Boolean value that determines whether or not the user must be authenticated in order to access the entry point.
$entry_point_registry['customEntryPoint'] = array(
    'file' => 'path/to/customEntryPoint.php',
    'auth' => true
);
Example
The first step is to create the actual entry point. This is where all of the logic for your entry point will be located. This file can be located anywhere you choose. For my example, I will create:
./custom/customEntryPoint.php
<?php
if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
echo "Hello World!";
Next, we will need to create our extension in the application extensions. This will be located at: ./custom/Extension/application/Ext/EntryPointRegistry/customEntryPoint.php
<?php


<?php
$entry_point_registry['customEntryPoint'] = array(
    'file' => 'custom/customEntryPoint.php',
    'auth' => true
);
Finally, navigate to Admin > Repair > Quick Repair and Rebuild. The system will then generate the file ./custom/application/Ext/EntryPointRegistry/entry_point_registry.ext.php containing your registry entry. We are now able to access our entry point by navigating to:
http://{sugar url}/index.php?entryPoint=customEntryPoint
  	Last modified: 2023-02-03 21:04:03


Shortcuts
Overview
Shortcuts isÂ a framework to add shortcut keys to the application. When shortcut keys are registered, they are registered to a particular shortcut session. Shortcut sessions group shortcut keys, and they can be activated, deactivated, saved, and restored. Global shortcut keys can be registered, but they are not tied to a particular shortcut session. They are rather applied everywhere in the application and can only be applied once.
The Shortcut framework is implemented on top of Mousetrap library.
Shortcut Sessions
In order to register a shortcut in Sugar, a shortcut session must first be created by adding ShortcutSession plugin to the top-level layout JavaScript controller.
plugins: ['ShortcutSession']
Then, the top-level layout needs to list which shortcuts are allowed in the shortcut session. ShortcutsÂ can be listed in the top-level layout JavaScript controller :
./custom/clients/layout/my-layout/my-layout.js
shortcuts: [
    'Sidebar:Toggle',
    'Record:Save',
    'Record:Cancel',
    'Record:Action:More'
]
Shortcuts can also be listed in the metadata :
./custom/clients/layout/my-layout/my-layout.php
'shortcuts' => array(
    'Sidebar:Toggle',
    'Record:Save',
    'Record:Cancel',
    'Record:Action:More'
),
Register
Once a shortcut session is created, shortcut keys can be registered by adding the following in your JavaScript code :
app.shortcuts.register('<unique shortcut ID>', '<shortcut keys>', <callback function>, <current component>, <call on focus?>);
Since shortcut keys should only be registered once in your component, they should be registered inside initialize() or someplace where re-rendering the component would not register more than once.
Shortcut IDs


Shortcut IDs
Shortcut IDs should be unique across the application. They should be namespaced by convention, for example Record:Next, Sidebar:Toggle, List:Edit. If the namespace starts with Global:, it assumes that the shortcut is global.
Global shortcuts
Global shortcuts are shortcut keys that are applied once and are available everywhere in the application.
app.shortcuts.register(app.shortcuts.GLOBAL + 'Footer:Help', '?', function() {}, this, false);
Shortcut Keys
There are only certain keys that areÂ supported under the Shortcut framework. The following keyboard keys can be used :Â
All alphanumeric characters and symbols
shift, ctrl, alt, option, meta, command
backspace, tab, enter, return, capslock, esc, escape, space, pageup, pagedown, end, home, ins, del
left, up, right, down
Additional Features
In addition to standard keys, the Shortcut framework also supports some additional features :
Key combinationsÂ : 'ctrl+s'
Multiple keysÂ : ['ctrl+a', 'command+a']
Key sequencesÂ : 'f a'
Input Focus
The fifth parameter in the register method specifies whether or not the shortcut key should be fired when the user is focused in an input field. It is false by default.
app.shortcuts.register('Record:Save', ['ctrl+s','command+s'], function() {}, this, true);
Limitations
Shortcut keys do not work on side panels, like dashboards and previews.
Shortcuts Help
Anytime a new shortcut is created, a help text should be provided in ./clients/base/layouts/shortcuts/shortcuts.php with a shortcut ID and a language string.
'List:Favorite' => 'LBL_SHORTCUT_FAVORITE_RECORD',
Shortcuts help is displayed when Shortcuts button is clicked.
Advanced Features
Enable/disable dynamically


Shortcuts help is displayed when Shortcuts button is clicked.
Advanced Features
Enable/disable dynamically
Shortcuts feature can be enabled and disabled dynamically via code by calling app.shortcuts.enable() and app.shortcuts.disable().
Dynamically saving, creating new, and restoring sessions
A new shortcut session is created when a user visits a top-level layout with ShortcutSession plugin. A new session can be created dynamically:
app.shortcuts.createSession(<array of shorcut IDs>, <component>);
But before creating a new session, the current session should be saved first.
app.shortcuts.saveSession();
app.shortcuts.createSession(<array of shorcut IDs>, <component>);
Once a new session is created, shortcut keys can be registered to it. When the need for the session is done, the previous shortcut session can be restored.
app.shortcuts.restoreSession();
Example
The following example will beÂ to add some custom shortcuts onto a custom layout. For more information on how to create a custom layout, please refer to the Creating Layouts  documentation.
First, on our custom JavaScript controller for our layout, we must enable the ShortcutSession plugin as well as list the shortcuts we will be enabling :
./custom/clients/base/layouts/my-layout/my-layout.js
({
    plugins: ['ShortcutSession'],
    shortcuts: [
        'Global:MyLayout:MyCallback',
    ],
})
Next, on the custom view being rendered on our layout, we will register the new shortcuts as well as define a callback method :Â
./custom/clients/base/views/my-view/my-view.js
...
initialize: function(options){
    this._super('initialize', [options]);
    // call myCallback method when command + a is pressed
    app.shortcuts.register(app.shortcuts.GLOBAL + 'MyLayout:MyCallback', 'command+a', this.myCallback, this, false);


app.shortcuts.saveSession();
    app.shortcuts.createSession([
        'MyLayout:InlineCall'
    ], this);
    // call inline code when control + m or command + m is pressed
    app.shortcuts.register('MyLayout:InlineCall', ['ctrl+m','command+m'], function() {
		console.log("Ctrl or Command m has been pressed");
    }, this, false);
},
myCallback: function(){
    console.log("MyCallback called from Command a");
},
...
The last step will be to create a new label for our shortcut help text and register the help so it displays when "Shortcuts" is click in the footer of the page :
./custom/Extension/application/Ext/Language/en_us.LBL_MYLAYOUT_SHORTCUT_HELP.php
<?php
$app_strings['LBL_MYLAYOUT_MYCALLBACK_HELP'] = "Activates my Callback Method";
$app_strings['LBL_MYLAYOUT_MYINLINECALL_HELP'] = "Activates Inline Code";
./custom/Extension/application/Ext/clients/base/layouts/shortcuts/shortcuts.php
<?php
$viewdefs['base']['layout']['shortcuts']['help']['Global:MyLayout:MyCallback'] = 'LBL_MYLAYOUT_MYCALLBACK_HELP';
$viewdefs['base']['layout']['shortcuts']['help']['MyLayout:InlineCall'] = 'LBL_MYLAYOUT_MYINLINECALL_HELP';
Navigate to Admin > Repair > Quick Repair and Rebuild. The system will then rebuild the extensions and add the new shortcut to the custom layout.
  	Last modified: 2023-02-03 21:04:03


Autoloader
Overview
The autoloader is an API that allows the unified handling of customizations and customizable metadata while reducing the number of filesystem accesses and improving performance.
SugarAutoLoader
The SugarAutoLoader class, located in ./include/utils/autoloader.php, keeps a map of files within the Sugar directory that may be loaded.
Included File Extensions
The autoloader will only map files with the following extensions:
bmp
css
gif
hbs
html
ico
jpg
js
less
override
php
png
tif
tpl
xml
*All other file extensions are excluded from the mapping.
Class Loading Directories
The autoloader will scan and autoload classes in the following directories:
./clients/base/api/
./data/duplicatecheck/
./data/Relationships/
./data/visibility/
./include/
./include/api/
./include/CalendarEvents/
./include/SugarSearchEngine/
./modules/Calendar/
./modules/Mailer/
Ignored Directories
The following directories in Sugar are ignored by the autoloader mapping:
./.idea/
./cache/
./custom/backup/
./custom/blowfish/
./custom/Extension/
./custom/history/
./custom/modulebuilder/
./docs/
./examples/
./portal/
./tests/
./upload/
./vendor/bin/
./vendor/HTMLPurifier/
./vendor/log4php/
./vendor/nusoap/
./vendor/pclzip/
./vendor/reCaptcha/
./vendor/ytree/
Â


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


File Check API
Overview
File check methods for use with the AutoLoader API.
existing(...)
Returns an array of filenames that exist in the file map. Accepts any number of arguments of which can be filename or array of filenames. If no files exist, empty array is returned.
$files = SugarAutoloader::existing('include/utils.php', 'include/TimeDate.php');
existingCustom(...)
This method accepts any number of arguments, each of which can be filename or array of filenames. It will return an array of filenames that exist in the file map, adding also files that exist when custom/ is prepended to them. If the original
            filename already had custom/ prefix, it is not prepended again. custom/ files are added to the list after the root directory files so that if included in order, they will override the data of the root file. If no files exist, empty array is
            returned.
$files = SugarAutoloader::existingCustom('include/utils.php', 'include/TimeDate.php');
existingCustomOne(...)
Returns the last file of the result returned by existingCustom(), or null if none exist. Accepts any number of arguments of which can be filename or array of filenames. Since customized files are placed after the root files, it will return customized file if exists, otherwise root file.

$files = SugarAutoloader::existingCustomOne('include/utils.php');
You should note that the existingCustomOne() method can be used for loading inline PHP files. An example is shown below:
foreach(SugarAutoLoader::existingCustomOne('custom/myFile.php') as $file)
{
    include $file;
}
Alternative to including inline PHP files, loading class files should be done using requireWithCustom() .
fileExists($filename)


fileExists($filename)
Checks if a file exists in the file map. You should note that ".." is not supported by this function and any paths including ".." will return false. The path components should be separated by /.
            You should also note that multiple slashes are compressed and treated as single slash.
$file = 'include/utils.php';
if (SugarAutoloader::fileExists($file))
{
    require_once($file);
}
getDirFiles($dir, $get_dirs = false, $extension = null)
Retrieves the list of files existing in the file map under the specified directory. If no files are found, the method will return an empty array. By default, the method will return file paths, however, If $get_dirs is set to true, the method will
            return only directories. If $extension is set, it would return only files having that specific extension.

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

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


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


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


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


Metadata API
Overview
Methods to load metadata for the AutoLoader API.
Metadata Loading
For the specific sets of metadata, such as detailviewdefs, editviewefs, listviewdefs, searchdefs, popupdefs, and searchfields, a special process is used to load the correct metadata file. You should note that the variable name for the defs, e.g. "detailviewdefs", is usually the same as variable name, except in the case of "searchfields" where it is "SearchFields".
The process is described below:
If ./custom/modules/{$module}/metadata/{$varname}.php exists, it is used as the data file.
If ./modules/{$module}/metadata/metafiles.php or ./custom/modules/{$module}/metadata/metafiles.php exists, it is loaded with the custom file being preferred. If the variable name exists in the data specified by the metafile, the corresponding filename is assumed to be the defs file name.
If the defs file name or its custom/ override exists, it is used as the data file (custom one is preferred).
If no file has been found yet, ./modules/{$module}/metadata/{$varname}.php is checked and if existing, it is used as the data file.
Otherwise, no metadata file is used.
loadWithMetafiles($module, $varname)
Returns the specified metadata file for a specific module. You should note that due to the scope nature of include(), this function does not load the actual metadata file but will return the file name that should be loaded by the caller.
$metadataPath = SugarAutoloader::loadWithMetafiles('Accounts', 'editviewdefs');
loadPopupMeta($module, $metadata = null)
Loads popup metadata for either specified $metadata variable or "popupdefs" variable via loadWithMetafiles() and returns it. If no metadata found returns empty array.


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


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


Email Addresses
Overview
Recommended approaches when working with email addresses in Sugar.
Client Side
Recommended approaches when accessing email addresses in Sugar from a client.
Sidecar
Sidecar is the JavaScript UI framework that users interact within their browsers.
Fetching Email Addresses in Sidecar
In Sidecar, the email field will return an array of email addresses and their properties for the record. Given the model, you can fetch it using:
var emailAddresses = model.get('email');
Note:Â In the past, developers could useÂ model.get("email1") to fetch the primary email address. While this currently does work,Â these legacy email fields areÂ deprecatedÂ and may be subject to removal in an upcoming Sugar release.
Fetching a Primary Email Address in Sidecar
To fetch the primary email address for a bean, you can use app.utils.getPrimaryEmailAddress():
var primaryEmailAddress = app.utils.getPrimaryEmailAddress(model);
Fetching an Email Address by Properties in Sidecar
To fetch an email address based on properties such as invalid_email, you can use app.utils.getEmailAddress(). This function will return the first email address that matches the options or an empty string if not found. An example is shown below:
var emailAddress = app.utils.getEmailAddress(model, {invalid_email: true});
If you have complex filtering rules, you can use _.find() to fetch an email address:
var emailAddress = _.find(model.get('email'), function(emailAddress){
    if (emailAddress.invalid_email == true) {
        return emailAddress;
    }
});
Validating Email Addresses in Sidecar
To validate an email address, you can use app.utils.isValidEmailAddress():
var isValid = app.utils.isValidEmailAddress(emailAddress);


var isValid = app.utils.isValidEmailAddress(emailAddress);
Note: This function is more permissive and does not conform exactly to the RFC standards used on the server. As such, theÂ email addressÂ will be validated again on theÂ serverÂ when theÂ record is saved, which could still fail validation.Â More information can be found in the email address validation section.
Iterating Email Address in Sidecar
To iterate through email addresses on a model, you can use _.each():
_.each(model.get('email'), function(emailAddress) {
    console.log(emailAddress.email_address);
});
Updating Email Addresses in Sidecar
This section covers how to manipulate the email addresses for a model.
Adding Email Addresses in Sidecar
In Sidecar, you can add email addresses to a model using the custom function below:
function addAddress(model, email) {
    var existingAddresses = model.get('email') ? app.utils.deepCopy(model.get('email')) : [],
        dupeAddress = _.find(existingAddresses, function(address){
            return (address.email_address === email);
        }),
        success = false;
    if (_.isUndefined(dupeAddress)) {
        existingAddresses.push({
            email_address: email,
            primary_address: (existingAddresses.length === 0),
            opt_out: app.config.newEmailAddressesOptedOut || false
        });
        model.set('email', existingAddresses);
        success = true;
    }
    return success;
}
Removing Email Addresses in Sidecar
In Sidecar, you can remove email addresses from a model using the custom function below:
function removeAddress(model, email) {
    var existingAddresses = app.utils.deepCopy(model.get('email'));
    var index = false;
    _.find(existingAddresses, function(emailAddress, idx){


_.find(existingAddresses, function(emailAddress, idx){
        if (emailAddress.email_address === email) {
            index = idx;
            return true;
        }
    });
    var primaryAddressRemoved = false;
    if (index !== false) {
        primaryAddressRemoved = !!existingAddresses[index]['primary_address'];
    }
    //Reject this index from existing addresses
    existingAddresses = _.reject(existingAddresses, function (emailInfo, i) { return i == index; });
    // If a removed address was the primary email, we still need at least one address to be set as the primary email
    if (primaryAddressRemoved) {
        //Let's pick the first one
        var address = _.first(existingAddresses);
        if (address) {
            address.primary_address = true;
        }
    }
    model.set('email', existingAddresses);
    return primaryAddressRemoved;
}
Server Side
Recommended approaches when accessing email addresses in Sugar from the server.
SugarBean
The SugarBean is Sugars PHP core object model.
Fetching Email Addresses Using the SugarBean
Using the SugarBean, the $bean->emailAddress->addressesÂ property will return an array of email addresses and its properties. TheÂ $bean->emailAddressÂ property makes use of the EmailAddress class which is located inÂ ./modules/EmailAddresses/EmailAddress.php. An example is shown below:
$emailAddresses = $bean->emailAddress->addresses;
Fetching a Primary Email Address Using the SugarBean
To fetch the primary email address for a bean, you can use $bean->emailAddress->getPrimaryAddress():
$primaryEmailAddress = $bean->emailAddress->getPrimaryAddress($bean);
Another alternative is to use theÂ email_addresses_primary relationship:
$primaryEmailAddress = false;


$primaryEmailAddress = false;
if ($this->load_relationship('email_addresses_primary')) {
    $relatedBeans = $this->email_addresses_primary->getBeans();
    if (!empty($relatedBeans)) {
        $primaryEmailAddress = current($relatedBeans);
    }
}
You may also choose to iterate the email address list with a foreach(). An example function is shown below:
function getPrimaryEmailAddress($bean)
{
    foreach ($bean->emailAddress->addresses as $emailAddress) {
        if ($emailAddress['primary_address'] == true) {
            return $emailAddress;
        }
    }
    return false;
}
Fetching an Email Address by Properties Using the SugarBean
To fetch an email address based on properties such as invalid_email, you can use a foreach():
$result = false;
foreach ($bean->emailAddress->addresses as $emailAddress) {
    if ($emailAddress['invalid_email']) {
        $result = $emailAddress;
        break;
    }
}
Validating Email Addresses Using the SugarBean
To validate an email address, you can use $bean->emailAddress->isValidEmail():
$isValid = $bean->emailAddress->isValidEmail($emailAddress);
Note: TheÂ EmailAddress::isValidEmail method leverages the PHPMailer library bundled with Sugar, specifically the PHPMailer::validateAddress method, which validates the addressÂ according to theÂ RFC 5321Â and RFC 5322 standards. More information can be found in the email address validation section.
Iterating Email Addresses Using the SugarBean
To iterate through email addresses on a bean, you can use foreach():
foreach ($bean->emailAddress->addresses as $emailAddress) {
    $GLOBALS['log']->info($emailAddress['email_address']);
}
Fetching Beans by Email Address Using the SugarBean


}
Fetching Beans by Email Address Using the SugarBean
To fetch all beans related to an email address you can useÂ getBeansByEmailAddress():
$beans = $bean->emailAddress->getBeansByEmailAddress($emailAddress);
If you don't have a bean available, you may choose to create a new EmailAddress object:
$sea = BeanFactory::newBean('EmailAddresses');
$sea->getBeansByEmailAddress($emailAddress);
Updating Email Addresses Using the SugarBean
This section covers how to manipulate the email addresses for a bean.
Adding Email Addresses Using the SugarBean
To add an email address to the bean, you can useÂ $bean->emailAddress->addAddress():
$bean->emailAddress->addAddress('address@sugar.crm');
Note: The addAddress() function has additional parameters that are defaulted for determining if the email address is a primary, reply to, invalid, or opted out email address. You can also specify an id for the email address and whether or not the email address should be validated.
function addAddress($addr, $primary=false, $replyTo=false, $invalid=false, $optOut=false, $email_id = null, $validate = true)
Removing Email Addresses Using the SugarBean
To remove an email address you can use $bean->emailAddress->removeAddress():
$bean->emailAddress->removeAddress('address@sugar.crm');
PDF Templates
Using the Sugar PDF templates, you can reference the primary email address of the bean using:
{$fields.email_addresses_primary.email_address}
REST API
Sugar comes out of the box with an API that can be called from custom applications utilizing the REST interface. The API can be used to mass create and update records in Sugar with external data. For more information on the REST API in Sugar, please refer to the Web Services documentation.
Creating Email Addresses Using the REST API


Creating Email Addresses Using the REST API
When creating records in Sugar through the API, modules with relationships to email addresses can utilize the email link field to specify email addresses for a record. Using the email link field, you can specify multiple email addresses to assign to the record. You may specify the following additional information regarding each email address being added:
Property
Description
invalid_email
Specify this email address as being invalid
opt_out
Specify this email address as being opted out. More information on opt-outs can be found in the Emails documentation.
primary_address
Specify this email address as the primary
Using the /<module>Â POST endpoint, you can send the following JSON payload to create a contact record with multiple email addresses using the email link field: POST URL: http://<site url>/rest/v<version>/Contacts
{
   "first_name":"Rob",
   "last_name":"Robertson",
   "email":[
      {
         "email_address":"rob.robertson@sugar.crm",
         "primary_address":"1",
         "invalid_email":"0",
         "opt_out":"0"
      },
      {
         "email_address":"rob@sugar.crm",
         "primary_address":"0",
         "invalid_email":"0",
         "opt_out":"1"
      }
   ]
}
For more information on the /<module>/:record POST endpoint, you can refer to your instance's help documentation found at:
http://<site url>/rest/v<version>/help
Or you can reference the <module> POST PHP example.
Updating Email Addresses Using the REST API


Updating Email Addresses Using the REST API
When updating existing records in Sugar through the API, modules with relationships to email addresses can use the email link field to specify email addresses for a record. Using the email link field, you can specify multiple email addresses to update the record with. You may specify the following additional information regarding each email address being added:
invalid_email : Specify this email address as being invalid
opt_out : Specify this email address as being opted out
primary_address : Specify this email address as the primary
Using the /<module>/:record PUT endpoint, you can send the following JSON payload to update a contact record with multiple email addresses: PUT URL: http://<site url>/rest/v<version>/Contacts/<record id>
{
   "email":[
      {
         "email_address":"rob.robertson@sugar.crm",
         "primary_address":"1",
         "invalid_email":"0",
         "opt_out":"0"
      },
      {
         "email_address":"rob@sugar.crm",
         "primary_address":"0",
         "invalid_email":"0",
         "opt_out":"1"
      }
   ]
}
For more information on the /<module>/:record PUT endpoint, you can refer to your instance's help documentation found at:
http://<site url>/rest/v<version>/help
You want to reference the <module>/:record PUT PHP example.
Legacy Email Fields
The legacy email fields in SugarÂ areÂ deprecatedÂ and may be subject to removal in an upcoming Sugar release. When using the email1 field, the default functionality is to import the email address specified as the primary address.
Legacy Email Field
Description
email1
The text value of primary email address. Does not indicate if the email address is valid or opted-out.
email2


email2
The text value of first non-primary email address. Does not indicate if the email address is valid or opted-out.
Note: For importing multiple email addresses with properties, you will need to use the email link field.
Creating Email Addresses Using Direct SQL
When importing records into Sugar directly via the database, it is important that you understand the data structure involved before loading data. Email addresses are not stored directly on the table for the module being imported in but are related via the email_addr_bean_rel table.
The table structure for email addresses can be seen from the database via the following SQL statement:
SELECT
 email_addr_bean_rel.bean_id,
 email_addr_bean_rel.bean_module,
 email_addresses.email_address
FROM email_addr_bean_rel
INNER JOIN email_addresses
 ON email_addresses.id = email_addr_bean_rel.email_address_id
 AND email_addr_bean_rel.deleted = 0
WHERE email_addresses.deleted = 0;
Checking for Duplicates
Email addresses can become duplicated in Sugar from a variety of sources including API calls, imports, and from data entry. There are a few ways to have the system check for duplicate contact records, but not many of those methods work for checking email addresses for duplicates. The following section will demonstrate how to find and clean up duplicate email addresses using SQL.
The following SQL query can be used to locate if any email addresses are being used against more than one bean record within Sugar:
SELECT
 email_addresses.email_address,
 COUNT(*) AS email_address_count
FROM email_addr_bean_rel
INNER JOIN email_addresses
 ON email_addresses.id = email_addr_bean_rel.email_address_id
 AND email_addr_bean_rel.deleted = 0
WHERE email_addresses.deleted = 0
GROUP BY email_addresses.email_address
HAVING COUNT(*) > 1;


GROUP BY email_addresses.email_address
HAVING COUNT(*) > 1;
Note: If you convert a Lead record to a Contact record, both the Lead and the Contact will be related to the same Email Address and will return as having duplicates in this query. You can add the following line to the WHERE clause to filter the duplicate check down to only one bean type:
AND email_addr_bean_rel.bean_module = 'Contacts'
Email addresses can not only beÂ duplicated in the system but can occasionally be missing critical data. Each bean record with an email address assigned to itÂ should have an email address designated the primary. The following query will locate any bean records that have at least one email address, where there is not an email address designated as the primary:
SELECT
 email_addr_bean_rel.bean_module,
 email_addr_bean_rel.bean_id,
 COUNT(*) AS email_count,
 COUNT(primary_email_addr_bean_rel.id) AS primary_email_count
FROM email_addr_bean_rel
LEFT JOIN email_addr_bean_rel primary_email_addr_bean_rel
 ON primary_email_addr_bean_rel.bean_module = email_addr_bean_rel.bean_module
 AND primary_email_addr_bean_rel.bean_id = email_addr_bean_rel.bean_id
 AND primary_email_addr_bean_rel.primary_address = '1'
 AND primary_email_addr_bean_rel.deleted = '0'
WHERE email_addr_bean_rel.deleted = '0'
GROUP BY email_addr_bean_rel.bean_module,
 email_addr_bean_rel.bean_id
HAVING primary_email_count < 1;
Note: If you are a SugarCloud customer, you can open up a case with Sugar Support to have this query run for you.
Removing Duplicates
If it is determined you have duplicate email addresses being used in your system, you can use the following query to clean up the records:
START TRANSACTION;
CREATE


START TRANSACTION;
CREATE
 TABLE email_addr_bean_rel_tmp
SELECT
 *
FROM email_addr_bean_rel
WHERE deleted = '0'
GROUP BY email_address_id,
 bean_module,
 bean_id
ORDER BY primary_address DESC;
TRUNCATE TABLE email_addr_bean_rel;
INSERT INTO email_addr_bean_rel
 SELECT
 *
 FROM email_addr_bean_rel_tmp;
SELECT
 COUNT(*) AS repetitions,
 date_modified,
 bean_id,
 bean_module
FROM email_addr_bean_rel
WHERE deleted = '0'
GROUP BY bean_id,
 bean_module,
 email_address_id
HAVING repetitions > 1;
COMMIT;
Note: If you are a SugarCloud customer, you can open up a case with Sugar Support to have this query run for you.
  	Last modified: 2023-02-03 21:04:03


Tags
Overview
The tagging feature allows a user to apply as many "tags" as they choose on any record they want to categorize. This allows the user to effectively group records together from any source within the application and relate them together. Please note that the tag field and Tags module do not support customization at this time.
The Tags Module
The Tags module is a very simple module based on the Basic SugarObject template. It is not studio editable, is part of the default module list, and is available to the application upon installation. By default, the Tags module is set up such that only an Administrator can perform any administrative tasks with the Tags module. In other words, a regular user will have a very restrictive set of permissions inside the Tags module, namely: create, export, view, and share. All other actions within the Tags modules must be carried out by an Administrative user.
The "tag" Field Type
The back end utilizes two new field types: the relatecollection field and the tag field. The tag field is a relatecollection type field with a few added enhancements specific to the tagging implementation:
Enforces uniqueness of a tag when created
Establishes the necessary relationship between the Tags module and the module record being tagged
Collects and formats a tag collection in a way the client understands
Format the tag field for consumption by and from the import process
Handles proper query setting for a search that is filtered by tags
The Taggable Implementation
The taggable implementation is applied to all SugarObject templates so that it is available on all custom modules created and is also applied to all sidecar enabled modules. Any module that implements a SugarObject templateÂ will be taggable by default. Any module that doesn't implement a SugarObject template can easily apply it using the uses property of the module vardefs:
$dictionary[$module] = array(
    ...
    'uses' => array(
        'taggable',
    ),


...
    'uses' => array(
        'taggable',
    ),
    ...
);
There may be instances where a module should not implement tagging yet implementsÂ the SugarObject template. To remove tagging from a module you can use the module vardefs ignore_templates property:
$dictionary[$module] = array(
    ...
    'ignore_templates' => array(
        'taggable',
    ),
    ...
);
The Tagging Relationship Structure
The tagging relationship schema is similar in nature to the email_addresses relationship schema in that it is used to represent a collection of 0-N Tags module records related to a module:
Column
Description
id
The GUID for the relationship between the tag record and the module record
tag_id
The id of the tag record used in this relationship
bean_id
The id of the module record used in this relationship
bean_module
The module for the record being tagged
date_modified
The date the relationship was created/modified
deleted
A tinyint(1) boolean value that indicates whether this relationship is deleted
Tagging in Action
For adding tags from the UI, please refer to the Tags documentation.
Tagging Records from the API
Tagging a record via the APIÂ is done by sending aÂ PUT request to the /<module>/<record>Â API endpoint with a tag property set to an array of key/value pairs that include a tag id (optional) and a tag name:
{
    "name": "Record Name",
    "tag": [
        {"id": "Test Tag", "name": "Test Tag"},
        {"name": "Test Tag 2"},
        {"id": "1234-56-7890", "name": "Test Tag 3"}
    ]
}


]
}
After the record is created/modified and the tag values are applied, the response will contain the returned collection of tags for that record complete with their ids:
{
    "name": "Record Name",
    "tag": [
        {"id": "9876-54-3210", "name": "Test Tag"},
        {"id": "4321-56-0987", "name": "Test Tag 2"},
        {"id": "1234-56-7890", "name": "Test Tag 3"}
    ]
}
You can visitÂ How to Manipulate Tags (CRUD)Â for a full example demonstratingÂ CRUD actions for tags.
Mass Updating Tags on Records Via the API
Mass updating records with tags are as simple as sending a MassUpdate PUT request to /<module>/MassUpdateÂ with a payload similar to:
{
    "massupdate_params": {
        "uid": ["12345-67890", "09876-54321"],
        "tag": [
            { "id": "23456-78901", "name": "MyTag1" },
            { "id": "34567-89012", "name": "MyTag2" }
        ]
    }
}
This request will override all existing tags on the named records. To append tags to a record, send the "tag_type" request argument set to a value of 1:
{
    "massupdate_params": {
        "uid": ["12345-67890", "09876-54321"],
        "tag": [
            { "id": "23456-78901", "name": "MyTag1" },
            { "id": "34567-89012", "name": "MyTag2" }


],
        "tag_type": "1"
    }
}
More information on this API endpoint can be found in the /<module>/MassUpdate PUT documentation.
Fetching Tags on a Record
By default, the tag field is added to all Sidecar module record views. That means when a request is made for a single record through the API, that single record will return the "tag" field, which will be a collection of key:value pair objects.
For example, when a request is made to the /Accounts/:record GET endpoint, the tags associated with the AccountrecordÂ  will be returned in the tag field as an array of objects:
{
    "id": "<record>",
    "name": "Record Name",
    "tag": [
        { "id": "9876-54-3210", "name": "Test Tag" },
        { "id": "4321-56-0987", "name": "Test Tag 2" },
        { "id": "1234-56-7890", "name": "Test Tag 3" }
    ]
}
Filtering a Record List by Tags
Filtering a list of records by tags can be done simply by sending a filter request to the ModuleApi endpoint for a module. For example, to filter the Accounts list where the tag field has a tag by the name of "Tradeshow", you can send a request to the /Accounts GETÂ endpoint with the following request arguments:
{
    "view": "list",
    "filter": [
        {
            "tag": {
                "$in": [
                    {
                        "name": "Tradeshow"
                    }
                ]
            }
        }
    ]
}
Currently, the tag field filter definitions support the following filter operators:


]
}
Currently, the tag field filter definitions support the following filter operators:
Is any of ($in)
Is not any of ($not_in)
Is empty ($empty)
Is not empty ($not_empty)
Fetching a list of Tags from the Tags module
Fetch a list of tag records from the Tags module is done the same way as fetch a list of records from any other module, namely by sending a GET request to the /Tags ModuleApi endpoint.Â More information can be found in the /<module> GET documentation.
Manipulating Tags Programmatically
Getting Tags Related to a Record
Here is an example that demonstrates how to get all the tags and its ids related to a contact record:
// Creating a Bean for Contacts
$bean = BeanFactory::getBean("Contacts");
// Creating a Bean for Tags
$tagBean = BeanFactory::newBean('Tags');
// Get all the tags related to Contacts Bean by givin Contact ID. You can provide more than one Record ID.
$tags = $tagBean->getRelatedModuleRecords($bean, ["<CONTACT_RECORD_ID>"]);
Creating a New Tag and Adding to a Record
Here is an example that demonstrates how to add create a tag and add to a contact record.
In order to add a new tag first, we will create the tag bean. Then using load_relationship function we will add the newly created tag id to the contactsÂ
// Creating new Tag Bean
$tagBean = BeanFactory::newBean("Tags");
// Setting its name
$tagBean->name = "New Tag";
$tagBean->save();
// Retrieving the Contacts Bean
$bean = BeanFactory::getBean("Contacts", "<RECORD_ID>");
// Getting tag field and its properties
$tagField = $bean->getTagField();


// Getting tag field and its properties
$tagField = $bean->getTagField();
$tagFieldProperties = $bean->field_defs[$tagField];
// Identifying relation link
$link = $tagFieldProperties['link'];
// Loading relationship
if ($bean->load_relationship($link)) {
    // Adding newly created Tag Bean
    $bean->$link->add($tagBean->id);
}
Removing Tags from a Record
Here is an example that demonstrates how to remove a tag from a contact record:
// Getting the Contacts Bean
$bean = BeanFactory::getBean("Contacts", "<RECORD_ID>");
// Getting tag field and its properties
$tagField = $bean->getTagField();
$tagFieldProperties = $bean->field_defs[$tagField];
// Identifying relation link
$link = $tagFieldProperties['link'];
// Loading relationship
if($bean->load_relationship($link)){
    // Removing the Tag ID
    $bean->$link->delete($bean->id, "<TAG_RECORD_ID>");
}
Synchronizing Tags by Name With API Helpers
If you have multiple tags that you need to add and delete at the same time, then you can use SugarFieldTag->apiSave method. Here is an example that demonstrates how to sync tags by using SugarFieldTag ClassÂ forÂ a Contact record.
It is important to know that since this is a sync; you will need to keep the existing tags if you want them to still exist in the record.Â
// Getting the Contacts Bean
$bean = BeanFactory::getBean("Contacts", "<RECORD_ID>");
// Getting Tag Field ID
$tagField = $bean->getTagField();
// Getting Tag Field Properties
$tagFieldProperties = $bean->field_defs[$tagField];


$tagFieldProperties = $bean->field_defs[$tagField];
// Preparing the latest Tags to be sync with the record
$tags = [
    // Note: Already attached tags will be automatically removed from the record
    // If you want to keep some of the existing tags then you will need to keep them in the array
    "tag" => [
        // Since this tag is already added, it will be preserved
        'already added tag' => 'Already Added Tag',
        // The new tags to add - All other tags that previously existed will be deleted
        'new tag' => 'New Tag',
        'new tag2' => 'New Tag2',
    ],
];
// Building SugarFieldTag instance
$SugarFieldTag = new SugarFieldTag();
// Passing the arguments to save the Tags
$SugarFieldTag->apiSave($bean, $tags, $tagField, $tagFieldProperties);
  	Last modified: 2023-02-03 21:04:03


Backward Compatibility
Overview
As of SugarÂ® 7, modules are built using the Sidecar framework. Any modules that still use the MVC architecture and have not yet migrated to the Sidecar framework are set in what Sugar refers to as "backward compatibility" (BWC) mode.
For the list of Studio-enabled modules in backward compatibility for your version of Sugar, please refer to the User Interface (Legacy Modules) documentation for your version of Sugar.
URL Differences
There are some important differences between the legacy MVC and Sidecar URLs. When a module is set in backward compatibility, the URL will contain "/#bwc/" in the path and use query string parameters to identify the module and action. An example of the Sidecar BWC URL is shown below.
http://{site url}/#bwc/index.php?module=<module>&action=<action>
You can see that this differs from the standard route of:
http://{site url}/#<module>/<record id>
Studio Differences
There are some important differences between the legacy MVC modules and Sidecar modules. When a module is in backward compatibility mode, an asterisk is placed next to the modules name in Studio. Modules in backward compatibility will use the legacy MVC metadata layouts, located in ./modules/<module>/metadata/, as follows:
Layouts
Edit View
Detail View
List View
Search
Basic Search
Advanced Search
These layouts differ from Sidecar in many ways. The underlying metadata is very different and you should notice that the MVC layouts have a separation between the Edit View and Detail View whereas the Sidercar layouts make use of the Record View. The Sidecar metadata for Sugar is located in ./modules/<module>/clients/base/views/.
Layouts
Record View
List View
Search
Search
TopicsEnabling Backward CompatibilityHow to enable backward compatibility for modules.Converting Legacy Modules To SidecarHow to upgrade a legacy module to be Sidecar enabled.


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


Enabling Backward Compatibility
Overview
How to enable backward compatibility for a module.
Enabling Backward Compatibility
Backward Compatibility Mode is not a permanent solution for modules with legacy customizations. If you should need to temporarily get a module working due to legacy customizations, you can follow the steps below to enable the legacy MVC framework.Â Please note that switching stock Sugar modules from the Sidecar framework to backward compatibilityÂ mode is not supported and may result in unexpected behaviors in the application.
Enabling BWC
To enable backward compatibility, you must first create a file in ./custom/Extension/application/Ext/Include/ for the module. If the module is custom, there will already be an existing file in this folder pertaining to the module that you can edit.
./custom/Extension/application/Ext/Include/<file>.php
<?php
$bwcModules[] = '<module key>';
Once the file is in place, you will need to navigate to Admin > Repair > Quick Repair and Rebuild. The Quick Repair can wait until you have completed the following sections for this customization.
Verifying BWC Is Enabled
To verify that backward compatibility is enabled, you can inspect App.metadata.get().modules after running a Quick Repair and Rebuild in your browser's console window:
Using Developer Tools in Google Chrome
Open Developer Tools
This can be done in the following ways:
Command + Option + i
View > Developer > Developer Tools
Right-click on the web page and selecting "Inspect Element"
Select the "Console" tab
Run the following command, replacing <module key> and verify that it returns true:
App.metadata.get().modules.<module key>.isBwcEnabled
Using Firebug in Google Chrome or Mozilla Firefox
Open Firebug
Select the "Console" tab
Run the following command, replacing <module key>, and verify that it returns true:


Run the following command, replacing <module key>, and verify that it returns true:
App.metadata.get().modules.<module key>.isBwcEnabled
  	Last modified: 2023-02-03 21:04:03


Converting Legacy Modules To Sidecar
How to upgrade a legacy module to be Sidecar enabled.
Overview
After upgrading your instance to Sugar 7.x, some custom modules may be left in the legacy backward compatible format. This is normally due to the module containing a custom view or file that Sugar does not recognize as being a supported customization for Sidecar. To get your module working with Sidecar, you will need to remove the unsupported customization and run the UpgradeModule.php script.
Note:Â The following commands should be runÂ on a sandbox instance before being applied to any production environment. It is also important to note that this script should only be run against custom modules. Stock modules in backward compatibility should remain in backward compatibility.
Steps to Complete
The following steps require access to both the Sugar filesystem as well as an administrative user.
Note:Â As of Sugar 10.0,Â the UpgradeModule.php script has been removed from the codebase. We are providing a zip of the files that were removed so that it is still possible to run the upgrade.
Begin by downloading this zip file that contains the previously removed code
Unzip the file and move the contents into your Sugar application at ./modules/UpgradeWizard/
To upgrade aÂ custom module, identify the module's unique key. This key can be easily found by identifying the module's folder name in ./modules/<module key name>/. The module's key name will be in the format of abc_module.Â
Next, change to theÂ ./modules/UpgradeWizard/Â path relative to your Sugar root directory in your terminal or command line application:cd <sugar root>/modules/UpgradeWizard/
Run theÂ UpgradeModule.phpÂ script, passing in theÂ sugar root directory andÂ the unique key of the legacy module:php UpgradeModule.php <sugar root> <module key>


An example is shown below:php UpgradeModule.php /var/www/html/sugarcrm/ abc_module
The script with then output a series of messages identifying issues that need to be corrected. Once the issues have been addressed, you can then run theÂ UpgradeModule.phpÂ script again to confirm no errors have been found.
Once completed, log into your instance and navigate to Admin > Repair > Quick Repair and Rebuild. Test the customÂ module to ensure itÂ is working as expected. There is noÂ guarantee that the module will be fully functional in Sidecar and may therefore require additional development effort to ensure compatibility.
  	Last modified: 2023-02-03 21:04:03


Access Control Lists
Overview
Access Control Lists (ACLs) are used to control access to the modules, data,Â and actions available to users in Sugar. By default, Sugar comes with a default ACL Strategy that is configurable by an Admin through the Roles module via Admin >Â Role Management. Sugar also comes with other ACL strategies that are located in the ./data/acl/ folder, and are used in other parts of the system such as the Administration module, Emails module, Locked Fields functionality, and many others. They can also be leveraged by customizations and custom ACL strategies can be implemented to control your own customizations.
Actions
Actions are the use cases in which a User interacts with a particular module in the system. ACLs control access by using different logic to determine if a user can do an action. Below is a list of actions that are utilized by Sugar and will be requested actions against a custom ACL strategy implementation. You can add your own actions to be checked against in a custom strategy, but the following list should always be considered:
index, list, listview - for module ListView access
detail, detailview, view - module DetailView access
popupeditview, editview - module EditView access
edit, save - editing module data
import - import into the module
export - export from the module
delete - delete module record
team_security - should this module have team security enabled?
field - access field ACLs. The field action is specified via context array, see below
subpanel - checks if subpanel should be displayed. Should have "subpanel" context option.
Note: Action names are not case sensitive, although the standard practice is to use them in all lowercase
Field Actions
When using the field action, the action for the particular field that is being checked is passed via Context. The field actions that are used in Sugar are:
access - any access to field data


access - any access to field data
read, detail - read access
write, edit - write access
Context
The context array is used throughout Sugar's ACL architecture as a way to pass extra contextual data that is used by an ACL Strategy to determine the access request.Â The following context parameters are used in stock contexts, and should be used as a guideline for custom ACL implementations as parameters that should be accommodated or used:
bean (SugarBean) - the current SugarBean object
user (User) - the user to check access for, otherwise defaults to the current user.
user_id (String) - the current user ID (also overrides global current user). If the current user object is available, use the user context, since it has precedent.
owner_override (Bool) - if set to true, apply ACLs as if the current user were the owner of the object.
subpanel (aSubPanel) - used in subpanel action to provide aSubPanel object describing the panel.
field, action (String) - used to specify field name and action name for field ACL requests.
massupdate (Bool) - true if save operation is a mass update
SugarACL
The SugarACL Class, ./data/SugarACL.php, is the static API for checking access for an action against a module.Â
checkAccess()
The checkAccess() method is used to check a users access for a given action against a module.Â
SugarACL::checkAccess('Accounts','edit');
Arguments
Name
Type
Required
Description
$module
String
true
The name of the module
$action
String
true
The name of the action. See Actions section.
$context
Array
false
The associative array of context data. See Context section.
Returns
Boolean
checkField()


The associative array of context data. See Context section.
Returns
Boolean
checkField()
The checkField() method is used to check a users access for a given field against a module.Â
SugarACL::checkField('Accounts','account_type','view');
Arguments
Name
Type
Required
Description
$module
String
true
The name of the module
$field
String
true
The name of the field
$action
String
true
The name of the action.Â See Actions section.
$context
Array
false
The associative array of context data. See Context section.
Returns
Boolean
getFieldAccess()
The getFieldAccess() method is used to get the access level for a specific
$access = SugarACL::getFieldAccess('Accounts','account_type');
Arguments
Name
Type
Required
Description
$module
String
true
The name of the module
$field
String
true
The name of the field
$context
Array
false
The associative array of context data. See Context section.
Returns
Integer
The integers are represented as constants in the SugarACL class, and correspond as follows:
SugarACL::ACL_NO_ACCESS = 0 - access denied
SugarACL::ACL_READ_ONLY = 1 - read only access
SugarACL::ACL_READ_WRITE = 4 - full access
filterModuleList()
The filterModuleList() method is used to filter the list of modules available for a given action. For example, if you wanted to get all the modules the current user had edit access to, you might call the following in code:
global $app_list_strings;
$modules = $app_list_strings['module_list'];
$editableModules = SugarACL::filterModuleList($modules,'edit');
Arguments
Name
Type
Required
Description
$modules
Array
true
The list of modules you want to filter


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


Returns
Array
The filtered list of modules.
moduleSupportsACL()
The moduleSupportsACL() method is used to check if a module has ACLs defined on the vardefs.Â A good use case for this method is to not run any ACL checks if module has no ACL definitions.
SugarACL::moduleSupportsACL('Accounts');
Arguments
Name
Type
Required
Description
$module
String
true
The name of the module
Returns
Boolean
listFilter()
The listFilter() method allows for filtering an array of fields for a module to remove those which the user does not have access to. The removal can be done in a couple of different ways, provided by the $options argument. The filtering of the array is done in place, rather than returning the filtered list.
$Account = BeanFactory::getBean('Accounts','12345');
$fields = array(
  'account_type' => 'foobar',
  'status' => 'New',
  'name' => 'Customer'
);
$context = array(
   'bean' => $Account
);
$options = array(
    'blank_value' => true
);
SugarACL::listFilter('Accounts',$fields,$context,$options);
echo
json_encode($fields)
;
Should the user not have access to the 'status' field, the above might output:
{
  "account_type": "foobar",
  "status": "",
  "name": "Customer"
}
Arguments
Name
Type
Required
Description
$module
String
true
The name of the module
$list
Array
true
The array, as a reference, which will be filtered
$context
Array
false
The associative array of context data. See Context section.
$options
Array
false


false
The associative array of context data. See Context section.
$options
Array
false
An associative array containing some of the following options:
blank_value (Bool) - instead of removing inaccessible field put '' there
add_acl (Bool) - instead of removing fields add 'acl' value with access level
suffix (String) - strip suffix from field names
min_access (Int) - require this level of access for the field.Â Defaults toÂ SugarACL::ACL_READ_ONLYÂ or 0
use_value (Bool) - look for field name in value, not in the key of the list
Returns
Void
SugarBean Usage
The SugarBean class also comes with some methods for working with the ACLs in regards to the current SugarBean context. These methods automatically provide the 'bean' context parameter and provide a wrapper that utilizes the SugarACL class
getACLCategory()
The getACLCategory() method is used by all of the ACL helper methods that exist on the SugarBean and is used to determine the module name that should be checked against for the current Bean. By default, it will return the acl_category property set on the Bean, otherwise if that is not set, it will use the module_dir property that is configured on the Bean. This method can be overridden by a custom Bean implementation, but the following code shows which properties on a Bean implementation should be set for usage with ACLs.
<?php
class testBean extends SugarBean
{
    public $module_dir = 'testBean';
    //all the other SugarBean class logic...
}
class test_SubmoduleBean extends SugarBean
{
    public $module_dir = 'testBean/submodule';
    public $acl_category = 'test_SubmoduleBean';
    ///all the other SugarBean class logic...
}
$test = new testBean();
//echoes testBean


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


$Account = BeanFactory::getBean('Accounts','12345');
$accountTypeAccess = $Account->ACLFieldGet('account_type');
Arguments
Name
Type
Required
Description
$field
String
true
The name of the field
$context
Array
false
The associative array of context data. See Context section.
Returns
Boolean
ACLFilterFields()
The ACLFilterFields()Â method uses theÂ SugarACL::checkField() method to blank out those fields which a user doesn't have access to on the current SugarBean.
$Account = BeanFactory::getBean('Accounts','12345');
$Account->status = 'Old';
$Account->ACLFilterFields('edit');
echo $Account->status;
Should the user not have 'edit' access to the 'status' field, the above wouldn't output any data since the status field would be blank.
Arguments
Name
Type
Required
Description
$action
String
true
The name of the action to check.Â See Actions section.
$context
Array
false
The associative array of context data. See Context section.
Returns
Void
ACLFilterFieldList()
The ACLFilterFieldList() method is a wrapper for SugarACL::listFilter() method, which provides the Bean context as the current SugarBean.
$Account = BeanFactory::getBean('Accounts','12345');
$fields = array(
  'account_type' => 'foobar',
  'status' => 'New',
  'name' => 'Customer'
);
$options = array(
    'blank_value' => true
);
$Account->ACLFilterFieldList($fields,array(),$options);
echo "<pre>".json_encode($fields)."</pre>";
Should the user not have access to the 'status' field, the above might output:
{


{
  "account_type": "foobar",
  "status": "",
  "name": "Customer"
}
Arguments
Name
Type
Required
Description
$list
Array
true
The array, as a reference, which will be filtered
$context
Array
false
The associative array of context data. See Context section.
$options
Array
false
An associative array containing some of the following options:
blank_value (Bool) - instead of removing inaccessible field put '' there
add_acl (Bool) - instead of removing fields add 'acl' value with access level
suffix (String) - strip suffix from field names
min_access (Int) - require this level of access for the field.Â Defaults toÂ SugarACL::ACL_READ_ONLYÂ or 0
use_value (Bool) - look for field name in value, not in the key of the list
Returns
Void
Legacy Methods
When working in the Sugar codebase there might be areas that still utilize the following legacy ACLController class. The following gives a brief overview of a few methods that might still be used but should be avoided in future custom development.
ACLController::checkAcess()
The ACLController::checkAccess() method is just a wrapper for SugarACL::checkAccess() method and has been left in place for backward compatibility.
ACLController::moduleSupportsACL()
The ACLController::moduleSupportsACL() method is just a wrapper for SugarACL::moduleSupportsACL() method and has been left in place for backward compatibility.
ACLController::displayNoAccess()
This method does an echo to display a "no access" message for pages.Â
  	Last modified: 2023-02-03 21:04:03


DateTime
Overview
The SugarDateTime class, located in, ./include/SugarDateTime.php, extends PHP's built inÂ DateTime class and can be usedÂ toÂ perform common operations on date and time values.Â
Setting
With existing SugarDateTime objects, it is recommended to clone the object before modifying or performing operations on it.
$new_datetime = clone $datetime;
Another option is to instantiate a new SugarDateTimeÂ object.
// defaults to now
$due_date_time = new SugarDateTime();
$due_date_time->setDate($dueYear, $dueMonth, $dueDay);
$due_date_time->setTime($dueHour, $dueMin);
$due_date_time->setTimezone($dueTZ);
Note : When creating a new SugarDateTime object, the date and time will default to the current date and time in the timezone configured in PHP.
SugarDateTime Objects can also be modified by passing in common English textual date/time strings to manipulate the value.
$due_date = clone $date;
$end_of_the_month->modify("last day of this month");
$end_of_next_month->modify("last day of next month");
$thirty_days_later->modify("+30 days");
Formatting
When formatting SugarDateTime objects into a string for display or logging purposes, there are a few options youÂ can utilize through theÂ formatDateTime() method.
Return the date/time formatted for the database:
$db_datetime_string = formatDateTime("datetime", "db");
Return the date/time formatted using a user's preference:
global $current_user;
$user_datetime_string = formatDateTime("datetime", "user", $current_user);
Return the date/time formatted following ISO-8601Â standards:
$iso_datetime_string = formatDateTime("datetime", "iso");


$iso_datetime_string = formatDateTime("datetime", "iso");
There are times when it is needed to return only certain parts of a date or time. The format() method accepts a string parameter to define what parts or information about the date/time should be returned.Â
// 28 Dec 2016
echo $due_date_time->format("j M Y");
// 2016-12-28 19:01:09
echo $due_date_time->asDb();
// The date/time 2016-12-28T11:01:09-08:00 is set in the timezone of America/Los_Angeles
echo "The date/time " . $due_date_time->format("c") . " is set in the timezone of " . $due_date_time->format("e");
// The time when this date/time object was created is 11:01:09 AM -0800
echo "The time when this date/time object was created is " . $due_date_time->format("H:i:s A O");
// There are 31 days in the month of December
echo "There are " . $due_date_time->format("t") . " days in the month of " . $due_date_time->format("F");
// There are 366 days in the year of 2016
echo "There are " . $due_date_time->__get("days_in_year") . " days in the year of " . $due_date_time->format("Y");
// Between Wed, 28 Dec 2016 11:01:09 -0800 and the end of the year, there are 4 days.
echo "Between " . $due_date_time . " and the end of the year, there are " . ($due_date_time->__get("days_in_year") - $due_date_time->format("z")) . " days.";


Â For more information on the available options, please refer to http://php.net/manual/en/function.date.phpÂ
TimeDate Class
The TimeDateÂ class, located in ./include/TimeDate.php, utilizes the SugarDateTime class to provide an extended toolset of methods for date/time usage.
Setting
With existing TimeDate objects, it is recommended to clone the object before modifying or performing operations on it.
$new_timedate = clone $timedate;
Another option is to instantiate a new TimeDateÂ object.
// current time in UTC
$timedate = new TimeDate();
$now_utcTZ = $timedate->getNow();
// current time in user's timezone
$timedate = new TimeDate($current_user);
$now_userTZ = $timedate->getNow(true);
Note : When creating a new TimeDateÂ object, the date and time will default to the current date and time in the UTC timezone unlessÂ a user object is passed in.Â
Formatting
TimeDate objects can return the Date/Time in either a string format or in a SugarDateTime object.Â The TimeDate object has many formatting options; some of the most common ones are :
getNow
Get the current date/time as aÂ SugarDateTimeÂ objectÂ
fromUser
Get SugarDateTime objectÂ from user's date/time string
asUser
Format DateTime object as user's date/time string
fromDb
Get SugarDateTimeÂ objectÂ from a DB date/time string
fromString
Create aÂ SugarDateTime object from a string
get_db_date_time_format
GetsÂ the current database date and time format
to_display_date_time
Format aÂ stringÂ as user's display date/time
getNow
returnsÂ SugarDateTime object
Parameters
Name
Data Type
Default


getNow
returnsÂ SugarDateTime object
Parameters
Name
Data Type
Default
Description
$userTz
bool
false
Whether to useÂ the user's timezone or not. False will use UTC
Returns a SugarDateTime object set to the current date/time. If there is aÂ user object associateÂ to the TimeDate object, passing true to the $userTz parameter will return the object in user's timezone. If no user is associated or false is passed, the timezone returned will be in UTC.
Example
$timedate = new TimeDate($current_user);
// returns current date/time in UTC
$now_utcTZ = $timedate->getNow();
// returns current date/time in the user's timezone
$now_userTZ = $timedate->getNow(true);
fromUser
returnsÂ SugarDateTime object
Parameters
Name
Data Type
Default
Description
$date
string
n/a
Date/Time string to convert to an object
$user
User
null
User to utilize formatting and timezone info from
Returns a SugarDateTime objectÂ converted from the passed in string.Â If there is a user object passed in as a parameter will return the object in user's timezone. If no user is passed in, the timezone returned will be in UTC.
Example
$date = "2016-12-28 13:09";
$timedate = new TimeDate();
$datetime = $timedate->fromUser($date, $current_user);
asUser
returnsÂ string
Parameters
Name
Data Type
Default
Description
$date
string
n/a
Date/Time string to convert to an object
$user
User
null
User to utilize formatting and timezone info from


$user
User
null
User to utilize formatting and timezone info from
Returns aÂ stringÂ of the date and time formatted based on the user's profile settings.Â If there is a user object passed in as a parameter it will return the object in user's timezone. If no user is passed in, the timezone returned will be in UTC.
Example
$datetime = new datetime("2016-12-28 13:09");
$timedate = new TimeDate();
$formattedDate = $timedate->asUser($datetime, $current_user);
fromDb
returns SugarDateTime
Parameters
Name
Data Type
Default
Description
$date
string
n/a
Date/Time string in database format to convert to an object
Returns a SugarDateTime objectÂ converted from the passed in database formatted string.
Note : If the string does not match the database format exactly, this function will return boolean false.
Example
// Y-m-d H:i:s
$db_datetime = "2016-12-28 13:09:01";
$timedate = new TimeDate();
$datetime = $timedate->fromDb($db_datetime);
// returns bool(false)
$wrong_datetime = "2016-12-28 13:09";
$timedate = new TimeDate();
$datetime = $timedate->fromDb($timedate);
fromString
returns SugarDateTime
Parameters
Name
Data Type
Default
Description
$date
string
n/a
Date/Time string to convert to an object
$user
User
null
User to utilize timezone info from


$user
User
null
User to utilize timezone info from
Returns a SugarDateTime objectÂ converted from the passed in database formatted string.Â If there is a user object passed in as a parameter it will return the object in user's timezone. If no user is passed in, the timezone returned will be in UTC.
Example
$datetime_str = "December 28th 2016 13:09";
$timedate = new TimeDate();
$datetime = $timedate->fromString($datetime_str);
get_db_date_time_format
returnsÂ string
Parameters
N/a
Returns aÂ string of the current database date and time format settings.Â
Note : For just the date format useÂ get_db_date_format() and for just the time format useÂ get_db_time_format().
Example
$timedate = new TimeDate();
// Y-m-d H:i:s
$db_format = $timedate->get_db_date_time_format();
to_display_date_time
returnsÂ string
Parameters
Name
Data Type
Default
Description
$date
string
n/a
Date/Time string in database format
$meridiem
boolean
true
Deprecated -- Remains for backwards compatibility only
$convert_tz
boolean
true
Convert to user's timezone
$user
User
null
User to utilize formatting and timezone info from
Returns a string of the date and time formatted based on the user's profile settings. If no user is passed in, the current user's default will be used. If $convert_tz is true the string returned will beÂ in the passed in user's timezone. If no user is passed in, the timezone returned will be in UTC.
Note : If the string does not match the database format exactly, this function will return boolean false.
Example


Example
$datetime_str = "2016-12-28 13:09:01";
// 12-28-2016 07:09
$timedate = new TimeDate();
$datetime = $timedate->to_display_date_time($datetime_str);
// 2016-12-28 13:09
$timedate = new TimeDate();
$datetime = $timedate->to_display_date_time($datetime_str, true, false, $diff_user);
Parsing
In addition to formatting, TimeDate objects can also return Date/Time values based on special parameters.Â The TimeDate object has many date parsing options; some of the most common ones are :
parseDateRange
GetsÂ the start and end date/time from a range keywordÂ
parseDateRange
returns array
Parameters
Name
Data Type
Default
Description
$range
string
n/a
StringÂ from a predetermined list of available ranges
$user
User
null
User to utilize formatting and timezone info from
$adjustForTimezone
boolean
true
Convert to user's timezone
Returns an array ofÂ SugarDateTime objects containing the start and Date/Time values calculated based on the entered parameter. If no user is passed in, the current user's default will be used. If $adjustForTimezoneÂ is true the string returned will beÂ in the passed in user's timezone. If NULL is passed in as theÂ user, the timezone returned will be in UTC. The available values for $range are :
Range String
Description
yesterday
Yesterday's start and end date/time
today
Today's start and end date/time
tomorrow
Tomorrows's start and end date/time
last_7_days
Start and end date/time of the last seven days
next_7_days