2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4 * SugarCRM Community Edition is a customer relationship management program developed by
5 * SugarCRM, Inc. Copyright (C) 2004-2012 SugarCRM Inc.
7 * This program is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU Affero General Public License version 3 as published by the
9 * Free Software Foundation with the addition of the following permission added
10 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11 * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
12 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
14 * This program is distributed in the hope that it will be useful, but WITHOUT
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
19 * You should have received a copy of the GNU Affero General Public License along with
20 * this program; if not, see http://www.gnu.org/licenses or write to the Free
21 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
24 * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
25 * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
27 * The interactive user interfaces in modified source and object code versions
28 * of this program must display Appropriate Legal Notices, as required under
29 * Section 5 of the GNU Affero General Public License version 3.
31 * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32 * these Appropriate Legal Notices must retain the display of the "Powered by
33 * SugarCRM" logo. If the display of the logo is not reasonably feasible for
34 * technical reasons, the Appropriate Legal Notices must display the words
35 * "Powered by SugarCRM".
36 ********************************************************************************/
38 /*********************************************************************************
40 * Description: Defines the base class for all data entities used throughout the
41 * application. The base class including its methods and variables is designed to
42 * be overloaded with module-specific methods and variables particular to the
43 * module's base entity class.
44 * Portions created by SugarCRM are Copyright (C) SugarCRM, Inc.
45 * All Rights Reserved.
46 *******************************************************************************/
48 require_once('modules/DynamicFields/DynamicField.php');
49 require_once("data/Relationships/RelationshipFactory.php");
56 * SugarBean is the base class for all business objects in Sugar. It implements
57 * the primary functionality needed for manipulating business objects: create,
58 * retrieve, update, delete. It allows for searching and retrieving list of records.
59 * It allows for retrieving related objects (e.g. contacts related to a specific account).
61 * In the current implementation, there can only be one bean per folder.
62 * Naming convention has the bean name be the same as the module and folder name.
63 * All bean names should be singular (e.g. Contact). The primary table name for
64 * a bean should be plural (e.g. contacts).
70 * A pointer to the database object
77 * When createing a bean, you can specify a value in the id column as
78 * long as that value is unique. During save, if the system finds an
79 * id, it assumes it is an update. Setting new_with_id to true will
80 * make sure the system performs an insert instead of an update.
82 * @var BOOL -- default false
84 var $new_with_id = false;
88 * How deep logic hooks can go
91 protected $max_logic_depth = 10;
94 * Disble vardefs. This should be set to true only for beans that do not have varders. Tracker is an example
96 * @var BOOL -- default false
98 var $disable_vardefs = false;
102 * holds the full name of the user that an item is assigned to. Only used if notifications
103 * are turned on and going to be sent out.
107 var $new_assigned_user_name;
110 * An array of booleans. This array is cleared out when data is loaded.
111 * As date/times are converted, a "1" is placed under the key, the field is converted.
113 * @var Array of booleans
115 var $processed_dates_times = array();
118 * Whether to process date/time fields for storage in the database in GMT
122 var $process_save_dates =true;
125 * This signals to the bean that it is being saved in a mass mode.
126 * Examples of this kind of save are import and mass update.
127 * We turn off notificaitons of this is the case to make things more efficient.
131 var $save_from_post = true;
134 * When running a query on related items using the method: retrieve_by_string_fields
135 * this value will be set to true if more than one item matches the search criteria.
139 var $duplicates_found = false;
142 * true if this bean has been deleted, false otherwise.
149 * Should the date modified column of the bean be updated during save?
150 * This is used for admin level functionality that should not be updating
151 * the date modified. This is only used by sync to allow for updates to be
152 * replicated in a way that will not cause them to be replicated back.
156 var $update_date_modified = true;
159 * Should the modified by column of the bean be updated during save?
160 * This is used for admin level functionality that should not be updating
161 * the modified by column. This is only used by sync to allow for updates to be
162 * replicated in a way that will not cause them to be replicated back.
166 var $update_modified_by = true;
169 * Setting this to true allows for updates to overwrite the date_entered
173 var $update_date_entered = false;
176 * This allows for seed data to be created without using the current uesr to set the id.
177 * This should be replaced by altering the current user before the call to save.
181 //TODO This should be replaced by altering the current user before the call to save.
182 var $set_created_by = true;
187 * The database table where records of this Bean are stored.
191 var $table_name = '';
194 * This is the singular name of the bean. (i.e. Contact).
198 var $object_name = '';
200 /** Set this to true if you query contains a sub-select and bean is converting both select statements
201 * into count queries.
203 var $ungreedy_count=false;
206 * The name of the module folder for this type of bean.
210 var $module_dir = '';
211 var $module_name = '';
215 var $column_fields = array();
216 var $list_fields = array();
217 var $additional_column_fields = array();
218 var $relationship_fields = array();
219 var $current_notify_user;
220 var $fetched_row=false;
222 var $force_load_details = false;
223 var $optimistic_lock = false;
224 var $disable_custom_fields = false;
225 var $number_formatting_done = false;
226 var $process_field_encrypted=false;
228 * The default ACL type
230 var $acltype = 'module';
233 var $additional_meta_fields = array();
236 * Set to true in the child beans if the module supports importing
238 var $importable = false;
241 * Set to true in the child beans if the module use the special notification template
243 var $special_notification = false;
246 * Set to true if the bean is being dealt with in a workflow
248 var $in_workflow = false;
252 * By default it will be true but if any module is to be kept non visible
253 * to tracker, then its value needs to be overriden in that particular module to false.
256 var $tracker_visibility = true;
259 * Used to pass inner join string to ListView Data.
261 var $listview_inner_join = array();
264 * Set to true in <modules>/Import/views/view.step4.php if a module is being imported
266 var $in_import = false;
268 * A way to keep track of the loaded relationships so when we clone the object we can unset them.
272 protected $loaded_relationships = array();
275 * Constructor for the bean, it performs following tasks:
277 * 1. Initalized a database connections
278 * 2. Load the vardefs for the module implemeting the class. cache the entries
280 * 3. Setup row-level security preference
281 * All implementing classes must call this constructor using the parent::SugarBean() class.
286 global $dictionary, $current_user;
287 static $loaded_defs = array();
288 $this->db = DBManagerFactory::getInstance();
289 if (empty($this->module_name))
290 $this->module_name = $this->module_dir;
291 if((false == $this->disable_vardefs && empty($loaded_defs[$this->object_name])) || !empty($GLOBALS['reload_vardefs']))
293 VardefManager::loadVardef($this->module_dir, $this->object_name);
295 // build $this->column_fields from the field_defs if they exist
296 if (!empty($dictionary[$this->object_name]['fields'])) {
297 foreach ($dictionary[$this->object_name]['fields'] as $key=>$value_array) {
298 $column_fields[] = $key;
299 if(!empty($value_array['required']) && !empty($value_array['name'])) {
300 $this->required_fields[$value_array['name']] = 1;
303 $this->column_fields = $column_fields;
306 //setup custom fields
307 if(!isset($this->custom_fields) &&
308 empty($this->disable_custom_fields))
310 $this->setupCustomFields($this->module_dir);
312 //load up field_arrays from CacheHandler;
313 if(empty($this->list_fields))
314 $this->list_fields = $this->_loadCachedArray($this->module_dir, $this->object_name, 'list_fields');
315 if(empty($this->column_fields))
316 $this->column_fields = $this->_loadCachedArray($this->module_dir, $this->object_name, 'column_fields');
317 if(empty($this->required_fields))
318 $this->required_fields = $this->_loadCachedArray($this->module_dir, $this->object_name, 'required_fields');
320 if(isset($GLOBALS['dictionary'][$this->object_name]) && !$this->disable_vardefs)
322 $this->field_name_map = $dictionary[$this->object_name]['fields'];
323 $this->field_defs = $dictionary[$this->object_name]['fields'];
325 if(!empty($dictionary[$this->object_name]['optimistic_locking']))
327 $this->optimistic_lock=true;
330 $loaded_defs[$this->object_name]['column_fields'] =& $this->column_fields;
331 $loaded_defs[$this->object_name]['list_fields'] =& $this->list_fields;
332 $loaded_defs[$this->object_name]['required_fields'] =& $this->required_fields;
333 $loaded_defs[$this->object_name]['field_name_map'] =& $this->field_name_map;
334 $loaded_defs[$this->object_name]['field_defs'] =& $this->field_defs;
338 $this->column_fields =& $loaded_defs[$this->object_name]['column_fields'] ;
339 $this->list_fields =& $loaded_defs[$this->object_name]['list_fields'];
340 $this->required_fields =& $loaded_defs[$this->object_name]['required_fields'];
341 $this->field_name_map =& $loaded_defs[$this->object_name]['field_name_map'];
342 $this->field_defs =& $loaded_defs[$this->object_name]['field_defs'];
343 $this->added_custom_field_defs = true;
345 if(!isset($this->custom_fields) &&
346 empty($this->disable_custom_fields))
348 $this->setupCustomFields($this->module_dir, false);
350 if(!empty($dictionary[$this->object_name]['optimistic_locking']))
352 $this->optimistic_lock=true;
356 if($this->bean_implements('ACL') && !empty($GLOBALS['current_user'])){
357 $this->acl_fields = (isset($dictionary[$this->object_name]['acl_fields']) && $dictionary[$this->object_name]['acl_fields'] === false)?false:true;
359 $this->populateDefaultValues();
364 * Returns the object name. If object_name is not set, table_name is returned.
366 * All implementing classes must set a value for the object_name variable.
368 * @param array $arr row of data fetched from the database.
372 function getObjectName()
374 if ($this->object_name)
375 return $this->object_name;
377 // This is a quick way out. The generated metadata files have the table name
378 // as the key. The correct way to do this is to override this function
379 // in bean and return the object name. That requires changing all the beans
380 // as well as put the object name in the generator.
381 return $this->table_name;
385 * Returns a list of fields with their definitions that have the audited property set to true.
386 * Before calling this function, check whether audit has been enabled for the table/module or not.
387 * You would set the audit flag in the implemting module's vardef file.
389 * @return an array of
390 * @see is_AuditEnabled
392 * Internal function, do not override.
394 function getAuditEnabledFieldDefinitions()
396 $aclcheck = $this->bean_implements('ACL');
397 $is_owner = $this->isOwner($GLOBALS['current_user']->id);
398 if (!isset($this->audit_enabled_fields))
401 $this->audit_enabled_fields=array();
402 foreach ($this->field_defs as $field => $properties)
407 !empty($properties['Audited']) || !empty($properties['audited']))
411 $this->audit_enabled_fields[$field]=$properties;
416 return $this->audit_enabled_fields;
420 * Return true if auditing is enabled for this object
421 * You would set the audit flag in the implemting module's vardef file.
425 * Internal function, do not override.
427 function is_AuditEnabled()
430 if (isset($dictionary[$this->getObjectName()]['audited']))
432 return $dictionary[$this->getObjectName()]['audited'];
443 * Returns the name of the audit table.
444 * Audit table's name is based on implementing class' table name.
446 * @return String Audit table name.
448 * Internal function, do not override.
450 function get_audit_table_name()
452 return $this->getTableName().'_audit';
456 * Returns the name of the custom table.
457 * Custom table's name is based on implementing class' table name.
459 * @return String Custom table name.
461 * Internal function, do not override.
463 public function get_custom_table_name()
465 return $this->getTableName().'_cstm';
469 * If auditing is enabled, create the audit table.
471 * Function is used by the install scripts and a repair utility in the admin panel.
473 * Internal function, do not override.
475 function create_audit_table()
478 $table_name=$this->get_audit_table_name();
480 require('metadata/audit_templateMetaData.php');
482 // Bug: 52583 Need ability to customize template for audit tables
483 $custom = 'custom/metadata/audit_templateMetaData_' . $this->getTableName() . '.php';
484 if (file_exists($custom))
489 $fieldDefs = $dictionary['audit']['fields'];
490 $indices = $dictionary['audit']['indices'];
492 // Renaming template indexes to fit the particular audit table (removed the brittle hard coding)
493 foreach($indices as $nr => $properties){
494 $indices[$nr]['name'] = 'idx_' . strtolower($this->getTableName()) . '_' . $properties['name'];
498 if(isset($dictionary['audit']['engine'])) {
499 $engine = $dictionary['audit']['engine'];
500 } else if(isset($dictionary[$this->getObjectName()]['engine'])) {
501 $engine = $dictionary[$this->getObjectName()]['engine'];
504 $this->db->createTableParams($table_name, $fieldDefs, $indices, $engine);
508 * Returns the implementing class' table name.
510 * All implementing classes set a value for the table_name variable. This value is returned as the
511 * table name. If not set, table name is extracted from the implementing module's vardef.
513 * @return String Table name.
515 * Internal function, do not override.
517 public function getTableName()
519 if(isset($this->table_name))
521 return $this->table_name;
524 return $dictionary[$this->getObjectName()]['table'];
528 * Returns field definitions for the implementing module.
530 * The definitions were loaded in the constructor.
532 * @return Array Field definitions.
534 * Internal function, do not override.
536 function getFieldDefinitions()
538 return $this->field_defs;
542 * Returns index definitions for the implementing module.
544 * The definitions were loaded in the constructor.
546 * @return Array Index definitions.
548 * Internal function, do not override.
550 function getIndices()
553 if(isset($dictionary[$this->getObjectName()]['indices']))
555 return $dictionary[$this->getObjectName()]['indices'];
561 * Returns field definition for the requested field name.
563 * The definitions were loaded in the constructor.
565 * @param string field name,
566 * @return Array Field properties or boolean false if the field doesn't exist
568 * Internal function, do not override.
570 function getFieldDefinition($name)
572 if ( !isset($this->field_defs[$name]) )
575 return $this->field_defs[$name];
579 * Returnss definition for the id field name.
581 * The definitions were loaded in the constructor.
583 * @return Array Field properties.
585 * Internal function, do not override.
587 function getPrimaryFieldDefinition()
589 $def = $this->getFieldDefinition("id");
591 $def = $this->getFieldDefinition(0);
594 $defs = $this->field_defs;
596 $def = current($defs);
601 * Returns the value for the requested field.
603 * When a row of data is fetched using the bean, all fields are created as variables in the context
604 * of the bean and then fetched values are set in these variables.
606 * @param string field name,
607 * @return varies Field value.
609 * Internal function, do not override.
611 function getFieldValue($name)
613 if (!isset($this->$name)){
616 if($this->$name === TRUE){
619 if($this->$name === FALSE){
626 * Basically undoes the effects of SugarBean::populateDefaultValues(); this method is best called right after object
629 public function unPopulateDefaultValues()
631 if ( !is_array($this->field_defs) )
634 foreach ($this->field_defs as $field => $value) {
635 if( !empty($this->$field)
636 && ((isset($value['default']) && $this->$field == $value['default']) || (!empty($value['display_default']) && $this->$field == $value['display_default']))
638 $this->$field = null;
641 if(!empty($this->$field) && !empty($value['display_default']) && in_array($value['type'], array('date', 'datetime', 'datetimecombo')) &&
642 $this->$field == $this->parseDateDefault($value['display_default'], ($value['type'] != 'date'))) {
643 $this->$field = null;
649 * Create date string from default value
651 * @param string $value
652 * @param bool $time Should be expect time set too?
655 protected function parseDateDefault($value, $time = false)
659 $dtAry = explode('&', $value, 2);
660 $dateValue = $timedate->getNow(true)->modify($dtAry[0]);
661 if(!empty($dtAry[1])) {
662 $timeValue = $timedate->fromString($dtAry[1]);
663 $dateValue->setTime($timeValue->hour, $timeValue->min, $timeValue->sec);
665 return $timedate->asUser($dateValue);
667 return $timedate->asUserDate($timedate->getNow(true)->modify($value));
671 function populateDefaultValues($force=false){
672 if ( !is_array($this->field_defs) )
674 foreach($this->field_defs as $field=>$value){
675 if((isset($value['default']) || !empty($value['display_default'])) && ($force || empty($this->$field))){
676 $type = $value['type'];
680 if(!empty($value['display_default'])){
681 $this->$field = $this->parseDateDefault($value['display_default']);
685 case 'datetimecombo':
686 if(!empty($value['display_default'])){
687 $this->$field = $this->parseDateDefault($value['display_default'], true);
691 if(empty($value['default']) && !empty($value['display_default']))
692 $this->$field = $value['display_default'];
694 $this->$field = $value['default'];
697 if(isset($this->$field)){
701 if ( isset($value['default']) && $value['default'] !== '' ) {
702 $this->$field = htmlentities($value['default'], ENT_QUOTES, 'UTF-8');
713 * Removes relationship metadata cache.
715 * Every module that has relationships defined with other modules, has this meta data cached. The cache is
716 * stores in 2 locations: relationships table and file system. This method clears the cache from both locations.
718 * @param string $key module whose meta cache is to be cleared.
719 * @param string $db database handle.
720 * @param string $tablename table name
721 * @param string $dictionary vardef for the module
722 * @param string $module_dir name of subdirectory where module is installed.
727 * Internal function, do not override.
729 function removeRelationshipMeta($key,$db,$tablename,$dictionary,$module_dir)
731 //load the module dictionary if not supplied.
732 if ((!isset($dictionary) or empty($dictionary)) && !empty($module_dir))
734 $filename='modules/'. $module_dir . '/vardefs.php';
735 if(file_exists($filename))
740 if (!is_array($dictionary) or !array_key_exists($key, $dictionary))
742 $GLOBALS['log']->fatal("removeRelationshipMeta: Metadata for table ".$tablename. " does not exist");
743 display_notice("meta data absent for table ".$tablename." keyed to $key ");
747 if (isset($dictionary[$key]['relationships']))
749 $RelationshipDefs = $dictionary[$key]['relationships'];
750 foreach ($RelationshipDefs as $rel_name)
752 Relationship::delete($rel_name,$db);
760 * This method has been deprecated.
762 * @see removeRelationshipMeta()
763 * @deprecated 4.5.1 - Nov 14, 2006
766 function remove_relationship_meta($key,$db,$log,$tablename,$dictionary,$module_dir)
768 SugarBean::removeRelationshipMeta($key,$db,$tablename,$dictionary,$module_dir);
773 * Populates the relationship meta for a module.
775 * It is called during setup/install. It is used statically to create relationship meta data for many-to-many tables.
777 * @param string $key name of the object.
778 * @param object $db database handle.
779 * @param string $tablename table, meta data is being populated for.
780 * @param array dictionary vardef dictionary for the object. *
781 * @param string module_dir name of subdirectory where module is installed.
782 * @param boolean $iscustom Optional,set to true if module is installed in a custom directory. Default value is false.
785 * Internal function, do not override.
787 function createRelationshipMeta($key,$db,$tablename,$dictionary,$module_dir,$iscustom=false)
789 //load the module dictionary if not supplied.
790 if (empty($dictionary) && !empty($module_dir))
794 $filename='custom/modules/' . $module_dir . '/Ext/Vardefs/vardefs.ext.php';
800 // a very special case for the Employees module
801 // this must be done because the Employees/vardefs.php does an include_once on
803 $filename='modules/Users/vardefs.php';
807 $filename='modules/'. $module_dir . '/vardefs.php';
811 if(file_exists($filename))
814 // cn: bug 7679 - dictionary entries defined as $GLOBALS['name'] not found
815 if(empty($dictionary) || !empty($GLOBALS['dictionary'][$key]))
817 $dictionary = $GLOBALS['dictionary'];
822 $GLOBALS['log']->debug("createRelationshipMeta: no metadata file found" . $filename);
827 if (!is_array($dictionary) or !array_key_exists($key, $dictionary))
829 $GLOBALS['log']->fatal("createRelationshipMeta: Metadata for table ".$tablename. " does not exist");
830 display_notice("meta data absent for table ".$tablename." keyed to $key ");
834 if (isset($dictionary[$key]['relationships']))
837 $RelationshipDefs = $dictionary[$key]['relationships'];
841 $beanList_ucase=array_change_key_case ( $beanList ,CASE_UPPER);
842 foreach ($RelationshipDefs as $rel_name=>$rel_def)
844 if (isset($rel_def['lhs_module']) and !isset($beanList_ucase[strtoupper($rel_def['lhs_module'])])) {
845 $GLOBALS['log']->debug('skipping orphaned relationship record ' . $rel_name . ' lhs module is missing ' . $rel_def['lhs_module']);
848 if (isset($rel_def['rhs_module']) and !isset($beanList_ucase[strtoupper($rel_def['rhs_module'])])) {
849 $GLOBALS['log']->debug('skipping orphaned relationship record ' . $rel_name . ' rhs module is missing ' . $rel_def['rhs_module']);
854 //check whether relationship exists or not first.
855 if (Relationship::exists($rel_name,$db))
857 $GLOBALS['log']->debug('Skipping, reltionship already exists '.$rel_name);
861 $seed = BeanFactory::getBean("Relationships");
862 $keys = array_keys($seed->field_defs);
864 foreach($keys as $key)
868 $toInsert[$key] = create_guid();
870 else if ($key == "relationship_name")
872 $toInsert[$key] = $rel_name;
874 else if (isset($rel_def[$key]))
876 $toInsert[$key] = $rel_def[$key];
878 //todo specify defaults if meta not defined.
882 $column_list = implode(",", array_keys($toInsert));
883 $value_list = "'" . implode("','", array_values($toInsert)) . "'";
885 //create the record. todo add error check.
886 $insert_string = "INSERT into relationships (" .$column_list. ") values (".$value_list.")";
887 $db->query($insert_string, true);
894 //log informational message stating no relationships meta was set for this bean.
900 * This method has been deprecated.
901 * @see createRelationshipMeta()
902 * @deprecated 4.5.1 - Nov 14, 2006
905 function create_relationship_meta($key,&$db,&$log,$tablename,$dictionary,$module_dir)
907 SugarBean::createRelationshipMeta($key,$db,$tablename,$dictionary,$module_dir);
912 * Handle the following when a SugarBean object is cloned
914 * Currently all this does it unset any relationships that were created prior to cloning the object
918 public function __clone()
920 if(!empty($this->loaded_relationships)) {
921 foreach($this->loaded_relationships as $rel) {
929 * Loads the request relationship. This method should be called before performing any operations on the related data.
931 * This method searches the vardef array for the requested attribute's definition. If the attribute is of the type
932 * link then it creates a similary named variable and loads the relationship definition.
934 * @param string $rel_name relationship/attribute name.
937 function load_relationship($rel_name)
939 $GLOBALS['log']->debug("SugarBean[{$this->object_name}].load_relationships, Loading relationship (".$rel_name.").");
941 if (empty($rel_name))
943 $GLOBALS['log']->error("SugarBean.load_relationships, Null relationship name passed.");
946 $fieldDefs = $this->getFieldDefinitions();
948 //find all definitions of type link.
949 if (!empty($fieldDefs[$rel_name]))
951 //initialize a variable of type Link
952 require_once('data/Link2.php');
953 $class = load_link_class($fieldDefs[$rel_name]);
954 if (isset($this->$rel_name) && $this->$rel_name instanceof $class) {
957 //if rel_name is provided, search the fieldef array keys by name.
958 if (isset($fieldDefs[$rel_name]['type']) && $fieldDefs[$rel_name]['type'] == 'link')
960 if ($class == "Link2")
961 $this->$rel_name = new $class($rel_name, $this);
963 $this->$rel_name = new $class($fieldDefs[$rel_name]['relationship'], $this, $fieldDefs[$rel_name]);
965 if (empty($this->$rel_name) ||
966 (method_exists($this->$rel_name, "loadedSuccesfully") && !$this->$rel_name->loadedSuccesfully()))
968 unset($this->$rel_name);
971 // keep track of the loaded relationships
972 $this->loaded_relationships[] = $rel_name;
976 $GLOBALS['log']->debug("SugarBean.load_relationships, Error Loading relationship (".$rel_name.")");
981 * Loads all attributes of type link.
983 * DO NOT CALL THIS FUNCTION IF YOU CAN AVOID IT. Please use load_relationship directly instead.
985 * Method searches the implmenting module's vardef file for attributes of type link, and for each attribute
986 * create a similary named variable and load the relationship definition.
990 * Internal function, do not override.
992 function load_relationships()
994 $GLOBALS['log']->debug("SugarBean.load_relationships, Loading all relationships of type link.");
995 $linked_fields=$this->get_linked_fields();
996 foreach($linked_fields as $name=>$properties)
998 $this->load_relationship($name);
1003 * Returns an array of beans of related data.
1005 * For instance, if an account is related to 10 contacts , this function will return an array of contacts beans (10)
1006 * with each bean representing a contact record.
1007 * Method will load the relationship if not done so already.
1009 * @param string $field_name relationship to be loaded.
1010 * @param string $bean name class name of the related bean.
1011 * @param array $sort_array optional, unused
1012 * @param int $begin_index Optional, default 0, unused.
1013 * @param int $end_index Optional, default -1
1014 * @param int $deleted Optional, Default 0, 0 adds deleted=0 filter, 1 adds deleted=1 filter.
1015 * @param string $optional_where, Optional, default empty.
1017 * Internal function, do not override.
1019 function get_linked_beans($field_name,$bean_name, $sort_array = array(), $begin_index = 0, $end_index = -1,
1020 $deleted=0, $optional_where="")
1022 //if bean_name is Case then use aCase
1023 if($bean_name=="Case")
1024 $bean_name = "aCase";
1026 if($this->load_relationship($field_name)) {
1027 if ($this->$field_name instanceof Link) {
1028 // some classes are still based on Link, e.g. TeamSetLink
1029 return array_values($this->$field_name->getBeans(new $bean_name(), $sort_array, $begin_index, $end_index, $deleted, $optional_where));
1032 if ($end_index != -1 || !empty($deleted) || !empty($optional_where))
1033 return array_values($this->$field_name->getBeans(array(
1034 'where' => $optional_where,
1035 'deleted' => $deleted,
1036 'limit' => ($end_index - $begin_index)
1039 return array_values($this->$field_name->getBeans());
1047 * Returns an array of fields that are of type link.
1049 * @return array List of fields.
1051 * Internal function, do not override.
1053 function get_linked_fields()
1056 $linked_fields=array();
1058 // require_once('data/Link.php');
1060 $fieldDefs = $this->getFieldDefinitions();
1062 //find all definitions of type link.
1063 if (!empty($fieldDefs))
1065 foreach ($fieldDefs as $name=>$properties)
1067 if (array_search('link',$properties) === 'type')
1069 $linked_fields[$name]=$properties;
1074 return $linked_fields;
1078 * Returns an array of fields that are able to be Imported into
1079 * i.e. 'importable' not set to 'false'
1081 * @return array List of fields.
1083 * Internal function, do not override.
1085 function get_importable_fields()
1087 $importableFields = array();
1089 $fieldDefs= $this->getFieldDefinitions();
1091 if (!empty($fieldDefs)) {
1092 foreach ($fieldDefs as $key=>$value_array) {
1093 if ( (isset($value_array['importable'])
1094 && (is_string($value_array['importable']) && $value_array['importable'] == 'false'
1095 || is_bool($value_array['importable']) && $value_array['importable'] == false))
1096 || (isset($value_array['type']) && $value_array['type'] == 'link')
1097 || (isset($value_array['auto_increment'])
1098 && ($value_array['type'] == true || $value_array['type'] == 'true')) ) {
1099 // only allow import if we force it
1100 if (isset($value_array['importable'])
1101 && (is_string($value_array['importable']) && $value_array['importable'] == 'true'
1102 || is_bool($value_array['importable']) && $value_array['importable'] == true)) {
1103 $importableFields[$key]=$value_array;
1107 $importableFields[$key]=$value_array;
1112 return $importableFields;
1116 * Returns an array of fields that are of type relate.
1118 * @return array List of fields.
1120 * Internal function, do not override.
1122 function get_related_fields()
1125 $related_fields=array();
1127 // require_once('data/Link.php');
1129 $fieldDefs = $this->getFieldDefinitions();
1131 //find all definitions of type link.
1132 if (!empty($fieldDefs))
1134 foreach ($fieldDefs as $name=>$properties)
1136 if (array_search('relate',$properties) === 'type')
1138 $related_fields[$name]=$properties;
1143 return $related_fields;
1147 * Returns an array of fields that are required for import
1151 function get_import_required_fields()
1153 $importable_fields = $this->get_importable_fields();
1154 $required_fields = array();
1156 foreach ( $importable_fields as $name => $properties ) {
1157 if ( isset($properties['importable']) && is_string($properties['importable']) && $properties['importable'] == 'required' ) {
1158 $required_fields[$name] = $properties;
1162 return $required_fields;
1166 * Iterates through all the relationships and deletes all records for reach relationship.
1168 * @param string $id Primary key value of the parent reocrd
1170 function delete_linked($id)
1172 $linked_fields=$this->get_linked_fields();
1173 foreach ($linked_fields as $name => $value)
1175 if ($this->load_relationship($name))
1177 $this->$name->delete($id);
1181 $GLOBALS['log']->fatal("error loading relationship $name");
1187 * Creates tables for the module implementing the class.
1188 * If you override this function make sure that your code can handles table creation.
1191 function create_tables()
1195 $key = $this->getObjectName();
1196 if (!array_key_exists($key, $dictionary))
1198 $GLOBALS['log']->fatal("create_tables: Metadata for table ".$this->table_name. " does not exist");
1199 display_notice("meta data absent for table ".$this->table_name." keyed to $key ");
1203 if(!$this->db->tableExists($this->table_name))
1205 $this->db->createTable($this);
1206 if($this->bean_implements('ACL')){
1207 if(!empty($this->acltype)){
1208 ACLAction::addActions($this->getACLCategory(), $this->acltype);
1210 ACLAction::addActions($this->getACLCategory());
1216 echo "Table already exists : $this->table_name<br>";
1218 if($this->is_AuditEnabled()){
1219 if (!$this->db->tableExists($this->get_audit_table_name())) {
1220 $this->create_audit_table();
1228 * Delete the primary table for the module implementing the class.
1229 * If custom fields were added to this table/module, the custom table will be removed too, along with the cache
1230 * entries that define the custom fields.
1233 function drop_tables()
1236 $key = $this->getObjectName();
1237 if (!array_key_exists($key, $dictionary))
1239 $GLOBALS['log']->fatal("drop_tables: Metadata for table ".$this->table_name. " does not exist");
1240 echo "meta data absent for table ".$this->table_name."<br>\n";
1242 if(empty($this->table_name))return;
1243 if ($this->db->tableExists($this->table_name))
1245 $this->db->dropTable($this);
1246 if ($this->db->tableExists($this->table_name. '_cstm'))
1248 $this->db->dropTableName($this->table_name. '_cstm');
1249 DynamicField::deleteCache();
1251 if ($this->db->tableExists($this->get_audit_table_name())) {
1252 $this->db->dropTableName($this->get_audit_table_name());
1261 * Loads the definition of custom fields defined for the module.
1262 * Local file system cache is created as needed.
1264 * @param string $module_name setting up custom fields for this module.
1265 * @param boolean $clean_load Optional, default true, rebuilds the cache if set to true.
1267 function setupCustomFields($module_name, $clean_load=true)
1269 $this->custom_fields = new DynamicField($module_name);
1270 $this->custom_fields->setup($this);
1275 * Cleans char, varchar, text, etc. fields of XSS type materials
1277 function cleanBean() {
1278 foreach($this->field_defs as $key => $def) {
1280 if (isset($def['type'])) {
1283 if(isset($def['dbType']))
1284 $type .= $def['dbType'];
1286 if($def['type'] == 'html') {
1287 $this->$key = SugarCleaner::cleanHtml($this->$key, true);
1288 } elseif((strpos($type, 'char') !== false ||
1289 strpos($type, 'text') !== false ||
1293 $this->$key = SugarCleaner::cleanHtml($this->$key);
1299 * Implements a generic insert and update logic for any SugarBean
1300 * This method only works for subclasses that implement the same variable names.
1301 * This method uses the presence of an id field that is not null to signify and update.
1302 * The id field should not be set otherwise.
1304 * @param boolean $check_notify Optional, default false, if set to true assignee of the record is notified via email.
1305 * @todo Add support for field type validation and encoding of parameters.
1307 function save($check_notify = FALSE)
1309 $this->in_save = true;
1310 // cn: SECURITY - strip XSS potential vectors
1312 // This is used so custom/3rd-party code can be upgraded with fewer issues, this will be removed in a future release
1313 $this->fixUpFormatting();
1315 global $current_user, $action;
1318 if(empty($this->id))
1323 if ( $this->new_with_id == true )
1327 if(empty($this->date_modified) || $this->update_date_modified)
1329 $this->date_modified = $GLOBALS['timedate']->nowDb();
1332 $this->_checkOptimisticLocking($action, $isUpdate);
1334 if(!empty($this->modified_by_name)) $this->old_modified_by_name = $this->modified_by_name;
1335 if($this->update_modified_by)
1337 $this->modified_user_id = 1;
1339 if (!empty($current_user))
1341 $this->modified_user_id = $current_user->id;
1342 $this->modified_by_name = $current_user->user_name;
1345 if ($this->deleted != 1)
1349 if (empty($this->date_entered))
1351 $this->date_entered = $this->date_modified;
1353 if($this->set_created_by == true)
1355 // created by should always be this user
1356 $this->created_by = (isset($current_user)) ? $current_user->id : "";
1358 if( $this->new_with_id == false)
1360 $this->id = create_guid();
1366 require_once("data/BeanFactory.php");
1367 BeanFactory::registerBean($this->module_name, $this);
1369 if (empty($GLOBALS['updating_relationships']) && empty($GLOBALS['saving_relationships']) && empty ($GLOBALS['resavingRelatedBeans']))
1371 $GLOBALS['saving_relationships'] = true;
1372 // let subclasses save related field changes
1373 $this->save_relationship_changes($isUpdate);
1374 $GLOBALS['saving_relationships'] = false;
1376 if($isUpdate && !$this->update_date_entered)
1378 unset($this->date_entered);
1380 // call the custom business logic
1381 $custom_logic_arguments['check_notify'] = $check_notify;
1384 $this->call_custom_logic("before_save", $custom_logic_arguments);
1385 unset($custom_logic_arguments);
1387 if(isset($this->custom_fields))
1389 $this->custom_fields->bean = $this;
1390 $this->custom_fields->save($isUpdate);
1393 // use the db independent query generator
1394 $this->preprocess_fields_on_save();
1396 //construct the SQL to create the audit record if auditing is enabled.
1397 $dataChanges=array();
1398 if ($this->is_AuditEnabled()) {
1399 if ($isUpdate && !isset($this->fetched_row)) {
1400 $GLOBALS['log']->debug('Auditing: Retrieve was not called, audit record will not be created.');
1402 $dataChanges=$this->db->getDataChanges($this);
1406 $this->_sendNotifications($check_notify);
1409 $this->db->update($this);
1411 $this->db->insert($this);
1414 if (!empty($dataChanges) && is_array($dataChanges))
1416 foreach ($dataChanges as $change)
1418 $this->db->save_audit_records($this,$change);
1423 if (empty($GLOBALS['resavingRelatedBeans'])){
1424 SugarRelationship::resaveRelatedBeans();
1428 //If we aren't in setup mode and we have a current user and module, then we track
1429 if(isset($GLOBALS['current_user']) && isset($this->module_dir))
1431 $this->track_view($current_user->id, $this->module_dir, 'save');
1434 $this->call_custom_logic('after_save', '');
1436 //Now that the record has been saved, we don't want to insert again on further saves
1437 $this->new_with_id = false;
1438 $this->in_save = false;
1444 * Performs a check if the record has been modified since the specified date
1446 * @param date $date Datetime for verification
1447 * @param string $modified_user_id User modified by
1449 function has_been_modified_since($date, $modified_user_id)
1451 global $current_user;
1452 $date = $this->db->convert($this->db->quoted($date), 'datetime');
1453 if (isset($current_user))
1455 $query = "SELECT date_modified FROM $this->table_name WHERE id='$this->id' AND modified_user_id != '$current_user->id'
1456 AND (modified_user_id != '$modified_user_id' OR date_modified > $date)";
1457 $result = $this->db->query($query);
1459 if($this->db->fetchByAssoc($result))
1468 * Determines which users receive a notification
1470 function get_notification_recipients() {
1471 $notify_user = new User();
1472 $notify_user->retrieve($this->assigned_user_id);
1473 $this->new_assigned_user_name = $notify_user->full_name;
1475 $GLOBALS['log']->info("Notifications: recipient is $this->new_assigned_user_name");
1477 $user_list = array($notify_user);
1480 //send notifications to followers, but ensure to not query for the assigned_user.
1481 return SugarFollowing::getFollowers($this, $notify_user);
1486 * Handles sending out email notifications when items are first assigned to users
1488 * @param string $notify_user user to notify
1489 * @param string $admin the admin user that sends out the notification
1491 function send_assignment_notifications($notify_user, $admin)
1493 global $current_user;
1495 if(($this->object_name == 'Meeting' || $this->object_name == 'Call') || $notify_user->receive_notifications)
1497 $sendToEmail = $notify_user->emailAddress->getPrimaryAddress($notify_user);
1499 if(empty($sendToEmail)) {
1500 $GLOBALS['log']->warn("Notifications: no e-mail address set for user {$notify_user->user_name}, cancelling send");
1504 $notify_mail = $this->create_notification_email($notify_user);
1505 $notify_mail->setMailerForSystem();
1507 if(empty($admin->settings['notify_send_from_assigning_user'])) {
1508 $notify_mail->From = $admin->settings['notify_fromaddress'];
1509 $notify_mail->FromName = (empty($admin->settings['notify_fromname'])) ? "" : $admin->settings['notify_fromname'];
1511 // Send notifications from the current user's e-mail (if set)
1512 $fromAddress = $current_user->emailAddress->getReplyToAddress($current_user);
1513 $fromAddress = !empty($fromAddress) ? $fromAddress : $admin->settings['notify_fromaddress'];
1514 $notify_mail->From = $fromAddress;
1515 //Use the users full name is available otherwise default to system name
1516 $from_name = !empty($admin->settings['notify_fromname']) ? $admin->settings['notify_fromname'] : "";
1517 $from_name = !empty($current_user->full_name) ? $current_user->full_name : $from_name;
1518 $notify_mail->FromName = $from_name;
1521 $oe = new OutboundEmail();
1522 $oe = $oe->getUserMailerSettings($current_user);
1523 //only send if smtp server is defined
1525 $smtpVerified = false;
1527 //first check the user settings
1528 if(!empty($oe->mail_smtpserver)){
1529 $smtpVerified = true;
1532 //if still not verified, check against the system settings
1533 if (!$smtpVerified){
1534 $oe = $oe->getSystemMailerSettings();
1535 if(!empty($oe->mail_smtpserver)){
1536 $smtpVerified = true;
1539 //if smtp was not verified against user or system, then do not send out email
1540 if (!$smtpVerified){
1541 $GLOBALS['log']->fatal("Notifications: error sending e-mail, smtp server was not found ");
1546 if(!$notify_mail->Send()) {
1547 $GLOBALS['log']->fatal("Notifications: error sending e-mail (method: {$notify_mail->Mailer}), (error: {$notify_mail->ErrorInfo})");
1549 $GLOBALS['log']->info("Notifications: e-mail successfully sent");
1557 * This function handles create the email notifications email.
1558 * @param string $notify_user the user to send the notification email to
1560 function create_notification_email($notify_user) {
1561 global $sugar_version;
1562 global $sugar_config;
1563 global $app_list_strings;
1564 global $current_user;
1567 $OBCharset = $locale->getPrecedentPreference('default_email_charset');
1570 require_once("include/SugarPHPMailer.php");
1572 $notify_address = $notify_user->emailAddress->getPrimaryAddress($notify_user);
1573 $notify_name = $notify_user->full_name;
1574 $GLOBALS['log']->debug("Notifications: user has e-mail defined");
1576 $notify_mail = new SugarPHPMailer();
1577 $notify_mail->AddAddress($notify_address,$locale->translateCharsetMIME(trim($notify_name), 'UTF-8', $OBCharset));
1579 if(empty($_SESSION['authenticated_user_language'])) {
1580 $current_language = $sugar_config['default_language'];
1582 $current_language = $_SESSION['authenticated_user_language'];
1584 $xtpl = new XTemplate(get_notify_template_file($current_language));
1585 if($this->module_dir == "Cases") {
1586 $template_name = "Case"; //we should use Case, you can refer to the en_us.notify_template.html.
1589 $template_name = $beanList[$this->module_dir]; //bug 20637, in workflow this->object_name = strange chars.
1592 $this->current_notify_user = $notify_user;
1594 if(in_array('set_notification_body', get_class_methods($this))) {
1595 $xtpl = $this->set_notification_body($xtpl, $this);
1597 $xtpl->assign("OBJECT", translate('LBL_MODULE_NAME'));
1598 $template_name = "Default";
1600 if(!empty($_SESSION["special_notification"]) && $_SESSION["special_notification"]) {
1601 $template_name = $beanList[$this->module_dir].'Special';
1603 if($this->special_notification) {
1604 $template_name = $beanList[$this->module_dir].'Special';
1606 $xtpl->assign("ASSIGNED_USER", $this->new_assigned_user_name);
1607 $xtpl->assign("ASSIGNER", $current_user->name);
1610 if(isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) {
1611 $port = $_SERVER['SERVER_PORT'];
1614 if (!isset($_SERVER['HTTP_HOST'])) {
1615 $_SERVER['HTTP_HOST'] = '';
1618 $httpHost = $_SERVER['HTTP_HOST'];
1620 if($colon = strpos($httpHost, ':')) {
1621 $httpHost = substr($httpHost, 0, $colon);
1624 $parsedSiteUrl = parse_url($sugar_config['site_url']);
1625 $host = $parsedSiteUrl['host'];
1626 if(!isset($parsedSiteUrl['port'])) {
1627 $parsedSiteUrl['port'] = 80;
1630 $port = ($parsedSiteUrl['port'] != 80) ? ":".$parsedSiteUrl['port'] : '';
1631 $path = !empty($parsedSiteUrl['path']) ? $parsedSiteUrl['path'] : "";
1632 $cleanUrl = "{$parsedSiteUrl['scheme']}://{$host}{$port}{$path}";
1634 $xtpl->assign("URL", $cleanUrl."/index.php?module={$this->module_dir}&action=DetailView&record={$this->id}");
1635 $xtpl->assign("SUGAR", "Sugar v{$sugar_version}");
1636 $xtpl->parse($template_name);
1637 $xtpl->parse($template_name . "_Subject");
1639 $notify_mail->Body = from_html(trim($xtpl->text($template_name)));
1640 $notify_mail->Subject = from_html($xtpl->text($template_name . "_Subject"));
1642 // cn: bug 8568 encode notify email in User's outbound email encoding
1643 $notify_mail->prepForOutbound();
1645 return $notify_mail;
1649 * This function is a good location to save changes that have been made to a relationship.
1650 * This should be overriden in subclasses that have something to save.
1652 * @param $is_update true if this save is an update.
1654 function save_relationship_changes($is_update, $exclude=array())
1656 $new_rel_id = false;
1657 $new_rel_link = false;
1659 // check incoming data
1660 if(isset($this->not_use_rel_in_req) && $this->not_use_rel_in_req)
1662 // if we should use relation data from properties (for REQUEST-independent calls)
1663 $rel_id=isset($this->new_rel_id) ? $this->new_rel_id : '';
1664 $rel_link=isset($this->new_rel_relname) ? $this->new_rel_relname : '';
1668 // if we should use relation data from REQUEST
1669 $rel_id=isset($_REQUEST['relate_id']) ? $_REQUEST['relate_id'] : '';
1670 $rel_link=isset($_REQUEST['relate_to']) ? $_REQUEST['relate_to'] : '';
1673 // filter relation data
1674 if($rel_id && $rel_link && !in_array($rel_link, $exclude) && $rel_id != $this->id)
1676 $new_rel_id = $rel_id;
1677 $new_rel_link = $rel_link;
1678 // Bug #53223 : wrong relationship from subpanel create button
1679 // if LHSModule and RHSModule are same module use left link to add new item b/s of:
1680 // $rel_id and $rel_link are not emty - request is from subpanel
1681 // $rel_link contains relationship name - checked by call load_relationship
1682 $this->load_relationship($rel_link);
1683 if ( !empty($this->$rel_link) && $this->$rel_link->getRelationshipObject() && $this->$rel_link->getRelationshipObject()->getLHSModule() == $this->$rel_link->getRelationshipObject()->getRHSModule() )
1685 $new_rel_link = $this->$rel_link->getRelationshipObject()->getLHSLink();
1689 //Try to find the link in this bean based on the relationship
1690 foreach ($this->field_defs as $key => $def)
1692 if (isset($def['type']) && $def['type'] == 'link' && isset($def['relationship']) && $def['relationship'] == $rel_link)
1694 $new_rel_link = $key;
1701 // First we handle the preset fields listed in the fixed relationship_fields array hardcoded into the OOB beans
1702 // TODO: remove this mechanism and replace with mechanism exclusively based on the vardefs
1703 if (isset($this->relationship_fields) && is_array($this->relationship_fields))
1705 foreach ($this->relationship_fields as $id=>$rel_name)
1708 if(in_array($id, $exclude))continue;
1710 if(!empty($this->$id))
1712 // Bug #44930 We do not need to update main related field if it is changed from sub-panel.
1713 if ($rel_name == $new_rel_link && $this->$id != $new_rel_id)
1717 $GLOBALS['log']->debug('save_relationship_changes(): From relationship_field array - adding a relationship record: '.$rel_name . ' = ' . $this->$id);
1718 //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
1719 if($this->$id == $new_rel_id){
1720 $new_rel_id = false;
1722 $this->load_relationship($rel_name);
1723 $this->$rel_name->add($this->$id);
1728 //if before value is not empty then attempt to delete relationship
1729 if(!empty($this->rel_fields_before_value[$id]))
1731 $GLOBALS['log']->debug('save_relationship_changes(): From relationship_field array - attempting to remove the relationship record, using relationship attribute'.$rel_name);
1732 $this->load_relationship($rel_name);
1733 $this->$rel_name->delete($this->id,$this->rel_fields_before_value[$id]);
1739 /* Next, we'll attempt to update all of the remaining relate fields in the vardefs that have 'save' set in their field_def
1740 Only the 'save' fields should be saved as some vardef entries today are not for display only purposes and break the application if saved
1741 If the vardef has entries for field <a> of type relate, where a->id_name = <b> and field <b> of type link
1742 then we receive a value for b from the MVC in the _REQUEST, and it should be set in the bean as $this->$b
1745 foreach ( $this->field_defs as $def )
1747 if ($def [ 'type' ] == 'relate' && isset ( $def [ 'id_name'] ) && isset ( $def [ 'link'] ) && isset ( $def[ 'save' ]) )
1749 if ( in_array( $def['id_name'], $exclude) || in_array( $def['id_name'], $this->relationship_fields ) )
1750 continue ; // continue to honor the exclude array and exclude any relationships that will be handled by the relationship_fields mechanism
1752 $linkField = $def [ 'link' ] ;
1753 if (isset( $this->field_defs[$linkField ] ))
1755 $linkfield = $this->field_defs[$linkField] ;
1757 if ($this->load_relationship ( $linkField))
1759 $idName = $def['id_name'];
1761 if (!empty($this->rel_fields_before_value[$idName]) && empty($this->$idName))
1763 //if before value is not empty then attempt to delete relationship
1764 $GLOBALS['log']->debug("save_relationship_changes(): From field_defs - attempting to remove the relationship record: {$def [ 'link' ]} = {$this->rel_fields_before_value[$def [ 'id_name' ]]}");
1765 $this->$def ['link' ]->delete($this->id, $this->rel_fields_before_value[$def [ 'id_name' ]] );
1768 if (!empty($this->$idName) && is_string($this->$idName))
1770 $GLOBALS['log']->debug("save_relationship_changes(): From field_defs - attempting to add a relationship record - {$def [ 'link' ]} = {$this->$def [ 'id_name' ]}" );
1772 $this->$linkField->add($this->$idName);
1775 $GLOBALS['log']->fatal("Failed to load relationship {$linkField} while saving {$this->module_dir}");
1781 // Finally, we update a field listed in the _REQUEST['*/relate_id']/_REQUEST['relate_to'] mechanism (if it hasn't already been updated above)
1782 if(!empty($new_rel_id)){
1784 if($this->load_relationship($new_rel_link)){
1785 $this->$new_rel_link->add($new_rel_id);
1788 $lower_link = strtolower($new_rel_link);
1789 if($this->load_relationship($lower_link)){
1790 $this->$lower_link->add($new_rel_id);
1793 require_once('data/Link2.php');
1794 $rel = Relationship::retrieve_by_modules($new_rel_link, $this->module_dir, $GLOBALS['db'], 'many-to-many');
1797 foreach($this->field_defs as $field=>$def){
1798 if($def['type'] == 'link' && !empty($def['relationship']) && $def['relationship'] == $rel){
1799 $this->load_relationship($field);
1800 $this->$field->add($new_rel_id);
1806 //ok so we didn't find it in the field defs let's save it anyway if we have the relationshp
1808 $this->$rel=new Link2($rel, $this, array());
1809 $this->$rel->add($new_rel_id);
1818 * This function retrieves a record of the appropriate type from the DB.
1819 * It fills in all of the fields from the DB into the object it was called on.
1821 * @param $id - If ID is specified, it overrides the current value of $this->id. If not specified the current value of $this->id will be used.
1822 * @return this - The object that it was called apon or null if exactly 1 record was not found.
1826 function check_date_relationships_load()
1828 global $disable_date_format;
1830 if (empty($timedate))
1831 $timedate=TimeDate::getInstance();
1833 if(empty($this->field_defs))
1837 foreach($this->field_defs as $fieldDef)
1839 $field = $fieldDef['name'];
1840 if(!isset($this->processed_dates_times[$field]))
1842 $this->processed_dates_times[$field] = '1';
1843 if(empty($this->$field)) continue;
1844 if($field == 'date_modified' || $field == 'date_entered')
1846 $this->$field = $this->db->fromConvert($this->$field, 'datetime');
1847 if(empty($disable_date_format)) {
1848 $this->$field = $timedate->to_display_date_time($this->$field);
1851 elseif(isset($this->field_name_map[$field]['type']))
1853 $type = $this->field_name_map[$field]['type'];
1855 if($type == 'relate' && isset($this->field_name_map[$field]['custom_module']))
1857 $type = $this->field_name_map[$field]['type'];
1862 if($this->$field == '0000-00-00')
1865 } elseif(!empty($this->field_name_map[$field]['rel_field']))
1867 $rel_field = $this->field_name_map[$field]['rel_field'];
1869 if(!empty($this->$rel_field))
1871 if(empty($disable_date_format)) {
1872 $mergetime = $timedate->merge_date_time($this->$field,$this->$rel_field);
1873 $this->$field = $timedate->to_display_date($mergetime);
1874 $this->$rel_field = $timedate->to_display_time($mergetime);
1880 if(empty($disable_date_format)) {
1881 $this->$field = $timedate->to_display_date($this->$field, false);
1884 } elseif($type == 'datetime' || $type == 'datetimecombo')
1886 if($this->$field == '0000-00-00 00:00:00')
1892 if(empty($disable_date_format)) {
1893 $this->$field = $timedate->to_display_date_time($this->$field, true, true);
1896 } elseif($type == 'time')
1898 if($this->$field == '00:00:00')
1903 //$this->$field = from_db_convert($this->$field, 'time');
1904 if(empty($this->field_name_map[$field]['rel_field']) && empty($disable_date_format))
1906 $this->$field = $timedate->to_display_time($this->$field,true, false);
1909 } elseif($type == 'encrypt' && empty($disable_date_format)){
1910 $this->$field = $this->decrypt_after_retrieve($this->$field);
1918 * This function processes the fields before save.
1919 * Interal function, do not override.
1921 function preprocess_fields_on_save()
1923 $GLOBALS['log']->deprecated('SugarBean.php: preprocess_fields_on_save() is deprecated');
1927 * Removes formatting from values posted from the user interface.
1928 * It only unformats numbers. Function relies on user/system prefernce for format strings.
1930 * Internal Function, do not override.
1932 function unformat_all_fields()
1934 $GLOBALS['log']->deprecated('SugarBean.php: unformat_all_fields() is deprecated');
1938 * This functions adds formatting to all number fields before presenting them to user interface.
1940 * Internal function, do not override.
1942 function format_all_fields()
1944 $GLOBALS['log']->deprecated('SugarBean.php: format_all_fields() is deprecated');
1947 function format_field($fieldDef)
1949 $GLOBALS['log']->deprecated('SugarBean.php: format_field() is deprecated');
1953 * Function corrects any bad formatting done by 3rd party/custom code
1955 * 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
1957 function fixUpFormatting()
1960 static $boolean_false_values = array('off', 'false', '0', 'no');
1963 foreach($this->field_defs as $field=>$def)
1965 if ( !isset($this->$field) ) {
1968 if ( (isset($def['source'])&&$def['source']=='non-db') || $field == 'deleted' ) {
1971 if ( isset($this->fetched_row[$field]) && $this->$field == $this->fetched_row[$field] ) {
1972 // Don't hand out warnings because the field was untouched between retrieval and saving, most database drivers hand pretty much everything back as strings.
1975 $reformatted = false;
1976 switch($def['type']) {
1978 case 'datetimecombo':
1979 if(empty($this->$field)) break;
1980 if ($this->$field == 'NULL') {
1984 if ( ! preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$/',$this->$field) ) {
1985 // This appears to be formatted in user date/time
1986 $this->$field = $timedate->to_db($this->$field);
1987 $reformatted = true;
1991 if(empty($this->$field)) break;
1992 if ($this->$field == 'NULL') {
1996 if ( ! preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/',$this->$field) ) {
1997 // This date appears to be formatted in the user's format
1998 $this->$field = $timedate->to_db_date($this->$field, false);
1999 $reformatted = true;
2003 if(empty($this->$field)) break;
2004 if ($this->$field == 'NULL') {
2008 if ( preg_match('/(am|pm)/i',$this->$field) ) {
2009 // This time appears to be formatted in the user's format
2010 $this->$field = $timedate->fromUserTime($this->$field)->format(TimeDate::DB_TIME_FORMAT);
2011 $reformatted = true;
2018 if ( $this->$field === '' || $this->$field == NULL || $this->$field == 'NULL') {
2021 if ( is_string($this->$field) ) {
2022 $this->$field = (float)unformat_number($this->$field);
2023 $reformatted = true;
2032 if ( $this->$field === '' || $this->$field == NULL || $this->$field == 'NULL') {
2035 if ( is_string($this->$field) ) {
2036 $this->$field = (int)unformat_number($this->$field);
2037 $reformatted = true;
2041 if (empty($this->$field)) {
2042 $this->$field = false;
2043 } else if(true === $this->$field || 1 == $this->$field) {
2044 $this->$field = true;
2045 } else if(in_array(strval($this->$field), $boolean_false_values)) {
2046 $this->$field = false;
2047 $reformatted = true;
2049 $this->$field = true;
2050 $reformatted = true;
2054 $this->$field = $this->encrpyt_before_save($this->$field);
2057 if ( $reformatted ) {
2058 $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');
2065 * Function fetches a single row of data given the primary key value.
2067 * The fetched data is then set into the bean. The function also processes the fetched data by formattig
2068 * date/time and numeric values.
2070 * @param string $id Optional, default -1, is set to -1 id value from the bean is used, else, passed value is used
2071 * @param boolean $encode Optional, default true, encodes the values fetched from the database.
2072 * @param boolean $deleted Optional, default true, if set to false deleted filter will not be added.
2074 * Internal function, do not override.
2076 function retrieve($id = -1, $encode=true,$deleted=true)
2079 $custom_logic_arguments['id'] = $id;
2080 $this->call_custom_logic('before_retrieve', $custom_logic_arguments);
2086 if(isset($this->custom_fields))
2088 $custom_join = $this->custom_fields->getJOIN();
2091 $custom_join = false;
2095 $query = "SELECT $this->table_name.*". $custom_join['select']. " FROM $this->table_name ";
2099 $query = "SELECT $this->table_name.* FROM $this->table_name ";
2104 $query .= ' ' . $custom_join['join'];
2106 $query .= " WHERE $this->table_name.id = ".$this->db->quoted($id);
2107 if ($deleted) $query .= " AND $this->table_name.deleted=0";
2108 $GLOBALS['log']->debug("Retrieve $this->object_name : ".$query);
2109 $result = $this->db->limitQuery($query,0,1,true, "Retrieving record by id $this->table_name:$id found ");
2115 $row = $this->db->fetchByAssoc($result, $encode);
2121 //make copy of the fetched row for construction of audit record and for business logic/workflow
2122 $row = $this->convertRow($row);
2123 $this->fetched_row=$row;
2124 $this->populateFromRow($row);
2126 global $module, $action;
2127 //Just to get optimistic locking working for this release
2128 if($this->optimistic_lock && $module == $this->module_dir && $action =='EditView' )
2130 $_SESSION['o_lock_id']= $id;
2131 $_SESSION['o_lock_dm']= $this->date_modified;
2132 $_SESSION['o_lock_on'] = $this->object_name;
2134 $this->processed_dates_times = array();
2135 $this->check_date_relationships_load();
2139 $this->custom_fields->fill_relationships();
2142 $this->fill_in_additional_detail_fields();
2143 $this->fill_in_relationship_fields();
2144 //make a copy of fields in the relationship_fields array. These field values will be used to
2145 //clear relationship.
2146 foreach ( $this->field_defs as $key => $def )
2148 if ($def [ 'type' ] == 'relate' && isset ( $def [ 'id_name'] ) && isset ( $def [ 'link'] ) && isset ( $def[ 'save' ])) {
2149 if (isset($this->$key)) {
2150 $this->rel_fields_before_value[$key]=$this->$key;
2151 if (isset($this->$def [ 'id_name']))
2152 $this->rel_fields_before_value[$def [ 'id_name']]=$this->$def [ 'id_name'];
2155 $this->rel_fields_before_value[$key]=null;
2158 if (isset($this->relationship_fields) && is_array($this->relationship_fields))
2160 foreach ($this->relationship_fields as $rel_id=>$rel_name)
2162 if (isset($this->$rel_id))
2163 $this->rel_fields_before_value[$rel_id]=$this->$rel_id;
2165 $this->rel_fields_before_value[$rel_id]=null;
2169 // call the custom business logic
2170 $custom_logic_arguments['id'] = $id;
2171 $custom_logic_arguments['encode'] = $encode;
2172 $this->call_custom_logic("after_retrieve", $custom_logic_arguments);
2173 unset($custom_logic_arguments);
2178 * Sets value from fetched row into the bean.
2180 * @param array $row Fetched row
2181 * @todo loop through vardefs instead
2182 * @internal runs into an issue when populating from field_defs for users - corrupts user prefs
2184 * Internal function, do not override.
2186 function populateFromRow($row)
2189 foreach($this->field_defs as $field=>$field_value)
2191 if($field == 'user_preferences' && $this->module_dir == 'Users')
2193 if(isset($row[$field]))
2195 $this->$field = $row[$field];
2196 $owner = $field . '_owner';
2197 if(!empty($row[$owner])){
2198 $this->$owner = $row[$owner];
2203 $this->$field = $nullvalue;
2211 * Add any required joins to the list count query. The joins are required if there
2212 * is a field in the $where clause that needs to be joined.
2214 * @param string $query
2215 * @param string $where
2217 * Internal Function, do Not override.
2219 function add_list_count_joins(&$query, $where)
2221 $custom_join = $this->custom_fields->getJOIN();
2224 $query .= $custom_join['join'];
2230 * Changes the select expression of the given query to be 'count(*)' so you
2231 * can get the number of items the query will return. This is used to
2232 * populate the upper limit on ListViews.
2234 * @param string $query Select query string
2235 * @return string count query
2237 * Internal function, do not override.
2239 function create_list_count_query($query)
2241 // remove the 'order by' clause which is expected to be at the end of the query
2242 $pattern = '/\sORDER BY.*/is'; // ignores the case
2244 $query = preg_replace($pattern, $replacement, $query);
2245 //handle distinct clause
2247 if(substr_count(strtolower($query), 'distinct')){
2248 if (!empty($this->seed) && !empty($this->seed->table_name ))
2249 $star = 'DISTINCT ' . $this->seed->table_name . '.id';
2251 $star = 'DISTINCT ' . $this->table_name . '.id';
2255 // change the select expression to 'count(*)'
2256 $pattern = '/SELECT(.*?)(\s){1}FROM(\s){1}/is'; // ignores the case
2257 $replacement = 'SELECT count(' . $star . ') c FROM ';
2259 //if the passed query has union clause then replace all instances of the pattern.
2260 //this is very rare. I have seen this happening only from projects module.
2261 //in addition to this added a condition that has union clause and uses
2263 if (strstr($query," UNION ALL ") !== false) {
2265 //separate out all the queries.
2266 $union_qs=explode(" UNION ALL ", $query);
2267 foreach ($union_qs as $key=>$union_query) {
2269 preg_match($pattern, $union_query, $matches);
2270 if (!empty($matches)) {
2271 if (stristr($matches[0], "distinct")) {
2272 if (!empty($this->seed) && !empty($this->seed->table_name ))
2273 $star = 'DISTINCT ' . $this->seed->table_name . '.id';
2275 $star = 'DISTINCT ' . $this->table_name . '.id';
2278 $replacement = 'SELECT count(' . $star . ') c FROM ';
2279 $union_qs[$key] = preg_replace($pattern, $replacement, $union_query,1);
2281 $modified_select_query=implode(" UNION ALL ",$union_qs);
2283 $modified_select_query = preg_replace($pattern, $replacement, $query,1);
2287 return $modified_select_query;
2291 * This function returns a paged list of the current object type. It is intended to allow for
2292 * hopping back and forth through pages of data. It only retrieves what is on the current page.
2294 * @internal This method must be called on a new instance. It trashes the values of all the fields in the current one.
2295 * @param string $order_by
2296 * @param string $where Additional where clause
2297 * @param int $row_offset Optaional,default 0, starting row number
2298 * @param init $limit Optional, default -1
2299 * @param int $max Optional, default -1
2300 * @param boolean $show_deleted Optional, default 0, if set to 1 system will show deleted records.
2301 * @return array Fetched data.
2303 * Internal function, do not override.
2306 function get_list($order_by = "", $where = "", $row_offset = 0, $limit=-1, $max=-1, $show_deleted = 0, $singleSelect=false, $select_fields = array())
2308 $GLOBALS['log']->debug("get_list: order_by = '$order_by' and where = '$where' and limit = '$limit'");
2309 if(isset($_SESSION['show_deleted']))
2313 $order_by=$this->process_order_by($order_by);
2315 if($this->bean_implements('ACL') && ACLController::requireOwner($this->module_dir, 'list') )
2317 global $current_user;
2318 $owner_where = $this->getOwnerWhere($current_user->id);
2320 //rrs - because $this->getOwnerWhere() can return '' we need to be sure to check for it and
2321 //handle it properly else you could get into a situation where you are create a where stmt like
2323 if(!empty($owner_where)){
2325 $where = $owner_where;
2327 $where .= ' AND '. $owner_where;
2331 $query = $this->create_new_list_query($order_by, $where,$select_fields,array(), $show_deleted,'',false,null,$singleSelect);
2332 return $this->process_list_query($query, $row_offset, $limit, $max, $where);
2336 * Prefixes column names with this bean's table name.
2338 * @param string $order_by Order by clause to be processed
2339 * @param SugarBean $submodule name of the module this order by clause is for
2340 * @return string Processed order by clause
2342 * Internal function, do not override.
2344 function process_order_by ($order_by, $submodule = null)
2346 if (empty($order_by))
2348 //submodule is empty,this is for list object in focus
2349 if (empty($submodule))
2351 $bean_queried = $this;
2355 //submodule is set, so this is for subpanel, use submodule
2356 $bean_queried = $submodule;
2358 $elements = explode(',',$order_by);
2359 foreach ($elements as $key=>$value)
2361 if (strchr($value,'.') === false)
2363 //value might have ascending and descending decorations
2364 $list_column = explode(' ',trim($value));
2365 if (isset($list_column[0]))
2367 $list_column_name=trim($list_column[0]);
2368 if (isset($bean_queried->field_defs[$list_column_name]))
2370 $source=isset($bean_queried->field_defs[$list_column_name]['source']) ? $bean_queried->field_defs[$list_column_name]['source']:'db';
2371 if (empty($bean_queried->field_defs[$list_column_name]['table']) && $source=='db')
2373 $list_column[0] = $bean_queried->table_name .".".$list_column[0] ;
2375 if (empty($bean_queried->field_defs[$list_column_name]['table']) && $source=='custom_fields')
2377 $list_column[0] = $bean_queried->table_name ."_cstm.".$list_column[0] ;
2379 // Bug 38803 - Use CONVERT() function when doing an order by on ntext, text, and image fields
2380 if ($source != 'non-db' && $this->db->isTextType($this->db->getFieldType($bean_queried->field_defs[$list_column_name]))) {
2381 $list_column[0] = $this->db->convert($list_column[0], "text2char");
2383 $value = implode(' ',$list_column);
2385 $GLOBALS['log']->debug("process_order_by: ($list_column[0]) does not have a vardef entry.");
2389 $elements[$key]=$value;
2391 return implode(',', $elements);
2397 * Returns a detail object like retrieving of the current object type.
2399 * It is intended for use in navigation buttons on the DetailView. It will pass an offset and limit argument to the sql query.
2400 * @internal This method must be called on a new instance. It overrides the values of all the fields in the current one.
2402 * @param string $order_by
2403 * @param string $where Additional where clause
2404 * @param int $row_offset Optaional,default 0, starting row number
2405 * @param init $limit Optional, default -1
2406 * @param int $max Optional, default -1
2407 * @param boolean $show_deleted Optioanl, default 0, if set to 1 system will show deleted records.
2408 * @return array Fetched data.
2410 * Internal function, do not override.
2412 function get_detail($order_by = "", $where = "", $offset = 0, $row_offset = 0, $limit=-1, $max=-1, $show_deleted = 0)
2414 $GLOBALS['log']->debug("get_detail: order_by = '$order_by' and where = '$where' and limit = '$limit' and offset = '$offset'");
2415 if(isset($_SESSION['show_deleted']))
2420 if($this->bean_implements('ACL') && ACLController::requireOwner($this->module_dir, 'list') )
2422 global $current_user;
2423 $owner_where = $this->getOwnerWhere($current_user->id);
2427 $where = $owner_where;
2431 $where .= ' AND '. $owner_where;
2434 $query = $this->create_new_list_query($order_by, $where,array(),array(), $show_deleted, $offset);
2436 //Add Limit and Offset to query
2437 //$query .= " LIMIT 1 OFFSET $offset";
2439 return $this->process_detail_query($query, $row_offset, $limit, $max, $where, $offset);
2443 * Fetches data from all related tables.
2445 * @param object $child_seed
2446 * @param string $related_field_name relation to fetch data for
2447 * @param string $order_by Optional, default empty
2448 * @param string $where Optional, additional where clause
2449 * @return array Fetched data.
2451 * Internal function, do not override.
2453 function get_related_list($child_seed,$related_field_name, $order_by = "", $where = "",
2454 $row_offset = 0, $limit=-1, $max=-1, $show_deleted = 0)
2456 global $layout_edit_mode;
2457 if(isset($layout_edit_mode) && $layout_edit_mode)
2459 $response = array();
2460 $child_seed->assign_display_fields($child_seed->module_dir);
2461 $response['list'] = array($child_seed);
2462 $response['row_count'] = 1;
2463 $response['next_offset'] = 0;
2464 $response['previous_offset'] = 0;
2468 $GLOBALS['log']->debug("get_related_list: order_by = '$order_by' and where = '$where' and limit = '$limit'");
2469 if(isset($_SESSION['show_deleted']))
2474 $this->load_relationship($related_field_name);
2475 $query_array = $this->$related_field_name->getQuery(true);
2476 $entire_where = $query_array['where'];
2479 if(empty($entire_where))
2481 $entire_where = ' WHERE ' . $where;
2485 $entire_where .= ' AND ' . $where;
2489 $query = 'SELECT '.$child_seed->table_name.'.* ' . $query_array['from'] . ' ' . $entire_where;
2490 if(!empty($order_by))
2492 $query .= " ORDER BY " . $order_by;
2495 return $child_seed->process_list_query($query, $row_offset, $limit, $max, $where);
2499 protected static function build_sub_queries_for_union($subpanel_list, $subpanel_def, $parentbean, $order_by)
2501 global $layout_edit_mode, $beanFiles, $beanList;
2502 $subqueries = array();
2503 foreach($subpanel_list as $this_subpanel)
2505 if(!$this_subpanel->isDatasourceFunction() || ($this_subpanel->isDatasourceFunction()
2506 && isset($this_subpanel->_instance_properties['generate_select'])
2507 && $this_subpanel->_instance_properties['generate_select']==true))
2509 //the custom query function must return an array with
2510 if ($this_subpanel->isDatasourceFunction()) {
2511 $shortcut_function_name = $this_subpanel->get_data_source_name();
2512 $parameters=$this_subpanel->get_function_parameters();
2513 if (!empty($parameters))
2515 //if the import file function is set, then import the file to call the custom function from
2516 if (is_array($parameters) && isset($parameters['import_function_file'])){
2517 //this call may happen multiple times, so only require if function does not exist
2518 if(!function_exists($shortcut_function_name)){
2519 require_once($parameters['import_function_file']);
2521 //call function from required file
2522 $query_array = $shortcut_function_name($parameters);
2524 //call function from parent bean
2525 $query_array = $parentbean->$shortcut_function_name($parameters);
2530 $query_array = $parentbean->$shortcut_function_name();
2533 $related_field_name = $this_subpanel->get_data_source_name();
2534 if (!$parentbean->load_relationship($related_field_name)){
2535 unset ($parentbean->$related_field_name);
2538 $query_array = $parentbean->$related_field_name->getSubpanelQuery(array(), true);
2540 $table_where = $this_subpanel->get_where();
2541 $where_definition = $query_array['where'];
2543 if(!empty($table_where))
2545 if(empty($where_definition))
2547 $where_definition = $table_where;
2551 $where_definition .= ' AND ' . $table_where;
2555 $submodulename = $this_subpanel->_instance_properties['module'];
2556 $submoduleclass = $beanList[$submodulename];
2557 //require_once($beanFiles[$submoduleclass]);
2558 $submodule = new $submoduleclass();
2559 $subwhere = $where_definition;
2563 $subwhere = str_replace('WHERE', '', $subwhere);
2564 $list_fields = $this_subpanel->get_list_fields();
2565 foreach($list_fields as $list_key=>$list_field)
2567 if(isset($list_field['usage']) && $list_field['usage'] == 'display_only')
2569 unset($list_fields[$list_key]);
2574 if(!$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'))
2576 $order_by = $submodule->table_name .'.'. $order_by;
2578 $table_name = $this_subpanel->table_name;
2579 $panel_name=$this_subpanel->name;
2581 $params['distinct'] = $this_subpanel->distinct_query();
2583 $params['joined_tables'] = $query_array['join_tables'];
2584 $params['include_custom_fields'] = !$subpanel_def->isCollection();
2585 $params['collection_list'] = $subpanel_def->get_inst_prop_value('collection_list');
2587 $subquery = $submodule->create_new_list_query('',$subwhere ,$list_fields,$params, 0,'', true,$parentbean);
2589 $subquery['select'] = $subquery['select']." , '$panel_name' panel_name ";
2590 $subquery['from'] = $subquery['from'].$query_array['join'];
2591 $subquery['query_array'] = $query_array;
2592 $subquery['params'] = $params;
2594 $subqueries[] = $subquery;
2601 * Constructs a query to fetch data for supanels and list views
2603 * It constructs union queries for activities subpanel.
2605 * @param SugarBean $parentbean constructing queries for link attributes in this bean
2606 * @param string $order_by Optional, order by clause
2607 * @param string $sort_order Optional, sort order
2608 * @param string $where Optional, additional where clause
2610 * Internal Function, do not overide.
2612 function get_union_related_list($parentbean, $order_by = "", $sort_order='', $where = "",
2613 $row_offset = 0, $limit=-1, $max=-1, $show_deleted = 0, $subpanel_def)
2615 $secondary_queries = array();
2616 global $layout_edit_mode, $beanFiles, $beanList;
2618 if(isset($_SESSION['show_deleted']))
2623 $final_query_rows = '';
2624 $subpanel_list=array();
2625 if ($subpanel_def->isCollection())
2627 $subpanel_def->load_sub_subpanels();
2628 $subpanel_list=$subpanel_def->sub_subpanels;
2632 $subpanel_list[]=$subpanel_def;
2637 //Breaking the building process into two loops. The first loop gets a list of all the sub-queries.
2638 //The second loop merges the queries and forces them to select the same number of columns
2639 //All columns in a sub-subpanel group must have the same aliases
2640 //If the subpanel is a datasource function, it can't be a collection so we just poll that function for the and return that
2641 foreach($subpanel_list as $this_subpanel)
2643 if($this_subpanel->isDatasourceFunction() && empty($this_subpanel->_instance_properties['generate_select']))
2645 $shortcut_function_name = $this_subpanel->get_data_source_name();
2646 $parameters=$this_subpanel->get_function_parameters();
2647 if (!empty($parameters))
2649 //if the import file function is set, then import the file to call the custom function from
2650 if (is_array($parameters) && isset($parameters['import_function_file'])){
2651 //this call may happen multiple times, so only require if function does not exist
2652 if(!function_exists($shortcut_function_name)){
2653 require_once($parameters['import_function_file']);
2655 //call function from required file
2656 $tmp_final_query = $shortcut_function_name($parameters);
2658 //call function from parent bean
2659 $tmp_final_query = $parentbean->$shortcut_function_name($parameters);
2662 $tmp_final_query = $parentbean->$shortcut_function_name();
2666 $final_query_rows .= ' UNION ALL ( '.$parentbean->create_list_count_query($tmp_final_query, $parameters) . ' )';
2667 $final_query .= ' UNION ALL ( '.$tmp_final_query . ' )';
2669 $final_query_rows = '(' . $parentbean->create_list_count_query($tmp_final_query, $parameters) . ')';
2670 $final_query = '(' . $tmp_final_query . ')';
2675 //If final_query is still empty, its time to build the sub-queries
2676 if (empty($final_query))
2678 $subqueries = SugarBean::build_sub_queries_for_union($subpanel_list, $subpanel_def, $parentbean, $order_by);
2679 $all_fields = array();
2680 foreach($subqueries as $i => $subquery)
2682 $query_fields = $GLOBALS['db']->getSelectFieldsFromQuery($subquery['select']);
2683 foreach($query_fields as $field => $select)
2685 if (!in_array($field, $all_fields))
2686 $all_fields[] = $field;
2688 $subqueries[$i]['query_fields'] = $query_fields;
2691 //Now ensure the queries have the same set of fields in the same order.
2692 foreach($subqueries as $subquery)
2694 $subquery['select'] = "SELECT";
2695 foreach($all_fields as $field)
2697 if (!isset($subquery['query_fields'][$field]))
2699 $subquery['select'] .= " ' ' $field,";
2703 $subquery['select'] .= " {$subquery['query_fields'][$field]},";
2706 $subquery['select'] = substr($subquery['select'], 0 , strlen($subquery['select']) - 1);
2707 //Put the query into the final_query
2708 $query = $subquery['select'] . " " . $subquery['from'] . " " . $subquery['where'];
2711 $query = ' UNION ALL ( '.$query . ' )';
2712 $final_query_rows .= " UNION ALL ";
2714 $query = '(' . $query . ')';
2717 $query_array = $subquery['query_array'];
2718 $select_position=strpos($query_array['select'],"SELECT");
2719 $distinct_position=strpos($query_array['select'],"DISTINCT");
2720 if ($select_position !== false && $distinct_position!= false)
2722 $query_rows = "( ".substr_replace($query_array['select'],"SELECT count(",$select_position,6). ")" . $subquery['from_min'].$query_array['join']. $subquery['where'].' )';
2726 //resort to default behavior.
2727 $query_rows = "( SELECT count(*)". $subquery['from_min'].$query_array['join']. $subquery['where'].' )';
2729 if(!empty($subquery['secondary_select']))
2732 $subquerystring= $subquery['secondary_select'] . $subquery['secondary_from'].$query_array['join']. $subquery['where'];
2733 if (!empty($subquery['secondary_where']))
2735 if (empty($subquery['where']))
2737 $subquerystring.=" WHERE " .$subquery['secondary_where'];
2741 $subquerystring.=" AND " .$subquery['secondary_where'];
2744 $secondary_queries[]=$subquerystring;
2746 $final_query .= $query;
2747 $final_query_rows .= $query_rows;
2751 if(!empty($order_by))
2754 if(!$subpanel_def->isCollection())
2756 $submodulename = $subpanel_def->_instance_properties['module'];
2757 $submoduleclass = $beanList[$submodulename];
2758 $submodule = new $submoduleclass();
2760 if(!empty($submodule) && !empty($submodule->table_name))
2762 $final_query .= " ORDER BY " .$parentbean->process_order_by($order_by, $submodule);
2767 $final_query .= " ORDER BY ". $order_by . ' ';
2769 if(!empty($sort_order))
2771 $final_query .= ' ' .$sort_order;
2776 if(isset($layout_edit_mode) && $layout_edit_mode)
2778 $response = array();
2779 if(!empty($submodule))
2781 $submodule->assign_display_fields($submodule->module_dir);
2782 $response['list'] = array($submodule);
2786 $response['list'] = array();
2788 $response['parent_data'] = array();
2789 $response['row_count'] = 1;
2790 $response['next_offset'] = 0;
2791 $response['previous_offset'] = 0;
2796 return $parentbean->process_union_list_query($parentbean, $final_query, $row_offset, $limit, $max, '',$subpanel_def, $final_query_rows, $secondary_queries);
2801 * Returns a full (ie non-paged) list of the current object type.
2803 * @param string $order_by the order by SQL parameter. defaults to ""
2804 * @param string $where where clause. defaults to ""
2805 * @param boolean $check_dates. defaults to false
2806 * @param int $show_deleted show deleted records. defaults to 0
2808 function get_full_list($order_by = "", $where = "", $check_dates=false, $show_deleted = 0)
2810 $GLOBALS['log']->debug("get_full_list: order_by = '$order_by' and where = '$where'");
2811 if(isset($_SESSION['show_deleted']))
2815 $query = $this->create_new_list_query($order_by, $where,array(),array(), $show_deleted);
2816 return $this->process_full_list_query($query, $check_dates);
2820 * Return the list query used by the list views and export button. Next generation of create_new_list_query function.
2822 * Override this function to return a custom query.
2824 * @param string $order_by custom order by clause
2825 * @param string $where custom where clause
2826 * @param array $filter Optioanal
2827 * @param array $params Optional *
2828 * @param int $show_deleted Optional, default 0, show deleted records is set to 1.
2829 * @param string $join_type
2830 * @param boolean $return_array Optional, default false, response as array
2831 * @param object $parentbean creating a subquery for this bean.
2832 * @param boolean $singleSelect Optional, default false.
2833 * @return String select query string, optionally an array value will be returned if $return_array= true.
2835 function create_new_list_query($order_by, $where,$filter=array(),$params=array(), $show_deleted = 0,$join_type='', $return_array = false,$parentbean=null, $singleSelect = false, $ifListForExport = false)
2837 global $beanFiles, $beanList;
2838 $selectedFields = array();
2839 $secondarySelectedFields = array();
2840 $ret_array = array();
2842 if($this->bean_implements('ACL') && ACLController::requireOwner($this->module_dir, 'list') )
2844 global $current_user;
2845 $owner_where = $this->getOwnerWhere($current_user->id);
2848 $where = $owner_where;
2852 $where .= ' AND '. $owner_where;
2855 if(!empty($params['distinct']))
2857 $distinct = ' DISTINCT ';
2861 $ret_array['select'] = " SELECT $distinct $this->table_name.* ";
2865 $ret_array['select'] = " SELECT $distinct $this->table_name.id ";
2867 $ret_array['from'] = " FROM $this->table_name ";
2868 $ret_array['from_min'] = $ret_array['from'];
2869 $ret_array['secondary_from'] = $ret_array['from'] ;
2870 $ret_array['where'] = '';
2871 $ret_array['order_by'] = '';
2872 //secondary selects are selects that need to be run after the primary query to retrieve additional info on main
2875 $ret_array['secondary_select']=& $ret_array['select'];
2876 $ret_array['secondary_from'] = & $ret_array['from'];
2880 $ret_array['secondary_select'] = '';
2882 $custom_join = false;
2883 if((!isset($params['include_custom_fields']) || $params['include_custom_fields']) && isset($this->custom_fields))
2886 $custom_join = $this->custom_fields->getJOIN( empty($filter)? true: $filter );
2889 $ret_array['select'] .= ' ' .$custom_join['select'];
2894 $ret_array['from'] .= ' ' . $custom_join['join'];
2895 // Bug 52490 - Captivea (Sve) - To be able to add custom fields inside where clause in a subpanel
2896 $ret_array['from_min'] .= ' ' . $custom_join['join'];
2899 //LOOP AROUND FOR FIXIN VARDEF ISSUES
2900 require('include/VarDefHandler/listvardefoverride.php');
2901 if (file_exists('custom/include/VarDefHandler/listvardefoverride.php'))
2903 require('custom/include/VarDefHandler/listvardefoverride.php');
2906 $joined_tables = array();
2907 if(!empty($params['joined_tables']))
2909 foreach($params['joined_tables'] as $table)
2911 $joined_tables[$table] = 1;
2917 $filterKeys = array_keys($filter);
2918 if(is_numeric($filterKeys[0]))
2921 foreach($filter as $field)
2923 $field = strtolower($field);
2924 //remove out id field so we don't duplicate it
2925 if ( $field == 'id' && !empty($filter) ) {
2928 if(isset($this->field_defs[$field]))
2930 $fields[$field]= $this->field_defs[$field];
2934 $fields[$field] = array('force_exists'=>true);
2943 $fields = $this->field_defs;
2946 $used_join_key = array();
2948 foreach($fields as $field=>$value)
2950 //alias is used to alias field names
2952 if (isset($value['alias']))
2954 $alias =' as ' . $value['alias'] . ' ';
2957 if(empty($this->field_defs[$field]) || !empty($value['force_blank']) )
2959 if(!empty($filter) && isset($filter[$field]['force_exists']) && $filter[$field]['force_exists'])
2961 if ( isset($filter[$field]['force_default']) )
2962 $ret_array['select'] .= ", {$filter[$field]['force_default']} $field ";
2964 //spaces are a fix for length issue problem with unions. The union only returns the maximum number of characters from the first select statement.
2965 $ret_array['select'] .= ", ' ' $field ";
2971 $data = $this->field_defs[$field];
2974 //ignore fields that are a part of the collection and a field has been removed as a result of
2975 //layout customization.. this happens in subpanel customizations, use case, from the contacts subpanel
2976 //in opportunities module remove the contact_role/opportunity_role field.
2977 $process_field=true;
2978 if (isset($data['relationship_fields']) and !empty($data['relationship_fields']))
2980 foreach ($data['relationship_fields'] as $field_name)
2982 if (!isset($fields[$field_name]))
2984 $process_field=false;
2988 if (!$process_field)
2993 if( (!isset($data['source']) || $data['source'] == 'db') && (!empty($alias) || !empty($filter) ))
2995 $ret_array['select'] .= ", $this->table_name.$field $alias";
2996 $selectedFields["$this->table_name.$field"] = true;
2997 } else if( (!isset($data['source']) || $data['source'] == 'custom_fields') && (!empty($alias) || !empty($filter) )) {
2998 //add this column only if it has NOT already been added to select statement string
2999 $colPos = strpos($ret_array['select'],"$this->table_name"."_cstm".".$field");
3000 if(!$colPos || $colPos<0)
3002 $ret_array['select'] .= ", $this->table_name"."_cstm".".$field $alias";
3005 $selectedFields["$this->table_name.$field"] = true;
3008 if($data['type'] != 'relate' && isset($data['db_concat_fields']))
3010 $ret_array['select'] .= ", " . $this->db->concat($this->table_name, $data['db_concat_fields']) . " as $field";
3011 $selectedFields[$this->db->concat($this->table_name, $data['db_concat_fields'])] = true;
3013 //Custom relate field or relate fields built in module builder which have no link field associated.
3014 if ($data['type'] == 'relate' && (isset($data['custom_module']) || isset($data['ext2']))) {
3015 $joinTableAlias = 'jt' . $jtcount;
3016 $relateJoinInfo = $this->custom_fields->getRelateJoin($data, $joinTableAlias);
3017 $ret_array['select'] .= $relateJoinInfo['select'];
3018 $ret_array['from'] .= $relateJoinInfo['from'];
3019 //Replace any references to the relationship in the where clause with the new alias
3020 //If the link isn't set, assume that search used the local table for the field
3021 $searchTable = isset($data['link']) ? $relateJoinInfo['rel_table'] : $this->table_name;
3022 $field_name = $relateJoinInfo['rel_table'] . '.' . !empty($data['name'])?$data['name']:'name';
3023 $where = preg_replace('/(^|[\s(])' . $field_name . '/' , '${1}' . $relateJoinInfo['name_field'], $where);
3027 if ($data['type'] == 'parent') {
3028 //See if we need to join anything by inspecting the where clause
3029 $match = preg_match('/(^|[\s(])parent_(\w+)_(\w+)\.name/', $where, $matches);
3031 $joinTableAlias = 'jt' . $jtcount;
3032 $joinModule = $matches[2];
3033 $joinTable = $matches[3];
3034 $localTable = $this->table_name;
3035 if (!empty($data['custom_module'])) {
3036 $localTable .= '_cstm';
3038 global $beanFiles, $beanList, $module;
3039 require_once($beanFiles[$beanList[$joinModule]]);
3040 $rel_mod = new $beanList[$joinModule]();
3041 $nameField = "$joinTableAlias.name";
3042 if (isset($rel_mod->field_defs['name']))
3044 $name_field_def = $rel_mod->field_defs['name'];
3045 if(isset($name_field_def['db_concat_fields']))
3047 $nameField = $this->db->concat($joinTableAlias, $name_field_def['db_concat_fields']);
3050 $ret_array['select'] .= ", $nameField {$data['name']} ";
3051 $ret_array['from'] .= " LEFT JOIN $joinTable $joinTableAlias
3052 ON $localTable.{$data['id_name']} = $joinTableAlias.id";
3053 //Replace any references to the relationship in the where clause with the new alias
3054 $where = preg_replace('/(^|[\s(])parent_' . $joinModule . '_' . $joinTable . '\.name/', '${1}' . $nameField, $where);
3058 if($data['type'] == 'relate' && isset($data['link']))
3060 $this->load_relationship($data['link']);
3061 if(!empty($this->$data['link']))
3064 if(empty($join_type))
3066 $params['join_type'] = ' LEFT JOIN ';
3070 $params['join_type'] = $join_type;
3072 if(isset($data['join_name']))
3074 $params['join_table_alias'] = $data['join_name'];
3078 $params['join_table_alias'] = 'jt' . $jtcount;
3081 if(isset($data['join_link_name']))
3083 $params['join_table_link_alias'] = $data['join_link_name'];
3087 $params['join_table_link_alias'] = 'jtl' . $jtcount;
3089 $join_primary = !isset($data['join_primary']) || $data['join_primary'];
3091 $join = $this->$data['link']->getJoin($params, true);
3092 $used_join_key[] = $join['rel_key'];
3093 $rel_module = $this->$data['link']->getRelatedModuleName();
3094 $table_joined = !empty($joined_tables[$params['join_table_alias']]) || (!empty($joined_tables[$params['join_table_link_alias']]) && isset($data['link_type']) && $data['link_type'] == 'relationship_info');
3096 //if rname is set to 'name', and bean files exist, then check if field should be a concatenated name
3097 global $beanFiles, $beanList;
3098 if($data['rname'] && !empty($beanFiles[$beanList[$rel_module]])) {
3100 //create an instance of the related bean
3101 require_once($beanFiles[$beanList[$rel_module]]);
3102 $rel_mod = new $beanList[$rel_module]();
3103 //if bean has first and last name fields, then name should be concatenated
3104 if(isset($rel_mod->field_name_map['first_name']) && isset($rel_mod->field_name_map['last_name'])){
3105 $data['db_concat_fields'] = array(0=>'first_name', 1=>'last_name');
3110 if($join['type'] == 'many-to-many')
3112 if(empty($ret_array['secondary_select']))
3114 $ret_array['secondary_select'] = " SELECT $this->table_name.id ref_id ";
3116 if(!empty($beanFiles[$beanList[$rel_module]]) && $join_primary)
3118 require_once($beanFiles[$beanList[$rel_module]]);
3119 $rel_mod = new $beanList[$rel_module]();
3120 if(isset($rel_mod->field_defs['assigned_user_id']))
3122 $ret_array['secondary_select'].= " , ". $params['join_table_alias'] . ".assigned_user_id {$field}_owner, '$rel_module' {$field}_mod";
3126 if(isset($rel_mod->field_defs['created_by']))
3128 $ret_array['secondary_select'].= " , ". $params['join_table_alias'] . ".created_by {$field}_owner , '$rel_module' {$field}_mod";
3134 if(isset($data['db_concat_fields']))
3136 $ret_array['secondary_select'] .= ' , ' . $this->db->concat($params['join_table_alias'], $data['db_concat_fields']) . ' ' . $field;
3140 if(!isset($data['relationship_fields']))
3142 $ret_array['secondary_select'] .= ' , ' . $params['join_table_alias'] . '.' . $data['rname'] . ' ' . $field;
3147 $ret_array['select'] .= ", ' ' $field ";
3150 foreach($used_join_key as $used_key) {
3151 if($used_key == $join['rel_key']) $count_used++;
3153 if($count_used <= 1) {//27416, the $ret_array['secondary_select'] should always generate, regardless the dbtype
3154 // add rel_key only if it was not aready added
3157 $ret_array['select'] .= ", ' ' " . $join['rel_key'] . ' ';
3159 $ret_array['secondary_select'] .= ', ' . $params['join_table_link_alias'].'.'. $join['rel_key'] .' ' . $join['rel_key'];
3161 if(isset($data['relationship_fields']))
3163 foreach($data['relationship_fields'] as $r_name=>$alias_name)
3165 if(!empty( $secondarySelectedFields[$alias_name]))continue;
3166 $ret_array['secondary_select'] .= ', ' . $params['join_table_link_alias'].'.'. $r_name .' ' . $alias_name;
3167 $secondarySelectedFields[$alias_name] = true;
3172 $ret_array['secondary_from'] .= ' ' . $join['join']. ' AND ' . $params['join_table_alias'].'.deleted=0';
3173 if (isset($data['link_type']) && $data['link_type'] == 'relationship_info' && ($parentbean instanceOf SugarBean))
3175 $ret_array['secondary_where'] = $params['join_table_link_alias'] . '.' . $join['rel_key']. "='" .$parentbean->id . "'";
3181 if(isset($data['db_concat_fields']))
3183 $ret_array['select'] .= ' , ' . $this->db->concat($params['join_table_alias'], $data['db_concat_fields']) . ' ' . $field;
3187 $ret_array['select'] .= ' , ' . $params['join_table_alias'] . '.' . $data['rname'] . ' ' . $field;
3189 if(isset($data['additionalFields'])){
3190 foreach($data['additionalFields'] as $k=>$v){
3191 $ret_array['select'] .= ' , ' . $params['join_table_alias'] . '.' . $k . ' ' . $v;
3196 $ret_array['from'] .= ' ' . $join['join']. ' AND ' . $params['join_table_alias'].'.deleted=0';
3197 if(!empty($beanList[$rel_module]) && !empty($beanFiles[$beanList[$rel_module]]))
3199 require_once($beanFiles[$beanList[$rel_module]]);
3200 $rel_mod = new $beanList[$rel_module]();
3201 if(isset($value['target_record_key']) && !empty($filter))
3203 $selectedFields[$this->table_name.'.'.$value['target_record_key']] = true;
3204 $ret_array['select'] .= " , $this->table_name.{$value['target_record_key']} ";
3206 if(isset($rel_mod->field_defs['assigned_user_id']))
3208 $ret_array['select'] .= ' , ' .$params['join_table_alias'] . '.assigned_user_id ' . $field . '_owner';
3212 $ret_array['select'] .= ' , ' .$params['join_table_alias'] . '.created_by ' . $field . '_owner';
3214 $ret_array['select'] .= " , '".$rel_module ."' " . $field . '_mod';
3219 // To fix SOAP stuff where we are trying to retrieve all the accounts data where accounts.id = ..
3220 // and this code changes accounts to jt4 as there is a self join with the accounts table.
3222 if(isset($data['db_concat_fields'])){
3223 $buildWhere = false;
3224 if(in_array('first_name', $data['db_concat_fields']) && in_array('last_name', $data['db_concat_fields']))
3226 $exp = '/\(\s*?'.$data['name'].'.*?\%\'\s*?\)/';
3227 if(preg_match($exp, $where, $matches))
3229 $search_expression = $matches[0];
3230 //Create three search conditions - first + last, first, last
3231 $first_name_search = str_replace($data['name'], $params['join_table_alias'] . '.first_name', $search_expression);
3232 $last_name_search = str_replace($data['name'], $params['join_table_alias'] . '.last_name', $search_expression);
3233 $full_name_search = str_replace($data['name'], $this->db->concat($params['join_table_alias'], $data['db_concat_fields']), $search_expression);
3235 $where = str_replace($search_expression, '(' . $full_name_search . ' OR ' . $first_name_search . ' OR ' . $last_name_search . ')', $where);
3241 $db_field = $this->db->concat($params['join_table_alias'], $data['db_concat_fields']);
3242 $where = preg_replace('/'.$data['name'].'/', $db_field, $where);
3245 $where = preg_replace('/(^|[\s(])' . $data['name'] . '/', '${1}' . $params['join_table_alias'] . '.'.$data['rname'], $where);
3249 $joined_tables[$params['join_table_alias']]=1;
3250 $joined_tables[$params['join_table_link_alias']]=1;
3259 if(isset($this->field_defs['assigned_user_id']) && empty($selectedFields[$this->table_name.'.assigned_user_id']))
3261 $ret_array['select'] .= ", $this->table_name.assigned_user_id ";
3263 else if(isset($this->field_defs['created_by']) && empty($selectedFields[$this->table_name.'.created_by']))
3265 $ret_array['select'] .= ", $this->table_name.created_by ";
3267 if(isset($this->field_defs['system_id']) && empty($selectedFields[$this->table_name.'.system_id']))
3269 $ret_array['select'] .= ", $this->table_name.system_id ";
3274 if ($ifListForExport) {
3275 if(isset($this->field_defs['email1'])) {
3276 $ret_array['select'].= " ,email_addresses.email_address email1";
3277 $ret_array['from'].= " LEFT JOIN email_addr_bean_rel on {$this->table_name}.id = email_addr_bean_rel.bean_id and email_addr_bean_rel.bean_module='{$this->module_dir}' and email_addr_bean_rel.deleted=0 and email_addr_bean_rel.primary_address=1 LEFT JOIN email_addresses on email_addresses.id = email_addr_bean_rel.email_address_id ";
3281 $where_auto = '1=1';
3282 if($show_deleted == 0)
3284 $where_auto = "$this->table_name.deleted=0";
3285 }else if($show_deleted == 1)
3287 $where_auto = "$this->table_name.deleted=1";
3290 $ret_array['where'] = " where ($where) AND $where_auto";
3292 $ret_array['where'] = " where $where_auto";
3293 if(!empty($order_by))
3295 //make call to process the order by clause
3296 $ret_array['order_by'] = " ORDER BY ". $this->process_order_by($order_by);
3300 unset($ret_array['secondary_where']);
3301 unset($ret_array['secondary_from']);
3302 unset($ret_array['secondary_select']);
3310 return $ret_array['select'] . $ret_array['from'] . $ret_array['where']. $ret_array['order_by'];
3313 * Returns parent record data for objects that store relationship information
3315 * @param array $type_info
3317 * Interal function, do not override.
3319 function retrieve_parent_fields($type_info)
3322 global $beanList, $beanFiles;
3323 $templates = array();
3324 $parent_child_map = array();
3325 foreach($type_info as $children_info)
3327 foreach($children_info as $child_info)
3329 if($child_info['type'] == 'parent')
3331 if(empty($templates[$child_info['parent_type']]))
3333 //Test emails will have an invalid parent_type, don't try to load the non-existent parent bean
3334 if ($child_info['parent_type'] == 'test') {
3337 $class = $beanList[$child_info['parent_type']];
3338 // Added to avoid error below; just silently fail and write message to log
3339 if ( empty($beanFiles[$class]) ) {
3340 $GLOBALS['log']->error($this->object_name.'::retrieve_parent_fields() - cannot load class "'.$class.'", skip loading.');
3343 require_once($beanFiles[$class]);
3344 $templates[$child_info['parent_type']] = new $class();
3347 if(empty($queries[$child_info['parent_type']]))
3349 $queries[$child_info['parent_type']] = "SELECT id ";
3350 $field_def = $templates[$child_info['parent_type']]->field_defs['name'];
3351 if(isset($field_def['db_concat_fields']))
3353 $queries[$child_info['parent_type']] .= ' , ' . $this->db->concat($templates[$child_info['parent_type']]->table_name, $field_def['db_concat_fields']) . ' parent_name';
3357 $queries[$child_info['parent_type']] .= ' , name parent_name';
3359 if(isset($templates[$child_info['parent_type']]->field_defs['assigned_user_id']))
3361 $queries[$child_info['parent_type']] .= ", assigned_user_id parent_name_owner , '{$child_info['parent_type']}' parent_name_mod";;
3362 }else if(isset($templates[$child_info['parent_type']]->field_defs['created_by']))
3364 $queries[$child_info['parent_type']] .= ", created_by parent_name_owner, '{$child_info['parent_type']}' parent_name_mod";
3366 $queries[$child_info['parent_type']] .= " FROM " . $templates[$child_info['parent_type']]->table_name ." WHERE id IN ('{$child_info['parent_id']}'";
3370 if(empty($parent_child_map[$child_info['parent_id']]))
3371 $queries[$child_info['parent_type']] .= " ,'{$child_info['parent_id']}'";
3373 $parent_child_map[$child_info['parent_id']][] = $child_info['child_id'];
3378 foreach($queries as $query)
3380 $result = $this->db->query($query . ')');
3381 while($row = $this->db->fetchByAssoc($result))
3383 $results[$row['id']] = $row;
3387 $child_results = array();
3388 foreach($parent_child_map as $parent_key=>$parent_child)
3390 foreach($parent_child as $child)
3392 if(isset( $results[$parent_key]))
3394 $child_results[$child] = $results[$parent_key];
3398 return $child_results;
3402 * Processes the list query and return fetched row.
3404 * Internal function, do not override.
3405 * @param string $query select query to be processed.
3406 * @param int $row_offset starting position
3407 * @param int $limit Optioanl, default -1
3408 * @param int $max_per_page Optional, default -1
3409 * @param string $where Optional, additional filter criteria.
3410 * @return array Fetched data
3412 function process_list_query($query, $row_offset, $limit= -1, $max_per_page = -1, $where = '')
3414 global $sugar_config;
3415 $db = DBManagerFactory::getInstance('listviews');
3417 * if the row_offset is set to 'end' go to the end of the list
3419 $toEnd = strval($row_offset) == 'end';
3420 $GLOBALS['log']->debug("process_list_query: ".$query);
3421 if($max_per_page == -1)
3423 $max_per_page = $sugar_config['list_max_entries_per_page'];
3425 // Check to see if we have a count query available.
3426 if(empty($sugar_config['disable_count_query']) || $toEnd)
3428 $count_query = $this->create_list_count_query($query);
3429 if(!empty($count_query) && (empty($limit) || $limit == -1))
3431 // We have a count query. Run it and get the results.
3432 $result = $db->query($count_query, true, "Error running count query for $this->object_name List: ");
3433 $assoc = $db->fetchByAssoc($result);
3434 if(!empty($assoc['c']))
3436 $rows_found = $assoc['c'];
3437 $limit = $sugar_config['list_max_entries_per_page'];
3441 $row_offset = (floor(($rows_found -1) / $limit)) * $limit;
3447 if((empty($limit) || $limit == -1))
3449 $limit = $max_per_page + 1;
3450 $max_per_page = $limit;
3455 if(empty($row_offset))
3459 if(!empty($limit) && $limit != -1 && $limit != -99)
3461 $result = $db->limitQuery($query, $row_offset, $limit,true,"Error retrieving $this->object_name list: ");
3465 $result = $db->query($query,true,"Error retrieving $this->object_name list: ");
3470 $previous_offset = $row_offset - $max_per_page;
3471 $next_offset = $row_offset + $max_per_page;
3473 $class = get_class($this);
3474 //FIXME: Bug? we should remove the magic number -99
3475 //use -99 to return all
3476 $index = $row_offset;
3477 while ($max_per_page == -99 || ($index < $row_offset + $max_per_page))
3479 $row = $db->fetchByAssoc($result);
3480 if (empty($row)) break;
3482 //instantiate a new class each time. This is because php5 passes
3483 //by reference by default so if we continually update $this, we will
3484 //at the end have a list of all the same objects
3485 $temp = new $class();
3487 foreach($this->field_defs as $field=>$value)
3489 if (isset($row[$field]))
3491 $temp->$field = $row[$field];
3492 $owner_field = $field . '_owner';
3493 if(isset($row[$owner_field]))
3495 $temp->$owner_field = $row[$owner_field];
3498 $GLOBALS['log']->debug("$temp->object_name({$row['id']}): ".$field." = ".$temp->$field);
3499 }else if (isset($row[$this->table_name .'.'.$field]))
3501 $temp->$field = $row[$this->table_name .'.'.$field];
3509 $temp->check_date_relationships_load();
3510 $temp->fill_in_additional_list_fields();
3511 if($temp->hasCustomFields()) $temp->custom_fields->fill_relationships();
3512 $temp->call_custom_logic("process_record");
3514 // fix defect #44206. implement the same logic as sugar_currency_format
3515 // Smarty modifier does.
3516 if (property_exists($temp, 'currency_id') && -99 == $temp->currency_id)
3518 // manually retrieve default currency object as long as it's
3519 // not stored in database and thus cannot be joined in query
3520 require_once 'modules/Currencies/Currency.php';
3521 $currency = new Currency();
3522 $currency->retrieve($temp->currency_id);
3524 // walk through all currency-related fields
3525 foreach ($temp->field_defs as $temp_field)
3527 if (isset($temp_field['type']) && 'relate' == $temp_field['type']
3528 && isset($temp_field['module']) && 'Currencies' == $temp_field['module']
3529 && isset($temp_field['id_name']) && 'currency_id' == $temp_field['id_name'])
3531 // populate related properties manually
3532 $temp_property = $temp_field['name'];
3533 $currency_property = $temp_field['rname'];
3534 $temp->$temp_property = $currency->$currency_property;
3543 if(!empty($sugar_config['disable_count_query']) && !empty($limit))
3546 $rows_found = $row_offset + count($list);
3548 unset($list[$limit - 1]);
3554 } else if(!isset($rows_found)){
3555 $rows_found = $row_offset + count($list);
3558 $response = Array();
3559 $response['list'] = $list;
3560 $response['row_count'] = $rows_found;
3561 $response['next_offset'] = $next_offset;
3562 $response['previous_offset'] = $previous_offset;
3563 $response['current_offset'] = $row_offset ;
3568 * Returns the number of rows that the given SQL query should produce
3570 * Internal function, do not override.
3571 * @param string $query valid select query
3572 * @param boolean $is_count_query Optional, Default false, set to true if passed query is a count query.
3573 * @return int count of rows found
3575 function _get_num_rows_in_query($query, $is_count_query=false)
3577 $num_rows_in_query = 0;
3578 if (!$is_count_query)
3580 $count_query = SugarBean::create_list_count_query($query);
3582 $count_query=$query;
3584 $result = $this->db->query($count_query, true, "Error running count query for $this->object_name List: ");
3586 while($row = $this->db->fetchByAssoc($result, true))
3588 $num_rows_in_query += current($row);
3591 return $num_rows_in_query;
3595 * Applies pagination window to union queries used by list view and subpanels,
3596 * executes the query and returns fetched data.
3598 * Internal function, do not override.
3599 * @param object $parent_bean
3600 * @param string $query query to be processed.
3601 * @param int $row_offset
3602 * @param int $limit optional, default -1
3603 * @param int $max_per_page Optional, default -1
3604 * @param string $where Custom where clause.
3605 * @param array $subpanel_def definition of sub-panel to be processed
3606 * @param string $query_row_count
3607 * @param array $seconday_queries
3608 * @return array Fetched data.
3610 function process_union_list_query($parent_bean, $query,
3611 $row_offset, $limit= -1, $max_per_page = -1, $where = '', $subpanel_def, $query_row_count='', $secondary_queries = array())
3614 $db = DBManagerFactory::getInstance('listviews');
3616 * if the row_offset is set to 'end' go to the end of the list
3618 $toEnd = strval($row_offset) == 'end';
3619 global $sugar_config;
3620 $use_count_query=false;
3621 $processing_collection=$subpanel_def->isCollection();
3623 $GLOBALS['log']->debug("process_union_list_query: ".$query);
3624 if($max_per_page == -1)
3626 $max_per_page = $sugar_config['list_max_entries_per_subpanel'];
3628 if(empty($query_row_count))
3630 $query_row_count = $query;
3632 $distinct_position=strpos($query_row_count,"DISTINCT");
3634 if ($distinct_position!= false)
3636 $use_count_query=true;
3638 $performSecondQuery = true;
3639 if(empty($sugar_config['disable_count_query']) || $toEnd)
3641 $rows_found = $this->_get_num_rows_in_query($query_row_count,$use_count_query);
3644 $performSecondQuery = false;
3646 if(!empty($rows_found) && (empty($limit) || $limit == -1))
3648 $limit = $sugar_config['list_max_entries_per_subpanel'];
3652 $row_offset = (floor(($rows_found -1) / $limit)) * $limit;
3658 if((empty($limit) || $limit == -1))
3660 $limit = $max_per_page + 1;
3661 $max_per_page = $limit;
3665 if(empty($row_offset))
3670 $previous_offset = $row_offset - $max_per_page;
3671 $next_offset = $row_offset + $max_per_page;
3673 if($performSecondQuery)
3675 if(!empty($limit) && $limit != -1 && $limit != -99)
3677 $result = $db->limitQuery($query, $row_offset, $limit,true,"Error retrieving $parent_bean->object_name list: ");
3681 $result = $db->query($query,true,"Error retrieving $this->object_name list: ");
3683 //use -99 to return all
3685 // get the current row
3686 $index = $row_offset;
3687 $row = $db->fetchByAssoc($result);
3689 $post_retrieve = array();
3690 $isFirstTime = true;
3693 $function_fields = array();
3694 if(($index < $row_offset + $max_per_page || $max_per_page == -99))
3696 if ($processing_collection)
3698 $current_bean =$subpanel_def->sub_subpanels[$row['panel_name']]->template_instance;
3701 $class = get_class($subpanel_def->sub_subpanels[$row['panel_name']]->template_instance);
3702 $current_bean = new $class();
3705 $current_bean=$subpanel_def->template_instance;
3708 $class = get_class($subpanel_def->template_instance);
3709 $current_bean = new $class();
3712 $isFirstTime = false;
3713 //set the panel name in the bean instance.
3714 if (isset($row['panel_name']))
3716 $current_bean->panel_name=$row['panel_name'];
3718 foreach($current_bean->field_defs as $field=>$value)
3721 if (isset($row[$field]))
3723 $current_bean->$field = $this->convertField($row[$field], $value);
3724 unset($row[$field]);
3726 else if (isset($row[$this->table_name .'.'.$field]))
3728 $current_bean->$field = $this->convertField($row[$this->table_name .'.'.$field], $value);
3729 unset($row[$this->table_name .'.'.$field]);
3733 $current_bean->$field = "";
3734 unset($row[$field]);
3736 if(isset($value['source']) && $value['source'] == 'function')
3738 $function_fields[]=$field;
3741 foreach($row as $key=>$value)
3743 $current_bean->$key = $value;
3745 foreach($function_fields as $function_field)
3747 $value = $current_bean->field_defs[$function_field];
3748 $can_execute = true;
3749 $execute_params = array();
3750 $execute_function = array();
3751 if(!empty($value['function_class']))
3753 $execute_function[] = $value['function_class'];
3754 $execute_function[] = $value['function_name'];
3758 $execute_function = $value['function_name'];
3760 foreach($value['function_params'] as $param )
3762 if (empty($value['function_params_source']) or $value['function_params_source']=='parent')
3764 if(empty($this->$param))
3766 $can_execute = false;
3767 } else if($param == '$this') {
3768 $execute_params[] = $this;
3772 $execute_params[] = $this->$param;
3774 } else if ($value['function_params_source']=='this')
3776 if(empty($current_bean->$param))
3778 $can_execute = false;
3779 } else if($param == '$this') {
3780 $execute_params[] = $current_bean;
3784 $execute_params[] = $current_bean->$param;
3789 $can_execute = false;
3795 if(!empty($value['function_require']))
3797 require_once($value['function_require']);
3799 $current_bean->$function_field = call_user_func_array($execute_function, $execute_params);
3802 if(!empty($current_bean->parent_type) && !empty($current_bean->parent_id))
3804 if(!isset($post_retrieve[$current_bean->parent_type]))
3806 $post_retrieve[$current_bean->parent_type] = array();
3808 $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');
3810 //$current_bean->fill_in_additional_list_fields();
3811 $list[$current_bean->id] = $current_bean;
3813 // go to the next row
3815 $row = $db->fetchByAssoc($result);
3817 //now handle retrieving many-to-many relationships
3820 foreach($secondary_queries as $query2)
3822 $result2 = $db->query($query2);
3824 $row2 = $db->fetchByAssoc($result2);
3827 $id_ref = $row2['ref_id'];
3829 if(isset($list[$id_ref]))
3831 foreach($row2 as $r2key=>$r2value)
3833 if($r2key != 'ref_id')
3835 $list[$id_ref]->$r2key = $r2value;
3839 $row2 = $db->fetchByAssoc($result2);
3845 if(isset($post_retrieve))
3847 $parent_fields = $this->retrieve_parent_fields($post_retrieve);
3851 $parent_fields = array();
3853 if(!empty($sugar_config['disable_count_query']) && !empty($limit))
3855 //C.L. Bug 43535 - Use the $index value to set the $rows_found value here
3856 $rows_found = isset($index) ? $index : $row_offset + count($list);
3858 if(count($list) >= $limit)
3872 $parent_fields = array();
3874 $response = array();
3875 $response['list'] = $list;
3876 $response['parent_data'] = $parent_fields;
3877 $response['row_count'] = $rows_found;
3878 $response['next_offset'] = $next_offset;
3879 $response['previous_offset'] = $previous_offset;
3880 $response['current_offset'] = $row_offset ;
3881 $response['query'] = $query;
3887 * Applies pagination window to select queries used by detail view,
3888 * executes the query and returns fetched data.
3890 * Internal function, do not override.
3891 * @param string $query query to be processed.
3892 * @param int $row_offset
3893 * @param int $limit optional, default -1
3894 * @param int $max_per_page Optional, default -1
3895 * @param string $where Custom where clause.
3896 * @param int $offset Optional, default 0
3897 * @return array Fetched data.
3900 function process_detail_query($query, $row_offset, $limit= -1, $max_per_page = -1, $where = '', $offset = 0)
3902 global $sugar_config;
3903 $GLOBALS['log']->debug("process_detail_query: ".$query);
3904 if($max_per_page == -1)
3906 $max_per_page = $sugar_config['list_max_entries_per_page'];
3909 // Check to see if we have a count query available.
3910 $count_query = $this->create_list_count_query($query);
3912 if(!empty($count_query) && (empty($limit) || $limit == -1))
3914 // We have a count query. Run it and get the results.
3915 $result = $this->db->query($count_query, true, "Error running count query for $this->object_name List: ");
3916 $assoc = $this->db->fetchByAssoc($result);
3917 if(!empty($assoc['c']))
3919 $total_rows = $assoc['c'];
3923 if(empty($row_offset))
3928 $result = $this->db->limitQuery($query, $offset, 1, true,"Error retrieving $this->object_name list: ");
3930 $previous_offset = $row_offset - $max_per_page;
3931 $next_offset = $row_offset + $max_per_page;
3933 $row = $this->db->fetchByAssoc($result);
3934 $this->retrieve($row['id']);
3936 $response = Array();
3937 $response['bean'] = $this;
3938 if (empty($total_rows))
3940 $response['row_count'] = $total_rows;
3941 $response['next_offset'] = $next_offset;
3942 $response['previous_offset'] = $previous_offset;
3948 * Processes fetched list view data
3950 * Internal function, do not override.
3951 * @param string $query query to be processed.
3952 * @param boolean $check_date Optional, default false. if set to true date time values are processed.
3953 * @return array Fetched data.
3956 function process_full_list_query($query, $check_date=false)
3959 $GLOBALS['log']->debug("process_full_list_query: query is ".$query);
3960 $result = $this->db->query($query, false);
3961 $GLOBALS['log']->debug("process_full_list_query: result is ".print_r($result,true));
3962 $class = get_class($this);
3963 $isFirstTime = true;
3964 $bean = new $class();
3966 // We have some data.
3967 while (($row = $bean->db->fetchByAssoc($result)) != null)
3969 $row = $this->convertRow($row);
3972 $bean = new $class();
3974 $isFirstTime = false;
3976 foreach($bean->field_defs as $field=>$value)
3978 if (isset($row[$field]))
3980 $bean->$field = $row[$field];
3981 $GLOBALS['log']->debug("process_full_list: $bean->object_name({$row['id']}): ".$field." = ".$bean->$field);
3990 $bean->processed_dates_times = array();
3991 $bean->check_date_relationships_load();
3993 $bean->fill_in_additional_list_fields();
3994 $bean->call_custom_logic("process_record");
3995 $bean->fetched_row = $row;
4000 if (isset($list)) return $list;
4005 * Tracks the viewing of a detail record.
4006 * This leverages get_summary_text() which is object specific.
4008 * Internal function, do not override.
4009 * @param string $user_id - String value of the user that is viewing the record.
4010 * @param string $current_module - String value of the module being processed.
4011 * @param string $current_view - String value of the current view
4013 function track_view($user_id, $current_module, $current_view='')
4015 $trackerManager = TrackerManager::getInstance();
4016 if($monitor = $trackerManager->getMonitor('tracker')){
4017 $monitor->setValue('date_modified', $GLOBALS['timedate']->nowDb());
4018 $monitor->setValue('user_id', $user_id);
4019 $monitor->setValue('module_name', $current_module);
4020 $monitor->setValue('action', $current_view);
4021 $monitor->setValue('item_id', $this->id);
4022 $monitor->setValue('item_summary', $this->get_summary_text());
4023 $monitor->setValue('visible', $this->tracker_visibility);
4024 $trackerManager->saveMonitor($monitor);
4029 * Returns the summary text that should show up in the recent history list for this object.
4033 public function get_summary_text()
4035 return "Base Implementation. Should be overridden.";
4039 * This is designed to be overridden and add specific fields to each record.
4040 * This allows the generic query to fill in the major fields, and then targeted
4041 * queries to get related fields and add them to the record. The contact's
4042 * account for instance. This method is only used for populating extra fields
4045 function fill_in_additional_list_fields(){
4046 if(!empty($this->field_defs['parent_name']) && empty($this->parent_name)){
4047 $this->fill_in_additional_parent_fields();
4052 * This is designed to be overridden and add specific fields to each record.
4053 * This allows the generic query to fill in the major fields, and then targeted
4054 * queries to get related fields and add them to the record. The contact's
4055 * account for instance. This method is only used for populating extra fields
4056 * in the detail form
4058 function fill_in_additional_detail_fields()
4060 if(!empty($this->field_defs['assigned_user_name']) && !empty($this->assigned_user_id)){
4062 $this->assigned_user_name = get_assigned_user_name($this->assigned_user_id);
4065 if(!empty($this->field_defs['created_by']) && !empty($this->created_by))
4066 $this->created_by_name = get_assigned_user_name($this->created_by);
4067 if(!empty($this->field_defs['modified_user_id']) && !empty($this->modified_user_id))
4068 $this->modified_by_name = get_assigned_user_name($this->modified_user_id);
4070 if(!empty($this->field_defs['parent_name'])){
4071 $this->fill_in_additional_parent_fields();
4076 * This is desgined to be overridden or called from extending bean. This method
4077 * will fill in any parent_name fields.
4079 function fill_in_additional_parent_fields() {
4081 if(!empty($this->parent_id) && !empty($this->last_parent_id) && $this->last_parent_id == $this->parent_id){
4084 $this->parent_name = '';
4086 if(!empty($this->parent_type)) {
4087 $this->last_parent_id = $this->parent_id;
4088 $this->getRelatedFields($this->parent_type, $this->parent_id, array('name'=>'parent_name', 'document_name' => 'parent_document_name', 'first_name'=>'parent_first_name', 'last_name'=>'parent_last_name'));
4089 if(!empty($this->parent_first_name) || !empty($this->parent_last_name) ){
4090 $this->parent_name = $GLOBALS['locale']->getLocaleFormattedName($this->parent_first_name, $this->parent_last_name);
4091 } else if(!empty($this->parent_document_name)){
4092 $this->parent_name = $this->parent_document_name;
4098 * Fill in a link field
4101 function fill_in_link_field( $linkFieldName , $def)
4103 $idField = $linkFieldName;
4104 //If the id_name provided really was an ID, don't try to load it as a link. Use the normal link
4105 if (!empty($this->field_defs[$linkFieldName]['type']) && $this->field_defs[$linkFieldName]['type'] == "id" && !empty($def['link']))
4107 $linkFieldName = $def['link'];
4109 if ($this->load_relationship($linkFieldName))
4111 $list=$this->$linkFieldName->get();
4112 $this->$idField = '' ; // match up with null value in $this->populateFromRow()
4114 $this->$idField = $list[0];
4119 * Fill in fields where type = relate
4121 function fill_in_relationship_fields(){
4122 global $fill_in_rel_depth;
4123 if(empty($fill_in_rel_depth) || $fill_in_rel_depth < 0)
4124 $fill_in_rel_depth = 0;
4126 if($fill_in_rel_depth > 1)
4129 $fill_in_rel_depth++;
4131 foreach($this->field_defs as $field)
4133 if(0 == strcmp($field['type'],'relate') && !empty($field['module']))
4135 $name = $field['name'];
4136 if(empty($this->$name))
4138 // set the value of this relate field in this bean ($this->$field['name']) to the value of the 'name' field in the related module for the record identified by the value of $this->$field['id_name']
4139 $related_module = $field['module'];
4140 $id_name = $field['id_name'];
4142 if (empty($this->$id_name))
4144 $this->fill_in_link_field($id_name, $field);
4146 if(!empty($this->$id_name) && ( $this->object_name != $related_module || ( $this->object_name == $related_module && $this->$id_name != $this->id ))){
4147 if(isset($GLOBALS['beanList'][ $related_module])){
4148 $class = $GLOBALS['beanList'][$related_module];
4150 if(!empty($this->$id_name) && file_exists($GLOBALS['beanFiles'][$class]) && isset($this->$name)){
4151 require_once($GLOBALS['beanFiles'][$class]);
4152 $mod = new $class();
4154 // disable row level security in order to be able
4155 // to retrieve related bean properties (bug #44928)
4157 $mod->retrieve($this->$id_name);
4159 if (!empty($field['rname'])) {
4160 $this->$name = $mod->$field['rname'];
4161 } else if (isset($mod->name)) {
4162 $this->$name = $mod->name;
4167 if(!empty($this->$id_name) && isset($this->$name))
4169 if(!isset($field['additionalFields']))
4170 $field['additionalFields'] = array();
4171 if(!empty($field['rname']))
4173 $field['additionalFields'][$field['rname']]= $name;
4177 $field['additionalFields']['name']= $name;
4179 $this->getRelatedFields($related_module, $this->$id_name, $field['additionalFields']);
4184 $fill_in_rel_depth--;
4188 * This is a helper function that is used to quickly created indexes when creating tables.
4190 function create_index($query)
4192 $GLOBALS['log']->info("create_index: $query");
4194 $result = $this->db->query($query, true, "Error creating index:");
4198 * This function should be overridden in each module. It marks an item as deleted.
4200 * If it is not overridden, then marking this type of item is not allowed
4202 function mark_deleted($id)
4204 global $current_user;
4205 $date_modified = $GLOBALS['timedate']->nowDb();
4206 if(isset($_SESSION['show_deleted']))
4208 $this->mark_undeleted($id);
4212 // call the custom business logic
4213 $custom_logic_arguments['id'] = $id;
4214 $this->call_custom_logic("before_delete", $custom_logic_arguments);
4216 $this->mark_relationships_deleted($id);
4217 if ( isset($this->field_defs['modified_user_id']) ) {
4218 if (!empty($current_user)) {
4219 $this->modified_user_id = $current_user->id;
4221 $this->modified_user_id = 1;
4223 $query = "UPDATE $this->table_name set deleted=1 , date_modified = '$date_modified', modified_user_id = '$this->modified_user_id' where id='$id'";
4225 $query = "UPDATE $this->table_name set deleted=1 , date_modified = '$date_modified' where id='$id'";
4227 $this->db->query($query, true,"Error marking record deleted: ");
4229 SugarRelationship::resaveRelatedBeans();
4231 // Take the item off the recently viewed lists
4232 $tracker = new Tracker();
4233 $tracker->makeInvisibleForAll($id);
4235 // call the custom business logic
4236 $this->call_custom_logic("after_delete", $custom_logic_arguments);
4241 * Restores data deleted by call to mark_deleted() function.
4243 * Internal function, do not override.
4245 function mark_undeleted($id)
4247 // call the custom business logic
4248 $custom_logic_arguments['id'] = $id;
4249 $this->call_custom_logic("before_restore", $custom_logic_arguments);
4251 $date_modified = $GLOBALS['timedate']->nowDb();
4252 $query = "UPDATE $this->table_name set deleted=0 , date_modified = '$date_modified' where id='$id'";
4253 $this->db->query($query, true,"Error marking record undeleted: ");
4255 // call the custom business logic
4256 $this->call_custom_logic("after_restore", $custom_logic_arguments);
4260 * This function deletes relationships to this object. It should be overridden
4261 * to handle the relationships of the specific object.
4262 * This function is called when the item itself is being deleted.
4264 * @param int $id id of the relationship to delete
4266 function mark_relationships_deleted($id)
4268 $this->delete_linked($id);
4272 * This function is used to execute the query and create an array template objects
4273 * from the resulting ids from the query.
4274 * It is currently used for building sub-panel arrays.
4276 * @param string $query - the query that should be executed to build the list
4277 * @param object $template - The object that should be used to copy the records.
4278 * @param int $row_offset Optional, default 0
4279 * @param int $limit Optional, default -1
4282 function build_related_list($query, &$template, $row_offset = 0, $limit = -1)
4284 $GLOBALS['log']->debug("Finding linked records $this->object_name: ".$query);
4285 $db = DBManagerFactory::getInstance('listviews');
4287 if(!empty($row_offset) && $row_offset != 0 && !empty($limit) && $limit != -1)
4289 $result = $db->limitQuery($query, $row_offset, $limit,true,"Error retrieving $template->object_name list: ");
4293 $result = $db->query($query, true);
4297 $isFirstTime = true;
4298 $class = get_class($template);
4299 while($row = $this->db->fetchByAssoc($result))
4303 $template = new $class();
4305 $isFirstTime = false;
4306 $record = $template->retrieve($row['id']);
4310 // this copies the object into the array
4311 $list[] = $template;
4318 * This function is used to execute the query and create an array template objects
4319 * from the resulting ids from the query.
4320 * It is currently used for building sub-panel arrays. It supports an additional
4321 * where clause that is executed as a filter on the results
4323 * @param string $query - the query that should be executed to build the list
4324 * @param object $template - The object that should be used to copy the records.
4326 function build_related_list_where($query, &$template, $where='', $in='', $order_by, $limit='', $row_offset = 0)
4328 $db = DBManagerFactory::getInstance('listviews');
4329 // No need to do an additional query
4330 $GLOBALS['log']->debug("Finding linked records $this->object_name: ".$query);
4331 if(empty($in) && !empty($query))
4333 $idList = $this->build_related_in($query);
4334 $in = $idList['in'];
4336 // MFH - Added Support For Custom Fields in Searches
4338 if(isset($this->custom_fields)) {
4339 $custom_join = $this->custom_fields->getJOIN();
4342 $query = "SELECT id ";
4344 if (!empty($custom_join)) {
4345 $query .= $custom_join['select'];
4347 $query .= " FROM $this->table_name ";
4349 if (!empty($custom_join) && !empty($custom_join['join'])) {
4350 $query .= " " . $custom_join['join'];
4353 $query .= " WHERE deleted=0 AND id IN $in";
4356 $query .= " AND $where";
4360 if(!empty($order_by))
4362 $query .= "ORDER BY $order_by";
4366 $result = $db->limitQuery($query, $row_offset, $limit,true,"Error retrieving $this->object_name list: ");
4370 $result = $db->query($query, true);
4374 $isFirstTime = true;
4375 $class = get_class($template);
4377 $disable_security_flag = ($template->disable_row_level_security) ? true : false;
4378 while($row = $db->fetchByAssoc($result))
4382 $template = new $class();
4383 $template->disable_row_level_security = $disable_security_flag;
4385 $isFirstTime = false;
4386 $record = $template->retrieve($row['id']);
4389 // this copies the object into the array
4390 $list[] = $template;
4398 * Constructs an comma separated list of ids from passed query results.
4400 * @param string @query query to be executed.
4403 function build_related_in($query)
4406 $result = $this->db->query($query, true);
4408 while($row = $this->db->fetchByAssoc($result))
4410 $idList[] = $row['id'];
4413 $ids = "('" . $row['id'] . "'";
4417 $ids .= ",'" . $row['id'] . "'";
4427 return array('list'=>$idList, 'in'=>$ids);
4431 * Optionally copies values from fetched row into the bean.
4433 * Internal function, do not override.
4435 * @param string $query - the query that should be executed to build the list
4436 * @param object $template - The object that should be used to copy the records
4437 * @param array $field_list List of fields.
4440 function build_related_list2($query, &$template, &$field_list)
4442 $GLOBALS['log']->debug("Finding linked values $this->object_name: ".$query);
4444 $result = $this->db->query($query, true);
4447 $isFirstTime = true;
4448 $class = get_class($template);
4449 while($row = $this->db->fetchByAssoc($result))
4451 // Create a blank copy
4455 $copy = new $class();
4457 $isFirstTime = false;
4458 foreach($field_list as $field)
4460 // Copy the relevant fields
4461 $copy->$field = $row[$field];
4465 // this copies the object into the array
4473 * Let implementing classes to fill in row specific columns of a list view form
4476 function list_view_parse_additional_sections(&$list_form)
4481 * Assigns all of the values into the template for the list view
4483 function get_list_view_array()
4485 static $cache = array();
4486 // cn: bug 12270 - sensitive fields being passed arbitrarily in listViews
4487 $sensitiveFields = array('user_hash' => '');
4490 $return_array = Array();
4491 global $app_list_strings, $mod_strings;
4492 foreach($this->field_defs as $field=>$value){
4494 if(isset($this->$field)){
4496 // cn: bug 12270 - sensitive fields being passed arbitrarily in listViews
4497 if(isset($sensitiveFields[$field]))
4499 if(!isset($cache[$field]))
4500 $cache[$field] = strtoupper($field);
4502 //Fields hidden by Dependent Fields
4503 if (isset($value['hidden']) && $value['hidden'] === true) {
4504 $return_array[$cache[$field]] = "";
4507 //cn: if $field is a _dom, detect and return VALUE not KEY
4508 //cl: empty function check for meta-data enum types that have values loaded from a function
4509 else if (((!empty($value['type']) && ($value['type'] == 'enum' || $value['type'] == 'radioenum') )) && empty($value['function'])){
4510 if(!empty($value['options']) && !empty($app_list_strings[$value['options']][$this->$field])){
4511 $return_array[$cache[$field]] = $app_list_strings[$value['options']][$this->$field];
4513 //nsingh- bug 21672. some modules such as manufacturers, Releases do not have a listing for select fields in the $app_list_strings. Must also check $mod_strings to localize.
4514 elseif(!empty($value['options']) && !empty($mod_strings[$value['options']][$this->$field]))
4516 $return_array[$cache[$field]] = $mod_strings[$value['options']][$this->$field];
4519 $return_array[$cache[$field]] = $this->$field;
4522 // tjy: no need to do this str_replace as the changes in 29994 for ListViewGeneric.tpl for translation handle this now
4523 // }elseif(!empty($value['type']) && $value['type'] == 'multienum'&& empty($value['function'])){
4524 // $return_array[strtoupper($field)] = str_replace('^,^', ', ', $this->$field );
4525 }elseif(!empty($value['custom_module']) && $value['type'] != 'currency'){
4526 // $this->format_field($value);
4527 $return_array[$cache[$field]] = $this->$field;
4529 $return_array[$cache[$field]] = $this->$field;
4531 // handle "Assigned User Name"
4532 if($field == 'assigned_user_name'){
4533 $return_array['ASSIGNED_USER_NAME'] = get_assigned_user_name($this->assigned_user_id);
4537 return $return_array;
4540 * Override this function to set values in the array used to render list view data.
4543 function get_list_view_data()
4545 return $this->get_list_view_array();
4549 * Construct where clause from a list of name-value pairs.
4550 * @param array $fields_array Name/value pairs for column checks
4551 * @return string The WHERE clause
4553 function get_where($fields_array)
4556 foreach ($fields_array as $name=>$value)
4558 if (!empty($where_clause)) {
4559 $where_clause .= " AND ";
4561 $name = $this->db->getValidDBName($name);
4563 $where_clause .= "$name = ".$this->db->quoted($value,false);
4565 if(!empty($where_clause)) {
4566 return "WHERE $where_clause AND deleted=0";
4568 return "WHERE deleted=0";
4574 * Constructs a select query and fetch 1 row using this query, and then process the row
4576 * Internal function, do not override.
4577 * @param array @fields_array array of name value pairs used to construct query.
4578 * @param boolean $encode Optional, default true, encode fetched data.
4579 * @return object Instance of this bean with fetched data.
4581 function retrieve_by_string_fields($fields_array, $encode=true)
4583 $where_clause = $this->get_where($fields_array);
4584 if(isset($this->custom_fields))
4585 $custom_join = $this->custom_fields->getJOIN();
4586 else $custom_join = false;
4589 $query = "SELECT $this->table_name.*". $custom_join['select']. " FROM $this->table_name " . $custom_join['join'];
4593 $query = "SELECT $this->table_name.* FROM $this->table_name ";
4595 $query .= " $where_clause";
4596 $GLOBALS['log']->debug("Retrieve $this->object_name: ".$query);
4597 //requireSingleResult has been deprecated.
4598 //$result = $this->db->requireSingleResult($query, true, "Retrieving record $where_clause:");
4599 $result = $this->db->limitQuery($query,0,1,true, "Retrieving record $where_clause:");
4606 $row = $this->db->fetchByAssoc($result, $encode);
4611 // Removed getRowCount-if-clause earlier and insert duplicates_found here as it seems that we have found something
4612 // if we didn't return null in the previous clause.
4613 $this->duplicates_found = true;
4614 $row = $this->convertRow($row);
4615 $this->fetched_row = $row;
4616 $this->fromArray($row);
4617 $this->fill_in_additional_detail_fields();
4622 * This method is called during an import before inserting a bean
4623 * Define an associative array called $special_fields
4624 * the keys are user defined, and don't directly map to the bean's fields
4625 * the value is the method name within that bean that will do extra
4626 * processing for that field. example: 'full_name'=>'get_names_from_full_name'
4629 function process_special_fields()
4631 foreach ($this->special_functions as $func_name)
4633 if ( method_exists($this,$func_name) )
4635 $this->$func_name();
4641 * Override this function to build a where clause based on the search criteria set into bean .
4644 function build_generic_where_clause($value)
4648 function getRelatedFields($module, $id, $fields, $return_array = false){
4649 if(empty($GLOBALS['beanList'][$module]))return '';
4650 $object = BeanFactory::getObjectName($module);
4652 VardefManager::loadVardef($module, $object);
4653 if(empty($GLOBALS['dictionary'][$object]['table']))return '';
4654 $table = $GLOBALS['dictionary'][$object]['table'];
4655 $query = 'SELECT id';
4656 foreach($fields as $field=>$alias){
4657 if(!empty($GLOBALS['dictionary'][$object]['fields'][$field]['db_concat_fields'])){
4658 $query .= ' ,' .$this->db->concat($table, $GLOBALS['dictionary'][$object]['fields'][$field]['db_concat_fields']) . ' as ' . $alias ;
4659 }else if(!empty($GLOBALS['dictionary'][$object]['fields'][$field]) &&
4660 (empty($GLOBALS['dictionary'][$object]['fields'][$field]['source']) ||
4661 $GLOBALS['dictionary'][$object]['fields'][$field]['source'] != "non-db"))
4663 $query .= ' ,' .$table . '.' . $field . ' as ' . $alias;
4665 if(!$return_array)$this->$alias = '';
4667 if($query == 'SELECT id' || empty($id)){
4672 if(isset($GLOBALS['dictionary'][$object]['fields']['assigned_user_id']))
4674 $query .= " , ". $table . ".assigned_user_id owner";
4677 else if(isset($GLOBALS['dictionary'][$object]['fields']['created_by']))
4679 $query .= " , ". $table . ".created_by owner";
4682 $query .= ' FROM ' . $table . ' WHERE deleted=0 AND id=';
4683 $result = $GLOBALS['db']->query($query . "'$id'" );
4684 $row = $GLOBALS['db']->fetchByAssoc($result);
4688 $owner = (empty($row['owner']))?'':$row['owner'];
4689 foreach($fields as $alias){
4690 $this->$alias = (!empty($row[$alias]))? $row[$alias]: '';
4691 $alias = $alias .'_owner';
4692 $this->$alias = $owner;
4693 $a_mod = $alias .'_mod';
4694 $this->$a_mod = $module;
4701 function &parse_additional_headers(&$list_form, $xTemplateSection)
4706 function assign_display_fields($currentModule)
4709 foreach($this->column_fields as $field)
4711 if(isset($this->field_name_map[$field]) && empty($this->$field))
4713 if($this->field_name_map[$field]['type'] != 'date' && $this->field_name_map[$field]['type'] != 'enum')
4714 $this->$field = $field;
4715 if($this->field_name_map[$field]['type'] == 'date')
4717 $this->$field = $timedate->to_display_date('1980-07-09');
4719 if($this->field_name_map[$field]['type'] == 'enum')
4721 $dom = $this->field_name_map[$field]['options'];
4722 global $current_language, $app_list_strings;
4723 $mod_strings = return_module_language($current_language, $currentModule);
4725 if(isset($mod_strings[$dom]))
4727 $options = $mod_strings[$dom];
4728 foreach($options as $key=>$value)
4730 if(!empty($key) && empty($this->$field ))
4732 $this->$field = $key;
4736 if(isset($app_list_strings[$dom]))
4738 $options = $app_list_strings[$dom];
4739 foreach($options as $key=>$value)
4741 if(!empty($key) && empty($this->$field ))
4743 $this->$field = $key;
4755 * RELATIONSHIP HANDLING
4758 function set_relationship($table, $relate_values, $check_duplicates = true,$do_update=false,$data_values=null)
4762 // make sure there is a date modified
4763 $date_modified = $this->db->convert("'".$GLOBALS['timedate']->nowDb()."'", 'datetime');
4766 if($check_duplicates)
4768 $query = "SELECT * FROM $table ";
4769 $where = "WHERE deleted = '0' ";
4770 foreach($relate_values as $name=>$value)
4772 $where .= " AND $name = '$value' ";
4775 $result = $this->db->query($query, false, "Looking For Duplicate Relationship:" . $query);
4776 $row=$this->db->fetchByAssoc($result);
4779 if(!$check_duplicates || empty($row) )
4781 unset($relate_values['id']);
4782 if ( isset($data_values))
4784 $relate_values = array_merge($relate_values,$data_values);
4786 $query = "INSERT INTO $table (id, ". implode(',', array_keys($relate_values)) . ", date_modified) VALUES ('" . create_guid() . "', " . "'" . implode("', '", $relate_values) . "', ".$date_modified.")" ;
4788 $this->db->query($query, false, "Creating Relationship:" . $query);
4790 else if ($do_update)
4793 foreach($data_values as $key=>$value)
4795 array_push($conds,$key."='".$this->db->quote($value)."'");
4797 $query = "UPDATE $table SET ". implode(',', $conds).",date_modified=".$date_modified." ".$where;
4798 $this->db->query($query, false, "Updating Relationship:" . $query);
4802 function retrieve_relationships($table, $values, $select_id)
4804 $query = "SELECT $select_id FROM $table WHERE deleted = 0 ";
4805 foreach($values as $name=>$value)
4807 $query .= " AND $name = '$value' ";
4809 $query .= " ORDER BY $select_id ";
4810 $result = $this->db->query($query, false, "Retrieving Relationship:" . $query);
4812 while($row = $this->db->fetchByAssoc($result))
4819 // TODO: this function needs adjustment
4820 function loadLayoutDefs()
4822 global $layout_defs;
4823 if(empty( $this->layout_def) && file_exists('modules/'. $this->module_dir . '/layout_defs.php'))
4825 include_once('modules/'. $this->module_dir . '/layout_defs.php');
4826 if(file_exists('custom/modules/'. $this->module_dir . '/Ext/Layoutdefs/layoutdefs.ext.php'))
4828 include_once('custom/modules/'. $this->module_dir . '/Ext/Layoutdefs/layoutdefs.ext.php');
4830 if ( empty( $layout_defs[get_class($this)]))
4832 echo "\$layout_defs[" . get_class($this) . "]; does not exist";
4835 $this->layout_def = $layout_defs[get_class($this)];
4840 * Trigger custom logic for this module that is defined for the provided hook
4841 * The custom logic file is located under custom/modules/[CURRENT_MODULE]/logic_hooks.php.
4842 * That file should define the $hook_version that should be used.
4843 * It should also define the $hook_array. The $hook_array will be a two dimensional array
4844 * the first dimension is the name of the event, the second dimension is the information needed
4845 * to fire the hook. Each entry in the top level array should be defined on a single line to make it
4846 * easier to automatically replace this file. There should be no contents of this file that are not replacable.
4848 * $hook_array['before_save'][] = Array(1, testtype, 'custom/modules/Leads/test12.php', 'TestClass', 'lead_before_save_1');
4849 * This sample line creates a before_save hook. The hooks are procesed in the order in which they
4850 * are added to the array. The second dimension is an array of:
4851 * processing index (for sorting before exporting the array)
4854 * php file to include
4855 * php class the method is in
4856 * php method to call
4858 * The method signature for version 1 hooks is:
4859 * function NAME(&$bean, $event, $arguments)
4860 * $bean - $this bean passed in by reference.
4861 * $event - The string for the current event (i.e. before_save)
4862 * $arguments - An array of arguments that are specific to the event.
4864 function call_custom_logic($event, $arguments = null)
4866 if(!isset($this->processed) || $this->processed == false){
4867 //add some logic to ensure we do not get into an infinite loop
4868 if(!empty($this->logicHookDepth[$event])) {
4869 if($this->logicHookDepth[$event] > $this->max_logic_depth)
4872 $this->logicHookDepth[$event] = 0;
4874 //we have to put the increment operator here
4875 //otherwise we may never increase the depth for that event in the case
4876 //where one event will trigger another as in the case of before_save and after_save
4877 //Also keeping the depth per event allow any number of hooks to be called on the bean
4878 //and we only will return if one event gets caught in a loop. We do not increment globally
4879 //for each event called.
4880 $this->logicHookDepth[$event]++;
4882 //method defined in 'include/utils/LogicHook.php'
4884 $logicHook = new LogicHook();
4885 $logicHook->setBean($this);
4886 $logicHook->call_custom_logic($this->module_dir, $event, $arguments);
4887 $this->logicHookDepth[$event]--;
4892 /* When creating a custom field of type Dropdown, it creates an enum row in the DB.
4893 A typical get_list_view_array() result will have the *KEY* value from that drop-down.
4894 Since custom _dom objects are flat-files included in the $app_list_strings variable,
4895 We need to generate a key-key pair to get the true value like so:
4896 ([module]_cstm->fields_meta_data->$app_list_strings->*VALUE*)*/
4897 function getRealKeyFromCustomFieldAssignedKey($name)
4899 if ($this->custom_fields->avail_fields[$name]['ext1'])
4903 elseif ($this->custom_fields->avail_fields[$name]['ext2'])
4907 elseif ($this->custom_fields->avail_fields[$name]['ext3'])
4913 $GLOBALS['log']->fatal("SUGARBEAN: cannot find Real Key for custom field of type dropdown - cannot return Value.");
4918 return $this->custom_fields->avail_fields[$name][$realKey];
4922 function bean_implements($interface)
4927 * Check whether the user has access to a particular view for the current bean/module
4928 * @param $view string required, the view to determine access for i.e. DetailView, ListView...
4929 * @param $is_owner bool optional, this is part of the ACL check if the current user is an owner they will receive different access
4931 function ACLAccess($view,$is_owner='not_set')
4933 global $current_user;
4934 if($current_user->isAdminForModule($this->getACLCategory())) {
4938 if($is_owner == 'not_set')
4941 $is_owner = $this->isOwner($current_user->id);
4944 // If we don't implement ACLs, return true.
4945 if(!$this->bean_implements('ACL'))
4947 $view = strtolower($view);
4953 return ACLController::checkAccess($this->module_dir,'list', true);
4956 if( !$is_owner && $not_set && !empty($this->id)){
4957 $class = get_class($this);
4958 $temp = new $class();
4959 if(!empty($this->fetched_row) && !empty($this->fetched_row['id']) && !empty($this->fetched_row['assigned_user_id']) && !empty($this->fetched_row['created_by'])){
4960 $temp->populateFromRow($this->fetched_row);
4962 $temp->retrieve($this->id);
4964 $is_owner = $temp->isOwner($current_user->id);
4966 case 'popupeditview':
4968 return ACLController::checkAccess($this->module_dir,'edit', $is_owner, $this->acltype);
4972 return ACLController::checkAccess($this->module_dir,'view', $is_owner, $this->acltype);
4974 return ACLController::checkAccess($this->module_dir,'delete', $is_owner, $this->acltype);
4976 return ACLController::checkAccess($this->module_dir,'export', $is_owner, $this->acltype);
4978 return ACLController::checkAccess($this->module_dir,'import', true, $this->acltype);
4980 //if it is not one of the above views then it should be implemented on the page level
4989 function getOwnerField($returnFieldName = false)
4991 if (isset($this->field_defs['assigned_user_id']))
4993 return $returnFieldName? 'assigned_user_id': $this->assigned_user_id;
4996 if (isset($this->field_defs['created_by']))
4998 return $returnFieldName? 'created_by': $this->created_by;
5005 * Returns true of false if the user_id passed is the owner
5007 * @param GUID $user_id
5010 function isOwner($user_id)
5012 //if we don't have an id we must be the owner as we are creating it
5013 if(!isset($this->id))
5017 //if there is an assigned_user that is the owner
5018 if(isset($this->assigned_user_id))
5020 if($this->assigned_user_id == $user_id) return true;
5025 //other wise if there is a created_by that is the owner
5026 if(isset($this->created_by) && $this->created_by == $user_id)
5034 * Gets there where statement for checking if a user is an owner
5036 * @param GUID $user_id
5039 function getOwnerWhere($user_id)
5041 if(isset($this->field_defs['assigned_user_id']))
5043 return " $this->table_name.assigned_user_id ='$user_id' ";
5045 if(isset($this->field_defs['created_by']))
5047 return " $this->table_name.created_by ='$user_id' ";
5054 * Used in order to manage ListView links and if they should
5055 * links or not based on the ACL permissions of the user
5057 * @return ARRAY of STRINGS
5059 function listviewACLHelper()
5061 $array_assign = array();
5062 if($this->ACLAccess('DetailView'))
5064 $array_assign['MAIN'] = 'a';
5068 $array_assign['MAIN'] = 'span';
5070 return $array_assign;
5074 * returns this bean as an array
5076 * @return array of fields with id, name, access and category
5078 function toArray($dbOnly = false, $stringOnly = false, $upperKeys=false)
5080 static $cache = array();
5083 foreach($this->field_defs as $field=>$data)
5085 if( !$dbOnly || !isset($data['source']) || $data['source'] == 'db')
5086 if(!$stringOnly || is_string($this->$field))
5089 if(!isset($cache[$field])){
5090 $cache[$field] = strtoupper($field);
5092 $arr[$cache[$field]] = $this->$field;
5096 if(isset($this->$field)){
5097 $arr[$field] = $this->$field;
5107 * Converts an array into an acl mapping name value pairs into files
5111 function fromArray($arr)
5113 foreach($arr as $name=>$value)
5115 $this->$name = $value;
5120 * Convert row data from DB format to internal format
5121 * Mostly useful for dates/times
5123 * @return array $row
5125 public function convertRow($row)
5127 foreach($this->field_defs as $name => $fieldDef)
5129 // skip empty fields and non-db fields
5130 if (isset($name) && !empty($row[$name])) {
5131 $row[$name] = $this->convertField($row[$name], $fieldDef);
5138 * Converts the field value based on the provided fieldDef
5139 * @param $fieldvalue
5143 public function convertField($fieldvalue, $fieldDef)
5145 if (!empty($fieldvalue)) {
5146 if (!(isset($fieldDef['source']) &&
5147 !in_array($fieldDef['source'], array('db', 'custom_fields', 'relate'))
5148 && !isset($fieldDef['dbType']))
5150 // fromConvert other fields
5151 $fieldvalue = $this->db->fromConvert($fieldvalue, $this->db->getFieldType($fieldDef));
5158 * Loads a row of data into instance of a bean. The data is passed as an array to this function
5160 * @param array $arr row of data fetched from the database.
5163 * Internal function do not override.
5165 function loadFromRow($arr)
5167 $this->populateFromRow($arr);
5168 $this->processed_dates_times = array();
5169 $this->check_date_relationships_load();
5171 $this->fill_in_additional_list_fields();
5173 if($this->hasCustomFields())$this->custom_fields->fill_relationships();
5174 $this->call_custom_logic("process_record");
5177 function hasCustomFields()
5179 return !empty($GLOBALS['dictionary'][$this->object_name]['custom_fields']);
5183 * Ensure that fields within order by clauses are properly qualified with
5184 * their tablename. This qualification is a requirement for sql server support.
5186 * @param string $order_by original order by from the query
5187 * @param string $qualify prefix for columns in the order by list.
5190 * Internal function do not override.
5192 function create_qualified_order_by( $order_by, $qualify)
5193 { // if the column is empty, but the sort order is defined, the value will throw an error, so do not proceed if no order by is given
5194 if (empty($order_by))
5198 $order_by_clause = " ORDER BY ";
5199 $tmp = explode(",", $order_by);
5201 foreach ( $tmp as $stmp)
5203 $stmp = (substr_count($stmp, ".") > 0?trim($stmp):"$qualify." . trim($stmp));
5204 $order_by_clause .= $comma . $stmp;
5207 return $order_by_clause;
5211 * Combined the contents of street field 2 thru 4 into the main field
5213 * @param string $street_field
5216 function add_address_streets(
5220 $street_field_2 = $street_field.'_2';
5221 $street_field_3 = $street_field.'_3';
5222 $street_field_4 = $street_field.'_4';
5223 if ( isset($this->$street_field_2)) {
5224 $this->$street_field .= "\n". $this->$street_field_2;
5225 unset($this->$street_field_2);
5227 if ( isset($this->$street_field_3)) {
5228 $this->$street_field .= "\n". $this->$street_field_3;
5229 unset($this->$street_field_3);
5231 if ( isset($this->$street_field_4)) {
5232 $this->$street_field .= "\n". $this->$street_field_4;
5233 unset($this->$street_field_4);
5235 if ( isset($this->$street_field)) {
5236 $this->$street_field = trim($this->$street_field, "\n");
5240 * Encrpyt and base64 encode an 'encrypt' field type in the bean using Blowfish. The default system key is stored in cache/Blowfish/{keytype}
5241 * @param STRING value -plain text value of the bean field.
5244 function encrpyt_before_save($value)
5246 require_once("include/utils/encryption_utils.php");
5247 return blowfishEncode(blowfishGetKey('encrypt_field'),$value);
5251 * Decode and decrypt a base 64 encoded string with field type 'encrypt' in this bean using Blowfish.
5252 * @param STRING value - an encrypted and base 64 encoded string.
5255 function decrypt_after_retrieve($value)
5257 require_once("include/utils/encryption_utils.php");
5258 return blowfishDecode(blowfishGetKey('encrypt_field'), $value);
5262 * Moved from save() method, functionality is the same, but this is intended to handle
5263 * Optimistic locking functionality.
5265 private function _checkOptimisticLocking($action, $isUpdate){
5266 if($this->optimistic_lock && !isset($_SESSION['o_lock_fs'])){
5267 if(isset($_SESSION['o_lock_id']) && $_SESSION['o_lock_id'] == $this->id && $_SESSION['o_lock_on'] == $this->object_name)
5269 if($action == 'Save' && $isUpdate && isset($this->modified_user_id) && $this->has_been_modified_since($_SESSION['o_lock_dm'], $this->modified_user_id))
5271 $_SESSION['o_lock_class'] = get_class($this);
5272 $_SESSION['o_lock_module'] = $this->module_dir;
5273 $_SESSION['o_lock_object'] = $this->toArray();
5274 $saveform = "<form name='save' id='save' method='POST'>";
5275 foreach($_POST as $key=>$arg)
5277 $saveform .= "<input type='hidden' name='". addslashes($key) ."' value='". addslashes($arg) ."'>";
5279 $saveform .= "</form><script>document.getElementById('save').submit();</script>";
5280 $_SESSION['o_lock_save'] = $saveform;
5281 header('Location: index.php?module=OptimisticLock&action=LockResolve');
5286 unset ($_SESSION['o_lock_object']);
5287 unset ($_SESSION['o_lock_id']);
5288 unset ($_SESSION['o_lock_dm']);
5294 if(isset($_SESSION['o_lock_object'])) { unset ($_SESSION['o_lock_object']); }
5295 if(isset($_SESSION['o_lock_id'])) { unset ($_SESSION['o_lock_id']); }
5296 if(isset($_SESSION['o_lock_dm'])) { unset ($_SESSION['o_lock_dm']); }
5297 if(isset($_SESSION['o_lock_fs'])) { unset ($_SESSION['o_lock_fs']); }
5298 if(isset($_SESSION['o_lock_save'])) { unset ($_SESSION['o_lock_save']); }
5303 * Send assignment notifications and invites for meetings and calls
5305 private function _sendNotifications($check_notify){
5306 if($check_notify || (isset($this->notify_inworkflow) && $this->notify_inworkflow == true) // cn: bug 5795 - no invites sent to Contacts, and also bug 25995, in workflow, it will set the notify_on_save=true.
5307 && !$this->isOwner($this->created_by) ) // cn: bug 42727 no need to send email to owner (within workflow)
5309 $admin = new Administration();
5310 $admin->retrieveSettings();
5311 $sendNotifications = false;
5313 if ($admin->settings['notify_on'])
5315 $GLOBALS['log']->info("Notifications: user assignment has changed, checking if user receives notifications");
5316 $sendNotifications = true;
5318 elseif(isset($_REQUEST['send_invites']) && $_REQUEST['send_invites'] == 1)
5320 // cn: bug 5795 Send Invites failing for Contacts
5321 $sendNotifications = true;
5325 $GLOBALS['log']->info("Notifications: not sending e-mail, notify_on is set to OFF");
5329 if($sendNotifications == true)
5331 $notify_list = $this->get_notification_recipients();
5332 foreach ($notify_list as $notify_user)
5334 $this->send_assignment_notifications($notify_user, $admin);
5342 * Called from ImportFieldSanitize::relate(), when creating a new bean in a related module. Will
5343 * copies fields over from the current bean into the related. Designed to be overriden in child classes.
5345 * @param SugarBean $newbean newly created related bean
5347 public function populateRelatedBean(
5354 * Called during the import process before a bean save, to handle any needed pre-save logic when
5355 * importing a record
5357 public function beforeImportSave()
5362 * Called during the import process after a bean save, to handle any needed post-save logic when
5363 * importing a record
5365 public function afterImportSave()
5370 * This function is designed to cache references to field arrays that were previously stored in the
5371 * bean files and have since been moved to separate files. Was previously in include/CacheHandler.php
5374 * @param $module_dir string the module directory
5375 * @param $module string the name of the module
5376 * @param $key string the type of field array we are referencing, i.e. list_fields, column_fields, required_fields
5378 private function _loadCachedArray(
5384 static $moduleDefs = array();
5386 $fileName = 'field_arrays.php';
5388 $cache_key = "load_cached_array.$module_dir.$module.$key";
5389 $result = sugar_cache_retrieve($cache_key);
5392 // Use SugarCache::EXTERNAL_CACHE_NULL_VALUE to store null values in the cache.
5393 if($result == SugarCache::EXTERNAL_CACHE_NULL_VALUE)
5401 if(file_exists('modules/'.$module_dir.'/'.$fileName))
5403 // If the data was not loaded, try loading again....
5404 if(!isset($moduleDefs[$module]))
5406 include('modules/'.$module_dir.'/'.$fileName);
5407 $moduleDefs[$module] = $fields_array;
5409 // Now that we have tried loading, make sure it was loaded
5410 if(empty($moduleDefs[$module]) || empty($moduleDefs[$module][$module][$key]))
5412 // It was not loaded.... Fail. Cache null to prevent future repeats of this calculation
5413 sugar_cache_put($cache_key, SugarCache::EXTERNAL_CACHE_NULL_VALUE);
5417 // It has been loaded, cache the result.
5418 sugar_cache_put($cache_key, $moduleDefs[$module][$module][$key]);
5419 return $moduleDefs[$module][$module][$key];
5422 // It was not loaded.... Fail. Cache null to prevent future repeats of this calculation
5423 sugar_cache_put($cache_key, SugarCache::EXTERNAL_CACHE_NULL_VALUE);
5428 * Returns the ACL category for this module; defaults to the SugarBean::$acl_category if defined
5429 * otherwise it is SugarBean::$module_dir
5433 public function getACLCategory()
5435 return !empty($this->acl_category)?$this->acl_category:$this->module_dir;
5439 * Returns the query used for the export functionality for a module. Override this method if you wish
5440 * to have a custom query to pull this data together instead
5442 * @param string $order_by
5443 * @param string $where
5444 * @return string SQL query
5446 public function create_export_query($order_by, $where)
5448 return $this->create_new_list_query($order_by, $where, array(), array(), 0, '', false, $this, true, true);