2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4 * SugarCRM Community Edition is a customer relationship management program developed by
5 * SugarCRM, Inc. Copyright (C) 2004-2012 SugarCRM Inc.
7 * This program is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU Affero General Public License version 3 as published by the
9 * Free Software Foundation with the addition of the following permission added
10 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11 * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
12 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
14 * This program is distributed in the hope that it will be useful, but WITHOUT
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
19 * You should have received a copy of the GNU Affero General Public License along with
20 * this program; if not, see http://www.gnu.org/licenses or write to the Free
21 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
24 * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
25 * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
27 * The interactive user interfaces in modified source and object code versions
28 * of this program must display Appropriate Legal Notices, as required under
29 * Section 5 of the GNU Affero General Public License version 3.
31 * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32 * these Appropriate Legal Notices must retain the display of the "Powered by
33 * SugarCRM" logo. If the display of the logo is not reasonably feasible for
34 * technical reasons, the Appropriate Legal Notices must display the words
35 * "Powered by SugarCRM".
36 ********************************************************************************/
38 /*********************************************************************************
40 * Description: Defines the base class for all data entities used throughout the
41 * application. The base class including its methods and variables is designed to
42 * be overloaded with module-specific methods and variables particular to the
43 * module's base entity class.
44 * Portions created by SugarCRM are Copyright (C) SugarCRM, Inc.
45 * All Rights Reserved.
46 *******************************************************************************/
48 require_once('modules/DynamicFields/DynamicField.php');
49 require_once("data/Relationships/RelationshipFactory.php");
56 * SugarBean is the base class for all business objects in Sugar. It implements
57 * the primary functionality needed for manipulating business objects: create,
58 * retrieve, update, delete. It allows for searching and retrieving list of records.
59 * It allows for retrieving related objects (e.g. contacts related to a specific account).
61 * In the current implementation, there can only be one bean per folder.
62 * Naming convention has the bean name be the same as the module and folder name.
63 * All bean names should be singular (e.g. Contact). The primary table name for
64 * a bean should be plural (e.g. contacts).
70 * A pointer to the database object
77 * When createing a bean, you can specify a value in the id column as
78 * long as that value is unique. During save, if the system finds an
79 * id, it assumes it is an update. Setting new_with_id to true will
80 * make sure the system performs an insert instead of an update.
82 * @var BOOL -- default false
84 var $new_with_id = false;
88 * How deep logic hooks can go
91 protected $max_logic_depth = 10;
94 * Disble vardefs. This should be set to true only for beans that do not have varders. Tracker is an example
96 * @var BOOL -- default false
98 var $disable_vardefs = false;
102 * holds the full name of the user that an item is assigned to. Only used if notifications
103 * are turned on and going to be sent out.
107 var $new_assigned_user_name;
110 * An array of booleans. This array is cleared out when data is loaded.
111 * As date/times are converted, a "1" is placed under the key, the field is converted.
113 * @var Array of booleans
115 var $processed_dates_times = array();
118 * Whether to process date/time fields for storage in the database in GMT
122 var $process_save_dates =true;
125 * This signals to the bean that it is being saved in a mass mode.
126 * Examples of this kind of save are import and mass update.
127 * We turn off notificaitons of this is the case to make things more efficient.
131 var $save_from_post = true;
134 * When running a query on related items using the method: retrieve_by_string_fields
135 * this value will be set to true if more than one item matches the search criteria.
139 var $duplicates_found = false;
142 * true if this bean has been deleted, false otherwise.
149 * Should the date modified column of the bean be updated during save?
150 * This is used for admin level functionality that should not be updating
151 * the date modified. This is only used by sync to allow for updates to be
152 * replicated in a way that will not cause them to be replicated back.
156 var $update_date_modified = true;
159 * Should the modified by column of the bean be updated during save?
160 * This is used for admin level functionality that should not be updating
161 * the modified by column. This is only used by sync to allow for updates to be
162 * replicated in a way that will not cause them to be replicated back.
166 var $update_modified_by = true;
169 * Setting this to true allows for updates to overwrite the date_entered
173 var $update_date_entered = false;
176 * This allows for seed data to be created without using the current uesr to set the id.
177 * This should be replaced by altering the current user before the call to save.
181 //TODO This should be replaced by altering the current user before the call to save.
182 var $set_created_by = true;
187 * The database table where records of this Bean are stored.
191 var $table_name = '';
194 * This is the singular name of the bean. (i.e. Contact).
198 var $object_name = '';
200 /** Set this to true if you query contains a sub-select and bean is converting both select statements
201 * into count queries.
203 var $ungreedy_count=false;
206 * The name of the module folder for this type of bean.
210 var $module_dir = '';
211 var $module_name = '';
215 var $column_fields = array();
216 var $list_fields = array();
217 var $additional_column_fields = array();
218 var $relationship_fields = array();
219 var $current_notify_user;
220 var $fetched_row=false;
222 var $force_load_details = false;
223 var $optimistic_lock = false;
224 var $disable_custom_fields = false;
225 var $number_formatting_done = false;
226 var $process_field_encrypted=false;
228 * The default ACL type
230 var $acltype = 'module';
233 var $additional_meta_fields = array();
236 * Set to true in the child beans if the module supports importing
238 var $importable = false;
241 * Set to true in the child beans if the module use the special notification template
243 var $special_notification = false;
246 * Set to true if the bean is being dealt with in a workflow
248 var $in_workflow = false;
252 * By default it will be true but if any module is to be kept non visible
253 * to tracker, then its value needs to be overriden in that particular module to false.
256 var $tracker_visibility = true;
259 * Used to pass inner join string to ListView Data.
261 var $listview_inner_join = array();
264 * Set to true in <modules>/Import/views/view.step4.php if a module is being imported
266 var $in_import = false;
268 * A way to keep track of the loaded relationships so when we clone the object we can unset them.
272 protected $loaded_relationships = array();
275 * set to true if dependent fields updated
277 protected $is_updated_dependent_fields = false;
280 * Constructor for the bean, it performs following tasks:
282 * 1. Initalized a database connections
283 * 2. Load the vardefs for the module implemeting the class. cache the entries
285 * 3. Setup row-level security preference
286 * All implementing classes must call this constructor using the parent::SugarBean() class.
291 global $dictionary, $current_user;
292 static $loaded_defs = array();
293 $this->db = DBManagerFactory::getInstance();
294 if (empty($this->module_name))
295 $this->module_name = $this->module_dir;
296 if((false == $this->disable_vardefs && empty($loaded_defs[$this->object_name])) || !empty($GLOBALS['reload_vardefs']))
298 VardefManager::loadVardef($this->module_dir, $this->object_name);
300 // build $this->column_fields from the field_defs if they exist
301 if (!empty($dictionary[$this->object_name]['fields'])) {
302 foreach ($dictionary[$this->object_name]['fields'] as $key=>$value_array) {
303 $column_fields[] = $key;
304 if(!empty($value_array['required']) && !empty($value_array['name'])) {
305 $this->required_fields[$value_array['name']] = 1;
308 $this->column_fields = $column_fields;
311 //setup custom fields
312 if(!isset($this->custom_fields) &&
313 empty($this->disable_custom_fields))
315 $this->setupCustomFields($this->module_dir);
317 //load up field_arrays from CacheHandler;
318 if(empty($this->list_fields))
319 $this->list_fields = $this->_loadCachedArray($this->module_dir, $this->object_name, 'list_fields');
320 if(empty($this->column_fields))
321 $this->column_fields = $this->_loadCachedArray($this->module_dir, $this->object_name, 'column_fields');
322 if(empty($this->required_fields))
323 $this->required_fields = $this->_loadCachedArray($this->module_dir, $this->object_name, 'required_fields');
325 if(isset($GLOBALS['dictionary'][$this->object_name]) && !$this->disable_vardefs)
327 $this->field_name_map = $dictionary[$this->object_name]['fields'];
328 $this->field_defs = $dictionary[$this->object_name]['fields'];
330 if(!empty($dictionary[$this->object_name]['optimistic_locking']))
332 $this->optimistic_lock=true;
335 $loaded_defs[$this->object_name]['column_fields'] =& $this->column_fields;
336 $loaded_defs[$this->object_name]['list_fields'] =& $this->list_fields;
337 $loaded_defs[$this->object_name]['required_fields'] =& $this->required_fields;
338 $loaded_defs[$this->object_name]['field_name_map'] =& $this->field_name_map;
339 $loaded_defs[$this->object_name]['field_defs'] =& $this->field_defs;
343 $this->column_fields =& $loaded_defs[$this->object_name]['column_fields'] ;
344 $this->list_fields =& $loaded_defs[$this->object_name]['list_fields'];
345 $this->required_fields =& $loaded_defs[$this->object_name]['required_fields'];
346 $this->field_name_map =& $loaded_defs[$this->object_name]['field_name_map'];
347 $this->field_defs =& $loaded_defs[$this->object_name]['field_defs'];
348 $this->added_custom_field_defs = true;
350 if(!isset($this->custom_fields) &&
351 empty($this->disable_custom_fields))
353 $this->setupCustomFields($this->module_dir, false);
355 if(!empty($dictionary[$this->object_name]['optimistic_locking']))
357 $this->optimistic_lock=true;
361 if($this->bean_implements('ACL') && !empty($GLOBALS['current_user'])){
362 $this->acl_fields = (isset($dictionary[$this->object_name]['acl_fields']) && $dictionary[$this->object_name]['acl_fields'] === false)?false:true;
364 $this->populateDefaultValues();
369 * Returns the object name. If object_name is not set, table_name is returned.
371 * All implementing classes must set a value for the object_name variable.
373 * @param array $arr row of data fetched from the database.
377 function getObjectName()
379 if ($this->object_name)
380 return $this->object_name;
382 // This is a quick way out. The generated metadata files have the table name
383 // as the key. The correct way to do this is to override this function
384 // in bean and return the object name. That requires changing all the beans
385 // as well as put the object name in the generator.
386 return $this->table_name;
390 * Returns a list of fields with their definitions that have the audited property set to true.
391 * Before calling this function, check whether audit has been enabled for the table/module or not.
392 * You would set the audit flag in the implemting module's vardef file.
394 * @return an array of
395 * @see is_AuditEnabled
397 * Internal function, do not override.
399 function getAuditEnabledFieldDefinitions()
401 $aclcheck = $this->bean_implements('ACL');
402 $is_owner = $this->isOwner($GLOBALS['current_user']->id);
403 if (!isset($this->audit_enabled_fields))
406 $this->audit_enabled_fields=array();
407 foreach ($this->field_defs as $field => $properties)
412 !empty($properties['Audited']) || !empty($properties['audited']))
416 $this->audit_enabled_fields[$field]=$properties;
421 return $this->audit_enabled_fields;
425 * Return true if auditing is enabled for this object
426 * You would set the audit flag in the implemting module's vardef file.
430 * Internal function, do not override.
432 function is_AuditEnabled()
435 if (isset($dictionary[$this->getObjectName()]['audited']))
437 return $dictionary[$this->getObjectName()]['audited'];
448 * Returns the name of the audit table.
449 * Audit table's name is based on implementing class' table name.
451 * @return String Audit table name.
453 * Internal function, do not override.
455 function get_audit_table_name()
457 return $this->getTableName().'_audit';
461 * Returns the name of the custom table.
462 * Custom table's name is based on implementing class' table name.
464 * @return String Custom table name.
466 * Internal function, do not override.
468 public function get_custom_table_name()
470 return $this->getTableName().'_cstm';
474 * If auditing is enabled, create the audit table.
476 * Function is used by the install scripts and a repair utility in the admin panel.
478 * Internal function, do not override.
480 function create_audit_table()
483 $table_name=$this->get_audit_table_name();
485 require('metadata/audit_templateMetaData.php');
487 // Bug: 52583 Need ability to customize template for audit tables
488 $custom = 'custom/metadata/audit_templateMetaData_' . $this->getTableName() . '.php';
489 if (file_exists($custom))
494 $fieldDefs = $dictionary['audit']['fields'];
495 $indices = $dictionary['audit']['indices'];
497 // Renaming template indexes to fit the particular audit table (removed the brittle hard coding)
498 foreach($indices as $nr => $properties){
499 $indices[$nr]['name'] = 'idx_' . strtolower($this->getTableName()) . '_' . $properties['name'];
503 if(isset($dictionary['audit']['engine'])) {
504 $engine = $dictionary['audit']['engine'];
505 } else if(isset($dictionary[$this->getObjectName()]['engine'])) {
506 $engine = $dictionary[$this->getObjectName()]['engine'];
509 $this->db->createTableParams($table_name, $fieldDefs, $indices, $engine);
513 * Returns the implementing class' table name.
515 * All implementing classes set a value for the table_name variable. This value is returned as the
516 * table name. If not set, table name is extracted from the implementing module's vardef.
518 * @return String Table name.
520 * Internal function, do not override.
522 public function getTableName()
524 if(isset($this->table_name))
526 return $this->table_name;
529 return $dictionary[$this->getObjectName()]['table'];
533 * Returns field definitions for the implementing module.
535 * The definitions were loaded in the constructor.
537 * @return Array Field definitions.
539 * Internal function, do not override.
541 function getFieldDefinitions()
543 return $this->field_defs;
547 * Returns index definitions for the implementing module.
549 * The definitions were loaded in the constructor.
551 * @return Array Index definitions.
553 * Internal function, do not override.
555 function getIndices()
558 if(isset($dictionary[$this->getObjectName()]['indices']))
560 return $dictionary[$this->getObjectName()]['indices'];
566 * Returns field definition for the requested field name.
568 * The definitions were loaded in the constructor.
570 * @param string field name,
571 * @return Array Field properties or boolean false if the field doesn't exist
573 * Internal function, do not override.
575 function getFieldDefinition($name)
577 if ( !isset($this->field_defs[$name]) )
580 return $this->field_defs[$name];
584 * Returnss definition for the id field name.
586 * The definitions were loaded in the constructor.
588 * @return Array Field properties.
590 * Internal function, do not override.
592 function getPrimaryFieldDefinition()
594 $def = $this->getFieldDefinition("id");
596 $def = $this->getFieldDefinition(0);
599 $defs = $this->field_defs;
601 $def = current($defs);
606 * Returns the value for the requested field.
608 * When a row of data is fetched using the bean, all fields are created as variables in the context
609 * of the bean and then fetched values are set in these variables.
611 * @param string field name,
612 * @return varies Field value.
614 * Internal function, do not override.
616 function getFieldValue($name)
618 if (!isset($this->$name)){
621 if($this->$name === TRUE){
624 if($this->$name === FALSE){
631 * Basically undoes the effects of SugarBean::populateDefaultValues(); this method is best called right after object
634 public function unPopulateDefaultValues()
636 if ( !is_array($this->field_defs) )
639 foreach ($this->field_defs as $field => $value) {
640 if( !empty($this->$field)
641 && ((isset($value['default']) && $this->$field == $value['default']) || (!empty($value['display_default']) && $this->$field == $value['display_default']))
643 $this->$field = null;
646 if(!empty($this->$field) && !empty($value['display_default']) && in_array($value['type'], array('date', 'datetime', 'datetimecombo')) &&
647 $this->$field == $this->parseDateDefault($value['display_default'], ($value['type'] != 'date'))) {
648 $this->$field = null;
654 * Create date string from default value
656 * @param string $value
657 * @param bool $time Should be expect time set too?
660 protected function parseDateDefault($value, $time = false)
664 $dtAry = explode('&', $value, 2);
665 $dateValue = $timedate->getNow(true)->modify($dtAry[0]);
666 if(!empty($dtAry[1])) {
667 $timeValue = $timedate->fromString($dtAry[1]);
668 $dateValue->setTime($timeValue->hour, $timeValue->min, $timeValue->sec);
670 return $timedate->asUser($dateValue);
672 return $timedate->asUserDate($timedate->getNow(true)->modify($value));
676 function populateDefaultValues($force=false){
677 if ( !is_array($this->field_defs) )
679 foreach($this->field_defs as $field=>$value){
680 if((isset($value['default']) || !empty($value['display_default'])) && ($force || empty($this->$field))){
681 $type = $value['type'];
685 if(!empty($value['display_default'])){
686 $this->$field = $this->parseDateDefault($value['display_default']);
690 case 'datetimecombo':
691 if(!empty($value['display_default'])){
692 $this->$field = $this->parseDateDefault($value['display_default'], true);
696 if(empty($value['default']) && !empty($value['display_default']))
697 $this->$field = $value['display_default'];
699 $this->$field = $value['default'];
702 if(isset($this->$field)){
706 if ( isset($value['default']) && $value['default'] !== '' ) {
707 $this->$field = htmlentities($value['default'], ENT_QUOTES, 'UTF-8');
718 * Removes relationship metadata cache.
720 * Every module that has relationships defined with other modules, has this meta data cached. The cache is
721 * stores in 2 locations: relationships table and file system. This method clears the cache from both locations.
723 * @param string $key module whose meta cache is to be cleared.
724 * @param string $db database handle.
725 * @param string $tablename table name
726 * @param string $dictionary vardef for the module
727 * @param string $module_dir name of subdirectory where module is installed.
732 * Internal function, do not override.
734 function removeRelationshipMeta($key,$db,$tablename,$dictionary,$module_dir)
736 //load the module dictionary if not supplied.
737 if ((!isset($dictionary) or empty($dictionary)) && !empty($module_dir))
739 $filename='modules/'. $module_dir . '/vardefs.php';
740 if(file_exists($filename))
745 if (!is_array($dictionary) or !array_key_exists($key, $dictionary))
747 $GLOBALS['log']->fatal("removeRelationshipMeta: Metadata for table ".$tablename. " does not exist");
748 display_notice("meta data absent for table ".$tablename." keyed to $key ");
752 if (isset($dictionary[$key]['relationships']))
754 $RelationshipDefs = $dictionary[$key]['relationships'];
755 foreach ($RelationshipDefs as $rel_name)
757 Relationship::delete($rel_name,$db);
765 * This method has been deprecated.
767 * @see removeRelationshipMeta()
768 * @deprecated 4.5.1 - Nov 14, 2006
771 function remove_relationship_meta($key,$db,$log,$tablename,$dictionary,$module_dir)
773 SugarBean::removeRelationshipMeta($key,$db,$tablename,$dictionary,$module_dir);
778 * Populates the relationship meta for a module.
780 * It is called during setup/install. It is used statically to create relationship meta data for many-to-many tables.
782 * @param string $key name of the object.
783 * @param object $db database handle.
784 * @param string $tablename table, meta data is being populated for.
785 * @param array dictionary vardef dictionary for the object. *
786 * @param string module_dir name of subdirectory where module is installed.
787 * @param boolean $iscustom Optional,set to true if module is installed in a custom directory. Default value is false.
790 * Internal function, do not override.
792 function createRelationshipMeta($key,$db,$tablename,$dictionary,$module_dir,$iscustom=false)
794 //load the module dictionary if not supplied.
795 if (empty($dictionary) && !empty($module_dir))
799 $filename='custom/modules/' . $module_dir . '/Ext/Vardefs/vardefs.ext.php';
805 // a very special case for the Employees module
806 // this must be done because the Employees/vardefs.php does an include_once on
808 $filename='modules/Users/vardefs.php';
812 $filename='modules/'. $module_dir . '/vardefs.php';
816 if(file_exists($filename))
819 // cn: bug 7679 - dictionary entries defined as $GLOBALS['name'] not found
820 if(empty($dictionary) || !empty($GLOBALS['dictionary'][$key]))
822 $dictionary = $GLOBALS['dictionary'];
827 $GLOBALS['log']->debug("createRelationshipMeta: no metadata file found" . $filename);
832 if (!is_array($dictionary) or !array_key_exists($key, $dictionary))
834 $GLOBALS['log']->fatal("createRelationshipMeta: Metadata for table ".$tablename. " does not exist");
835 display_notice("meta data absent for table ".$tablename." keyed to $key ");
839 if (isset($dictionary[$key]['relationships']))
842 $RelationshipDefs = $dictionary[$key]['relationships'];
846 $beanList_ucase=array_change_key_case ( $beanList ,CASE_UPPER);
847 foreach ($RelationshipDefs as $rel_name=>$rel_def)
849 if (isset($rel_def['lhs_module']) and !isset($beanList_ucase[strtoupper($rel_def['lhs_module'])])) {
850 $GLOBALS['log']->debug('skipping orphaned relationship record ' . $rel_name . ' lhs module is missing ' . $rel_def['lhs_module']);
853 if (isset($rel_def['rhs_module']) and !isset($beanList_ucase[strtoupper($rel_def['rhs_module'])])) {
854 $GLOBALS['log']->debug('skipping orphaned relationship record ' . $rel_name . ' rhs module is missing ' . $rel_def['rhs_module']);
859 //check whether relationship exists or not first.
860 if (Relationship::exists($rel_name,$db))
862 $GLOBALS['log']->debug('Skipping, reltionship already exists '.$rel_name);
866 $seed = BeanFactory::getBean("Relationships");
867 $keys = array_keys($seed->field_defs);
869 foreach($keys as $key)
873 $toInsert[$key] = create_guid();
875 else if ($key == "relationship_name")
877 $toInsert[$key] = $rel_name;
879 else if (isset($rel_def[$key]))
881 $toInsert[$key] = $rel_def[$key];
883 //todo specify defaults if meta not defined.
887 $column_list = implode(",", array_keys($toInsert));
888 $value_list = "'" . implode("','", array_values($toInsert)) . "'";
890 //create the record. todo add error check.
891 $insert_string = "INSERT into relationships (" .$column_list. ") values (".$value_list.")";
892 $db->query($insert_string, true);
899 //log informational message stating no relationships meta was set for this bean.
905 * This method has been deprecated.
906 * @see createRelationshipMeta()
907 * @deprecated 4.5.1 - Nov 14, 2006
910 function create_relationship_meta($key,&$db,&$log,$tablename,$dictionary,$module_dir)
912 SugarBean::createRelationshipMeta($key,$db,$tablename,$dictionary,$module_dir);
917 * Handle the following when a SugarBean object is cloned
919 * Currently all this does it unset any relationships that were created prior to cloning the object
923 public function __clone()
925 if(!empty($this->loaded_relationships)) {
926 foreach($this->loaded_relationships as $rel) {
934 * Loads the request relationship. This method should be called before performing any operations on the related data.
936 * This method searches the vardef array for the requested attribute's definition. If the attribute is of the type
937 * link then it creates a similary named variable and loads the relationship definition.
939 * @param string $rel_name relationship/attribute name.
942 function load_relationship($rel_name)
944 $GLOBALS['log']->debug("SugarBean[{$this->object_name}].load_relationships, Loading relationship (".$rel_name.").");
946 if (empty($rel_name))
948 $GLOBALS['log']->error("SugarBean.load_relationships, Null relationship name passed.");
951 $fieldDefs = $this->getFieldDefinitions();
953 //find all definitions of type link.
954 if (!empty($fieldDefs[$rel_name]))
956 //initialize a variable of type Link
957 require_once('data/Link2.php');
958 $class = load_link_class($fieldDefs[$rel_name]);
959 if (isset($this->$rel_name) && $this->$rel_name instanceof $class) {
962 //if rel_name is provided, search the fieldef array keys by name.
963 if (isset($fieldDefs[$rel_name]['type']) && $fieldDefs[$rel_name]['type'] == 'link')
965 if ($class == "Link2")
966 $this->$rel_name = new $class($rel_name, $this);
968 $this->$rel_name = new $class($fieldDefs[$rel_name]['relationship'], $this, $fieldDefs[$rel_name]);
970 if (empty($this->$rel_name) ||
971 (method_exists($this->$rel_name, "loadedSuccesfully") && !$this->$rel_name->loadedSuccesfully()))
973 unset($this->$rel_name);
976 // keep track of the loaded relationships
977 $this->loaded_relationships[] = $rel_name;
981 $GLOBALS['log']->debug("SugarBean.load_relationships, Error Loading relationship (".$rel_name.")");
986 * Loads all attributes of type link.
988 * DO NOT CALL THIS FUNCTION IF YOU CAN AVOID IT. Please use load_relationship directly instead.
990 * Method searches the implmenting module's vardef file for attributes of type link, and for each attribute
991 * create a similary named variable and load the relationship definition.
995 * Internal function, do not override.
997 function load_relationships()
999 $GLOBALS['log']->debug("SugarBean.load_relationships, Loading all relationships of type link.");
1000 $linked_fields=$this->get_linked_fields();
1001 foreach($linked_fields as $name=>$properties)
1003 $this->load_relationship($name);
1008 * Returns an array of beans of related data.
1010 * For instance, if an account is related to 10 contacts , this function will return an array of contacts beans (10)
1011 * with each bean representing a contact record.
1012 * Method will load the relationship if not done so already.
1014 * @param string $field_name relationship to be loaded.
1015 * @param string $bean name class name of the related bean.
1016 * @param array $sort_array optional, unused
1017 * @param int $begin_index Optional, default 0, unused.
1018 * @param int $end_index Optional, default -1
1019 * @param int $deleted Optional, Default 0, 0 adds deleted=0 filter, 1 adds deleted=1 filter.
1020 * @param string $optional_where, Optional, default empty.
1022 * Internal function, do not override.
1024 function get_linked_beans($field_name,$bean_name, $sort_array = array(), $begin_index = 0, $end_index = -1,
1025 $deleted=0, $optional_where="")
1027 //if bean_name is Case then use aCase
1028 if($bean_name=="Case")
1029 $bean_name = "aCase";
1031 if($this->load_relationship($field_name)) {
1032 if ($this->$field_name instanceof Link) {
1033 // some classes are still based on Link, e.g. TeamSetLink
1034 return array_values($this->$field_name->getBeans(new $bean_name(), $sort_array, $begin_index, $end_index, $deleted, $optional_where));
1037 if ($end_index != -1 || !empty($deleted) || !empty($optional_where))
1038 return array_values($this->$field_name->getBeans(array(
1039 'where' => $optional_where,
1040 'deleted' => $deleted,
1041 'limit' => ($end_index - $begin_index)
1044 return array_values($this->$field_name->getBeans());
1052 * Returns an array of fields that are of type link.
1054 * @return array List of fields.
1056 * Internal function, do not override.
1058 function get_linked_fields()
1061 $linked_fields=array();
1063 // require_once('data/Link.php');
1065 $fieldDefs = $this->getFieldDefinitions();
1067 //find all definitions of type link.
1068 if (!empty($fieldDefs))
1070 foreach ($fieldDefs as $name=>$properties)
1072 if (array_search('link',$properties) === 'type')
1074 $linked_fields[$name]=$properties;
1079 return $linked_fields;
1083 * Returns an array of fields that are able to be Imported into
1084 * i.e. 'importable' not set to 'false'
1086 * @return array List of fields.
1088 * Internal function, do not override.
1090 function get_importable_fields()
1092 $importableFields = array();
1094 $fieldDefs= $this->getFieldDefinitions();
1096 if (!empty($fieldDefs)) {
1097 foreach ($fieldDefs as $key=>$value_array) {
1098 if ( (isset($value_array['importable'])
1099 && (is_string($value_array['importable']) && $value_array['importable'] == 'false'
1100 || is_bool($value_array['importable']) && $value_array['importable'] == false))
1101 || (isset($value_array['type']) && $value_array['type'] == 'link')
1102 || (isset($value_array['auto_increment'])
1103 && ($value_array['type'] == true || $value_array['type'] == 'true')) ) {
1104 // only allow import if we force it
1105 if (isset($value_array['importable'])
1106 && (is_string($value_array['importable']) && $value_array['importable'] == 'true'
1107 || is_bool($value_array['importable']) && $value_array['importable'] == true)) {
1108 $importableFields[$key]=$value_array;
1112 $importableFields[$key]=$value_array;
1117 return $importableFields;
1121 * Returns an array of fields that are of type relate.
1123 * @return array List of fields.
1125 * Internal function, do not override.
1127 function get_related_fields()
1130 $related_fields=array();
1132 // require_once('data/Link.php');
1134 $fieldDefs = $this->getFieldDefinitions();
1136 //find all definitions of type link.
1137 if (!empty($fieldDefs))
1139 foreach ($fieldDefs as $name=>$properties)
1141 if (array_search('relate',$properties) === 'type')
1143 $related_fields[$name]=$properties;
1148 return $related_fields;
1152 * Returns an array of fields that are required for import
1156 function get_import_required_fields()
1158 $importable_fields = $this->get_importable_fields();
1159 $required_fields = array();
1161 foreach ( $importable_fields as $name => $properties ) {
1162 if ( isset($properties['importable']) && is_string($properties['importable']) && $properties['importable'] == 'required' ) {
1163 $required_fields[$name] = $properties;
1167 return $required_fields;
1171 * Iterates through all the relationships and deletes all records for reach relationship.
1173 * @param string $id Primary key value of the parent reocrd
1175 function delete_linked($id)
1177 $linked_fields=$this->get_linked_fields();
1178 foreach ($linked_fields as $name => $value)
1180 if ($this->load_relationship($name))
1182 $this->$name->delete($id);
1186 $GLOBALS['log']->fatal("error loading relationship $name");
1192 * Creates tables for the module implementing the class.
1193 * If you override this function make sure that your code can handles table creation.
1196 function create_tables()
1200 $key = $this->getObjectName();
1201 if (!array_key_exists($key, $dictionary))
1203 $GLOBALS['log']->fatal("create_tables: Metadata for table ".$this->table_name. " does not exist");
1204 display_notice("meta data absent for table ".$this->table_name." keyed to $key ");
1208 if(!$this->db->tableExists($this->table_name))
1210 $this->db->createTable($this);
1211 if($this->bean_implements('ACL')){
1212 if(!empty($this->acltype)){
1213 ACLAction::addActions($this->getACLCategory(), $this->acltype);
1215 ACLAction::addActions($this->getACLCategory());
1221 echo "Table already exists : $this->table_name<br>";
1223 if($this->is_AuditEnabled()){
1224 if (!$this->db->tableExists($this->get_audit_table_name())) {
1225 $this->create_audit_table();
1233 * Delete the primary table for the module implementing the class.
1234 * If custom fields were added to this table/module, the custom table will be removed too, along with the cache
1235 * entries that define the custom fields.
1238 function drop_tables()
1241 $key = $this->getObjectName();
1242 if (!array_key_exists($key, $dictionary))
1244 $GLOBALS['log']->fatal("drop_tables: Metadata for table ".$this->table_name. " does not exist");
1245 echo "meta data absent for table ".$this->table_name."<br>\n";
1247 if(empty($this->table_name))return;
1248 if ($this->db->tableExists($this->table_name))
1250 $this->db->dropTable($this);
1251 if ($this->db->tableExists($this->table_name. '_cstm'))
1253 $this->db->dropTableName($this->table_name. '_cstm');
1254 DynamicField::deleteCache();
1256 if ($this->db->tableExists($this->get_audit_table_name())) {
1257 $this->db->dropTableName($this->get_audit_table_name());
1266 * Loads the definition of custom fields defined for the module.
1267 * Local file system cache is created as needed.
1269 * @param string $module_name setting up custom fields for this module.
1270 * @param boolean $clean_load Optional, default true, rebuilds the cache if set to true.
1272 function setupCustomFields($module_name, $clean_load=true)
1274 $this->custom_fields = new DynamicField($module_name);
1275 $this->custom_fields->setup($this);
1280 * Cleans char, varchar, text, etc. fields of XSS type materials
1282 function cleanBean() {
1283 foreach($this->field_defs as $key => $def) {
1285 if (isset($def['type'])) {
1288 if(isset($def['dbType']))
1289 $type .= $def['dbType'];
1291 if($def['type'] == 'html') {
1292 $this->$key = SugarCleaner::cleanHtml($this->$key, true);
1293 } elseif((strpos($type, 'char') !== false ||
1294 strpos($type, 'text') !== false ||
1298 $this->$key = SugarCleaner::cleanHtml($this->$key);
1304 * Implements a generic insert and update logic for any SugarBean
1305 * This method only works for subclasses that implement the same variable names.
1306 * This method uses the presence of an id field that is not null to signify and update.
1307 * The id field should not be set otherwise.
1309 * @param boolean $check_notify Optional, default false, if set to true assignee of the record is notified via email.
1310 * @todo Add support for field type validation and encoding of parameters.
1312 function save($check_notify = FALSE)
1314 $this->in_save = true;
1315 // cn: SECURITY - strip XSS potential vectors
1317 // This is used so custom/3rd-party code can be upgraded with fewer issues, this will be removed in a future release
1318 $this->fixUpFormatting();
1320 global $current_user, $action;
1323 if(empty($this->id))
1328 if ( $this->new_with_id == true )
1332 if(empty($this->date_modified) || $this->update_date_modified)
1334 $this->date_modified = $GLOBALS['timedate']->nowDb();
1337 $this->_checkOptimisticLocking($action, $isUpdate);
1339 if(!empty($this->modified_by_name)) $this->old_modified_by_name = $this->modified_by_name;
1340 if($this->update_modified_by)
1342 $this->modified_user_id = 1;
1344 if (!empty($current_user))
1346 $this->modified_user_id = $current_user->id;
1347 $this->modified_by_name = $current_user->user_name;
1350 if ($this->deleted != 1)
1354 if (empty($this->date_entered))
1356 $this->date_entered = $this->date_modified;
1358 if($this->set_created_by == true)
1360 // created by should always be this user
1361 $this->created_by = (isset($current_user)) ? $current_user->id : "";
1363 if( $this->new_with_id == false)
1365 $this->id = create_guid();
1371 require_once("data/BeanFactory.php");
1372 BeanFactory::registerBean($this->module_name, $this);
1374 if (empty($GLOBALS['updating_relationships']) && empty($GLOBALS['saving_relationships']) && empty ($GLOBALS['resavingRelatedBeans']))
1376 $GLOBALS['saving_relationships'] = true;
1377 // let subclasses save related field changes
1378 $this->save_relationship_changes($isUpdate);
1379 $GLOBALS['saving_relationships'] = false;
1381 if($isUpdate && !$this->update_date_entered)
1383 unset($this->date_entered);
1385 // call the custom business logic
1386 $custom_logic_arguments['check_notify'] = $check_notify;
1389 $this->call_custom_logic("before_save", $custom_logic_arguments);
1390 unset($custom_logic_arguments);
1392 if(isset($this->custom_fields))
1394 $this->custom_fields->bean = $this;
1395 $this->custom_fields->save($isUpdate);
1398 // use the db independent query generator
1399 $this->preprocess_fields_on_save();
1401 //construct the SQL to create the audit record if auditing is enabled.
1402 $dataChanges=array();
1403 if ($this->is_AuditEnabled()) {
1404 if ($isUpdate && !isset($this->fetched_row)) {
1405 $GLOBALS['log']->debug('Auditing: Retrieve was not called, audit record will not be created.');
1407 $dataChanges=$this->db->getDataChanges($this);
1411 $this->_sendNotifications($check_notify);
1414 $this->db->update($this);
1416 $this->db->insert($this);
1419 if (!empty($dataChanges) && is_array($dataChanges))
1421 foreach ($dataChanges as $change)
1423 $this->db->save_audit_records($this,$change);
1428 if (empty($GLOBALS['resavingRelatedBeans'])){
1429 SugarRelationship::resaveRelatedBeans();
1433 //If we aren't in setup mode and we have a current user and module, then we track
1434 if(isset($GLOBALS['current_user']) && isset($this->module_dir))
1436 $this->track_view($current_user->id, $this->module_dir, 'save');
1439 $this->call_custom_logic('after_save', '');
1441 //Now that the record has been saved, we don't want to insert again on further saves
1442 $this->new_with_id = false;
1443 $this->in_save = false;
1449 * Performs a check if the record has been modified since the specified date
1451 * @param date $date Datetime for verification
1452 * @param string $modified_user_id User modified by
1454 function has_been_modified_since($date, $modified_user_id)
1456 global $current_user;
1457 $date = $this->db->convert($this->db->quoted($date), 'datetime');
1458 if (isset($current_user))
1460 $query = "SELECT date_modified FROM $this->table_name WHERE id='$this->id' AND modified_user_id != '$current_user->id'
1461 AND (modified_user_id != '$modified_user_id' OR date_modified > $date)";
1462 $result = $this->db->query($query);
1464 if($this->db->fetchByAssoc($result))
1473 * Determines which users receive a notification
1475 function get_notification_recipients() {
1476 $notify_user = new User();
1477 $notify_user->retrieve($this->assigned_user_id);
1478 $this->new_assigned_user_name = $notify_user->full_name;
1480 $GLOBALS['log']->info("Notifications: recipient is $this->new_assigned_user_name");
1482 $user_list = array($notify_user);
1485 //send notifications to followers, but ensure to not query for the assigned_user.
1486 return SugarFollowing::getFollowers($this, $notify_user);
1491 * Handles sending out email notifications when items are first assigned to users
1493 * @param string $notify_user user to notify
1494 * @param string $admin the admin user that sends out the notification
1496 function send_assignment_notifications($notify_user, $admin)
1498 global $current_user;
1500 if(($this->object_name == 'Meeting' || $this->object_name == 'Call') || $notify_user->receive_notifications)
1502 $sendToEmail = $notify_user->emailAddress->getPrimaryAddress($notify_user);
1504 if(empty($sendToEmail)) {
1505 $GLOBALS['log']->warn("Notifications: no e-mail address set for user {$notify_user->user_name}, cancelling send");
1509 $notify_mail = $this->create_notification_email($notify_user);
1510 $notify_mail->setMailerForSystem();
1512 if(empty($admin->settings['notify_send_from_assigning_user'])) {
1513 $notify_mail->From = $admin->settings['notify_fromaddress'];
1514 $notify_mail->FromName = (empty($admin->settings['notify_fromname'])) ? "" : $admin->settings['notify_fromname'];
1516 // Send notifications from the current user's e-mail (if set)
1517 $fromAddress = $current_user->emailAddress->getReplyToAddress($current_user);
1518 $fromAddress = !empty($fromAddress) ? $fromAddress : $admin->settings['notify_fromaddress'];
1519 $notify_mail->From = $fromAddress;
1520 //Use the users full name is available otherwise default to system name
1521 $from_name = !empty($admin->settings['notify_fromname']) ? $admin->settings['notify_fromname'] : "";
1522 $from_name = !empty($current_user->full_name) ? $current_user->full_name : $from_name;
1523 $notify_mail->FromName = $from_name;
1526 $oe = new OutboundEmail();
1527 $oe = $oe->getUserMailerSettings($current_user);
1528 //only send if smtp server is defined
1530 $smtpVerified = false;
1532 //first check the user settings
1533 if(!empty($oe->mail_smtpserver)){
1534 $smtpVerified = true;
1537 //if still not verified, check against the system settings
1538 if (!$smtpVerified){
1539 $oe = $oe->getSystemMailerSettings();
1540 if(!empty($oe->mail_smtpserver)){
1541 $smtpVerified = true;
1544 //if smtp was not verified against user or system, then do not send out email
1545 if (!$smtpVerified){
1546 $GLOBALS['log']->fatal("Notifications: error sending e-mail, smtp server was not found ");
1551 if(!$notify_mail->Send()) {
1552 $GLOBALS['log']->fatal("Notifications: error sending e-mail (method: {$notify_mail->Mailer}), (error: {$notify_mail->ErrorInfo})");
1554 $GLOBALS['log']->info("Notifications: e-mail successfully sent");
1562 * This function handles create the email notifications email.
1563 * @param string $notify_user the user to send the notification email to
1565 function create_notification_email($notify_user) {
1566 global $sugar_version;
1567 global $sugar_config;
1568 global $app_list_strings;
1569 global $current_user;
1572 $OBCharset = $locale->getPrecedentPreference('default_email_charset');
1575 require_once("include/SugarPHPMailer.php");
1577 $notify_address = $notify_user->emailAddress->getPrimaryAddress($notify_user);
1578 $notify_name = $notify_user->full_name;
1579 $GLOBALS['log']->debug("Notifications: user has e-mail defined");
1581 $notify_mail = new SugarPHPMailer();
1582 $notify_mail->AddAddress($notify_address,$locale->translateCharsetMIME(trim($notify_name), 'UTF-8', $OBCharset));
1584 if(empty($_SESSION['authenticated_user_language'])) {
1585 $current_language = $sugar_config['default_language'];
1587 $current_language = $_SESSION['authenticated_user_language'];
1589 $xtpl = new XTemplate(get_notify_template_file($current_language));
1590 if($this->module_dir == "Cases") {
1591 $template_name = "Case"; //we should use Case, you can refer to the en_us.notify_template.html.
1594 $template_name = $beanList[$this->module_dir]; //bug 20637, in workflow this->object_name = strange chars.
1597 $this->current_notify_user = $notify_user;
1599 if(in_array('set_notification_body', get_class_methods($this))) {
1600 $xtpl = $this->set_notification_body($xtpl, $this);
1602 $xtpl->assign("OBJECT", translate('LBL_MODULE_NAME'));
1603 $template_name = "Default";
1605 if(!empty($_SESSION["special_notification"]) && $_SESSION["special_notification"]) {
1606 $template_name = $beanList[$this->module_dir].'Special';
1608 if($this->special_notification) {
1609 $template_name = $beanList[$this->module_dir].'Special';
1611 $xtpl->assign("ASSIGNED_USER", $this->new_assigned_user_name);
1612 $xtpl->assign("ASSIGNER", $current_user->name);
1615 if(isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) {
1616 $port = $_SERVER['SERVER_PORT'];
1619 if (!isset($_SERVER['HTTP_HOST'])) {
1620 $_SERVER['HTTP_HOST'] = '';
1623 $httpHost = $_SERVER['HTTP_HOST'];
1625 if($colon = strpos($httpHost, ':')) {
1626 $httpHost = substr($httpHost, 0, $colon);
1629 $parsedSiteUrl = parse_url($sugar_config['site_url']);
1630 $host = $parsedSiteUrl['host'];
1631 if(!isset($parsedSiteUrl['port'])) {
1632 $parsedSiteUrl['port'] = 80;
1635 $port = ($parsedSiteUrl['port'] != 80) ? ":".$parsedSiteUrl['port'] : '';
1636 $path = !empty($parsedSiteUrl['path']) ? $parsedSiteUrl['path'] : "";
1637 $cleanUrl = "{$parsedSiteUrl['scheme']}://{$host}{$port}{$path}";
1639 $xtpl->assign("URL", $cleanUrl."/index.php?module={$this->module_dir}&action=DetailView&record={$this->id}");
1640 $xtpl->assign("SUGAR", "Sugar v{$sugar_version}");
1641 $xtpl->parse($template_name);
1642 $xtpl->parse($template_name . "_Subject");
1644 $notify_mail->Body = from_html(trim($xtpl->text($template_name)));
1645 $notify_mail->Subject = from_html($xtpl->text($template_name . "_Subject"));
1647 // cn: bug 8568 encode notify email in User's outbound email encoding
1648 $notify_mail->prepForOutbound();
1650 return $notify_mail;
1654 * This function is a good location to save changes that have been made to a relationship.
1655 * This should be overriden in subclasses that have something to save.
1657 * @param $is_update true if this save is an update.
1659 function save_relationship_changes($is_update, $exclude=array())
1661 $new_rel_id = false;
1662 $new_rel_link = false;
1664 // check incoming data
1665 if(isset($this->not_use_rel_in_req) && $this->not_use_rel_in_req)
1667 // if we should use relation data from properties (for REQUEST-independent calls)
1668 $rel_id=isset($this->new_rel_id) ? $this->new_rel_id : '';
1669 $rel_link=isset($this->new_rel_relname) ? $this->new_rel_relname : '';
1673 // if we should use relation data from REQUEST
1674 $rel_id=isset($_REQUEST['relate_id']) ? $_REQUEST['relate_id'] : '';
1675 $rel_link=isset($_REQUEST['relate_to']) ? $_REQUEST['relate_to'] : '';
1678 // filter relation data
1679 if($rel_id && $rel_link && !in_array($rel_link, $exclude) && $rel_id != $this->id)
1681 $new_rel_id = $rel_id;
1682 $new_rel_link = $rel_link;
1683 // Bug #53223 : wrong relationship from subpanel create button
1684 // if LHSModule and RHSModule are same module use left link to add new item b/s of:
1685 // $rel_id and $rel_link are not emty - request is from subpanel
1686 // $rel_link contains relationship name - checked by call load_relationship
1687 $this->load_relationship($rel_link);
1688 if ( !empty($this->$rel_link) && $this->$rel_link->getRelationshipObject() && $this->$rel_link->getRelationshipObject()->getLHSModule() == $this->$rel_link->getRelationshipObject()->getRHSModule() )
1690 $new_rel_link = $this->$rel_link->getRelationshipObject()->getLHSLink();
1694 //Try to find the link in this bean based on the relationship
1695 foreach ($this->field_defs as $key => $def)
1697 if (isset($def['type']) && $def['type'] == 'link' && isset($def['relationship']) && $def['relationship'] == $rel_link)
1699 $new_rel_link = $key;
1706 // First we handle the preset fields listed in the fixed relationship_fields array hardcoded into the OOB beans
1707 // TODO: remove this mechanism and replace with mechanism exclusively based on the vardefs
1708 if (isset($this->relationship_fields) && is_array($this->relationship_fields))
1710 foreach ($this->relationship_fields as $id=>$rel_name)
1713 if(in_array($id, $exclude))continue;
1715 if(!empty($this->$id))
1717 // Bug #44930 We do not need to update main related field if it is changed from sub-panel.
1718 if ($rel_name == $new_rel_link && $this->$id != $new_rel_id)
1722 $GLOBALS['log']->debug('save_relationship_changes(): From relationship_field array - adding a relationship record: '.$rel_name . ' = ' . $this->$id);
1723 //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
1724 if($this->$id == $new_rel_id){
1725 $new_rel_id = false;
1727 $this->load_relationship($rel_name);
1728 $this->$rel_name->add($this->$id);
1733 //if before value is not empty then attempt to delete relationship
1734 if(!empty($this->rel_fields_before_value[$id]))
1736 $GLOBALS['log']->debug('save_relationship_changes(): From relationship_field array - attempting to remove the relationship record, using relationship attribute'.$rel_name);
1737 $this->load_relationship($rel_name);
1738 $this->$rel_name->delete($this->id,$this->rel_fields_before_value[$id]);
1744 /* Next, we'll attempt to update all of the remaining relate fields in the vardefs that have 'save' set in their field_def
1745 Only the 'save' fields should be saved as some vardef entries today are not for display only purposes and break the application if saved
1746 If the vardef has entries for field <a> of type relate, where a->id_name = <b> and field <b> of type link
1747 then we receive a value for b from the MVC in the _REQUEST, and it should be set in the bean as $this->$b
1750 foreach ( $this->field_defs as $def )
1752 if ($def [ 'type' ] == 'relate' && isset ( $def [ 'id_name'] ) && isset ( $def [ 'link'] ) && isset ( $def[ 'save' ]) )
1754 if ( in_array( $def['id_name'], $exclude) || in_array( $def['id_name'], $this->relationship_fields ) )
1755 continue ; // continue to honor the exclude array and exclude any relationships that will be handled by the relationship_fields mechanism
1757 $linkField = $def [ 'link' ] ;
1758 if (isset( $this->field_defs[$linkField ] ))
1760 $linkfield = $this->field_defs[$linkField] ;
1762 if ($this->load_relationship ( $linkField))
1764 $idName = $def['id_name'];
1766 if (!empty($this->rel_fields_before_value[$idName]) && empty($this->$idName))
1768 //if before value is not empty then attempt to delete relationship
1769 $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' ]]}");
1770 $this->$def ['link' ]->delete($this->id, $this->rel_fields_before_value[$def [ 'id_name' ]] );
1773 if (!empty($this->$idName) && is_string($this->$idName))
1775 $GLOBALS['log']->debug("save_relationship_changes(): From field_defs - attempting to add a relationship record - {$def [ 'link' ]} = {$this->$def [ 'id_name' ]}" );
1777 $this->$linkField->add($this->$idName);
1780 $GLOBALS['log']->fatal("Failed to load relationship {$linkField} while saving {$this->module_dir}");
1786 // Finally, we update a field listed in the _REQUEST['*/relate_id']/_REQUEST['relate_to'] mechanism (if it hasn't already been updated above)
1787 if(!empty($new_rel_id)){
1789 if($this->load_relationship($new_rel_link)){
1790 $this->$new_rel_link->add($new_rel_id);
1793 $lower_link = strtolower($new_rel_link);
1794 if($this->load_relationship($lower_link)){
1795 $this->$lower_link->add($new_rel_id);
1798 require_once('data/Link2.php');
1799 $rel = Relationship::retrieve_by_modules($new_rel_link, $this->module_dir, $GLOBALS['db'], 'many-to-many');
1802 foreach($this->field_defs as $field=>$def){
1803 if($def['type'] == 'link' && !empty($def['relationship']) && $def['relationship'] == $rel){
1804 $this->load_relationship($field);
1805 $this->$field->add($new_rel_id);
1811 //ok so we didn't find it in the field defs let's save it anyway if we have the relationshp
1813 $this->$rel=new Link2($rel, $this, array());
1814 $this->$rel->add($new_rel_id);
1823 * This function retrieves a record of the appropriate type from the DB.
1824 * It fills in all of the fields from the DB into the object it was called on.
1826 * @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.
1827 * @return this - The object that it was called apon or null if exactly 1 record was not found.
1831 function check_date_relationships_load()
1833 global $disable_date_format;
1835 if (empty($timedate))
1836 $timedate=TimeDate::getInstance();
1838 if(empty($this->field_defs))
1842 foreach($this->field_defs as $fieldDef)
1844 $field = $fieldDef['name'];
1845 if(!isset($this->processed_dates_times[$field]))
1847 $this->processed_dates_times[$field] = '1';
1848 if(empty($this->$field)) continue;
1849 if($field == 'date_modified' || $field == 'date_entered')
1851 $this->$field = $this->db->fromConvert($this->$field, 'datetime');
1852 if(empty($disable_date_format)) {
1853 $this->$field = $timedate->to_display_date_time($this->$field);
1856 elseif(isset($this->field_name_map[$field]['type']))
1858 $type = $this->field_name_map[$field]['type'];
1860 if($type == 'relate' && isset($this->field_name_map[$field]['custom_module']))
1862 $type = $this->field_name_map[$field]['type'];
1867 if($this->$field == '0000-00-00')
1870 } elseif(!empty($this->field_name_map[$field]['rel_field']))
1872 $rel_field = $this->field_name_map[$field]['rel_field'];
1874 if(!empty($this->$rel_field))
1876 if(empty($disable_date_format)) {
1877 $mergetime = $timedate->merge_date_time($this->$field,$this->$rel_field);
1878 $this->$field = $timedate->to_display_date($mergetime);
1879 $this->$rel_field = $timedate->to_display_time($mergetime);
1885 if(empty($disable_date_format)) {
1886 $this->$field = $timedate->to_display_date($this->$field, false);
1889 } elseif($type == 'datetime' || $type == 'datetimecombo')
1891 if($this->$field == '0000-00-00 00:00:00')
1897 if(empty($disable_date_format)) {
1898 $this->$field = $timedate->to_display_date_time($this->$field, true, true);
1901 } elseif($type == 'time')
1903 if($this->$field == '00:00:00')
1908 //$this->$field = from_db_convert($this->$field, 'time');
1909 if(empty($this->field_name_map[$field]['rel_field']) && empty($disable_date_format))
1911 $this->$field = $timedate->to_display_time($this->$field,true, false);
1914 } elseif($type == 'encrypt' && empty($disable_date_format)){
1915 $this->$field = $this->decrypt_after_retrieve($this->$field);
1923 * This function processes the fields before save.
1924 * Interal function, do not override.
1926 function preprocess_fields_on_save()
1928 $GLOBALS['log']->deprecated('SugarBean.php: preprocess_fields_on_save() is deprecated');
1932 * Removes formatting from values posted from the user interface.
1933 * It only unformats numbers. Function relies on user/system prefernce for format strings.
1935 * Internal Function, do not override.
1937 function unformat_all_fields()
1939 $GLOBALS['log']->deprecated('SugarBean.php: unformat_all_fields() is deprecated');
1943 * This functions adds formatting to all number fields before presenting them to user interface.
1945 * Internal function, do not override.
1947 function format_all_fields()
1949 $GLOBALS['log']->deprecated('SugarBean.php: format_all_fields() is deprecated');
1952 function format_field($fieldDef)
1954 $GLOBALS['log']->deprecated('SugarBean.php: format_field() is deprecated');
1958 * Function corrects any bad formatting done by 3rd party/custom code
1960 * 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
1962 function fixUpFormatting()
1965 static $boolean_false_values = array('off', 'false', '0', 'no');
1968 foreach($this->field_defs as $field=>$def)
1970 if ( !isset($this->$field) ) {
1973 if ( (isset($def['source'])&&$def['source']=='non-db') || $field == 'deleted' ) {
1976 if ( isset($this->fetched_row[$field]) && $this->$field == $this->fetched_row[$field] ) {
1977 // Don't hand out warnings because the field was untouched between retrieval and saving, most database drivers hand pretty much everything back as strings.
1980 $reformatted = false;
1981 switch($def['type']) {
1983 case 'datetimecombo':
1984 if(empty($this->$field)) break;
1985 if ($this->$field == 'NULL') {
1989 if ( ! preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$/',$this->$field) ) {
1990 // This appears to be formatted in user date/time
1991 $this->$field = $timedate->to_db($this->$field);
1992 $reformatted = true;
1996 if(empty($this->$field)) break;
1997 if ($this->$field == 'NULL') {
2001 if ( ! preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/',$this->$field) ) {
2002 // This date appears to be formatted in the user's format
2003 $this->$field = $timedate->to_db_date($this->$field, false);
2004 $reformatted = true;
2008 if(empty($this->$field)) break;
2009 if ($this->$field == 'NULL') {
2013 if ( preg_match('/(am|pm)/i',$this->$field) ) {
2014 // This time appears to be formatted in the user's format
2015 $this->$field = $timedate->fromUserTime($this->$field)->format(TimeDate::DB_TIME_FORMAT);
2016 $reformatted = true;
2023 if ( $this->$field === '' || $this->$field == NULL || $this->$field == 'NULL') {
2026 if ( is_string($this->$field) ) {
2027 $this->$field = (float)unformat_number($this->$field);
2028 $reformatted = true;
2037 if ( $this->$field === '' || $this->$field == NULL || $this->$field == 'NULL') {
2040 if ( is_string($this->$field) ) {
2041 $this->$field = (int)unformat_number($this->$field);
2042 $reformatted = true;
2046 if (empty($this->$field)) {
2047 $this->$field = false;
2048 } else if(true === $this->$field || 1 == $this->$field) {
2049 $this->$field = true;
2050 } else if(in_array(strval($this->$field), $boolean_false_values)) {
2051 $this->$field = false;
2052 $reformatted = true;
2054 $this->$field = true;
2055 $reformatted = true;
2059 $this->$field = $this->encrpyt_before_save($this->$field);
2062 if ( $reformatted ) {
2063 $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');
2070 * Function fetches a single row of data given the primary key value.
2072 * The fetched data is then set into the bean. The function also processes the fetched data by formattig
2073 * date/time and numeric values.
2075 * @param string $id Optional, default -1, is set to -1 id value from the bean is used, else, passed value is used
2076 * @param boolean $encode Optional, default true, encodes the values fetched from the database.
2077 * @param boolean $deleted Optional, default true, if set to false deleted filter will not be added.
2079 * Internal function, do not override.
2081 function retrieve($id = -1, $encode=true,$deleted=true)
2084 $custom_logic_arguments['id'] = $id;
2085 $this->call_custom_logic('before_retrieve', $custom_logic_arguments);
2091 if(isset($this->custom_fields))
2093 $custom_join = $this->custom_fields->getJOIN();
2096 $custom_join = false;
2100 $query = "SELECT $this->table_name.*". $custom_join['select']. " FROM $this->table_name ";
2104 $query = "SELECT $this->table_name.* FROM $this->table_name ";
2109 $query .= ' ' . $custom_join['join'];
2111 $query .= " WHERE $this->table_name.id = ".$this->db->quoted($id);
2112 if ($deleted) $query .= " AND $this->table_name.deleted=0";
2113 $GLOBALS['log']->debug("Retrieve $this->object_name : ".$query);
2114 $result = $this->db->limitQuery($query,0,1,true, "Retrieving record by id $this->table_name:$id found ");
2120 $row = $this->db->fetchByAssoc($result, $encode);
2126 //make copy of the fetched row for construction of audit record and for business logic/workflow
2127 $row = $this->convertRow($row);
2128 $this->fetched_row=$row;
2129 $this->populateFromRow($row);
2131 global $module, $action;
2132 //Just to get optimistic locking working for this release
2133 if($this->optimistic_lock && $module == $this->module_dir && $action =='EditView' )
2135 $_SESSION['o_lock_id']= $id;
2136 $_SESSION['o_lock_dm']= $this->date_modified;
2137 $_SESSION['o_lock_on'] = $this->object_name;
2139 $this->processed_dates_times = array();
2140 $this->check_date_relationships_load();
2144 $this->custom_fields->fill_relationships();
2147 $this->is_updated_dependent_fields = false;
2148 $this->fill_in_additional_detail_fields();
2149 $this->fill_in_relationship_fields();
2150 //make a copy of fields in the relationship_fields array. These field values will be used to
2151 //clear relationship.
2152 foreach ( $this->field_defs as $key => $def )
2154 if ($def [ 'type' ] == 'relate' && isset ( $def [ 'id_name'] ) && isset ( $def [ 'link'] ) && isset ( $def[ 'save' ])) {
2155 if (isset($this->$key)) {
2156 $this->rel_fields_before_value[$key]=$this->$key;
2157 if (isset($this->$def [ 'id_name']))
2158 $this->rel_fields_before_value[$def [ 'id_name']]=$this->$def [ 'id_name'];
2161 $this->rel_fields_before_value[$key]=null;
2164 if (isset($this->relationship_fields) && is_array($this->relationship_fields))
2166 foreach ($this->relationship_fields as $rel_id=>$rel_name)
2168 if (isset($this->$rel_id))
2169 $this->rel_fields_before_value[$rel_id]=$this->$rel_id;
2171 $this->rel_fields_before_value[$rel_id]=null;
2175 // call the custom business logic
2176 $custom_logic_arguments['id'] = $id;
2177 $custom_logic_arguments['encode'] = $encode;
2178 $this->call_custom_logic("after_retrieve", $custom_logic_arguments);
2179 unset($custom_logic_arguments);
2184 * Sets value from fetched row into the bean.
2186 * @param array $row Fetched row
2187 * @todo loop through vardefs instead
2188 * @internal runs into an issue when populating from field_defs for users - corrupts user prefs
2190 * Internal function, do not override.
2192 function populateFromRow($row)
2195 foreach($this->field_defs as $field=>$field_value)
2197 if($field == 'user_preferences' && $this->module_dir == 'Users')
2199 if(isset($row[$field]))
2201 $this->$field = $row[$field];
2202 $owner = $field . '_owner';
2203 if(!empty($row[$owner])){
2204 $this->$owner = $row[$owner];
2209 $this->$field = $nullvalue;
2217 * Add any required joins to the list count query. The joins are required if there
2218 * is a field in the $where clause that needs to be joined.
2220 * @param string $query
2221 * @param string $where
2223 * Internal Function, do Not override.
2225 function add_list_count_joins(&$query, $where)
2227 $custom_join = $this->custom_fields->getJOIN();
2230 $query .= $custom_join['join'];
2236 * Changes the select expression of the given query to be 'count(*)' so you
2237 * can get the number of items the query will return. This is used to
2238 * populate the upper limit on ListViews.
2240 * @param string $query Select query string
2241 * @return string count query
2243 * Internal function, do not override.
2245 function create_list_count_query($query)
2247 // remove the 'order by' clause which is expected to be at the end of the query
2248 $pattern = '/\sORDER BY.*/is'; // ignores the case
2250 $query = preg_replace($pattern, $replacement, $query);
2251 //handle distinct clause
2253 if(substr_count(strtolower($query), 'distinct')){
2254 if (!empty($this->seed) && !empty($this->seed->table_name ))
2255 $star = 'DISTINCT ' . $this->seed->table_name . '.id';
2257 $star = 'DISTINCT ' . $this->table_name . '.id';
2261 // change the select expression to 'count(*)'
2262 $pattern = '/SELECT(.*?)(\s){1}FROM(\s){1}/is'; // ignores the case
2263 $replacement = 'SELECT count(' . $star . ') c FROM ';
2265 //if the passed query has union clause then replace all instances of the pattern.
2266 //this is very rare. I have seen this happening only from projects module.
2267 //in addition to this added a condition that has union clause and uses
2269 if (strstr($query," UNION ALL ") !== false) {
2271 //separate out all the queries.
2272 $union_qs=explode(" UNION ALL ", $query);
2273 foreach ($union_qs as $key=>$union_query) {
2275 preg_match($pattern, $union_query, $matches);
2276 if (!empty($matches)) {
2277 if (stristr($matches[0], "distinct")) {
2278 if (!empty($this->seed) && !empty($this->seed->table_name ))
2279 $star = 'DISTINCT ' . $this->seed->table_name . '.id';
2281 $star = 'DISTINCT ' . $this->table_name . '.id';
2284 $replacement = 'SELECT count(' . $star . ') c FROM ';
2285 $union_qs[$key] = preg_replace($pattern, $replacement, $union_query,1);
2287 $modified_select_query=implode(" UNION ALL ",$union_qs);
2289 $modified_select_query = preg_replace($pattern, $replacement, $query,1);
2293 return $modified_select_query;
2297 * This function returns a paged list of the current object type. It is intended to allow for
2298 * hopping back and forth through pages of data. It only retrieves what is on the current page.
2300 * @internal This method must be called on a new instance. It trashes the values of all the fields in the current one.
2301 * @param string $order_by
2302 * @param string $where Additional where clause
2303 * @param int $row_offset Optaional,default 0, starting row number
2304 * @param init $limit Optional, default -1
2305 * @param int $max Optional, default -1
2306 * @param boolean $show_deleted Optional, default 0, if set to 1 system will show deleted records.
2307 * @return array Fetched data.
2309 * Internal function, do not override.
2312 function get_list($order_by = "", $where = "", $row_offset = 0, $limit=-1, $max=-1, $show_deleted = 0, $singleSelect=false, $select_fields = array())
2314 $GLOBALS['log']->debug("get_list: order_by = '$order_by' and where = '$where' and limit = '$limit'");
2315 if(isset($_SESSION['show_deleted']))
2319 $order_by=$this->process_order_by($order_by);
2321 if($this->bean_implements('ACL') && ACLController::requireOwner($this->module_dir, 'list') )
2323 global $current_user;
2324 $owner_where = $this->getOwnerWhere($current_user->id);
2326 //rrs - because $this->getOwnerWhere() can return '' we need to be sure to check for it and
2327 //handle it properly else you could get into a situation where you are create a where stmt like
2329 if(!empty($owner_where)){
2331 $where = $owner_where;
2333 $where .= ' AND '. $owner_where;
2337 $query = $this->create_new_list_query($order_by, $where,$select_fields,array(), $show_deleted,'',false,null,$singleSelect);
2338 return $this->process_list_query($query, $row_offset, $limit, $max, $where);
2342 * Prefixes column names with this bean's table name.
2344 * @param string $order_by Order by clause to be processed
2345 * @param SugarBean $submodule name of the module this order by clause is for
2346 * @return string Processed order by clause
2348 * Internal function, do not override.
2350 function process_order_by ($order_by, $submodule = null)
2352 if (empty($order_by))
2354 //submodule is empty,this is for list object in focus
2355 if (empty($submodule))
2357 $bean_queried = $this;
2361 //submodule is set, so this is for subpanel, use submodule
2362 $bean_queried = $submodule;
2364 $elements = explode(',',$order_by);
2365 foreach ($elements as $key=>$value)
2367 if (strchr($value,'.') === false)
2369 //value might have ascending and descending decorations
2370 $list_column = explode(' ',trim($value));
2371 if (isset($list_column[0]))
2373 $list_column_name=trim($list_column[0]);
2374 if (isset($bean_queried->field_defs[$list_column_name]))
2376 $source=isset($bean_queried->field_defs[$list_column_name]['source']) ? $bean_queried->field_defs[$list_column_name]['source']:'db';
2377 if (empty($bean_queried->field_defs[$list_column_name]['table']) && $source=='db')
2379 $list_column[0] = $bean_queried->table_name .".".$list_column[0] ;
2381 if (empty($bean_queried->field_defs[$list_column_name]['table']) && $source=='custom_fields')
2383 $list_column[0] = $bean_queried->table_name ."_cstm.".$list_column[0] ;
2385 // Bug 38803 - Use CONVERT() function when doing an order by on ntext, text, and image fields
2386 if ($source != 'non-db' && $this->db->isTextType($this->db->getFieldType($bean_queried->field_defs[$list_column_name]))) {
2387 $list_column[0] = $this->db->convert($list_column[0], "text2char");
2389 $value = implode(' ',$list_column);
2391 $GLOBALS['log']->debug("process_order_by: ($list_column[0]) does not have a vardef entry.");
2395 $elements[$key]=$value;
2397 return implode(',', $elements);
2403 * Returns a detail object like retrieving of the current object type.
2405 * It is intended for use in navigation buttons on the DetailView. It will pass an offset and limit argument to the sql query.
2406 * @internal This method must be called on a new instance. It overrides the values of all the fields in the current one.
2408 * @param string $order_by
2409 * @param string $where Additional where clause
2410 * @param int $row_offset Optaional,default 0, starting row number
2411 * @param init $limit Optional, default -1
2412 * @param int $max Optional, default -1
2413 * @param boolean $show_deleted Optioanl, default 0, if set to 1 system will show deleted records.
2414 * @return array Fetched data.
2416 * Internal function, do not override.
2418 function get_detail($order_by = "", $where = "", $offset = 0, $row_offset = 0, $limit=-1, $max=-1, $show_deleted = 0)
2420 $GLOBALS['log']->debug("get_detail: order_by = '$order_by' and where = '$where' and limit = '$limit' and offset = '$offset'");
2421 if(isset($_SESSION['show_deleted']))
2426 if($this->bean_implements('ACL') && ACLController::requireOwner($this->module_dir, 'list') )
2428 global $current_user;
2429 $owner_where = $this->getOwnerWhere($current_user->id);
2433 $where = $owner_where;
2437 $where .= ' AND '. $owner_where;
2440 $query = $this->create_new_list_query($order_by, $where,array(),array(), $show_deleted, $offset);
2442 //Add Limit and Offset to query
2443 //$query .= " LIMIT 1 OFFSET $offset";
2445 return $this->process_detail_query($query, $row_offset, $limit, $max, $where, $offset);
2449 * Fetches data from all related tables.
2451 * @param object $child_seed
2452 * @param string $related_field_name relation to fetch data for
2453 * @param string $order_by Optional, default empty
2454 * @param string $where Optional, additional where clause
2455 * @return array Fetched data.
2457 * Internal function, do not override.
2459 function get_related_list($child_seed,$related_field_name, $order_by = "", $where = "",
2460 $row_offset = 0, $limit=-1, $max=-1, $show_deleted = 0)
2462 global $layout_edit_mode;
2463 $query_array = array();
2465 if(isset($layout_edit_mode) && $layout_edit_mode)
2467 $response = array();
2468 $child_seed->assign_display_fields($child_seed->module_dir);
2469 $response['list'] = array($child_seed);
2470 $response['row_count'] = 1;
2471 $response['next_offset'] = 0;
2472 $response['previous_offset'] = 0;
2476 $GLOBALS['log']->debug("get_related_list: order_by = '$order_by' and where = '$where' and limit = '$limit'");
2477 if(isset($_SESSION['show_deleted']))
2482 $this->load_relationship($related_field_name);
2484 if ($this->$related_field_name instanceof Link) {
2486 $query_array = $this->$related_field_name->getQuery(true);
2489 $query_array = $this->$related_field_name->getQuery(array(
2490 "return_as_array" => true,
2491 'where' => '1=1' // hook for 'where' clause in M2MRelationship file
2495 $entire_where = $query_array['where'];
2498 if(empty($entire_where))
2500 $entire_where = ' WHERE ' . $where;
2504 $entire_where .= ' AND ' . $where;
2508 $query = 'SELECT '.$child_seed->table_name.'.* ' . $query_array['from'] . ' ' . $entire_where;
2509 if(!empty($order_by))
2511 $query .= " ORDER BY " . $order_by;
2514 return $child_seed->process_list_query($query, $row_offset, $limit, $max, $where);
2518 protected static function build_sub_queries_for_union($subpanel_list, $subpanel_def, $parentbean, $order_by)
2520 global $layout_edit_mode, $beanFiles, $beanList;
2521 $subqueries = array();
2522 foreach($subpanel_list as $this_subpanel)
2524 if(!$this_subpanel->isDatasourceFunction() || ($this_subpanel->isDatasourceFunction()
2525 && isset($this_subpanel->_instance_properties['generate_select'])
2526 && $this_subpanel->_instance_properties['generate_select']==true))
2528 //the custom query function must return an array with
2529 if ($this_subpanel->isDatasourceFunction()) {
2530 $shortcut_function_name = $this_subpanel->get_data_source_name();
2531 $parameters=$this_subpanel->get_function_parameters();
2532 if (!empty($parameters))
2534 //if the import file function is set, then import the file to call the custom function from
2535 if (is_array($parameters) && isset($parameters['import_function_file'])){
2536 //this call may happen multiple times, so only require if function does not exist
2537 if(!function_exists($shortcut_function_name)){
2538 require_once($parameters['import_function_file']);
2540 //call function from required file
2541 $query_array = $shortcut_function_name($parameters);
2543 //call function from parent bean
2544 $query_array = $parentbean->$shortcut_function_name($parameters);
2549 $query_array = $parentbean->$shortcut_function_name();
2552 $related_field_name = $this_subpanel->get_data_source_name();
2553 if (!$parentbean->load_relationship($related_field_name)){
2554 unset ($parentbean->$related_field_name);
2557 $query_array = $parentbean->$related_field_name->getSubpanelQuery(array(), true);
2559 $table_where = $this_subpanel->get_where();
2560 $where_definition = $query_array['where'];
2562 if(!empty($table_where))
2564 if(empty($where_definition))
2566 $where_definition = $table_where;
2570 $where_definition .= ' AND ' . $table_where;
2574 $submodulename = $this_subpanel->_instance_properties['module'];
2575 $submoduleclass = $beanList[$submodulename];
2576 //require_once($beanFiles[$submoduleclass]);
2577 $submodule = new $submoduleclass();
2578 $subwhere = $where_definition;
2582 $subwhere = str_replace('WHERE', '', $subwhere);
2583 $list_fields = $this_subpanel->get_list_fields();
2584 foreach($list_fields as $list_key=>$list_field)
2586 if(isset($list_field['usage']) && $list_field['usage'] == 'display_only')
2588 unset($list_fields[$list_key]);
2593 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'))
2595 $order_by = $submodule->table_name .'.'. $order_by;
2597 $table_name = $this_subpanel->table_name;
2598 $panel_name=$this_subpanel->name;
2600 $params['distinct'] = $this_subpanel->distinct_query();
2602 $params['joined_tables'] = $query_array['join_tables'];
2603 $params['include_custom_fields'] = !$subpanel_def->isCollection();
2604 $params['collection_list'] = $subpanel_def->get_inst_prop_value('collection_list');
2606 $subquery = $submodule->create_new_list_query('',$subwhere ,$list_fields,$params, 0,'', true,$parentbean);
2608 $subquery['select'] = $subquery['select']." , '$panel_name' panel_name ";
2609 $subquery['from'] = $subquery['from'].$query_array['join'];
2610 $subquery['query_array'] = $query_array;
2611 $subquery['params'] = $params;
2613 $subqueries[] = $subquery;
2620 * Constructs a query to fetch data for supanels and list views
2622 * It constructs union queries for activities subpanel.
2624 * @param SugarBean $parentbean constructing queries for link attributes in this bean
2625 * @param string $order_by Optional, order by clause
2626 * @param string $sort_order Optional, sort order
2627 * @param string $where Optional, additional where clause
2629 * Internal Function, do not overide.
2631 function get_union_related_list($parentbean, $order_by = "", $sort_order='', $where = "",
2632 $row_offset = 0, $limit=-1, $max=-1, $show_deleted = 0, $subpanel_def)
2634 $secondary_queries = array();
2635 global $layout_edit_mode, $beanFiles, $beanList;
2637 if(isset($_SESSION['show_deleted']))
2642 $final_query_rows = '';
2643 $subpanel_list=array();
2644 if ($subpanel_def->isCollection())
2646 $subpanel_def->load_sub_subpanels();
2647 $subpanel_list=$subpanel_def->sub_subpanels;
2651 $subpanel_list[]=$subpanel_def;
2656 //Breaking the building process into two loops. The first loop gets a list of all the sub-queries.
2657 //The second loop merges the queries and forces them to select the same number of columns
2658 //All columns in a sub-subpanel group must have the same aliases
2659 //If the subpanel is a datasource function, it can't be a collection so we just poll that function for the and return that
2660 foreach($subpanel_list as $this_subpanel)
2662 if($this_subpanel->isDatasourceFunction() && empty($this_subpanel->_instance_properties['generate_select']))
2664 $shortcut_function_name = $this_subpanel->get_data_source_name();
2665 $parameters=$this_subpanel->get_function_parameters();
2666 if (!empty($parameters))
2668 //if the import file function is set, then import the file to call the custom function from
2669 if (is_array($parameters) && isset($parameters['import_function_file'])){
2670 //this call may happen multiple times, so only require if function does not exist
2671 if(!function_exists($shortcut_function_name)){
2672 require_once($parameters['import_function_file']);
2674 //call function from required file
2675 $tmp_final_query = $shortcut_function_name($parameters);
2677 //call function from parent bean
2678 $tmp_final_query = $parentbean->$shortcut_function_name($parameters);
2681 $tmp_final_query = $parentbean->$shortcut_function_name();
2685 $final_query_rows .= ' UNION ALL ( '.$parentbean->create_list_count_query($tmp_final_query, $parameters) . ' )';
2686 $final_query .= ' UNION ALL ( '.$tmp_final_query . ' )';
2688 $final_query_rows = '(' . $parentbean->create_list_count_query($tmp_final_query, $parameters) . ')';
2689 $final_query = '(' . $tmp_final_query . ')';
2694 //If final_query is still empty, its time to build the sub-queries
2695 if (empty($final_query))
2697 $subqueries = SugarBean::build_sub_queries_for_union($subpanel_list, $subpanel_def, $parentbean, $order_by);
2698 $all_fields = array();
2699 foreach($subqueries as $i => $subquery)
2701 $query_fields = $GLOBALS['db']->getSelectFieldsFromQuery($subquery['select']);
2702 foreach($query_fields as $field => $select)
2704 if (!in_array($field, $all_fields))
2705 $all_fields[] = $field;
2707 $subqueries[$i]['query_fields'] = $query_fields;
2710 //Now ensure the queries have the same set of fields in the same order.
2711 foreach($subqueries as $subquery)
2713 $subquery['select'] = "SELECT";
2714 foreach($all_fields as $field)
2716 if (!isset($subquery['query_fields'][$field]))
2718 $subquery['select'] .= " ' ' $field,";
2722 $subquery['select'] .= " {$subquery['query_fields'][$field]},";
2725 $subquery['select'] = substr($subquery['select'], 0 , strlen($subquery['select']) - 1);
2726 //Put the query into the final_query
2727 $query = $subquery['select'] . " " . $subquery['from'] . " " . $subquery['where'];
2730 $query = ' UNION ALL ( '.$query . ' )';
2731 $final_query_rows .= " UNION ALL ";
2733 $query = '(' . $query . ')';
2736 $query_array = $subquery['query_array'];
2737 $select_position=strpos($query_array['select'],"SELECT");
2738 $distinct_position=strpos($query_array['select'],"DISTINCT");
2739 if (!empty($subquery['params']['distinct']) && !empty($subpanel_def->table_name))
2741 $query_rows = "( SELECT count(DISTINCT ". $subpanel_def->table_name . ".id)". $subquery['from_min'].$query_array['join']. $subquery['where'].' )';
2743 elseif ($select_position !== false && $distinct_position!= false)
2745 $query_rows = "( ".substr_replace($query_array['select'],"SELECT count(",$select_position,6). ")" . $subquery['from_min'].$query_array['join']. $subquery['where'].' )';
2749 //resort to default behavior.
2750 $query_rows = "( SELECT count(*)". $subquery['from_min'].$query_array['join']. $subquery['where'].' )';
2752 if(!empty($subquery['secondary_select']))
2755 $subquerystring= $subquery['secondary_select'] . $subquery['secondary_from'].$query_array['join']. $subquery['where'];
2756 if (!empty($subquery['secondary_where']))
2758 if (empty($subquery['where']))
2760 $subquerystring.=" WHERE " .$subquery['secondary_where'];
2764 $subquerystring.=" AND " .$subquery['secondary_where'];
2767 $secondary_queries[]=$subquerystring;
2769 $final_query .= $query;
2770 $final_query_rows .= $query_rows;
2774 if(!empty($order_by))
2777 if(!$subpanel_def->isCollection())
2779 $submodulename = $subpanel_def->_instance_properties['module'];
2780 $submoduleclass = $beanList[$submodulename];
2781 $submodule = new $submoduleclass();
2783 if(!empty($submodule) && !empty($submodule->table_name))
2785 $final_query .= " ORDER BY " .$parentbean->process_order_by($order_by, $submodule);
2790 $final_query .= " ORDER BY ". $order_by . ' ';
2792 if(!empty($sort_order))
2794 $final_query .= ' ' .$sort_order;
2799 if(isset($layout_edit_mode) && $layout_edit_mode)
2801 $response = array();
2802 if(!empty($submodule))
2804 $submodule->assign_display_fields($submodule->module_dir);
2805 $response['list'] = array($submodule);
2809 $response['list'] = array();
2811 $response['parent_data'] = array();
2812 $response['row_count'] = 1;
2813 $response['next_offset'] = 0;
2814 $response['previous_offset'] = 0;
2819 return $parentbean->process_union_list_query($parentbean, $final_query, $row_offset, $limit, $max, '',$subpanel_def, $final_query_rows, $secondary_queries);
2824 * Returns a full (ie non-paged) list of the current object type.
2826 * @param string $order_by the order by SQL parameter. defaults to ""
2827 * @param string $where where clause. defaults to ""
2828 * @param boolean $check_dates. defaults to false
2829 * @param int $show_deleted show deleted records. defaults to 0
2831 function get_full_list($order_by = "", $where = "", $check_dates=false, $show_deleted = 0)
2833 $GLOBALS['log']->debug("get_full_list: order_by = '$order_by' and where = '$where'");
2834 if(isset($_SESSION['show_deleted']))
2838 $query = $this->create_new_list_query($order_by, $where,array(),array(), $show_deleted);
2839 return $this->process_full_list_query($query, $check_dates);
2843 * Return the list query used by the list views and export button. Next generation of create_new_list_query function.
2845 * Override this function to return a custom query.
2847 * @param string $order_by custom order by clause
2848 * @param string $where custom where clause
2849 * @param array $filter Optioanal
2850 * @param array $params Optional *
2851 * @param int $show_deleted Optional, default 0, show deleted records is set to 1.
2852 * @param string $join_type
2853 * @param boolean $return_array Optional, default false, response as array
2854 * @param object $parentbean creating a subquery for this bean.
2855 * @param boolean $singleSelect Optional, default false.
2856 * @return String select query string, optionally an array value will be returned if $return_array= true.
2858 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)
2860 global $beanFiles, $beanList;
2861 $selectedFields = array();
2862 $secondarySelectedFields = array();
2863 $ret_array = array();
2865 if($this->bean_implements('ACL') && ACLController::requireOwner($this->module_dir, 'list') )
2867 global $current_user;
2868 $owner_where = $this->getOwnerWhere($current_user->id);
2871 $where = $owner_where;
2875 $where .= ' AND '. $owner_where;
2878 if(!empty($params['distinct']))
2880 $distinct = ' DISTINCT ';
2884 $ret_array['select'] = " SELECT $distinct $this->table_name.* ";
2888 $ret_array['select'] = " SELECT $distinct $this->table_name.id ";
2890 $ret_array['from'] = " FROM $this->table_name ";
2891 $ret_array['from_min'] = $ret_array['from'];
2892 $ret_array['secondary_from'] = $ret_array['from'] ;
2893 $ret_array['where'] = '';
2894 $ret_array['order_by'] = '';
2895 //secondary selects are selects that need to be run after the primary query to retrieve additional info on main
2898 $ret_array['secondary_select']=& $ret_array['select'];
2899 $ret_array['secondary_from'] = & $ret_array['from'];
2903 $ret_array['secondary_select'] = '';
2905 $custom_join = false;
2906 if((!isset($params['include_custom_fields']) || $params['include_custom_fields']) && isset($this->custom_fields))
2909 $custom_join = $this->custom_fields->getJOIN( empty($filter)? true: $filter );
2912 $ret_array['select'] .= ' ' .$custom_join['select'];
2917 $ret_array['from'] .= ' ' . $custom_join['join'];
2918 // Bug 52490 - Captivea (Sve) - To be able to add custom fields inside where clause in a subpanel
2919 $ret_array['from_min'] .= ' ' . $custom_join['join'];
2922 //LOOP AROUND FOR FIXIN VARDEF ISSUES
2923 require('include/VarDefHandler/listvardefoverride.php');
2924 if (file_exists('custom/include/VarDefHandler/listvardefoverride.php'))
2926 require('custom/include/VarDefHandler/listvardefoverride.php');
2929 $joined_tables = array();
2930 if(!empty($params['joined_tables']))
2932 foreach($params['joined_tables'] as $table)
2934 $joined_tables[$table] = 1;
2940 $filterKeys = array_keys($filter);
2941 if(is_numeric($filterKeys[0]))
2944 foreach($filter as $field)
2946 $field = strtolower($field);
2947 //remove out id field so we don't duplicate it
2948 if ( $field == 'id' && !empty($filter) ) {
2951 if(isset($this->field_defs[$field]))
2953 $fields[$field]= $this->field_defs[$field];
2957 $fields[$field] = array('force_exists'=>true);
2966 $fields = $this->field_defs;
2969 $used_join_key = array();
2971 foreach($fields as $field=>$value)
2973 //alias is used to alias field names
2975 if (isset($value['alias']))
2977 $alias =' as ' . $value['alias'] . ' ';
2980 if(empty($this->field_defs[$field]) || !empty($value['force_blank']) )
2982 if(!empty($filter) && isset($filter[$field]['force_exists']) && $filter[$field]['force_exists'])
2984 if ( isset($filter[$field]['force_default']) )
2985 $ret_array['select'] .= ", {$filter[$field]['force_default']} $field ";
2987 //spaces are a fix for length issue problem with unions. The union only returns the maximum number of characters from the first select statement.
2988 $ret_array['select'] .= ", ' ' $field ";
2994 $data = $this->field_defs[$field];
2997 //ignore fields that are a part of the collection and a field has been removed as a result of
2998 //layout customization.. this happens in subpanel customizations, use case, from the contacts subpanel
2999 //in opportunities module remove the contact_role/opportunity_role field.
3000 $process_field=true;
3001 if (isset($data['relationship_fields']) and !empty($data['relationship_fields']))
3003 foreach ($data['relationship_fields'] as $field_name)
3005 if (!isset($fields[$field_name]))
3007 $process_field=false;
3011 if (!$process_field)
3016 if( (!isset($data['source']) || $data['source'] == 'db') && (!empty($alias) || !empty($filter) ))
3018 $ret_array['select'] .= ", $this->table_name.$field $alias";
3019 $selectedFields["$this->table_name.$field"] = true;
3020 } else if( (!isset($data['source']) || $data['source'] == 'custom_fields') && (!empty($alias) || !empty($filter) )) {
3021 //add this column only if it has NOT already been added to select statement string
3022 $colPos = strpos($ret_array['select'],"$this->table_name"."_cstm".".$field");
3023 if(!$colPos || $colPos<0)
3025 $ret_array['select'] .= ", $this->table_name"."_cstm".".$field $alias";
3028 $selectedFields["$this->table_name.$field"] = true;
3031 if($data['type'] != 'relate' && isset($data['db_concat_fields']))
3033 $ret_array['select'] .= ", " . $this->db->concat($this->table_name, $data['db_concat_fields']) . " as $field";
3034 $selectedFields[$this->db->concat($this->table_name, $data['db_concat_fields'])] = true;
3036 //Custom relate field or relate fields built in module builder which have no link field associated.
3037 if ($data['type'] == 'relate' && (isset($data['custom_module']) || isset($data['ext2']))) {
3038 $joinTableAlias = 'jt' . $jtcount;
3039 $relateJoinInfo = $this->custom_fields->getRelateJoin($data, $joinTableAlias);
3040 $ret_array['select'] .= $relateJoinInfo['select'];
3041 $ret_array['from'] .= $relateJoinInfo['from'];
3042 //Replace any references to the relationship in the where clause with the new alias
3043 //If the link isn't set, assume that search used the local table for the field
3044 $searchTable = isset($data['link']) ? $relateJoinInfo['rel_table'] : $this->table_name;
3045 $field_name = $relateJoinInfo['rel_table'] . '.' . !empty($data['name'])?$data['name']:'name';
3046 $where = preg_replace('/(^|[\s(])' . $field_name . '/' , '${1}' . $relateJoinInfo['name_field'], $where);
3050 if ($data['type'] == 'parent') {
3051 //See if we need to join anything by inspecting the where clause
3052 $match = preg_match('/(^|[\s(])parent_(\w+)_(\w+)\.name/', $where, $matches);
3054 $joinTableAlias = 'jt' . $jtcount;
3055 $joinModule = $matches[2];
3056 $joinTable = $matches[3];
3057 $localTable = $this->table_name;
3058 if (!empty($data['custom_module'])) {
3059 $localTable .= '_cstm';
3061 global $beanFiles, $beanList, $module;
3062 require_once($beanFiles[$beanList[$joinModule]]);
3063 $rel_mod = new $beanList[$joinModule]();
3064 $nameField = "$joinTableAlias.name";
3065 if (isset($rel_mod->field_defs['name']))
3067 $name_field_def = $rel_mod->field_defs['name'];
3068 if(isset($name_field_def['db_concat_fields']))
3070 $nameField = $this->db->concat($joinTableAlias, $name_field_def['db_concat_fields']);
3073 $ret_array['select'] .= ", $nameField {$data['name']} ";
3074 $ret_array['from'] .= " LEFT JOIN $joinTable $joinTableAlias
3075 ON $localTable.{$data['id_name']} = $joinTableAlias.id";
3076 //Replace any references to the relationship in the where clause with the new alias
3077 $where = preg_replace('/(^|[\s(])parent_' . $joinModule . '_' . $joinTable . '\.name/', '${1}' . $nameField, $where);
3081 if($data['type'] == 'relate' && isset($data['link']))
3083 $this->load_relationship($data['link']);
3084 if(!empty($this->$data['link']))
3087 if(empty($join_type))
3089 $params['join_type'] = ' LEFT JOIN ';
3093 $params['join_type'] = $join_type;
3095 if(isset($data['join_name']))
3097 $params['join_table_alias'] = $data['join_name'];
3101 $params['join_table_alias'] = 'jt' . $jtcount;
3104 if(isset($data['join_link_name']))
3106 $params['join_table_link_alias'] = $data['join_link_name'];
3110 $params['join_table_link_alias'] = 'jtl' . $jtcount;
3112 $join_primary = !isset($data['join_primary']) || $data['join_primary'];
3114 $join = $this->$data['link']->getJoin($params, true);
3115 $used_join_key[] = $join['rel_key'];
3116 $rel_module = $this->$data['link']->getRelatedModuleName();
3117 $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');
3119 //if rname is set to 'name', and bean files exist, then check if field should be a concatenated name
3120 global $beanFiles, $beanList;
3121 if($data['rname'] && !empty($beanFiles[$beanList[$rel_module]])) {
3123 //create an instance of the related bean
3124 require_once($beanFiles[$beanList[$rel_module]]);
3125 $rel_mod = new $beanList[$rel_module]();
3126 //if bean has first and last name fields, then name should be concatenated
3127 if(isset($rel_mod->field_name_map['first_name']) && isset($rel_mod->field_name_map['last_name'])){
3128 $data['db_concat_fields'] = array(0=>'first_name', 1=>'last_name');
3133 if($join['type'] == 'many-to-many')
3135 if(empty($ret_array['secondary_select']))
3137 $ret_array['secondary_select'] = " SELECT $this->table_name.id ref_id ";
3139 if(!empty($beanFiles[$beanList[$rel_module]]) && $join_primary)
3141 require_once($beanFiles[$beanList[$rel_module]]);
3142 $rel_mod = new $beanList[$rel_module]();
3143 if(isset($rel_mod->field_defs['assigned_user_id']))
3145 $ret_array['secondary_select'].= " , ". $params['join_table_alias'] . ".assigned_user_id {$field}_owner, '$rel_module' {$field}_mod";
3149 if(isset($rel_mod->field_defs['created_by']))
3151 $ret_array['secondary_select'].= " , ". $params['join_table_alias'] . ".created_by {$field}_owner , '$rel_module' {$field}_mod";
3157 if(isset($data['db_concat_fields']))
3159 $ret_array['secondary_select'] .= ' , ' . $this->db->concat($params['join_table_alias'], $data['db_concat_fields']) . ' ' . $field;
3163 if(!isset($data['relationship_fields']))
3165 $ret_array['secondary_select'] .= ' , ' . $params['join_table_alias'] . '.' . $data['rname'] . ' ' . $field;
3170 $ret_array['select'] .= ", ' ' $field ";
3173 foreach($used_join_key as $used_key) {
3174 if($used_key == $join['rel_key']) $count_used++;
3176 if($count_used <= 1) {//27416, the $ret_array['secondary_select'] should always generate, regardless the dbtype
3177 // add rel_key only if it was not aready added
3180 $ret_array['select'] .= ", ' ' " . $join['rel_key'] . ' ';
3182 $ret_array['secondary_select'] .= ', ' . $params['join_table_link_alias'].'.'. $join['rel_key'] .' ' . $join['rel_key'];
3184 if(isset($data['relationship_fields']))
3186 foreach($data['relationship_fields'] as $r_name=>$alias_name)
3188 if(!empty( $secondarySelectedFields[$alias_name]))continue;
3189 $ret_array['secondary_select'] .= ', ' . $params['join_table_link_alias'].'.'. $r_name .' ' . $alias_name;
3190 $secondarySelectedFields[$alias_name] = true;
3195 $ret_array['secondary_from'] .= ' ' . $join['join']. ' AND ' . $params['join_table_alias'].'.deleted=0';
3196 if (isset($data['link_type']) && $data['link_type'] == 'relationship_info' && ($parentbean instanceOf SugarBean))
3198 $ret_array['secondary_where'] = $params['join_table_link_alias'] . '.' . $join['rel_key']. "='" .$parentbean->id . "'";
3204 if(isset($data['db_concat_fields']))
3206 $ret_array['select'] .= ' , ' . $this->db->concat($params['join_table_alias'], $data['db_concat_fields']) . ' ' . $field;
3210 $ret_array['select'] .= ' , ' . $params['join_table_alias'] . '.' . $data['rname'] . ' ' . $field;
3212 if(isset($data['additionalFields'])){
3213 foreach($data['additionalFields'] as $k=>$v){
3214 $ret_array['select'] .= ' , ' . $params['join_table_alias'] . '.' . $k . ' ' . $v;
3219 $ret_array['from'] .= ' ' . $join['join']. ' AND ' . $params['join_table_alias'].'.deleted=0';
3220 if(!empty($beanList[$rel_module]) && !empty($beanFiles[$beanList[$rel_module]]))
3222 require_once($beanFiles[$beanList[$rel_module]]);
3223 $rel_mod = new $beanList[$rel_module]();
3224 if(isset($value['target_record_key']) && !empty($filter))
3226 $selectedFields[$this->table_name.'.'.$value['target_record_key']] = true;
3227 $ret_array['select'] .= " , $this->table_name.{$value['target_record_key']} ";
3229 if(isset($rel_mod->field_defs['assigned_user_id']))
3231 $ret_array['select'] .= ' , ' .$params['join_table_alias'] . '.assigned_user_id ' . $field . '_owner';
3235 $ret_array['select'] .= ' , ' .$params['join_table_alias'] . '.created_by ' . $field . '_owner';
3237 $ret_array['select'] .= " , '".$rel_module ."' " . $field . '_mod';
3242 // To fix SOAP stuff where we are trying to retrieve all the accounts data where accounts.id = ..
3243 // and this code changes accounts to jt4 as there is a self join with the accounts table.
3245 if(isset($data['db_concat_fields'])){
3246 $buildWhere = false;
3247 if(in_array('first_name', $data['db_concat_fields']) && in_array('last_name', $data['db_concat_fields']))
3249 $exp = '/\(\s*?'.$data['name'].'.*?\%\'\s*?\)/';
3250 if(preg_match($exp, $where, $matches))
3252 $search_expression = $matches[0];
3253 //Create three search conditions - first + last, first, last
3254 $first_name_search = str_replace($data['name'], $params['join_table_alias'] . '.first_name', $search_expression);
3255 $last_name_search = str_replace($data['name'], $params['join_table_alias'] . '.last_name', $search_expression);
3256 $full_name_search = str_replace($data['name'], $this->db->concat($params['join_table_alias'], $data['db_concat_fields']), $search_expression);
3258 $where = str_replace($search_expression, '(' . $full_name_search . ' OR ' . $first_name_search . ' OR ' . $last_name_search . ')', $where);
3264 $db_field = $this->db->concat($params['join_table_alias'], $data['db_concat_fields']);
3265 $where = preg_replace('/'.$data['name'].'/', $db_field, $where);
3268 $where = preg_replace('/(^|[\s(])' . $data['name'] . '/', '${1}' . $params['join_table_alias'] . '.'.$data['rname'], $where);
3272 $joined_tables[$params['join_table_alias']]=1;
3273 $joined_tables[$params['join_table_link_alias']]=1;
3282 if(isset($this->field_defs['assigned_user_id']) && empty($selectedFields[$this->table_name.'.assigned_user_id']))
3284 $ret_array['select'] .= ", $this->table_name.assigned_user_id ";
3286 else if(isset($this->field_defs['created_by']) && empty($selectedFields[$this->table_name.'.created_by']))
3288 $ret_array['select'] .= ", $this->table_name.created_by ";
3290 if(isset($this->field_defs['system_id']) && empty($selectedFields[$this->table_name.'.system_id']))
3292 $ret_array['select'] .= ", $this->table_name.system_id ";
3297 if ($ifListForExport) {
3298 if(isset($this->field_defs['email1'])) {
3299 $ret_array['select'].= " ,email_addresses.email_address email1";
3300 $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 ";
3304 $where_auto = '1=1';
3305 if($show_deleted == 0)
3307 $where_auto = "$this->table_name.deleted=0";
3308 }else if($show_deleted == 1)
3310 $where_auto = "$this->table_name.deleted=1";
3313 $ret_array['where'] = " where ($where) AND $where_auto";
3315 $ret_array['where'] = " where $where_auto";
3316 if(!empty($order_by))
3318 //make call to process the order by clause
3319 $ret_array['order_by'] = " ORDER BY ". $this->process_order_by($order_by);
3323 unset($ret_array['secondary_where']);
3324 unset($ret_array['secondary_from']);
3325 unset($ret_array['secondary_select']);
3333 return $ret_array['select'] . $ret_array['from'] . $ret_array['where']. $ret_array['order_by'];
3336 * Returns parent record data for objects that store relationship information
3338 * @param array $type_info
3340 * Interal function, do not override.
3342 function retrieve_parent_fields($type_info)
3345 global $beanList, $beanFiles;
3346 $templates = array();
3347 $parent_child_map = array();
3348 foreach($type_info as $children_info)
3350 foreach($children_info as $child_info)
3352 if($child_info['type'] == 'parent')
3354 if(empty($templates[$child_info['parent_type']]))
3356 //Test emails will have an invalid parent_type, don't try to load the non-existent parent bean
3357 if ($child_info['parent_type'] == 'test') {
3360 $class = $beanList[$child_info['parent_type']];
3361 // Added to avoid error below; just silently fail and write message to log
3362 if ( empty($beanFiles[$class]) ) {
3363 $GLOBALS['log']->error($this->object_name.'::retrieve_parent_fields() - cannot load class "'.$class.'", skip loading.');
3366 require_once($beanFiles[$class]);
3367 $templates[$child_info['parent_type']] = new $class();
3370 if(empty($queries[$child_info['parent_type']]))
3372 $queries[$child_info['parent_type']] = "SELECT id ";
3373 $field_def = $templates[$child_info['parent_type']]->field_defs['name'];
3374 if(isset($field_def['db_concat_fields']))
3376 $queries[$child_info['parent_type']] .= ' , ' . $this->db->concat($templates[$child_info['parent_type']]->table_name, $field_def['db_concat_fields']) . ' parent_name';
3380 $queries[$child_info['parent_type']] .= ' , name parent_name';
3382 if(isset($templates[$child_info['parent_type']]->field_defs['assigned_user_id']))
3384 $queries[$child_info['parent_type']] .= ", assigned_user_id parent_name_owner , '{$child_info['parent_type']}' parent_name_mod";;
3385 }else if(isset($templates[$child_info['parent_type']]->field_defs['created_by']))
3387 $queries[$child_info['parent_type']] .= ", created_by parent_name_owner, '{$child_info['parent_type']}' parent_name_mod";
3389 $queries[$child_info['parent_type']] .= " FROM " . $templates[$child_info['parent_type']]->table_name ." WHERE id IN ('{$child_info['parent_id']}'";
3393 if(empty($parent_child_map[$child_info['parent_id']]))
3394 $queries[$child_info['parent_type']] .= " ,'{$child_info['parent_id']}'";
3396 $parent_child_map[$child_info['parent_id']][] = $child_info['child_id'];
3401 foreach($queries as $query)
3403 $result = $this->db->query($query . ')');
3404 while($row = $this->db->fetchByAssoc($result))
3406 $results[$row['id']] = $row;
3410 $child_results = array();
3411 foreach($parent_child_map as $parent_key=>$parent_child)
3413 foreach($parent_child as $child)
3415 if(isset( $results[$parent_key]))
3417 $child_results[$child] = $results[$parent_key];
3421 return $child_results;
3425 * Processes the list query and return fetched row.
3427 * Internal function, do not override.
3428 * @param string $query select query to be processed.
3429 * @param int $row_offset starting position
3430 * @param int $limit Optioanl, default -1
3431 * @param int $max_per_page Optional, default -1
3432 * @param string $where Optional, additional filter criteria.
3433 * @return array Fetched data
3435 function process_list_query($query, $row_offset, $limit= -1, $max_per_page = -1, $where = '')
3437 global $sugar_config;
3438 $db = DBManagerFactory::getInstance('listviews');
3440 * if the row_offset is set to 'end' go to the end of the list
3442 $toEnd = strval($row_offset) == 'end';
3443 $GLOBALS['log']->debug("process_list_query: ".$query);
3444 if($max_per_page == -1)
3446 $max_per_page = $sugar_config['list_max_entries_per_page'];
3448 // Check to see if we have a count query available.
3449 if(empty($sugar_config['disable_count_query']) || $toEnd)
3451 $count_query = $this->create_list_count_query($query);
3452 if(!empty($count_query) && (empty($limit) || $limit == -1))
3454 // We have a count query. Run it and get the results.
3455 $result = $db->query($count_query, true, "Error running count query for $this->object_name List: ");
3456 $assoc = $db->fetchByAssoc($result);
3457 if(!empty($assoc['c']))
3459 $rows_found = $assoc['c'];
3460 $limit = $sugar_config['list_max_entries_per_page'];
3464 $row_offset = (floor(($rows_found -1) / $limit)) * $limit;
3470 if((empty($limit) || $limit == -1))
3472 $limit = $max_per_page + 1;
3473 $max_per_page = $limit;
3478 if(empty($row_offset))
3482 if(!empty($limit) && $limit != -1 && $limit != -99)
3484 $result = $db->limitQuery($query, $row_offset, $limit,true,"Error retrieving $this->object_name list: ");
3488 $result = $db->query($query,true,"Error retrieving $this->object_name list: ");
3493 $previous_offset = $row_offset - $max_per_page;
3494 $next_offset = $row_offset + $max_per_page;
3496 $class = get_class($this);
3497 //FIXME: Bug? we should remove the magic number -99
3498 //use -99 to return all
3499 $index = $row_offset;
3500 while ($max_per_page == -99 || ($index < $row_offset + $max_per_page))
3502 $row = $db->fetchByAssoc($result);
3503 if (empty($row)) break;
3505 //instantiate a new class each time. This is because php5 passes
3506 //by reference by default so if we continually update $this, we will
3507 //at the end have a list of all the same objects
3508 $temp = new $class();
3510 foreach($this->field_defs as $field=>$value)
3512 if (isset($row[$field]))
3514 $temp->$field = $row[$field];
3515 $owner_field = $field . '_owner';
3516 if(isset($row[$owner_field]))
3518 $temp->$owner_field = $row[$owner_field];
3521 $GLOBALS['log']->debug("$temp->object_name({$row['id']}): ".$field." = ".$temp->$field);
3522 }else if (isset($row[$this->table_name .'.'.$field]))
3524 $temp->$field = $row[$this->table_name .'.'.$field];
3532 $temp->check_date_relationships_load();
3533 $temp->fill_in_additional_list_fields();
3534 if($temp->hasCustomFields()) $temp->custom_fields->fill_relationships();
3535 $temp->call_custom_logic("process_record");
3537 // fix defect #44206. implement the same logic as sugar_currency_format
3538 // Smarty modifier does.
3539 if (property_exists($temp, 'currency_id') && -99 == $temp->currency_id)
3541 // manually retrieve default currency object as long as it's
3542 // not stored in database and thus cannot be joined in query
3543 require_once 'modules/Currencies/Currency.php';
3544 $currency = new Currency();
3545 $currency->retrieve($temp->currency_id);
3547 // walk through all currency-related fields
3548 foreach ($temp->field_defs as $temp_field)
3550 if (isset($temp_field['type']) && 'relate' == $temp_field['type']
3551 && isset($temp_field['module']) && 'Currencies' == $temp_field['module']
3552 && isset($temp_field['id_name']) && 'currency_id' == $temp_field['id_name'])
3554 // populate related properties manually
3555 $temp_property = $temp_field['name'];
3556 $currency_property = $temp_field['rname'];
3557 $temp->$temp_property = $currency->$currency_property;
3566 if(!empty($sugar_config['disable_count_query']) && !empty($limit))
3569 $rows_found = $row_offset + count($list);
3571 unset($list[$limit - 1]);
3577 } else if(!isset($rows_found)){
3578 $rows_found = $row_offset + count($list);
3581 $response = Array();
3582 $response['list'] = $list;
3583 $response['row_count'] = $rows_found;
3584 $response['next_offset'] = $next_offset;
3585 $response['previous_offset'] = $previous_offset;
3586 $response['current_offset'] = $row_offset ;
3591 * Returns the number of rows that the given SQL query should produce
3593 * Internal function, do not override.
3594 * @param string $query valid select query
3595 * @param boolean $is_count_query Optional, Default false, set to true if passed query is a count query.
3596 * @return int count of rows found
3598 function _get_num_rows_in_query($query, $is_count_query=false)
3600 $num_rows_in_query = 0;
3601 if (!$is_count_query)
3603 $count_query = SugarBean::create_list_count_query($query);
3605 $count_query=$query;
3607 $result = $this->db->query($count_query, true, "Error running count query for $this->object_name List: ");
3609 while($row = $this->db->fetchByAssoc($result, true))
3611 $num_rows_in_query += current($row);
3614 return $num_rows_in_query;
3618 * Applies pagination window to union queries used by list view and subpanels,
3619 * executes the query and returns fetched data.
3621 * Internal function, do not override.
3622 * @param object $parent_bean
3623 * @param string $query query to be processed.
3624 * @param int $row_offset
3625 * @param int $limit optional, default -1
3626 * @param int $max_per_page Optional, default -1
3627 * @param string $where Custom where clause.
3628 * @param array $subpanel_def definition of sub-panel to be processed
3629 * @param string $query_row_count
3630 * @param array $seconday_queries
3631 * @return array Fetched data.
3633 function process_union_list_query($parent_bean, $query,
3634 $row_offset, $limit= -1, $max_per_page = -1, $where = '', $subpanel_def, $query_row_count='', $secondary_queries = array())
3637 $db = DBManagerFactory::getInstance('listviews');
3639 * if the row_offset is set to 'end' go to the end of the list
3641 $toEnd = strval($row_offset) == 'end';
3642 global $sugar_config;
3643 $use_count_query=false;
3644 $processing_collection=$subpanel_def->isCollection();
3646 $GLOBALS['log']->debug("process_union_list_query: ".$query);
3647 if($max_per_page == -1)
3649 $max_per_page = $sugar_config['list_max_entries_per_subpanel'];
3651 if(empty($query_row_count))
3653 $query_row_count = $query;
3655 $distinct_position=strpos($query_row_count,"DISTINCT");
3657 if ($distinct_position!= false)
3659 $use_count_query=true;
3661 $performSecondQuery = true;
3662 if(empty($sugar_config['disable_count_query']) || $toEnd)
3664 $rows_found = $this->_get_num_rows_in_query($query_row_count,$use_count_query);
3667 $performSecondQuery = false;
3669 if(!empty($rows_found) && (empty($limit) || $limit == -1))
3671 $limit = $sugar_config['list_max_entries_per_subpanel'];
3675 $row_offset = (floor(($rows_found -1) / $limit)) * $limit;
3681 if((empty($limit) || $limit == -1))
3683 $limit = $max_per_page + 1;
3684 $max_per_page = $limit;
3688 if(empty($row_offset))
3693 $previous_offset = $row_offset - $max_per_page;
3694 $next_offset = $row_offset + $max_per_page;
3696 if($performSecondQuery)
3698 if(!empty($limit) && $limit != -1 && $limit != -99)
3700 $result = $db->limitQuery($query, $row_offset, $limit,true,"Error retrieving $parent_bean->object_name list: ");
3704 $result = $db->query($query,true,"Error retrieving $this->object_name list: ");
3706 //use -99 to return all
3708 // get the current row
3709 $index = $row_offset;
3710 $row = $db->fetchByAssoc($result);
3712 $post_retrieve = array();
3713 $isFirstTime = true;
3716 $function_fields = array();
3717 if(($index < $row_offset + $max_per_page || $max_per_page == -99))
3719 if ($processing_collection)
3721 $current_bean =$subpanel_def->sub_subpanels[$row['panel_name']]->template_instance;
3724 $class = get_class($subpanel_def->sub_subpanels[$row['panel_name']]->template_instance);
3725 $current_bean = new $class();
3728 $current_bean=$subpanel_def->template_instance;
3731 $class = get_class($subpanel_def->template_instance);
3732 $current_bean = new $class();
3735 $isFirstTime = false;
3736 //set the panel name in the bean instance.
3737 if (isset($row['panel_name']))
3739 $current_bean->panel_name=$row['panel_name'];
3741 foreach($current_bean->field_defs as $field=>$value)
3744 if (isset($row[$field]))
3746 $current_bean->$field = $this->convertField($row[$field], $value);
3747 unset($row[$field]);
3749 else if (isset($row[$this->table_name .'.'.$field]))
3751 $current_bean->$field = $this->convertField($row[$this->table_name .'.'.$field], $value);
3752 unset($row[$this->table_name .'.'.$field]);
3756 $current_bean->$field = "";
3757 unset($row[$field]);
3759 if(isset($value['source']) && $value['source'] == 'function')
3761 $function_fields[]=$field;
3764 foreach($row as $key=>$value)
3766 $current_bean->$key = $value;
3768 foreach($function_fields as $function_field)
3770 $value = $current_bean->field_defs[$function_field];
3771 $can_execute = true;
3772 $execute_params = array();
3773 $execute_function = array();
3774 if(!empty($value['function_class']))
3776 $execute_function[] = $value['function_class'];
3777 $execute_function[] = $value['function_name'];
3781 $execute_function = $value['function_name'];
3783 foreach($value['function_params'] as $param )
3785 if (empty($value['function_params_source']) or $value['function_params_source']=='parent')
3787 if(empty($this->$param))
3789 $can_execute = false;
3790 } else if($param == '$this') {
3791 $execute_params[] = $this;
3795 $execute_params[] = $this->$param;
3797 } else if ($value['function_params_source']=='this')
3799 if(empty($current_bean->$param))
3801 $can_execute = false;
3802 } else if($param == '$this') {
3803 $execute_params[] = $current_bean;
3807 $execute_params[] = $current_bean->$param;
3812 $can_execute = false;
3818 if(!empty($value['function_require']))
3820 require_once($value['function_require']);
3822 $current_bean->$function_field = call_user_func_array($execute_function, $execute_params);
3825 if(!empty($current_bean->parent_type) && !empty($current_bean->parent_id))
3827 if(!isset($post_retrieve[$current_bean->parent_type]))
3829 $post_retrieve[$current_bean->parent_type] = array();
3831 $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');
3833 //$current_bean->fill_in_additional_list_fields();
3834 $list[$current_bean->id] = $current_bean;
3836 // go to the next row
3838 $row = $db->fetchByAssoc($result);
3840 //now handle retrieving many-to-many relationships
3843 foreach($secondary_queries as $query2)
3845 $result2 = $db->query($query2);
3847 $row2 = $db->fetchByAssoc($result2);
3850 $id_ref = $row2['ref_id'];
3852 if(isset($list[$id_ref]))
3854 foreach($row2 as $r2key=>$r2value)
3856 if($r2key != 'ref_id')
3858 $list[$id_ref]->$r2key = $r2value;
3862 $row2 = $db->fetchByAssoc($result2);
3868 if(isset($post_retrieve))
3870 $parent_fields = $this->retrieve_parent_fields($post_retrieve);
3874 $parent_fields = array();
3876 if(!empty($sugar_config['disable_count_query']) && !empty($limit))
3878 //C.L. Bug 43535 - Use the $index value to set the $rows_found value here
3879 $rows_found = isset($index) ? $index : $row_offset + count($list);
3881 if(count($list) >= $limit)
3895 $parent_fields = array();
3897 $response = array();
3898 $response['list'] = $list;
3899 $response['parent_data'] = $parent_fields;
3900 $response['row_count'] = $rows_found;
3901 $response['next_offset'] = $next_offset;
3902 $response['previous_offset'] = $previous_offset;
3903 $response['current_offset'] = $row_offset ;
3904 $response['query'] = $query;
3910 * Applies pagination window to select queries used by detail view,
3911 * executes the query and returns fetched data.
3913 * Internal function, do not override.
3914 * @param string $query query to be processed.
3915 * @param int $row_offset
3916 * @param int $limit optional, default -1
3917 * @param int $max_per_page Optional, default -1
3918 * @param string $where Custom where clause.
3919 * @param int $offset Optional, default 0
3920 * @return array Fetched data.
3923 function process_detail_query($query, $row_offset, $limit= -1, $max_per_page = -1, $where = '', $offset = 0)
3925 global $sugar_config;
3926 $GLOBALS['log']->debug("process_detail_query: ".$query);
3927 if($max_per_page == -1)
3929 $max_per_page = $sugar_config['list_max_entries_per_page'];
3932 // Check to see if we have a count query available.
3933 $count_query = $this->create_list_count_query($query);
3935 if(!empty($count_query) && (empty($limit) || $limit == -1))
3937 // We have a count query. Run it and get the results.
3938 $result = $this->db->query($count_query, true, "Error running count query for $this->object_name List: ");
3939 $assoc = $this->db->fetchByAssoc($result);
3940 if(!empty($assoc['c']))
3942 $total_rows = $assoc['c'];
3946 if(empty($row_offset))
3951 $result = $this->db->limitQuery($query, $offset, 1, true,"Error retrieving $this->object_name list: ");
3953 $previous_offset = $row_offset - $max_per_page;
3954 $next_offset = $row_offset + $max_per_page;
3956 $row = $this->db->fetchByAssoc($result);
3957 $this->retrieve($row['id']);
3959 $response = Array();
3960 $response['bean'] = $this;
3961 if (empty($total_rows))
3963 $response['row_count'] = $total_rows;
3964 $response['next_offset'] = $next_offset;
3965 $response['previous_offset'] = $previous_offset;
3971 * Processes fetched list view data
3973 * Internal function, do not override.
3974 * @param string $query query to be processed.
3975 * @param boolean $check_date Optional, default false. if set to true date time values are processed.
3976 * @return array Fetched data.
3979 function process_full_list_query($query, $check_date=false)
3982 $GLOBALS['log']->debug("process_full_list_query: query is ".$query);
3983 $result = $this->db->query($query, false);
3984 $GLOBALS['log']->debug("process_full_list_query: result is ".print_r($result,true));
3985 $class = get_class($this);
3986 $isFirstTime = true;
3987 $bean = new $class();
3989 // We have some data.
3990 while (($row = $bean->db->fetchByAssoc($result)) != null)
3992 $row = $this->convertRow($row);
3995 $bean = new $class();
3997 $isFirstTime = false;
3999 foreach($bean->field_defs as $field=>$value)
4001 if (isset($row[$field]))
4003 $bean->$field = $row[$field];
4004 $GLOBALS['log']->debug("process_full_list: $bean->object_name({$row['id']}): ".$field." = ".$bean->$field);
4013 $bean->processed_dates_times = array();
4014 $bean->check_date_relationships_load();
4016 $bean->fill_in_additional_list_fields();
4017 $bean->call_custom_logic("process_record");
4018 $bean->fetched_row = $row;
4023 if (isset($list)) return $list;
4028 * Tracks the viewing of a detail record.
4029 * This leverages get_summary_text() which is object specific.
4031 * Internal function, do not override.
4032 * @param string $user_id - String value of the user that is viewing the record.
4033 * @param string $current_module - String value of the module being processed.
4034 * @param string $current_view - String value of the current view
4036 function track_view($user_id, $current_module, $current_view='')
4038 $trackerManager = TrackerManager::getInstance();
4039 if($monitor = $trackerManager->getMonitor('tracker')){
4040 $monitor->setValue('date_modified', $GLOBALS['timedate']->nowDb());
4041 $monitor->setValue('user_id', $user_id);
4042 $monitor->setValue('module_name', $current_module);
4043 $monitor->setValue('action', $current_view);
4044 $monitor->setValue('item_id', $this->id);
4045 $monitor->setValue('item_summary', $this->get_summary_text());
4046 $monitor->setValue('visible', $this->tracker_visibility);
4047 $trackerManager->saveMonitor($monitor);
4052 * Returns the summary text that should show up in the recent history list for this object.
4056 public function get_summary_text()
4058 return "Base Implementation. Should be overridden.";
4062 * This is designed to be overridden and add specific fields to each record.
4063 * This allows the generic query to fill in the major fields, and then targeted
4064 * queries to get related fields and add them to the record. The contact's
4065 * account for instance. This method is only used for populating extra fields
4068 function fill_in_additional_list_fields(){
4069 if(!empty($this->field_defs['parent_name']) && empty($this->parent_name)){
4070 $this->fill_in_additional_parent_fields();
4075 * This is designed to be overridden and add specific fields to each record.
4076 * This allows the generic query to fill in the major fields, and then targeted
4077 * queries to get related fields and add them to the record. The contact's
4078 * account for instance. This method is only used for populating extra fields
4079 * in the detail form
4081 function fill_in_additional_detail_fields()
4083 if(!empty($this->field_defs['assigned_user_name']) && !empty($this->assigned_user_id)){
4085 $this->assigned_user_name = get_assigned_user_name($this->assigned_user_id);
4088 if(!empty($this->field_defs['created_by']) && !empty($this->created_by))
4089 $this->created_by_name = get_assigned_user_name($this->created_by);
4090 if(!empty($this->field_defs['modified_user_id']) && !empty($this->modified_user_id))
4091 $this->modified_by_name = get_assigned_user_name($this->modified_user_id);
4093 if(!empty($this->field_defs['parent_name'])){
4094 $this->fill_in_additional_parent_fields();
4099 * This is desgined to be overridden or called from extending bean. This method
4100 * will fill in any parent_name fields.
4102 function fill_in_additional_parent_fields() {
4104 if(!empty($this->parent_id) && !empty($this->last_parent_id) && $this->last_parent_id == $this->parent_id){
4107 $this->parent_name = '';
4109 if(!empty($this->parent_type)) {
4110 $this->last_parent_id = $this->parent_id;
4111 $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'));
4112 if(!empty($this->parent_first_name) || !empty($this->parent_last_name) ){
4113 $this->parent_name = $GLOBALS['locale']->getLocaleFormattedName($this->parent_first_name, $this->parent_last_name);
4114 } else if(!empty($this->parent_document_name)){
4115 $this->parent_name = $this->parent_document_name;
4121 * Fill in a link field
4124 function fill_in_link_field( $linkFieldName , $def)
4126 $idField = $linkFieldName;
4127 //If the id_name provided really was an ID, don't try to load it as a link. Use the normal link
4128 if (!empty($this->field_defs[$linkFieldName]['type']) && $this->field_defs[$linkFieldName]['type'] == "id" && !empty($def['link']))
4130 $linkFieldName = $def['link'];
4132 if ($this->load_relationship($linkFieldName))
4134 $list=$this->$linkFieldName->get();
4135 $this->$idField = '' ; // match up with null value in $this->populateFromRow()
4137 $this->$idField = $list[0];
4142 * Fill in fields where type = relate
4144 function fill_in_relationship_fields(){
4145 global $fill_in_rel_depth;
4146 if(empty($fill_in_rel_depth) || $fill_in_rel_depth < 0)
4147 $fill_in_rel_depth = 0;
4149 if($fill_in_rel_depth > 1)
4152 $fill_in_rel_depth++;
4154 foreach($this->field_defs as $field)
4156 if(0 == strcmp($field['type'],'relate') && !empty($field['module']))
4158 $name = $field['name'];
4159 if(empty($this->$name))
4161 // 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']
4162 $related_module = $field['module'];
4163 $id_name = $field['id_name'];
4165 if (empty($this->$id_name))
4167 $this->fill_in_link_field($id_name, $field);
4169 if(!empty($this->$id_name) && ( $this->object_name != $related_module || ( $this->object_name == $related_module && $this->$id_name != $this->id ))){
4170 if(isset($GLOBALS['beanList'][ $related_module])){
4171 $class = $GLOBALS['beanList'][$related_module];
4173 if(!empty($this->$id_name) && file_exists($GLOBALS['beanFiles'][$class]) && isset($this->$name)){
4174 require_once($GLOBALS['beanFiles'][$class]);
4175 $mod = new $class();
4177 // disable row level security in order to be able
4178 // to retrieve related bean properties (bug #44928)
4180 $mod->retrieve($this->$id_name);
4182 if (!empty($field['rname'])) {
4183 $this->$name = $mod->$field['rname'];
4184 } else if (isset($mod->name)) {
4185 $this->$name = $mod->name;
4190 if(!empty($this->$id_name) && isset($this->$name))
4192 if(!isset($field['additionalFields']))
4193 $field['additionalFields'] = array();
4194 if(!empty($field['rname']))
4196 $field['additionalFields'][$field['rname']]= $name;
4200 $field['additionalFields']['name']= $name;
4202 $this->getRelatedFields($related_module, $this->$id_name, $field['additionalFields']);
4207 $fill_in_rel_depth--;
4211 * This is a helper function that is used to quickly created indexes when creating tables.
4213 function create_index($query)
4215 $GLOBALS['log']->info("create_index: $query");
4217 $result = $this->db->query($query, true, "Error creating index:");
4221 * This function should be overridden in each module. It marks an item as deleted.
4223 * If it is not overridden, then marking this type of item is not allowed
4225 function mark_deleted($id)
4227 global $current_user;
4228 $date_modified = $GLOBALS['timedate']->nowDb();
4229 if(isset($_SESSION['show_deleted']))
4231 $this->mark_undeleted($id);
4235 // call the custom business logic
4236 $custom_logic_arguments['id'] = $id;
4237 $this->call_custom_logic("before_delete", $custom_logic_arguments);
4239 $this->mark_relationships_deleted($id);
4240 if ( isset($this->field_defs['modified_user_id']) ) {
4241 if (!empty($current_user)) {
4242 $this->modified_user_id = $current_user->id;
4244 $this->modified_user_id = 1;
4246 $query = "UPDATE $this->table_name set deleted=1 , date_modified = '$date_modified', modified_user_id = '$this->modified_user_id' where id='$id'";
4248 $query = "UPDATE $this->table_name set deleted=1 , date_modified = '$date_modified' where id='$id'";
4250 $this->db->query($query, true,"Error marking record deleted: ");
4252 SugarRelationship::resaveRelatedBeans();
4254 // Take the item off the recently viewed lists
4255 $tracker = new Tracker();
4256 $tracker->makeInvisibleForAll($id);
4258 // call the custom business logic
4259 $this->call_custom_logic("after_delete", $custom_logic_arguments);
4264 * Restores data deleted by call to mark_deleted() function.
4266 * Internal function, do not override.
4268 function mark_undeleted($id)
4270 // call the custom business logic
4271 $custom_logic_arguments['id'] = $id;
4272 $this->call_custom_logic("before_restore", $custom_logic_arguments);
4274 $date_modified = $GLOBALS['timedate']->nowDb();
4275 $query = "UPDATE $this->table_name set deleted=0 , date_modified = '$date_modified' where id='$id'";
4276 $this->db->query($query, true,"Error marking record undeleted: ");
4278 // call the custom business logic
4279 $this->call_custom_logic("after_restore", $custom_logic_arguments);
4283 * This function deletes relationships to this object. It should be overridden
4284 * to handle the relationships of the specific object.
4285 * This function is called when the item itself is being deleted.
4287 * @param int $id id of the relationship to delete
4289 function mark_relationships_deleted($id)
4291 $this->delete_linked($id);
4295 * This function is used to execute the query and create an array template objects
4296 * from the resulting ids from the query.
4297 * It is currently used for building sub-panel arrays.
4299 * @param string $query - the query that should be executed to build the list
4300 * @param object $template - The object that should be used to copy the records.
4301 * @param int $row_offset Optional, default 0
4302 * @param int $limit Optional, default -1
4305 function build_related_list($query, &$template, $row_offset = 0, $limit = -1)
4307 $GLOBALS['log']->debug("Finding linked records $this->object_name: ".$query);
4308 $db = DBManagerFactory::getInstance('listviews');
4310 if(!empty($row_offset) && $row_offset != 0 && !empty($limit) && $limit != -1)
4312 $result = $db->limitQuery($query, $row_offset, $limit,true,"Error retrieving $template->object_name list: ");
4316 $result = $db->query($query, true);
4320 $isFirstTime = true;
4321 $class = get_class($template);
4322 while($row = $this->db->fetchByAssoc($result))
4326 $template = new $class();
4328 $isFirstTime = false;
4329 $record = $template->retrieve($row['id']);
4333 // this copies the object into the array
4334 $list[] = $template;
4341 * This function is used to execute the query and create an array template objects
4342 * from the resulting ids from the query.
4343 * It is currently used for building sub-panel arrays. It supports an additional
4344 * where clause that is executed as a filter on the results
4346 * @param string $query - the query that should be executed to build the list
4347 * @param object $template - The object that should be used to copy the records.
4349 function build_related_list_where($query, &$template, $where='', $in='', $order_by, $limit='', $row_offset = 0)
4351 $db = DBManagerFactory::getInstance('listviews');
4352 // No need to do an additional query
4353 $GLOBALS['log']->debug("Finding linked records $this->object_name: ".$query);
4354 if(empty($in) && !empty($query))
4356 $idList = $this->build_related_in($query);
4357 $in = $idList['in'];
4359 // MFH - Added Support For Custom Fields in Searches
4361 if(isset($this->custom_fields)) {
4362 $custom_join = $this->custom_fields->getJOIN();
4365 $query = "SELECT id ";
4367 if (!empty($custom_join)) {
4368 $query .= $custom_join['select'];
4370 $query .= " FROM $this->table_name ";
4372 if (!empty($custom_join) && !empty($custom_join['join'])) {
4373 $query .= " " . $custom_join['join'];
4376 $query .= " WHERE deleted=0 AND id IN $in";
4379 $query .= " AND $where";
4383 if(!empty($order_by))
4385 $query .= "ORDER BY $order_by";
4389 $result = $db->limitQuery($query, $row_offset, $limit,true,"Error retrieving $this->object_name list: ");
4393 $result = $db->query($query, true);
4397 $isFirstTime = true;
4398 $class = get_class($template);
4400 $disable_security_flag = ($template->disable_row_level_security) ? true : false;
4401 while($row = $db->fetchByAssoc($result))
4405 $template = new $class();
4406 $template->disable_row_level_security = $disable_security_flag;
4408 $isFirstTime = false;
4409 $record = $template->retrieve($row['id']);
4412 // this copies the object into the array
4413 $list[] = $template;
4421 * Constructs an comma separated list of ids from passed query results.
4423 * @param string @query query to be executed.
4426 function build_related_in($query)
4429 $result = $this->db->query($query, true);
4431 while($row = $this->db->fetchByAssoc($result))
4433 $idList[] = $row['id'];
4436 $ids = "('" . $row['id'] . "'";
4440 $ids .= ",'" . $row['id'] . "'";
4450 return array('list'=>$idList, 'in'=>$ids);
4454 * Optionally copies values from fetched row into the bean.
4456 * Internal function, do not override.
4458 * @param string $query - the query that should be executed to build the list
4459 * @param object $template - The object that should be used to copy the records
4460 * @param array $field_list List of fields.
4463 function build_related_list2($query, &$template, &$field_list)
4465 $GLOBALS['log']->debug("Finding linked values $this->object_name: ".$query);
4467 $result = $this->db->query($query, true);
4470 $isFirstTime = true;
4471 $class = get_class($template);
4472 while($row = $this->db->fetchByAssoc($result))
4474 // Create a blank copy
4478 $copy = new $class();
4480 $isFirstTime = false;
4481 foreach($field_list as $field)
4483 // Copy the relevant fields
4484 $copy->$field = $row[$field];
4488 // this copies the object into the array
4496 * Let implementing classes to fill in row specific columns of a list view form
4499 function list_view_parse_additional_sections(&$list_form)
4503 * Assigns all of the values into the template for the list view
4505 function get_list_view_array()
4507 static $cache = array();
4508 // cn: bug 12270 - sensitive fields being passed arbitrarily in listViews
4509 $sensitiveFields = array('user_hash' => '');
4511 $return_array = Array();
4512 global $app_list_strings, $mod_strings;
4513 foreach($this->field_defs as $field=>$value){
4515 if(isset($this->$field)){
4517 // cn: bug 12270 - sensitive fields being passed arbitrarily in listViews
4518 if(isset($sensitiveFields[$field]))
4520 if(!isset($cache[$field]))
4521 $cache[$field] = strtoupper($field);
4523 //Fields hidden by Dependent Fields
4524 if (isset($value['hidden']) && $value['hidden'] === true) {
4525 $return_array[$cache[$field]] = "";
4528 //cn: if $field is a _dom, detect and return VALUE not KEY
4529 //cl: empty function check for meta-data enum types that have values loaded from a function
4530 else if (((!empty($value['type']) && ($value['type'] == 'enum' || $value['type'] == 'radioenum') )) && empty($value['function'])){
4531 if(!empty($value['options']) && !empty($app_list_strings[$value['options']][$this->$field])){
4532 $return_array[$cache[$field]] = $app_list_strings[$value['options']][$this->$field];
4534 //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.
4535 elseif(!empty($value['options']) && !empty($mod_strings[$value['options']][$this->$field]))
4537 $return_array[$cache[$field]] = $mod_strings[$value['options']][$this->$field];
4540 $return_array[$cache[$field]] = $this->$field;
4543 // tjy: no need to do this str_replace as the changes in 29994 for ListViewGeneric.tpl for translation handle this now
4544 // }elseif(!empty($value['type']) && $value['type'] == 'multienum'&& empty($value['function'])){
4545 // $return_array[strtoupper($field)] = str_replace('^,^', ', ', $this->$field );
4546 }elseif(!empty($value['custom_module']) && $value['type'] != 'currency'){
4547 // $this->format_field($value);
4548 $return_array[$cache[$field]] = $this->$field;
4550 $return_array[$cache[$field]] = $this->$field;
4552 // handle "Assigned User Name"
4553 if($field == 'assigned_user_name'){
4554 $return_array['ASSIGNED_USER_NAME'] = get_assigned_user_name($this->assigned_user_id);
4558 return $return_array;
4561 * Override this function to set values in the array used to render list view data.
4564 function get_list_view_data()
4566 return $this->get_list_view_array();
4570 * Construct where clause from a list of name-value pairs.
4571 * @param array $fields_array Name/value pairs for column checks
4572 * @param boolean $deleted Optional, default true, if set to false deleted filter will not be added.
4573 * @return string The WHERE clause
4575 function get_where($fields_array, $deleted=true)
4578 foreach ($fields_array as $name=>$value)
4580 if (!empty($where_clause)) {
4581 $where_clause .= " AND ";
4583 $name = $this->db->getValidDBName($name);
4585 $where_clause .= "$name = ".$this->db->quoted($value,false);
4587 if(!empty($where_clause)) {
4589 return "WHERE $where_clause AND deleted=0";
4591 return "WHERE $where_clause";
4600 * Constructs a select query and fetch 1 row using this query, and then process the row
4602 * Internal function, do not override.
4603 * @param array @fields_array array of name value pairs used to construct query.
4604 * @param boolean $encode Optional, default true, encode fetched data.
4605 * @param boolean $deleted Optional, default true, if set to false deleted filter will not be added.
4606 * @return object Instance of this bean with fetched data.
4608 function retrieve_by_string_fields($fields_array, $encode=true, $deleted=true)
4610 $where_clause = $this->get_where($fields_array, $deleted);
4611 if(isset($this->custom_fields))
4612 $custom_join = $this->custom_fields->getJOIN();
4613 else $custom_join = false;
4616 $query = "SELECT $this->table_name.*". $custom_join['select']. " FROM $this->table_name " . $custom_join['join'];
4620 $query = "SELECT $this->table_name.* FROM $this->table_name ";
4622 $query .= " $where_clause";
4623 $GLOBALS['log']->debug("Retrieve $this->object_name: ".$query);
4624 //requireSingleResult has been deprecated.
4625 //$result = $this->db->requireSingleResult($query, true, "Retrieving record $where_clause:");
4626 $result = $this->db->limitQuery($query,0,1,true, "Retrieving record $where_clause:");
4633 $row = $this->db->fetchByAssoc($result, $encode);
4638 // Removed getRowCount-if-clause earlier and insert duplicates_found here as it seems that we have found something
4639 // if we didn't return null in the previous clause.
4640 $this->duplicates_found = true;
4641 $row = $this->convertRow($row);
4642 $this->fetched_row = $row;
4643 $this->fromArray($row);
4644 $this->is_updated_dependent_fields = false;
4645 $this->fill_in_additional_detail_fields();
4650 * This method is called during an import before inserting a bean
4651 * Define an associative array called $special_fields
4652 * the keys are user defined, and don't directly map to the bean's fields
4653 * the value is the method name within that bean that will do extra
4654 * processing for that field. example: 'full_name'=>'get_names_from_full_name'
4657 function process_special_fields()
4659 foreach ($this->special_functions as $func_name)
4661 if ( method_exists($this,$func_name) )
4663 $this->$func_name();
4669 * Override this function to build a where clause based on the search criteria set into bean .
4672 function build_generic_where_clause($value)
4676 function getRelatedFields($module, $id, $fields, $return_array = false){
4677 if(empty($GLOBALS['beanList'][$module]))return '';
4678 $object = BeanFactory::getObjectName($module);
4680 VardefManager::loadVardef($module, $object);
4681 if(empty($GLOBALS['dictionary'][$object]['table']))return '';
4682 $table = $GLOBALS['dictionary'][$object]['table'];
4683 $query = 'SELECT id';
4684 foreach($fields as $field=>$alias){
4685 if(!empty($GLOBALS['dictionary'][$object]['fields'][$field]['db_concat_fields'])){
4686 $query .= ' ,' .$this->db->concat($table, $GLOBALS['dictionary'][$object]['fields'][$field]['db_concat_fields']) . ' as ' . $alias ;
4687 }else if(!empty($GLOBALS['dictionary'][$object]['fields'][$field]) &&
4688 (empty($GLOBALS['dictionary'][$object]['fields'][$field]['source']) ||
4689 $GLOBALS['dictionary'][$object]['fields'][$field]['source'] != "non-db"))
4691 $query .= ' ,' .$table . '.' . $field . ' as ' . $alias;
4693 if(!$return_array)$this->$alias = '';
4695 if($query == 'SELECT id' || empty($id)){
4700 if(isset($GLOBALS['dictionary'][$object]['fields']['assigned_user_id']))
4702 $query .= " , ". $table . ".assigned_user_id owner";
4705 else if(isset($GLOBALS['dictionary'][$object]['fields']['created_by']))
4707 $query .= " , ". $table . ".created_by owner";
4710 $query .= ' FROM ' . $table . ' WHERE deleted=0 AND id=';
4711 $result = $GLOBALS['db']->query($query . "'$id'" );
4712 $row = $GLOBALS['db']->fetchByAssoc($result);
4716 $owner = (empty($row['owner']))?'':$row['owner'];
4717 foreach($fields as $alias){
4718 $this->$alias = (!empty($row[$alias]))? $row[$alias]: '';
4719 $alias = $alias .'_owner';
4720 $this->$alias = $owner;
4721 $a_mod = $alias .'_mod';
4722 $this->$a_mod = $module;
4729 function &parse_additional_headers(&$list_form, $xTemplateSection)
4734 function assign_display_fields($currentModule)
4737 foreach($this->column_fields as $field)
4739 if(isset($this->field_name_map[$field]) && empty($this->$field))
4741 if($this->field_name_map[$field]['type'] != 'date' && $this->field_name_map[$field]['type'] != 'enum')
4742 $this->$field = $field;
4743 if($this->field_name_map[$field]['type'] == 'date')
4745 $this->$field = $timedate->to_display_date('1980-07-09');
4747 if($this->field_name_map[$field]['type'] == 'enum')
4749 $dom = $this->field_name_map[$field]['options'];
4750 global $current_language, $app_list_strings;
4751 $mod_strings = return_module_language($current_language, $currentModule);
4753 if(isset($mod_strings[$dom]))
4755 $options = $mod_strings[$dom];
4756 foreach($options as $key=>$value)
4758 if(!empty($key) && empty($this->$field ))
4760 $this->$field = $key;
4764 if(isset($app_list_strings[$dom]))
4766 $options = $app_list_strings[$dom];
4767 foreach($options as $key=>$value)
4769 if(!empty($key) && empty($this->$field ))
4771 $this->$field = $key;
4783 * RELATIONSHIP HANDLING
4786 function set_relationship($table, $relate_values, $check_duplicates = true,$do_update=false,$data_values=null)
4790 // make sure there is a date modified
4791 $date_modified = $this->db->convert("'".$GLOBALS['timedate']->nowDb()."'", 'datetime');
4794 if($check_duplicates)
4796 $query = "SELECT * FROM $table ";
4797 $where = "WHERE deleted = '0' ";
4798 foreach($relate_values as $name=>$value)
4800 $where .= " AND $name = '$value' ";
4803 $result = $this->db->query($query, false, "Looking For Duplicate Relationship:" . $query);
4804 $row=$this->db->fetchByAssoc($result);
4807 if(!$check_duplicates || empty($row) )
4809 unset($relate_values['id']);
4810 if ( isset($data_values))
4812 $relate_values = array_merge($relate_values,$data_values);
4814 $query = "INSERT INTO $table (id, ". implode(',', array_keys($relate_values)) . ", date_modified) VALUES ('" . create_guid() . "', " . "'" . implode("', '", $relate_values) . "', ".$date_modified.")" ;
4816 $this->db->query($query, false, "Creating Relationship:" . $query);
4818 else if ($do_update)
4821 foreach($data_values as $key=>$value)
4823 array_push($conds,$key."='".$this->db->quote($value)."'");
4825 $query = "UPDATE $table SET ". implode(',', $conds).",date_modified=".$date_modified." ".$where;
4826 $this->db->query($query, false, "Updating Relationship:" . $query);
4830 function retrieve_relationships($table, $values, $select_id)
4832 $query = "SELECT $select_id FROM $table WHERE deleted = 0 ";
4833 foreach($values as $name=>$value)
4835 $query .= " AND $name = '$value' ";
4837 $query .= " ORDER BY $select_id ";
4838 $result = $this->db->query($query, false, "Retrieving Relationship:" . $query);
4840 while($row = $this->db->fetchByAssoc($result))
4847 // TODO: this function needs adjustment
4848 function loadLayoutDefs()
4850 global $layout_defs;
4851 if(empty( $this->layout_def) && file_exists('modules/'. $this->module_dir . '/layout_defs.php'))
4853 include_once('modules/'. $this->module_dir . '/layout_defs.php');
4854 if(file_exists('custom/modules/'. $this->module_dir . '/Ext/Layoutdefs/layoutdefs.ext.php'))
4856 include_once('custom/modules/'. $this->module_dir . '/Ext/Layoutdefs/layoutdefs.ext.php');
4858 if ( empty( $layout_defs[get_class($this)]))
4860 echo "\$layout_defs[" . get_class($this) . "]; does not exist";
4863 $this->layout_def = $layout_defs[get_class($this)];
4868 * Trigger custom logic for this module that is defined for the provided hook
4869 * The custom logic file is located under custom/modules/[CURRENT_MODULE]/logic_hooks.php.
4870 * That file should define the $hook_version that should be used.
4871 * It should also define the $hook_array. The $hook_array will be a two dimensional array
4872 * the first dimension is the name of the event, the second dimension is the information needed
4873 * to fire the hook. Each entry in the top level array should be defined on a single line to make it
4874 * easier to automatically replace this file. There should be no contents of this file that are not replacable.
4876 * $hook_array['before_save'][] = Array(1, testtype, 'custom/modules/Leads/test12.php', 'TestClass', 'lead_before_save_1');
4877 * This sample line creates a before_save hook. The hooks are procesed in the order in which they
4878 * are added to the array. The second dimension is an array of:
4879 * processing index (for sorting before exporting the array)
4882 * php file to include
4883 * php class the method is in
4884 * php method to call
4886 * The method signature for version 1 hooks is:
4887 * function NAME(&$bean, $event, $arguments)
4888 * $bean - $this bean passed in by reference.
4889 * $event - The string for the current event (i.e. before_save)
4890 * $arguments - An array of arguments that are specific to the event.
4892 function call_custom_logic($event, $arguments = null)
4894 if(!isset($this->processed) || $this->processed == false){
4895 //add some logic to ensure we do not get into an infinite loop
4896 if(!empty($this->logicHookDepth[$event])) {
4897 if($this->logicHookDepth[$event] > $this->max_logic_depth)
4900 $this->logicHookDepth[$event] = 0;
4902 //we have to put the increment operator here
4903 //otherwise we may never increase the depth for that event in the case
4904 //where one event will trigger another as in the case of before_save and after_save
4905 //Also keeping the depth per event allow any number of hooks to be called on the bean
4906 //and we only will return if one event gets caught in a loop. We do not increment globally
4907 //for each event called.
4908 $this->logicHookDepth[$event]++;
4910 //method defined in 'include/utils/LogicHook.php'
4912 $logicHook = new LogicHook();
4913 $logicHook->setBean($this);
4914 $logicHook->call_custom_logic($this->module_dir, $event, $arguments);
4915 $this->logicHookDepth[$event]--;
4920 /* When creating a custom field of type Dropdown, it creates an enum row in the DB.
4921 A typical get_list_view_array() result will have the *KEY* value from that drop-down.
4922 Since custom _dom objects are flat-files included in the $app_list_strings variable,
4923 We need to generate a key-key pair to get the true value like so:
4924 ([module]_cstm->fields_meta_data->$app_list_strings->*VALUE*)*/
4925 function getRealKeyFromCustomFieldAssignedKey($name)
4927 if ($this->custom_fields->avail_fields[$name]['ext1'])
4931 elseif ($this->custom_fields->avail_fields[$name]['ext2'])
4935 elseif ($this->custom_fields->avail_fields[$name]['ext3'])
4941 $GLOBALS['log']->fatal("SUGARBEAN: cannot find Real Key for custom field of type dropdown - cannot return Value.");
4946 return $this->custom_fields->avail_fields[$name][$realKey];
4950 function bean_implements($interface)
4955 * Check whether the user has access to a particular view for the current bean/module
4956 * @param $view string required, the view to determine access for i.e. DetailView, ListView...
4957 * @param $is_owner bool optional, this is part of the ACL check if the current user is an owner they will receive different access
4959 function ACLAccess($view,$is_owner='not_set')
4961 global $current_user;
4962 if($current_user->isAdminForModule($this->getACLCategory())) {
4966 if($is_owner == 'not_set')
4969 $is_owner = $this->isOwner($current_user->id);
4972 // If we don't implement ACLs, return true.
4973 if(!$this->bean_implements('ACL'))
4975 $view = strtolower($view);
4981 return ACLController::checkAccess($this->module_dir,'list', true);
4984 if( !$is_owner && $not_set && !empty($this->id)){
4985 $class = get_class($this);
4986 $temp = new $class();
4987 if(!empty($this->fetched_row) && !empty($this->fetched_row['id']) && !empty($this->fetched_row['assigned_user_id']) && !empty($this->fetched_row['created_by'])){
4988 $temp->populateFromRow($this->fetched_row);
4990 $temp->retrieve($this->id);
4992 $is_owner = $temp->isOwner($current_user->id);
4994 case 'popupeditview':
4996 return ACLController::checkAccess($this->module_dir,'edit', $is_owner, $this->acltype);
5000 return ACLController::checkAccess($this->module_dir,'view', $is_owner, $this->acltype);
5002 return ACLController::checkAccess($this->module_dir,'delete', $is_owner, $this->acltype);
5004 return ACLController::checkAccess($this->module_dir,'export', $is_owner, $this->acltype);
5006 return ACLController::checkAccess($this->module_dir,'import', true, $this->acltype);
5008 //if it is not one of the above views then it should be implemented on the page level
5017 function getOwnerField($returnFieldName = false)
5019 if (isset($this->field_defs['assigned_user_id']))
5021 return $returnFieldName? 'assigned_user_id': $this->assigned_user_id;
5024 if (isset($this->field_defs['created_by']))
5026 return $returnFieldName? 'created_by': $this->created_by;
5033 * Returns true of false if the user_id passed is the owner
5035 * @param GUID $user_id
5038 function isOwner($user_id)
5040 //if we don't have an id we must be the owner as we are creating it
5041 if(!isset($this->id))
5045 //if there is an assigned_user that is the owner
5046 if(isset($this->assigned_user_id))
5048 if($this->assigned_user_id == $user_id) return true;
5053 //other wise if there is a created_by that is the owner
5054 if(isset($this->created_by) && $this->created_by == $user_id)
5062 * Gets there where statement for checking if a user is an owner
5064 * @param GUID $user_id
5067 function getOwnerWhere($user_id)
5069 if(isset($this->field_defs['assigned_user_id']))
5071 return " $this->table_name.assigned_user_id ='$user_id' ";
5073 if(isset($this->field_defs['created_by']))
5075 return " $this->table_name.created_by ='$user_id' ";
5082 * Used in order to manage ListView links and if they should
5083 * links or not based on the ACL permissions of the user
5085 * @return ARRAY of STRINGS
5087 function listviewACLHelper()
5089 $array_assign = array();
5090 if($this->ACLAccess('DetailView'))
5092 $array_assign['MAIN'] = 'a';
5096 $array_assign['MAIN'] = 'span';
5098 return $array_assign;
5102 * returns this bean as an array
5104 * @return array of fields with id, name, access and category
5106 function toArray($dbOnly = false, $stringOnly = false, $upperKeys=false)
5108 static $cache = array();
5111 foreach($this->field_defs as $field=>$data)
5113 if( !$dbOnly || !isset($data['source']) || $data['source'] == 'db')
5114 if(!$stringOnly || is_string($this->$field))
5117 if(!isset($cache[$field])){
5118 $cache[$field] = strtoupper($field);
5120 $arr[$cache[$field]] = $this->$field;
5124 if(isset($this->$field)){
5125 $arr[$field] = $this->$field;
5135 * Converts an array into an acl mapping name value pairs into files
5139 function fromArray($arr)
5141 foreach($arr as $name=>$value)
5143 $this->$name = $value;
5148 * Convert row data from DB format to internal format
5149 * Mostly useful for dates/times
5151 * @return array $row
5153 public function convertRow($row)
5155 foreach($this->field_defs as $name => $fieldDef)
5157 // skip empty fields and non-db fields
5158 if (isset($name) && !empty($row[$name])) {
5159 $row[$name] = $this->convertField($row[$name], $fieldDef);
5166 * Converts the field value based on the provided fieldDef
5167 * @param $fieldvalue
5171 public function convertField($fieldvalue, $fieldDef)
5173 if (!empty($fieldvalue)) {
5174 if (!(isset($fieldDef['source']) &&
5175 !in_array($fieldDef['source'], array('db', 'custom_fields', 'relate'))
5176 && !isset($fieldDef['dbType']))
5178 // fromConvert other fields
5179 $fieldvalue = $this->db->fromConvert($fieldvalue, $this->db->getFieldType($fieldDef));
5186 * Loads a row of data into instance of a bean. The data is passed as an array to this function
5188 * @param array $arr row of data fetched from the database.
5191 * Internal function do not override.
5193 function loadFromRow($arr)
5195 $this->populateFromRow($arr);
5196 $this->processed_dates_times = array();
5197 $this->check_date_relationships_load();
5199 $this->fill_in_additional_list_fields();
5201 if($this->hasCustomFields())$this->custom_fields->fill_relationships();
5202 $this->call_custom_logic("process_record");
5205 function hasCustomFields()
5207 return !empty($GLOBALS['dictionary'][$this->object_name]['custom_fields']);
5211 * Ensure that fields within order by clauses are properly qualified with
5212 * their tablename. This qualification is a requirement for sql server support.
5214 * @param string $order_by original order by from the query
5215 * @param string $qualify prefix for columns in the order by list.
5218 * Internal function do not override.
5220 function create_qualified_order_by( $order_by, $qualify)
5221 { // 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
5222 if (empty($order_by))
5226 $order_by_clause = " ORDER BY ";
5227 $tmp = explode(",", $order_by);
5229 foreach ( $tmp as $stmp)
5231 $stmp = (substr_count($stmp, ".") > 0?trim($stmp):"$qualify." . trim($stmp));
5232 $order_by_clause .= $comma . $stmp;
5235 return $order_by_clause;
5239 * Combined the contents of street field 2 thru 4 into the main field
5241 * @param string $street_field
5244 function add_address_streets(
5248 $street_field_2 = $street_field.'_2';
5249 $street_field_3 = $street_field.'_3';
5250 $street_field_4 = $street_field.'_4';
5251 if ( isset($this->$street_field_2)) {
5252 $this->$street_field .= "\n". $this->$street_field_2;
5253 unset($this->$street_field_2);
5255 if ( isset($this->$street_field_3)) {
5256 $this->$street_field .= "\n". $this->$street_field_3;
5257 unset($this->$street_field_3);
5259 if ( isset($this->$street_field_4)) {
5260 $this->$street_field .= "\n". $this->$street_field_4;
5261 unset($this->$street_field_4);
5263 if ( isset($this->$street_field)) {
5264 $this->$street_field = trim($this->$street_field, "\n");
5268 * Encrpyt and base64 encode an 'encrypt' field type in the bean using Blowfish. The default system key is stored in cache/Blowfish/{keytype}
5269 * @param STRING value -plain text value of the bean field.
5272 function encrpyt_before_save($value)
5274 require_once("include/utils/encryption_utils.php");
5275 return blowfishEncode(blowfishGetKey('encrypt_field'),$value);
5279 * Decode and decrypt a base 64 encoded string with field type 'encrypt' in this bean using Blowfish.
5280 * @param STRING value - an encrypted and base 64 encoded string.
5283 function decrypt_after_retrieve($value)
5285 require_once("include/utils/encryption_utils.php");
5286 return blowfishDecode(blowfishGetKey('encrypt_field'), $value);
5290 * Moved from save() method, functionality is the same, but this is intended to handle
5291 * Optimistic locking functionality.
5293 private function _checkOptimisticLocking($action, $isUpdate){
5294 if($this->optimistic_lock && !isset($_SESSION['o_lock_fs'])){
5295 if(isset($_SESSION['o_lock_id']) && $_SESSION['o_lock_id'] == $this->id && $_SESSION['o_lock_on'] == $this->object_name)
5297 if($action == 'Save' && $isUpdate && isset($this->modified_user_id) && $this->has_been_modified_since($_SESSION['o_lock_dm'], $this->modified_user_id))
5299 $_SESSION['o_lock_class'] = get_class($this);
5300 $_SESSION['o_lock_module'] = $this->module_dir;
5301 $_SESSION['o_lock_object'] = $this->toArray();
5302 $saveform = "<form name='save' id='save' method='POST'>";
5303 foreach($_POST as $key=>$arg)
5305 $saveform .= "<input type='hidden' name='". addslashes($key) ."' value='". addslashes($arg) ."'>";
5307 $saveform .= "</form><script>document.getElementById('save').submit();</script>";
5308 $_SESSION['o_lock_save'] = $saveform;
5309 header('Location: index.php?module=OptimisticLock&action=LockResolve');
5314 unset ($_SESSION['o_lock_object']);
5315 unset ($_SESSION['o_lock_id']);
5316 unset ($_SESSION['o_lock_dm']);
5322 if(isset($_SESSION['o_lock_object'])) { unset ($_SESSION['o_lock_object']); }
5323 if(isset($_SESSION['o_lock_id'])) { unset ($_SESSION['o_lock_id']); }
5324 if(isset($_SESSION['o_lock_dm'])) { unset ($_SESSION['o_lock_dm']); }
5325 if(isset($_SESSION['o_lock_fs'])) { unset ($_SESSION['o_lock_fs']); }
5326 if(isset($_SESSION['o_lock_save'])) { unset ($_SESSION['o_lock_save']); }
5331 * Send assignment notifications and invites for meetings and calls
5333 private function _sendNotifications($check_notify){
5334 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.
5335 && !$this->isOwner($this->created_by) ) // cn: bug 42727 no need to send email to owner (within workflow)
5337 $admin = new Administration();
5338 $admin->retrieveSettings();
5339 $sendNotifications = false;
5341 if ($admin->settings['notify_on'])
5343 $GLOBALS['log']->info("Notifications: user assignment has changed, checking if user receives notifications");
5344 $sendNotifications = true;
5346 elseif(isset($_REQUEST['send_invites']) && $_REQUEST['send_invites'] == 1)
5348 // cn: bug 5795 Send Invites failing for Contacts
5349 $sendNotifications = true;
5353 $GLOBALS['log']->info("Notifications: not sending e-mail, notify_on is set to OFF");
5357 if($sendNotifications == true)
5359 $notify_list = $this->get_notification_recipients();
5360 foreach ($notify_list as $notify_user)
5362 $this->send_assignment_notifications($notify_user, $admin);
5370 * Called from ImportFieldSanitize::relate(), when creating a new bean in a related module. Will
5371 * copies fields over from the current bean into the related. Designed to be overriden in child classes.
5373 * @param SugarBean $newbean newly created related bean
5375 public function populateRelatedBean(
5382 * Called during the import process before a bean save, to handle any needed pre-save logic when
5383 * importing a record
5385 public function beforeImportSave()
5390 * Called during the import process after a bean save, to handle any needed post-save logic when
5391 * importing a record
5393 public function afterImportSave()
5398 * This function is designed to cache references to field arrays that were previously stored in the
5399 * bean files and have since been moved to separate files. Was previously in include/CacheHandler.php
5402 * @param $module_dir string the module directory
5403 * @param $module string the name of the module
5404 * @param $key string the type of field array we are referencing, i.e. list_fields, column_fields, required_fields
5406 private function _loadCachedArray(
5412 static $moduleDefs = array();
5414 $fileName = 'field_arrays.php';
5416 $cache_key = "load_cached_array.$module_dir.$module.$key";
5417 $result = sugar_cache_retrieve($cache_key);
5420 // Use SugarCache::EXTERNAL_CACHE_NULL_VALUE to store null values in the cache.
5421 if($result == SugarCache::EXTERNAL_CACHE_NULL_VALUE)
5429 if(file_exists('modules/'.$module_dir.'/'.$fileName))
5431 // If the data was not loaded, try loading again....
5432 if(!isset($moduleDefs[$module]))
5434 include('modules/'.$module_dir.'/'.$fileName);
5435 $moduleDefs[$module] = $fields_array;
5437 // Now that we have tried loading, make sure it was loaded
5438 if(empty($moduleDefs[$module]) || empty($moduleDefs[$module][$module][$key]))
5440 // It was not loaded.... Fail. Cache null to prevent future repeats of this calculation
5441 sugar_cache_put($cache_key, SugarCache::EXTERNAL_CACHE_NULL_VALUE);
5445 // It has been loaded, cache the result.
5446 sugar_cache_put($cache_key, $moduleDefs[$module][$module][$key]);
5447 return $moduleDefs[$module][$module][$key];
5450 // It was not loaded.... Fail. Cache null to prevent future repeats of this calculation
5451 sugar_cache_put($cache_key, SugarCache::EXTERNAL_CACHE_NULL_VALUE);
5456 * Returns the ACL category for this module; defaults to the SugarBean::$acl_category if defined
5457 * otherwise it is SugarBean::$module_dir
5461 public function getACLCategory()
5463 return !empty($this->acl_category)?$this->acl_category:$this->module_dir;
5467 * Returns the query used for the export functionality for a module. Override this method if you wish
5468 * to have a custom query to pull this data together instead
5470 * @param string $order_by
5471 * @param string $where
5472 * @return string SQL query
5474 public function create_export_query($order_by, $where)
5476 return $this->create_new_list_query($order_by, $where, array(), array(), 0, '', false, $this, true, true);