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-2013 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 * Unique object identifier
84 * When createing a bean, you can specify a value in the id column as
85 * long as that value is unique. During save, if the system finds an
86 * id, it assumes it is an update. Setting new_with_id to true will
87 * make sure the system performs an insert instead of an update.
89 * @var BOOL -- default false
91 var $new_with_id = false;
95 * How deep logic hooks can go
98 protected $max_logic_depth = 10;
101 * Disble vardefs. This should be set to true only for beans that do not have varders. Tracker is an example
103 * @var BOOL -- default false
105 var $disable_vardefs = false;
109 * holds the full name of the user that an item is assigned to. Only used if notifications
110 * are turned on and going to be sent out.
114 var $new_assigned_user_name;
117 * An array of booleans. This array is cleared out when data is loaded.
118 * As date/times are converted, a "1" is placed under the key, the field is converted.
120 * @var Array of booleans
122 var $processed_dates_times = array();
125 * Whether to process date/time fields for storage in the database in GMT
129 var $process_save_dates =true;
132 * This signals to the bean that it is being saved in a mass mode.
133 * Examples of this kind of save are import and mass update.
134 * We turn off notificaitons of this is the case to make things more efficient.
138 var $save_from_post = true;
141 * When running a query on related items using the method: retrieve_by_string_fields
142 * this value will be set to true if more than one item matches the search criteria.
146 var $duplicates_found = false;
149 * true if this bean has been deleted, false otherwise.
156 * Should the date modified column of the bean be updated during save?
157 * This is used for admin level functionality that should not be updating
158 * the date modified. This is only used by sync to allow for updates to be
159 * replicated in a way that will not cause them to be replicated back.
163 var $update_date_modified = true;
166 * Should the modified by column of the bean be updated during save?
167 * This is used for admin level functionality that should not be updating
168 * the modified by column. This is only used by sync to allow for updates to be
169 * replicated in a way that will not cause them to be replicated back.
173 var $update_modified_by = true;
176 * Setting this to true allows for updates to overwrite the date_entered
180 var $update_date_entered = false;
183 * This allows for seed data to be created without using the current uesr to set the id.
184 * This should be replaced by altering the current user before the call to save.
188 //TODO This should be replaced by altering the current user before the call to save.
189 var $set_created_by = true;
194 * The database table where records of this Bean are stored.
198 var $table_name = '';
201 * This is the singular name of the bean. (i.e. Contact).
205 var $object_name = '';
207 /** Set this to true if you query contains a sub-select and bean is converting both select statements
208 * into count queries.
210 var $ungreedy_count=false;
213 * The name of the module folder for this type of bean.
217 var $module_dir = '';
218 var $module_name = '';
222 var $column_fields = array();
223 var $list_fields = array();
224 var $additional_column_fields = array();
225 var $relationship_fields = array();
226 var $current_notify_user;
227 var $fetched_row=false;
228 var $fetched_rel_row = array();
230 var $force_load_details = false;
231 var $optimistic_lock = false;
232 var $disable_custom_fields = false;
233 var $number_formatting_done = false;
234 var $process_field_encrypted=false;
236 * The default ACL type
238 var $acltype = 'module';
241 var $additional_meta_fields = array();
244 * Set to true in the child beans if the module supports importing
246 var $importable = false;
249 * Set to true in the child beans if the module use the special notification template
251 var $special_notification = false;
254 * Set to true if the bean is being dealt with in a workflow
256 var $in_workflow = false;
260 * By default it will be true but if any module is to be kept non visible
261 * to tracker, then its value needs to be overriden in that particular module to false.
264 var $tracker_visibility = true;
267 * Used to pass inner join string to ListView Data.
269 var $listview_inner_join = array();
272 * Set to true in <modules>/Import/views/view.step4.php if a module is being imported
274 var $in_import = false;
276 * A way to keep track of the loaded relationships so when we clone the object we can unset them.
280 protected $loaded_relationships = array();
283 * set to true if dependent fields updated
285 protected $is_updated_dependent_fields = false;
288 * Blowfish encryption key
291 static protected $field_key;
294 * Cache of fields which can contain files
298 static protected $fileFields = array();
301 * Constructor for the bean, it performs following tasks:
303 * 1. Initalized a database connections
304 * 2. Load the vardefs for the module implemeting the class. cache the entries
306 * 3. Setup row-level security preference
307 * All implementing classes must call this constructor using the parent::SugarBean() class.
312 global $dictionary, $current_user;
313 static $loaded_defs = array();
314 $this->db = DBManagerFactory::getInstance();
315 if (empty($this->module_name))
316 $this->module_name = $this->module_dir;
317 if((false == $this->disable_vardefs && empty($loaded_defs[$this->object_name])) || !empty($GLOBALS['reload_vardefs']))
319 VardefManager::loadVardef($this->module_dir, $this->object_name);
321 // build $this->column_fields from the field_defs if they exist
322 if (!empty($dictionary[$this->object_name]['fields'])) {
323 foreach ($dictionary[$this->object_name]['fields'] as $key=>$value_array) {
324 $column_fields[] = $key;
325 if(!empty($value_array['required']) && !empty($value_array['name'])) {
326 $this->required_fields[$value_array['name']] = 1;
329 $this->column_fields = $column_fields;
332 //setup custom fields
333 if(!isset($this->custom_fields) &&
334 empty($this->disable_custom_fields))
336 $this->setupCustomFields($this->module_dir);
338 //load up field_arrays from CacheHandler;
339 if(empty($this->list_fields))
340 $this->list_fields = $this->_loadCachedArray($this->module_dir, $this->object_name, 'list_fields');
341 if(empty($this->column_fields))
342 $this->column_fields = $this->_loadCachedArray($this->module_dir, $this->object_name, 'column_fields');
343 if(empty($this->required_fields))
344 $this->required_fields = $this->_loadCachedArray($this->module_dir, $this->object_name, 'required_fields');
346 if(isset($GLOBALS['dictionary'][$this->object_name]) && !$this->disable_vardefs)
348 $this->field_name_map = $dictionary[$this->object_name]['fields'];
349 $this->field_defs = $dictionary[$this->object_name]['fields'];
351 if(!empty($dictionary[$this->object_name]['optimistic_locking']))
353 $this->optimistic_lock=true;
356 $loaded_defs[$this->object_name]['column_fields'] =& $this->column_fields;
357 $loaded_defs[$this->object_name]['list_fields'] =& $this->list_fields;
358 $loaded_defs[$this->object_name]['required_fields'] =& $this->required_fields;
359 $loaded_defs[$this->object_name]['field_name_map'] =& $this->field_name_map;
360 $loaded_defs[$this->object_name]['field_defs'] =& $this->field_defs;
364 $this->column_fields =& $loaded_defs[$this->object_name]['column_fields'] ;
365 $this->list_fields =& $loaded_defs[$this->object_name]['list_fields'];
366 $this->required_fields =& $loaded_defs[$this->object_name]['required_fields'];
367 $this->field_name_map =& $loaded_defs[$this->object_name]['field_name_map'];
368 $this->field_defs =& $loaded_defs[$this->object_name]['field_defs'];
369 $this->added_custom_field_defs = true;
371 if(!isset($this->custom_fields) &&
372 empty($this->disable_custom_fields))
374 $this->setupCustomFields($this->module_dir, false);
376 if(!empty($dictionary[$this->object_name]['optimistic_locking']))
378 $this->optimistic_lock=true;
382 if($this->bean_implements('ACL') && !empty($GLOBALS['current_user'])){
383 $this->acl_fields = (isset($dictionary[$this->object_name]['acl_fields']) && $dictionary[$this->object_name]['acl_fields'] === false)?false:true;
385 $this->populateDefaultValues();
390 * Returns the object name. If object_name is not set, table_name is returned.
392 * All implementing classes must set a value for the object_name variable.
394 * @param array $arr row of data fetched from the database.
398 function getObjectName()
400 if ($this->object_name)
401 return $this->object_name;
403 // This is a quick way out. The generated metadata files have the table name
404 // as the key. The correct way to do this is to override this function
405 // in bean and return the object name. That requires changing all the beans
406 // as well as put the object name in the generator.
407 return $this->table_name;
411 * Returns a list of fields with their definitions that have the audited property set to true.
412 * Before calling this function, check whether audit has been enabled for the table/module or not.
413 * You would set the audit flag in the implemting module's vardef file.
416 * @see is_AuditEnabled
418 * Internal function, do not override.
420 function getAuditEnabledFieldDefinitions()
422 $aclcheck = $this->bean_implements('ACL');
423 $is_owner = $this->isOwner($GLOBALS['current_user']->id);
424 if (!isset($this->audit_enabled_fields))
427 $this->audit_enabled_fields=array();
428 foreach ($this->field_defs as $field => $properties)
433 !empty($properties['Audited']) || !empty($properties['audited']))
437 $this->audit_enabled_fields[$field]=$properties;
442 return $this->audit_enabled_fields;
446 * Return true if auditing is enabled for this object
447 * You would set the audit flag in the implemting module's vardef file.
451 * Internal function, do not override.
453 function is_AuditEnabled()
456 if (isset($dictionary[$this->getObjectName()]['audited']))
458 return $dictionary[$this->getObjectName()]['audited'];
469 * Returns the name of the audit table.
470 * Audit table's name is based on implementing class' table name.
472 * @return String Audit table name.
474 * Internal function, do not override.
476 function get_audit_table_name()
478 return $this->getTableName().'_audit';
482 * Returns the name of the custom table.
483 * Custom table's name is based on implementing class' table name.
485 * @return String Custom table name.
487 * Internal function, do not override.
489 public function get_custom_table_name()
491 return $this->getTableName().'_cstm';
495 * If auditing is enabled, create the audit table.
497 * Function is used by the install scripts and a repair utility in the admin panel.
499 * Internal function, do not override.
501 function create_audit_table()
504 $table_name=$this->get_audit_table_name();
506 require('metadata/audit_templateMetaData.php');
508 // Bug: 52583 Need ability to customize template for audit tables
509 $custom = 'custom/metadata/audit_templateMetaData_' . $this->getTableName() . '.php';
510 if (file_exists($custom))
515 $fieldDefs = $dictionary['audit']['fields'];
516 $indices = $dictionary['audit']['indices'];
518 // Renaming template indexes to fit the particular audit table (removed the brittle hard coding)
519 foreach($indices as $nr => $properties){
520 $indices[$nr]['name'] = 'idx_' . strtolower($this->getTableName()) . '_' . $properties['name'];
524 if(isset($dictionary['audit']['engine'])) {
525 $engine = $dictionary['audit']['engine'];
526 } else if(isset($dictionary[$this->getObjectName()]['engine'])) {
527 $engine = $dictionary[$this->getObjectName()]['engine'];
530 $this->db->createTableParams($table_name, $fieldDefs, $indices, $engine);
534 * Returns the implementing class' table name.
536 * All implementing classes set a value for the table_name variable. This value is returned as the
537 * table name. If not set, table name is extracted from the implementing module's vardef.
539 * @return String Table name.
541 * Internal function, do not override.
543 public function getTableName()
545 if(isset($this->table_name))
547 return $this->table_name;
550 return $dictionary[$this->getObjectName()]['table'];
554 * Returns field definitions for the implementing module.
556 * The definitions were loaded in the constructor.
558 * @return Array Field definitions.
560 * Internal function, do not override.
562 function getFieldDefinitions()
564 return $this->field_defs;
568 * Returns index definitions for the implementing module.
570 * The definitions were loaded in the constructor.
572 * @return Array Index definitions.
574 * Internal function, do not override.
576 function getIndices()
579 if(isset($dictionary[$this->getObjectName()]['indices']))
581 return $dictionary[$this->getObjectName()]['indices'];
587 * Returns field definition for the requested field name.
589 * The definitions were loaded in the constructor.
591 * @param string field name,
592 * @return Array Field properties or boolean false if the field doesn't exist
594 * Internal function, do not override.
596 function getFieldDefinition($name)
598 if ( !isset($this->field_defs[$name]) )
601 return $this->field_defs[$name];
605 * Returnss definition for the id field name.
607 * The definitions were loaded in the constructor.
609 * @return Array Field properties.
611 * Internal function, do not override.
613 function getPrimaryFieldDefinition()
615 $def = $this->getFieldDefinition("id");
617 $def = $this->getFieldDefinition(0);
620 $defs = $this->field_defs;
622 $def = current($defs);
627 * Returns the value for the requested field.
629 * When a row of data is fetched using the bean, all fields are created as variables in the context
630 * of the bean and then fetched values are set in these variables.
632 * @param string field name,
633 * @return varies Field value.
635 * Internal function, do not override.
637 function getFieldValue($name)
639 if (!isset($this->$name)){
642 if($this->$name === TRUE){
645 if($this->$name === FALSE){
652 * Basically undoes the effects of SugarBean::populateDefaultValues(); this method is best called right after object
655 public function unPopulateDefaultValues()
657 if ( !is_array($this->field_defs) )
660 foreach ($this->field_defs as $field => $value) {
661 if( !empty($this->$field)
662 && ((isset($value['default']) && $this->$field == $value['default']) || (!empty($value['display_default']) && $this->$field == $value['display_default']))
664 $this->$field = null;
667 if(!empty($this->$field) && !empty($value['display_default']) && in_array($value['type'], array('date', 'datetime', 'datetimecombo')) &&
668 $this->$field == $this->parseDateDefault($value['display_default'], ($value['type'] != 'date'))) {
669 $this->$field = null;
675 * Create date string from default value
677 * @param string $value
678 * @param bool $time Should be expect time set too?
681 protected function parseDateDefault($value, $time = false)
685 $dtAry = explode('&', $value, 2);
686 $dateValue = $timedate->getNow(true)->modify($dtAry[0]);
687 if(!empty($dtAry[1])) {
688 $timeValue = $timedate->fromString($dtAry[1]);
689 $dateValue->setTime($timeValue->hour, $timeValue->min, $timeValue->sec);
691 return $timedate->asUser($dateValue);
693 return $timedate->asUserDate($timedate->getNow(true)->modify($value));
697 function populateDefaultValues($force=false){
698 if ( !is_array($this->field_defs) )
700 foreach($this->field_defs as $field=>$value){
701 if((isset($value['default']) || !empty($value['display_default'])) && ($force || empty($this->$field))){
702 $type = $value['type'];
706 if(!empty($value['display_default'])){
707 $this->$field = $this->parseDateDefault($value['display_default']);
711 case 'datetimecombo':
712 if(!empty($value['display_default'])){
713 $this->$field = $this->parseDateDefault($value['display_default'], true);
717 if(empty($value['default']) && !empty($value['display_default']))
718 $this->$field = $value['display_default'];
720 $this->$field = $value['default'];
723 if(isset($this->$field)){
727 if ( isset($value['default']) && $value['default'] !== '' ) {
728 $this->$field = htmlentities($value['default'], ENT_QUOTES, 'UTF-8');
739 * Removes relationship metadata cache.
741 * Every module that has relationships defined with other modules, has this meta data cached. The cache is
742 * stores in 2 locations: relationships table and file system. This method clears the cache from both locations.
744 * @param string $key module whose meta cache is to be cleared.
745 * @param string $db database handle.
746 * @param string $tablename table name
747 * @param string $dictionary vardef for the module
748 * @param string $module_dir name of subdirectory where module is installed.
753 * Internal function, do not override.
755 function removeRelationshipMeta($key,$db,$tablename,$dictionary,$module_dir)
757 //load the module dictionary if not supplied.
758 if ((!isset($dictionary) or empty($dictionary)) && !empty($module_dir))
760 $filename='modules/'. $module_dir . '/vardefs.php';
761 if(file_exists($filename))
766 if (!is_array($dictionary) or !array_key_exists($key, $dictionary))
768 $GLOBALS['log']->fatal("removeRelationshipMeta: Metadata for table ".$tablename. " does not exist");
769 display_notice("meta data absent for table ".$tablename." keyed to $key ");
773 if (isset($dictionary[$key]['relationships']))
775 $RelationshipDefs = $dictionary[$key]['relationships'];
776 foreach ($RelationshipDefs as $rel_name)
778 Relationship::delete($rel_name,$db);
786 * This method has been deprecated.
788 * @see removeRelationshipMeta()
789 * @deprecated 4.5.1 - Nov 14, 2006
792 function remove_relationship_meta($key,$db,$log,$tablename,$dictionary,$module_dir)
794 SugarBean::removeRelationshipMeta($key,$db,$tablename,$dictionary,$module_dir);
799 * Populates the relationship meta for a module.
801 * It is called during setup/install. It is used statically to create relationship meta data for many-to-many tables.
803 * @param string $key name of the object.
804 * @param object $db database handle.
805 * @param string $tablename table, meta data is being populated for.
806 * @param array dictionary vardef dictionary for the object. *
807 * @param string module_dir name of subdirectory where module is installed.
808 * @param boolean $iscustom Optional,set to true if module is installed in a custom directory. Default value is false.
811 * Internal function, do not override.
813 function createRelationshipMeta($key,$db,$tablename,$dictionary,$module_dir,$iscustom=false)
815 //load the module dictionary if not supplied.
816 if (empty($dictionary) && !empty($module_dir))
820 $filename='custom/modules/' . $module_dir . '/Ext/Vardefs/vardefs.ext.php';
826 // a very special case for the Employees module
827 // this must be done because the Employees/vardefs.php does an include_once on
829 $filename='modules/Users/vardefs.php';
833 $filename='modules/'. $module_dir . '/vardefs.php';
837 if(file_exists($filename))
840 // cn: bug 7679 - dictionary entries defined as $GLOBALS['name'] not found
841 if(empty($dictionary) || !empty($GLOBALS['dictionary'][$key]))
843 $dictionary = $GLOBALS['dictionary'];
848 $GLOBALS['log']->debug("createRelationshipMeta: no metadata file found" . $filename);
853 if (!is_array($dictionary) or !array_key_exists($key, $dictionary))
855 $GLOBALS['log']->fatal("createRelationshipMeta: Metadata for table ".$tablename. " does not exist");
856 display_notice("meta data absent for table ".$tablename." keyed to $key ");
860 if (isset($dictionary[$key]['relationships']))
863 $RelationshipDefs = $dictionary[$key]['relationships'];
867 $beanList_ucase=array_change_key_case ( $beanList ,CASE_UPPER);
868 foreach ($RelationshipDefs as $rel_name=>$rel_def)
870 if (isset($rel_def['lhs_module']) and !isset($beanList_ucase[strtoupper($rel_def['lhs_module'])])) {
871 $GLOBALS['log']->debug('skipping orphaned relationship record ' . $rel_name . ' lhs module is missing ' . $rel_def['lhs_module']);
874 if (isset($rel_def['rhs_module']) and !isset($beanList_ucase[strtoupper($rel_def['rhs_module'])])) {
875 $GLOBALS['log']->debug('skipping orphaned relationship record ' . $rel_name . ' rhs module is missing ' . $rel_def['rhs_module']);
880 //check whether relationship exists or not first.
881 if (Relationship::exists($rel_name,$db))
883 $GLOBALS['log']->debug('Skipping, reltionship already exists '.$rel_name);
887 $seed = BeanFactory::getBean("Relationships");
888 $keys = array_keys($seed->field_defs);
890 foreach($keys as $key)
894 $toInsert[$key] = create_guid();
896 else if ($key == "relationship_name")
898 $toInsert[$key] = $rel_name;
900 else if (isset($rel_def[$key]))
902 $toInsert[$key] = $rel_def[$key];
904 //todo specify defaults if meta not defined.
908 $column_list = implode(",", array_keys($toInsert));
909 $value_list = "'" . implode("','", array_values($toInsert)) . "'";
911 //create the record. todo add error check.
912 $insert_string = "INSERT into relationships (" .$column_list. ") values (".$value_list.")";
913 $db->query($insert_string, true);
920 //log informational message stating no relationships meta was set for this bean.
926 * This method has been deprecated.
927 * @see createRelationshipMeta()
928 * @deprecated 4.5.1 - Nov 14, 2006
931 function create_relationship_meta($key,&$db,&$log,$tablename,$dictionary,$module_dir)
933 SugarBean::createRelationshipMeta($key,$db,$tablename,$dictionary,$module_dir);
938 * Handle the following when a SugarBean object is cloned
940 * Currently all this does it unset any relationships that were created prior to cloning the object
944 public function __clone()
946 if(!empty($this->loaded_relationships)) {
947 foreach($this->loaded_relationships as $rel) {
955 * Loads the request relationship. This method should be called before performing any operations on the related data.
957 * This method searches the vardef array for the requested attribute's definition. If the attribute is of the type
958 * link then it creates a similary named variable and loads the relationship definition.
960 * @param string $rel_name relationship/attribute name.
963 function load_relationship($rel_name)
965 $GLOBALS['log']->debug("SugarBean[{$this->object_name}].load_relationships, Loading relationship (".$rel_name.").");
967 if (empty($rel_name))
969 $GLOBALS['log']->error("SugarBean.load_relationships, Null relationship name passed.");
972 $fieldDefs = $this->getFieldDefinitions();
974 //find all definitions of type link.
975 if (!empty($fieldDefs[$rel_name]))
977 //initialize a variable of type Link
978 require_once('data/Link2.php');
979 $class = load_link_class($fieldDefs[$rel_name]);
980 if (isset($this->$rel_name) && $this->$rel_name instanceof $class) {
983 //if rel_name is provided, search the fieldef array keys by name.
984 if (isset($fieldDefs[$rel_name]['type']) && $fieldDefs[$rel_name]['type'] == 'link')
986 if ($class == "Link2")
987 $this->$rel_name = new $class($rel_name, $this);
989 $this->$rel_name = new $class($fieldDefs[$rel_name]['relationship'], $this, $fieldDefs[$rel_name]);
991 if (empty($this->$rel_name) ||
992 (method_exists($this->$rel_name, "loadedSuccesfully") && !$this->$rel_name->loadedSuccesfully()))
994 unset($this->$rel_name);
997 // keep track of the loaded relationships
998 $this->loaded_relationships[] = $rel_name;
1002 $GLOBALS['log']->debug("SugarBean.load_relationships, Error Loading relationship (".$rel_name.")");
1007 * Loads all attributes of type link.
1009 * DO NOT CALL THIS FUNCTION IF YOU CAN AVOID IT. Please use load_relationship directly instead.
1011 * Method searches the implmenting module's vardef file for attributes of type link, and for each attribute
1012 * create a similary named variable and load the relationship definition.
1016 * Internal function, do not override.
1018 function load_relationships()
1020 $GLOBALS['log']->debug("SugarBean.load_relationships, Loading all relationships of type link.");
1021 $linked_fields=$this->get_linked_fields();
1022 foreach($linked_fields as $name=>$properties)
1024 $this->load_relationship($name);
1029 * Returns an array of beans of related data.
1031 * For instance, if an account is related to 10 contacts , this function will return an array of contacts beans (10)
1032 * with each bean representing a contact record.
1033 * Method will load the relationship if not done so already.
1035 * @param string $field_name relationship to be loaded.
1036 * @param string $bean name class name of the related bean.
1037 * @param array $sort_array optional, unused
1038 * @param int $begin_index Optional, default 0, unused.
1039 * @param int $end_index Optional, default -1
1040 * @param int $deleted Optional, Default 0, 0 adds deleted=0 filter, 1 adds deleted=1 filter.
1041 * @param string $optional_where, Optional, default empty.
1043 * Internal function, do not override.
1045 function get_linked_beans($field_name,$bean_name, $sort_array = array(), $begin_index = 0, $end_index = -1,
1046 $deleted=0, $optional_where="")
1048 //if bean_name is Case then use aCase
1049 if($bean_name=="Case")
1050 $bean_name = "aCase";
1052 if($this->load_relationship($field_name)) {
1053 if ($this->$field_name instanceof Link) {
1054 // some classes are still based on Link, e.g. TeamSetLink
1055 return array_values($this->$field_name->getBeans(new $bean_name(), $sort_array, $begin_index, $end_index, $deleted, $optional_where));
1058 if ($end_index != -1 || !empty($deleted) || !empty($optional_where))
1059 return array_values($this->$field_name->getBeans(array(
1060 'where' => $optional_where,
1061 'deleted' => $deleted,
1062 'limit' => ($end_index - $begin_index)
1065 return array_values($this->$field_name->getBeans());
1073 * Returns an array of fields that are of type link.
1075 * @return array List of fields.
1077 * Internal function, do not override.
1079 function get_linked_fields()
1082 $linked_fields=array();
1084 // require_once('data/Link.php');
1086 $fieldDefs = $this->getFieldDefinitions();
1088 //find all definitions of type link.
1089 if (!empty($fieldDefs))
1091 foreach ($fieldDefs as $name=>$properties)
1093 if (array_search('link',$properties) === 'type')
1095 $linked_fields[$name]=$properties;
1100 return $linked_fields;
1104 * Returns an array of fields that are able to be Imported into
1105 * i.e. 'importable' not set to 'false'
1107 * @return array List of fields.
1109 * Internal function, do not override.
1111 function get_importable_fields()
1113 $importableFields = array();
1115 $fieldDefs= $this->getFieldDefinitions();
1117 if (!empty($fieldDefs)) {
1118 foreach ($fieldDefs as $key=>$value_array) {
1119 if ( (isset($value_array['importable'])
1120 && (is_string($value_array['importable']) && $value_array['importable'] == 'false'
1121 || is_bool($value_array['importable']) && $value_array['importable'] == false))
1122 || (isset($value_array['type']) && $value_array['type'] == 'link')
1123 || (isset($value_array['auto_increment'])
1124 && ($value_array['type'] == true || $value_array['type'] == 'true')) ) {
1125 // only allow import if we force it
1126 if (isset($value_array['importable'])
1127 && (is_string($value_array['importable']) && $value_array['importable'] == 'true'
1128 || is_bool($value_array['importable']) && $value_array['importable'] == true)) {
1129 $importableFields[$key]=$value_array;
1134 //Expose the cooresponding id field of a relate field if it is only defined as a link so that users can relate records by id during import
1135 if( isset($value_array['type']) && ($value_array['type'] == 'relate') && isset($value_array['id_name']) )
1137 $idField = $value_array['id_name'];
1138 if( isset($fieldDefs[$idField]) && isset($fieldDefs[$idField]['type'] ) && $fieldDefs[$idField]['type'] == 'link' )
1140 $tmpFieldDefs = $fieldDefs[$idField];
1141 $tmpFieldDefs['vname'] = translate($value_array['vname'], $this->module_dir) . " " . $GLOBALS['app_strings']['LBL_ID'];
1142 $importableFields[$idField]=$tmpFieldDefs;
1146 $importableFields[$key]=$value_array;
1151 return $importableFields;
1155 * Returns an array of fields that are of type relate.
1157 * @return array List of fields.
1159 * Internal function, do not override.
1161 function get_related_fields()
1164 $related_fields=array();
1166 // require_once('data/Link.php');
1168 $fieldDefs = $this->getFieldDefinitions();
1170 //find all definitions of type link.
1171 if (!empty($fieldDefs))
1173 foreach ($fieldDefs as $name=>$properties)
1175 if (array_search('relate',$properties) === 'type')
1177 $related_fields[$name]=$properties;
1182 return $related_fields;
1186 * Returns an array of fields that are required for import
1190 function get_import_required_fields()
1192 $importable_fields = $this->get_importable_fields();
1193 $required_fields = array();
1195 foreach ( $importable_fields as $name => $properties ) {
1196 if ( isset($properties['importable']) && is_string($properties['importable']) && $properties['importable'] == 'required' ) {
1197 $required_fields[$name] = $properties;
1201 return $required_fields;
1205 * Iterates through all the relationships and deletes all records for reach relationship.
1207 * @param string $id Primary key value of the parent reocrd
1209 function delete_linked($id)
1211 $linked_fields=$this->get_linked_fields();
1212 foreach ($linked_fields as $name => $value)
1214 if ($this->load_relationship($name))
1216 $this->$name->delete($id);
1220 $GLOBALS['log']->fatal("error loading relationship $name");
1226 * Creates tables for the module implementing the class.
1227 * If you override this function make sure that your code can handles table creation.
1230 function create_tables()
1234 $key = $this->getObjectName();
1235 if (!array_key_exists($key, $dictionary))
1237 $GLOBALS['log']->fatal("create_tables: Metadata for table ".$this->table_name. " does not exist");
1238 display_notice("meta data absent for table ".$this->table_name." keyed to $key ");
1242 if(!$this->db->tableExists($this->table_name))
1244 $this->db->createTable($this);
1245 if($this->bean_implements('ACL')){
1246 if(!empty($this->acltype)){
1247 ACLAction::addActions($this->getACLCategory(), $this->acltype);
1249 ACLAction::addActions($this->getACLCategory());
1255 echo "Table already exists : $this->table_name<br>";
1257 if($this->is_AuditEnabled()){
1258 if (!$this->db->tableExists($this->get_audit_table_name())) {
1259 $this->create_audit_table();
1267 * Delete the primary table for the module implementing the class.
1268 * If custom fields were added to this table/module, the custom table will be removed too, along with the cache
1269 * entries that define the custom fields.
1272 function drop_tables()
1275 $key = $this->getObjectName();
1276 if (!array_key_exists($key, $dictionary))
1278 $GLOBALS['log']->fatal("drop_tables: Metadata for table ".$this->table_name. " does not exist");
1279 echo "meta data absent for table ".$this->table_name."<br>\n";
1281 if(empty($this->table_name))return;
1282 if ($this->db->tableExists($this->table_name))
1284 $this->db->dropTable($this);
1285 if ($this->db->tableExists($this->table_name. '_cstm'))
1287 $this->db->dropTableName($this->table_name. '_cstm');
1288 DynamicField::deleteCache();
1290 if ($this->db->tableExists($this->get_audit_table_name())) {
1291 $this->db->dropTableName($this->get_audit_table_name());
1300 * Loads the definition of custom fields defined for the module.
1301 * Local file system cache is created as needed.
1303 * @param string $module_name setting up custom fields for this module.
1304 * @param boolean $clean_load Optional, default true, rebuilds the cache if set to true.
1306 function setupCustomFields($module_name, $clean_load=true)
1308 $this->custom_fields = new DynamicField($module_name);
1309 $this->custom_fields->setup($this);
1314 * Cleans char, varchar, text, etc. fields of XSS type materials
1316 function cleanBean() {
1317 foreach($this->field_defs as $key => $def) {
1319 if (isset($def['type'])) {
1322 if(isset($def['dbType']))
1323 $type .= $def['dbType'];
1325 if($def['type'] == 'html' || $def['type'] == 'longhtml') {
1326 $this->$key = SugarCleaner::cleanHtml($this->$key, true);
1327 } elseif((strpos($type, 'char') !== false ||
1328 strpos($type, 'text') !== false ||
1332 $this->$key = SugarCleaner::cleanHtml($this->$key);
1338 * Implements a generic insert and update logic for any SugarBean
1339 * This method only works for subclasses that implement the same variable names.
1340 * This method uses the presence of an id field that is not null to signify and update.
1341 * The id field should not be set otherwise.
1343 * @param boolean $check_notify Optional, default false, if set to true assignee of the record is notified via email.
1344 * @todo Add support for field type validation and encoding of parameters.
1346 function save($check_notify = FALSE)
1348 $this->in_save = true;
1349 // cn: SECURITY - strip XSS potential vectors
1351 // This is used so custom/3rd-party code can be upgraded with fewer issues, this will be removed in a future release
1352 $this->fixUpFormatting();
1354 global $current_user, $action;
1357 if(empty($this->id))
1362 if ( $this->new_with_id == true )
1366 if(empty($this->date_modified) || $this->update_date_modified)
1368 $this->date_modified = $GLOBALS['timedate']->nowDb();
1371 $this->_checkOptimisticLocking($action, $isUpdate);
1373 if(!empty($this->modified_by_name)) $this->old_modified_by_name = $this->modified_by_name;
1374 if($this->update_modified_by)
1376 $this->modified_user_id = 1;
1378 if (!empty($current_user))
1380 $this->modified_user_id = $current_user->id;
1381 $this->modified_by_name = $current_user->user_name;
1384 if ($this->deleted != 1)
1388 if (empty($this->date_entered))
1390 $this->date_entered = $this->date_modified;
1392 if($this->set_created_by == true)
1394 // created by should always be this user
1395 $this->created_by = (isset($current_user)) ? $current_user->id : "";
1397 if( $this->new_with_id == false)
1399 $this->id = create_guid();
1405 require_once("data/BeanFactory.php");
1406 BeanFactory::registerBean($this->module_name, $this);
1408 if (empty($GLOBALS['updating_relationships']) && empty($GLOBALS['saving_relationships']) && empty ($GLOBALS['resavingRelatedBeans']))
1410 $GLOBALS['saving_relationships'] = true;
1411 // let subclasses save related field changes
1412 $this->save_relationship_changes($isUpdate);
1413 $GLOBALS['saving_relationships'] = false;
1415 if($isUpdate && !$this->update_date_entered)
1417 unset($this->date_entered);
1419 // call the custom business logic
1420 $custom_logic_arguments['check_notify'] = $check_notify;
1423 $this->call_custom_logic("before_save", $custom_logic_arguments);
1424 unset($custom_logic_arguments);
1426 // If we're importing back semi-colon separated non-primary emails
1427 if ($this->hasEmails() && !empty($this->email_addresses_non_primary) && is_array($this->email_addresses_non_primary))
1429 // Add each mail to the account
1430 foreach ($this->email_addresses_non_primary as $mail)
1432 $this->emailAddress->addAddress($mail);
1434 $this->emailAddress->save($this->id, $this->module_dir);
1437 if(isset($this->custom_fields))
1439 $this->custom_fields->bean = $this;
1440 $this->custom_fields->save($isUpdate);
1443 // use the db independent query generator
1444 $this->preprocess_fields_on_save();
1446 //construct the SQL to create the audit record if auditing is enabled.
1447 $auditDataChanges=array();
1448 if ($this->is_AuditEnabled()) {
1449 if ($isUpdate && !isset($this->fetched_row)) {
1450 $GLOBALS['log']->debug('Auditing: Retrieve was not called, audit record will not be created.');
1452 $auditDataChanges=$this->db->getAuditDataChanges($this);
1456 $this->_sendNotifications($check_notify);
1459 $this->db->update($this);
1461 $this->db->insert($this);
1464 if (!empty($auditDataChanges) && is_array($auditDataChanges))
1466 foreach ($auditDataChanges as $change)
1468 $this->db->save_audit_records($this,$change);
1473 if (empty($GLOBALS['resavingRelatedBeans'])){
1474 SugarRelationship::resaveRelatedBeans();
1477 // populate fetched row with current bean values
1478 foreach ($auditDataChanges as $change) {
1479 $this->fetched_row[$change['field_name']] = $change['after'];
1483 //If we aren't in setup mode and we have a current user and module, then we track
1484 if(isset($GLOBALS['current_user']) && isset($this->module_dir))
1486 $this->track_view($current_user->id, $this->module_dir, 'save');
1489 $this->call_custom_logic('after_save', '');
1491 //Now that the record has been saved, we don't want to insert again on further saves
1492 $this->new_with_id = false;
1493 $this->in_save = false;
1499 * Performs a check if the record has been modified since the specified date
1501 * @param date $date Datetime for verification
1502 * @param string $modified_user_id User modified by
1504 function has_been_modified_since($date, $modified_user_id)
1506 global $current_user;
1507 $date = $this->db->convert($this->db->quoted($date), 'datetime');
1508 if (isset($current_user))
1510 $query = "SELECT date_modified FROM $this->table_name WHERE id='$this->id' AND modified_user_id != '$current_user->id'
1511 AND (modified_user_id != '$modified_user_id' OR date_modified > $date)";
1512 $result = $this->db->query($query);
1514 if($this->db->fetchByAssoc($result))
1523 * Determines which users receive a notification
1525 function get_notification_recipients() {
1526 $notify_user = new User();
1527 $notify_user->retrieve($this->assigned_user_id);
1528 $this->new_assigned_user_name = $notify_user->full_name;
1530 $GLOBALS['log']->info("Notifications: recipient is $this->new_assigned_user_name");
1532 $user_list = array($notify_user);
1535 //send notifications to followers, but ensure to not query for the assigned_user.
1536 return SugarFollowing::getFollowers($this, $notify_user);
1541 * Handles sending out email notifications when items are first assigned to users
1543 * @param string $notify_user user to notify
1544 * @param string $admin the admin user that sends out the notification
1546 function send_assignment_notifications($notify_user, $admin)
1548 global $current_user;
1550 if(($this->object_name == 'Meeting' || $this->object_name == 'Call') || $notify_user->receive_notifications)
1552 $sendToEmail = $notify_user->emailAddress->getPrimaryAddress($notify_user);
1554 if(empty($sendToEmail)) {
1555 $GLOBALS['log']->warn("Notifications: no e-mail address set for user {$notify_user->user_name}, cancelling send");
1559 $notify_mail = $this->create_notification_email($notify_user);
1560 $notify_mail->setMailerForSystem();
1562 if(empty($admin->settings['notify_send_from_assigning_user'])) {
1563 $notify_mail->From = $admin->settings['notify_fromaddress'];
1564 $notify_mail->FromName = (empty($admin->settings['notify_fromname'])) ? "" : $admin->settings['notify_fromname'];
1566 // Send notifications from the current user's e-mail (if set)
1567 $fromAddress = $current_user->emailAddress->getReplyToAddress($current_user);
1568 $fromAddress = !empty($fromAddress) ? $fromAddress : $admin->settings['notify_fromaddress'];
1569 $notify_mail->From = $fromAddress;
1570 //Use the users full name is available otherwise default to system name
1571 $from_name = !empty($admin->settings['notify_fromname']) ? $admin->settings['notify_fromname'] : "";
1572 $from_name = !empty($current_user->full_name) ? $current_user->full_name : $from_name;
1573 $notify_mail->FromName = $from_name;
1576 $oe = new OutboundEmail();
1577 $oe = $oe->getUserMailerSettings($current_user);
1578 //only send if smtp server is defined
1580 $smtpVerified = false;
1582 //first check the user settings
1583 if(!empty($oe->mail_smtpserver)){
1584 $smtpVerified = true;
1587 //if still not verified, check against the system settings
1588 if (!$smtpVerified){
1589 $oe = $oe->getSystemMailerSettings();
1590 if(!empty($oe->mail_smtpserver)){
1591 $smtpVerified = true;
1594 //if smtp was not verified against user or system, then do not send out email
1595 if (!$smtpVerified){
1596 $GLOBALS['log']->fatal("Notifications: error sending e-mail, smtp server was not found ");
1601 if(!$notify_mail->Send()) {
1602 $GLOBALS['log']->fatal("Notifications: error sending e-mail (method: {$notify_mail->Mailer}), (error: {$notify_mail->ErrorInfo})");
1604 $GLOBALS['log']->info("Notifications: e-mail successfully sent");
1612 * This function handles create the email notifications email.
1613 * @param string $notify_user the user to send the notification email to
1615 function create_notification_email($notify_user) {
1616 global $sugar_version;
1617 global $sugar_config;
1618 global $app_list_strings;
1619 global $current_user;
1622 $OBCharset = $locale->getPrecedentPreference('default_email_charset');
1625 require_once("include/SugarPHPMailer.php");
1627 $notify_address = $notify_user->emailAddress->getPrimaryAddress($notify_user);
1628 $notify_name = $notify_user->full_name;
1629 $GLOBALS['log']->debug("Notifications: user has e-mail defined");
1631 $notify_mail = new SugarPHPMailer();
1632 $notify_mail->AddAddress($notify_address,$locale->translateCharsetMIME(trim($notify_name), 'UTF-8', $OBCharset));
1634 if(empty($_SESSION['authenticated_user_language'])) {
1635 $current_language = $sugar_config['default_language'];
1637 $current_language = $_SESSION['authenticated_user_language'];
1639 $xtpl = new XTemplate(get_notify_template_file($current_language));
1640 if($this->module_dir == "Cases") {
1641 $template_name = "Case"; //we should use Case, you can refer to the en_us.notify_template.html.
1644 $template_name = $beanList[$this->module_dir]; //bug 20637, in workflow this->object_name = strange chars.
1647 $this->current_notify_user = $notify_user;
1649 if(in_array('set_notification_body', get_class_methods($this))) {
1650 $xtpl = $this->set_notification_body($xtpl, $this);
1652 $xtpl->assign("OBJECT", translate('LBL_MODULE_NAME'));
1653 $template_name = "Default";
1655 if(!empty($_SESSION["special_notification"]) && $_SESSION["special_notification"]) {
1656 $template_name = $beanList[$this->module_dir].'Special';
1658 if($this->special_notification) {
1659 $template_name = $beanList[$this->module_dir].'Special';
1661 $xtpl->assign("ASSIGNED_USER", $this->new_assigned_user_name);
1662 $xtpl->assign("ASSIGNER", $current_user->name);
1665 if(isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) {
1666 $port = $_SERVER['SERVER_PORT'];
1669 if (!isset($_SERVER['HTTP_HOST'])) {
1670 $_SERVER['HTTP_HOST'] = '';
1673 $httpHost = $_SERVER['HTTP_HOST'];
1675 if($colon = strpos($httpHost, ':')) {
1676 $httpHost = substr($httpHost, 0, $colon);
1679 $parsedSiteUrl = parse_url($sugar_config['site_url']);
1680 $host = $parsedSiteUrl['host'];
1681 if(!isset($parsedSiteUrl['port'])) {
1682 $parsedSiteUrl['port'] = 80;
1685 $port = ($parsedSiteUrl['port'] != 80) ? ":".$parsedSiteUrl['port'] : '';
1686 $path = !empty($parsedSiteUrl['path']) ? $parsedSiteUrl['path'] : "";
1687 $cleanUrl = "{$parsedSiteUrl['scheme']}://{$host}{$port}{$path}";
1689 $xtpl->assign("URL", $cleanUrl."/index.php?module={$this->module_dir}&action=DetailView&record={$this->id}");
1690 $xtpl->assign("SUGAR", "Sugar v{$sugar_version}");
1691 $xtpl->parse($template_name);
1692 $xtpl->parse($template_name . "_Subject");
1694 $notify_mail->Body = from_html(trim($xtpl->text($template_name)));
1695 $notify_mail->Subject = from_html($xtpl->text($template_name . "_Subject"));
1697 // cn: bug 8568 encode notify email in User's outbound email encoding
1698 $notify_mail->prepForOutbound();
1700 return $notify_mail;
1704 * This function is a good location to save changes that have been made to a relationship.
1705 * This should be overridden in subclasses that have something to save.
1707 * @param boolean $is_update true if this save is an update.
1708 * @param array $exclude a way to exclude relationships
1710 public function save_relationship_changes($is_update, $exclude = array())
1712 list($new_rel_id, $new_rel_link) = $this->set_relationship_info($exclude);
1714 $new_rel_id = $this->handle_preset_relationships($new_rel_id, $new_rel_link, $exclude);
1716 $this->handle_remaining_relate_fields($exclude);
1718 $this->update_parent_relationships($exclude);
1720 $this->handle_request_relate($new_rel_id, $new_rel_link);
1724 * Look in the bean for the new relationship_id and relationship_name if $this->not_use_rel_in_req is set to true,
1725 * otherwise check the $_REQUEST param for a relate_id and relate_to field. Once we have that make sure that it's
1726 * not excluded from the passed in array of relationships to exclude
1728 * @param array $exclude any relationship's to exclude
1729 * @return array The relationship_id and relationship_name in an array
1731 protected function set_relationship_info($exclude = array())
1734 $new_rel_id = false;
1735 $new_rel_link = false;
1736 // check incoming data
1737 if (isset($this->not_use_rel_in_req) && $this->not_use_rel_in_req == true) {
1738 // if we should use relation data from properties (for REQUEST-independent calls)
1739 $rel_id = isset($this->new_rel_id) ? $this->new_rel_id : '';
1740 $rel_link = isset($this->new_rel_relname) ? $this->new_rel_relname : '';
1744 // if we should use relation data from REQUEST
1745 $rel_id = isset($_REQUEST['relate_id']) ? $_REQUEST['relate_id'] : '';
1746 $rel_link = isset($_REQUEST['relate_to']) ? $_REQUEST['relate_to'] : '';
1749 // filter relation data
1750 if ($rel_id && $rel_link && !in_array($rel_link, $exclude) && $rel_id != $this->id) {
1751 $new_rel_id = $rel_id;
1752 $new_rel_link = $rel_link;
1753 // Bug #53223 : wrong relationship from subpanel create button
1754 // if LHSModule and RHSModule are same module use left link to add new item b/s of:
1755 // $rel_id and $rel_link are not emty - request is from subpanel
1756 // $rel_link contains relationship name - checked by call load_relationship
1757 $isRelationshipLoaded = $this->load_relationship($rel_link);
1758 if ($isRelationshipLoaded && !empty($this->$rel_link) && $this->$rel_link->getRelationshipObject() && $this->$rel_link->getRelationshipObject()->getLHSModule() == $this->$rel_link->getRelationshipObject()->getRHSModule() )
1760 $new_rel_link = $this->$rel_link->getRelationshipObject()->getLHSLink();
1764 //Try to find the link in this bean based on the relationship
1765 foreach ($this->field_defs as $key => $def)
1767 if (isset($def['type']) && $def['type'] == 'link' && isset($def['relationship']) && $def['relationship'] == $rel_link)
1769 $new_rel_link = $key;
1775 return array($new_rel_id, $new_rel_link);
1779 * Handle the preset fields listed in the fixed relationship_fields array hardcoded into the OOB beans
1781 * TODO: remove this mechanism and replace with mechanism exclusively based on the vardefs
1784 * @see save_relationship_changes
1785 * @param string|boolean $new_rel_id String of the ID to add
1786 * @param string Relationship Name
1787 * @param array $exclude any relationship's to exclude
1788 * @return string|boolean Return the new_rel_id if it was not used. False if it was used.
1790 protected function handle_preset_relationships($new_rel_id, $new_rel_link, $exclude = array())
1792 if (isset($this->relationship_fields) && is_array($this->relationship_fields)) {
1793 foreach ($this->relationship_fields as $id => $rel_name)
1796 if (in_array($id, $exclude)) continue;
1798 if(!empty($this->$id))
1800 // Bug #44930 We do not need to update main related field if it is changed from sub-panel.
1801 if ($rel_name == $new_rel_link && $this->$id != $new_rel_id)
1805 $GLOBALS['log']->debug('save_relationship_changes(): From relationship_field array - adding a relationship record: '.$rel_name . ' = ' . $this->$id);
1806 //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
1807 $this->load_relationship($rel_name);
1808 $rel_add = $this->$rel_name->add($this->$id);
1809 // move this around to only take out the id if it was save successfully
1810 if ($this->$id == $new_rel_id && $rel_add == true) {
1811 $new_rel_id = false;
1814 //if before value is not empty then attempt to delete relationship
1815 if (!empty($this->rel_fields_before_value[$id])) {
1816 $GLOBALS['log']->debug('save_relationship_changes(): From relationship_field array - attempting to remove the relationship record, using relationship attribute' . $rel_name);
1817 $this->load_relationship($rel_name);
1818 $this->$rel_name->delete($this->id, $this->rel_fields_before_value[$id]);
1828 * Next, we'll attempt to update all of the remaining relate fields in the vardefs that have 'save' set in their field_def
1829 * Only the 'save' fields should be saved as some vardef entries today are not for display only purposes and break the application if saved
1830 * If the vardef has entries for field <a> of type relate, where a->id_name = <b> and field <b> of type link
1831 * then we receive a value for b from the MVC in the _REQUEST, and it should be set in the bean as $this->$b
1834 * @see save_relationship_changes
1835 * @param array $exclude any relationship's to exclude
1836 * @return array the list of relationships that were added or removed successfully or if they were a failure
1838 protected function handle_remaining_relate_fields($exclude = array())
1841 $modified_relationships = array(
1842 'add' => array('success' => array(), 'failure' => array()),
1843 'remove' => array('success' => array(), 'failure' => array()),
1846 foreach ($this->field_defs as $def)
1848 if ($def ['type'] == 'relate' && isset ($def ['id_name']) && isset ($def ['link']) && isset ($def['save'])) {
1849 if (in_array($def['id_name'], $exclude) || in_array($def['id_name'], $this->relationship_fields))
1850 continue; // continue to honor the exclude array and exclude any relationships that will be handled by the relationship_fields mechanism
1852 $linkField = $def ['link'];
1853 if (isset($this->field_defs[$linkField])) {
1854 if ($this->load_relationship($linkField)) {
1855 $idName = $def['id_name'];
1857 if (!empty($this->rel_fields_before_value[$idName]) && empty($this->$idName)) {
1858 //if before value is not empty then attempt to delete relationship
1859 $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' ]]}");
1860 $success = $this->$def ['link']->delete($this->id, $this->rel_fields_before_value[$def ['id_name']]);
1861 // just need to make sure it's true and not an array as it's possible to return an array
1862 if($success == true) {
1863 $modified_relationships['remove']['success'][] = $def['link'];
1865 $modified_relationships['remove']['failure'][] = $def['link'];
1867 $GLOBALS['log']->debug("save_relationship_changes(): From field_defs - attempting to remove the relationship record returned " . var_export($success, true));
1870 if (!empty($this->$idName) && is_string($this->$idName)) {
1871 $GLOBALS['log']->debug("save_relationship_changes(): From field_defs - attempting to add a relationship record - {$def [ 'link' ]} = {$this->$def [ 'id_name' ]}");
1873 $success = $this->$linkField->add($this->$idName);
1875 // just need to make sure it's true and not an array as it's possible to return an array
1876 if($success == true) {
1877 $modified_relationships['add']['success'][] = $linkField;
1879 $modified_relationships['add']['failure'][] = $linkField;
1882 $GLOBALS['log']->debug("save_relationship_changes(): From field_defs - add a relationship record returned " . var_export($success, true));
1885 $GLOBALS['log']->fatal("Failed to load relationship {$linkField} while saving {$this->module_dir}");
1891 return $modified_relationships;
1896 * Updates relationships based on changes to fields of type 'parent' which
1897 * may or may not have links associated with them
1899 * @param array $exclude
1901 protected function update_parent_relationships($exclude = array())
1903 foreach ($this->field_defs as $def)
1905 if (!empty($def['type']) && $def['type'] == "parent")
1907 if (empty($def['type_name']) || empty($def['id_name']))
1909 $typeField = $def['type_name'];
1910 $idField = $def['id_name'];
1911 if (in_array($idField, $exclude))
1913 //Determine if the parent field has changed.
1915 //First check if the fetched row parent existed and now we no longer have one
1916 (!empty($this->fetched_row[$typeField]) && !empty($this->fetched_row[$idField])
1917 && (empty($this->$typeField) || empty($this->$idField))
1919 //Next check if we have one now that doesn't match the fetch row
1920 (!empty($this->$typeField) && !empty($this->$idField) &&
1921 (empty($this->fetched_row[$typeField]) || empty($this->fetched_row[$idField])
1922 || $this->fetched_row[$idField] != $this->$idField)
1924 // Check if we are deleting the bean, should remove the bean from any relationships
1927 $parentLinks = array();
1928 //Correlate links to parent field module types
1929 foreach ($this->field_defs as $ldef)
1931 if (!empty($ldef['type']) && $ldef['type'] == "link" && !empty($ldef['relationship']))
1933 $relDef = SugarRelationshipFactory::getInstance()->getRelationshipDef($ldef['relationship']);
1934 if (!empty($relDef['relationship_role_column']) && $relDef['relationship_role_column'] == $typeField)
1936 $parentLinks[$relDef['lhs_module']] = $ldef;
1941 // Save $this->$idField, because it can be resetted in case of link->delete() call
1942 $idFieldVal = $this->$idField;
1944 //If we used to have a parent, call remove on that relationship
1945 if (!empty($this->fetched_row[$typeField]) && !empty($this->fetched_row[$idField])
1946 && !empty($parentLinks[$this->fetched_row[$typeField]])
1947 && ($this->fetched_row[$idField] != $this->$idField))
1949 $oldParentLink = $parentLinks[$this->fetched_row[$typeField]]['name'];
1950 //Load the relationship
1951 if ($this->load_relationship($oldParentLink))
1953 $this->$oldParentLink->delete($this->fetched_row[$idField]);
1954 // Should resave the old parent
1955 SugarRelationship::addToResaveList(BeanFactory::getBean($this->fetched_row[$typeField], $this->fetched_row[$idField]));
1959 // If both parent type and parent id are set, save it unless the bean is being deleted
1960 if (!empty($this->$typeField)
1961 && !empty($idFieldVal)
1962 && !empty($parentLinks[$this->$typeField]['name'])
1963 && $this->deleted != 1
1965 //Now add the new parent
1966 $parentLink = $parentLinks[$this->$typeField]['name'];
1967 if ($this->load_relationship($parentLink))
1969 $this->$parentLink->add($idFieldVal);
1978 * Finally, we update a field listed in the _REQUEST['%/relate_id']/_REQUEST['relate_to'] mechanism (if it has not already been updated)
1981 * @see save_relationship_changes
1982 * @param string|boolean $new_rel_id
1983 * @param string $new_rel_link
1986 protected function handle_request_relate($new_rel_id, $new_rel_link)
1988 if (!empty($new_rel_id)) {
1990 if ($this->load_relationship($new_rel_link)) {
1991 return $this->$new_rel_link->add($new_rel_id);
1993 $lower_link = strtolower($new_rel_link);
1994 if ($this->load_relationship($lower_link)) {
1995 return $this->$lower_link->add($new_rel_id);
1998 require_once('data/Link2.php');
1999 $rel = Relationship::retrieve_by_modules($new_rel_link, $this->module_dir, $this->db, 'many-to-many');
2002 foreach ($this->field_defs as $field => $def) {
2003 if ($def['type'] == 'link' && !empty($def['relationship']) && $def['relationship'] == $rel) {
2004 $this->load_relationship($field);
2005 return $this->$field->add($new_rel_id);
2009 //ok so we didn't find it in the field defs let's save it anyway if we have the relationshp
2011 $this->$rel = new Link2($rel, $this, array());
2012 return $this->$rel->add($new_rel_id);
2018 // nothing was saved so just return false;
2023 * This function retrieves a record of the appropriate type from the DB.
2024 * It fills in all of the fields from the DB into the object it was called on.
2026 * @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.
2027 * @return this - The object that it was called apon or null if exactly 1 record was not found.
2031 function check_date_relationships_load()
2033 global $disable_date_format;
2035 if (empty($timedate))
2036 $timedate=TimeDate::getInstance();
2038 if(empty($this->field_defs))
2042 foreach($this->field_defs as $fieldDef)
2044 $field = $fieldDef['name'];
2045 if(!isset($this->processed_dates_times[$field]))
2047 $this->processed_dates_times[$field] = '1';
2048 if(empty($this->$field)) continue;
2049 if($field == 'date_modified' || $field == 'date_entered')
2051 $this->$field = $this->db->fromConvert($this->$field, 'datetime');
2052 if(empty($disable_date_format)) {
2053 $this->$field = $timedate->to_display_date_time($this->$field);
2056 elseif(isset($this->field_name_map[$field]['type']))
2058 $type = $this->field_name_map[$field]['type'];
2060 if($type == 'relate' && isset($this->field_name_map[$field]['custom_module']))
2062 $type = $this->field_name_map[$field]['type'];
2067 if($this->$field == '0000-00-00')
2070 } elseif(!empty($this->field_name_map[$field]['rel_field']))
2072 $rel_field = $this->field_name_map[$field]['rel_field'];
2074 if(!empty($this->$rel_field))
2076 if(empty($disable_date_format)) {
2077 $mergetime = $timedate->merge_date_time($this->$field,$this->$rel_field);
2078 $this->$field = $timedate->to_display_date($mergetime);
2079 $this->$rel_field = $timedate->to_display_time($mergetime);
2085 if(empty($disable_date_format)) {
2086 $this->$field = $timedate->to_display_date($this->$field, false);
2089 } elseif($type == 'datetime' || $type == 'datetimecombo')
2091 if($this->$field == '0000-00-00 00:00:00')
2097 if(empty($disable_date_format)) {
2098 $this->$field = $timedate->to_display_date_time($this->$field, true, true);
2101 } elseif($type == 'time')
2103 if($this->$field == '00:00:00')
2108 //$this->$field = from_db_convert($this->$field, 'time');
2109 if(empty($this->field_name_map[$field]['rel_field']) && empty($disable_date_format))
2111 $this->$field = $timedate->to_display_time($this->$field,true, false);
2114 } elseif($type == 'encrypt' && empty($disable_date_format)){
2115 $this->$field = $this->decrypt_after_retrieve($this->$field);
2123 * This function processes the fields before save.
2124 * Interal function, do not override.
2126 function preprocess_fields_on_save()
2128 $GLOBALS['log']->deprecated('SugarBean.php: preprocess_fields_on_save() is deprecated');
2132 * Removes formatting from values posted from the user interface.
2133 * It only unformats numbers. Function relies on user/system prefernce for format strings.
2135 * Internal Function, do not override.
2137 function unformat_all_fields()
2139 $GLOBALS['log']->deprecated('SugarBean.php: unformat_all_fields() is deprecated');
2143 * This functions adds formatting to all number fields before presenting them to user interface.
2145 * Internal function, do not override.
2147 function format_all_fields()
2149 $GLOBALS['log']->deprecated('SugarBean.php: format_all_fields() is deprecated');
2152 function format_field($fieldDef)
2154 $GLOBALS['log']->deprecated('SugarBean.php: format_field() is deprecated');
2158 * Function corrects any bad formatting done by 3rd party/custom code
2160 * 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
2162 function fixUpFormatting()
2165 static $boolean_false_values = array('off', 'false', '0', 'no');
2168 foreach($this->field_defs as $field=>$def)
2170 if ( !isset($this->$field) ) {
2173 if ( (isset($def['source'])&&$def['source']=='non-db') || $field == 'deleted' ) {
2176 if ( isset($this->fetched_row[$field]) && $this->$field == $this->fetched_row[$field] ) {
2177 // Don't hand out warnings because the field was untouched between retrieval and saving, most database drivers hand pretty much everything back as strings.
2180 $reformatted = false;
2181 switch($def['type']) {
2183 case 'datetimecombo':
2184 if(empty($this->$field)) break;
2185 if ($this->$field == 'NULL') {
2189 if ( ! preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$/',$this->$field) ) {
2190 // This appears to be formatted in user date/time
2191 $this->$field = $timedate->to_db($this->$field);
2192 $reformatted = true;
2196 if(empty($this->$field)) break;
2197 if ($this->$field == 'NULL') {
2201 if ( ! preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/',$this->$field) ) {
2202 // This date appears to be formatted in the user's format
2203 $this->$field = $timedate->to_db_date($this->$field, false);
2204 $reformatted = true;
2208 if(empty($this->$field)) break;
2209 if ($this->$field == 'NULL') {
2213 if ( preg_match('/(am|pm)/i',$this->$field) ) {
2214 // This time appears to be formatted in the user's format
2215 $this->$field = $timedate->fromUserTime($this->$field)->format(TimeDate::DB_TIME_FORMAT);
2216 $reformatted = true;
2223 if ( $this->$field === '' || $this->$field == NULL || $this->$field == 'NULL') {
2226 if ( is_string($this->$field) ) {
2227 $this->$field = (float)unformat_number($this->$field);
2228 $reformatted = true;
2237 if ( $this->$field === '' || $this->$field == NULL || $this->$field == 'NULL') {
2240 if ( is_string($this->$field) ) {
2241 $this->$field = (int)unformat_number($this->$field);
2242 $reformatted = true;
2246 if (empty($this->$field)) {
2247 $this->$field = false;
2248 } else if(true === $this->$field || 1 == $this->$field) {
2249 $this->$field = true;
2250 } else if(in_array(strval($this->$field), $boolean_false_values)) {
2251 $this->$field = false;
2252 $reformatted = true;
2254 $this->$field = true;
2255 $reformatted = true;
2259 $this->$field = $this->encrpyt_before_save($this->$field);
2262 if ( $reformatted ) {
2263 $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');
2270 * Function fetches a single row of data given the primary key value.
2272 * The fetched data is then set into the bean. The function also processes the fetched data by formattig
2273 * date/time and numeric values.
2275 * @param string $id Optional, default -1, is set to -1 id value from the bean is used, else, passed value is used
2276 * @param boolean $encode Optional, default true, encodes the values fetched from the database.
2277 * @param boolean $deleted Optional, default true, if set to false deleted filter will not be added.
2279 * Internal function, do not override.
2281 function retrieve($id = -1, $encode=true,$deleted=true)
2284 $custom_logic_arguments['id'] = $id;
2285 $this->call_custom_logic('before_retrieve', $custom_logic_arguments);
2291 $custom_join = $this->getCustomJoin();
2293 $query = "SELECT $this->table_name.*". $custom_join['select']. " FROM $this->table_name ";
2295 $query .= $custom_join['join'];
2296 $query .= " WHERE $this->table_name.id = ".$this->db->quoted($id);
2297 if ($deleted) $query .= " AND $this->table_name.deleted=0";
2298 $GLOBALS['log']->debug("Retrieve $this->object_name : ".$query);
2299 $result = $this->db->limitQuery($query,0,1,true, "Retrieving record by id $this->table_name:$id found ");
2305 $row = $this->db->fetchByAssoc($result, $encode);
2311 //make copy of the fetched row for construction of audit record and for business logic/workflow
2312 $row = $this->convertRow($row);
2313 $this->fetched_row=$row;
2314 $this->populateFromRow($row);
2316 // fix defect #52438. implement the same logic as sugar_currency_format
2317 // Smarty modifier does.
2318 $this->populateCurrencyFields();
2320 global $module, $action;
2321 //Just to get optimistic locking working for this release
2322 if($this->optimistic_lock && $module == $this->module_dir && $action =='EditView' )
2324 $_SESSION['o_lock_id']= $id;
2325 $_SESSION['o_lock_dm']= $this->date_modified;
2326 $_SESSION['o_lock_on'] = $this->object_name;
2328 $this->processed_dates_times = array();
2329 $this->check_date_relationships_load();
2331 if(isset($this->custom_fields))
2333 $this->custom_fields->fill_relationships();
2336 $this->is_updated_dependent_fields = false;
2337 $this->fill_in_additional_detail_fields();
2338 $this->fill_in_relationship_fields();
2339 // save related fields values for audit
2340 foreach ($this->get_related_fields() as $rel_field_name)
2342 if (! empty($this->$rel_field_name['name']))
2344 $this->fetched_rel_row[$rel_field_name['name']] = $this->$rel_field_name['name'];
2347 //make a copy of fields in the relationship_fields array. These field values will be used to
2348 //clear relationship.
2349 foreach ( $this->field_defs as $key => $def )
2351 if ($def [ 'type' ] == 'relate' && isset ( $def [ 'id_name'] ) && isset ( $def [ 'link'] ) && isset ( $def[ 'save' ])) {
2352 if (isset($this->$key)) {
2353 $this->rel_fields_before_value[$key]=$this->$key;
2354 if (isset($this->$def [ 'id_name']))
2355 $this->rel_fields_before_value[$def [ 'id_name']]=$this->$def [ 'id_name'];
2358 $this->rel_fields_before_value[$key]=null;
2361 if (isset($this->relationship_fields) && is_array($this->relationship_fields))
2363 foreach ($this->relationship_fields as $rel_id=>$rel_name)
2365 if (isset($this->$rel_id))
2366 $this->rel_fields_before_value[$rel_id]=$this->$rel_id;
2368 $this->rel_fields_before_value[$rel_id]=null;
2372 // call the custom business logic
2373 $custom_logic_arguments['id'] = $id;
2374 $custom_logic_arguments['encode'] = $encode;
2375 $this->call_custom_logic("after_retrieve", $custom_logic_arguments);
2376 unset($custom_logic_arguments);
2381 * Sets value from fetched row into the bean.
2383 * @param array $row Fetched row
2384 * @todo loop through vardefs instead
2385 * @internal runs into an issue when populating from field_defs for users - corrupts user prefs
2387 * Internal function, do not override.
2389 function populateFromRow($row)
2392 foreach($this->field_defs as $field=>$field_value)
2394 if($field == 'user_preferences' && $this->module_dir == 'Users')
2396 if(isset($row[$field]))
2398 $this->$field = $row[$field];
2399 $owner = $field . '_owner';
2400 if(!empty($row[$owner])){
2401 $this->$owner = $row[$owner];
2406 $this->$field = $nullvalue;
2414 * Add any required joins to the list count query. The joins are required if there
2415 * is a field in the $where clause that needs to be joined.
2417 * @param string $query
2418 * @param string $where
2420 * Internal Function, do Not override.
2422 function add_list_count_joins(&$query, $where)
2424 $custom_join = $this->getCustomJoin();
2425 $query .= $custom_join['join'];
2430 * Changes the select expression of the given query to be 'count(*)' so you
2431 * can get the number of items the query will return. This is used to
2432 * populate the upper limit on ListViews.
2434 * @param string $query Select query string
2435 * @return string count query
2437 * Internal function, do not override.
2439 function create_list_count_query($query)
2441 // remove the 'order by' clause which is expected to be at the end of the query
2442 $pattern = '/\sORDER BY.*/is'; // ignores the case
2444 $query = preg_replace($pattern, $replacement, $query);
2445 //handle distinct clause
2447 if(substr_count(strtolower($query), 'distinct')){
2448 if (!empty($this->seed) && !empty($this->seed->table_name ))
2449 $star = 'DISTINCT ' . $this->seed->table_name . '.id';
2451 $star = 'DISTINCT ' . $this->table_name . '.id';
2455 // change the select expression to 'count(*)'
2456 $pattern = '/SELECT(.*?)(\s){1}FROM(\s){1}/is'; // ignores the case
2457 $replacement = 'SELECT count(' . $star . ') c FROM ';
2459 //if the passed query has union clause then replace all instances of the pattern.
2460 //this is very rare. I have seen this happening only from projects module.
2461 //in addition to this added a condition that has union clause and uses
2463 if (strstr($query," UNION ALL ") !== false) {
2465 //separate out all the queries.
2466 $union_qs=explode(" UNION ALL ", $query);
2467 foreach ($union_qs as $key=>$union_query) {
2469 preg_match($pattern, $union_query, $matches);
2470 if (!empty($matches)) {
2471 if (stristr($matches[0], "distinct")) {
2472 if (!empty($this->seed) && !empty($this->seed->table_name ))
2473 $star = 'DISTINCT ' . $this->seed->table_name . '.id';
2475 $star = 'DISTINCT ' . $this->table_name . '.id';
2478 $replacement = 'SELECT count(' . $star . ') c FROM ';
2479 $union_qs[$key] = preg_replace($pattern, $replacement, $union_query,1);
2481 $modified_select_query=implode(" UNION ALL ",$union_qs);
2483 $modified_select_query = preg_replace($pattern, $replacement, $query,1);
2487 return $modified_select_query;
2491 * This function returns a paged list of the current object type. It is intended to allow for
2492 * hopping back and forth through pages of data. It only retrieves what is on the current page.
2494 * @internal This method must be called on a new instance. It trashes the values of all the fields in the current one.
2495 * @param string $order_by
2496 * @param string $where Additional where clause
2497 * @param int $row_offset Optaional,default 0, starting row number
2498 * @param init $limit Optional, default -1
2499 * @param int $max Optional, default -1
2500 * @param boolean $show_deleted Optional, default 0, if set to 1 system will show deleted records.
2501 * @return array Fetched data.
2503 * Internal function, do not override.
2506 function get_list($order_by = "", $where = "", $row_offset = 0, $limit=-1, $max=-1, $show_deleted = 0, $singleSelect=false, $select_fields = array())
2508 $GLOBALS['log']->debug("get_list: order_by = '$order_by' and where = '$where' and limit = '$limit'");
2509 if(isset($_SESSION['show_deleted']))
2514 if($this->bean_implements('ACL') && ACLController::requireOwner($this->module_dir, 'list') )
2516 global $current_user;
2517 $owner_where = $this->getOwnerWhere($current_user->id);
2519 //rrs - because $this->getOwnerWhere() can return '' we need to be sure to check for it and
2520 //handle it properly else you could get into a situation where you are create a where stmt like
2522 if(!empty($owner_where)){
2524 $where = $owner_where;
2526 $where .= ' AND '. $owner_where;
2530 $query = $this->create_new_list_query($order_by, $where,$select_fields,array(), $show_deleted,'',false,null,$singleSelect);
2531 return $this->process_list_query($query, $row_offset, $limit, $max, $where);
2535 * Prefixes column names with this bean's table name.
2537 * @param string $order_by Order by clause to be processed
2538 * @param SugarBean $submodule name of the module this order by clause is for
2539 * @param boolean $suppress_table_name Whether table name should be suppressed
2540 * @return string Processed order by clause
2542 * Internal function, do not override.
2544 public function process_order_by($order_by, $submodule = null, $suppress_table_name = false)
2546 if (empty($order_by))
2548 //submodule is empty,this is for list object in focus
2549 if (empty($submodule))
2551 $bean_queried = $this;
2555 //submodule is set, so this is for subpanel, use submodule
2556 $bean_queried = $submodule;
2559 $raw_elements = explode(',', $order_by);
2560 $valid_elements = array();
2561 foreach ($raw_elements as $key => $value) {
2565 //value might have ascending and descending decorations
2566 $list_column = preg_split('/\s/', trim($value), 2);
2567 $list_column = array_map('trim', $list_column);
2569 $list_column_name = $list_column[0];
2570 if (isset($bean_queried->field_defs[$list_column_name])) {
2571 $field_defs = $bean_queried->field_defs[$list_column_name];
2572 $source = isset($field_defs['source']) ? $field_defs['source'] : 'db';
2574 if (empty($field_defs['table']) && !$suppress_table_name) {
2575 if ($source == 'db') {
2576 $list_column[0] = $bean_queried->table_name . '.' . $list_column[0] ;
2577 } elseif ($source == 'custom_fields') {
2578 $list_column[0] = $bean_queried->table_name . '_cstm.' . $list_column[0] ;
2582 // Bug 38803 - Use CONVERT() function when doing an order by on ntext, text, and image fields
2583 if ($source != 'non-db'
2584 && $this->db->isTextType($this->db->getFieldType($bean_queried->field_defs[$list_column_name]))) {
2585 // array(10000) is for db2 only. It tells db2manager to cast 'clob' to varchar(10000) for this 'sort by' column
2586 $list_column[0] = $this->db->convert($list_column[0], "text2char", array(10000));
2591 if (isset($list_column[1])) {
2592 switch (strtolower($list_column[1])) {
2597 $GLOBALS['log']->debug("process_order_by: ($list_column[1]) is not a valid order.");
2598 unset($list_column[1]);
2603 $GLOBALS['log']->debug("process_order_by: ($list_column[0]) does not have a vardef entry.");
2607 $valid_elements[$key] = implode(' ', $list_column);
2611 return implode(', ', $valid_elements);
2617 * Returns a detail object like retrieving of the current object type.
2619 * It is intended for use in navigation buttons on the DetailView. It will pass an offset and limit argument to the sql query.
2620 * @internal This method must be called on a new instance. It overrides the values of all the fields in the current one.
2622 * @param string $order_by
2623 * @param string $where Additional where clause
2624 * @param int $row_offset Optaional,default 0, starting row number
2625 * @param init $limit Optional, default -1
2626 * @param int $max Optional, default -1
2627 * @param boolean $show_deleted Optioanl, default 0, if set to 1 system will show deleted records.
2628 * @return array Fetched data.
2630 * Internal function, do not override.
2632 function get_detail($order_by = "", $where = "", $offset = 0, $row_offset = 0, $limit=-1, $max=-1, $show_deleted = 0)
2634 $GLOBALS['log']->debug("get_detail: order_by = '$order_by' and where = '$where' and limit = '$limit' and offset = '$offset'");
2635 if(isset($_SESSION['show_deleted']))
2640 if($this->bean_implements('ACL') && ACLController::requireOwner($this->module_dir, 'list') )
2642 global $current_user;
2643 $owner_where = $this->getOwnerWhere($current_user->id);
2647 $where = $owner_where;
2651 $where .= ' AND '. $owner_where;
2654 $query = $this->create_new_list_query($order_by, $where,array(),array(), $show_deleted, $offset);
2656 //Add Limit and Offset to query
2657 //$query .= " LIMIT 1 OFFSET $offset";
2659 return $this->process_detail_query($query, $row_offset, $limit, $max, $where, $offset);
2663 * Fetches data from all related tables.
2665 * @param object $child_seed
2666 * @param string $related_field_name relation to fetch data for
2667 * @param string $order_by Optional, default empty
2668 * @param string $where Optional, additional where clause
2669 * @return array Fetched data.
2671 * Internal function, do not override.
2673 function get_related_list($child_seed,$related_field_name, $order_by = "", $where = "",
2674 $row_offset = 0, $limit=-1, $max=-1, $show_deleted = 0)
2676 global $layout_edit_mode;
2677 $query_array = array();
2679 if(isset($layout_edit_mode) && $layout_edit_mode)
2681 $response = array();
2682 $child_seed->assign_display_fields($child_seed->module_dir);
2683 $response['list'] = array($child_seed);
2684 $response['row_count'] = 1;
2685 $response['next_offset'] = 0;
2686 $response['previous_offset'] = 0;
2690 $GLOBALS['log']->debug("get_related_list: order_by = '$order_by' and where = '$where' and limit = '$limit'");
2691 if(isset($_SESSION['show_deleted']))
2696 $this->load_relationship($related_field_name);
2698 if ($this->$related_field_name instanceof Link) {
2700 $query_array = $this->$related_field_name->getQuery(true);
2703 $query_array = $this->$related_field_name->getQuery(array(
2704 "return_as_array" => true,
2705 'where' => '1=1' // hook for 'where' clause in M2MRelationship file
2709 $entire_where = $query_array['where'];
2712 if(empty($entire_where))
2714 $entire_where = ' WHERE ' . $where;
2718 $entire_where .= ' AND ' . $where;
2722 $query = 'SELECT '.$child_seed->table_name.'.* ' . $query_array['from'] . ' ' . $entire_where;
2723 if(!empty($order_by))
2725 $query .= " ORDER BY " . $order_by;
2728 return $child_seed->process_list_query($query, $row_offset, $limit, $max, $where);
2732 protected static function build_sub_queries_for_union($subpanel_list, $subpanel_def, $parentbean, $order_by)
2734 global $layout_edit_mode, $beanFiles, $beanList;
2735 $subqueries = array();
2736 foreach($subpanel_list as $this_subpanel)
2738 if(!$this_subpanel->isDatasourceFunction() || ($this_subpanel->isDatasourceFunction()
2739 && isset($this_subpanel->_instance_properties['generate_select'])
2740 && $this_subpanel->_instance_properties['generate_select']==true))
2742 //the custom query function must return an array with
2743 if ($this_subpanel->isDatasourceFunction()) {
2744 $shortcut_function_name = $this_subpanel->get_data_source_name();
2745 $parameters=$this_subpanel->get_function_parameters();
2746 if (!empty($parameters))
2748 //if the import file function is set, then import the file to call the custom function from
2749 if (is_array($parameters) && isset($parameters['import_function_file'])){
2750 //this call may happen multiple times, so only require if function does not exist
2751 if(!function_exists($shortcut_function_name)){
2752 require_once($parameters['import_function_file']);
2754 //call function from required file
2755 $query_array = $shortcut_function_name($parameters);
2757 //call function from parent bean
2758 $query_array = $parentbean->$shortcut_function_name($parameters);
2763 $query_array = $parentbean->$shortcut_function_name();
2766 $related_field_name = $this_subpanel->get_data_source_name();
2767 if (!$parentbean->load_relationship($related_field_name)){
2768 unset ($parentbean->$related_field_name);
2771 $query_array = $parentbean->$related_field_name->getSubpanelQuery(array(), true);
2773 $table_where = preg_replace('/^\s*WHERE/i', '', $this_subpanel->get_where());
2774 $where_definition = preg_replace('/^\s*WHERE/i', '', $query_array['where']);
2776 if(!empty($table_where))
2778 if(empty($where_definition))
2780 $where_definition = $table_where;
2784 $where_definition .= ' AND ' . $table_where;
2788 $submodulename = $this_subpanel->_instance_properties['module'];
2789 $submoduleclass = $beanList[$submodulename];
2790 //require_once($beanFiles[$submoduleclass]);
2792 /** @var SugarBean $submodule */
2793 $submodule = new $submoduleclass();
2794 $subwhere = $where_definition;
2798 $list_fields = $this_subpanel->get_list_fields();
2799 foreach($list_fields as $list_key=>$list_field)
2801 if(isset($list_field['usage']) && $list_field['usage'] == 'display_only')
2803 unset($list_fields[$list_key]);
2808 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'))
2810 $order_by = $submodule->table_name .'.'. $order_by;
2812 $table_name = $this_subpanel->table_name;
2813 $panel_name=$this_subpanel->name;
2815 $params['distinct'] = $this_subpanel->distinct_query();
2817 $params['joined_tables'] = $query_array['join_tables'];
2818 $params['include_custom_fields'] = !$subpanel_def->isCollection();
2819 $params['collection_list'] = $subpanel_def->get_inst_prop_value('collection_list');
2821 // use single select in case when sorting by relate field
2822 $singleSelect = $submodule->is_relate_field($order_by);
2824 $subquery = $submodule->create_new_list_query('',$subwhere ,$list_fields,$params, 0,'', true,$parentbean, $singleSelect);
2826 $subquery['select'] = $subquery['select']." , '$panel_name' panel_name ";
2827 $subquery['from'] = $subquery['from'].$query_array['join'];
2828 $subquery['query_array'] = $query_array;
2829 $subquery['params'] = $params;
2831 $subqueries[] = $subquery;
2838 * Constructs a query to fetch data for supanels and list views
2840 * It constructs union queries for activities subpanel.
2842 * @param SugarBean $parentbean constructing queries for link attributes in this bean
2843 * @param string $order_by Optional, order by clause
2844 * @param string $sort_order Optional, sort order
2845 * @param string $where Optional, additional where clause
2846 * @param int $row_offset
2849 * @param int $show_deleted
2850 * @param aSubPanel $subpanel_def
2854 * Internal Function, do not overide.
2856 function get_union_related_list($parentbean, $order_by = "", $sort_order='', $where = "",
2857 $row_offset = 0, $limit=-1, $max=-1, $show_deleted = 0, $subpanel_def)
2859 $secondary_queries = array();
2860 global $layout_edit_mode, $beanFiles, $beanList;
2862 if(isset($_SESSION['show_deleted']))
2867 $final_query_rows = '';
2868 $subpanel_list=array();
2869 if ($subpanel_def->isCollection())
2871 $subpanel_def->load_sub_subpanels();
2872 $subpanel_list=$subpanel_def->sub_subpanels;
2876 $subpanel_list[]=$subpanel_def;
2881 //Breaking the building process into two loops. The first loop gets a list of all the sub-queries.
2882 //The second loop merges the queries and forces them to select the same number of columns
2883 //All columns in a sub-subpanel group must have the same aliases
2884 //If the subpanel is a datasource function, it can't be a collection so we just poll that function for the and return that
2885 foreach($subpanel_list as $this_subpanel)
2887 if($this_subpanel->isDatasourceFunction() && empty($this_subpanel->_instance_properties['generate_select']))
2889 $shortcut_function_name = $this_subpanel->get_data_source_name();
2890 $parameters=$this_subpanel->get_function_parameters();
2891 if (!empty($parameters))
2893 //if the import file function is set, then import the file to call the custom function from
2894 if (is_array($parameters) && isset($parameters['import_function_file'])){
2895 //this call may happen multiple times, so only require if function does not exist
2896 if(!function_exists($shortcut_function_name)){
2897 require_once($parameters['import_function_file']);
2899 //call function from required file
2900 $tmp_final_query = $shortcut_function_name($parameters);
2902 //call function from parent bean
2903 $tmp_final_query = $parentbean->$shortcut_function_name($parameters);
2906 $tmp_final_query = $parentbean->$shortcut_function_name();
2910 $final_query_rows .= ' UNION ALL ( '.$parentbean->create_list_count_query($tmp_final_query, $parameters) . ' )';
2911 $final_query .= ' UNION ALL ( '.$tmp_final_query . ' )';
2913 $final_query_rows = '(' . $parentbean->create_list_count_query($tmp_final_query, $parameters) . ')';
2914 $final_query = '(' . $tmp_final_query . ')';
2919 //If final_query is still empty, its time to build the sub-queries
2920 if (empty($final_query))
2922 $subqueries = SugarBean::build_sub_queries_for_union($subpanel_list, $subpanel_def, $parentbean, $order_by);
2923 $all_fields = array();
2924 foreach($subqueries as $i => $subquery)
2926 $query_fields = $GLOBALS['db']->getSelectFieldsFromQuery($subquery['select']);
2927 foreach($query_fields as $field => $select)
2929 if (!in_array($field, $all_fields))
2930 $all_fields[] = $field;
2932 $subqueries[$i]['query_fields'] = $query_fields;
2935 //Now ensure the queries have the same set of fields in the same order.
2936 foreach($subqueries as $subquery)
2938 $subquery['select'] = "SELECT";
2939 foreach($all_fields as $field)
2941 if (!isset($subquery['query_fields'][$field]))
2943 $subquery['select'] .= " NULL $field,";
2947 $subquery['select'] .= " {$subquery['query_fields'][$field]},";
2950 $subquery['select'] = substr($subquery['select'], 0 , strlen($subquery['select']) - 1);
2951 //Put the query into the final_query
2952 $query = $subquery['select'] . " " . $subquery['from'] . " " . $subquery['where'];
2955 $query = ' UNION ALL ( '.$query . ' )';
2956 $final_query_rows .= " UNION ALL ";
2958 $query = '(' . $query . ')';
2961 $query_array = $subquery['query_array'];
2962 $select_position=strpos($query_array['select'],"SELECT");
2963 $distinct_position=strpos($query_array['select'],"DISTINCT");
2964 if (!empty($subquery['params']['distinct']) && !empty($subpanel_def->table_name))
2966 $query_rows = "( SELECT count(DISTINCT ". $subpanel_def->table_name . ".id)". $subquery['from_min'].$query_array['join']. $subquery['where'].' )';
2968 elseif ($select_position !== false && $distinct_position!= false)
2970 $query_rows = "( ".substr_replace($query_array['select'],"SELECT count(",$select_position,6). ")" . $subquery['from_min'].$query_array['join']. $subquery['where'].' )';
2974 //resort to default behavior.
2975 $query_rows = "( SELECT count(*)". $subquery['from_min'].$query_array['join']. $subquery['where'].' )';
2977 if(!empty($subquery['secondary_select']))
2980 $subquerystring= $subquery['secondary_select'] . $subquery['secondary_from'].$query_array['join']. $subquery['where'];
2981 if (!empty($subquery['secondary_where']))
2983 if (empty($subquery['where']))
2985 $subquerystring.=" WHERE " .$subquery['secondary_where'];
2989 $subquerystring.=" AND " .$subquery['secondary_where'];
2992 $secondary_queries[]=$subquerystring;
2994 $final_query .= $query;
2995 $final_query_rows .= $query_rows;
2999 if(!empty($order_by))
3001 $isCollection = $subpanel_def->isCollection();
3002 if ($isCollection) {
3003 /** @var aSubPanel $header */
3004 $header = $subpanel_def->get_header_panel_def();
3005 $submodule = $header->template_instance;
3006 $suppress_table_name = true;
3008 $submodule = $subpanel_def->template_instance;
3009 $suppress_table_name = false;
3012 if (!empty($sort_order)) {
3013 $order_by .= ' ' . $sort_order;
3016 $order_by = $parentbean->process_order_by($order_by, $submodule, $suppress_table_name);
3017 if (!empty($order_by)) {
3018 $final_query .= ' ORDER BY ' . $order_by;
3023 if(isset($layout_edit_mode) && $layout_edit_mode)
3025 $response = array();
3026 if(!empty($submodule))
3028 $submodule->assign_display_fields($submodule->module_dir);
3029 $response['list'] = array($submodule);
3033 $response['list'] = array();
3035 $response['parent_data'] = array();
3036 $response['row_count'] = 1;
3037 $response['next_offset'] = 0;
3038 $response['previous_offset'] = 0;
3043 return $parentbean->process_union_list_query($parentbean, $final_query, $row_offset, $limit, $max, '',$subpanel_def, $final_query_rows, $secondary_queries);
3048 * Returns a full (ie non-paged) list of the current object type.
3050 * @param string $order_by the order by SQL parameter. defaults to ""
3051 * @param string $where where clause. defaults to ""
3052 * @param boolean $check_dates. defaults to false
3053 * @param int $show_deleted show deleted records. defaults to 0
3055 function get_full_list($order_by = "", $where = "", $check_dates=false, $show_deleted = 0)
3057 $GLOBALS['log']->debug("get_full_list: order_by = '$order_by' and where = '$where'");
3058 if(isset($_SESSION['show_deleted']))
3062 $query = $this->create_new_list_query($order_by, $where,array(),array(), $show_deleted);
3063 return $this->process_full_list_query($query, $check_dates);
3067 * Return the list query used by the list views and export button. Next generation of create_new_list_query function.
3069 * Override this function to return a custom query.
3071 * @param string $order_by custom order by clause
3072 * @param string $where custom where clause
3073 * @param array $filter Optioanal
3074 * @param array $params Optional *
3075 * @param int $show_deleted Optional, default 0, show deleted records is set to 1.
3076 * @param string $join_type
3077 * @param boolean $return_array Optional, default false, response as array
3078 * @param object $parentbean creating a subquery for this bean.
3079 * @param boolean $singleSelect Optional, default false.
3080 * @return String select query string, optionally an array value will be returned if $return_array= true.
3082 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)
3084 global $beanFiles, $beanList;
3085 $selectedFields = array();
3086 $secondarySelectedFields = array();
3087 $ret_array = array();
3089 if($this->bean_implements('ACL') && ACLController::requireOwner($this->module_dir, 'list') )
3091 global $current_user;
3092 $owner_where = $this->getOwnerWhere($current_user->id);
3095 $where = $owner_where;
3099 $where .= ' AND '. $owner_where;
3102 if(!empty($params['distinct']))
3104 $distinct = ' DISTINCT ';
3108 $ret_array['select'] = " SELECT $distinct $this->table_name.* ";
3112 $ret_array['select'] = " SELECT $distinct $this->table_name.id ";
3114 $ret_array['from'] = " FROM $this->table_name ";
3115 $ret_array['from_min'] = $ret_array['from'];
3116 $ret_array['secondary_from'] = $ret_array['from'] ;
3117 $ret_array['where'] = '';
3118 $ret_array['order_by'] = '';
3119 //secondary selects are selects that need to be run after the primary query to retrieve additional info on main
3122 $ret_array['secondary_select']=& $ret_array['select'];
3123 $ret_array['secondary_from'] = & $ret_array['from'];
3127 $ret_array['secondary_select'] = '';
3129 $custom_join = $this->getCustomJoin( empty($filter)? true: $filter );
3130 if((!isset($params['include_custom_fields']) || $params['include_custom_fields']))
3132 $ret_array['select'] .= $custom_join['select'];
3134 $ret_array['from'] .= $custom_join['join'];
3135 // Bug 52490 - Captivea (Sve) - To be able to add custom fields inside where clause in a subpanel
3136 $ret_array['from_min'] .= $custom_join['join'];
3138 //LOOP AROUND FOR FIXIN VARDEF ISSUES
3139 require('include/VarDefHandler/listvardefoverride.php');
3140 if (file_exists('custom/include/VarDefHandler/listvardefoverride.php'))
3142 require('custom/include/VarDefHandler/listvardefoverride.php');
3145 $joined_tables = array();
3146 if(!empty($params['joined_tables']))
3148 foreach($params['joined_tables'] as $table)
3150 $joined_tables[$table] = 1;
3156 $filterKeys = array_keys($filter);
3157 if(is_numeric($filterKeys[0]))
3160 foreach($filter as $field)
3162 $field = strtolower($field);
3163 //remove out id field so we don't duplicate it
3164 if ( $field == 'id' && !empty($filter) ) {
3167 if(isset($this->field_defs[$field]))
3169 $fields[$field]= $this->field_defs[$field];
3173 $fields[$field] = array('force_exists'=>true);
3182 $fields = $this->field_defs;
3185 $used_join_key = array();
3187 foreach($fields as $field=>$value)
3189 //alias is used to alias field names
3191 if (isset($value['alias']))
3193 $alias =' as ' . $value['alias'] . ' ';
3196 if(empty($this->field_defs[$field]) || !empty($value['force_blank']) )
3198 if(!empty($filter) && isset($filter[$field]['force_exists']) && $filter[$field]['force_exists'])
3200 if ( isset($filter[$field]['force_default']) )
3201 $ret_array['select'] .= ", {$filter[$field]['force_default']} $field ";
3203 //spaces are a fix for length issue problem with unions. The union only returns the maximum number of characters from the first select statement.
3204 $ret_array['select'] .= ", ' ' $field ";
3210 $data = $this->field_defs[$field];
3213 //ignore fields that are a part of the collection and a field has been removed as a result of
3214 //layout customization.. this happens in subpanel customizations, use case, from the contacts subpanel
3215 //in opportunities module remove the contact_role/opportunity_role field.
3216 $process_field=true;
3217 if (isset($data['relationship_fields']) and !empty($data['relationship_fields']))
3219 foreach ($data['relationship_fields'] as $field_name)
3221 if (!isset($fields[$field_name]))
3223 $process_field=false;
3227 if (!$process_field)
3232 if( (!isset($data['source']) || $data['source'] == 'db') && (!empty($alias) || !empty($filter) ))
3234 $ret_array['select'] .= ", $this->table_name.$field $alias";
3235 $selectedFields["$this->table_name.$field"] = true;
3236 } else if( (!isset($data['source']) || $data['source'] == 'custom_fields') && (!empty($alias) || !empty($filter) )) {
3237 //add this column only if it has NOT already been added to select statement string
3238 $colPos = strpos($ret_array['select'],"$this->table_name"."_cstm".".$field");
3239 if(!$colPos || $colPos<0)
3241 $ret_array['select'] .= ", $this->table_name"."_cstm".".$field $alias";
3244 $selectedFields["$this->table_name.$field"] = true;
3247 if($data['type'] != 'relate' && isset($data['db_concat_fields']))
3249 $ret_array['select'] .= ", " . $this->db->concat($this->table_name, $data['db_concat_fields']) . " as $field";
3250 $selectedFields[$this->db->concat($this->table_name, $data['db_concat_fields'])] = true;
3252 //Custom relate field or relate fields built in module builder which have no link field associated.
3253 if ($data['type'] == 'relate' && (isset($data['custom_module']) || isset($data['ext2']))) {
3254 $joinTableAlias = 'jt' . $jtcount;
3255 $relateJoinInfo = $this->custom_fields->getRelateJoin($data, $joinTableAlias, false);
3256 $ret_array['select'] .= $relateJoinInfo['select'];
3257 $ret_array['from'] .= $relateJoinInfo['from'];
3258 //Replace any references to the relationship in the where clause with the new alias
3259 //If the link isn't set, assume that search used the local table for the field
3260 $searchTable = isset($data['link']) ? $relateJoinInfo['rel_table'] : $this->table_name;
3261 $field_name = $relateJoinInfo['rel_table'] . '.' . !empty($data['name'])?$data['name']:'name';
3262 $where = preg_replace('/(^|[\s(])' . $field_name . '/' , '${1}' . $relateJoinInfo['name_field'], $where);
3266 if ($data['type'] == 'parent') {
3267 //See if we need to join anything by inspecting the where clause
3268 $match = preg_match('/(^|[\s(])parent_(\w+)_(\w+)\.name/', $where, $matches);
3270 $joinTableAlias = 'jt' . $jtcount;
3271 $joinModule = $matches[2];
3272 $joinTable = $matches[3];
3273 $localTable = $this->table_name;
3274 if (!empty($data['custom_module'])) {
3275 $localTable .= '_cstm';
3277 global $beanFiles, $beanList, $module;
3278 require_once($beanFiles[$beanList[$joinModule]]);
3279 $rel_mod = new $beanList[$joinModule]();
3280 $nameField = "$joinTableAlias.name";
3281 if (isset($rel_mod->field_defs['name']))
3283 $name_field_def = $rel_mod->field_defs['name'];
3284 if(isset($name_field_def['db_concat_fields']))
3286 $nameField = $this->db->concat($joinTableAlias, $name_field_def['db_concat_fields']);
3289 $ret_array['select'] .= ", $nameField {$data['name']} ";
3290 $ret_array['from'] .= " LEFT JOIN $joinTable $joinTableAlias
3291 ON $localTable.{$data['id_name']} = $joinTableAlias.id";
3292 //Replace any references to the relationship in the where clause with the new alias
3293 $where = preg_replace('/(^|[\s(])parent_' . $joinModule . '_' . $joinTable . '\.name/', '${1}' . $nameField, $where);
3298 if ($this->is_relate_field($field))
3300 $this->load_relationship($data['link']);
3301 if(!empty($this->$data['link']))
3304 if(empty($join_type))
3306 $params['join_type'] = ' LEFT JOIN ';
3310 $params['join_type'] = $join_type;
3312 if(isset($data['join_name']))
3314 $params['join_table_alias'] = $data['join_name'];
3318 $params['join_table_alias'] = 'jt' . $jtcount;
3321 if(isset($data['join_link_name']))
3323 $params['join_table_link_alias'] = $data['join_link_name'];
3327 $params['join_table_link_alias'] = 'jtl' . $jtcount;
3329 $join_primary = !isset($data['join_primary']) || $data['join_primary'];
3331 $join = $this->$data['link']->getJoin($params, true);
3332 $used_join_key[] = $join['rel_key'];
3333 $rel_module = $this->$data['link']->getRelatedModuleName();
3334 $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');
3336 //if rname is set to 'name', and bean files exist, then check if field should be a concatenated name
3337 global $beanFiles, $beanList;
3338 if($data['rname'] && !empty($beanFiles[$beanList[$rel_module]])) {
3340 //create an instance of the related bean
3341 require_once($beanFiles[$beanList[$rel_module]]);
3342 $rel_mod = new $beanList[$rel_module]();
3343 //if bean has first and last name fields, then name should be concatenated
3344 if(isset($rel_mod->field_name_map['first_name']) && isset($rel_mod->field_name_map['last_name'])){
3345 $data['db_concat_fields'] = array(0=>'first_name', 1=>'last_name');
3350 if($join['type'] == 'many-to-many')
3352 if(empty($ret_array['secondary_select']))
3354 $ret_array['secondary_select'] = " SELECT $this->table_name.id ref_id ";
3356 if(!empty($beanFiles[$beanList[$rel_module]]) && $join_primary)
3358 require_once($beanFiles[$beanList[$rel_module]]);
3359 $rel_mod = new $beanList[$rel_module]();
3360 if(isset($rel_mod->field_defs['assigned_user_id']))
3362 $ret_array['secondary_select'].= " , ". $params['join_table_alias'] . ".assigned_user_id {$field}_owner, '$rel_module' {$field}_mod";
3366 if(isset($rel_mod->field_defs['created_by']))
3368 $ret_array['secondary_select'].= " , ". $params['join_table_alias'] . ".created_by {$field}_owner , '$rel_module' {$field}_mod";
3374 if(isset($data['db_concat_fields']))
3376 $ret_array['secondary_select'] .= ' , ' . $this->db->concat($params['join_table_alias'], $data['db_concat_fields']) . ' ' . $field;
3380 if(!isset($data['relationship_fields']))
3382 $ret_array['secondary_select'] .= ' , ' . $params['join_table_alias'] . '.' . $data['rname'] . ' ' . $field;
3387 $ret_array['select'] .= ", ' ' $field ";
3390 foreach($used_join_key as $used_key) {
3391 if($used_key == $join['rel_key']) $count_used++;
3393 if($count_used <= 1) {//27416, the $ret_array['secondary_select'] should always generate, regardless the dbtype
3394 // add rel_key only if it was not aready added
3397 $ret_array['select'] .= ", ' ' " . $join['rel_key'] . ' ';
3399 $ret_array['secondary_select'] .= ', ' . $params['join_table_link_alias'].'.'. $join['rel_key'] .' ' . $join['rel_key'];
3401 if(isset($data['relationship_fields']))
3403 foreach($data['relationship_fields'] as $r_name=>$alias_name)
3405 if(!empty( $secondarySelectedFields[$alias_name]))continue;
3406 $ret_array['secondary_select'] .= ', ' . $params['join_table_link_alias'].'.'. $r_name .' ' . $alias_name;
3407 $secondarySelectedFields[$alias_name] = true;
3412 $ret_array['secondary_from'] .= ' ' . $join['join']. ' AND ' . $params['join_table_alias'].'.deleted=0';
3413 if (isset($data['link_type']) && $data['link_type'] == 'relationship_info' && ($parentbean instanceOf SugarBean))
3415 $ret_array['secondary_where'] = $params['join_table_link_alias'] . '.' . $join['rel_key']. "='" .$parentbean->id . "'";
3421 if(isset($data['db_concat_fields']))
3423 $ret_array['select'] .= ' , ' . $this->db->concat($params['join_table_alias'], $data['db_concat_fields']) . ' ' . $field;
3427 $ret_array['select'] .= ' , ' . $params['join_table_alias'] . '.' . $data['rname'] . ' ' . $field;
3429 if(isset($data['additionalFields'])){
3430 foreach($data['additionalFields'] as $k=>$v){
3431 if (!empty($data['id_name']) && $data['id_name'] == $v && !empty($fields[$data['id_name']])) {
3434 $ret_array['select'] .= ' , ' . $params['join_table_alias'] . '.' . $k . ' ' . $v;
3439 $ret_array['from'] .= ' ' . $join['join']. ' AND ' . $params['join_table_alias'].'.deleted=0';
3440 if(!empty($beanList[$rel_module]) && !empty($beanFiles[$beanList[$rel_module]]))
3442 require_once($beanFiles[$beanList[$rel_module]]);
3443 $rel_mod = new $beanList[$rel_module]();
3444 if(isset($value['target_record_key']) && !empty($filter))
3446 $selectedFields[$this->table_name.'.'.$value['target_record_key']] = true;
3447 $ret_array['select'] .= " , $this->table_name.{$value['target_record_key']} ";
3449 if(isset($rel_mod->field_defs['assigned_user_id']))
3451 $ret_array['select'] .= ' , ' .$params['join_table_alias'] . '.assigned_user_id ' . $field . '_owner';
3455 $ret_array['select'] .= ' , ' .$params['join_table_alias'] . '.created_by ' . $field . '_owner';
3457 $ret_array['select'] .= " , '".$rel_module ."' " . $field . '_mod';
3462 // To fix SOAP stuff where we are trying to retrieve all the accounts data where accounts.id = ..
3463 // and this code changes accounts to jt4 as there is a self join with the accounts table.
3465 if(isset($data['db_concat_fields'])){
3466 $buildWhere = false;
3467 if(in_array('first_name', $data['db_concat_fields']) && in_array('last_name', $data['db_concat_fields']))
3469 $exp = '/\(\s*?'.$data['name'].'.*?\%\'\s*?\)/';
3470 if(preg_match($exp, $where, $matches))
3472 $search_expression = $matches[0];
3473 //Create three search conditions - first + last, first, last
3474 $first_name_search = str_replace($data['name'], $params['join_table_alias'] . '.first_name', $search_expression);
3475 $last_name_search = str_replace($data['name'], $params['join_table_alias'] . '.last_name', $search_expression);
3476 $full_name_search = str_replace($data['name'], $this->db->concat($params['join_table_alias'], $data['db_concat_fields']), $search_expression);
3478 $where = str_replace($search_expression, '(' . $full_name_search . ' OR ' . $first_name_search . ' OR ' . $last_name_search . ')', $where);
3484 $db_field = $this->db->concat($params['join_table_alias'], $data['db_concat_fields']);
3485 $where = preg_replace('/'.$data['name'].'/', $db_field, $where);
3488 $where = preg_replace('/(^|[\s(])' . $data['name'] . '/', '${1}' . $params['join_table_alias'] . '.'.$data['rname'], $where);
3492 $joined_tables[$params['join_table_alias']]=1;
3493 $joined_tables[$params['join_table_link_alias']]=1;
3502 if(isset($this->field_defs['assigned_user_id']) && empty($selectedFields[$this->table_name.'.assigned_user_id']))
3504 $ret_array['select'] .= ", $this->table_name.assigned_user_id ";
3506 else if(isset($this->field_defs['created_by']) && empty($selectedFields[$this->table_name.'.created_by']))
3508 $ret_array['select'] .= ", $this->table_name.created_by ";
3510 if(isset($this->field_defs['system_id']) && empty($selectedFields[$this->table_name.'.system_id']))
3512 $ret_array['select'] .= ", $this->table_name.system_id ";
3517 if ($ifListForExport) {
3518 if(isset($this->field_defs['email1'])) {
3519 $ret_array['select'].= " ,email_addresses.email_address email1";
3520 $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 ";
3524 $where_auto = '1=1';
3525 if($show_deleted == 0)
3527 $where_auto = "$this->table_name.deleted=0";
3528 }else if($show_deleted == 1)
3530 $where_auto = "$this->table_name.deleted=1";
3533 $ret_array['where'] = " where ($where) AND $where_auto";
3535 $ret_array['where'] = " where $where_auto";
3537 //make call to process the order by clause
3538 $order_by = $this->process_order_by($order_by);
3539 if (!empty($order_by)) {
3540 $ret_array['order_by'] = " ORDER BY " . $order_by;
3544 unset($ret_array['secondary_where']);
3545 unset($ret_array['secondary_from']);
3546 unset($ret_array['secondary_select']);
3554 return $ret_array['select'] . $ret_array['from'] . $ret_array['where']. $ret_array['order_by'];
3557 * Returns parent record data for objects that store relationship information
3559 * @param array $type_info
3561 * Interal function, do not override.
3563 function retrieve_parent_fields($type_info)
3566 global $beanList, $beanFiles;
3567 $templates = array();
3568 $parent_child_map = array();
3569 foreach($type_info as $children_info)
3571 foreach($children_info as $child_info)
3573 if($child_info['type'] == 'parent')
3575 if(empty($templates[$child_info['parent_type']]))
3577 //Test emails will have an invalid parent_type, don't try to load the non-existent parent bean
3578 if ($child_info['parent_type'] == 'test') {
3581 $class = $beanList[$child_info['parent_type']];
3582 // Added to avoid error below; just silently fail and write message to log
3583 if ( empty($beanFiles[$class]) ) {
3584 $GLOBALS['log']->error($this->object_name.'::retrieve_parent_fields() - cannot load class "'.$class.'", skip loading.');
3587 require_once($beanFiles[$class]);
3588 $templates[$child_info['parent_type']] = new $class();
3591 if(empty($queries[$child_info['parent_type']]))
3593 $queries[$child_info['parent_type']] = "SELECT id ";
3594 $field_def = $templates[$child_info['parent_type']]->field_defs['name'];
3595 if(isset($field_def['db_concat_fields']))
3597 $queries[$child_info['parent_type']] .= ' , ' . $this->db->concat($templates[$child_info['parent_type']]->table_name, $field_def['db_concat_fields']) . ' parent_name';
3601 $queries[$child_info['parent_type']] .= ' , name parent_name';
3603 if(isset($templates[$child_info['parent_type']]->field_defs['assigned_user_id']))
3605 $queries[$child_info['parent_type']] .= ", assigned_user_id parent_name_owner , '{$child_info['parent_type']}' parent_name_mod";;
3606 }else if(isset($templates[$child_info['parent_type']]->field_defs['created_by']))
3608 $queries[$child_info['parent_type']] .= ", created_by parent_name_owner, '{$child_info['parent_type']}' parent_name_mod";
3610 $queries[$child_info['parent_type']] .= " FROM " . $templates[$child_info['parent_type']]->table_name ." WHERE id IN ('{$child_info['parent_id']}'";
3614 if(empty($parent_child_map[$child_info['parent_id']]))
3615 $queries[$child_info['parent_type']] .= " ,'{$child_info['parent_id']}'";
3617 $parent_child_map[$child_info['parent_id']][] = $child_info['child_id'];
3622 foreach($queries as $query)
3624 $result = $this->db->query($query . ')');
3625 while($row = $this->db->fetchByAssoc($result))
3627 $results[$row['id']] = $row;
3631 $child_results = array();
3632 foreach($parent_child_map as $parent_key=>$parent_child)
3634 foreach($parent_child as $child)
3636 if(isset( $results[$parent_key]))
3638 $child_results[$child] = $results[$parent_key];
3642 return $child_results;
3646 * Processes the list query and return fetched row.
3648 * Internal function, do not override.
3649 * @param string $query select query to be processed.
3650 * @param int $row_offset starting position
3651 * @param int $limit Optioanl, default -1
3652 * @param int $max_per_page Optional, default -1
3653 * @param string $where Optional, additional filter criteria.
3654 * @return array Fetched data
3656 function process_list_query($query, $row_offset, $limit= -1, $max_per_page = -1, $where = '')
3658 global $sugar_config;
3659 $db = DBManagerFactory::getInstance('listviews');
3661 * if the row_offset is set to 'end' go to the end of the list
3663 $toEnd = strval($row_offset) == 'end';
3664 $GLOBALS['log']->debug("process_list_query: ".$query);
3665 if($max_per_page == -1)
3667 $max_per_page = $sugar_config['list_max_entries_per_page'];
3669 // Check to see if we have a count query available.
3670 if(empty($sugar_config['disable_count_query']) || $toEnd)
3672 $count_query = $this->create_list_count_query($query);
3673 if(!empty($count_query) && (empty($limit) || $limit == -1))
3675 // We have a count query. Run it and get the results.
3676 $result = $db->query($count_query, true, "Error running count query for $this->object_name List: ");
3677 $assoc = $db->fetchByAssoc($result);
3678 if(!empty($assoc['c']))
3680 $rows_found = $assoc['c'];
3681 $limit = $sugar_config['list_max_entries_per_page'];
3685 $row_offset = (floor(($rows_found -1) / $limit)) * $limit;
3691 if((empty($limit) || $limit == -1))
3693 $limit = $max_per_page + 1;
3694 $max_per_page = $limit;
3699 if(empty($row_offset))
3703 if(!empty($limit) && $limit != -1 && $limit != -99)
3705 $result = $db->limitQuery($query, $row_offset, $limit,true,"Error retrieving $this->object_name list: ");
3709 $result = $db->query($query,true,"Error retrieving $this->object_name list: ");
3714 $previous_offset = $row_offset - $max_per_page;
3715 $next_offset = $row_offset + $max_per_page;
3717 $class = get_class($this);
3718 //FIXME: Bug? we should remove the magic number -99
3719 //use -99 to return all
3720 $index = $row_offset;
3721 while ($max_per_page == -99 || ($index < $row_offset + $max_per_page))
3723 $row = $db->fetchByAssoc($result);
3724 if (empty($row)) break;
3726 //instantiate a new class each time. This is because php5 passes
3727 //by reference by default so if we continually update $this, we will
3728 //at the end have a list of all the same objects
3729 /** @var SugarBean $temp */
3730 $temp = new $class();
3732 foreach($this->field_defs as $field=>$value)
3734 if (isset($row[$field]))
3736 $temp->$field = $row[$field];
3737 $owner_field = $field . '_owner';
3738 if(isset($row[$owner_field]))
3740 $temp->$owner_field = $row[$owner_field];
3743 $GLOBALS['log']->debug("$temp->object_name({$row['id']}): ".$field." = ".$temp->$field);
3744 }else if (isset($row[$this->table_name .'.'.$field]))
3746 $temp->$field = $row[$this->table_name .'.'.$field];
3754 $temp->check_date_relationships_load();
3755 $temp->fill_in_additional_list_fields();
3756 if($temp->hasCustomFields()) $temp->custom_fields->fill_relationships();
3757 $temp->call_custom_logic("process_record");
3759 // fix defect #44206. implement the same logic as sugar_currency_format
3760 // Smarty modifier does.
3761 $temp->populateCurrencyFields();
3766 if(!empty($sugar_config['disable_count_query']) && !empty($limit))
3769 $rows_found = $row_offset + count($list);
3776 } else if(!isset($rows_found)){
3777 $rows_found = $row_offset + count($list);
3780 $response = Array();
3781 $response['list'] = $list;
3782 $response['row_count'] = $rows_found;
3783 $response['next_offset'] = $next_offset;
3784 $response['previous_offset'] = $previous_offset;
3785 $response['current_offset'] = $row_offset ;
3790 * Returns the number of rows that the given SQL query should produce
3792 * Internal function, do not override.
3793 * @param string $query valid select query
3794 * @param boolean $is_count_query Optional, Default false, set to true if passed query is a count query.
3795 * @return int count of rows found
3797 function _get_num_rows_in_query($query, $is_count_query=false)
3799 $num_rows_in_query = 0;
3800 if (!$is_count_query)
3802 $count_query = SugarBean::create_list_count_query($query);
3804 $count_query=$query;
3806 $result = $this->db->query($count_query, true, "Error running count query for $this->object_name List: ");
3808 while($row = $this->db->fetchByAssoc($result, true))
3810 $num_rows_in_query += current($row);
3813 return $num_rows_in_query;
3817 * Applies pagination window to union queries used by list view and subpanels,
3818 * executes the query and returns fetched data.
3820 * Internal function, do not override.
3821 * @param object $parent_bean
3822 * @param string $query query to be processed.
3823 * @param int $row_offset
3824 * @param int $limit optional, default -1
3825 * @param int $max_per_page Optional, default -1
3826 * @param string $where Custom where clause.
3827 * @param array $subpanel_def definition of sub-panel to be processed
3828 * @param string $query_row_count
3829 * @param array $seconday_queries
3830 * @return array Fetched data.
3832 function process_union_list_query($parent_bean, $query,
3833 $row_offset, $limit= -1, $max_per_page = -1, $where = '', $subpanel_def, $query_row_count='', $secondary_queries = array())
3836 $db = DBManagerFactory::getInstance('listviews');
3838 * if the row_offset is set to 'end' go to the end of the list
3840 $toEnd = strval($row_offset) == 'end';
3841 global $sugar_config;
3842 $use_count_query=false;
3843 $processing_collection=$subpanel_def->isCollection();
3845 $GLOBALS['log']->debug("process_union_list_query: ".$query);
3846 if($max_per_page == -1)
3848 $max_per_page = $sugar_config['list_max_entries_per_subpanel'];
3850 if(empty($query_row_count))
3852 $query_row_count = $query;
3854 $distinct_position=strpos($query_row_count,"DISTINCT");
3856 if ($distinct_position!= false)
3858 $use_count_query=true;
3860 $performSecondQuery = true;
3861 if(empty($sugar_config['disable_count_query']) || $toEnd)
3863 $rows_found = $this->_get_num_rows_in_query($query_row_count,$use_count_query);
3866 $performSecondQuery = false;
3868 if(!empty($rows_found) && (empty($limit) || $limit == -1))
3870 $limit = $sugar_config['list_max_entries_per_subpanel'];
3874 $row_offset = (floor(($rows_found -1) / $limit)) * $limit;
3880 if((empty($limit) || $limit == -1))
3882 $limit = $max_per_page + 1;
3883 $max_per_page = $limit;
3887 if(empty($row_offset))
3892 $previous_offset = $row_offset - $max_per_page;
3893 $next_offset = $row_offset + $max_per_page;
3895 if($performSecondQuery)
3897 if(!empty($limit) && $limit != -1 && $limit != -99)
3899 $result = $db->limitQuery($query, $row_offset, $limit,true,"Error retrieving $parent_bean->object_name list: ");
3903 $result = $db->query($query,true,"Error retrieving $this->object_name list: ");
3905 //use -99 to return all
3907 // get the current row
3908 $index = $row_offset;
3909 $row = $db->fetchByAssoc($result);
3911 $post_retrieve = array();
3912 $isFirstTime = true;
3915 $function_fields = array();
3916 if(($index < $row_offset + $max_per_page || $max_per_page == -99))
3918 if ($processing_collection)
3920 $current_bean =$subpanel_def->sub_subpanels[$row['panel_name']]->template_instance;
3923 $class = get_class($subpanel_def->sub_subpanels[$row['panel_name']]->template_instance);
3924 $current_bean = new $class();
3927 $current_bean=$subpanel_def->template_instance;
3930 $class = get_class($subpanel_def->template_instance);
3931 $current_bean = new $class();
3934 $isFirstTime = false;
3935 //set the panel name in the bean instance.
3936 if (isset($row['panel_name']))
3938 $current_bean->panel_name=$row['panel_name'];
3940 foreach($current_bean->field_defs as $field=>$value)
3943 if (isset($row[$field]))
3945 $current_bean->$field = $this->convertField($row[$field], $value);
3946 unset($row[$field]);
3948 else if (isset($row[$this->table_name .'.'.$field]))
3950 $current_bean->$field = $this->convertField($row[$this->table_name .'.'.$field], $value);
3951 unset($row[$this->table_name .'.'.$field]);
3955 $current_bean->$field = "";
3956 unset($row[$field]);
3958 if(isset($value['source']) && $value['source'] == 'function')
3960 $function_fields[]=$field;
3963 foreach($row as $key=>$value)
3965 $current_bean->$key = $value;
3967 foreach($function_fields as $function_field)
3969 $value = $current_bean->field_defs[$function_field];
3970 $can_execute = true;
3971 $execute_params = array();
3972 $execute_function = array();
3973 if(!empty($value['function_class']))
3975 $execute_function[] = $value['function_class'];
3976 $execute_function[] = $value['function_name'];
3980 $execute_function = $value['function_name'];
3982 foreach($value['function_params'] as $param )
3984 if (empty($value['function_params_source']) or $value['function_params_source']=='parent')
3986 if(empty($this->$param))
3988 $can_execute = false;
3989 } else if($param == '$this') {
3990 $execute_params[] = $this;
3994 $execute_params[] = $this->$param;
3996 } else if ($value['function_params_source']=='this')
3998 if(empty($current_bean->$param))
4000 $can_execute = false;
4001 } else if($param == '$this') {
4002 $execute_params[] = $current_bean;
4006 $execute_params[] = $current_bean->$param;
4011 $can_execute = false;
4017 if(!empty($value['function_require']))
4019 require_once($value['function_require']);
4021 $current_bean->$function_field = call_user_func_array($execute_function, $execute_params);
4024 if(!empty($current_bean->parent_type) && !empty($current_bean->parent_id))
4026 if(!isset($post_retrieve[$current_bean->parent_type]))
4028 $post_retrieve[$current_bean->parent_type] = array();
4030 $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');
4032 //$current_bean->fill_in_additional_list_fields();
4033 $list[$current_bean->id] = $current_bean;
4035 // go to the next row
4037 $row = $db->fetchByAssoc($result);
4039 //now handle retrieving many-to-many relationships
4042 foreach($secondary_queries as $query2)
4044 $result2 = $db->query($query2);
4046 $row2 = $db->fetchByAssoc($result2);
4049 $id_ref = $row2['ref_id'];
4051 if(isset($list[$id_ref]))
4053 foreach($row2 as $r2key=>$r2value)
4055 if($r2key != 'ref_id')
4057 $list[$id_ref]->$r2key = $r2value;
4061 $row2 = $db->fetchByAssoc($result2);
4067 if(isset($post_retrieve))
4069 $parent_fields = $this->retrieve_parent_fields($post_retrieve);
4073 $parent_fields = array();
4075 if(!empty($sugar_config['disable_count_query']) && !empty($limit))
4077 //C.L. Bug 43535 - Use the $index value to set the $rows_found value here
4078 $rows_found = isset($index) ? $index : $row_offset + count($list);
4080 if(count($list) >= $limit)
4094 $parent_fields = array();
4096 $response = array();
4097 $response['list'] = $list;
4098 $response['parent_data'] = $parent_fields;
4099 $response['row_count'] = $rows_found;
4100 $response['next_offset'] = $next_offset;
4101 $response['previous_offset'] = $previous_offset;
4102 $response['current_offset'] = $row_offset ;
4103 $response['query'] = $query;
4109 * Applies pagination window to select queries used by detail view,
4110 * executes the query and returns fetched data.
4112 * Internal function, do not override.
4113 * @param string $query query to be processed.
4114 * @param int $row_offset
4115 * @param int $limit optional, default -1
4116 * @param int $max_per_page Optional, default -1
4117 * @param string $where Custom where clause.
4118 * @param int $offset Optional, default 0
4119 * @return array Fetched data.
4122 function process_detail_query($query, $row_offset, $limit= -1, $max_per_page = -1, $where = '', $offset = 0)
4124 global $sugar_config;
4125 $GLOBALS['log']->debug("process_detail_query: ".$query);
4126 if($max_per_page == -1)
4128 $max_per_page = $sugar_config['list_max_entries_per_page'];
4131 // Check to see if we have a count query available.
4132 $count_query = $this->create_list_count_query($query);
4134 if(!empty($count_query) && (empty($limit) || $limit == -1))
4136 // We have a count query. Run it and get the results.
4137 $result = $this->db->query($count_query, true, "Error running count query for $this->object_name List: ");
4138 $assoc = $this->db->fetchByAssoc($result);
4139 if(!empty($assoc['c']))
4141 $total_rows = $assoc['c'];
4145 if(empty($row_offset))
4150 $result = $this->db->limitQuery($query, $offset, 1, true,"Error retrieving $this->object_name list: ");
4152 $previous_offset = $row_offset - $max_per_page;
4153 $next_offset = $row_offset + $max_per_page;
4155 $row = $this->db->fetchByAssoc($result);
4156 $this->retrieve($row['id']);
4158 $response = Array();
4159 $response['bean'] = $this;
4160 if (empty($total_rows))
4162 $response['row_count'] = $total_rows;
4163 $response['next_offset'] = $next_offset;
4164 $response['previous_offset'] = $previous_offset;
4170 * Processes fetched list view data
4172 * Internal function, do not override.
4173 * @param string $query query to be processed.
4174 * @param boolean $check_date Optional, default false. if set to true date time values are processed.
4175 * @return array Fetched data.
4178 function process_full_list_query($query, $check_date=false)
4181 $GLOBALS['log']->debug("process_full_list_query: query is ".$query);
4182 $result = $this->db->query($query, false);
4183 $GLOBALS['log']->debug("process_full_list_query: result is ".print_r($result,true));
4184 $class = get_class($this);
4185 $isFirstTime = true;
4186 $bean = new $class();
4188 // We have some data.
4189 while (($row = $bean->db->fetchByAssoc($result)) != null)
4191 $row = $this->convertRow($row);
4194 $bean = new $class();
4196 $isFirstTime = false;
4198 foreach($bean->field_defs as $field=>$value)
4200 if (isset($row[$field]))
4202 $bean->$field = $row[$field];
4203 $GLOBALS['log']->debug("process_full_list: $bean->object_name({$row['id']}): ".$field." = ".$bean->$field);
4212 $bean->processed_dates_times = array();
4213 $bean->check_date_relationships_load();
4215 $bean->fill_in_additional_list_fields();
4216 $bean->call_custom_logic("process_record");
4217 $bean->fetched_row = $row;
4222 if (isset($list)) return $list;
4227 * Tracks the viewing of a detail record.
4228 * This leverages get_summary_text() which is object specific.
4230 * Internal function, do not override.
4231 * @param string $user_id - String value of the user that is viewing the record.
4232 * @param string $current_module - String value of the module being processed.
4233 * @param string $current_view - String value of the current view
4235 function track_view($user_id, $current_module, $current_view='')
4237 $trackerManager = TrackerManager::getInstance();
4238 if($monitor = $trackerManager->getMonitor('tracker')){
4239 $monitor->setValue('date_modified', $GLOBALS['timedate']->nowDb());
4240 $monitor->setValue('user_id', $user_id);
4241 $monitor->setValue('module_name', $current_module);
4242 $monitor->setValue('action', $current_view);
4243 $monitor->setValue('item_id', $this->id);
4244 $monitor->setValue('item_summary', $this->get_summary_text());
4245 $monitor->setValue('visible', $this->tracker_visibility);
4246 $trackerManager->saveMonitor($monitor);
4251 * Returns the summary text that should show up in the recent history list for this object.
4255 public function get_summary_text()
4257 return "Base Implementation. Should be overridden.";
4261 * This is designed to be overridden and add specific fields to each record.
4262 * This allows the generic query to fill in the major fields, and then targeted
4263 * queries to get related fields and add them to the record. The contact's
4264 * account for instance. This method is only used for populating extra fields
4267 function fill_in_additional_list_fields(){
4268 if(!empty($this->field_defs['parent_name']) && empty($this->parent_name)){
4269 $this->fill_in_additional_parent_fields();
4274 * This is designed to be overridden and add specific fields to each record.
4275 * This allows the generic query to fill in the major fields, and then targeted
4276 * queries to get related fields and add them to the record. The contact's
4277 * account for instance. This method is only used for populating extra fields
4278 * in the detail form
4280 function fill_in_additional_detail_fields()
4282 if(!empty($this->field_defs['assigned_user_name']) && !empty($this->assigned_user_id)){
4284 $this->assigned_user_name = get_assigned_user_name($this->assigned_user_id);
4287 if(!empty($this->field_defs['created_by']) && !empty($this->created_by))
4288 $this->created_by_name = get_assigned_user_name($this->created_by);
4289 if(!empty($this->field_defs['modified_user_id']) && !empty($this->modified_user_id))
4290 $this->modified_by_name = get_assigned_user_name($this->modified_user_id);
4292 if(!empty($this->field_defs['parent_name'])){
4293 $this->fill_in_additional_parent_fields();
4298 * This is desgined to be overridden or called from extending bean. This method
4299 * will fill in any parent_name fields.
4301 function fill_in_additional_parent_fields() {
4303 if(!empty($this->parent_id) && !empty($this->last_parent_id) && $this->last_parent_id == $this->parent_id){
4306 $this->parent_name = '';
4308 if(!empty($this->parent_type)) {
4309 $this->last_parent_id = $this->parent_id;
4310 $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'));
4311 if(!empty($this->parent_first_name) || !empty($this->parent_last_name) ){
4312 $this->parent_name = $GLOBALS['locale']->getLocaleFormattedName($this->parent_first_name, $this->parent_last_name);
4313 } else if(!empty($this->parent_document_name)){
4314 $this->parent_name = $this->parent_document_name;
4320 * Fill in a link field
4323 function fill_in_link_field( $linkFieldName , $def)
4325 $idField = $linkFieldName;
4326 //If the id_name provided really was an ID, don't try to load it as a link. Use the normal link
4327 if (!empty($this->field_defs[$linkFieldName]['type']) && $this->field_defs[$linkFieldName]['type'] == "id" && !empty($def['link']))
4329 $linkFieldName = $def['link'];
4331 if ($this->load_relationship($linkFieldName))
4333 $list=$this->$linkFieldName->get();
4334 $this->$idField = '' ; // match up with null value in $this->populateFromRow()
4336 $this->$idField = $list[0];
4341 * Fill in fields where type = relate
4343 function fill_in_relationship_fields(){
4344 global $fill_in_rel_depth;
4345 if(empty($fill_in_rel_depth) || $fill_in_rel_depth < 0)
4346 $fill_in_rel_depth = 0;
4348 if($fill_in_rel_depth > 1)
4351 $fill_in_rel_depth++;
4353 foreach($this->field_defs as $field)
4355 if(0 == strcmp($field['type'],'relate') && !empty($field['module']))
4357 $name = $field['name'];
4358 if(empty($this->$name))
4360 // 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']
4361 $related_module = $field['module'];
4362 $id_name = $field['id_name'];
4364 if (empty($this->$id_name))
4366 $this->fill_in_link_field($id_name, $field);
4368 if(!empty($this->$id_name) && ( $this->object_name != $related_module || ( $this->object_name == $related_module && $this->$id_name != $this->id ))){
4369 if(isset($GLOBALS['beanList'][ $related_module])){
4370 $class = $GLOBALS['beanList'][$related_module];
4372 if(!empty($this->$id_name) && file_exists($GLOBALS['beanFiles'][$class]) && isset($this->$name)){
4373 require_once($GLOBALS['beanFiles'][$class]);
4374 $mod = new $class();
4376 // disable row level security in order to be able
4377 // to retrieve related bean properties (bug #44928)
4379 $mod->retrieve($this->$id_name);
4381 if (!empty($field['rname'])) {
4382 $this->$name = $mod->$field['rname'];
4383 } else if (isset($mod->name)) {
4384 $this->$name = $mod->name;
4389 if(!empty($this->$id_name) && isset($this->$name))
4391 if(!isset($field['additionalFields']))
4392 $field['additionalFields'] = array();
4393 if(!empty($field['rname']))
4395 $field['additionalFields'][$field['rname']]= $name;
4399 $field['additionalFields']['name']= $name;
4401 $this->getRelatedFields($related_module, $this->$id_name, $field['additionalFields']);
4406 $fill_in_rel_depth--;
4410 * This is a helper function that is used to quickly created indexes when creating tables.
4412 function create_index($query)
4414 $GLOBALS['log']->info("create_index: $query");
4416 $result = $this->db->query($query, true, "Error creating index:");
4420 * This function should be overridden in each module. It marks an item as deleted.
4422 * If it is not overridden, then marking this type of item is not allowed
4424 function mark_deleted($id)
4426 global $current_user;
4427 $date_modified = $GLOBALS['timedate']->nowDb();
4428 if(isset($_SESSION['show_deleted']))
4430 $this->mark_undeleted($id);
4434 // call the custom business logic
4435 $custom_logic_arguments['id'] = $id;
4436 $this->call_custom_logic("before_delete", $custom_logic_arguments);
4438 $this->mark_relationships_deleted($id);
4439 if ( isset($this->field_defs['modified_user_id']) ) {
4440 if (!empty($current_user)) {
4441 $this->modified_user_id = $current_user->id;
4443 $this->modified_user_id = 1;
4445 $query = "UPDATE $this->table_name set deleted=1 , date_modified = '$date_modified', modified_user_id = '$this->modified_user_id' where id='$id'";
4447 $query = "UPDATE $this->table_name set deleted=1 , date_modified = '$date_modified' where id='$id'";
4449 $this->db->query($query, true,"Error marking record deleted: ");
4451 SugarRelationship::resaveRelatedBeans();
4453 // Take the item off the recently viewed lists
4454 $tracker = new Tracker();
4455 $tracker->makeInvisibleForAll($id);
4458 $this->deleteFiles();
4460 // call the custom business logic
4461 $this->call_custom_logic("after_delete", $custom_logic_arguments);
4466 * Restores data deleted by call to mark_deleted() function.
4468 * Internal function, do not override.
4470 function mark_undeleted($id)
4472 // call the custom business logic
4473 $custom_logic_arguments['id'] = $id;
4474 $this->call_custom_logic("before_restore", $custom_logic_arguments);
4476 $date_modified = $GLOBALS['timedate']->nowDb();
4477 $query = "UPDATE $this->table_name set deleted=0 , date_modified = '$date_modified' where id='$id'";
4478 $this->db->query($query, true,"Error marking record undeleted: ");
4480 $this->restoreFiles();
4482 // call the custom business logic
4483 $this->call_custom_logic("after_restore", $custom_logic_arguments);
4487 * This function deletes relationships to this object. It should be overridden
4488 * to handle the relationships of the specific object.
4489 * This function is called when the item itself is being deleted.
4491 * @param int $id id of the relationship to delete
4493 function mark_relationships_deleted($id)
4495 $this->delete_linked($id);
4499 * Returns path for files of bean or false on error
4501 * @return bool|string
4503 public function deleteFileDirectory()
4505 if (empty($this->id)) {
4508 return preg_replace('/^(..)(..)(..)/', '$1/$2/$3/', $this->id);
4512 * Moves file to deleted folder
4514 * @return bool success of movement
4516 public function deleteFiles()
4521 if (!$this->haveFiles()) {
4524 $files = $this->getFiles();
4525 if (empty($files)) {
4529 $directory = $this->deleteFileDirectory();
4531 $isCreated = sugar_is_dir('upload://deleted/' . $directory);
4533 sugar_mkdir('upload://deleted/' . $directory, 0777, true);
4534 $isCreated = sugar_is_dir('upload://deleted/' . $directory);
4540 foreach ($files as $file) {
4541 if (file_exists('upload://' . $file)) {
4542 if (!sugar_rename('upload://' . $file, 'upload://deleted/' . $directory . '/' . $file)) {
4543 $GLOBALS['log']->error('Could not move file ' . $file . ' to deleted directory');
4549 * @var DBManager $db
4553 'bean_id' => $db->quoted($this->id),
4554 'module' => $db->quoted($this->module_name),
4555 'date_modified' => $db->convert($db->quoted(date('Y-m-d H:i:s')), 'datetime')
4557 $recordDB = $db->fetchOne("SELECT id FROM cron_remove_documents WHERE module={$record['module']} AND bean_id={$record['bean_id']}");
4558 if (!empty($recordDB)) {
4559 $record['id'] = $db->quoted($recordDB['id']);
4561 if (empty($record['id'])) {
4562 $record['id'] = $db->quoted(create_guid());
4563 $db->query('INSERT INTO cron_remove_documents (' . implode(', ', array_keys($record)) . ') VALUES(' . implode(', ', $record) . ')');
4565 $db->query("UPDATE cron_remove_documents SET date_modified={$record['date_modified']} WHERE id={$record['id']}");
4572 * Restores files from deleted folder
4574 * @return bool success of operation
4576 protected function restoreFiles()
4581 if (!$this->haveFiles()) {
4584 $files = $this->getFiles();
4585 if (empty($files)) {
4589 $directory = $this->deleteFileDirectory();
4591 foreach ($files as $file) {
4592 if (sugar_is_file('upload://deleted/' . $directory . '/' . $file)) {
4593 if (!sugar_rename('upload://deleted/' . $directory . '/' . $file, 'upload://' . $file)) {
4594 $GLOBALS['log']->error('Could not move file ' . $directory . '/' . $file . ' from deleted directory');
4600 * @var DBManager $db
4603 $db->query('DELETE FROM cron_remove_documents WHERE bean_id=' . $db->quoted($this->id));
4609 * Method returns true if bean has files
4613 public function haveFiles()
4616 if ($this->bean_implements('FILE')) {
4618 } elseif ($this instanceof File) {
4620 } elseif (!empty(self::$fileFields[$this->module_name])) {
4622 } elseif (!empty($this->field_defs)) {
4623 foreach ($this->field_defs as $fieldDef) {
4624 if ($fieldDef['type'] != 'image') {
4635 * Method returns array of names of files for current bean
4639 public function getFiles() {
4641 foreach ($this->getFilesFields() as $field) {
4642 if (!empty($this->$field)) {
4643 $files[] = $this->$field;
4650 * Method returns array of name of fields which contain names of files
4652 * @param bool $resetCache do not use cache
4655 public function getFilesFields($resetCache = false)
4657 if (isset(self::$fileFields[$this->module_name]) && $resetCache == false) {
4658 return self::$fileFields[$this->module_name];
4661 self::$fileFields = array();
4662 if ($this->bean_implements('FILE') || $this instanceof File) {
4663 self::$fileFields[$this->module_name][] = 'id';
4665 foreach ($this->field_defs as $fieldName => $fieldDef) {
4666 if ($fieldDef['type'] != 'image') {
4669 self::$fileFields[$this->module_name][] = $fieldName;
4672 return self::$fileFields[$this->module_name];
4676 * This function is used to execute the query and create an array template objects
4677 * from the resulting ids from the query.
4678 * It is currently used for building sub-panel arrays.
4680 * @param string $query - the query that should be executed to build the list
4681 * @param object $template - The object that should be used to copy the records.
4682 * @param int $row_offset Optional, default 0
4683 * @param int $limit Optional, default -1
4686 function build_related_list($query, &$template, $row_offset = 0, $limit = -1)
4688 $GLOBALS['log']->debug("Finding linked records $this->object_name: ".$query);
4689 $db = DBManagerFactory::getInstance('listviews');
4691 if(!empty($row_offset) && $row_offset != 0 && !empty($limit) && $limit != -1)
4693 $result = $db->limitQuery($query, $row_offset, $limit,true,"Error retrieving $template->object_name list: ");
4697 $result = $db->query($query, true);
4701 $isFirstTime = true;
4702 $class = get_class($template);
4703 while($row = $this->db->fetchByAssoc($result))
4707 $template = new $class();
4709 $isFirstTime = false;
4710 $record = $template->retrieve($row['id']);
4714 // this copies the object into the array
4715 $list[] = $template;
4722 * This function is used to execute the query and create an array template objects
4723 * from the resulting ids from the query.
4724 * It is currently used for building sub-panel arrays. It supports an additional
4725 * where clause that is executed as a filter on the results
4727 * @param string $query - the query that should be executed to build the list
4728 * @param object $template - The object that should be used to copy the records.
4730 function build_related_list_where($query, &$template, $where='', $in='', $order_by, $limit='', $row_offset = 0)
4732 $db = DBManagerFactory::getInstance('listviews');
4733 // No need to do an additional query
4734 $GLOBALS['log']->debug("Finding linked records $this->object_name: ".$query);
4735 if(empty($in) && !empty($query))
4737 $idList = $this->build_related_in($query);
4738 $in = $idList['in'];
4740 // MFH - Added Support For Custom Fields in Searches
4741 $custom_join = $this->getCustomJoin();
4743 $query = "SELECT id ";
4745 $query .= $custom_join['select'];
4746 $query .= " FROM $this->table_name ";
4748 $query .= $custom_join['join'];
4750 $query .= " WHERE deleted=0 AND id IN $in";
4753 $query .= " AND $where";
4757 if(!empty($order_by))
4759 $query .= "ORDER BY $order_by";
4763 $result = $db->limitQuery($query, $row_offset, $limit,true,"Error retrieving $this->object_name list: ");
4767 $result = $db->query($query, true);
4771 $isFirstTime = true;
4772 $class = get_class($template);
4774 $disable_security_flag = ($template->disable_row_level_security) ? true : false;
4775 while($row = $db->fetchByAssoc($result))
4779 $template = new $class();
4780 $template->disable_row_level_security = $disable_security_flag;
4782 $isFirstTime = false;
4783 $record = $template->retrieve($row['id']);
4786 // this copies the object into the array
4787 $list[] = $template;
4795 * Constructs an comma separated list of ids from passed query results.
4797 * @param string @query query to be executed.
4800 function build_related_in($query)
4803 $result = $this->db->query($query, true);
4805 while($row = $this->db->fetchByAssoc($result))
4807 $idList[] = $row['id'];
4810 $ids = "('" . $row['id'] . "'";
4814 $ids .= ",'" . $row['id'] . "'";
4824 return array('list'=>$idList, 'in'=>$ids);
4828 * Optionally copies values from fetched row into the bean.
4830 * Internal function, do not override.
4832 * @param string $query - the query that should be executed to build the list
4833 * @param object $template - The object that should be used to copy the records
4834 * @param array $field_list List of fields.
4837 function build_related_list2($query, &$template, &$field_list)
4839 $GLOBALS['log']->debug("Finding linked values $this->object_name: ".$query);
4841 $result = $this->db->query($query, true);
4844 $isFirstTime = true;
4845 $class = get_class($template);
4846 while($row = $this->db->fetchByAssoc($result))
4848 // Create a blank copy
4852 $copy = new $class();
4854 $isFirstTime = false;
4855 foreach($field_list as $field)
4857 // Copy the relevant fields
4858 $copy->$field = $row[$field];
4862 // this copies the object into the array
4870 * Let implementing classes to fill in row specific columns of a list view form
4873 function list_view_parse_additional_sections(&$list_form)
4877 * Assigns all of the values into the template for the list view
4879 function get_list_view_array()
4881 static $cache = array();
4882 // cn: bug 12270 - sensitive fields being passed arbitrarily in listViews
4883 $sensitiveFields = array('user_hash' => '');
4885 $return_array = Array();
4886 global $app_list_strings, $mod_strings;
4887 foreach($this->field_defs as $field=>$value){
4889 if(isset($this->$field)){
4891 // cn: bug 12270 - sensitive fields being passed arbitrarily in listViews
4892 if(isset($sensitiveFields[$field]))
4894 if(!isset($cache[$field]))
4895 $cache[$field] = strtoupper($field);
4897 //Fields hidden by Dependent Fields
4898 if (isset($value['hidden']) && $value['hidden'] === true) {
4899 $return_array[$cache[$field]] = "";
4902 //cn: if $field is a _dom, detect and return VALUE not KEY
4903 //cl: empty function check for meta-data enum types that have values loaded from a function
4904 else if (((!empty($value['type']) && ($value['type'] == 'enum' || $value['type'] == 'radioenum') )) && empty($value['function'])){
4905 if(!empty($value['options']) && !empty($app_list_strings[$value['options']][$this->$field])){
4906 $return_array[$cache[$field]] = $app_list_strings[$value['options']][$this->$field];
4908 //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.
4909 elseif(!empty($value['options']) && !empty($mod_strings[$value['options']][$this->$field]))
4911 $return_array[$cache[$field]] = $mod_strings[$value['options']][$this->$field];
4914 $return_array[$cache[$field]] = $this->$field;
4917 // tjy: no need to do this str_replace as the changes in 29994 for ListViewGeneric.tpl for translation handle this now
4918 // }elseif(!empty($value['type']) && $value['type'] == 'multienum'&& empty($value['function'])){
4919 // $return_array[strtoupper($field)] = str_replace('^,^', ', ', $this->$field );
4920 }elseif(!empty($value['custom_module']) && $value['type'] != 'currency'){
4921 // $this->format_field($value);
4922 $return_array[$cache[$field]] = $this->$field;
4924 $return_array[$cache[$field]] = $this->$field;
4926 // handle "Assigned User Name"
4927 if($field == 'assigned_user_name'){
4928 $return_array['ASSIGNED_USER_NAME'] = get_assigned_user_name($this->assigned_user_id);
4932 return $return_array;
4935 * Override this function to set values in the array used to render list view data.
4938 function get_list_view_data()
4940 return $this->get_list_view_array();
4944 * Construct where clause from a list of name-value pairs.
4945 * @param array $fields_array Name/value pairs for column checks
4946 * @param boolean $deleted Optional, default true, if set to false deleted filter will not be added.
4947 * @return string The WHERE clause
4949 function get_where($fields_array, $deleted=true)
4952 foreach ($fields_array as $name=>$value)
4954 if (!empty($where_clause)) {
4955 $where_clause .= " AND ";
4957 $name = $this->db->getValidDBName($name);
4959 $where_clause .= "$name = ".$this->db->quoted($value,false);
4961 if(!empty($where_clause)) {
4963 return "WHERE $where_clause AND deleted=0";
4965 return "WHERE $where_clause";
4974 * Constructs a select query and fetch 1 row using this query, and then process the row
4976 * Internal function, do not override.
4977 * @param array @fields_array array of name value pairs used to construct query.
4978 * @param boolean $encode Optional, default true, encode fetched data.
4979 * @param boolean $deleted Optional, default true, if set to false deleted filter will not be added.
4980 * @return object Instance of this bean with fetched data.
4982 function retrieve_by_string_fields($fields_array, $encode=true, $deleted=true)
4984 $where_clause = $this->get_where($fields_array, $deleted);
4985 $custom_join = $this->getCustomJoin();
4986 $query = "SELECT $this->table_name.*". $custom_join['select']. " FROM $this->table_name " . $custom_join['join'];
4987 $query .= " $where_clause";
4988 $GLOBALS['log']->debug("Retrieve $this->object_name: ".$query);
4989 //requireSingleResult has been deprecated.
4990 //$result = $this->db->requireSingleResult($query, true, "Retrieving record $where_clause:");
4991 $result = $this->db->limitQuery($query,0,1,true, "Retrieving record $where_clause:");
4998 $row = $this->db->fetchByAssoc($result, $encode);
5003 // Removed getRowCount-if-clause earlier and insert duplicates_found here as it seems that we have found something
5004 // if we didn't return null in the previous clause.
5005 $this->duplicates_found = true;
5006 $row = $this->convertRow($row);
5007 $this->fetched_row = $row;
5008 $this->fromArray($row);
5009 $this->is_updated_dependent_fields = false;
5010 $this->fill_in_additional_detail_fields();
5015 * This method is called during an import before inserting a bean
5016 * Define an associative array called $special_fields
5017 * the keys are user defined, and don't directly map to the bean's fields
5018 * the value is the method name within that bean that will do extra
5019 * processing for that field. example: 'full_name'=>'get_names_from_full_name'
5022 function process_special_fields()
5024 foreach ($this->special_functions as $func_name)
5026 if ( method_exists($this,$func_name) )
5028 $this->$func_name();
5034 * Override this function to build a where clause based on the search criteria set into bean .
5037 function build_generic_where_clause($value)
5041 function getRelatedFields($module, $id, $fields, $return_array = false){
5042 if(empty($GLOBALS['beanList'][$module]))return '';
5043 $object = BeanFactory::getObjectName($module);
5045 VardefManager::loadVardef($module, $object);
5046 if(empty($GLOBALS['dictionary'][$object]['table']))return '';
5047 $table = $GLOBALS['dictionary'][$object]['table'];
5048 $query = 'SELECT id';
5049 foreach($fields as $field=>$alias){
5050 if(!empty($GLOBALS['dictionary'][$object]['fields'][$field]['db_concat_fields'])){
5051 $query .= ' ,' .$this->db->concat($table, $GLOBALS['dictionary'][$object]['fields'][$field]['db_concat_fields']) . ' as ' . $alias ;
5052 }else if(!empty($GLOBALS['dictionary'][$object]['fields'][$field]) &&
5053 (empty($GLOBALS['dictionary'][$object]['fields'][$field]['source']) ||
5054 $GLOBALS['dictionary'][$object]['fields'][$field]['source'] != "non-db"))
5056 $query .= ' ,' .$table . '.' . $field . ' as ' . $alias;
5058 if(!$return_array)$this->$alias = '';
5060 if($query == 'SELECT id' || empty($id)){
5065 if(isset($GLOBALS['dictionary'][$object]['fields']['assigned_user_id']))
5067 $query .= " , ". $table . ".assigned_user_id owner";
5070 else if(isset($GLOBALS['dictionary'][$object]['fields']['created_by']))
5072 $query .= " , ". $table . ".created_by owner";
5075 $query .= ' FROM ' . $table . ' WHERE deleted=0 AND id=';
5076 $result = $GLOBALS['db']->query($query . "'$id'" );
5077 $row = $GLOBALS['db']->fetchByAssoc($result);
5081 $owner = (empty($row['owner']))?'':$row['owner'];
5082 foreach($fields as $alias){
5083 $this->$alias = (!empty($row[$alias]))? $row[$alias]: '';
5084 $alias = $alias .'_owner';
5085 $this->$alias = $owner;
5086 $a_mod = $alias .'_mod';
5087 $this->$a_mod = $module;
5094 function &parse_additional_headers(&$list_form, $xTemplateSection)
5099 function assign_display_fields($currentModule)
5102 foreach($this->column_fields as $field)
5104 if(isset($this->field_name_map[$field]) && empty($this->$field))
5106 if($this->field_name_map[$field]['type'] != 'date' && $this->field_name_map[$field]['type'] != 'enum')
5107 $this->$field = $field;
5108 if($this->field_name_map[$field]['type'] == 'date')
5110 $this->$field = $timedate->to_display_date('1980-07-09');
5112 if($this->field_name_map[$field]['type'] == 'enum')
5114 $dom = $this->field_name_map[$field]['options'];
5115 global $current_language, $app_list_strings;
5116 $mod_strings = return_module_language($current_language, $currentModule);
5118 if(isset($mod_strings[$dom]))
5120 $options = $mod_strings[$dom];
5121 foreach($options as $key=>$value)
5123 if(!empty($key) && empty($this->$field ))
5125 $this->$field = $key;
5129 if(isset($app_list_strings[$dom]))
5131 $options = $app_list_strings[$dom];
5132 foreach($options as $key=>$value)
5134 if(!empty($key) && empty($this->$field ))
5136 $this->$field = $key;
5148 * RELATIONSHIP HANDLING
5151 function set_relationship($table, $relate_values, $check_duplicates = true,$do_update=false,$data_values=null)
5155 // make sure there is a date modified
5156 $date_modified = $this->db->convert("'".$GLOBALS['timedate']->nowDb()."'", 'datetime');
5159 if($check_duplicates)
5161 $query = "SELECT * FROM $table ";
5162 $where = "WHERE deleted = '0' ";
5163 foreach($relate_values as $name=>$value)
5165 $where .= " AND $name = '$value' ";
5168 $result = $this->db->query($query, false, "Looking For Duplicate Relationship:" . $query);
5169 $row=$this->db->fetchByAssoc($result);
5172 if(!$check_duplicates || empty($row) )
5174 unset($relate_values['id']);
5175 if ( isset($data_values))
5177 $relate_values = array_merge($relate_values,$data_values);
5179 $query = "INSERT INTO $table (id, ". implode(',', array_keys($relate_values)) . ", date_modified) VALUES ('" . create_guid() . "', " . "'" . implode("', '", $relate_values) . "', ".$date_modified.")" ;
5181 $this->db->query($query, false, "Creating Relationship:" . $query);
5183 else if ($do_update)
5186 foreach($data_values as $key=>$value)
5188 array_push($conds,$key."='".$this->db->quote($value)."'");
5190 $query = "UPDATE $table SET ". implode(',', $conds).",date_modified=".$date_modified." ".$where;
5191 $this->db->query($query, false, "Updating Relationship:" . $query);
5195 function retrieve_relationships($table, $values, $select_id)
5197 $query = "SELECT $select_id FROM $table WHERE deleted = 0 ";
5198 foreach($values as $name=>$value)
5200 $query .= " AND $name = '$value' ";
5202 $query .= " ORDER BY $select_id ";
5203 $result = $this->db->query($query, false, "Retrieving Relationship:" . $query);
5205 while($row = $this->db->fetchByAssoc($result))
5212 // TODO: this function needs adjustment
5213 function loadLayoutDefs()
5215 global $layout_defs;
5216 if(empty( $this->layout_def) && file_exists('modules/'. $this->module_dir . '/layout_defs.php'))
5218 include_once('modules/'. $this->module_dir . '/layout_defs.php');
5219 if(file_exists('custom/modules/'. $this->module_dir . '/Ext/Layoutdefs/layoutdefs.ext.php'))
5221 include_once('custom/modules/'. $this->module_dir . '/Ext/Layoutdefs/layoutdefs.ext.php');
5223 if ( empty( $layout_defs[get_class($this)]))
5225 echo "\$layout_defs[" . get_class($this) . "]; does not exist";
5228 $this->layout_def = $layout_defs[get_class($this)];
5233 * Trigger custom logic for this module that is defined for the provided hook
5234 * The custom logic file is located under custom/modules/[CURRENT_MODULE]/logic_hooks.php.
5235 * That file should define the $hook_version that should be used.
5236 * It should also define the $hook_array. The $hook_array will be a two dimensional array
5237 * the first dimension is the name of the event, the second dimension is the information needed
5238 * to fire the hook. Each entry in the top level array should be defined on a single line to make it
5239 * easier to automatically replace this file. There should be no contents of this file that are not replacable.
5241 * $hook_array['before_save'][] = Array(1, testtype, 'custom/modules/Leads/test12.php', 'TestClass', 'lead_before_save_1');
5242 * This sample line creates a before_save hook. The hooks are procesed in the order in which they
5243 * are added to the array. The second dimension is an array of:
5244 * processing index (for sorting before exporting the array)
5247 * php file to include
5248 * php class the method is in
5249 * php method to call
5251 * The method signature for version 1 hooks is:
5252 * function NAME(&$bean, $event, $arguments)
5253 * $bean - $this bean passed in by reference.
5254 * $event - The string for the current event (i.e. before_save)
5255 * $arguments - An array of arguments that are specific to the event.
5257 function call_custom_logic($event, $arguments = null)
5259 if(!isset($this->processed) || $this->processed == false){
5260 //add some logic to ensure we do not get into an infinite loop
5261 if(!empty($this->logicHookDepth[$event])) {
5262 if($this->logicHookDepth[$event] > $this->max_logic_depth)
5265 $this->logicHookDepth[$event] = 0;
5267 //we have to put the increment operator here
5268 //otherwise we may never increase the depth for that event in the case
5269 //where one event will trigger another as in the case of before_save and after_save
5270 //Also keeping the depth per event allow any number of hooks to be called on the bean
5271 //and we only will return if one event gets caught in a loop. We do not increment globally
5272 //for each event called.
5273 $this->logicHookDepth[$event]++;
5275 //method defined in 'include/utils/LogicHook.php'
5277 $logicHook = new LogicHook();
5278 $logicHook->setBean($this);
5279 $logicHook->call_custom_logic($this->module_dir, $event, $arguments);
5280 $this->logicHookDepth[$event]--;
5285 /* When creating a custom field of type Dropdown, it creates an enum row in the DB.
5286 A typical get_list_view_array() result will have the *KEY* value from that drop-down.
5287 Since custom _dom objects are flat-files included in the $app_list_strings variable,
5288 We need to generate a key-key pair to get the true value like so:
5289 ([module]_cstm->fields_meta_data->$app_list_strings->*VALUE*)*/
5290 function getRealKeyFromCustomFieldAssignedKey($name)
5292 if ($this->custom_fields->avail_fields[$name]['ext1'])
5296 elseif ($this->custom_fields->avail_fields[$name]['ext2'])
5300 elseif ($this->custom_fields->avail_fields[$name]['ext3'])
5306 $GLOBALS['log']->fatal("SUGARBEAN: cannot find Real Key for custom field of type dropdown - cannot return Value.");
5311 return $this->custom_fields->avail_fields[$name][$realKey];
5315 function bean_implements($interface)
5320 * Check whether the user has access to a particular view for the current bean/module
5321 * @param $view string required, the view to determine access for i.e. DetailView, ListView...
5322 * @param $is_owner bool optional, this is part of the ACL check if the current user is an owner they will receive different access
5324 function ACLAccess($view,$is_owner='not_set')
5326 global $current_user;
5327 if($current_user->isAdmin()) {
5331 if($is_owner == 'not_set')
5334 $is_owner = $this->isOwner($current_user->id);
5337 // If we don't implement ACLs, return true.
5338 if(!$this->bean_implements('ACL'))
5340 $view = strtolower($view);
5346 return ACLController::checkAccess($this->module_dir,'list', true);
5349 if( !$is_owner && $not_set && !empty($this->id)){
5350 $class = get_class($this);
5351 $temp = new $class();
5352 if(!empty($this->fetched_row) && !empty($this->fetched_row['id']) && !empty($this->fetched_row['assigned_user_id']) && !empty($this->fetched_row['created_by'])){
5353 $temp->populateFromRow($this->fetched_row);
5355 $temp->retrieve($this->id);
5357 $is_owner = $temp->isOwner($current_user->id);
5359 case 'popupeditview':
5361 return ACLController::checkAccess($this->module_dir,'edit', $is_owner, $this->acltype);
5365 return ACLController::checkAccess($this->module_dir,'view', $is_owner, $this->acltype);
5367 return ACLController::checkAccess($this->module_dir,'delete', $is_owner, $this->acltype);
5369 return ACLController::checkAccess($this->module_dir,'export', $is_owner, $this->acltype);
5371 return ACLController::checkAccess($this->module_dir,'import', true, $this->acltype);
5373 //if it is not one of the above views then it should be implemented on the page level
5382 function getOwnerField($returnFieldName = false)
5384 if (isset($this->field_defs['assigned_user_id']))
5386 return $returnFieldName? 'assigned_user_id': $this->assigned_user_id;
5389 if (isset($this->field_defs['created_by']))
5391 return $returnFieldName? 'created_by': $this->created_by;
5398 * Returns true of false if the user_id passed is the owner
5400 * @param GUID $user_id
5403 function isOwner($user_id)
5405 //if we don't have an id we must be the owner as we are creating it
5406 if(!isset($this->id))
5410 //if there is an assigned_user that is the owner
5411 if (!empty($this->fetched_row['assigned_user_id'])) {
5412 if ($this->fetched_row['assigned_user_id'] == $user_id) {
5416 } elseif (isset($this->assigned_user_id)) {
5417 if($this->assigned_user_id == $user_id) return true;
5422 //other wise if there is a created_by that is the owner
5423 if(isset($this->created_by) && $this->created_by == $user_id)
5431 * Gets there where statement for checking if a user is an owner
5433 * @param GUID $user_id
5436 function getOwnerWhere($user_id)
5438 if(isset($this->field_defs['assigned_user_id']))
5440 return " $this->table_name.assigned_user_id ='$user_id' ";
5442 if(isset($this->field_defs['created_by']))
5444 return " $this->table_name.created_by ='$user_id' ";
5451 * Used in order to manage ListView links and if they should
5452 * links or not based on the ACL permissions of the user
5454 * @return ARRAY of STRINGS
5456 function listviewACLHelper()
5458 $array_assign = array();
5459 if($this->ACLAccess('DetailView'))
5461 $array_assign['MAIN'] = 'a';
5465 $array_assign['MAIN'] = 'span';
5467 return $array_assign;
5471 * returns this bean as an array
5473 * @return array of fields with id, name, access and category
5475 function toArray($dbOnly = false, $stringOnly = false, $upperKeys=false)
5477 static $cache = array();
5480 foreach($this->field_defs as $field=>$data)
5482 if( !$dbOnly || !isset($data['source']) || $data['source'] == 'db')
5483 if(!$stringOnly || is_string($this->$field))
5486 if(!isset($cache[$field])){
5487 $cache[$field] = strtoupper($field);
5489 $arr[$cache[$field]] = $this->$field;
5493 if(isset($this->$field)){
5494 $arr[$field] = $this->$field;
5504 * Converts an array into an acl mapping name value pairs into files
5508 function fromArray($arr)
5510 foreach($arr as $name=>$value)
5512 $this->$name = $value;
5517 * Convert row data from DB format to internal format
5518 * Mostly useful for dates/times
5520 * @return array $row
5522 public function convertRow($row)
5524 foreach($this->field_defs as $name => $fieldDef)
5526 // skip empty fields and non-db fields
5527 if (isset($name) && !empty($row[$name])) {
5528 $row[$name] = $this->convertField($row[$name], $fieldDef);
5535 * Converts the field value based on the provided fieldDef
5536 * @param $fieldvalue
5540 public function convertField($fieldvalue, $fieldDef)
5542 if (!empty($fieldvalue)) {
5543 if (!(isset($fieldDef['source']) &&
5544 !in_array($fieldDef['source'], array('db', 'custom_fields', 'relate'))
5545 && !isset($fieldDef['dbType']))
5547 // fromConvert other fields
5548 $fieldvalue = $this->db->fromConvert($fieldvalue, $this->db->getFieldType($fieldDef));
5555 * Loads a row of data into instance of a bean. The data is passed as an array to this function
5557 * @param array $arr row of data fetched from the database.
5560 * Internal function do not override.
5562 function loadFromRow($arr)
5564 $this->populateFromRow($arr);
5565 $this->processed_dates_times = array();
5566 $this->check_date_relationships_load();
5568 $this->fill_in_additional_list_fields();
5570 if($this->hasCustomFields())$this->custom_fields->fill_relationships();
5571 $this->call_custom_logic("process_record");
5574 function hasCustomFields()
5576 return !empty($GLOBALS['dictionary'][$this->object_name]['custom_fields']);
5580 * Ensure that fields within order by clauses are properly qualified with
5581 * their tablename. This qualification is a requirement for sql server support.
5583 * @param string $order_by original order by from the query
5584 * @param string $qualify prefix for columns in the order by list.
5587 * Internal function do not override.
5589 function create_qualified_order_by( $order_by, $qualify)
5590 { // 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
5591 if (empty($order_by))
5595 $order_by_clause = " ORDER BY ";
5596 $tmp = explode(",", $order_by);
5598 foreach ( $tmp as $stmp)
5600 $stmp = (substr_count($stmp, ".") > 0?trim($stmp):"$qualify." . trim($stmp));
5601 $order_by_clause .= $comma . $stmp;
5604 return $order_by_clause;
5608 * Combined the contents of street field 2 thru 4 into the main field
5610 * @param string $street_field
5613 function add_address_streets(
5617 if (isset($this->$street_field)) {
5618 $street_field_2 = $street_field.'_2';
5619 $street_field_3 = $street_field.'_3';
5620 $street_field_4 = $street_field.'_4';
5621 if ( isset($this->$street_field_2)) {
5622 $this->$street_field .= "\n". $this->$street_field_2;
5623 unset($this->$street_field_2);
5625 if ( isset($this->$street_field_3)) {
5626 $this->$street_field .= "\n". $this->$street_field_3;
5627 unset($this->$street_field_3);
5629 if ( isset($this->$street_field_4)) {
5630 $this->$street_field .= "\n". $this->$street_field_4;
5631 unset($this->$street_field_4);
5633 $this->$street_field = trim($this->$street_field, "\n");
5637 protected function getEncryptKey()
5639 if(empty(self::$field_key)) {
5640 self::$field_key = blowfishGetKey('encrypt_field');
5642 return self::$field_key;
5646 * Encrpyt and base64 encode an 'encrypt' field type in the bean using Blowfish. The default system key is stored in cache/Blowfish/{keytype}
5647 * @param STRING value -plain text value of the bean field.
5650 function encrpyt_before_save($value)
5652 require_once("include/utils/encryption_utils.php");
5653 return blowfishEncode($this->getEncryptKey(), $value);
5657 * Decode and decrypt a base 64 encoded string with field type 'encrypt' in this bean using Blowfish.
5658 * @param STRING value - an encrypted and base 64 encoded string.
5661 function decrypt_after_retrieve($value)
5663 if(empty($value)) return $value; // no need to decrypt empty
5664 require_once("include/utils/encryption_utils.php");
5665 return blowfishDecode($this->getEncryptKey(), $value);
5669 * Moved from save() method, functionality is the same, but this is intended to handle
5670 * Optimistic locking functionality.
5672 private function _checkOptimisticLocking($action, $isUpdate){
5673 if($this->optimistic_lock && !isset($_SESSION['o_lock_fs'])){
5674 if(isset($_SESSION['o_lock_id']) && $_SESSION['o_lock_id'] == $this->id && $_SESSION['o_lock_on'] == $this->object_name)
5676 if($action == 'Save' && $isUpdate && isset($this->modified_user_id) && $this->has_been_modified_since($_SESSION['o_lock_dm'], $this->modified_user_id))
5678 $_SESSION['o_lock_class'] = get_class($this);
5679 $_SESSION['o_lock_module'] = $this->module_dir;
5680 $_SESSION['o_lock_object'] = $this->toArray();
5681 $saveform = "<form name='save' id='save' method='POST'>";
5682 foreach($_POST as $key=>$arg)
5684 $saveform .= "<input type='hidden' name='". addslashes($key) ."' value='". addslashes($arg) ."'>";
5686 $saveform .= "</form><script>document.getElementById('save').submit();</script>";
5687 $_SESSION['o_lock_save'] = $saveform;
5688 header('Location: index.php?module=OptimisticLock&action=LockResolve');
5693 unset ($_SESSION['o_lock_object']);
5694 unset ($_SESSION['o_lock_id']);
5695 unset ($_SESSION['o_lock_dm']);
5701 if(isset($_SESSION['o_lock_object'])) { unset ($_SESSION['o_lock_object']); }
5702 if(isset($_SESSION['o_lock_id'])) { unset ($_SESSION['o_lock_id']); }
5703 if(isset($_SESSION['o_lock_dm'])) { unset ($_SESSION['o_lock_dm']); }
5704 if(isset($_SESSION['o_lock_fs'])) { unset ($_SESSION['o_lock_fs']); }
5705 if(isset($_SESSION['o_lock_save'])) { unset ($_SESSION['o_lock_save']); }
5710 * Send assignment notifications and invites for meetings and calls
5712 private function _sendNotifications($check_notify){
5713 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.
5714 && !$this->isOwner($this->created_by) ) // cn: bug 42727 no need to send email to owner (within workflow)
5716 $admin = new Administration();
5717 $admin->retrieveSettings();
5718 $sendNotifications = false;
5720 if ($admin->settings['notify_on'])
5722 $GLOBALS['log']->info("Notifications: user assignment has changed, checking if user receives notifications");
5723 $sendNotifications = true;
5725 elseif(isset($_REQUEST['send_invites']) && $_REQUEST['send_invites'] == 1)
5727 // cn: bug 5795 Send Invites failing for Contacts
5728 $sendNotifications = true;
5732 $GLOBALS['log']->info("Notifications: not sending e-mail, notify_on is set to OFF");
5736 if($sendNotifications == true)
5738 $notify_list = $this->get_notification_recipients();
5739 foreach ($notify_list as $notify_user)
5741 $this->send_assignment_notifications($notify_user, $admin);
5749 * Called from ImportFieldSanitize::relate(), when creating a new bean in a related module. Will
5750 * copies fields over from the current bean into the related. Designed to be overriden in child classes.
5752 * @param SugarBean $newbean newly created related bean
5754 public function populateRelatedBean(
5761 * Called during the import process before a bean save, to handle any needed pre-save logic when
5762 * importing a record
5764 public function beforeImportSave()
5769 * Called during the import process after a bean save, to handle any needed post-save logic when
5770 * importing a record
5772 public function afterImportSave()
5777 * This function is designed to cache references to field arrays that were previously stored in the
5778 * bean files and have since been moved to separate files. Was previously in include/CacheHandler.php
5781 * @param $module_dir string the module directory
5782 * @param $module string the name of the module
5783 * @param $key string the type of field array we are referencing, i.e. list_fields, column_fields, required_fields
5785 private function _loadCachedArray(
5791 static $moduleDefs = array();
5793 $fileName = 'field_arrays.php';
5795 $cache_key = "load_cached_array.$module_dir.$module.$key";
5796 $result = sugar_cache_retrieve($cache_key);
5799 // Use SugarCache::EXTERNAL_CACHE_NULL_VALUE to store null values in the cache.
5800 if($result == SugarCache::EXTERNAL_CACHE_NULL_VALUE)
5808 if(file_exists('modules/'.$module_dir.'/'.$fileName))
5810 // If the data was not loaded, try loading again....
5811 if(!isset($moduleDefs[$module]))
5813 include('modules/'.$module_dir.'/'.$fileName);
5814 $moduleDefs[$module] = $fields_array;
5816 // Now that we have tried loading, make sure it was loaded
5817 if(empty($moduleDefs[$module]) || empty($moduleDefs[$module][$module][$key]))
5819 // It was not loaded.... Fail. Cache null to prevent future repeats of this calculation
5820 sugar_cache_put($cache_key, SugarCache::EXTERNAL_CACHE_NULL_VALUE);
5824 // It has been loaded, cache the result.
5825 sugar_cache_put($cache_key, $moduleDefs[$module][$module][$key]);
5826 return $moduleDefs[$module][$module][$key];
5829 // It was not loaded.... Fail. Cache null to prevent future repeats of this calculation
5830 sugar_cache_put($cache_key, SugarCache::EXTERNAL_CACHE_NULL_VALUE);
5835 * Returns the ACL category for this module; defaults to the SugarBean::$acl_category if defined
5836 * otherwise it is SugarBean::$module_dir
5840 public function getACLCategory()
5842 return !empty($this->acl_category)?$this->acl_category:$this->module_dir;
5846 * Returns the query used for the export functionality for a module. Override this method if you wish
5847 * to have a custom query to pull this data together instead
5849 * @param string $order_by
5850 * @param string $where
5851 * @return string SQL query
5853 public function create_export_query($order_by, $where)
5855 return $this->create_new_list_query($order_by, $where, array(), array(), 0, '', false, $this, true, true);
5859 * Determine whether the given field is a relate field
5861 * @param string $field Field name
5864 protected function is_relate_field($field)
5866 if (!isset($this->field_defs[$field]))
5871 $field_def = $this->field_defs[$field];
5873 return isset($field_def['type'])
5874 && $field_def['type'] == 'relate'
5875 && isset($field_def['link']);
5879 * Proxy method for DynamicField::getJOIN
5880 * @param bool $expandedList
5881 * @param bool $includeRelates
5882 * @param string|bool $where
5885 public function getCustomJoin($expandedList = false, $includeRelates = false, &$where = false)
5891 if(isset($this->custom_fields))
5893 $result = $this->custom_fields->getJOIN($expandedList, $includeRelates, $where);
5899 * Populates currency fields in case of currency is default and it's
5900 * attributes are not retrieved from database (bugs ##44206, 52438)
5902 protected function populateCurrencyFields()
5904 if (property_exists($this, 'currency_id') && $this->currency_id == -99) {
5905 // manually retrieve default currency object as long as it's
5906 // not stored in database and thus cannot be joined in query
5907 $currency = BeanFactory::getBean('Currencies', $this->currency_id);
5910 // walk through all currency-related fields
5911 foreach ($this->field_defs as $this_field) {
5912 if (isset($this_field['type']) && $this_field['type'] == 'relate'
5913 && isset($this_field['module']) && $this_field['module'] == 'Currencies'
5914 && isset($this_field['id_name']) && $this_field['id_name'] == 'currency_id') {
5915 // populate related properties manually
5916 $this_property = $this_field['name'];
5917 $currency_property = $this_field['rname'];
5918 $this->$this_property = $currency->$currency_property;
5926 * Checks if Bean has email defs
5930 public function hasEmails()
5932 if (!empty($this->field_defs['email_addresses']) && $this->field_defs['email_addresses']['type'] == 'link' &&
5933 !empty($this->field_defs['email_addresses_non_primary']) && $this->field_defs['email_addresses_non_primary']['type'] == 'email')