Implementing Custom SugarBean Templates
Overview
This 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.
Creating a Custom SugarBean Template
The following example will create a custom Bean template that can be used by custom modules to shrink down the overall size of the Bean model, by only implementing the two fields required by all modules, the id
field and the deleted
field. In order to do this, we will have to override some core SugarBean methods, as the base SugarBean is statically configured to do things like Save/Delete the model with the date_entered, date_modified, modified_user_id and created_user_id fields. In order to accommodate custom Beans that wish to use those fields, this template will also have properties for configuring those types of fields so that they are populated using the default SugarBean logic.
To create a template for use by custom modules, you create a class that extends from SugarBean in ./custom/include/SugarObjects/templates/<template_name>/<class_name>.php.
For this example, we will create the "bare" template with the following file:
./custom/include/SugarObjects/templates/bare/BareBean.php
<?php
class BareBean extends SugarBean
{
/**
* The configured modified date field
* @var string|false
*/
protected $_modified_date_field = false;
/**
* The configured modified user field
* @var string|false
*/
protected $_modified_user_field = false;
/**
* The configured created date field
* @var string|false
*/
protected $_created_date_field = false;
/**
* The configured created user field
* @var string|false
*/
protected $_created_user_field = false;
/**
* Get the field name where modified date is stored, if in use by Module
* @return string|false
*/
public function getModifiedDateField()
{
if ($this->_modified_date_field !== FALSE){
$field = $this->_modified_date_field;
if (isset($this->field_defs[$field]) && $this->field_defs[$field]['type'] == 'datetime'){
return $field;
}
}
return FALSE;
}
/**
* Override the stock setModifiedDate method
* - Check that field is in use by this Module
* - If in use, set the configured field to current time
* @inheritdoc
**/
public function setModifiedDate($date = '')
{
global $timedate;
$field = $this->getModifiedDateField();
if ($field !== FALSE){
// This code was duplicated from the stock SugarBean::setModifiedDate
if ($this->update_date_modified || empty($this->$field)) {
// This only needs to be calculated if it is going to be used
if (empty($date)) {
$date = $timedate->nowDb();
}
$this->$field = $date;
}
}
}
/**
* Get the field name where modified user ID is stored, if in use by Module
* @return string|false
**/
public function getModifiedUserField()
{
if ($this->_modified_user_field !== FALSE){
$field = $this->_modified_user_field;
if (isset($this->field_defs[$field]) && $this->field_defs[$field]['type'] == 'assigned_user_name'){
return $field;
}
}
return FALSE;
}
/**
* Override the stock setModifiedUser Method
* - Check that field is in use by this Module
* - If in use, set the configured field to user_id
* @inheritdoc
* @param User|null $user [description]
*/
public function setModifiedUser(User $user = null)
{
global $current_user;
$field = $this->getModifiedUserField();
if ($field !== FALSE){
// If the update date modified by flag is set then carry out this directive
if ($this->update_modified_by) {
// Default the modified user id to the default
$this->$field = 1;
// If a user was not presented, default to the current user
if (empty($user)) {
$user = $current_user;
}
// If the user is set, use it
if (!empty($user)) {
$this->$field = $user->id;
}
}
}
}
/**
* Get the field name where created date is stored, if in use by Module
* @return string|false
*/
public function getCreatedDateField()
{
if ($this->_created_date_field !== FALSE){
$field = $this->_created_date_field;
if (isset($this->field_defs[$field]) && $this->field_defs[$field]['type'] == 'datetime'){
return $field;
}
}
return FALSE;
}
/**
* Get the field name where created user ID is stored, if in use by Module
* @return string|false
*/
public function getCreatedUserField()
{
if ($this->_created_user_field !== FALSE){
$field = $this->_created_user_field;
if (isset($this->field_defs[$field]) && $this->field_defs[$field]['type'] == 'assigned_user_name'){
return $field;
}
}
return FALSE;
}
/**
* Override the stock setCreateData method
* - Code was duplicated from stock, to accommodate not having created date or created user fields
* @inheritdoc
*/
public function setCreateData($isUpdate, User $user = null)
{
if (!$isUpdate) {
global $current_user;
$field = $this->getCreatedDateField();
if ($field !== FALSE){
//Duplicated from SugarBean::setCreateData with modifications for dynamic field name
if (empty($this->$field)) {
$this->$field = $this->getDateModified();
}
if (empty($this->$field)){
global $timedate;
$this->$field = $timedate->nowDb();
}
}
$field = $this->getCreatedUserField();
if ($field !== FALSE){
//Duplicated from SugarBean::setCreateData with modifications for dynamic field name
if ($this->set_created_by == true) {
// created by should always be this user
// unless it was set outside of the bean
if ($user) {
$this->$field = $user->id;
} else {
$this->$field = isset($current_user) ? $current_user->id : "";
}
}
}
if ($this->new_with_id == false) {
$this->id = Sugarcrm\Sugarcrm\Util\Uuid::uuid1();
}
}
}
/**
* Get the Date Modified fields value
* @return mixed|false
*/
public function getDateModified()
{
if ($this->_modified_date_field !== FALSE){
$field = $this->_modified_date_field;
return $this->$field;
}
return FALSE;
}
/**
* Get the Date Created Fields value
* @return mixed|false
*/
public function getDateCreated()
{
if ($this->_created_date_field !== FALSE){
$field = $this->_created_date_field;
return $this->$field;
}
return FALSE;
}
/**
* @inheritdoc
*/
public function mark_deleted($id)
{
$date_modified = $GLOBALS['timedate']->nowDb();
if (isset($_SESSION['show_deleted'])) {
$this->mark_undeleted($id);
} else {
// Ensure that Activity Messages do not occur in the context of a Delete action (e.g. unlink)
// and do so for all nested calls within the Top Level Delete Context
$opflag = static::enterOperation('delete');
$aflag = Activity::isEnabled();
Activity::disable();
// call the custom business logic
$custom_logic_arguments['id'] = $id;
$this->call_custom_logic("before_delete", $custom_logic_arguments);
$this->deleted = 1;
if (isset($this->field_defs['team_id'])) {
if (empty($this->teams)) {
$this->load_relationship('teams');
}
if (!empty($this->teams)) {
$this->teams->removeTeamSetModule();
}
}
$this->mark_relationships_deleted($id);
$updateFields = array('deleted' => 1);
$field = $this->getModifiedDateField();
if ($field !== FALSE){
$this->setModifiedDate();
$updateFields[$field] = $this->$field;
}
$field = $this->getModifiedUserField();
if ($field !== FALSE){
$this->setModifiedUser();
$updateFields[$field] = $this->$field;
}
$this->db->updateParams(
$this->table_name,
$this->field_defs,
$updateFields,
array('id' => $id)
);
if ($this->isFavoritesEnabled()) {
SugarFavorites::markRecordDeletedInFavorites($id, $date_modified);
}
// Take the item off the recently viewed lists
$tracker = BeanFactory::newBean('Trackers');
$tracker->makeInvisibleForAll($id);
SugarRelationship::resaveRelatedBeans();
// call the custom business logic
$this->call_custom_logic("after_delete", $custom_logic_arguments);
if(static::leaveOperation('delete', $opflag) && $aflag) {
Activity::enable();
}
}
}
/**
* @inheritdoc
*/
public function mark_undeleted($id)
{
// call the custom business logic
$custom_logic_arguments['id'] = $id;
$this->call_custom_logic("before_restore", $custom_logic_arguments);
$this->deleted = 0;
$modified_date_field = $this->getModifiedDateField();
if ($modified_date_field !== FALSE){
$this->setModifiedDate();
}
$query = "UPDATE {$this->table_name} SET deleted = ?".(!$modified_date_field?"":",$modified_date_field = ?")." WHERE id = ?";
$conn = $this->db->getConnection();
$params = array($this->deleted);
if ($modified_date_field){
$params[] = $this->$modified_date_field;
}
$params[] = $id;
$conn->executeQuery($query, $params);
// call the custom business logic
$this->call_custom_logic("after_restore", $custom_logic_arguments);
}
}
With the Bean template in place, we can define the default vardefs for the template by creating the following file:
./custom/include/SugarObjects/templates/bare/vardefs.php
<?php
$vardefs = array(
'audited' => false,
'favorites' => false,
'activity_enabled' => false,
'fields' => array(
'id' => array(
'name' => 'id',
'vname' => 'LBL_ID',
'type' => 'id',
'required' => true,
'reportable' => true,
'duplicate_on_record_copy' => 'no',
'comment' => 'Unique identifier',
'mandatory_fetch' => true,
),
'deleted' => array(
'name' => 'deleted',
'vname' => 'LBL_DELETED',
'type' => 'bool',
'default' => '0',
'reportable' => false,
'duplicate_on_record_copy' => 'no',
'comment' => 'Record deletion indicator'
)
),
'indices' => array(
'id' => array(
'name' => 'idx_' . preg_replace('/[^a-z0-9_\-]/i', '', strtolower($module)) . '_pk',
'type' => 'primary',
'fields' => array('id')
),
'deleted' => array(
'name' => 'idx_' . strtolower($table_name) . '_id_del',
'type' => 'index',
'fields' => array('id', 'deleted')
)
),
'duplicate_check' => array(
'enabled' => false
)
);
Note: This vardef template also shrinks the SugarBean template down even further, by defaulting auditing, favorites, and activity stream to false.
Using a Custom Bean Template
With the custom SugarBean template in place, we can now implement the template on a custom module. Typically if you deployed a module from Module Builder, there would be two classes generated by Sugar, "custom_Module_sugar" and "custom_Module". The "_sugar" generated class extends from the Bean template that was selected in Module Builder, and the primary Bean class is what is utilized in the system and where your development would take place. For the above example template that is created, the assumption is that you are writing the Bean class, rather than using the generated classes by Module Builder, or at the very least removing the intermediary "_sugar" class generated by Module Builder and extending from the desired template. If you deployed a module via Module Builder, and are going to switch to a custom template, please note that some of the stock templates contain internal logic for the setup of fields for that template and that would need to be duplicated if you intend to continue using those fields. The stock templates, located in ./include/SugarObjects/templates/
, are available for your reference to copy over any of the required logic to your custom Bean class. With all that being said, let us take a look at a custom Bean class that extends from our custom template.
This example will use the "custom_Module" module which is a module that will be used for Tagging records. Since the module is just storing tags, a slimmed down Bean works well as we only need a "name" field to store those tags. The following class would implement the custom Bean template created above.
./modules/custom_Module/custom_Module.php
<?php
require_once 'custom/include/SugarObjects/templates/bare/BareBean.php';
class custom_Module extends BareBean {
public $new_schema = true;
public $module_dir = 'custom_Module';
public $object_name = 'custom_Module';
public $table_name = 'custom_Module';
public $importable = true;
public $id;
public $name;
public $deleted;
public function __construct(){
parent::__construct();
}
public function bean_implements($interface){
switch($interface){
case 'ACL': return true;
}
return false;
}
}
With the modules SugarBean class created, the other thing that needs to be implemented is the vardefs.php file:
./modules/custom_Module/vardefs.php
<?php
$module = 'custom_Module';
$table_name = strtolower($module);
$dictionary[$module] = array(
'table' => $table_name,
'fields' => array(
'name' => array(
'name' => 'name',
'vname' => 'LBL_NAME',
'type' => 'username',
'link' => true,
'dbType' => 'varchar',
'len' => 255,
'unified_search' => true,
'full_text_search' => array(),
'required' => true,
'importable' => 'required',
'duplicate_merge' => 'enabled',
//'duplicate_merge_dom_value' => '3',
'merge_filter' => 'selected',
'duplicate_on_record_copy' => 'always',
),
),
'relationships' => array (
),
'optimistic_locking' => false,
'unified_search' => false,
);
VardefManager::createVardef(
$module,
$module,
//Specify the bare template to be used to create the vardefs
array('bare')
);
With these files implemented, the "custom_Module" module would implement the "bare" template we created.
Creating Custom Vardef Templates
For some customizations, you might not need a SugarBean template as you may not be implementing logic that needs to be shared across multiple module's Bean classes. However, you may have field definitions that are common across multiple modules that would be beneficial for implementing as Vardef Templates. To create a vardef template, a file as follows, ./custom/include/SugarObjects/implements/<template_name>/vardefs.php
.
Continuing on with our example custom_Module module from above, we might want to have a creation date on this custom tags module since our 'bare' SugarBean template does not come with one by default. We could easily just add one in the modules vardef file, but for our example purposes, we know that we will use our 'bare' SugarBean template on other customizations, and on some of those we might also want to include a creation date. To implement the vardef template for the creation date field, we create the following:
./custom/include/SugarObjects/implements/date_entered/vardefs.php
<?php
$vardefs = array(
'fields' => array(
'date_entered' => array(
'name' => 'date_entered',
'vname' => 'LBL_DATE_ENTERED',
'type' => 'datetime',
'group' => 'created_by_name',
'comment' => 'Date record created',
'enable_range_search' => true,
'options' => 'date_range_search_dom',
'studio' => array(
'portaleditview' => false,
),
'duplicate_on_record_copy' => 'no',
'readonly' => true,
'massupdate' => false,
'full_text_search' => array(
'enabled' => true,
'searchable' => false
),
)
),
'indices' => array(
'date_entered' => array(
'name' => 'idx_' . strtolower($table_name) . '_date_entered',
'type' => 'index',
'fields' => array('date_entered')
)
)
);
Using a Custom Vardef Template
Once the vardef template is in place, you can use the template by adding it to the 'uses' array property of your module vardefs. Continuing with our example module custom_Module, we can update the vardefs file as follows:
./modules/custom_Module/vardefs.php
<?php
$module = 'custom_Module';
$table_name = strtolower($module);
$dictionary[$module] = array(
'table' => $table_name,
'fields' => array(
'name' => array(
'name' => 'name',
'vname' => 'LBL_NAME',
'type' => 'username',
'link' => true,
'dbType' => 'varchar',
'len' => 255,
'unified_search' => true,
'full_text_search' => array(),
'required' => true,
'importable' => 'required',
'duplicate_merge' => 'enabled',
//'duplicate_merge_dom_value' => '3',
'merge_filter' => 'selected',
'duplicate_on_record_copy' => 'always',
),
),
'relationships' => array (
),
//Add the desired vardef templates to this list
'uses' => array(
'date_entered'
),
'optimistic_locking' => false,
'unified_search' => false,
);
VardefManager::createVardef(
$module,
$module,
//Specify the bare template to be used to create the vardefs
array('bare')
);
After a Quick Repair and Rebuild, a SQL statement should be generated to update the table of the module with the new date_entered field that was added to the vardefs using the vardef template.