Permalink
Find file Copy path
executable file 6294 lines (5771 sloc) 250 KB
<?php
/**
*
* SugarCRM Community Edition is a customer relationship management program developed by
* SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
*
* SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd.
* Copyright (C) 2011 - 2018 SalesAgility Ltd.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by the
* Free Software Foundation with the addition of the following permission added
* to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
* IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
* OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along with
* this program; if not, see http://www.gnu.org/licenses or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
* You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
* SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "Powered by
* SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not
* reasonably feasible for technical reasons, the Appropriate Legal Notices must
* display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM".
*/
if (!defined('sugarEntry') || !sugarEntry) {
die('Not A Valid Entry Point');
}
require_once 'modules/DynamicFields/DynamicField.php';
require_once "data/Relationships/RelationshipFactory.php";
/**
* SugarBean is the base class for all business objects in Sugar. It implements
* the primary functionality needed for manipulating business objects: create,
* retrieve, update, delete. It allows for searching and retrieving list of records.
* It allows for retrieving related objects (e.g. contacts related to a specific account).
*
* In the current implementation, there can only be one bean per folder.
* Naming convention has the bean name be the same as the module and folder name.
* All bean names should be singular (e.g. Contact). The primary table name for
* a bean should be plural (e.g. contacts).
* @api
*/
class SugarBean
{
/**
* Blowfish encryption key
* @var string $field_key
*/
protected static $field_key;
/**
* Cache of fields which can contain files
*
* @var array $fileFields
*/
protected static $fileFields = array();
/**
* A pointer to the database object
*
* @var DBManager $db
*/
public $db;
/**
* Unique object identifier
*
* @var string $id
*/
public $id;
/**
* When creating a bean, you can specify a value in the id column as
* long as that value is unique. During save, if the system finds an
* id, it assumes it is an update. Setting new_with_id to true will
* make sure the system performs an insert instead of an update.
*
* @var boolean $new_with_id -- default false
*/
public $new_with_id = false;
/**
* Disable vardefs. This should be set to true only for beans that do not have vardefs. Tracker is an example
*
* @var boolean $disable_vardefs -- default false
*/
public $disable_vardefs = false;
/**
* holds the full name of the user that an item is assigned to. Only used if notifications
* are turned on and going to be sent out.
*
* @var string $new_assigned_user_name
*/
public $new_assigned_user_name;
/**
* An array of bool. This array is cleared out when data is loaded.
* As date/times are converted, a "1" is placed under the key, the field is converted.
*
* @var array $processed_dates_times array of bool
*/
public $processed_dates_times = array();
/**
* Whether to process date/time fields for storage in the database in GMT
*
* @var boolean $process_save_dates
*/
public $process_save_dates = true;
/**
* This signals to the bean that it is being saved in a mass mode.
* Examples of this kind of save are import and mass update.
* We turn off notifications of this is the case to make things more efficient.
*
* @var boolean $save_from_post
*/
public $save_from_post = true;
/**
* When running a query on related items using the method: retrieve_by_string_fields
* this value will be set to true if more than one item matches the search criteria.
*
* @var boolean $duplicates_found
*/
public $duplicates_found = false;
/**
* true if this bean has been deleted, false otherwise.
*
* @var integer $deleted - 0 === false, 1 === true
*/
public $deleted = 0;
/**
* Should the date modified column of the bean be updated during save?
* This is used for admin level functionality that should not be updating
* the date modified. This is only used by sync to allow for updates to be
* replicated in a way that will not cause them to be replicated back.
*
* @var boolean $update_date_modified
*/
public $update_date_modified = true;
/**
* Should the modified by column of the bean be updated during save?
* This is used for admin level functionality that should not be updating
* the modified by column. This is only used by sync to allow for updates to be
* replicated in a way that will not cause them to be replicated back.
*
* @var boolean $update_modified_by
*/
public $update_modified_by = true;
/**
* Setting this to true allows for updates to overwrite the date_entered
*
* @var boolean $update_date_entered
*/
public $update_date_entered = false;
/**
* This allows for seed data to be created without using the current user to set the id.
* This should be replaced by altering the current user before the call to save.
*
* @var boolean $set_created_by
*/
public $set_created_by = true;
/**
* The database table where records of this Bean are stored.
*
* @var string $table_name
*/
public $table_name = '';
/**
* This is the singular name of the bean. (i.e. Contact).
*
* @var string $object_name
*/
public $object_name = '';
/** Set this to true if you query contains a sub-select and bean is converting both select statements
* into count queries.
* @var boolean $ungreedy_count
*/
public $ungreedy_count = false;
/**
* The name of the module folder for this type of bean.
*
* @var string $module_dir
*/
public $module_dir = '';
/**
* @var string $module_name
*/
public $module_name = '';
/**
* @var array $field_name_map
*/
public $field_name_map;
/**
* @var array $field_defs
*/
public $field_defs;
/**
* @var DynamicField $custom_fields
*/
public $custom_fields;
/**
* @var array $column_fields
*/
public $column_fields = array();
/**
* @var array $list_fields
*/
public $list_fields = array();
/**
* @var array $additional_column_fields
*/
public $additional_column_fields = array();
/**
* @var array $relationship_fields
*/
public $relationship_fields = array();
/**
* @var bool $current_notify_user
*/
public $current_notify_user;
/**
* @var bool|array $fetched_row
*/
public $fetched_row = false;
/**
* @var array $fetched_rel_row
*/
public $fetched_rel_row = array();
/**
* @var array $layout_def
*/
public $layout_def;
/**
* @var bool $force_load_details
*/
public $force_load_details = false;
/**
* @var bool $optimistic_lock
*/
public $optimistic_lock = false;
/**
* @var bool $disable_custom_fields
*/
public $disable_custom_fields = false;
/**
* @var bool $number_formatting_done
*/
public $number_formatting_done = false;
/**
* @var bool $process_field_encrypted
*/
public $process_field_encrypted = false;
/**
* @var string $acltype
*/
public $acltype = 'module';
/**
* @var array $additional_meta_fields
*/
public $additional_meta_fields = array();
/**
* @var bool $notify_inworkflow
*/
public $notify_inworkflow;
/**
* @var string $name
*/
public $name;
/**
* @var string $description
*/
public $description;
/**
* @var string $data_entered
*/
public $date_entered;
/**
* @var string $date_modified
*/
public $date_modified;
/**
* @var string $modified_user_id
*/
public $modified_user_id;
/**
* @var string $assigned_user_id
*/
public $assigned_user_id;
/**
* @var string $created_by
*/
public $created_by;
/**
* @var string $created_by_name
*/
public $created_by_name;
/**
* @var string $modified_by_name
*/
public $modified_by_name;
/**
* @var boolean $importable Set to true in the child beans if the module supports importing
*/
public $importable = false;
/**
* @var boolean $special_notification Set to true in the child beans if the module use the
* special notification template
*/
public $special_notification = false;
/**
* @var boolean $in_workflow Set to true if the bean is being dealt with in a workflow
*/
public $in_workflow = false;
/**
*
* @var boolean $tracker_visibility By default it will be true but if any module is to be kept non visible
* to tracker, then its value needs to be overridden in that particular module to false.
*
*/
public $tracker_visibility = true;
/**
* @var array $listview_inner_join Used to pass inner join string to ListView Data.
*/
public $listview_inner_join = array();
/**
* @var boolean $in_import Set to true in <modules>/Import/views/view.step4.php if a module is being imported
*/
public $in_import = false;
/**
* @var boolean $in_save
*/
public $in_save;
/**
* @var integer $logicHookDepth
*/
public $logicHookDepth;
/**
* @var int $max_logic_depth How deep logic hooks can go
*/
protected $max_logic_depth = 10;
/**
* A way to keep track of the loaded relationships so when we clone the object we can unset them.
*
* @var array
*/
protected $loaded_relationships = array();
/**
* @var boolean $is_updated_dependent_fields set to true if dependent fields updated
*/
protected $is_updated_dependent_fields = false;
/**
* Returns the ACL category for this module; defaults to the SugarBean::$acl_category if defined
* otherwise it is SugarBean::$module_dir
*
* @var null|string
*/
public $acl_category;
/**
* @var string $old_modified_by_name
*/
public $old_modified_by_name;
/**
* SugarBean constructor.
* Performs following tasks:
*
* 1. Initialized a database connections
* 2. Load the vardefs for the module implementing the class. cache the entries
* if needed
* 3. Setup row-level security preference
* All implementing classes must call this constructor using the parent::SugarBean() class.
*
*/
public function __construct()
{
global $dictionary;
static $loaded_definitions = array();
$this->db = DBManagerFactory::getInstance();
if (empty($this->module_name)) {
$this->module_name = $this->module_dir;
}
if ((!$this->disable_vardefs && empty($loaded_definitions[$this->object_name]))
|| !empty($GLOBALS['reload_vardefs'])) {
VardefManager::loadVardef($this->module_dir, $this->object_name);
// build $this->column_fields from the field_defs if they exist
if (!empty($dictionary[$this->object_name]['fields'])) {
foreach ($dictionary[$this->object_name]['fields'] as $key => $value_array) {
$column_fields[] = $key;
if (!empty($value_array['required']) && !empty($value_array['name'])) {
$this->required_fields[$value_array['name']] = 1;
}
}
$this->column_fields = $column_fields;
}
//setup custom fields
if (!isset($this->custom_fields) &&
empty($this->disable_custom_fields)
) {
$this->setupCustomFields($this->module_dir);
}
if (isset($GLOBALS['dictionary'][$this->object_name]) && !$this->disable_vardefs) {
$this->field_name_map = isset($dictionary[$this->object_name]['fields']) ? $dictionary[$this->object_name]['fields'] : null;
if (!isset($dictionary[$this->object_name]['fields'])) {
LoggerManager::getLogger()->warn('SugarBean constructor error: Object has not fields in dictionary. Object name was: ' . $this->object_name);
$this->field_defs = null;
} else {
$this->field_defs = $dictionary[$this->object_name]['fields'];
}
if (!empty($dictionary[$this->object_name]['optimistic_locking'])) {
$this->optimistic_lock = true;
}
}
$loaded_definitions[$this->object_name]['column_fields'] =& $this->column_fields;
$loaded_definitions[$this->object_name]['list_fields'] =& $this->list_fields;
$loaded_definitions[$this->object_name]['required_fields'] =& $this->required_fields;
$loaded_definitions[$this->object_name]['field_name_map'] =& $this->field_name_map;
$loaded_definitions[$this->object_name]['field_defs'] =& $this->field_defs;
} else {
$this->column_fields =& $loaded_definitions[$this->object_name]['column_fields'];
$this->list_fields =& $loaded_definitions[$this->object_name]['list_fields'];
$this->required_fields =& $loaded_definitions[$this->object_name]['required_fields'];
$this->field_name_map =& $loaded_definitions[$this->object_name]['field_name_map'];
$this->field_defs =& $loaded_definitions[$this->object_name]['field_defs'];
$this->added_custom_field_defs = true;
if (!isset($this->custom_fields) &&
empty($this->disable_custom_fields)
) {
$this->setupCustomFields($this->module_dir);
}
if (!empty($dictionary[$this->object_name]['optimistic_locking'])) {
$this->optimistic_lock = true;
}
}
if ($this->bean_implements('ACL') && !empty($GLOBALS['current_user'])) {
$this->acl_fields = !(isset($dictionary[$this->object_name]['acl_fields'])
&& $dictionary[$this->object_name]['acl_fields'] === false);
}
$this->populateDefaultValues();
}
/**
* @deprecated deprecated since version 7.6, PHP4 Style Constructors are deprecated and will be remove in 7.8,
* please update your code, use __construct instead
* @see SugarBean::__construct
*/
public function SugarBean()
{
$deprecatedMessage = 'PHP4 Style Constructors are deprecated and will be remove in 7.8, ' .
'please update your code';
if (isset($GLOBALS['log'])) {
$GLOBALS['log']->deprecated($deprecatedMessage);
} else {
trigger_error($deprecatedMessage, E_USER_DEPRECATED);
}
self::__construct();
}
/**
* Loads the definition of custom fields defined for the module.
* Local file system cache is created as needed.
*
* @param string $module_name setting up custom fields for this module.
*/
public function setupCustomFields($module_name)
{
$this->custom_fields = new DynamicField($module_name);
$this->custom_fields->setup($this);
}
/**
* @param $interface
*
* @return bool
*/
public function bean_implements($interface)
{
return false;
}
/**
* @param bool $force
*/
public function populateDefaultValues($force = false)
{
if (!is_array($this->field_defs)) {
$GLOBALS['log']->fatal('SugarBean::populateDefaultValues $field_defs should be an array');
return;
}
foreach ($this->field_defs as $field => $value) {
if ((isset($value['default']) || !empty($value['display_default'])) && ($force || empty($this->$field))) {
if (!isset($value['type'])) {
$GLOBALS['log']->warn('Undefined index: type');
$type = null;
} else {
$type = $value['type'];
}
switch ($type) {
case 'date':
if (!empty($value['display_default'])) {
$this->$field = $this->parseDateDefault($value['display_default']);
}
break;
case 'datetime':
case 'datetimecombo':
if (!empty($value['display_default'])) {
$this->$field = $this->parseDateDefault($value['display_default'], true);
}
break;
case 'multienum':
if (empty($value['default']) && !empty($value['display_default'])) {
$this->$field = $value['display_default'];
} else {
$this->$field = $value['default'];
}
break;
case 'bool':
if (isset($this->$field)) {
break;
}
default:
if (isset($value['default']) && $value['default'] !== '') {
$this->$field = htmlentities($value['default'], ENT_QUOTES, 'UTF-8');
} else {
$this->$field = '';
}
} //switch
}
// refact info:
// may we should htmlentities on field:
// $this->field = htmlentities($this->$field, ENT_QUOTES, 'UTF-8');
} //foreach
}
/**
* Create date string from default value
* like '+1 month'
* @param string $value
* @param bool $time Should be expect time set too?
* @return string
*
* @throws \Exception
*/
protected function parseDateDefault($value, $time = false)
{
global $timedate;
if ($time) {
$dtAry = explode('&', $value, 2);
$now = $timedate->getNow(true);
$dateValue = $now->modify($dtAry[0]);
if ($dateValue === false) {
$GLOBALS['log']->fatal('Invalid modifier for DateTime::modify(): ' . $dtAry[0]);
}
if (!empty($dtAry[1])) {
$timeValue = $timedate->fromString($dtAry[1]);
$dateValue->setTime($timeValue->hour, $timeValue->min, $timeValue->sec);
}
if (is_bool($dateValue)) {
$GLOBALS['log']->fatal('Type Error: Argument 1 passed to TimeDate::asUser() ' .
'must be an instance of DateTime, boolean given');
return false;
}
return $timedate->asUser($dateValue);
} else {
$now = $timedate->getNow(true);
try {
$results = $now->modify($value);
} catch (Exception $e) {
$GLOBALS['log']->fatal('DateTime error: ' . $e->getMessage());
}
if (is_bool($results)) {
$GLOBALS['log']->fatal('Type Error: Argument 1 passed to TimeDate::asUser() ' .
'must be an instance of DateTime, boolean given');
return false;
}
return $timedate->asUserDate($results);
}
}
/**
* Removes relationship metadata cache.
*
* Every module that has relationships defined with other modules, has this meta data cached. The cache is
* stores in 2 locations: relationships table and file system. This method clears the cache from both locations.
*
* @param string $key module whose meta cache is to be cleared.
* @param string $db database handle.
* @param string $tablename table name
* @param array $dictionary vardef for the module
* @param string $module_dir name of subdirectory where module is installed.
*
* @static
*
* Internal function, do not override.
*/
public static function removeRelationshipMeta($key, $db, $tablename, $dictionary, $module_dir)
{
//load the module dictionary if not supplied.
if ((!isset($dictionary) || empty($dictionary)) && !empty($module_dir)) {
$filename = 'modules/' . $module_dir . '/vardefs.php';
if (file_exists($filename)) {
include($filename);
}
}
if (!is_array($dictionary) || !array_key_exists($key, $dictionary)) {
$GLOBALS['log']->fatal("removeRelationshipMeta: Metadata for table " . $tablename . " does not exist");
display_notice("meta data absent for table " . $tablename . " keyed to $key ");
} else {
if (isset($dictionary[$key]['relationships'])) {
$RelationshipDefs = $dictionary[$key]['relationships'];
if (!is_array($RelationshipDefs)) {
$GLOBALS['log']->fatal('Relationship definitions should be an array');
$RelationshipDefs = (array)$RelationshipDefs;
}
foreach ($RelationshipDefs as $rel_name) {
Relationship::delete($rel_name, $db);
}
}
}
}
/**
* Populates the relationship meta for a module.
*
* It is called during setup/install. It is used statically to create relationship meta data
* for many-to-many tables.
*
* @param string $key name of the object.
* @param object $db database handle.
* @param string $tablename table, meta data is being populated for.
* @param array $dictionary vardef dictionary for the object. *
* @param string $module_dir name of subdirectory where module is installed.
* @param bool $is_custom Optional,set to true if module is installed in a custom directory. Default value is false.
* @static
*
* Internal function, do not override.
*/
public static function createRelationshipMeta($key, $db, $tablename, array $dictionary, $module_dir, $is_custom = false)
{
//load the module dictionary if not supplied.
if (empty($dictionary) && !empty($module_dir)) {
if ($is_custom) {
$filename = 'custom/modules/' . $module_dir . '/Ext/Vardefs/vardefs.ext.php';
} else {
if ($key == 'User') {
// a very special case for the Employees module
// this must be done because the Employees/vardefs.php does an include_once on
// Users/vardefs.php
$filename = 'modules/Users/vardefs.php';
} else {
$filename = 'modules/' . $module_dir . '/vardefs.php';
}
}
if (file_exists($filename)) {
include($filename);
// cn: bug 7679 - dictionary entries defined as $GLOBALS['name'] not found
if (empty($dictionary) || !empty($GLOBALS['dictionary'][$key])) {
$dictionary = $GLOBALS['dictionary'];
}
} else {
$GLOBALS['log']->debug('createRelationshipMeta: no metadata file found' . $filename);
return;
}
}
if (!is_array($dictionary) || !array_key_exists($key, $dictionary)) {
$GLOBALS['log']->fatal("createRelationshipMeta: Metadata for table " . $tablename . " does not exist");
display_notice("meta data absent for table " . $tablename . " keyed to $key ");
} else {
if (isset($dictionary[$key]['relationships'])) {
$RelationshipDefs = $dictionary[$key]['relationships'];
global $beanList;
$beanList_ucase = array_change_key_case($beanList, CASE_UPPER);
foreach ($RelationshipDefs as $rel_name => $rel_def) {
if (isset($rel_def['lhs_module']) && !isset($beanList_ucase[strtoupper($rel_def['lhs_module'])])) {
$GLOBALS['log']->debug('skipping orphaned relationship record ' .
$rel_name . ' lhs module is missing ' . $rel_def['lhs_module']);
continue;
}
if (isset($rel_def['rhs_module']) && !isset($beanList_ucase[strtoupper($rel_def['rhs_module'])])) {
$GLOBALS['log']->debug('skipping orphaned relationship record ' .
$rel_name . ' rhs module is missing ' . $rel_def['rhs_module']);
continue;
}
//check whether relationship exists or not first.
if (Relationship::exists($rel_name, $db)) {
$GLOBALS['log']->debug('Skipping, relationship already exists ' . $rel_name);
} else {
$seed = BeanFactory::getBean("Relationships");
$keys = array_keys($seed->field_defs);
$toInsert = array();
foreach ($keys as $key) {
if ($key == "id") {
$toInsert[$key] = create_guid();
} elseif ($key == "relationship_name") {
$toInsert[$key] = $rel_name;
} elseif (isset($rel_def[$key])) {
$toInsert[$key] = $rel_def[$key];
}
//todo specify defaults if meta not defined.
}
$column_list = implode(",", array_keys($toInsert));
$value_list = "'" . implode("','", array_values($toInsert)) . "'";
//create the record. todo add error check.
$insert_string = "INSERT into relationships (" . $column_list . ") " .
"values (" . $value_list . ")";
if ($db instanceof DBManager) {
$db->query($insert_string, true);
} else {
$GLOBALS['log']->fatal('Invalid Argument: Argument 2 should be a DBManager');
}
}
}
} else {
$GLOBALS['log']->info('No relationships meta set for '.$module_dir);
}
}
}
/**
* Constructs a query to fetch data for subpanels and list views
*
* It constructs union queries for activities subpanel.
*
* @param SugarBean $parentbean constructing queries for link attributes in this bean
* @param string $order_by Optional, order by clause
* @param string $sort_order Optional, sort order
* @param string $where Optional, additional where clause
* @param int $row_offset
* @param int $limit
* @param int $max
* @param int $show_deleted
* @param aSubPanel $subpanel_def
*
* @return array
*
* Internal Function, do not override.
*/
public static function get_union_related_list(
$parentbean,
$order_by = '',
$sort_order = '',
$where = '',
$row_offset = 0,
$limit = -1,
$max = -1,
$show_deleted = 0,
$subpanel_def= null
)
{
if (is_null($subpanel_def)) {
$GLOBALS['log']->fatal('subpanel_def is null');
}
$secondary_queries = array();
global $layout_edit_mode;
$final_query = '';
$final_query_rows = '';
$subpanel_list = array();
if (method_exists($subpanel_def, 'isCollection')) {
if ($subpanel_def->isCollection()) {
if ($subpanel_def->load_sub_subpanels() === false) {
$subpanel_list = array();
} else {
$subpanel_list = $subpanel_def->sub_subpanels;
}
} else {
$subpanel_list[] = $subpanel_def;
}
} else {
$GLOBALS['log']->fatal('Subpanel definition should be an aSubPanel');
}
$first = true;
//Breaking the building process into two loops. The first loop gets a list of all the sub-queries.
//The second loop merges the queries and forces them to select the same number of columns
//All columns in a sub-subpanel group must have the same aliases
//If the subpanel is a datasource function, it can't be a collection
// so we just poll that function for the and return that
foreach ($subpanel_list as $this_subpanel) {
if ($this_subpanel->isDatasourceFunction()
&& empty($this_subpanel->_instance_properties['generate_select'])) {
$shortcut_function_name = $this_subpanel->get_data_source_name();
$parameters = $this_subpanel->get_function_parameters();
if (!empty($parameters)) {
//if the import file function is set, then import the file to call the custom function from
if (is_array($parameters) && isset($parameters['import_function_file'])) {
//this call may happen multiple times, so only require if function does not exist
if (!function_exists($shortcut_function_name)) {
require_once($parameters['import_function_file']);
}
//call function from required file
$tmp_final_query = $shortcut_function_name($parameters);
} else {
//call function from parent bean
$tmp_final_query = $parentbean->$shortcut_function_name($parameters);
}
} else {
$tmp_final_query = $parentbean->$shortcut_function_name();
}
if (!$first) {
$final_query_rows .= ' UNION ALL ( '
. $parentbean->create_list_count_query($tmp_final_query, $parameters) . ' )';
$final_query .= ' UNION ALL ( ' . $tmp_final_query . ' )';
} else {
$final_query_rows = '(' . $parentbean->create_list_count_query($tmp_final_query, $parameters) . ')';
$final_query = '(' . $tmp_final_query . ')';
$first = false;
}
}
}
//If final_query is still empty, its time to build the sub-queries
if (empty($final_query)) {
$subqueries = SugarBean::build_sub_queries_for_union($subpanel_list, $subpanel_def, $parentbean, $order_by);
$all_fields = array();
foreach ($subqueries as $i => $subquery) {
$query_fields = DBManagerFactory::getInstance()->getSelectFieldsFromQuery($subquery['select']);
foreach ($query_fields as $field => $select) {
if (!in_array($field, $all_fields)) {
$all_fields[] = $field;
}
}
$subqueries[$i]['query_fields'] = $query_fields;
}
$first = true;
//Now ensure the queries have the same set of fields in the same order.
foreach ($subqueries as $subquery) {
$subquery['select'] = "SELECT";
foreach ($all_fields as $field) {
if (!isset($subquery['query_fields'][$field])) {
$subquery['select'] .= " NULL $field,";
} else {
$subquery['select'] .= " {$subquery['query_fields'][$field]},";
}
}
$subquery['select'] = substr($subquery['select'], 0, strlen($subquery['select']) - 1);
// Find related email address for sub panel ordering
if ($order_by && isset($subpanel_def->panel_definition['list_fields'][$order_by]['widget_class']) &&
$subpanel_def->panel_definition['list_fields'][$order_by]['widget_class'] == 'SubPanelEmailLink' &&
!in_array($order_by, array_keys($subquery['query_fields']))) {
$relatedBeanTable = $subpanel_def->table_name;
$relatedBeanModule = $subpanel_def->get_module_name();
$subquery['select'] .= ",
(SELECT email_addresses.email_address
FROM email_addr_bean_rel
JOIN email_addresses ON email_addresses.id = email_addr_bean_rel.email_address_id
WHERE
email_addr_bean_rel.primary_address = 1 AND
email_addr_bean_rel.deleted = 0 AND
email_addr_bean_rel.bean_id = $relatedBeanTable.id AND
email_addr_bean_rel.bean_module = '$relatedBeanModule') as $order_by";
}
//Put the query into the final_query
$query = $subquery['select'] . " " . $subquery['from'] . " " . $subquery['where'];
if (!$first) {
$query = ' UNION ALL ( ' . $query . ' )';
$final_query_rows .= " UNION ALL ";
} else {
$query = '(' . $query . ')';
$first = false;
}
$query_array = $subquery['query_array'];
$select_position = strpos($query_array['select'], "SELECT");
$distinct_position = strpos($query_array['select'], "DISTINCT");
if (!empty($subquery['params']['distinct']) && !empty($subpanel_def->table_name)) {
$query_rows = "( SELECT count(DISTINCT " . $subpanel_def->table_name . ".id)"
. $subquery['from_min'] . $query_array['join'] . $subquery['where'] . ' )';
} elseif ($select_position !== false && $distinct_position !== false) {
$replacement = substr_replace($query_array['select'], "SELECT count(", $select_position, 6);
$query_rows = "( " . $replacement . ")" . $subquery['from_min']
. $query_array['join'] . $subquery['where'] . ' )';
} else {
//resort to default behavior.
$query_rows = "( SELECT count(*)" . $subquery['from_min']
. $query_array['join'] . $subquery['where'] . ' )';
}
if (!empty($subquery['secondary_select'])) {
$subquerystring = $subquery['secondary_select'] . $subquery['secondary_from']
. $query_array['join'] . $subquery['where'];
if (!empty($subquery['secondary_where'])) {
if (empty($subquery['where'])) {
$subquerystring .= " WHERE " . $subquery['secondary_where'];
} else {
$subquerystring .= " AND " . $subquery['secondary_where'];
}
}
$secondary_queries[] = $subquerystring;
}
$final_query .= $query;
$final_query_rows .= $query_rows;
}
}
if (!empty($order_by)) {
$isCollection = $subpanel_def->isCollection();
if ($isCollection) {
/** @var aSubPanel $header */
$header = $subpanel_def->get_header_panel_def();
$submodule = $header->template_instance;
$suppress_table_name = true;
} else {
$submodule = $subpanel_def->template_instance;
$suppress_table_name = false;
}
if (!empty($sort_order)) {
$order_by .= ' ' . $sort_order;
}
$order_by = $parentbean->process_order_by($order_by, $submodule, $suppress_table_name);
if (!empty($order_by)) {
$final_query .= ' ORDER BY ' . $order_by;
}
}
if (isset($layout_edit_mode) && $layout_edit_mode) {
$response = array();
if (!empty($submodule)) {
$submodule->assign_display_fields($submodule->module_dir);
$response['list'] = array($submodule);
} else {
$response['list'] = array();
}
$response['parent_data'] = array();
$response['row_count'] = 1;
$response['next_offset'] = 0;
$response['previous_offset'] = 0;
return $response;
}
if (method_exists($parentbean, 'process_union_list_query')) {
return $parentbean->process_union_list_query(
$parentbean,
$final_query,
$row_offset,
$limit,
$max,
'',
$subpanel_def,
$final_query_rows,
$secondary_queries
);
} else {
$GLOBALS['log']->fatal('Parent bean should be a SugarBean');
return null;
}
}
/**
* @param $subpanel_list
* @param $subpanel_def
* @param $parentbean
* @param $order_by
*
* @return array
*/
protected static function build_sub_queries_for_union($subpanel_list, $subpanel_def, $parentbean, $order_by)
{
global $beanList;
$subqueries = array();
if (!is_array($subpanel_list) or is_object($subpanel_list)) {
$GLOBALS['log']->fatal('Invalid Argument: Subpanel list should be an array.');
$subpanel_list = (array) $subpanel_list;
}
foreach ($subpanel_list as $this_subpanel) {
if (
method_exists($this_subpanel, 'isDatasourceFunction')
) {
if (!$this_subpanel->isDatasourceFunction() || ($this_subpanel->isDatasourceFunction()
&& isset($this_subpanel->_instance_properties['generate_select'])
&& $this_subpanel->_instance_properties['generate_select'])
) {
//the custom query function must return an array with
if ($this_subpanel->isDatasourceFunction()) {
$shortcut_function_name = $this_subpanel->get_data_source_name();
$parameters = $this_subpanel->get_function_parameters();
if (!empty($parameters)) {
//if the import file function is set, then import the file to call the custom function from
if (is_array($parameters) && isset($parameters['import_function_file'])) {
//this call may happen multiple times, so only require if function does not exist
if (!function_exists($shortcut_function_name)) {
require_once($parameters['import_function_file']);
}
//call function from required file
$query_array = $shortcut_function_name($parameters);
} else {
//call function from parent bean
$query_array = $parentbean->$shortcut_function_name($parameters);
}
} else {
$query_array = $parentbean->$shortcut_function_name();
}
} else {
$related_field_name = $this_subpanel->get_data_source_name();
if (!method_exists($parentbean, 'load_relationship')) {
$GLOBALS['log']->fatal('Fatal error: Call to a member function load_relationship() ' .
'on an invalid object');
} else {
if (!$parentbean->load_relationship($related_field_name)) {
if (isset($parentbean->$related_field_name)) {
unset($parentbean->$related_field_name);
}
continue;
}
$query_array = $parentbean->$related_field_name->getSubpanelQuery(array(), true);
}
}
$table_where = preg_replace('/^\s*WHERE/i', '', $this_subpanel->get_where());
$queryArrayWhere = '';
if (isset($query_array)) {
$queryArrayWhere = $query_array['where'];
} else {
$GLOBALS['log']->fatal('Undefined variable: query_array');
}
$where_definition = preg_replace('/^\s*WHERE/i', '', $queryArrayWhere);
if (!empty($table_where)) {
if (empty($where_definition)) {
$where_definition = $table_where;
} else {
$where_definition .= ' AND ' . $table_where;
}
}
if (isset($this_subpanel->_instance_properties['module'])) {
$submodulename = $this_subpanel->_instance_properties['module'];
} else {
$GLOBALS['log']->fatal('Undefined index: module');
$submodulename = '';
}
if (isset($beanList[$submodulename])) {
$submoduleclass = $beanList[$submodulename];
} else {
$GLOBALS['log']->fatal('Undefined index: ' . $submodulename);
$submoduleclass = null;
}
/** @var SugarBean $submodule */
if (class_exists($submoduleclass)) {
$submodule = new $submoduleclass();
} else {
$GLOBALS['log']->fatal('Class name must be a valid object or a string');
$submodule = null;
}
$subwhere = $where_definition;
$list_fields = $this_subpanel->get_list_fields();
foreach ($list_fields as $list_key => $list_field) {
if (isset($list_field['usage']) && $list_field['usage'] == 'display_only') {
unset($list_fields[$list_key]);
}
}
if (!method_exists($subpanel_def, 'isCollection')) {
$GLOBALS['log']->fatal('Call to a member function isCollection() on an invalid object');
}
if (
method_exists($subpanel_def, 'isCollection') &&
!$subpanel_def->isCollection() &&
isset($list_fields[$order_by]) &&
isset($submodule->field_defs[$order_by]) &&
(!isset($submodule->field_defs[$order_by]['source'])
|| $submodule->field_defs[$order_by]['source'] == 'db')
) {
$order_by = $submodule->table_name . '.' . $order_by;
}
$panel_name = $this_subpanel->name;
$params = array();
$params['distinct'] = $this_subpanel->distinct_query();
$params['joined_tables'] = isset($query_array['join_tables']) ? $query_array['join_tables'] : null;
$params['include_custom_fields'] = method_exists($subpanel_def, 'isCollection')
? !$subpanel_def->isCollection() : null;
$params['collection_list'] = method_exists($subpanel_def, 'get_inst_prop_value')
? $subpanel_def->get_inst_prop_value('collection_list') : null;
// use single select in case when sorting by relate field
$singleSelect = method_exists($submodule, 'is_relate_field')
? $submodule->is_relate_field($order_by) : null;
$subquery = method_exists($submodule, 'create_new_list_query')
? $submodule->create_new_list_query(
'',
$subwhere,
$list_fields,
$params,
0,
'',
true,
$parentbean,
$singleSelect
) : null;
if (isset($subquery['select'])) {
$subquery['select'] .= " , '$panel_name' panel_name ";
} else {
$subquery['select'] = " , '$panel_name' panel_name ";
}
if (isset($query_array)) {
$subquery['from'] .= $query_array['join'];
$subquery['query_array'] = $query_array;
} else {
$subquery['query_array'] = null;
}
$subquery['params'] = $params;
$subqueries[] = $subquery;
}
} else {
$GLOBALS['log']->fatal('isDatasourceFunction() is not implemented.');
}
}
return $subqueries;
}
/**
* Applies pagination window to union queries used by list view and subpanels,
* executes the query and returns fetched data.
*
* Internal function, do not override.
* @param object $parent_bean
* @param string $query query to be processed.
* @param int $row_offset
* @param int $limit optional, default -1
* @param int $max_per_page Optional, default -1
* @param string $where Custom where clause.
* @param aSubPanel $subpanel_def definition of sub-panel to be processed
* @param string $query_row_count
* @param array $secondary_queries
* @return array $fetched data.
*/
public function process_union_list_query(
$parent_bean,
$query,
$row_offset,
$limit = -1,
$max_per_page = -1,
$where = '',
$subpanel_def= null,
$query_row_count = '',
$secondary_queries = array()
)
{
if (is_null($subpanel_def)) {
$GLOBALS['log']->fatal('subpanel_def is null');
}
$db = DBManagerFactory::getInstance('listviews');
/**
* if the row_offset is set to 'end' go to the end of the list
*/
$toEnd = strval($row_offset) == 'end';
global $sugar_config;
$use_count_query = false;
if (!method_exists($subpanel_def, 'isCollection')) {
$GLOBALS['log']->fatal('Call to a member function isCollection() on an invalid object');
$processing_collection = null;
} else {
$processing_collection = $subpanel_def->isCollection();
}
$GLOBALS['log']->debug("process_union_list_query: " . $query);
if ($max_per_page == -1) {
$max_per_page = $sugar_config['list_max_entries_per_subpanel'];
}
if (empty($query_row_count)) {
$query_row_count = $query;
}
$distinct_position = strpos($query_row_count, "DISTINCT");
if ($distinct_position !== false) {
$use_count_query = true;
}
$performSecondQuery = true;
if (empty($sugar_config['disable_count_query']) || $toEnd) {
$rows_found = $this->_get_num_rows_in_query($query_row_count, $use_count_query);
if ($rows_found < 1) {
$performSecondQuery = false;
}
if (!empty($rows_found) && (empty($limit) || $limit == -1)) {
$limit = $max_per_page;
}
if ($toEnd) {
$row_offset = (floor(($rows_found - 1) / $limit)) * $limit;
}
} else {
if ((empty($limit) || $limit == -1)) {
$limit = $max_per_page + 1;
$max_per_page = $limit;
}
}
if (empty($row_offset)) {
$row_offset = 0;
}
$list = array();
$previous_offset = $row_offset - $max_per_page;
$next_offset = $row_offset + $max_per_page;
if ($performSecondQuery) {
if (!empty($limit) && $limit != -1 && $limit != -99) {
if (empty($parent_bean)) {
$objectName = '[empty parent bean]';
} else {
$objectName = $parent_bean->object_name;
}
$result = $db->limitQuery($query, $row_offset, $limit, true, "Error retrieving $objectName list: ");
} else {
$result = $db->query($query, true, "Error retrieving $this->object_name list: ");
}
//use -99 to return all
// get the current row
$index = $row_offset;
$row = $db->fetchByAssoc($result);
$post_retrieve = array();
$isFirstTime = true;
while ($row) {
$function_fields = array();
if (($index < $row_offset + $max_per_page || $max_per_page == -99)) {
if ($processing_collection) {
if (!isset($row['panel_name'])) {
$GLOBALS['log']->fatal('"panel_name" is not set');
$row['panel_name'] = null;
}
if (
!isset($subpanel_def->sub_subpanels) ||
!isset($subpanel_def->sub_subpanels[$row['panel_name']]) ||
!isset($subpanel_def->sub_subpanels[$row['panel_name']]->template_instance)) {
$current_bean = new stdClass();
} else {
$current_bean = $subpanel_def->sub_subpanels[$row['panel_name']]->template_instance;
}
if (!$isFirstTime) {
$class = get_class($subpanel_def->sub_subpanels[$row['panel_name']]->template_instance);
$current_bean = new $class();
}
} else {
if (!is_object($subpanel_def)) {
$GLOBALS['log']->fatal('Subpanel Definition is not an object');
} elseif (!isset($subpanel_def->template_instance)) {
$GLOBALS['log']->fatal('Undefined template instance');
} else {
$current_bean = $subpanel_def->template_instance;
if (!$isFirstTime) {
$class = get_class($subpanel_def->template_instance);
$current_bean = new $class();
}
}
}
$isFirstTime = false;
//set the panel name in the bean instance.
if (isset($row['panel_name'])) {
$current_bean->panel_name = $row['panel_name'];
}
$fieldDefs = array();
if (isset($current_bean) && is_object($current_bean)) {
if (!isset($current_bean->field_defs)) {
$GLOBALS['log']->fatal('Trying to get property of non-object');
} else {
if (!is_array($current_bean->field_defs)) {
$GLOBALS['log']->fatal(
'SugarBean::process_union_list_query $field_defs should be an array'
);
$fieldDefs = (array)$current_bean->field_defs;
} else {
$fieldDefs = $current_bean->field_defs;
}
}
} else {
$GLOBALS['log']->fatal('Unresolved current bean');
$current_bean = new stdClass();
}
foreach ($fieldDefs as $field => $value) {
if (isset($row[$field])) {
$current_bean->$field = $this->convertField($row[$field], $value);
unset($row[$field]);
} elseif (isset($row[$this->table_name . '.' . $field])) {
$current_bean->$field = $this->convertField($row[$this->table_name . '.' . $field], $value);
unset($row[$this->table_name . '.' . $field]);
} else {
$current_bean->$field = "";
unset($row[$field]);
}
if (isset($value['source']) && $value['source'] == 'function') {
$function_fields[] = $field;
}
}
foreach ($row as $key => $value) {
$current_bean->$key = $value;
}
foreach ($function_fields as $function_field) {
$value = $current_bean->field_defs[$function_field];
$can_execute = true;
$execute_params = array();
$execute_function = array();
if (!empty($value['function_class'])) {
$execute_function[] = $value['function_class'];
$execute_function[] = $value['function_name'];
} else {
$execute_function = $value['function_name'];
}
foreach ($value['function_params'] as $param) {
if (empty($value['function_params_source'])
|| $value['function_params_source'] == 'parent') {
if (empty($this->$param)) {
$can_execute = false;
} elseif ($param == '$this') {
$execute_params[] = $this;
} else {
$execute_params[] = $this->$param;
}
} elseif ($value['function_params_source'] == 'this') {
if (empty($current_bean->$param)) {
$can_execute = false;
} elseif ($param == '$this') {
$execute_params[] = $current_bean;
} else {
$execute_params[] = $current_bean->$param;
}
} else {
$can_execute = false;
}
}
if ($can_execute) {
if (!empty($value['function_require'])) {
require_once($value['function_require']);
}
$current_bean->$function_field = call_user_func_array($execute_function, $execute_params);
}
}
if (!empty($current_bean->parent_type) && !empty($current_bean->parent_id)) {
if (!isset($post_retrieve[$current_bean->parent_type])) {
$post_retrieve[$current_bean->parent_type] = array();
}
$post_retrieve[$current_bean->parent_type][] = array(
'child_id' => $current_bean->id,
'parent_id' => $current_bean->parent_id,
'parent_type' => $current_bean->parent_type,
'type' => 'parent'
);
}
if (!isset($current_bean->id)) {
$current_bean->id = null;
}
$list[$current_bean->id] = $current_bean;
}
// go to the next row
$index++;
$row = $db->fetchByAssoc($result);
}
//now handle retrieving many-to-many relationships
if (!empty($list)) {
foreach ($secondary_queries as $query2) {
$result2 = $db->query($query2);
$row2 = $db->fetchByAssoc($result2);
while ($row2) {
$id_ref = $row2['ref_id'];
if (isset($list[$id_ref])) {
foreach ($row2 as $r2key => $r2value) {
if ($r2key != 'ref_id') {
$list[$id_ref]->$r2key = $r2value;
}
}
}
$row2 = $db->fetchByAssoc($result2);
}
}
}
if (isset($post_retrieve)) {
$parent_fields = $this->retrieve_parent_fields($post_retrieve);
} else {
$parent_fields = array();
}
if (!empty($sugar_config['disable_count_query']) && !empty($limit)) {
//C.L. Bug 43535 - Use the $index value to set the $rows_found value here
$rows_found = isset($index) ? $index : $row_offset + count($list);
if (count($list) >= $limit) {
array_pop($list);
}
if (!$toEnd) {
$next_offset--;
$previous_offset++;
}
}
} else {
$parent_fields = array();
}
$response = array();
$response['list'] = $list;
$response['parent_data'] = $parent_fields;
$response['row_count'] = $rows_found;
$response['next_offset'] = $next_offset;
$response['previous_offset'] = $previous_offset;
$response['current_offset'] = $row_offset;
$response['query'] = $query;
return $response;
}
/**
* Returns the number of rows that the given SQL query should produce
*
* Internal function, do not override.
* @param string $query valid select query
* @param bool $is_count_query Optional, Default false, set to true if passed query is a count query.
* @return int count of rows found
*/
public function _get_num_rows_in_query($query, $is_count_query = false)
{
$num_rows_in_query = 0;
if (!$is_count_query) {
$count_query = SugarBean::create_list_count_query($query);
} else {
$count_query = $query;
}
$result = $this->db->query($count_query, true, "Error running count query for $this->object_name List: ");
while ($row = $this->db->fetchByAssoc($result, true)) {
$num_rows_in_query += current($row);
}
return (int)$num_rows_in_query;
}
/**
* Returns parent record data for objects that store relationship information
*
* @param array $type_info
* @return array
*
* Internal function, do not override.
*/
public function retrieve_parent_fields($type_info)
{
$queries = array();
global $beanList, $beanFiles;
$templates = array();
$parent_child_map = array();
if (!is_array($type_info)) {
$GLOBALS['log']->warn('Type info is not an array');
}
foreach ((array)$type_info as $children_info) {
if (!is_array($children_info)) {
$GLOBALS['log']->warn('Children info is not an array');
}
foreach ((array)$children_info as $child_info) {
if ($child_info['type'] == 'parent') {
if (!isset($child_info['parent_type'])) {
$GLOBALS['log']->fatal('"parent_type" is not set');
}
if (!isset($child_info['parent_type']) || empty($templates[$child_info['parent_type']])) {
//Test emails will have an invalid parent_type, don't try to load the non-existent parent bean
if (isset($child_info['parent_type'])) {
if ($child_info['parent_type'] == 'test') {
continue;
}
if (isset($beanList[$child_info['parent_type']])) {
$class = $beanList[$child_info['parent_type']];
} else {
$GLOBALS['log']->fatal('Parent type is not a valid bean: '
. $child_info['parent_type']);
$class = null;
}
}
// Added to avoid error below; just silently fail and write message to log
if (isset($class)) {
if (empty($beanFiles[$class])) {
$GLOBALS['log']->error($this->object_name . '::retrieve_parent_fields() - ' .
'cannot load class "' . $class . '", skip loading.');
continue;
}
require_once($beanFiles[$class]);
$templates[$child_info['parent_type']] = new $class();
}
}
if (isset($child_info['parent_type']) && empty($queries[$child_info['parent_type']])) {
$queries[$child_info['parent_type']] = "SELECT id ";
if (isset($templates[$child_info['parent_type']])) {
$field_def = $templates[$child_info['parent_type']]->field_defs['name'];
} else {
$GLOBALS['log']->fatal('No template for Parent type: ' . $child_info['parent_type']);
$field_def = null;
}
if (isset($field_def['db_concat_fields'])) {
$queries[$child_info['parent_type']] .= ' , '
. $this->db->concat(
$templates[$child_info['parent_type']]->table_name,
$field_def['db_concat_fields']
) . ' parent_name';
} else {
$queries[$child_info['parent_type']] .= ' , name parent_name';
}
if (isset($templates[$child_info['parent_type']]->field_defs['assigned_user_id'])) {
$queries[$child_info['parent_type']] .= ", assigned_user_id parent_name_owner , " .
"'{$child_info['parent_type']}' parent_name_mod";
} elseif (isset($templates[$child_info['parent_type']]->field_defs['created_by'])) {
$queries[$child_info['parent_type']] .= ", created_by parent_name_owner, " .
"'{$child_info['parent_type']}' parent_name_mod";
}
if (isset($templates[$child_info['parent_type']])) {
if (isset($child_info['parent_id'])) {
$childInfoParentId = $child_info['parent_id'];
} else {
$GLOBALS['log']->fatal('"parent_id" is not set');
$childInfoParentId = null;
}
$queries[$child_info['parent_type']] .=
" FROM " . $templates[$child_info['parent_type']]->table_name .
" WHERE id IN ('$childInfoParentId'";
}
} else{
if (isset($child_info['parent_id']) && empty($parent_child_map[$child_info['parent_id']]) && isset($child_info['parent_type'])) {
$queries[$child_info['parent_type']] .= " ,'{$child_info['parent_id']}'";
}
}
if (isset($child_info['parent_id'])) {
if (isset($child_info['child_id'])) {
$parent_child_map[$child_info['parent_id']][] = $child_info['child_id'];
} else {
$GLOBALS['log']->fatal('"child_id" is not set');
$parent_child_map[$child_info['parent_id']][] = null;
}
}
}
}
}
$results = array();
foreach ($queries as $query) {
$result = $this->db->query($query . ')');
while ($row = $this->db->fetchByAssoc($result)) {
$results[$row['id']] = $row;
}
}
$child_results = array();
foreach ($parent_child_map as $parent_key => $parent_child) {
foreach ($parent_child as $child) {
if (isset($results[$parent_key])) {
$child_results[$child] = $results[$parent_key];
}
}
}
return $child_results;
}
/**
* Returns a list of fields with their definitions that have the audited property set to true.
* Before calling this function, check whether audit has been enabled for the table/module or not.
* You would set the audit flag in the implementing module's vardef file.
*
* @return array
* @see is_AuditEnabled
*
* Internal function, do not override.
*/
public function getAuditEnabledFieldDefinitions()
{
if (!isset($this->audit_enabled_fields)) {
$this->audit_enabled_fields = array();
if (!isset($this->field_defs)) {
$GLOBALS['log']->fatal('Field definition is not set.');
} elseif (!is_array($this->field_defs)) {
$GLOBALS['log']->fatal('Field definition is not an array.');
} else {
foreach ($this->field_defs as $field => $properties) {
if (
(
!empty($properties['Audited']) || !empty($properties['audited']))
) {
$this->audit_enabled_fields[$field] = $properties;
}
}
}
}
return $this->audit_enabled_fields;
}
/**
* Returns true of false if the user_id passed is the owner
*
* @param string $user_id GUID
* @return bool
*/
public function isOwner($user_id)
{
//if we don't have an id we must be the owner as we are creating it
if (!isset($this->id) || $this->id == "[SELECT_ID_LIST]") {
return true;
}
//if there is an assigned_user that is the owner
if (!empty($this->fetched_row['assigned_user_id'])) {
if ($this->fetched_row['assigned_user_id'] == $user_id) {
return true;
}
return false;
} elseif (isset($this->assigned_user_id)) {
if ($this->assigned_user_id == $user_id) {
return true;
}
return false;
} else {
//other wise if there is a created_by that is the owner
if (isset($this->created_by) && $this->created_by == $user_id) {
return true;
}
}
//other wise if there is a created_by that is the owner
if (isset($this->created_by) && $this->created_by == $user_id) {
return true;
}
return false;
}
/**
* Returns the name of the custom table.
* Custom table's name is based on implementing class' table name.
*
* @return String Custom table name.
*
* Internal function, do not override.
*/
public function get_custom_table_name()
{
return $this->getTableName() . '_cstm';
}
/**
* Returns the implementing class' table name.
*
* All implementing classes set a value for the table_name variable. This value is returned as the
* table name. If not set, table name is extracted from the implementing module's vardef.
*
* @return String Table name.
*
* Internal function, do not override.
*/
public function getTableName()
{
if (isset($this->table_name)) {
return $this->table_name;
}
global $dictionary;
if (!isset($dictionary[$this->getObjectName()])) {
$GLOBALS['log']->fatal('Dictionary doesn\'t contains an index: ' . $this->getObjectName());
return null;
}
return $dictionary[$this->getObjectName()]['table'];
}
/**
* Returns the object name. If object_name is not set, table_name is returned.
*
* All implementing classes must set a value for the object_name variable.
*
* @return string
*
*/
public function getObjectName()
{
if (!isset($this->object_name)) {
$GLOBALS['log']->fatal('"object_name" is not set');
return null;
}
if ($this->object_name) {
return $this->object_name;
}
// This is a quick way out. The generated metadata files have the table name
// as the key. The correct way to do this is to override this function
// in bean and return the object name. That requires changing all the beans
// as well as put the object name in the generator.
if (!isset($this->table_name)) {
$GLOBALS['log']->fatal('"table_name" is not set');
return null;
}
return $this->table_name;
}
/**
* Returns index definitions for the implementing module.
*
* The definitions were loaded in the constructor.
*
* @return array Index definitions.
*
* Internal function, do not override.
*/
public function getIndices()
{
global $dictionary;
if (isset($dictionary[$this->getObjectName()]['indices'])) {
return $dictionary[$this->getObjectName()]['indices'];
}
return array();
}
/**
* Returns definition for the id field name.
*
* The definitions were loaded in the constructor.
*
* @return array Field properties.
*
* Internal function, do not override.
*/
public function getPrimaryFieldDefinition()
{
$def = $this->getFieldDefinition("id");
if (empty($def)) {
$def = $this->getFieldDefinition(0);
}
if (empty($def)) {
if (!is_array($this->field_defs) && !is_object($this->field_defs)) {
$GLOBALS['log']->fatal('SugarBean::getPrimaryFieldDefinition $field_defs should be an array');
} else {
$defs = (array)$this->field_defs;
reset($defs);
$def = current($defs);
}
}
return $def;
}
/**
* Returns field definition for the requested field name.
*
* The definitions were loaded in the constructor.
*
* @param string $name ,
* @return mixed Field properties or bool false if the field doesn't exist
*
* Internal function, do not override.
*/
public function getFieldDefinition($name)
{
if (!isset($this->field_defs[$name])) {
return false;
}
return $this->field_defs[$name];
}
/**
* Returns the value for the requested field.
*
* When a row of data is fetched using the bean, all fields are created as variables in the context
* of the bean and then fetched values are set in these variables.
*
* @param string $name ,
* @return mixed.
*
* Internal function, do not override.
*/
public function getFieldValue($name)
{
if (!isset($this->$name)) {
return false;
}
if ($this->$name === true) {
return 1;
}
if ($this->$name === false) {
return 0;
}
return $this->$name;
}
/**
* Basically undoes the effects of SugarBean::populateDefaultValues(); this method is best called right after object
* initialization.
*/
public function unPopulateDefaultValues()
{
if (!is_array($this->field_defs)) {
return;
}
foreach ($this->field_defs as $field => $value) {
if (!empty($this->$field)
&& ((isset($value['default']) && $this->$field == $value['default'])
|| (!empty($value['display_default']) && $this->$field == $value['display_default']))
) {
$this->$field = null;
continue;
}
if (
!empty($this->$field) &&
!empty($value['display_default']) &&
in_array($value['type'], array('date', 'datetime', 'datetimecombo')) &&
$this->$field == $this->parseDateDefault($value['display_default'], ($value['type'] != 'date'))
) {
$this->$field = null;
}
}
}
/**
* Handle the following when a SugarBean object is cloned
*
* Currently all this does it unset any relationships that were created prior to cloning the object
*
* @api
*/
public function __clone()
{
if (!empty($this->loaded_relationships)) {
foreach ($this->loaded_relationships as $rel) {
unset($this->$rel);
}
}
}
/**
* Loads all attributes of type link.
*
* DO NOT CALL THIS FUNCTION IF YOU CAN AVOID IT. Please use load_relationship directly instead.
*
* Method searches the implementing module's vardef file for attributes of type link, and for each attribute
* create a similarly named variable and load the relationship definition.
*
* Internal function, do not override.
*/
public function load_relationships()
{
$GLOBALS['log']->debug("SugarBean.load_relationships, Loading all relationships of type link.");
$linked_fields = $this->get_linked_fields();
foreach ($linked_fields as $name => $properties) {
$this->load_relationship($name);
}
}
/**
* Returns an array of fields that are of type link.
*
* @return array List of fields.
*
* Internal function, do not override.
*/
public function get_linked_fields()
{
$linked_fields = array();
$fieldDefs = $this->getFieldDefinitions();
//find all definitions of type link.
if (!empty($fieldDefs)) {
foreach ($fieldDefs as $name => $properties) {
if (!is_array($properties)) {
$GLOBALS['log']->fatal('array_search() expects parameter 2 to be array, ' .
gettype($properties) . ' given');
} elseif (array_search('link', $properties) === 'type') {
$linked_fields[$name] = $properties;
}
}
}
return $linked_fields;
}
/**
* Returns field definitions for the implementing module.
*
* The definitions were loaded in the constructor.
*
* @return array Field definitions.
*
* Internal function, do not override.
*/
public function getFieldDefinitions()
{
return $this->field_defs;
}
/**
* Loads the request relationship.
* This method should be called before performing any operations on the related data.
*
* This method searches the vardef array for the requested attribute's definition. If the attribute is of the type
* link then it creates a similarly named variable and loads the relationship definition.
*
* @param string $rel_name relationship/attribute name.
* @return bool.
*/
public function load_relationship($rel_name)
{
$GLOBALS['log']->debug("SugarBean[{$this->object_name}].load_relationships, Loading relationship (" .
$rel_name . ").");
if (empty($rel_name)) {
$GLOBALS['log']->error("SugarBean.load_relationships, Null relationship name passed.");
return false;
}
$fieldDefs = $this->getFieldDefinitions();
//find all definitions of type link.
if (!empty($fieldDefs[$rel_name])) {
//initialize a variable of type Link
require_once('data/Link2.php');
$class = load_link_class($fieldDefs[$rel_name]);
if (isset($this->$rel_name) && $this->$rel_name instanceof $class) {
return true;
}
//if rel_name is provided, search the fieldDef array keys by name.
if (isset($fieldDefs[$rel_name]['type']) && $fieldDefs[$rel_name]['type'] == 'link') {
if ($class == "Link2") {
$this->$rel_name = new $class($rel_name, $this);
} else {
if (!class_exists($class)) {
$GLOBALS['log']->fatal('Class not found: ' . $class);
} else {
if (!isset($fieldDefs[$rel_name]['relationship'])) {
$GLOBALS['log']->fatal('Relationship not found');
} else {
$this->$rel_name = new $class(
$fieldDefs[$rel_name]['relationship'],
$this,
$fieldDefs[$rel_name]
);
}
}
}
if (empty($this->$rel_name) ||
(method_exists($this->$rel_name, "loadedSuccesfully") && !$this->$rel_name->loadedSuccesfully())
) {
unset($this->$rel_name);
return false;
}
// keep track of the loaded relationships
$this->loaded_relationships[] = $rel_name;
return true;
}
}
$GLOBALS['log']->debug("SugarBean.load_relationships, failed Loading relationship (" . $rel_name . ")");
return false;
}
/**
* Returns an array of beans of related data.
*
* For instance, if an account is related to 10 contacts , this function will return an array of contacts beans (10)
* with each bean representing a contact record.
* Method will load the relationship if not done so already.
*
* @param string $field_name relationship to be loaded.
* @param string $bean_name class name of the related bean.legacy
* @param string $order_by , Optional, default empty.
* @param int $begin_index Optional, default 0, unused.
* @param int $end_index Optional, default -1
* @param int $deleted Optional, Default 0, 0 adds deleted=0 filter, 1 adds deleted=1 filter.
* @param string $optional_where , Optional, default empty.
* @return SugarBean[]
*
* Internal function, do not override.
*/
public function get_linked_beans(
$field_name,
$bean_name = '',
$order_by = '',
$begin_index = 0,
$end_index = -1,
$deleted = 0,
$optional_where = ""
)
{
//if bean_name is Case then use aCase
if ($bean_name == "Case") {
$bean_name = "aCase";
}
if ($this->load_relationship($field_name)) {
if ($this->$field_name instanceof Link) {
// some classes are still based on Link, e.g. TeamSetLink
return array_values($this->$field_name->getBeans(
new $bean_name(),
$order_by,
$begin_index,
$end_index,
$deleted,
$optional_where
));
} else {
// Link2 style
if ($end_index != -1 || !empty($deleted) || !empty($optional_where) || !empty($order_by)) {
return array_values($this->$field_name->getBeans(array(
'where' => $optional_where,
'deleted' => $deleted,
'limit' => ($end_index - $begin_index),
'order_by' => $order_by
)));
} else {
return array_values($this->$field_name->getBeans());
}
}
} else {
return array();
}
}
/**
* Returns an array of fields that are required for import
*
* @return array
*/
public function get_import_required_fields()
{
$importable_fields = $this->get_importable_fields();
$required_fields = array();
foreach ($importable_fields as $name => $properties) {
if (isset($properties['importable']) && is_string($properties['importable'])
&& $properties['importable'] == 'required') {
$required_fields[$name] = $properties;
}
}
return $required_fields;
}
/**
* Returns an array of fields that are able to be Imported into
* i.e. 'importable' not set to 'false'
*
* @return array List of fields.
*
* Internal function, do not override.
*/
public function get_importable_fields()
{
$importableFields = array();
$fieldDefs = $this->getFieldDefinitions();
if (!empty($fieldDefs)) {
foreach ($fieldDefs as $key => $value_array) {
if ((isset($value_array['importable'])
&& (is_string($value_array['importable']) && $value_array['importable'] == 'false'
|| is_bool($value_array['importable']) && !$value_array['importable']))
|| (isset($value_array['type']) && $value_array['type'] == 'link')
|| (isset($value_array['auto_increment'])
&& ($value_array['type'] || $value_array['type'] == 'true'))
) {
// only allow import if we force it
if (isset($value_array['importable'])
&& (is_string($value_array['importable']) && $value_array['importable'] == 'true'
|| is_bool($value_array['importable']) && $value_array['importable'])
) {
$importableFields[$key] = $value_array;
}
} else {
//Expose the corresponding id field of a relate field if it is only defined as a link
// so that users can relate records by id during import
if (isset($value_array['type']) && ($value_array['type'] == 'relate')
&& isset($value_array['id_name'])) {
$idField = $value_array['id_name'];
if (isset($fieldDefs[$idField]) && isset($fieldDefs[$idField]['type'])
&& $fieldDefs[$idField]['type'] == 'link') {
$tmpFieldDefs = $fieldDefs[$idField];
$tmpFieldDefs['vname'] = translate($value_array['vname'], $this->module_dir)
. " " . $GLOBALS['app_strings']['LBL_ID'];
$importableFields[$idField] = $tmpFieldDefs;
}
}
$importableFields[$key] = $value_array;
}
}
}
return $importableFields;
}
/**
* Creates tables for the module implementing the class.
* If you override this function make sure that your code can handles table creation.
*
*/
public function create_tables()
{
global $dictionary;
$key = $this->getObjectName();
if (!array_key_exists($key, $dictionary)) {
$GLOBALS['log']->fatal("create_tables: Metadata for table " . $this->table_name . " does not exist");
display_notice("meta data absent for table " . $this->table_name . " keyed to $key ");
} else {
if (!$this->db->tableExists($this->table_name)) {
$this->db->createTable($this);
if ($this->bean_implements('ACL')) {
if (!empty($this->acltype)) {
ACLAction::addActions($this->getACLCategory(), $this->acltype);
} else {
ACLAction::addActions($this->getACLCategory());
}
}
} else {
echo "Table already exists : $this->table_name<br>";
}
if ($this->is_AuditEnabled() && !$this->db->tableExists($this->get_audit_table_name())) {
$this->create_audit_table();
}
}
}
/**
* Returns the ACL category for this module; defaults to the SugarBean::$acl_category if defined
* otherwise it is SugarBean::$module_dir
*
* @return string
*/
public function getACLCategory()
{
return !empty($this->acl_category) ? $this->acl_category : $this->module_dir;
}
/**
* Return true if auditing is enabled for this object
* You would set the audit flag in the implementing module's vardef file.
*
* @return bool
*
* Internal function, do not override.
*/
public function is_AuditEnabled()
{
global $dictionary;
if (isset($dictionary[$this->getObjectName()]['audited'])) {
return $dictionary[$this->getObjectName()]['audited'];
} else {
return false;
}
}
/**
* Returns the name of the audit table.
* Audit table's name is based on implementing class' table name.
*
* @return String Audit table name.
*
* Internal function, do not override.
*/
public function get_audit_table_name()
{
return $this->getTableName() . '_audit';
}
/**
* If auditing is enabled, create the audit table.
*
* Function is used by the install scripts and a repair utility in the admin panel.
*
* Internal function, do not override.
*/
public function create_audit_table()
{
global $dictionary;
$table_name = $this->get_audit_table_name();
require('metadata/audit_templateMetaData.php');
// Bug: 52583 Need ability to customize template for audit tables
$custom = 'custom/metadata/audit_templateMetaData_' . $this->getTableName() . '.php';
if (file_exists($custom)) {
require($custom);
}
$fieldDefs = $dictionary['audit']['fields'];
$indices = $dictionary['audit']['indices'];
// Renaming template indexes to fit the particular audit table (removed the brittle hard coding)
foreach ($indices as $nr => $properties) {
$indices[$nr]['name'] = 'idx_' . strtolower($this->getTableName()) . '_' . $properties['name'];
}
$engine = null;
if (isset($dictionary['audit']['engine'])) {
$engine = $dictionary['audit']['engine'];
} elseif (isset($dictionary[$this->getObjectName()]['engine'])) {
$engine = $dictionary[$this->getObjectName()]['engine'];
}
$this->db->createTableParams($table_name, $fieldDefs, $indices, $engine);
}
/**
* Delete the primary table for the module implementing the class.
* If custom fields were added to this table/module, the custom table will be removed too, along with the cache
* entries that define the custom fields.
*
*/
public function drop_tables()
{
global $dictionary;
$key = $this->getObjectName();
if (!array_key_exists($key, $dictionary)) {
$GLOBALS['log']->fatal("drop_tables: Metadata for table " . $this->table_name . " does not exist");
echo "meta data absent for table " . $this->table_name . "<br>\n";
} else {
if (empty($this->table_name)) {
return;
}
if ($this->db->tableExists($this->table_name)) {
$this->db->dropTable($this);
}
if ($this->db->tableExists($this->table_name . '_cstm')) {
$this->db->dropTableName($this->table_name . '_cstm');
DynamicField::deleteCache();
}
if ($this->db->tableExists($this->get_audit_table_name())) {
$this->db->dropTableName($this->get_audit_table_name());
}
}
}
/**
* Implements a generic insert and update logic for any SugarBean
* This method only works for subclasses that implement the same variable names.
* This method uses the presence of an id field that is not null to signify and update.
* The id field should not be set otherwise.
*
* @param bool $check_notify Optional, default false, if set to true assignee of the record is notified via email.
* @return string ID
* @todo Add support for field type validation and encoding of parameters.
*/
public function save($check_notify = false)
{
$this->in_save = true;
// cn: SECURITY - strip XSS potential vectors
$this->cleanBean();
// This is used so custom/3rd-party code can be upgraded with fewer issues,
// this will be removed in a future release
$this->fixUpFormatting();
global $current_user, $action;
$isUpdate = true;
if (empty($this->id)) {
$isUpdate = false;
}
if ($this->new_with_id) {
$isUpdate = false;
}
if (empty($this->date_modified) || $this->update_date_modified) {
$this->date_modified = $GLOBALS['timedate']->nowDb();
}
$this->_checkOptimisticLocking($action, $isUpdate);
if (!empty($this->modified_by_name)) {
$this->old_modified_by_name = $this->modified_by_name;
}
if ($this->update_modified_by) {
$this->modified_user_id = 1;
if (!empty($current_user)) {
$this->modified_user_id = $current_user->id;
$this->modified_by_name = $current_user->user_name;
}
}
if ($this->deleted != 1) {
$this->deleted = 0;
}
if (!$isUpdate) {
if (empty($this->date_entered)) {
$this->date_entered = $this->date_modified;
}
if ($this->set_created_by) {
// created by should always be this user
$this->created_by = (isset($current_user)) ? $current_user->id : "";
}
if (!$this->new_with_id) {
$this->id = create_guid();
}
}
require_once("data/BeanFactory.php");
BeanFactory::registerBean($this->module_name, $this);
if (empty($GLOBALS['updating_relationships']) && empty($GLOBALS['saving_relationships'])
&& empty($GLOBALS['resavingRelatedBeans'])) {
$GLOBALS['saving_relationships'] = true;
// let subclasses save related field changes
$this->save_relationship_changes($isUpdate);
$GLOBALS['saving_relationships'] = false;
}
if ($isUpdate && !$this->update_date_entered) {
unset($this->date_entered);
}
// call the custom business logic
$custom_logic_arguments['check_notify'] = $check_notify;
$this->call_custom_logic("before_save", $custom_logic_arguments);
unset($custom_logic_arguments);
// If we're importing back semi-colon separated non-primary emails
if ($this->hasEmails() && !empty($this->email_addresses_non_primary)
&& is_array($this->email_addresses_non_primary)) {
// Add each mail to the account
if (isset($this->emailAddress)) {
if ($this->emailAddress instanceof EmailAddress) {
foreach ($this->email_addresses_non_primary as $mail) {
$this->emailAddress->addAddress($mail);
}
$this->emailAddress->saveEmail(
$this->id,
$this->module_dir,
'',
'',
'',
'',
'',
$this->in_workflow);
} else {
LoggerManager::getLogger()->fatal('SugarBean::$emailAddress should be an EmailAddress, ' . gettype($this->emailAddress) . ' given.');
}
} else {
LoggerManager::getLogger()->fatal('SugarBean::$emailAddress is not set, email address(es) is not applied to Bean.');
}
}
if (isset($this->custom_fields)) {
$this->custom_fields->bean = $this;
$this->custom_fields->save($isUpdate);
}
$this->_sendNotifications($check_notify);
if ($isUpdate) {
$ret = $this->db->update($this);
} else {
$ret = $this->db->insert($this);
}
if (empty($GLOBALS['resavingRelatedBeans'])) {
SugarRelationship::resaveRelatedBeans();
}
/* BEGIN - SECURITY GROUPS - inheritance */
require_once('modules/SecurityGroups/SecurityGroup.php');
SecurityGroup::inherit($this, $isUpdate);
/* END - SECURITY GROUPS */
//If we aren't in setup mode and we have a current user and module, then we track
if (isset($GLOBALS['current_user']) && isset($this->module_dir)) {
$this->track_view($current_user->id, $this->module_dir, 'save');
}
$this->call_custom_logic('after_save', '');
$this->auditBean($isUpdate);
//Now that the record has been saved, we don't want to insert again on further saves
$this->new_with_id = false;
$this->in_save = false;
return $this->id;
}
/**
* Cleans char, varchar, text, etc. fields of XSS type materials
*/
public function cleanBean()
{
if (!is_array($this->field_defs) && !is_object($this->field_defs)) {
$GLOBALS['log']->fatal('SugarBean::$filed_defs should be an array');
} else {
foreach ((array)$this->field_defs as $key => $def) {
$type = '';
if (isset($def['type'])) {
$type = $def['type'];
}
if (isset($def['dbType'])) {
$type .= $def['dbType'];
}
if (isset($def['type']) && ($def['type'] == 'html' || $def['type'] == 'longhtml')) {
$this->$key = htmlentities(SugarCleaner::cleanHtml($this->$key, true));
} elseif (
(strpos($type, 'char') !== false || strpos($type, 'text') !== false || $type == 'enum') &&
!empty($this->$key)
) {
$this->$key = htmlentities(SugarCleaner::cleanHtml($this->$key, true));
}
}
}
}
/**
* Function corrects any bad formatting done by 3rd party/custom code
*
* This function will be removed in a future release, it is only here to assist upgrading existing code
* that expects formatted data in the bean
*/
public function fixUpFormatting()
{
global $timedate;
static $bool_false_values = array('off', 'false', '0', 'no');
if (!is_array($this->field_defs) && !is_object($this->field_defs)) {
$GLOBALS['log']->fatal('SugarBean::fixUpFormatting $field_defs should be an array');
} else {
foreach ((array)$this->field_defs as $field => $def) {
if (!isset($this->$field)) {
continue;
}
if ((isset($def['source']) && $def['source'] == 'non-db') || $field == 'deleted') {
continue;
}
if (isset($this->fetched_row[$field]) && $this->$field == $this->fetched_row[$field]) {
// Don't hand out warnings because the field was untouched between retrieval and saving,
// most database drivers hand pretty much everything back as strings.
continue;
}
$reformatted = false;
if (isset($def['type']) && $def['type']) {
switch ($def['type']) {
case 'datetime':
case 'datetimecombo':
if (empty($this->$field) || $this->$field == 'NULL') {
$this->$field = '';
break;
}
if (!preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$/', $this->$field)) {
$this->$field = $timedate->to_db($this->$field);
$reformatted = true;
}
break;
case 'date':
if (empty($this->$field) || $this->$field == 'NULL') {
$this->$field = '';
break;
}
if (!preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/', $this->$field)) {
$this->$field = $timedate->to_db_date($this->$field, false);
$reformatted = true;
}
break;
case 'time':
if (empty($this->$field) || $this->$field == 'NULL') {
$this->$field = '';
break;
}
if (preg_match('/(am|pm)/i', $this->$field)) {
$fromUserTime = $timedate->fromUserTime($this->$field);
if (is_object($fromUserTime) && method_exists($fromUserTime, 'format')) {
$this->$field = $fromUserTime->format(TimeDate::DB_TIME_FORMAT);
$reformatted = true;
} else {
$GLOBALS['log']->fatal('Get DateTime from user time string is failed.');
}
}
break;
case 'double':
case 'decimal':
case 'currency':
case 'float':
if ($this->$field === '' || $this->$field == null || $this->$field == 'NULL') {
continue;
}
if (is_string($this->$field)) {
$this->$field = (float)unformat_number($this->$field);
$reformatted = true;
}
break;
case 'uint':
case 'ulong':
case 'long':
case 'short':
case 'tinyint':
case 'int':
if ($this->$field === '' || $this->$field == null || $this->$field == 'NULL') {
continue;
}
if (is_string($this->$field)) {
$this->$field = (int)unformat_number($this->$field);
$reformatted = true;
}
break;
case 'bool':
if (empty($this->$field) || in_array(strval($this->$field), $bool_false_values)) {
$this->$field = false;
} elseif (true === $this->$field || 1 == $this->$field) {
$this->$field = true;
} else {
$this->$field = true;
$reformatted = true;
}
break;
case 'encrypt':
$this->$field = $this->encrpyt_before_save($this->$field);
break;
default :
//do nothing
}
}
if ($reformatted) {
$GLOBALS['log']->deprecated('Formatting correction: ' . $this->module_dir . '->' . $field .
' had formatting automatically corrected. This will be removed in the future, ' .
'please upgrade your external code');
}
}
}
}
/**
* Encrypt and base64 encode an 'encrypt' field type in the bean using Blowfish.
* The default system key is stored in cache/Blowfish/{keytype}
* @param string $value -plain text value of the bean field.
* @return string
*/
public function encrpyt_before_save($value)
{
require_once("include/utils/encryption_utils.php");
return blowfishEncode($this->getEncryptKey(), $value);
}
/**
* @return string
*/
protected function getEncryptKey()
{
if (empty(static::$field_key)) {
static::$field_key = blowfishGetKey('encrypt_field');
}
return static::$field_key;
}
/**
* Moved from save() method, functionality is the same, but this is intended to handle
* Optimistic locking functionality.
*
* @param string $action
* @param bool $isUpdate
*
*/
private function _checkOptimisticLocking($action, $isUpdate)
{
if ($this->optimistic_lock && !isset($_SESSION['o_lock_fs'])) {
if (isset($_SESSION['o_lock_id']) && $_SESSION['o_lock_id'] == $this->id
&& $_SESSION['o_lock_on'] == $this->object_name) {
if ($action == 'Save' && $isUpdate && isset($this->modified_user_id)
&& $this->has_been_modified_since($_SESSION['o_lock_dm'], $this->modified_user_id)) {
$_SESSION['o_lock_class'] = get_class($this);
$_SESSION['o_lock_module'] = $this->module_dir;
$_SESSION['o_lock_object'] = $this->toArray();
$saveform = "<form name='save' id='save' method='POST'>";
foreach ($_POST as $key => $arg) {
$saveform .= "<input type='hidden' name='" . addslashes($key)
. "' value='" . addslashes($arg) . "'>";
}
$saveform .= "</form><script>document.getElementById('save').submit();</script>";
$_SESSION['o_lock_save'] = $saveform;
header('Location: index.php?module=OptimisticLock&action=LockResolve');
die();
} else {
unset($_SESSION['o_lock_object']);
unset($_SESSION['o_lock_id']);
unset($_SESSION['o_lock_dm']);
}
}
} else {
if (isset($_SESSION['o_lock_object'])) {
unset($_SESSION['o_lock_object']);
}
if (isset($_SESSION['o_lock_id'])) {
unset($_SESSION['o_lock_id']);
}
if (isset($_SESSION['o_lock_dm'])) {
unset($_SESSION['o_lock_dm']);
}
if (isset($_SESSION['o_lock_fs'])) {
unset($_SESSION['o_lock_fs']);
}
if (isset($_SESSION['o_lock_save'])) {
unset($_SESSION['o_lock_save']);
}
}
}
/**
* Performs a check if the record has been modified since the specified date
*
* @param Datetime $date Datetime for verification
* @param string $modified_user_id User modified by
* @return bool
*/
public function has_been_modified_since($date, $modified_user_id)
{
global $current_user;
$date = $this->db->convert($this->db->quoted($date), 'datetime');
if (isset($current_user)) {
$query = "SELECT date_modified FROM $this->table_name WHERE id='$this->id' AND modified_user_id != " .
"'$current_user->id' AND (modified_user_id != '$modified_user_id' OR date_modified > $date)";
$result = $this->db->query($query);
if ($this->db->fetchByAssoc($result)) {
return true;
}
}
return false;
}
/**
* returns this bean as an array
*
* @param bool $dbOnly
* @param bool $stringOnly
* @param bool $upperKeys
* @return array of fields with id, name, access and category
*/
public function toArray($dbOnly = false, $stringOnly = false, $upperKeys = false)
{
static $cache = array();
$arr = array();
foreach ($this->field_defs as $field => $data) {
if (!$dbOnly || !isset($data['source']) || $data['source'] == 'db') {
if (!$stringOnly || is_string($this->$field)) {
if ($upperKeys) {
if (!isset($cache[$field])) {
$cache[$field] = strtoupper($field);
}
$arr[$cache[$field]] = $this->$field;
} else {
if (isset($this->$field)) {
$arr[$field] = $this->$field;
} else {
$arr[$field] = '';
}
}
}
}
}
return $arr;
}
/**
* This function is a good location to save changes that have been made to a relationship.
* This should be overridden in subclasses that have something to save.
*
* @param bool $is_update true if this save is an update.
* @param array $exclude a way to exclude relationships
*/
public function save_relationship_changes($is_update, $exclude = array())
{
list($new_rel_id, $new_rel_link) = $this->set_relationship_info($exclude);
$new_rel_id = $this->handle_preset_relationships($new_rel_id, $new_rel_link, $exclude);
$this->handle_remaining_relate_fields($exclude);
$this->update_parent_relationships($exclude);
$this->handle_request_relate($new_rel_id, $new_rel_link);
}
/**
* Look in the bean for the new relationship_id and relationship_name if $this->not_use_rel_in_req is set to true,
* otherwise check the $_REQUEST param for a relate_id and relate_to field. Once we have that make sure that it's
* not excluded from the passed in array of relationships to exclude
*
* @param array $exclude any relationship's to exclude
* @return array The relationship_id and relationship_name in an array
*/
protected function set_relationship_info($exclude = array())
{
$new_rel_id = false;
$new_rel_link = false;
// check incoming data
if (isset($this->not_use_rel_in_req) && $this->not_use_rel_in_req) {
// if we should use relation data from properties (for REQUEST-independent calls)
$rel_id = isset($this->new_rel_id) ? $this->new_rel_id : '';
$rel_link = isset($this->new_rel_relname) ? $this->new_rel_relname : '';
} else {
// if we should use relation data from REQUEST
$rel_id = isset($_REQUEST['relate_id']) ? $_REQUEST['relate_id'] : '';
$rel_link = isset($_REQUEST['relate_to']) ? $_REQUEST['relate_to'] : '';
}
// filter relation data
if ($rel_id && $rel_link && !in_array($rel_link, $exclude) && $rel_id != $this->id) {
$new_rel_id = $rel_id;
$new_rel_link = $rel_link;
// Bug #53223 : wrong relationship from subpanel create button
// if LHSModule and RHSModule are same module use left link to add new item b/s of:
// $rel_id and $rel_link are not empty - request is from subpanel
// $rel_link contains relationship name - checked by call load_relationship
$isRelationshipLoaded = $this->load_relationship($rel_link);
if ($isRelationshipLoaded && !empty($this->$rel_link) && $this->$rel_link->getRelationshipObject()
&& $this->$rel_link->getRelationshipObject()->getLHSModule()
== $this->$rel_link->getRelationshipObject()->getRHSModule()) {
$new_rel_link = $this->$rel_link->getRelationshipObject()->getLHSLink();
} else {
//Try to find the link in this bean based on the relationship
foreach ($this->field_defs as $key => $def) {
if (isset($def['type']) && $def['type'] == 'link' && isset($def['relationship'])
&& $def['relationship'] == $rel_link) {
$new_rel_link = $key;
}
}
}
}
return array($new_rel_id, $new_rel_link);
}
/**
* Handle the preset fields listed in the fixed relationship_fields array hardcoded into the OOB beans
*
* TODO: remove this mechanism and replace with mechanism exclusively based on the vardefs
*
* @api
* @see save_relationship_changes
* @param string|bool $new_rel_id String of the ID to add
* @param string Relationship Name
* @param array $exclude any relationship's to exclude
* @return string|bool Return the new_rel_id if it was not used. False if it was used.
*/
protected function handle_preset_relationships($new_rel_id, $new_rel_link, $exclude = array())
{
if (isset($this->relationship_fields) && is_array($this->relationship_fields)) {
foreach ($this->relationship_fields as $id => $rel_name) {
if (in_array($id, $exclude)) {
continue;
}
if (!empty($this->$id)) {
// Bug #44930 We do not need to update main related field if it is changed from sub-panel.
if ($rel_name == $new_rel_link && $this->$id != $new_rel_id) {
$new_rel_id = '';
}
$GLOBALS['log']->debug('save_relationship_changes(): From relationship_field array - ' .
'adding a relationship record: ' . $rel_name . ' = ' . $this->$id);
//already related the new relationship id so let's set it to false so we don't add it again
// using the _REQUEST['relate_i'] mechanism in a later block
$this->load_relationship($rel_name);
$rel_add = $this->$rel_name->add($this->$id);
// move this around to only take out the id if it was save successfully
if ($this->$id == $new_rel_id && $rel_add) {
$new_rel_id = false;
}
} else {
//if before value is not empty then attempt to delete relationship
if (!empty($this->rel_fields_before_value[$id])) {
$GLOBALS['log']->debug('save_relationship_changes(): From relationship_field array - ' .
'attempting to remove the relationship record, using relationship attribute' . $rel_name);
$this->load_relationship($rel_name);
$this->$rel_name->delete($this->id, $this->rel_fields_before_value[$id]);
}
}
}
}
return $new_rel_id;
}
/**
* Next, we'll attempt to update all of the remaining relate fields in the vardefs that have 'save' set in their
* field_def
* Only the 'save' fields should be saved as some vardef entries today are not for display only purposes
* and break the application if saved
* If the vardef has entries for field <a> of type relate, where a->id_name = <b> and field <b> of type link
* then we receive a value for b from the MVC in the _REQUEST, and it should be set in the bean as $this->$b
*
* @api
* @see save_relationship_changes
* @param array $exclude any relationship's to exclude
* @return array the list of relationships that were added or removed successfully or if they were a failure
*/
protected function handle_remaining_relate_fields($exclude = array())
{
$modified_relationships = array(
'add' => array('success' => array(), 'failure' => array()),
'remove' => array('success' => array(), 'failure' => array()),
);
if (!is_array($this->field_defs) && !is_object($this->field_defs)) {
$GLOBALS['log']->fatal('SugarBean::handle_remaining_relate_fields $field_defs should be an array');
} else {
foreach ((array)$this->field_defs as $def) {
if ((isset($def['type']) && $def ['type'] == 'relate') && isset($def ['id_name'])
&& isset($def ['link']) && isset($def['save'])) {
if (in_array($def['id_name'], $exclude) || in_array($def['id_name'], $this->relationship_fields)) {
// continue to honor the exclude array and exclude any relationships that will be handled
// by the relationship_fields mechanism
continue;
}
$linkField = $def['link'];
if (isset($this->field_defs[$linkField])) {
if ($this->load_relationship($linkField)) {
$idName = $def['id_name'];
if (!empty($this->rel_fields_before_value[$idName]) && empty($this->$idName)) {
//if before value is not empty then attempt to delete relationship
$GLOBALS['log']->debug("save_relationship_changes(): From field_defs - attempting to " .
"remove the relationship record: {$linkField} = " .
"{$this->rel_fields_before_value[$idName]}");
$success = $this->$linkField->delete(
$this->id,
$this->rel_fields_before_value[$idName]
);
// just need to make sure it's true and not an array as it's possible to return an array
if ($success) {
$modified_relationships['remove']['success'][] = $linkField;
} else {
$modified_relationships['remove']['failure'][] = $linkField;
}
$GLOBALS['log']