Visibility Framework
Overview
The visibility framework provides the capability to alter the queries Sugar uses to retrieve records from the database. This framework can allow for additional restrictions or specific logic to meet business requirements of hiding or showing only specific records. Visibility classes only apply to certain aspects of Sugar record retrieval, e.g. List Views, Dashlets, and Filter Lookups.
Custom Row Visibility
Custom visibility class files are stored under ./custom/data/visibility/. The files in this directory can be enabled or disabled by modifying a module's visibility property located inÂ ./custom/Extension/modules/<module>/Ext/Vardefs/. Every enabled visibility class is merged into the module's definition, allowing multiple layers of logic to be added to a module.
Visibility Class
To add custom row visibility, you must create a visibility class that will extend the coreÂ SugarVisibility class ./data/SugarVisibility.php .Â The visibility class has the ability to override the following methods:
Name
Description
addVisibilityQuery
Add visibility clauses to a SugarQuery object.
addVisibilityFrom
[Deprecated] Add visibility clauses to the FROM part of the query. This method shouldÂ still be implemented, as not all objects have been switched over to use addVisibilityQuery() method.
addVisibilityFromQuery
[Deprecated] Add visibility clauses to the FROM part of SugarQuery.Â This method shouldÂ still be implemented, as not all objects have been switched over to use addVisibilityQuery() method.
addVisibilityWhere
[Deprecated] Add visibility clauses to the WHERE part of the query.Â This method shouldÂ still be implemented, as not all objects have been switched over to useÂ addVisibilityQuery() method.
addVisibilityWhereQuery
[Deprecated] Add visibility clauses to the WHERE part of SugarQuery.Â This method shouldÂ still be implemented, as not all objects have been switched over to use addVisibilityQuery() method.


The visibility class should also implementÂ Sugarcrm\Sugarcrm\Elasticsearch\Provider\Visibility\StrategyInterfaceÂ so that the visibility rules are also applied to the global search.Â Â StrategyInterfaceÂ has the following functions that should be implemented:
Name
Description
elasticBuildAnalysis
Build Elasticsearch analysis settings.Â  This function can be empty if you do not need any special analyzers.
elasticBuildMapping
Build Elasticsearch mapping.Â  This function should contain a mapping of fields that should be analyzed.
elasticProcessDocumentPreIndex
Process document before it's being indexed. This function should perform any actions to the document that needs to be completed beforeÂ it is indexed.Â
elasticGetBeanIndexFields
Bean index fields to be indexed.Â  This function should return an array of the fields that need to be indexed as part of yourÂ custom visibility.
elasticAddFilters
Add visibility filters.Â  This function should apply the Elastic filters.
Example
The following example creates a custom visibility filter that determines whether Opportunity records should be displayed based on their Sales Stage.Â  Opportunity records with Sales Stage set to Closed Won or Closed Lost will not be displayed in the Opportunities module or global search for users with the Demo Visibility role.Â Â
/custom/data/visibility/FilterOpportunities.php:
<?php
use Sugarcrm\Sugarcrm\Elasticsearch\Provider\Visibility\StrategyInterface as ElasticStrategyInterface;
use Sugarcrm\Sugarcrm\Elasticsearch\Provider\Visibility\Visibility;
use Sugarcrm\Sugarcrm\Elasticsearch\Analysis\AnalysisBuilder;
use Sugarcrm\Sugarcrm\Elasticsearch\Mapping\Mapping;
use Sugarcrm\Sugarcrm\Elasticsearch\Adapter\Document;
/**
 *
 * Custom visibility class for Opportunities module:
 *
 * This demo allows to restrict access to opportunity records based on the


*
 * This demo allows to restrict access to opportunity records based on the
 * user's role and configured filtered sales stages.
 *
 * The following $sugar_config parameters are available:
 *
 * $sugar_config['custom']['visibility']['opportunities']['target_role']
 * This parameter takes a string containing the role name for which
 * the filtering should apply.
 *
 * $sugar_config['custom']['visibility']['opportunities']['filter_sales_stages']
 * This parameters takes an array of filtered sales stages. If current user is
 * member of the above configured role, then the opportunities with the sale
 * stages as configured in this array will be inaccessible.
 *
 *
 * Example configuration given that 'Demo Visibility' role exists (config_override.php):
 *
 * $sugar_config['custom']['visibility']['opportunities']['target_role'] = 'Demo Visibility';
 * $sugar_config['custom']['visibility']['opportunities']['filter_sales_stages'] = array('Closed Won', 'Closed Lost');
 *
 */
class FilterOpportunities extends SugarVisibility implements ElasticStrategyInterface
{
    /**
     * The target role name
     * @var string
     */
    protected $targetRole = '';
    /**
     * Filtered sales stages
     * @var array
     */
    protected $filterSalesStages = array();
    /**
     * {@inheritdoc}
     */
    public function __construct(SugarBean $bean, $params = null)
    {
        parent::__construct($bean, $params);
        $config = SugarConfig::getInstance();
        $this->targetRole = $config->get(
            'custom.visibility.opportunities.target_role',
            $this->targetRole
        );
        $this->filterSalesStages = $config->get(


);
        $this->filterSalesStages = $config->get(
            'custom.visibility.opportunities.filter_sales_stages',
            $this->filterSalesStages
        );
    }
    /**
     * {@inheritdoc}
     */
    public function addVisibilityWhere(&$query)
    {
        if (!$this->isSecurityApplicable()) {
            return $query;
        }
        $whereClause = sprintf(
            "%s.sales_stage NOT IN (%s)",
            $this->getTableAlias(),
            implode(',', array_map(array($this->bean->db, 'quoted'), $this->filterSalesStages))
        );
        if (!empty($query)) {
            $query .= " AND $whereClause ";
        } else {
            $query = $whereClause;
        }
        return $query;
    }
    /**
     * {@inheritdoc}
     */
    public function addVisibilityWhereQuery(SugarQuery $sugarQuery, $options = array())
    {
        $where = null;
        $this->addVisibilityWhere($where, $options);
        if (!empty($where)) {
            $sugarQuery->where()->addRaw($where);
        }
        return $sugarQuery;
    }
    /**
     * Check if we can apply our security model
     * @param User $user
     * @return false|User
     */
    protected function isSecurityApplicable(User $user = null)
    {
        $user = $user ?: $this->getCurrentUser();
        if (!$user) {
            return false;
        }
        if (empty($this->targetRole) || empty($this->filterSalesStages)) {
            return false;
        }


return false;
        }
        if (!is_string($this->targetRole) || !is_array($this->filterSalesStages)) {
            return false;
        }
        if (!$this->isUserMemberOfRole($this->targetRole, $user)) {
            return false;
        }
        if ($user->isAdminForModule("Opportunities")) {
            return false;
        }
        return $user;
    }
    /**
     * Get current user
     * @return false|User
     */
    protected function getCurrentUser()
    {
        return empty($GLOBALS['current_user']) ? false : $GLOBALS['current_user'];
    }
    /**
     * Check if given user has a given role assigned
     * @param string $targetRole Name of the role
     * @param User $user
     * @return boolean
     */
    protected function isUserMemberOfRole($targetRole, User $user)
    {
        $roles = ACLRole::getUserRoleNames($user->id);
        return in_array($targetRole, $roles) ? true : false;
    }
    /**
     * Get table alias
     * @return string
     */
    protected function getTableAlias()
    {
        $tableAlias = $this->getOption('table_alias');
        if (empty($tableAlias)) {
            $tableAlias = $this->bean->table_name;
        }
        return $tableAlias;
    }
    /**
     * {@inheritdoc}
     */
    public function elasticBuildAnalysis(AnalysisBuilder $analysisBuilder, Visibility $provider)
    {
        // no special analyzers needed
    }
    /**
     * {@inheritdoc}
     */


}
    /**
     * {@inheritdoc}
     */
    public function elasticBuildMapping(Mapping $mapping, Visibility $provider)
    {
        $mapping->addNotAnalyzedField('visibility_sales_stage');
    }
    /**
     * {@inheritdoc}
     */
    public function elasticProcessDocumentPreIndex(Document $document, \SugarBean $bean, Visibility $provider)
    {
        // populate the sales_stage into our explicit filter field
        $sales_stage = isset($bean->sales_stage) ? $bean->sales_stage : '';
        $document->setDataField('visibility_sales_stage', $sales_stage);
    }
    /**
     * {@inheritdoc}
     */
    public function elasticGetBeanIndexFields($module, Visibility $provider)
    {
        // make sure to pull sales_stage regardless of search
        return array('sales_stage');
    }
    /**
     * {@inheritdoc}
     */
    public function elasticAddFilters(User $user, Elastica\Query\BoolQuery $filter,
                                      Sugarcrm\Sugarcrm\Elasticsearch\Provider\Visibility\Visibility $provider)
    {
        if (!$this->isSecurityApplicable($user)) {
            return;
        }
        // apply elastic filter to exclude the given sales stages
        $filter->addMustNot($provider->createFilter(
            'OpportunitySalesStages',
            array(
                'filter_sales_stages' => $this->filterSalesStages,
            )
        ));
    }
}
./custom/Extension/modules/Opportunities/Ext/Vardefs/opp_visibility.php
<?php
if (!defined('sugarEntry') || !sugarEntry) {
    die('Not A Valid Entry Point');
}


die('Not A Valid Entry Point');
}
$dictionary['Opportunity']['visibility']['FilterOpportunities'] = true;
./custom/src/Elasticsearch/Provider/Visibility/Filter/OpportunitySalesStagesFilter.php
<?php
namespace Sugarcrm\Sugarcrm\custom\Elasticsearch\Provider\Visibility\Filter;
use Sugarcrm\Sugarcrm\Elasticsearch\Provider\Visibility\Filter\FilterInterface;
use Sugarcrm\Sugarcrm\Elasticsearch\Provider\Visibility\Visibility;
/**
 *
 * Custom opportunity filter by sales_stage
 *
 * This logic can exist directly in the FilterOpportunities visibility class.
 * However by abstracting the (custom) filters makes it possible to re-use
 * them in other places as well.
 */
class OpportunitySalesStagesFilter implements FilterInterface
{
    /**
     * @var Visibility
     */
    protected $provider;
    /**
     * {@inheritdoc}
     */
    public function setProvider(Visibility $provider)
    {
        $this->provider = $provider;
    }
    /**
     * {@inheritdoc}
     */
    public function buildFilter(array $options = array())
    {
        return new \Elastica\Query\Terms(
            'visibility_sales_stage',
            $options['filter_sales_stages']
        );
    }
}
After creating the above files, log in to your Sugar instance as an administrator and navigate toÂ AdministrationÂ > Repair > Quick Repair and Rebuild.Â Â
Next, perform a full reindex by navigating toÂ AdministrationÂ > SearchÂ and selecting the "delete existing data" option.Â
Execute a cron to process all of the queued records into ElasticsearchÂ by doing the following:Â


Open a command line client and navigate to your Sugar directory.
ExecuteÂ chmod +x bin/sugarcrmÂ to ensureÂ bin/sugarcrm is executable.
ExecuteÂ php cron.phpÂ to consume the queue.
ExecuteÂ bin/sugarcrm elastic:queueÂ to see if the queue has finished.
Repeat steps 3 and 4 until the queue has 0 records.
This example requires the Sales Stage field to be part of the Opportunities module.Â Â Navigate toÂ Administration > OpportunitiesÂ and ensure theÂ OpportunitiesÂ radio button is selected.
Create a new role named "Demo Visibility"Â and assign a user to this role.Â  Note: if you are using the sample data, do NOT use Jim as he has admin permission on the Opportunities module and will be able to view all records. We recommend using Max.
Configure your instance to filter opportunities for a given sales stages for this role by adding the following to ./config_override.php:
<?php
$sugar_config['custom']['visibility']['opportunities']['target_role'] = 'Demo Visibility';
$sugar_config['custom']['visibility']['opportunities']['filter_sales_stages'] = array('Closed Won', 'Closed Lost');
Log in as the user to whom you assigned the Demo Visibility role. Observe that opportunity records in the sales stages "Closed Won" and "Closed Lost"Â areÂ no longer accessible.
You can download a module loadable package of this example here.
  	Last modified: 2023-02-03 21:04:03


Manipulating Teams Programmatically
Overview
How to manipulate team relationships.
Fetching Teams
To fetch teams related to a bean, you will need to retrieve an instance of a TeamSet object and use the getTeams() method to retrieve the teams using the team_set_id. An example is shown below:
//Create a TeamSet bean - no BeanFactory
require_once 'modules/Teams/TeamSet.php';
$teamSetBean = new TeamSet();
//Retrieve the bean
$bean = BeanFactory::getBean($module, $record_id);
//Retrieve the teams from the team_set_id
$teams = $teamSetBean->getTeams($bean->team_set_id);
Adding Teams
To add a team to a bean, you will need to load the team's relationship and use the add() method. This method accepts an array of team ids to add. An example is shown below:
//Retrieve the bean
$bean = BeanFactory::getBean($module, $record_id);
//Load the team relationship
$bean->load_relationship('teams');
//Add the teams
$bean->teams->add(
    array(
        $team_id_1,
        $team_id_2
    )
);
Considerations
If adding teams in a logic hook, the recommended approach is to use an after_save hook rather than a before_save hook as the $_REQUEST may reset any changes you make.
Removing Teams
To remove a team from a bean, you will need to load the team's relationship and use the remove() method. This method accepts an array of team ids to remove. An example is shown below:
//Retrieve the bean
$bean = BeanFactory::getBean($module, $record_id);
//Load the team relationship
$bean->load_relationship('teams');
//Remove the teams
$bean->teams->remove(


//Remove the teams
$bean->teams->remove(
    array(
        $team_id_1,
        $team_id_2
    )
);
Considerations
If removing teams in a logic hook, the recommended approach is to use an after_save hook rather than a before_save hook as the $_REQUEST may reset any changes you make.
Replacing Team Sets
To replace all of the teams related to a bean, you will need to load the team's relationship and use the replace() method. This method accepts an array of team ids. An example is shown below:
//Retrieve the bean
$bean = BeanFactory::getBean($module, $record_id);
//Load the team relationship
$bean->load_relationship('teams');
//Set the primary team
$bean->team_id = $team_id_1
//Replace the teams
$bean->teams->replace(
    array(
        $team_id_1,
        $team_id_2
    )
);
//Save to update primary team
$bean->save()
Considerations
If replacing teams in a logic hook, the recommended approach is to use an after_save hook rather than a before_save hook as the $_REQUEST or workflow may reset any changes you make.
This method does not replace (or set) the primary team for the record. When replacing teams, you need to also make sure that the primary team, determined by the team_id field, is set appropriately and included in the replacement ids. If this is being done in a logic hook you should set the primary team in a before_save hook and replace the team set in the after_save hook.
When using an after_saveÂ hook, be sure to call $bean->teams->setSaved(false) to explicitly reset the save state. This ensures that the updates to the team sets are applied.
Example:


Example:
//before save function
public function before_save_hook($bean, $event, $arguments)
{
    $bean->team_id = $team_id_1;
}
//after save function
public function after_save_hook($bean, $event, $arguments)
{
	$bean->teams->setSaved(false); // Manually reset TeamSet state for save
    $bean->teams->replace(
        array(
            $team_id_1,
            $team_id_2
        )
    );
}
Creating and Retrieving Team Set IDs
To create or retrieve the team_set_id for a group of teams, you will need to retrieve an instance of a TeamSet object and use the addTeams() method. If a team set does not exist, this method will create it and return an id. An example is below:
//Create a TeamSet bean - no BeanFactory
require_once 'modules/Teams/TeamSet.php';
$teamSetBean = new TeamSet();
//Retrieve/create the team_set_id
$team_set_id = $teamSetBean->addTeams(
    array(
        $team_id_1,
        $team_id_2
    )
);
Adding Additional Access
To enable additional access for a record, you can either set the team id or team setÂ to the beansÂ acl_team_set_id field. An example of this is shown below:
require_once 'modules/Teams/TeamSet.php';
// Create a new teamset or fetch the id of an existing teamset
$teamSetBean = new TeamSet();
$teamSetId = $teamSetBean->addTeams([
    'East', // Demo Team ID
    'West', // Demo Team ID
]);


'East', // Demo Team ID
    'West', // Demo Team ID
]);
$bean = BeanFactory::getBean('Accounts', '15bcf01c-1e1e-11e8-9e13-f45c89a8598f');
// Set additional access
$bean->acl_team_set_id = $teamSetId; // if set to NULL, this will remove any existing value
$bean->save();
  	Last modified: 2023-02-03 21:04:03


Data Framework
The Sugar application comprises core elements such as modules, fields, vardefs, and other foundational components. The Data Framework pages document how these core elements are modeled and implemented in Sugar.
TopicsModulesHow modules are defined and used within the systemModelsAn overview of the SugarBean model.VardefsVardefs (Variable Definitions) provide the Sugar application with information about SugarBeans. Vardefs specify information on the individual fields, relationships between beans, and the indexes for a given bean.FieldsHow fields interact with the various aspects of Sugar.RelationshipsRelationships are the basis for linking information within the system. This page explains the various aspects of relationships. For information on custom relationships, please refer to the Custom Relationships documentation.SubpanelsFor Sidecar, Sugar's subpanel layouts have been modified to work as simplified metadata. This page is an overview of the metadata framework for subpanels.DatabaseAll Sugar products support the MySQL and Microsoft SQL Server databases. Sugar Enterprise and Sugar Ultimate also support the DB2 and Oracle databases. In general, Sugar uses only common database functionality, and the application logic is embedded in the PHP code. Sugar does not use or recommend database triggers or stored procedures. This design simplifies coding and testing across different database vendors. The only implementation difference across the various supported databases is column types.
  	Last modified: 2023-02-03 21:04:03


Modules
Overview
How modules are defined and used within the system
Module Definitions
The module definitions, defined in ./include/modules.php, determineÂ how modules are displayed and used throughout the application. Any custom metadata, whether from a plugin or a custom module, should be loaded through the IncludeÂ extension.Â Prior to 6.3.x, module definitions could be added by creating the file ./include/modules_override.php. This method of creating module definitions is still compatible but is not recommended from a best practices standpoint.Â
Hierarchy Diagram
The modules metadata are loaded in the following manner:
$moduleList
The $moduleList is an array containing a list of modules in the system. The format of the array is to have a numeric index and a value of the modules unique key.
$moduleList[] = 'Accounts';
$beanList
The $beanList variable is an array that stores a list of all active beans (modules) in the application. The format of the array is array('<bean plural name>' => '<bean singular name>');. The $beanList key is used to lookup values in the $beanFiles variable.
$beanList['Accounts'] = 'Account';
$beanFiles
The $beanFiles variable is an array used to reference the class files for a bean. The format of the array is array('<bean singular name>' => '<relative class file>');. The bean name, stored in singular form, is a reference to the class name of the object, which is looked up from the $beanList 'key'.
$beanFiles['Account'] = 'modules/Accounts/Account.php';
$modInvisList


$modInvisList
The $modInvisList variableÂ removes a module from the navigation tab in the MegaMenu, reporting, and it's subpanels under related modules.To enable a hidden module for reporting, you can useÂ $report_include_modules.Â To enable a hidden modulesÂ subpanels on related modules, you can useÂ $modules_exempt_from_availability_check.Â TheÂ
$modInvisList[] = 'Prospects';
$modules_exempt_from_availability_check
TheÂ $modules_exempt_from_availability_check variable is used in conjunction withÂ $modInvisList. When a module has been removed from the MegaMenu view with $modInvisList,Â this will allow for the display of the modules subpanels under related modules.
$modules_exempt_from_availability_check['OAuthKeys'] = 'OAuthKeys';
$report_include_modules
Â TheÂ $report_include_modulesÂ variable is used in conjunction withÂ $modInvisList. When a module has been hidden with $modInvisList,Â this will allow for the module to be enabled for reporting.
$report_include_modules['Prospects'] = 'Prospect';
$adminOnlyList
The $adminOnlyList variable is an extra level of security for modules that are can be accessed only by administrators through the Admin page. Specifying all will restrict all actions to be admin only.
$adminOnlyList['PdfManager'] = array(
    'all' => 1
);
$bwcModules
The $bwcModules variable determines which modules are in backward compatibility mode. More information on backward compatibility can be found in the Backward Compatibility section.
  	Last modified: 2023-02-03 21:04:03


Relationships
Overview
Relationships are the basis for linking information within the system. This pageÂ explains the various aspects of relationships. For information on custom relationships, please refer to the Custom Relationships documentation.
Definitions
Relationships are initially defined in the module's vardefs file under the relationships array. For reference, you can find them using the vardef pathÂ ./modules/<module>/vardefs.php.
Database Structure
In Sugar, most relationships are stored using a joining table. This applies to both one-to-many (1:M) relationships as well as many-to-many (M:M) relationships. An example of this is the relationship between Accounts and Opportunities where there are three tables:Â accounts,Â accounts_opportunities,Â andÂ opportunities.Â You will find that the joining table,Â accounts_opportunities,Â will contain the fields needed in order to establish the relationship link.
The fields on the accounts_opportunitiesÂ table are listed below:
Fields
Description
id
A unique identifier for the relationship row (not typically used)
opportunity_id
The ID for the related opportunityÂ record. This isÂ named uniquely based on the relationship
account_id
The ID for the related accountÂ record. This is named uniquely based on the relationship
date_modified
The date the row was last modified
deleted
Whether or not the relationship still exists
Relationship Cache
All relationships in Sugar are compiled into the cache directory ./cache/Relationships/relationships.cache.php.Â If needed, the relationships cache can be rebuilt by navigating toÂ Admin > Repair > Rebuild Relationships.
TopicsCustom RelationshipsThis page needs an overview
  	Last modified: 2023-02-03 21:04:03


Custom Relationships
Overview
This page needs an overview
Creating Custom Relationships
Relationships are initially defined in the module's vardefs file under the relationships array. For reference, you can find them using the vardef path as follows:
./modules/<module>/vardefs.php
Custom relationships are created in a different way using the Extension Framework. The process requires two steps which are explained in the following sections:
Defining the Relationship MetaData
Defining the Relationship in the TableDictionary
Defining the Relationship MetaData
The definitions for custom relationships will be found in a path similar to:
./custom/metadata/<relatonship name>MetaData.php
This file will contain the $dictionary information needed for the system to generate the relationship. The $dictionary array will contain the following:
Index
Type
Description
true_relationship_type
String
The relationship's structure (possible values: 'one-to-many' or 'many-to-many')
from_studio
Boolean
Whether the relationship was created in Studio
table
String
The name of the table that is created in the database to contain the link ids
fields
Array
An array of fields to be created on the relationship join table
indices
Array
The list of indexes to be created
relationships
Array
An array defining relationships
relationships.<rel>
Array
The array defining the relationship
relationships.<rel>.lhs_module
String
Left-hand module (should match $beanList index)
relationships.<rel>.lhs_table
String
Left-hand table name
relationships.<rel>.lhs_key
String
The key to use from the table on the left
relationships.<rel>.rhs_module
String
Right-hand module (should match $beanList index)
relationships.<rel>.rhs_table
String
Right-hand table name
relationships.<rel>.rhs_key
String
The key to use from the table on the right
relationships.<rel>.relationship_type
String


The key to use from the table on the right
relationships.<rel>.relationship_type
String
The relationship type, typically stored as 'many-to-many'
relationships.<rel>.join_table
String
The join table
relationships.<rel>.join_key_lhs
String
Left table key, should exist in table field definitions above
relationships.<rel>.join_key_rhs
String
RightÂ table key, should exist in table field definitions above
MetaData Example
Creating a custom 1:M relationship between Accounts and Contacts will yield the following metadata file:
./custom/metadata/accounts_contacts_1MetaData.php
<?php
// created: 2013-09-20 15:15:47
$dictionary["accounts_contacts_1"] = array (
  'true_relationship_type' => 'one-to-many',
  'from_studio' => true,
  'relationships' =>
  array (
    'accounts_contacts_1' =>
    array (
      'lhs_module' => 'Accounts',
      'lhs_table' => 'accounts',
      'lhs_key' => 'id',
      'rhs_module' => 'Contacts',
      'rhs_table' => 'contacts',
      'rhs_key' => 'id',
      'relationship_type' => 'many-to-many',
      'join_table' => 'accounts_contacts_1_c',
      'join_key_lhs' => 'accounts_contacts_1accounts_ida',
      'join_key_rhs' => 'accounts_contacts_1contacts_idb',
    ),
  ),
  'table' => 'accounts_contacts_1_c',
  'fields' =>
  array (
    0 =>
    array (
      'name' => 'id',
      'type' => 'varchar',
      'len' => 36,
    ),
    1 =>
    array (


),
    1 =>
    array (
      'name' => 'date_modified',
      'type' => 'datetime',
    ),
    2 =>
    array (
      'name' => 'deleted',
      'type' => 'bool',
      'len' => '1',
      'default' => '0',
      'required' => true,
    ),
    3 =>
    array (
      'name' => 'accounts_contacts_1accounts_ida',
      'type' => 'varchar',
      'len' => 36,
    ),
    4 =>
    array (
      'name' => 'accounts_contacts_1contacts_idb',
      'type' => 'varchar',
      'len' => 36,
    ),
  ),
  'indices' =>
  array (
    0 =>
    array (
      'name' => 'accounts_contacts_1spk',
      'type' => 'primary',
      'fields' =>
      array (
        0 => 'id',
      ),
    ),
    1 =>
    array (
      'name' => 'accounts_contacts_1_ida1',
      'type' => 'index',
      'fields' =>
      array (
        0 => 'accounts_contacts_1accounts_ida',
      ),
    ),
    2 =>
    array (
      'name' => 'accounts_contacts_1_alt',
      'type' => 'alternate_key',
      'fields' =>
      array (
        0 => 'accounts_contacts_1contacts_idb',
      ),
    ),
  ),
);
Defining the Relationship in the TableDictionary


),
    ),
  ),
);
Defining the Relationship in the TableDictionary
Once a relationship's metadata has been created, the metadata file will have a reference placed in the TableDictionary:
./custom/Extension/application/Ext/TableDictionary/<relationship name>.php
This file will contain an 'include' reference to the metadata file:
<?php
    include('custom/metadata/<relationship name>MetaData.php');
?>
TableDictionary Example
The custom 1:M relationship between Accounts and Contacts will yield the following TableDictionary file:
./custom/Extension/application/Ext/TableDictionary/accounts_contacts_1.php
<?php
    //WARNING: The contents of this file are auto-generated
    include('custom/metadata/accounts_contacts_1MetaData.php');
?>
If you have created theÂ relationship through Studio, the files above will be automatically created. If you are manually creating the files, run a Quick Repair and Rebuild and run any SQL scripts generated. The Quick Repair and Rebuild will rebuild the file map and relationship cache as well as populate the relationship in the relationships table.
  	Last modified: 2023-02-03 21:04:03


Database
Overview
All Sugar products support the MySQL and Microsoft SQL Server databases. Sugar Enterprise and Sugar Ultimate also support the DB2 and Oracle databases. In general, Sugar uses only common database functionality, and the application logic is embedded in the PHP code. Sugar does not use or recommend database triggers or stored procedures. This design simplifies coding and testing across different database vendors. The only implementation difference across the various supported databases is column types.Â
Primary Keys, Foreign Keys, and GUIDs
By default, Sugar uses globally unique identification values (GUIDs) for primary keys for all database records. Sugar provides a Sugarcrm\Sugarcrm\Util\Uuid::uuid1() utility function for creating these GUIDs in the following format: aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee. The primary key's column length is 36 characters.Â
The GUID format and value has no special meaning (relevance) in Sugar other than the ability to match records in the database. Sugar links two records (such as an Accounts record with a Contacts record) with a specified ID in the record type relationship table (e.g. accounts_contacts).
Primary keys in Sugar mayÂ contain any unique string such as a GUID algorithm, a key that has some meaning (e.g. bean type first, followed by info), an external key, or auto-incrementing numbers converted to strings. Sugar chose GUIDs over auto-incrementing keys to enable easier data synchronization across databases and avoid primary-key collisions.
You can also import data from a previous system with one primary key format and make all new records in Sugar use the GUID primary key format. All keys mustÂ be stored as globally unique strings with no more than 36 characters.
NoticeÂ If multiple records between modules contain matching ids, you may experience undesired behaviors within the system.


ToÂ implement a new primary key method or to import data with a different primary key format (based on the existing GUID mechanism for new records), keep in mindÂ the following rules of primary key behavior:
Quote characters : Sugar expects primary keys to be string types and will format the SQL with quotes. If you change the primary key types to an integer type, SQL errors may occur since Sugar stores all ID values in quotes in the generated SQL. The database may be able to ignore this issue. MySQL running in Safe mode experiences issues, for instance.
Case sensitivity : The ID values abc and ABC are treated the same in MySQL but represent different values in Oracle. When migrating data to Sugar, some CRM systems may use case-sensitive strings as their IDs on export. If this is the case, and you are running MySQL, you mustÂ run an algorithm on the data to make sure all of the IDs are unique. One simple algorithm is to MD5 the ID values that they provide. A quick check will let you know if there is a problem. If you imported 80,000 leads and there are only 60,000 in the system, some may have been lost due to non-unique primary keys caused by case insensitivity.
Key sizeÂ : Sugar only tracks the first 36 characters in the primary key. Any replacement primary key will either require changing all of the ID columns with one of an appropriate size or to make sure you do not run into any truncation or padding issues. MySQL in some versions has had issues with Sugar where the IDs were not matching because it was adding spaces to pad the row out to the full size. MySQL's handling of char and varchar padding has changed in laterÂ versions. To protect against this, make sure the GUIDs are not padded with blanks in the database by removing any leading or trailing space characters.
Indexes


Indexes
Indexes can be defined in the main or custom vardefs.php for a module in an array under the key indices. See below for an example of defining several indices:
'indices' => array(
     array(
         'name' => 'idx_modulename_name',
         'type' => 'index',
         'fields' => array('name'),
     ),
     array(
         'name' => 'idx_modulename_assigned_deleted',
         'type' => 'index',
         'fields' => array('assigned_user_id', 'deleted'),
     ),
 ),
The name of the index must start with idx_ and must be unique across the database. Possible values for type include primary for a primary key or index for a normal index. The fields list matches the column names used in the database.
Doctrine
In order to provide robust support for Prepared Statements, which provide more security and better database access performance, Sugar 7.9 has adopted parts of Doctrine's Database Abstraction Layer, especially the QueryBuilder class, for working with prepared statements. The picture below shows how Sugar objects like DBManager and SugarQuery utilize Doctrine to provide this functionality, while still using the same toolset that has existed in Sugar.
Â
DBManager
The DBManager class will use Doctrine QueryBuilder for building INSERT and UPDATE queries.
SugarQuery
The SugarQuery class will use Doctrine QueryBuilder for building SELECT queries.
SugarBean
The SugarBean class will continue to use DBManager class for saving all fields.
TopicsDBManagerThe DBManager Object provides an interface for working with the database.SugarQuerySugarQuery, located in ./include/SugarQuery/SugarQuery.php, provides an object-oriented approach to working with the database. This allows developers to generate the applicable SQL for a Sugar system without having to know which database backend the instance is using. SugarQuery supports all databases supported by Sugar.


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


DBManager
Overview
The DBManager Object provides an interface for working with the database.
Instantiating the DBManager Object
The DBManagerFactory class, located in ./include/database/DBManagerFactory.php,Â can help instantiate a DBManager object using the getInstance() method.
$db = \DBManagerFactory::getInstance();
For best practices, we recommend using the global DBManager Object:
$GLOBALS['db']
Querying The Database
Sugar supports prepared statements. The following sections outline the legacy usage and the newly prepared statement usage.
SELECT queries
For select queries that do not have a dynamic portion of the where clause, you can use the query()Â method on the DBManager object. For queries that are accepting data passed into the system in the where clause, the following examples demonstrate how best utilize the new Prepared Statement functionality.
Legacy:
$id = '1234-abcde-fgh45-6789';
$query = 'SELECT * FROM accounts WHERE id = ' . $GLOBALS['db']->quoted($id);
$results = $GLOBALS['db']->query($query);
Best Practice:
Use the getConnection() method to retrieve a Doctrine Connection Object which handles prepared statements.
$id = '1234-abcde-fgh45-6789';
$query = 'SELECT * FROM accounts WHERE id = ?';
$conn = $GLOBALS['db']->getConnection();
$result = $conn->executeQuery($query, [$id]);
In the case that query logic is variable or conditionally built then it makes sense to use Doctrine QueryBuilder directly.
Legacy:
$query = 'SELECT * FROM accounts';
if ($status !== null) {
	$query .= ' WHERE status = ' . $GLOBALS['db']->quoted($status);
}
$results = $GLOBALS['db']->query($query);
Best Practice:


}
$results = $GLOBALS['db']->query($query);
Best Practice:
Use the getConnection() method to retrieve the Doctrine Connection Object, and then use the createQueryBuilder() method on the Connection Object to retrieve the QueryBuilder Object.
$builder = $GLOBALS['db']->getConnection()->createQueryBuilder();
$builder->select('*')->from('accounts');
if ($status !== null) {
	$builder->where('status = ' . $builder->createPositionalParameter($status));
}
$result = $builder->executeQuery();
Retrieving Results
Legacy:
After using theÂ query()Â method, such as in the Legacy code examples above, you can use theÂ fetchByAssoc() method to retrieve results. The query() method will submit the query and retrieve the results while the fetchByAssoc() method will iterate through the results:
$sql = "SELECT id FROM accounts WHERE deleted = 0";
$result = $GLOBALS['db']->query($sql);
while($row = $GLOBALS['db']->fetchByAssoc($result) )
{
    //Use $row['id'] to grab the id fields value
    $id = $row['id'];
}
Best Practice:
When using Prepared Statements, both the Doctrine Query Builder and the Doctrine Connection Object will return a Doctrine\DBAL\Result Object to allow iterating through the results of the query. You can use the fetchNumeric()Â ,Â fetchAssociative() or fetchAllNumeric()Â or fetchAllAssociative() methods to retrieve results.
fetchAllAssociative() Example
The fetchAllAssociative() method will return the entire result set as an associative array, with each index containing a row of data.
$conn = $GLOBALS['db']->getConnection();
$result = $conn->executeQuery("SELECT * FROM accounts");
foreach ($result->iterateAssociative() as $row) {


foreach ($result->iterateAssociative() as $row) {
    $name = $row['name'];
    // do something
}
iterateAssociativeIndexed() Example
The iterateAssociativeIndexed() method executes the query and iterate over the result with the key representing the first column and the value is an associative array of the rest of the columns and their values:
$query = 'SELECT id, name FROM accounts';
$conn = $GLOBALS['db']->getConnection();
foreach ($conn->iterateAssociativeIndexed($query) as $id => $data) {
    // ...
}
Retrieving a Single Result
To retrieve a single result from the database, such as a specific record field, you can use the getOne() method for Legacy query usage.
$sql = "SELECT name FROM accounts WHERE id = :id";
$conn = $GLOBALS['db']->getConnection();
$row = $conn->fetchAssociative($sql, ['id' => $id]);
// alternative
$sql = "SELECT name FROM accounts WHERE id = '{$id}'";
$name = $GLOBALS['db']->getOne($sql);
Limiting Results
To limit the results of a query, you can add a limit to the SQL string or for legacy query usage you can use the limitQuery() method on the DBManager Object:
Legacy:
$sql = "SELECT id FROM accounts WHERE deleted = 0";
$offset = 0;
$limit = 1;
$result = $GLOBALS['db']->limitQuery($sql, $offset, $limit);
while($row = $GLOBALS['db']->fetchByAssoc($result) )
{
    //Use $row['id'] to grab the id fields value
    $id = $row['id'];
}
Prepared Statements:


$id = $row['id'];
}
Prepared Statements:
When using the Doctrine Query Builder, you can limit the results of the query by using theÂ setMaxResults() method.
$builder = $GLOBALS['db']->getConnection()->createQueryBuilder();
$builder->select('*')->from('accounts');
if ($status !== null) {
	$builder->where('status = ' . $builder->createPositionalParameter($status));
}
$builder->setMaxResults(2);
$result = $builder->executeQuery();
INSERT queries
INSERT queriesÂ can be easily performed using DBManager class.
Legacy:
$query = 'INSERT INTO table (foo, bar) VALUES ("foo", "bar")';
$GLOBALS['db']->query($query);
Best Practice:
$fieldDefs = $GLOBALS['dictionary']['table']['fields'];
$GLOBALS['db']->insertParams('table', $fieldDefs, ['foo' => 'foo','bar' => 'bar']);
UPDATE queries
When updating records with known IDs or a set of records with simple filtering criteria, then DBManager can be used:
Legacy:
$query = 'UPDATE table SET foo = "bar" WHERE id = ' . $GLOBALS['db']->quoted($id);
$GLOBALS['db']->query($query);
Best Practice:
$fieldDefs = $GLOBALS['dictionary']['table']['fields'];
$GLOBALS['db']->updateParams('table', $fieldDefs, ['foo' => 'bar',], ['id' => $id]);
For more complex criteriaÂ or when column values contain expressions or references to other fields in the table then Doctrine QueryBuilder can be used.
Legacy:
$query = 'UPDATE table SET foo = "bar" WHERE foo = "foo" OR foo IS NULL';
$GLOBALS['db']->query($query);
Best Practice:


$GLOBALS['db']->query($query);
Best Practice:
$query = 'UPDATE table SET foo = ? WHERE foo = ? OR foo IS NULL';
$conn = $GLOBALS['db']->getConnection();
$rowCount = $conn->executeStatement($query, ['bar', 'foo']);
Generating SQL Queries from SugarBean
To have Sugar automatically generate SQL queries, you can use the followingÂ methods from the bean class.
Select Queries
To create a select query you can use the create_new_list_query() method:
$bean = BeanFactory::newBean($module);
$order_by = '';
$where = '';
$fields = array(
    'id',
    'name',
);
$sql = $bean->create_new_list_query($order_by, $where, $fields);
Count Queries
You can also run the generated SQL through the create_list_count_query() method to generate a count query:
$bean = BeanFactory::newBean('Accounts');
$sql = "SELECT * FROM accounts WHERE deleted = 0";
$count_sql = $bean->create_list_count_query($sql);
  	Last modified: 2023-02-03 21:04:03


SugarQuery
Overview
SugarQuery, located in ./include/SugarQuery/SugarQuery.php, provides an object-oriented approachÂ to working with the database. This allows developers to generateÂ theÂ applicable SQL for a Sugar system without having to know which database backend the instance is using. SugarQuery supports all databases supported by Sugar.
Note: SugarQuery only supports reading data from the database at this time (i.e. SELECT statements).Â
Setup
To use SugarQuery, simply create a new SugarQuery object.
$sugarQuery = new SugarQuery();
Basic Usage
Using the SugarQuery objectÂ to retrieve records or generate SQL queries is very simple. At a minimum you need to set the ModuleÂ you are working with, using the from()Â method, however, there are helper methods for just about any operation you would need in a SQL query. The methods listed below will outline the major methodsÂ you should consider utilizing on the SugarQuery object in order to achieve your development goals.
from()
The from() method is used to set the primary module the SugarQuery object will be querying from. It is also used to set some crucial options for the query, such as whether Team Security should be used or if only non-deleted records should be queried. The following example will set the SugarQuery object to query from the Accounts module.
$sugarQuery->from(BeanFactory::newBean('Accounts'));
Arguments
Name
Type
Required
Description
$bean
SugarBean Object
true
The SugarBean object for aÂ specified module. The SugarBean object does not have to be a blank or new Bean as seen in the example above, but can be a previously instantiated SugarBean object.
$options
Array
false
An associative array that can specify anyÂ of the following options:


Array
false
An associative array that can specify anyÂ of the following options:
alias - string -Â The alias for the module table in the generated SQL query
team_security -Â boolean - Whether or not Team Security should be added to the generated SQLÂ queryÂ
add_deleted -Â boolean - Whether or not 'deleted' = 0 should be added to Where clause of generated SQL query
Returns
SugarQuery Object
Allows for method chaining on the SugarQuery object.
select()
The example aboveÂ demonstrates the mostÂ basic example of retrieving records from a module. The select() method can be used on the SugarQuery object to specify the specific fields you wish to retrieve from the query.
//Alter the Selected Fields
$sugarQuery->select(array('id', 'name'));
Arguments
Name
Type
Required
Description
$fields
Array
false
Sets the fields that should be added to the SELECT portion of the SQL query
Returns
SugarQuery_Builder_Select Object
You cannot chain SugarQuery methods off of the select() method, however, you can use the returned Select object to modify the SELECT portion of the statement. Review theÂ SugarQuery_Builder_Select object in ./include/SugarQuery/Builder/Select.php for additional information on usage.
where()
To add a WHERE clause to the query, use the where() method to generate the Where object, and then use method chaining with the various helper methods to add conditions. To add aÂ WHERE clause for records with theÂ name field containingÂ the letter "I", you could addÂ the following code.Â
//add the where clause
$sugarQuery->where()->contains('name', 'I');
Arguments
None
Returns
SugarQuery_Builder_Where Object


Arguments
None
Returns
SugarQuery_Builder_Where Object
Allows for method chaining on the Where object as shown above.Â Review the SugarQuery Conditions documentation for aÂ full spectrum of where() method usage.
Relationships
join()
To add data from a related module to the SugarQuery, use the join() method. Adding to the same SugarQuery code example in this page, theÂ following code wouldÂ add the JOIN from Accounts module tables to Contacts table:
//add join
$sugarQuery->join('contacts');
Arguments
Name
Type
Required
Description
$link_name
String
true
The name of the relationship
$options
Array
false
Â
An associative array that can specify anyÂ of the following options:
alias - string -Â The alias for the module table in the generated SQL query
relatedJoin -Â stringÂ - If joining to a secondary table (related to a related module), such as joining on Opportunities related to Contacts, when querying from Accounts, you can specify either the name of the relationship or the alias you specified for that relationship table.
Returns
SugarQuery_Builder_Join Object
Allows for method chaining on the SugarQuery_Builder_Join Object, to add additional conditions to the WHERE clause of the SQL condition.Â
joinTable()
If you were using the joinRaw() method in previous versions of Sugar, this is the replacement method which allows for joining to aÂ related table in SugarQuery. Adding to the same SugarQuery code example in this page, theÂ following code wouldÂ add the JOIN from Accounts module tables to theÂ accounts_contactsÂ table:
//add join
$sugarQuery->joinTable('accounts_contacts', array('alias' => 'ac'))->on()
    ->equalsField('accounts.id','ac.account_id')


->equalsField('accounts.id','ac.account_id')
    ->equals('ac.primary_account',1);
Arguments
Name
Type
Required
Description
$table_name
String
true
The name of the database table to join.Â
$options
Array
false
Â
An associative array that can specify anyÂ of the following options:
alias - string -Â The alias for the module table in the generated SQL query
Returns
SugarQuery_Builder_Join Object
Allows for method chaining on the SugarQuery_Builder_Join Object, to add additional conditions to the ONÂ clause using the on() method.Â
Altering Results
AlteringÂ the result set of a query can help the performance, as well as be crucial to finding the correct data. The following methods provide ways to limit the result set and change the order.
distinct()
To group the query on a field, you can use the corresponding distinct() method.
//add group by
$sugarQuery->distinct(true);
Arguments
Name
Type
Required
Description
$value
Boolean
true
Set whether or not the DISTINCT statement should be added to the query
Returns
Current SugarQuery Object
Allows for method chaining on the SugarQuery Object.
limit()
To limit the results of the query, you can use the limit() method.Â
//set the limit
$sugarQuery->limit(10);
Arguments
Name
Type
Required
Description
$number
Integer
true
The max amount of rows that should be returned by the query
Returns
Current SugarQuery Object
Allows for method chaining on the SugarQuery Object.
offset()


Current SugarQuery Object
Allows for method chaining on the SugarQuery Object.
offset()
Adding a limit to the query limits the rows returned, however when doing so, you may need to alter the offset of the query to account for pagination or access other portions of the result set. To set an offset, you can use the offset() method.
//set the offset
$sugarQuery->offset(5);
Arguments
Name
Type
Required
Description
$number
Integer
true
TheÂ offset amount of rows, or starting point, of the result
Returns
Current SugarQuery Object
Allows for method chaining on the SugarQuery Object.
orderBy()
To orderÂ the query on a field, you can use the corresponding orderBy() method. This method can be called multiple times, to add multiple fields to the order by clauseÂ of the query.
//add group by
$sugarQuery->orderBy('account_type');
Arguments
Name
Type
Required
Description
$column
String
true
TheÂ field you want the query to be grouped on
$direction
String
false
Sets the direction of sorting. Must be 'ASC' or 'DESC'. The default is 'DESC'.
Returns
Current SugarQuery Object
Allows for method chaining on the SugarQuery Object.
Execution
Once you have the SugarQuery object setup and configured for your statement, you will want to retrieve the results of the query, or simply get the generated query for the object. The following methods are used for executing the SugarQuery object.
execute()
To query the database for a result set, you will use the execute() method. The execute() method will retrieve the results and return them as a raw string, db object, json, or an array depending on the $type parameter. By default, results are returned as an array. An example of fetching records from an account is below:


//fetch the result
$result = $sugarQuery->execute();
The execute() function will return an array of results that you can iterate through as shown below:
Array
(
    [0] => Array
    (
        [id] => f39593da-3f88-3059-4f18-524b4d23d07b
        [name] => International Art Inc
    )
)
Note:Â An empty resultset will return an empty array.
Arguments
Name
Type
Required
Description
$type
String
false
How you want the results of the Query returned. Can be one of the following options:
dbÂ - Returns the result directly from the DatabaseManager resource
arrayÂ -Â DefaultÂ - Returns the results as a formatted array
json - Returns the results encoded as JSON
Returns
Default:Â Array.Â See above argument details for details on other Return options.
compile()
If you want to log the query being generated or want to output the query without running it during development, the compile() method is what should be used retrieve the Prepared Statement. You can then retrieve the Prepared Statement Object to retrieve the Parameterized SQL and the Parameters. For further information on Prepared Statement usage, see our Database documentation.
//get the compiled prepared statement
$preparedStmt = $sugarQuery->compile();
//Retrieve the Parameterized SQL
$sql = $preparedStmt->getSQL();
//Retrieve the parameters as an array
$parameters = $preparedStmt->getParameters();
Arguments
No arguments
Returns
Object
The compiled SQL Query built by the SugarQuery object.
TopicsSugarQuery ConditionsLearn about the various methods that can be utilized with SugarQuery to add conditional statements to a query.Advanced TechniquesLearn about some of the advanced methods that SugarQuery has to offer, that are not as commonly used.


SugarQuery Conditions
Overview
Learn about the various methods that can be utilized with SugarQuery to add conditional statements to a query.Â
Where Clause
Manipulating, the WHERE clause of a SugarQuery object is crucial for getting the correct results. To create a WHERE clause on the query, use the where() method on the SugarQuery object, as outlined in the SugarQuery documentation. Once you have the Where object, you can utilize the following methods on the Where object to add conditional statements.
equals() | notEquals()
Used to equate a field to a given value. Wildcards will not work with this function, as it is looking for an exact match.
//add equals
$SugarQuery->where()->equals('name','Test');
//add Not Equals
$SugarQuery->where()->notEquals('name','Tester');
Arguments
Name
Type
Required
Description
$field
String
true
TheÂ field you are checking againstÂ
$value
String
true
The value the field should be equal to
Returns
SugarQuery_Builder_Where Object
Allows for method chaining on the Where object to add additional conditions.
equalsField() | notEqualsField()
Used to equate a field to another field in the result set.
//add an Equals Field statement
$SugarQuery->where()->equalsField('industry','account_type');
//add a Not Equals Field statement
$SugarQuery->where()->notEqualsField('name','account_type');
Arguments
Name
Type
Required
Description
$field
String
true
TheÂ field you are checking againstÂ
$field
String
true
The other field you want the first field to be equal to
Returns
SugarQuery_Builder_Where Object
Allows for method chaining on the Where object to add additional conditions.Â
isEmpty() | isNotEmpty()


isEmpty() | isNotEmpty()
Used to check if a field is or isn't empty.
//add an isEmpty statement
$SugarQuery->where()->isEmpty('industry');
//add an isNotEmpty statement
$SugarQuery->where()->isNotEmpty('name');
Arguments
Name
Type
Required
Description
$field
String
true
TheÂ field you are checking againstÂ
Returns
SugarQuery_Builder_Where Object
Allows for method chaining on the Where object to add additional conditions.Â
isNull() | notNull()
Used to check if a field is or isn't equal to NULL.
//add an isNull statement
$SugarQuery->where()->isNull('industry');

//add a notNull statement
$SugarQuery->where()->notNull('name');
Arguments
Name
Type
Required
Description
$field
String
true
TheÂ field you are checking againstÂ
Returns
SugarQuery_Builder_Where Object
Allows for method chaining on the Where object to add additional conditions.Â
contains() | notContains()
Used to check if a field has or doesn't have a specified string in its value. Utilizes the LIKE statement, and wildcards on both sides of the provided string.
//add an isNull statement
$SugarQuery->where()->contains('name','Test');
//add a notNull statement
$SugarQuery->where()->notContains('industry','Test');
Arguments
Name
Type
Required
Description
$field
String
true
TheÂ field you are checking againstÂ
$value
String
true
The stringÂ being searched for in the value of the field
Returns
SugarQuery_Builder_Where Object
Allows for method chaining on the WhereÂ Object to add additional conditions.Â
starts() | ends()


starts() | ends()
Similar to the above contains() method, theseÂ methods use the LIKE statement in the SQL query and wildcards for searching for a specified string in the field's value. However,Â the starts() and ends() methods only wildcard the right side and the left side, respectively. The following example demonstrates searching for records where the Name field starts with A, and ends with E.
//add an starts and ends statement
$SugarQuery->where()->starts('name','A')->ends('name','e');
Arguments
Name
Type
Required
Description
$field
String
true
TheÂ field you are checking againstÂ
$value
String
true
The stringÂ being searched for in the value of the field
Returns
SugarQuery_Builder_Where Object
Allows for method chaining on the Where object to add additional conditions.Â
in() | notIn()
Used to check if a field's value is or isn't one of a set of specified values. The following examples lookÂ for records where the industry fieldÂ is in a list of values, and not in a separate list of values.
$values = array(
    'Support',
    'Sales',
    'Engineering'
);
//add in statement
$SugarQuery->where()->in('industry',$values);
$values = array(
    'Marketing',
    'Accounting'
);
//add NotIn Statement
$SugarQuery->where()->notIn('industry',$values);
Arguments
Name
Type
Required
Description
$field
String
true
TheÂ field you are checkingÂ
$values
Array
true
The array of values which the field is being checked against
Returns
SugarQuery_Builder_Where Object
Allows for method chaining on the Where object to add additional conditions.Â
between()


Allows for method chaining on the Where object to add additional conditions.Â
between()
Used primarily for numeric type fields, to check if the value is greater than the minimum number specifiedÂ and less than the maximum number specified. The following code would check for records where the employees field is between 50 and 1000.
//add Between statement
$SugarQuery->where()->between('employees',50,1000);
Arguments
Name
Type
Required
Description
$field
String
true
TheÂ field you are checking againstÂ
$min
Number
true
The lowest number the field's value should be
$max
Number
true
The highest number the field's value should be
Returns
SugarQuery_Builder_Where Object
Allows for method chaining on the Where object to add additional conditions.Â
lt() | lte() | gt() | gte()
These methodsÂ are primarily for numeric fields, to check if a field's value is less than (<), less than or equal (<=), greater than (>), or greater than or equal (>=) to a specified value.
//Add Less Than Statement
$SugarQuery->where()->lt('gross_revenue',1000000);
//Add Less Than or Equal to Statement
$SugarQuery->where()->lte('net_revenue','500000');
//Add Greater Than Statement
$SugarQuery->where()->gt('gross_revenue',500000);
//Add Greater Than or Equal to Statement
$SugarQuery->where()->gte('net_revenue',100000);
Arguments
Name
Type
Required
Description
$field
String
true
TheÂ field you are checking againstÂ
$value
Number
true
The numeric value for comparisonÂ
Returns
SugarQuery_Builder_Where Object
Allows for method chaining on the Where object to add additional conditions.Â


Allows for method chaining on the Where object to add additional conditions.Â
dateRange()
Used to check if a field's value is betweenÂ a preset date range from the current time. See the TimeDate documentation on the available date range keys.
//add DateRange statement
$SugarQuery->where()->dateRange('date_modified','last_30_days');
Arguments
Name
Type
Required
Description
$field
String
true
TheÂ field you are checking againstÂ
$value
String
true
The stringÂ specifying the date range key that will be used for comparison. ExampleÂ 'next_7_days'
Returns
SugarQuery_Builder_Where Object
Allows for method chaining on the Where object to add additional conditions.Â
dateBetween()
To group the query on a field, you can use the corresponding groupBy() method. This method can be called multiple times, to add multiple fields to the grouping of the query.
//add group by
$SugarQuery->where()->dateBetween('date_created',array('2016-01-01','2016-03-01'));
Arguments
Name
Type
Required
Description
$field
String
true
TheÂ field you are checking againstÂ
$value
Array
true
An array containing the minimum date in the first key, and the maximum date in the second.
Returns
SugarQuery_Builder_Where Object
Allows for method chaining on the WhereÂ Object to add additional conditions.Â
Combinations


Combinations
Now that you have reviewed all of the available conditional statements for SugarQuery, you may want to combine them using AND and OR all within the same query. By default when the where() method is called, chained conditional methods will be added with AND to the where clause. You can specify an OR where clause on the main SugarQuery object by using the orWhere() method, which works the same as the where() method, just adds conditional statements with OR instead. The following methods allow for adding internal AND and OR logic to conditional statements on the Where object.
queryAnd()
To start a group of conditional statements that should allÂ evaluate to True, use the queryAnd() method.Â For example, if you want to query for Accounts, where the name contains 'Test' AND description contains 'Test', you might use the following code:
$SugarQuery = new SugarQuery();
$SugarQuery->select(array('name'));
$SugarQuery->from(BeanFactory::newBean('Accounts'));
//Using queryAnd
$SugarQuery->where()->queryAnd()->contains('name','Test')->contains('description','Test');
The above use of queryAnd() method isn't entirely needed, as the main Where object would be using AND for all conditions anyway, but it does group the two conditions inside of their own parenthesis in the compiled query, as shown below, to demonstrate how it can be used for altering query logic.
SELECT  accounts.name name FROM accounts WHERE accounts.deleted = 0 AND (accounts.name LIKE '%Test%' AND accounts.description LIKE '%Test%')
queryOr()
To start a group of conditional statements that should evaluate to true, if any condition is true, you can use the queryOr() method. For example, if you want to query for Accounts, where the name contains 'Test' or where the description contains 'Test', you might use the following code:


$SugarQuery = new SugarQuery();
$SugarQuery->select(array('name'));
$SugarQuery->from(BeanFactory::newBean('Accounts'));
//Using queryOr
$SugarQuery->where()->queryOr()->contains('name','Test')->contains('description','Test');
This will group the two conditions inside of their own parenthesis in the compiled query.Â If eitherÂ of the conditions isÂ True, it will return a record. An example is shown below.
SELECT  accounts.name name FROM accounts WHERE accounts.deleted = 0 AND (accounts.name LIKE '%Test%' OR accounts.description LIKE '%Test%')
  	Last modified: 2023-02-03 21:04:03


Advanced Techniques
Overview
Learn about some of the advanced methods that SugarQuery has to offer, that are not as commonly used.
Get First Record
Getting the first record in a result set, can be accomplished by using the limit() method. The getOne() method is similar in that it gets the first record, but it also returns the first piece of data for that record.Â
getOne()
Get the first piece of data on the first record returned by the generated query. In this example, we want the 'name' from the Account with a given ID.
$SugarQuery = new SugarQuery();
$SugarQuery->select(array('name'));
$SugarQuery->from(BeanFactory::newBean('Accounts'));
$SugarQuery->where()->equals('id',$id);
//Get the Name of the account
$accountName = $SugarQuery->getOne();
Aggregates
setCountQuery()
Currently, the only method available for creating an aggregate column, is the setCountQuery() method on the SugarQuery_Builder_Select Object. You can add this method to your select() method chain, to add count(0) as record_count to the SQL SELECT statement.
$SugarQuery = new SugarQuery();
$SugarQuery->select(array('name'))->setCountQuery();
$SugarQuery->from(BeanFactory::newBean('Accounts'));
$SugarQuery->groupByRaw('accounts.name');
The above example will output the following prepared statement when using compile():
SELECT accounts.name, COUNT(0) AS record_count FROM accounts WHERE accounts.deleted = ? GROUP BY accounts.name, accounts.name
Parameters
array (
    [1] => 0
)
Arguments
No arguments
Returns
SugarQuery_Builder_Select Object
Allows for method chaining on the SelectÂ Object.
Joins


Allows for method chaining on the SelectÂ Object.
Joins
Joining to tables and joining via SugarBean relationships is outlined in the SugarQuery documentation, however the SugarQuery_Builder_Join Object has a few helpful methods not mentioned there.
joinName()
If you are not using a custom alias for the relationship or table, you may want to retrieve the generated name used by SugarQuery to add a conditions or join to.
$SugarQuery = new SugarQuery();
$SugarQuery->from(BeanFactory::getBean('Accounts'));
$contacts = $SugarQuery->join('contacts')->joinName();
$SugarQuery->select(array("$contacts.full_name"));
$SugarQuery->where()->equals('industry', 'Media');
The above example will output the following prepared statement when using compile():
SELECT jt0_contacts.salutation rel_full_name_salutation, jt0_contacts.first_name rel_full_name_first_name, jt0_contacts.last_name rel_full_name_last_name FROM accounts INNER JOIN accounts_contacts jt1_accounts_contacts ON (accounts.id = jt1_accounts_contacts.account_id) AND (jt1_accounts_contacts.deleted = ?) INNER JOIN contacts jt0_contacts ON (jt0_contacts.id = jt1_accounts_contacts.contact_id) AND (jt0_contacts.deleted = ?) WHERE (accounts.industry = ?) AND (accounts.deleted = ?)
Parameters:
array (
    [1] => 0
    [2] => 0
    [3] => Media
    [4] => 0
)
Â
Arguments
No arguments
Returns
string
The name used in Query to identify the joined table
Unions
Unions allow joining multiple queries with the same selected fields to be combined during output. You can use Unions in SugarQuery by using the union() method.
union()


union()
To add a union, you can use the corresponding union() method. The example below will join two SQL queries:
//Fetch the bean of the module to query
$bean = BeanFactory::newBean('Accounts');
//Specify fields to fetch
$fields = array(
    'id',
    'name'
);
//Create first query
$sq1 = new SugarQuery();
$sq1->select($fields);
$sq1->from($bean, array('team_security' => false));
$sq1->Where()->in('account_type', array('Customer'));
//Create second query
$sq2 = new SugarQuery();
$sq2->select($fields);
$sq2->from($bean, array('team_security' => false));
$sq2->Where()->in('account_type', array('Investor'));
//Create union
$sqUnion = new SugarQuery();
$sqUnion->union($sq1);
$sqUnion->union($sq2);
$sqUnion->limit(5);
The above example will output the following prepared statement when using compile():
SELECT accounts.id, accounts.name FROM accounts WHERE (accounts.account_type IN (?)) AND (accounts.deleted = ?) UNION ALL SELECT accounts.id, accounts.name FROM accounts WHERE (accounts.account_type IN (?)) AND (accounts.deleted = ?) LIMIT 5
Parameters:
array (
    [1] => Customer
    [2] => 0
    [3] => Investor
    [4] => 0
)
Arguments
Name
Type
Required
Description
$selectÂ
SugarQuery
true
The SugarQuery Object you wish to add to the UNION query
$all
Boolean
false
Whether to use UNION ALL or just UNION in the query. The default value is TRUE.
Returns
SugarQuery_Builder_Union Object


Returns
SugarQuery_Builder_Union Object
Allows for method chaining on the UnionÂ Object.
Having
When using aggregates in a query, you might want to filter out values based on a condition. SugarQuery provides the having() method for adding HAVING clause to the query.
having()
To use the having() method, you have to build a SugarQuery_Builder_Condition Object and set the field, operator, and value that condition is based on.
$SugarQuery = new SugarQuery();
$SugarQuery->from(BeanFactory::getBean('Accounts'));
$SugarQuery->join('contacts', array('alias' => 'industryContacts'));
$SugarQuery->join('opportunities', array('relatedJoin' => 'industryContacts', 'alias' => 'contactsOpportunities'));
$SugarQuery->select()->setCountQuery();
$SugarQuery->where()->equals('contactsOpportunities.sales_stage', 'closed');
$havingCondition = new SugarQuery_Builder_Condition($SugarQuery);
$havingCondition->setField('contactsOpportunities.amount')->setOperator('>')->setValues('1000');
$SugarQuery->having($havingCondition);
The above example will output the following prepared statement when using compile():


The above example will output the following prepared statement when using compile():
SELECT COUNT(0) AS record_count FROM accounts INNER JOIN accounts_contacts jt0_accounts_contacts ON (accounts.id = jt0_accounts_contacts.account_id) AND (jt0_accounts_contacts.deleted = ?) INNER JOIN contacts industryContacts ON (industryContacts.id = jt0_accounts_contacts.contact_id) AND (industryContacts.deleted = ?) INNER JOIN opportunities_contacts jt1_opportunities_contacts ON jt1_opportunities_contacts.deleted = ? INNER JOIN opportunities contactsOpportunities ON (contactsOpportunities.id = jt1_opportunities_contacts.opportunity_id) AND (contactsOpportunities.deleted = ?) WHERE (contactsOpportunities.sales_stage = ?) AND (accounts.deleted = ?) HAVING contactsOpportunities.amount > ?
Parameters:
array (
    [1] => 0
    [2] => 0
    [3] => 0
    [4] => 0
    [5] => closed
    [6] => 0
    [7] => 1000
)
Arguments
Name
Type
Required
Description
$conditionÂ
SugarQuery_Builder_Condition
true
The conditional object used to generate the HAVING clause
Returns
SugarQuery_Builder_Having Object
Allows for method chaining on the HavingÂ Object to add additional conditions.
Raw Methods
The SugarQuery Object has a few helper methods that allow raw SQL statement parts to be passed into. This allows for more complex statements or edge case scenarios where a helper function may not have met the requirements for the query.Â
whereRaw()


whereRaw()
To add to the WHERE clause of SugarQuery Object with raw SQL syntax, you can utilize the whereRaw() method. This method will append the specified statement, to the WHERE clause using an AND operator, and will wrap the entire statement in parenthesis. The following is an example use with the output:
$SugarQuery = new SugarQuery();
$SugarQuery->select(array('name'));
$SugarQuery->from(BeanFactory::newBean('Accounts'));
$SugarQuery->whereRaw("name LIKE '%T%'");
The above example will output the following prepared statement when using compile():
SELECT accounts.name FROM accounts WHERE (name LIKE '%T%') AND (accounts.deleted = ?)
Parameters:
array (
    [1] => 0
)
Arguments
Name
Type
Required
Description
$sql
String
true
TheÂ WHERE clause SQLÂ to be appended to the where clause on the SugarQuery object. All conditions passed in are wrapped in parenthesis and appended using AND (if other conditions exist on where clause).
Returns
SugarQuery_Builder_Where Object
Allows for method chaining on the Where object.
groupByRaw()
To add multiple fields to the GROUP BY statement on the SugarQuery Object, it may be easiest to use the groupByRaw() method.
$SugarQuery = new SugarQuery();
$SugarQuery->select(array('account_type', 'industry'));
$SugarQuery->from(BeanFactory::newBean('Accounts'));
$SugarQuery->groupByRaw("accounts.account_type,accounts.industry");
The above example will output the following prepared statement when using compile():
SELECT accounts.account_type, accounts.industry FROM accounts WHERE accounts.deleted = ? GROUP BY accounts.account_type,accounts.industry
Parameters:
array (
    [1] => 0
)
Arguments
Name
Type
Required
Description
$sql


)
Arguments
Name
Type
Required
Description
$sql
String
true
The GROUP BY statement, without the GROUP BY keyword.Â
Returns
SugarQuery Object
Allows for method chaining on the SugarQuery Object.
orderByRaw()
Using the oderBy() method only allows for adding a single field to the SugarQuery object at a time. In some cases, you might consider using the orderByRaw() method to add multiple fields or the entire ORDER BY statement to the SugarQuery object.Â
$SugarQuery = new SugarQuery();
$SugarQuery->select(array('name'));
$SugarQuery->from(BeanFactory::newBean('Accounts'));
$SugarQuery->orderByRaw("accounts.name DESC, accounts.date_modified");
The above example will output the following prepared statement when using compile():
SELECT accounts.name FROM accounts WHERE accounts.deleted = ? ORDER BY accounts.name DESC, accounts.date_modified DESC, accounts.id DESC
Parameters:
array (
    [1] => 0
)
Arguments
Name
Type
Required
Description
$sql
String
true
The ORDER BY statement, without the ORDER BY keyword.
Returns
SugarQuery Object
Allows for method chaining on the SugarQuery Object.
havingRaw()
Using the havingRaw() method allows for adding a having statementÂ to the SugarQuery object.Â
$SugarQuery = new SugarQuery();
$SugarQuery->from(BeanFactory::getBean('Accounts'));
$SugarQuery->join('contacts', array('alias' => 'industryContacts'));
$SugarQuery->join('opportunities', array('relatedJoin' => 'industryContacts', 'alias' => 'contactsOpportunities'));
$SugarQuery->select()->setCountQuery();
$SugarQuery->where()->equals('contactsOpportunities.sales_stage', 'closed');


$SugarQuery->where()->equals('contactsOpportunities.sales_stage', 'closed');
$SugarQuery->havingRaw("contactsOpportunities.amount > 1000");
The above example will output the following prepared statement when using compile():
SELECT COUNT(0) AS record_count FROM accounts INNER JOIN accounts_contacts jt0_accounts_contacts ON (accounts.id = jt0_accounts_contacts.account_id) AND (jt0_accounts_contacts.deleted = ?) INNER JOIN contacts industryContacts ON (industryContacts.id = jt0_accounts_contacts.contact_id) AND (industryContacts.deleted = ?) INNER JOIN opportunities_contacts jt1_opportunities_contacts ON jt1_opportunities_contacts.deleted = ? INNER JOIN opportunities contactsOpportunities ON (contactsOpportunities.id = jt1_opportunities_contacts.opportunity_id) AND (contactsOpportunities.deleted = ?) WHERE (contactsOpportunities.sales_stage = ?) AND (accounts.deleted = ?) HAVING contactsOpportunities.amount > 1000
Parameters:
array (
    [1] => 0
    [2] => 0
    [3] => 0
    [4] => 0
    [5] => closed
    [6] => 0
)
Arguments
Name
Type
Required
Description
$sql
String
true
The HAVINGÂ statement, without the HAVINGÂ keyword.
Returns
SugarQuery Object
Allows for method chaining on the SugarQuery Object.Â
  	Last modified: 2023-02-03 21:04:03


Subpanels
Overview
For Sidecar, Sugar's subpanel layouts have been modified to work as simplified metadata.Â This page is anÂ overview of the metadata framework for subpanels.Â
The reason for this change is that previous versions of Sugar generated the metadata from various sources such as the SubPanelLayout and MetaDataManager classes. This eliminates the need for generating and processing the layouts and allows the metadata to be easily loaded to Sidecar.
Note:Â Modules running in backward compatibility mode do not use the Sidecar subpanel layouts as they use the legacy MVC framework.
Hierarchy Diagram
When loading the Sidecar subpanel layouts, the system processes the layout in the following manner:
Note: The Sugar application's client type is "base". For more information on the various client types, please refer toÂ the User Interface page.
Subpanels and Subpanel Layouts
Sugar contains both a subpanels (plural) layout and a subpanel (singular) layout. The subpanels layout contains the collection of subpanels,Â whereasÂ the subpanel layout renders the actual subpanel widget.
An example of a stock module's subpanels layout is:
./modules/Bugs/clients/base/layouts/subpanels/subpanels.php
<?php
$viewdefs['Bugs']['base']['layout']['subpanels'] = array (
  'components' => array (
      array (
          'layout' => 'subpanel',
          'label' => 'LBL_DOCUMENTS_SUBPANEL_TITLE',
          'context' => array (
              'link' => 'documents',
          ),
      ),
      array (
          'layout' => 'subpanel',
          'label' => 'LBL_CONTACTS_SUBPANEL_TITLE',
          'context' => array (
              'link' => 'contacts',
          ),
      ),
      array (


'link' => 'contacts',
          ),
      ),
      array (
          'layout' => 'subpanel',
          'label' => 'LBL_ACCOUNTS_SUBPANEL_TITLE',
          'context' => array (
              'link' => 'accounts',
          ),
      ),
      array (
          'layout' => 'subpanel',
          'label' => 'LBL_CASES_SUBPANEL_TITLE',
          'context' => array (
              'link' => 'cases',
          ),
      ),
  ),
  'type' => 'subpanels',
  'span' => 12,
);
You can see that the layout incorporates the use of the subpanel layout for each module. As most of the subpanel data is similar, this approach allows us to use less duplicate code. The subpanel layout, shown below, shows the three views that make up the subpanel widgets users see.
./clients/base/layouts/subpanel/subpanel.php
<?php
$viewdefs['base']['layout']['subpanel']  = array (
    'components' => array (
        array (
            'view' => 'panel-top',
        )
        array (
            'view' => 'subpanel-list',
        ),
        array (
            'view' => 'list-bottom',
        ),
    ),
    'span' => 12,
    'last_state' => array(
        'id' => 'subpanel'
    ),
);
Adding Subpanel Layouts


),
);
Adding Subpanel Layouts
When a new relationship is deployed from Studio, the relationship creation process will generate the layouts using the extension framework. You should note that for stock relationships and custom deployed relationships, layouts are generated for both Sidecar and Legacy MVC Subpanel formats. This is done to ensure that any related modules, whether in Sidecar or Backward Compatibility mode, display a related subpanel as expected.
Sidecar Layouts
Custom Sidecar layouts, located in ./custom/Extension/modules/<module>/Ext/clients/<client>/layouts/subpanels/, are compiled into ./custom/modules/<module>/Ext/clients/<client>/layouts/subpanels/subpanels.ext.php using the extension framework. When a relationship is saved, layout files are created for both the "base" and "mobile" client types.
For example, deploying a 1:M relationship from Bugs to Leads will generate the following Sidecar files:
./custom/Extension/modules/Bugs/Ext/clients/base/layouts/subpanels/bugs_leads_1_Bugs.php
<?php
$viewdefs['Bugs']['base']['layout']['subpanels']['components'][] = array (
  'layout' => 'subpanel',
  'label' => 'LBL_BUGS_LEADS_1_FROM_LEADS_TITLE',
  'context' =>
  array (
    'link' => 'bugs_leads_1',
  ),
);
./custom/Extension/modules/Bugs/Ext/clients/mobile/layouts/subpanels/bugs_leads_1_Bugs.php
<?php
$viewdefs['Bugs']['mobile']['layout']['subpanels']['components'][] = array (
  'layout' => 'subpanel',
  'label' => 'LBL_BUGS_LEADS_1_FROM_LEADS_TITLE',
  'context' =>
  array (
    'link' => 'bugs_leads_1',
  ),


array (
    'link' => 'bugs_leads_1',
  ),
);
Note: The additional legacy MVC layouts generated by a relationships deployment are described below.
Legacy MVC Subpanel Layouts
Custom Legacy MVC Subpanel layouts, located in ./custom/Extension/modules/<module>/Ext/Layoutdefs/, are compiled into ./custom/modules/<module>/Ext/Layoutdefs/layoutdefs.ext.php using the extension framework. You should also note that when a relationship is saved, wireless layouts, located in ./custom/Extension/modules/<module>/Ext/WirelessLayoutdefs/, are created and compiled into ./custom/modules/<module>/Ext/Layoutdefs/layoutdefs.ext.php.
An example of this is when deploying a 1-M relationship from Bugs to Leads, the following layoutdef files are generated:
./custom/Extension/modules/Bugs/Ext/Layoutdefs/bugs_leads_1_Bugs.php
<?php
$layout_defs["Bugs"]["subpanel_setup"]['bugs_leads_1'] = array (
  'order' => 100,
  'module' => 'Leads',
  'subpanel_name' => 'default',
  'sort_order' => 'asc',
  'sort_by' => 'id',
  'title_key' => 'LBL_BUGS_LEADS_1_FROM_LEADS_TITLE',
  'get_subpanel_data' => 'bugs_leads_1',
  'top_buttons' =>
  array (
    0 =>
    array (
      'widget_class' => 'SubPanelTopButtonQuickCreate',
    ),
    1 =>
    array (
      'widget_class' => 'SubPanelTopSelectButton',
      'mode' => 'MultiSelect',
    ),
  ),
);


'mode' => 'MultiSelect',
    ),
  ),
);
./custom/Extension/modules/Bugs/Ext/WirelessLayoutdefs/bugs_leads_1_Bugs.php
<?php
$layout_defs["Bugs"]["subpanel_setup"]['bugs_leads_1'] = array (
  'order' => 100,
  'module' => 'Leads',
  'subpanel_name' => 'default',
  'title_key' => 'LBL_BUGS_LEADS_1_FROM_LEADS_TITLE',
  'get_subpanel_data' => 'bugs_leads_1',
);
Fields Metadata
Sidecar's subpanel field layouts are initially defined by the subpanel list-view metadata.
Hierarchy Diagram
The subpanel list metadata is loaded in the following manner:
Note: The Sugar application's client type is "base". For more information on the various client types, please refer toÂ the User Interface page.
Subpanel List Views
By default, all modules come with a default set of subpanel fields for when they are rendered as a subpanel. An example of this is can be found in the Bugs module:
./modules/Bugs/clients/base/views/subpanel-list/subpanel-list.php
<?php
$subpanel_layout['list_fields'] = array (
  'full_name' =>
  array (
    'type' => 'fullname',
    'link' => true,
    'studio' =>
    array (
      'listview' => false,
    ),
    'vname' => 'LBL_NAME',
    'width' => '10%',
    'default' => true,
  ),
  'date_entered' =>
  array (
    'type' => 'datetime',
    'studio' =>
    array (


'type' => 'datetime',
    'studio' =>
    array (
      'portaleditview' => false,
    ),
    'readonly' => true,
    'vname' => 'LBL_DATE_ENTERED',
    'width' => '10%',
    'default' => true,
  ),
  'refered_by' =>
  array (
    'vname' => 'LBL_LIST_REFERED_BY',
    'width' => '10%',
    'default' => true,
  ),
  'lead_source' =>
  array (
    'vname' => 'LBL_LIST_LEAD_SOURCE',
    'width' => '10%',
    'default' => true,
  ),
  'phone_work' =>
  array (
    'vname' => 'LBL_LIST_PHONE',
    'width' => '10%',
    'default' => true,
  ),
  'lead_source_description' =>
  array (
    'name' => 'lead_source_description',
    'vname' => 'LBL_LIST_LEAD_SOURCE_DESCRIPTION',
    'width' => '10%',
    'sortable' => false,
    'default' => true,
  ),
  'assigned_user_name' =>
  array (
    'name' => 'assigned_user_name',
    'vname' => 'LBL_LIST_ASSIGNED_TO_NAME',
    'widget_class' => 'SubPanelDetailViewLink',
    'target_record_key' => 'assigned_user_id',
    'target_module' => 'Employees',
    'width' => '10%',
    'default' => true,
  ),
  'first_name' =>
  array (
    'usage' => 'query_only',
  ),
  'last_name' =>


'usage' => 'query_only',
  ),
  'last_name' =>
  array (
    'usage' => 'query_only',
  ),
  'salutation' =>
  array (
    'name' => 'salutation',
    'usage' => 'query_only',
  ),
);
To modify this layout, navigate to Admin > Studio > {Parent Module} > Subpanels > Bugs and make your changes. Once saved, Sugar will generate ./custom/modules/Bugs/clients/<client>/views/subpanel-for-<link>/subpanel-for-<link>.php which will be used for rendering the fields you selected.
You should note that, just as Sugar mimics the Sidecar layouts in the legacy MVC framework for modules in backward compatibility, it also mimics the field list in ./modules/<module>/metadata/subpanels/default.php and ./custom/modules/<module>/metadata/subpanels/default.php. This is done to ensure that any related modules, whether in Sidecar or Backward Compatibility mode, display the same field list as expected.
  	Last modified: 2023-02-03 21:04:03


Models
Overview
Each module in Sugar is extending theÂ Sugar Model. This model is determined by the SugarBean, which contains methods to create, read/retrieve, update, and delete records in the database and any subclass of the SugarBean. Many of the common Sugar modules also use the SugarObjects class, which is explained in the next section.
SugarObject Templates
Sugar objects extend the concept of subclassing a step further and allow you to subclass the vardefs. This includes inheriting of fields, relationships, indexes, and language files. However, unlike subclassing, you are not limited to a single inheritance. For example, if there were a Sugar object for fields used in every module (e.g. id, deleted, or date_modified), you could have the module inherit from both the Basic Sugar object and the Person Sugar object.
To further illustrate this, let's say the Basic object type has a field 'name' with length 10 and the Company object has a field 'name' with length 20. If you inherit from Basic first and then Company, your field will inherit the Company object's length of 20. However, the module-level setting always overrides any values provided by Sugar objects, so, if the field 'name' in your module has been set to length 60, then the field's length will ultimately be 60.
There are six types of SugarObject templates:
Basic : Contains the basic fields required by all Sugar modules
Person : Based on the Contacts, Prospects, and Leads modules
Issue : Based on the Bugs and Cases modules
Company : Based on the Accounts module
File : Based on the Documents module
Sale : Based on the Opportunities module
SugarObject Interfaces


File : Based on the Documents module
Sale : Based on the Opportunities module
SugarObject Interfaces
In addition to the object templates above, "assignable" object templates can beÂ used for modules with records that should contain an Assigned To field. Although every module does not use this, most modules allow assignment of records to users. SugarObject interfaces allow us to add the assignable attribute to these modules.
SugarObject interfaces and SugarObject templates are very similar to one another, but the main distinction is that templates have a base class you can subclass while interfaces do not. If you look into the file structure, templates include many additional files, including a full metadata directory. This is used primarily for Module Builder.
File Structure
./include/SugarObjects/interfaces
./include/SugarObjects/templates
Implementation
There are two things you need to do to take advantage of SugarÂ objects:
Subclass the SugarObject class you wish to extend for your class:class MyClass extends Person
{
    function MyClass()
    {
        parent::Person();
    }
}
Add the following line to the end of theÂ vardefs.php file:VardefManager::createVardef('Contacts', 'Contact', array('default', 'assignable', 'team_security', 'person'));
This snippet tells the VardefManager to create a cache of the Contacts vardefs with the addition of all the default fields, assignable fields, team security fields, and all fields from the person class.
Performance Considerations
VardefManager caches the generated vardefs into a single file that is loaded at runtime. If that file is not found, SugarÂ loads the vardefs.php file, located in your module's directory. The same is true for language files. This caching also includes data for custom fields and any vardef or language extensions that are dropped into the extension framework.
Cache Files


Cache Files
./cache/modules/<module>/<object_name>vardefs.php
./cache/modules/<module>/languages/en_us.lang.php
TopicsSugarBean$bean = BeanFactory::retrieveBean($module, $id);BeanFactoryThe BeanFactory class, located in ./data/BeanFactory.php, is used for loading an instance of a SugarBean. This class should be used any time you are creating or retrieving bean objects. It will automatically handle any classes required for the bean.
  	Last modified: 2023-02-03 21:04:03


SugarBean
//Retrieve bean
$bean = BeanFactory::retrieveBean($module, $id);
//Set deleted to true
$bean->mark_deleted($bean->id);
//Save
$bean->save();
Overview
The SugarBean class supports all the model operations to and from the database. Any module that interacts with the database utilizes the SugarBean class, which contains methods to create, read/retrieve, update, and delete records in the database (CRUD), as well as fetch related records.
CRUD Handling
The SugarBean handles the most basic functions of the Sugar database, allowing you to create, retrieve, update, and delete data.Â
Creating and Retrieving Records
The BeanFactory class is used for bean loading. The class should be used wheneverÂ you are creating or retrieving bean objects. It will automatically handle any classes required for the bean. More information on this can be found in the BeanFactory section.
Obtaining the Id of a Recently Saved Bean
For new records, a GUID is generated and assigned to the record id field. Saving new or existing records returns a single value to the calling routine, which is the id attribute of the saved record. The id has the following format:
aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
You can retrieve a newly created record's id with the following:
//Create bean
$bean = BeanFactory::newBean($module);
//Populate bean fields
$bean->name = 'Example Record';
//Save
$bean->save();
//Retrieve the bean id
$record_id = $bean->id;
Saving a Bean with a Specific ID


$record_id = $bean->id;
Saving a Bean with a Specific ID
Saving a record with a specific id requires the id and new_with_id attribute of the bean to be set. When doing so, it is important that you use a globally unique identifier. Failing to do so may result in unexpected behavior in the system. An example setting an id is shown below:
//Create bean
$bean = BeanFactory::newBean($module);
//set the new flag
$bean->new_with_id = true;
//Set the record id with a static id
$id = '38c90c70-7788-13a2-668d-513e2b8df5e1';
$bean->id = $id;
//or have the system generate an id for you
//$id = Sugarcrm\Sugarcrm\Util\Uuid::uuid1()
//$bean->id = $id;
//Populate bean fields
$bean->name = 'Example Record';
//Save
$bean->save();
Setting new_with_id to true prevents the save method from creating a new id value and uses the assigned id attribute. If the id attribute is empty and the new_with_id attribute is set to true, the save will fail. If you would like for the system to generate an id for you, you can use Sugarcrm\Sugarcrm\Util\Uuid::uuid1().
Distinguishing New from Existing Records
To identify whether or not a record is new or existing, you can check the fetched_rows property. If the $bean has a fetched_row, it was loaded from the database. An example is shown below:
if (!isset($bean->fetched_row['id'])) {
    //new record
} else {
    //existing record
}


//new record
} else {
    //existing record
}
If you are working with a logic hook such as before_save or after_save, you should check the arguments.isUpdate property to determine this as shown below:
<?php
if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
class logic_hooks_class
{
    function hook_method($bean, $event, $arguments)
    {
        if (isset($arguments['isUpdate']) && $arguments['isUpdate'] == false) {
            //new record
        } else {
            //existing record
        }
    }
}
?>
Retrieving a Bean by Unique Field
Sometimes developers have a need to fetch a record based on a unique field other than the id. In previous versions you were able to use theÂ retrieve_by_string_fields()Â method to accomplishÂ this, however, that has now been deprecated. To search andÂ fetch records,Â you shouldÂ use the SugarQueryÂ builder. An example of this is shown below:
require_once('include/SugarQuery/SugarQuery.php');
$sugarQuery = new SugarQuery();
//fetch the bean of the module to query
$bean = BeanFactory::newBean('<modules>');
//create first query
$sql = new SugarQuery();
$sql->select('id');
$sql->from($bean);
$sql->Where()->equals('<field>', '<unique value>');
$result = $sql->execute();
$count = count($result);
if ($count == 0) {
    //no results were found
} elseif ($count == 1) {
    //one result was found
    $bean = BeanFactory::getBean('<module>', $result[0]['id']);
} else {
    //multiple results were found
}


} else {
    //multiple results were found
}
Updating a Bean
You can updateÂ a bean by fetching a record and then updating the property:
//Retrieve bean
$bean = BeanFactory::retrieveBean($module, $id);
//Fields to update
$bean->name = 'Updated Name';
//Save
$bean->save();
Note: Disabling row-level security when accessing a bean should be set to trueÂ only when it is absolutely necessary to bypass security, for example, when updating a Lead record from a custom Entry Point.Â An example of accessing the bean while bypassing row security is:
$bean = BeanFactory::retrieveBean($module, $record_id, array('disable_row_level_security' => true));
Updating a Bean Without Changing the Date Modified
The SugarBean class contains an attribute called update_date_modified, which is set to true when the class is instantiated and means that the date_modified attribute is updated to the current database date timestamp. SettingÂ update_date_modified to false would result in the date_modified attribute not being set with the current database date timestamp.
//Retrieve bean
$bean = BeanFactory::retrieveBean($module, $id);
//Set modified flag
$bean->update_date_modified = false;
//Fields to update
$bean->name = 'Updated Name';
//Save
$bean->save();
Note: Disabling row-level security when accessing a bean should be set to trueÂ only when it is absolutely necessary to bypass security, for example, when updating a Lead record from a custom Entry Point.Â An example of accessing the bean while bypassing row security is:
$bean = BeanFactory::retrieveBean($module, $record_id, array('disable_row_level_security' => true));
Deleting a Bean


Deleting a Bean
Deleting a bean can be done by fetching it then calling theÂ mark_deleted()Â method which makes sure any relationships with related records are removedÂ as well:
//Retrieve bean
$bean = BeanFactory::retrieveBean($module, $id);
//Set deleted to true
$bean->mark_deleted($id);
//Save
$bean->save();
Note: Disabling row-level security when accessing a bean should be set to trueÂ only when it is absolutely necessary to bypass security, for example, when updating a Lead record from a custom Entry Point.Â An example of accessing the bean while bypassing row security is:
$bean = BeanFactory::retrieveBean($module, $record_id, array('disable_row_level_security' => true));
Fetching Relationships
This sectionÂ explains how theÂ SugarBean class can be used to fetch related information from the database.
Fetching Related Records
To fetch related records, load the relationship using the link:
//If relationship is loaded
if ($bean->load_relationship($link)) {
    //Fetch related beans
    $relatedBeans = $bean->$link->getBeans();
}
An example of this is to load the contacts related to an account:
//Load Account
$bean = BeanFactory::getBean('Accounts', $id);
//If relationship is loaded
if ($bean->load_relationship('contacts')) {
    //Fetch related beans
    $relatedBeans = $bean->contacts->getBeans();
}
Fetching Related Record IDs
To fetch only related record IDs, load the relationship using the link:
//If relationship is loaded
if ($bean->load_relationship($link)) {
    //Fetch related record IDs
    $relatedBeans = $bean->$link->get();
}


$relatedBeans = $bean->$link->get();
}
An example of this is to load the record IDs of contacts related to an account:
//Load Account
$bean = BeanFactory::getBean('Accounts', $id);
//If relationship is loaded
if ($bean->load_relationship('contacts')) {
    //Fetch related beans
    $relatedBeans = $bean->contacts->get();
}
Fetching a Parent Record
Fetching a parent record is similar to fetching child records in that you will still need to load the relationship using the link:
//If relationship is loaded
if ($bean->load_relationship($link)) {
    //Fetch related beans
    $relatedBeans = $bean->$link->getBeans();
    $parentBean = false;
    if (!empty($relatedBeans)) {
        //order the results
        reset($relatedBeans);
        //first record in the list is the parent
        $parentBean = current($relatedBeans);
    }
}
An example of this is to load a contact and fetch its parent account:
//Load Contact
$bean = BeanFactory::getBean('Contacts', $id);
//If relationship is loaded
if ($bean->load_relationship('accounts')) {
    //Fetch related beans
    $relatedBeans = $bean->accounts->getBeans();
    $parentBean = false;
    if (!empty($relatedBeans)) {
        //order the results
        reset($relatedBeans);
        //first record in the list is the parent
        $parentBean = current($relatedBeans);
    }
}


$parentBean = current($relatedBeans);
    }
}
TopicsCustomizing Core SugarBeansThis article covers how to extend core SugarBean objects to implement custom code. This can be used to alter the stock assignment notification message or to add logic to change the default modules behavior.Implementing Custom SugarBean TemplatesThis article covers how to implement custom SugarBean templates, which can then be extended by custom modules. It also covers vardef templates, which can more portable and used by multiple modules. By default, Sugar comes with a few templates such as Company, Person, Issue, and File templates.
  	Last modified: 2023-02-03 21:04:03


Customizing Core SugarBeans
Overview
This article covers how to extend core SugarBean objects to implement custom code. This can be used to alter the stock assignment notification message or toÂ add logic to change theÂ default modules behavior.
Customizing a Core Module
The best approach to customizing a core SugarBean object is to become familiar with how the core Bean operates and only modify the bean logic where absolutely necessary. After understanding where you wish to make your customization in the module Bean class, you can extend the class in the custom directory.Â In order to avoid duplicating code from the core Bean class, it is always best to extend the class rather than duplicate the class to the custom directory.Â
Extending the SugarBean
For this example, we will look at modifying the Leads SugarBean object. To extend the core Leads bean, create the following file:
./custom/modules/Leads/CustomLead.php
<?php
require_once 'modules/Leads/Lead.php';
class CustomLead extends Lead
{
    /**
     * Saves the record
     * - You can use this method to implement before save or after save logic
     *
     * @param bool $check_notify
     * @return string
     */
    function save($check_notify = FALSE)
    {
        $id = parent::save($check_notify);
        return $id;
    }
    /**
     * Retrieves a record
     * - You can use this method to set properties when fetching a bean
     *
     * @param string $id
     * @param bool $encode
     * @param bool $deleted
     * @return $this
     */
    function retrieve($id = '-1', $encode = true, $deleted = true)
    {
        return parent::retrieve($id, $encode, $deleted);
    }


return parent::retrieve($id, $encode, $deleted);
    }
    /**
     * Calls custom logic events
     * - You can use this method to watch for specific logic hook events
     *
     * @param $event
     * @param array $arguments
     */
    function call_custom_logic($event, $arguments = array())
    {
        parent::call_custom_logic($event, $arguments);
    }
}
Register the Custom Class
Once the custom SugarBean class file has been created, register that class to be used by the module so that the BeanFactory knows which class to use. To register the class you will create the following file in the ./custom/Extension/ directory:
./custom/Extension/application/Ext/Include/customLead.php
<?php
/**
* The $objectList array, maps the module name to the Vardef property
* By default only a few core modules have this defined, since their Class/Object names differs from their Vardef Property
**/
$objectList['Leads'] = 'Lead';
// $beanList maps the Bean/Module name to the Class name
$beanList['Leads'] = 'CustomLead';
// $beanFiles maps the Class name to the PHP Class file
$beanFiles['CustomLead'] = 'custom/modules/Leads/CustomLead.php';
Note:Â The $objectListÂ array only needs to be set on those modules that do not have it set already. You can view ./include/modules.php to see the core modules that have it defined already.
Once the registration file is in place, go to Admin > Repairs, and run a Quick Repair and Rebuild so that the system starts using the custom class.
Things to Note
Overriding a core SugarBean class comes with some caveats: