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-2011 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');
51 * SugarBean is the base class for all business objects in Sugar. It implements
52 * the primary functionality needed for manipulating business objects: create,
53 * retrieve, update, delete. It allows for searching and retrieving list of records.
54 * It allows for retrieving related objects (e.g. contacts related to a specific account).
56 * In the current implementation, there can only be one bean per folder.
57 * Naming convention has the bean name be the same as the module and folder name.
58 * All bean names should be singular (e.g. Contact). The primary table name for
59 * a bean should be plural (e.g. contacts).
65 * A pointer to the database helper object DBHelper
72 * When createing a bean, you can specify a value in the id column as
73 * long as that value is unique. During save, if the system finds an
74 * id, it assumes it is an update. Setting new_with_id to true will
75 * make sure the system performs an insert instead of an update.
77 * @var BOOL -- default false
79 var $new_with_id = false;
83 * Disble vardefs. This should be set to true only for beans that do not have varders. Tracker is an example
85 * @var BOOL -- default false
87 var $disable_vardefs = false;
91 * holds the full name of the user that an item is assigned to. Only used if notifications
92 * are turned on and going to be sent out.
96 var $new_assigned_user_name;
99 * An array of booleans. This array is cleared out when data is loaded.
100 * As date/times are converted, a "1" is placed under the key, the field is converted.
102 * @var Array of booleans
104 var $processed_dates_times = array();
107 * Whether to process date/time fields for storage in the database in GMT
111 var $process_save_dates =true;
114 * This signals to the bean that it is being saved in a mass mode.
115 * Examples of this kind of save are import and mass update.
116 * We turn off notificaitons of this is the case to make things more efficient.
120 var $save_from_post = true;
123 * When running a query on related items using the method: retrieve_by_string_fields
124 * this value will be set to true if more than one item matches the search criteria.
128 var $duplicates_found = false;
131 * The DBManager instance that was used to load this bean and should be used for
132 * future database interactions.
139 * true if this bean has been deleted, false otherwise.
146 * Should the date modified column of the bean be updated during save?
147 * This is used for admin level functionality that should not be updating
148 * the date modified. This is only used by sync to allow for updates to be
149 * replicated in a way that will not cause them to be replicated back.
153 var $update_date_modified = true;
156 * Should the modified by column of the bean be updated during save?
157 * This is used for admin level functionality that should not be updating
158 * the modified by column. This is only used by sync to allow for updates to be
159 * replicated in a way that will not cause them to be replicated back.
163 var $update_modified_by = true;
166 * Setting this to true allows for updates to overwrite the date_entered
170 var $update_date_entered = false;
173 * This allows for seed data to be created without using the current uesr to set the id.
174 * This should be replaced by altering the current user before the call to save.
178 //TODO This should be replaced by altering the current user before the call to save.
179 var $set_created_by = true;
184 * The database table where records of this Bean are stored.
188 var $table_name = '';
191 * This is the singular name of the bean. (i.e. Contact).
195 var $object_name = '';
197 /** Set this to true if you query contains a sub-select and bean is converting both select statements
198 * into count queries.
200 var $ungreedy_count=false;
203 * The name of the module folder for this type of bean.
207 var $module_dir = '';
211 var $column_fields = array();
212 var $list_fields = array();
213 var $additional_column_fields = array();
214 var $relationship_fields = array();
215 var $current_notify_user;
216 var $fetched_row=false;
218 var $force_load_details = false;
219 var $optimistic_lock = false;
220 var $disable_custom_fields = false;
221 var $number_formatting_done = false;
222 var $process_field_encrypted=false;
224 * The default ACL type
226 var $acltype = 'module';
229 var $additional_meta_fields = array();
232 * Set to true in the child beans if the module supports importing
234 var $importable = false;
237 * Set to true in the child beans if the module use the special notification template
239 var $special_notification = false;
242 * Set to true if the bean is being dealt with in a workflow
244 var $in_workflow = false;
248 * By default it will be true but if any module is to be kept non visible
249 * to tracker, then its value needs to be overriden in that particular module to false.
252 var $tracker_visibility = true;
255 * Used to pass inner join string to ListView Data.
257 var $listview_inner_join = array();
260 * Set to true in <modules>/Import/views/view.step4.php if a module is being imported
262 var $in_import = false;
264 * Constructor for the bean, it performs following tasks:
266 * 1. Initalized a database connections
267 * 2. Load the vardefs for the module implemeting the class. cache the entries
269 * 3. Setup row-level security preference
270 * All implementing classes must call this constructor using the parent::SugarBean() class.
275 global $dictionary, $current_user;
276 static $loaded_defs = array();
277 $this->db = DBManagerFactory::getInstance();
279 $this->dbManager = DBManagerFactory::getInstance();
280 if((false == $this->disable_vardefs && empty($loaded_defs[$this->object_name])) || !empty($GLOBALS['reload_vardefs']))
282 VardefManager::loadVardef($this->module_dir, $this->object_name);
284 // build $this->column_fields from the field_defs if they exist
285 if (!empty($dictionary[$this->object_name]['fields'])) {
286 foreach ($dictionary[$this->object_name]['fields'] as $key=>$value_array) {
287 $column_fields[] = $key;
288 if(!empty($value_array['required']) && !empty($value_array['name'])) {
289 $this->required_fields[$value_array['name']] = 1;
292 $this->column_fields = $column_fields;
295 //setup custom fields
296 if(!isset($this->custom_fields) &&
297 empty($this->disable_custom_fields))
299 $this->setupCustomFields($this->module_dir);
301 //load up field_arrays from CacheHandler;
302 if(empty($this->list_fields))
303 $this->list_fields = $this->_loadCachedArray($this->module_dir, $this->object_name, 'list_fields');
304 if(empty($this->column_fields))
305 $this->column_fields = $this->_loadCachedArray($this->module_dir, $this->object_name, 'column_fields');
306 if(empty($this->required_fields))
307 $this->required_fields = $this->_loadCachedArray($this->module_dir, $this->object_name, 'required_fields');
309 if(isset($GLOBALS['dictionary'][$this->object_name]) && !$this->disable_vardefs)
311 $this->field_name_map = $dictionary[$this->object_name]['fields'];
312 $this->field_defs = $dictionary[$this->object_name]['fields'];
314 if(!empty($dictionary[$this->object_name]['optimistic_locking']))
316 $this->optimistic_lock=true;
319 $loaded_defs[$this->object_name]['column_fields'] =& $this->column_fields;
320 $loaded_defs[$this->object_name]['list_fields'] =& $this->list_fields;
321 $loaded_defs[$this->object_name]['required_fields'] =& $this->required_fields;
322 $loaded_defs[$this->object_name]['field_name_map'] =& $this->field_name_map;
323 $loaded_defs[$this->object_name]['field_defs'] =& $this->field_defs;
327 $this->column_fields =& $loaded_defs[$this->object_name]['column_fields'] ;
328 $this->list_fields =& $loaded_defs[$this->object_name]['list_fields'];
329 $this->required_fields =& $loaded_defs[$this->object_name]['required_fields'];
330 $this->field_name_map =& $loaded_defs[$this->object_name]['field_name_map'];
331 $this->field_defs =& $loaded_defs[$this->object_name]['field_defs'];
332 $this->added_custom_field_defs = true;
334 if(!isset($this->custom_fields) &&
335 empty($this->disable_custom_fields))
337 $this->setupCustomFields($this->module_dir, false);
339 if(!empty($dictionary[$this->object_name]['optimistic_locking']))
341 $this->optimistic_lock=true;
345 if($this->bean_implements('ACL') && !empty($GLOBALS['current_user'])){
346 $this->acl_fields = (isset($dictionary[$this->object_name]['acl_fields']) && $dictionary[$this->object_name]['acl_fields'] === false)?false:true;
348 $this->populateDefaultValues();
353 * Returns the object name. If object_name is not set, table_name is returned.
355 * All implementing classes must set a value for the object_name variable.
357 * @param array $arr row of data fetched from the database.
361 function getObjectName()
363 if ($this->object_name)
364 return $this->object_name;
366 // This is a quick way out. The generated metadata files have the table name
367 // as the key. The correct way to do this is to override this function
368 // in bean and return the object name. That requires changing all the beans
369 // as well as put the object name in the generator.
370 return $this->table_name;
374 * Returns a list of fields with their definitions that have the audited property set to true.
375 * Before calling this function, check whether audit has been enabled for the table/module or not.
376 * You would set the audit flag in the implemting module's vardef file.
378 * @return an array of
379 * @see is_AuditEnabled
381 * Internal function, do not override.
383 function getAuditEnabledFieldDefinitions()
385 $aclcheck = $this->bean_implements('ACL');
386 $is_owner = $this->isOwner($GLOBALS['current_user']->id);
387 if (!isset($this->audit_enabled_fields))
390 $this->audit_enabled_fields=array();
391 foreach ($this->field_defs as $field => $properties)
396 !empty($properties['Audited']) || !empty($properties['audited']))
400 $this->audit_enabled_fields[$field]=$properties;
405 return $this->audit_enabled_fields;
409 * Return true if auditing is enabled for this object
410 * You would set the audit flag in the implemting module's vardef file.
414 * Internal function, do not override.
416 function is_AuditEnabled()
419 if (isset($dictionary[$this->getObjectName()]['audited']))
421 return $dictionary[$this->getObjectName()]['audited'];
432 * Returns the name of the audit table.
433 * Audit table's name is based on implementing class' table name.
435 * @return String Audit table name.
437 * Internal function, do not override.
439 function get_audit_table_name()
441 return $this->getTableName().'_audit';
445 * If auditing is enabled, create the audit table.
447 * Function is used by the install scripts and a repair utility in the admin panel.
449 * Internal function, do not override.
451 function create_audit_table()
454 $table_name=$this->get_audit_table_name();
456 require('metadata/audit_templateMetaData.php');
458 $fieldDefs = $dictionary['audit']['fields'];
459 $indices = $dictionary['audit']['indices'];
460 // '0' stands for the first index for all the audit tables
461 $indices[0]['name'] = 'idx_' . strtolower($this->getTableName()) . '_' . $indices[0]['name'];
462 $indices[1]['name'] = 'idx_' . strtolower($this->getTableName()) . '_' . $indices[1]['name'];
464 if(isset($dictionary['audit']['engine'])) {
465 $engine = $dictionary['audit']['engine'];
466 } else if(isset($dictionary[$this->getObjectName()]['engine'])) {
467 $engine = $dictionary[$this->getObjectName()]['engine'];
470 $sql=$this->dbManager->helper->createTableSQLParams($table_name, $fieldDefs, $indices, $engine);
472 $msg = "Error creating table: ".$table_name. ":";
473 $this->dbManager->query($sql,true,$msg);
477 * Returns the implementing class' table name.
479 * All implementing classes set a value for the table_name variable. This value is returned as the
480 * table name. If not set, table name is extracted from the implementing module's vardef.
482 * @return String Table name.
484 * Internal function, do not override.
486 function getTableName()
489 if(isset($this->table_name))
491 return $this->table_name;
493 return $dictionary[$this->getObjectName()]['table'];
497 * Returns field definitions for the implementing module.
499 * The definitions were loaded in the constructor.
501 * @return Array Field definitions.
503 * Internal function, do not override.
505 function getFieldDefinitions()
507 return $this->field_defs;
511 * Returns index definitions for the implementing module.
513 * The definitions were loaded in the constructor.
515 * @return Array Index definitions.
517 * Internal function, do not override.
519 function getIndices()
522 if(isset($dictionary[$this->getObjectName()]['indices']))
524 return $dictionary[$this->getObjectName()]['indices'];
530 * Returns field definition for the requested field name.
532 * The definitions were loaded in the constructor.
534 * @param string field name,
535 * @return Array Field properties or boolean false if the field doesn't exist
537 * Internal function, do not override.
539 function getFieldDefinition($name)
541 if ( !isset($this->field_defs[$name]) )
544 return $this->field_defs[$name];
548 * Returnss definition for the id field name.
550 * The definitions were loaded in the constructor.
552 * @return Array Field properties.
554 * Internal function, do not override.
556 function getPrimaryFieldDefinition()
558 $def = $this->getFieldDefinition("id");
560 $def = $this->getFieldDefinition(0);
564 * Returns the value for the requested field.
566 * When a row of data is fetched using the bean, all fields are created as variables in the context
567 * of the bean and then fetched values are set in these variables.
569 * @param string field name,
570 * @return varies Field value.
572 * Internal function, do not override.
574 function getFieldValue($name)
576 if (!isset($this->$name)){
579 if($this->$name === TRUE){
582 if($this->$name === FALSE){
589 * Basically undoes the effects of SugarBean::populateDefaultValues(); this method is best called right after object
592 public function unPopulateDefaultValues()
594 if ( !is_array($this->field_defs) )
597 foreach ($this->field_defs as $field => $value) {
598 if( !empty($this->$field)
599 && ((isset($value['default']) && $this->$field == $value['default']) || (!empty($value['display_default']) && $this->$field == $value['display_default']))
601 $this->$field = null;
604 if(!empty($this->$field) && !empty($value['display_default']) && in_array($value['type'], array('date', 'datetime', 'datetimecombo')) &&
605 $this->$field == $this->parseDateDefault($value['display_default'], ($value['type'] != 'date'))) {
606 $this->$field = null;
612 * Create date string from default value
614 * @param string $value
615 * @param bool $time Should be expect time set too?
618 protected function parseDateDefault($value, $time = false)
622 $dtAry = explode('&', $value, 2);
623 $dateValue = $timedate->getNow(true)->modify($dtAry[0]);
624 if(!empty($dtAry[1])) {
625 $timeValue = $timedate->fromString($dtAry[1]);
626 $dateValue->setTime($timeValue->hour, $timeValue->min, $timeValue->sec);
628 return $timedate->asUser($dateValue);
630 return $timedate->asUserDate($timedate->getNow(true)->modify($value));
634 function populateDefaultValues($force=false){
635 if ( !is_array($this->field_defs) )
637 foreach($this->field_defs as $field=>$value){
638 if((isset($value['default']) || !empty($value['display_default'])) && ($force || empty($this->$field))){
639 $type = $value['type'];
643 if(!empty($value['display_default'])){
644 $this->$field = $this->parseDateDefault($value['display_default']);
648 case 'datetimecombo':
649 if(!empty($value['display_default'])){
650 $this->$field = $this->parseDateDefault($value['display_default'], true);
654 if(empty($value['default']) && !empty($value['display_default']))
655 $this->$field = $value['display_default'];
657 $this->$field = $value['default'];
660 if ( isset($value['default']) && $value['default'] !== '' ) {
661 $this->$field = htmlentities($value['default'], ENT_QUOTES, 'UTF-8');
672 * Removes relationship metadata cache.
674 * Every module that has relationships defined with other modules, has this meta data cached. The cache is
675 * stores in 2 locations: relationships table and file system. This method clears the cache from both locations.
677 * @param string $key module whose meta cache is to be cleared.
678 * @param string $db database handle.
679 * @param string $tablename table name
680 * @param string $dictionary vardef for the module
681 * @param string $module_dir name of subdirectory where module is installed.
686 * Internal function, do not override.
688 function removeRelationshipMeta($key,$db,$tablename,$dictionary,$module_dir)
690 //load the module dictionary if not supplied.
691 if ((!isset($dictionary) or empty($dictionary)) && !empty($module_dir))
693 $filename='modules/'. $module_dir . '/vardefs.php';
694 if(file_exists($filename))
699 if (!is_array($dictionary) or !array_key_exists($key, $dictionary))
701 $GLOBALS['log']->fatal("removeRelationshipMeta: Metadata for table ".$tablename. " does not exist");
702 display_notice("meta data absent for table ".$tablename." keyed to $key ");
706 if (isset($dictionary[$key]['relationships']))
708 $RelationshipDefs = $dictionary[$key]['relationships'];
709 foreach ($RelationshipDefs as $rel_name)
711 Relationship::delete($rel_name,$db);
719 * This method has been deprecated.
721 * @see removeRelationshipMeta()
722 * @deprecated 4.5.1 - Nov 14, 2006
725 function remove_relationship_meta($key,$db,$log,$tablename,$dictionary,$module_dir)
727 SugarBean::removeRelationshipMeta($key,$db,$tablename,$dictionary,$module_dir);
732 * Populates the relationship meta for a module.
734 * It is called during setup/install. It is used statically to create relationship meta data for many-to-many tables.
736 * @param string $key name of the object.
737 * @param object $db database handle.
738 * @param string $tablename table, meta data is being populated for.
739 * @param array dictionary vardef dictionary for the object. *
740 * @param string module_dir name of subdirectory where module is installed.
741 * @param boolean $iscustom Optional,set to true if module is installed in a custom directory. Default value is false.
744 * Internal function, do not override.
746 function createRelationshipMeta($key,$db,$tablename,$dictionary,$module_dir,$iscustom=false)
748 //load the module dictionary if not supplied.
749 if (empty($dictionary) && !empty($module_dir))
753 $filename='custom/modules/' . $module_dir . '/Ext/Vardefs/vardefs.ext.php';
759 // a very special case for the Employees module
760 // this must be done because the Employees/vardefs.php does an include_once on
762 $filename='modules/Users/vardefs.php';
766 $filename='modules/'. $module_dir . '/vardefs.php';
770 if(file_exists($filename))
773 // cn: bug 7679 - dictionary entries defined as $GLOBALS['name'] not found
774 if(empty($dictionary) || !empty($GLOBALS['dictionary'][$key]))
776 $dictionary = $GLOBALS['dictionary'];
781 $GLOBALS['log']->debug("createRelationshipMeta: no metadata file found" . $filename);
786 if (!is_array($dictionary) or !array_key_exists($key, $dictionary))
788 $GLOBALS['log']->fatal("createRelationshipMeta: Metadata for table ".$tablename. " does not exist");
789 display_notice("meta data absent for table ".$tablename." keyed to $key ");
793 if (isset($dictionary[$key]['relationships']))
796 $RelationshipDefs = $dictionary[$key]['relationships'];
800 $beanList_ucase=array_change_key_case ( $beanList ,CASE_UPPER);
801 foreach ($RelationshipDefs as $rel_name=>$rel_def)
803 if (isset($rel_def['lhs_module']) and !isset($beanList_ucase[strtoupper($rel_def['lhs_module'])])) {
804 $GLOBALS['log']->debug('skipping orphaned relationship record ' . $rel_name . ' lhs module is missing ' . $rel_def['lhs_module']);
807 if (isset($rel_def['rhs_module']) and !isset($beanList_ucase[strtoupper($rel_def['rhs_module'])])) {
808 $GLOBALS['log']->debug('skipping orphaned relationship record ' . $rel_name . ' rhs module is missing ' . $rel_def['rhs_module']);
813 //check whether relationship exists or not first.
814 if (Relationship::exists($rel_name,$db))
816 $GLOBALS['log']->debug('Skipping, reltionship already exists '.$rel_name);
820 // add Id to the insert statement.
822 $value_list="'".create_guid()."'";
824 //add relationship name to the insert statement.
825 $column_list .= $delimiter.'relationship_name';
826 $value_list .= $delimiter."'".$rel_name."'";
828 //todo check whether $rel_def is an array or not.
829 //for now make that assumption.
830 //todo specify defaults if meta not defined.
831 foreach ($rel_def as $def_key=>$value)
833 $column_list.= $delimiter.$def_key;
834 $value_list.= $delimiter."'".$value."'";
837 //create the record. todo add error check.
838 $insert_string = "INSERT into relationships (" .$column_list. ") values (".$value_list.")";
839 $db->query($insert_string, true);
846 //log informational message stating no relationships meta was set for this bean.
852 * This method has been deprecated.
853 * @see createRelationshipMeta()
854 * @deprecated 4.5.1 - Nov 14, 2006
857 function create_relationship_meta($key,&$db,&$log,$tablename,$dictionary,$module_dir)
859 SugarBean::createRelationshipMeta($key,$db,$tablename,$dictionary,$module_dir);
864 * Loads the request relationship. This method should be called before performing any operations on the related data.
866 * This method searches the vardef array for the requested attribute's definition. If the attribute is of the type
867 * link then it creates a similary named variable and loads the relationship definition.
869 * @param string $rel_name relationship/attribute name.
872 function load_relationship($rel_name)
874 $GLOBALS['log']->debug("SugarBean.load_relationships, Loading relationship (".$rel_name.").");
876 if (empty($rel_name))
878 $GLOBALS['log']->error("SugarBean.load_relationships, Null relationship name passed.");
881 $fieldDefs = $this->getFieldDefinitions();
883 //find all definitions of type link.
884 if (!empty($fieldDefs))
886 //if rel_name is provided, search the fieldef array keys by name.
887 if (array_key_exists($rel_name, $fieldDefs))
889 if (array_search('link',$fieldDefs[$rel_name]) === 'type')
891 //initialize a variable of type Link
892 require_once('data/Link.php');
893 $class = load_link_class($fieldDefs[$rel_name]);
895 $this->$rel_name=new $class($fieldDefs[$rel_name]['relationship'], $this, $fieldDefs[$rel_name]);
897 if (empty($this->$rel_name->_relationship->id)) {
898 unset($this->$rel_name);
906 $GLOBALS['log']->debug("SugarBean.load_relationships, Error Loading relationship (".$rel_name.").");
915 * Loads all attributes of type link.
917 * Method searches the implmenting module's vardef file for attributes of type link, and for each attribute
918 * create a similary named variable and load the relationship definition.
922 * Internal function, do not override.
924 function load_relationships()
927 $GLOBALS['log']->debug("SugarBean.load_relationships, Loading all relationships of type link.");
929 $linked_fields=$this->get_linked_fields();
930 require_once("data/Link.php");
931 foreach($linked_fields as $name=>$properties)
933 $class = load_link_class($properties);
935 $this->$name=new $class($properties['relationship'], $this, $properties);
940 * Returns an array of beans of related data.
942 * For instance, if an account is related to 10 contacts , this function will return an array of contacts beans (10)
943 * with each bean representing a contact record.
944 * Method will load the relationship if not done so already.
946 * @param string $field_name relationship to be loaded.
947 * @param string $bean name class name of the related bean.
948 * @param array $sort_array optional, unused
949 * @param int $begin_index Optional, default 0, unused.
950 * @param int $end_index Optional, default -1
951 * @param int $deleted Optional, Default 0, 0 adds deleted=0 filter, 1 adds deleted=1 filter.
952 * @param string $optional_where, Optional, default empty.
954 * Internal function, do not override.
956 function get_linked_beans($field_name,$bean_name, $sort_array = array(), $begin_index = 0, $end_index = -1,
957 $deleted=0, $optional_where="")
960 //if bean_name is Case then use aCase
961 if($bean_name=="Case")
962 $bean_name = "aCase";
964 //add a references to bean_name if it doe not exist aleady.
965 if (!(class_exists($bean_name)))
968 if (isset($GLOBALS['beanList']) && isset($GLOBALS['beanFiles']))
976 $bean_file=$beanFiles[$bean_name];
977 include_once($bean_file);
980 $this->load_relationship($field_name);
982 return $this->$field_name->getBeans(new $bean_name(), $sort_array, $begin_index, $end_index, $deleted, $optional_where);
986 * Returns an array of fields that are of type link.
988 * @return array List of fields.
990 * Internal function, do not override.
992 function get_linked_fields()
995 $linked_fields=array();
997 // require_once('data/Link.php');
999 $fieldDefs = $this->getFieldDefinitions();
1001 //find all definitions of type link.
1002 if (!empty($fieldDefs))
1004 foreach ($fieldDefs as $name=>$properties)
1006 if (array_search('link',$properties) === 'type')
1008 $linked_fields[$name]=$properties;
1013 return $linked_fields;
1017 * Returns an array of fields that are able to be Imported into
1018 * i.e. 'importable' not set to 'false'
1020 * @return array List of fields.
1022 * Internal function, do not override.
1024 function get_importable_fields()
1026 $importableFields = array();
1028 $fieldDefs= $this->getFieldDefinitions();
1030 if (!empty($fieldDefs)) {
1031 foreach ($fieldDefs as $key=>$value_array) {
1032 if ( (isset($value_array['importable'])
1033 && (is_string($value_array['importable']) && $value_array['importable'] == 'false'
1034 || is_bool($value_array['importable']) && $value_array['importable'] == false))
1035 || (isset($value_array['type']) && $value_array['type'] == 'link')
1036 || (isset($value_array['auto_increment'])
1037 && ($value_array['type'] == true || $value_array['type'] == 'true')) ) {
1038 // only allow import if we force it
1039 if (isset($value_array['importable'])
1040 && (is_string($value_array['importable']) && $value_array['importable'] == 'true'
1041 || is_bool($value_array['importable']) && $value_array['importable'] == true)) {
1042 $importableFields[$key]=$value_array;
1046 $importableFields[$key]=$value_array;
1051 return $importableFields;
1055 * Returns an array of fields that are of type relate.
1057 * @return array List of fields.
1059 * Internal function, do not override.
1061 function get_related_fields()
1064 $related_fields=array();
1066 // require_once('data/Link.php');
1068 $fieldDefs = $this->getFieldDefinitions();
1070 //find all definitions of type link.
1071 if (!empty($fieldDefs))
1073 foreach ($fieldDefs as $name=>$properties)
1075 if (array_search('relate',$properties) === 'type')
1077 $related_fields[$name]=$properties;
1082 return $related_fields;
1086 * Returns an array of fields that are required for import
1090 function get_import_required_fields()
1092 $importable_fields = $this->get_importable_fields();
1093 $required_fields = array();
1095 foreach ( $importable_fields as $name => $properties ) {
1096 if ( isset($properties['importable']) && is_string($properties['importable']) && $properties['importable'] == 'required' ) {
1097 $required_fields[$name] = $properties;
1101 return $required_fields;
1105 * Iterates through all the relationships and deletes all records for reach relationship.
1107 * @param string $id Primary key value of the parent reocrd
1109 function delete_linked($id)
1111 $linked_fields=$this->get_linked_fields();
1113 foreach ($linked_fields as $name => $value)
1115 if ($this->load_relationship($name))
1117 $GLOBALS['log']->debug('relationship loaded');
1118 $this->$name->delete($id);
1122 $GLOBALS['log']->error('error loading relationship');
1128 * Creates tables for the module implementing the class.
1129 * If you override this function make sure that your code can handles table creation.
1132 function create_tables()
1136 $key = $this->getObjectName();
1137 if (!array_key_exists($key, $dictionary))
1139 $GLOBALS['log']->fatal("create_tables: Metadata for table ".$this->table_name. " does not exist");
1140 display_notice("meta data absent for table ".$this->table_name." keyed to $key ");
1144 if(!$this->db->tableExists($this->table_name))
1146 $this->dbManager->createTable($this);
1147 if($this->bean_implements('ACL')){
1148 if(!empty($this->acltype)){
1149 ACLAction::addActions($this->getACLCategory(), $this->acltype);
1151 ACLAction::addActions($this->getACLCategory());
1157 echo "Table already exists : $this->table_name<br>";
1159 if($this->is_AuditEnabled()){
1160 if (!$this->db->tableExists($this->get_audit_table_name())) {
1161 $this->create_audit_table();
1169 * Delete the primary table for the module implementing the class.
1170 * If custom fields were added to this table/module, the custom table will be removed too, along with the cache
1171 * entries that define the custom fields.
1174 function drop_tables()
1177 $key = $this->getObjectName();
1178 if (!array_key_exists($key, $dictionary))
1180 $GLOBALS['log']->fatal("drop_tables: Metadata for table ".$this->table_name. " does not exist");
1181 echo "meta data absent for table ".$this->table_name."<br>\n";
1183 if(empty($this->table_name))return;
1184 if ($this->db->tableExists($this->table_name))
1186 $this->dbManager->dropTable($this);
1187 if ($this->db->tableExists($this->table_name. '_cstm'))
1189 $this->dbManager->dropTableName($this->table_name. '_cstm');
1190 DynamicField::deleteCache();
1192 if ($this->db->tableExists($this->get_audit_table_name())) {
1193 $this->dbManager->dropTableName($this->get_audit_table_name());
1202 * Loads the definition of custom fields defined for the module.
1203 * Local file system cache is created as needed.
1205 * @param string $module_name setting up custom fields for this module.
1206 * @param boolean $clean_load Optional, default true, rebuilds the cache if set to true.
1208 function setupCustomFields($module_name, $clean_load=true)
1210 $this->custom_fields = new DynamicField($module_name);
1211 $this->custom_fields->setup($this);
1216 * Cleans char, varchar, text, etc. fields of XSS type materials
1218 function cleanBean() {
1219 foreach($this->field_defs as $key => $def) {
1221 if (isset($def['type'])) {
1224 if(isset($def['dbType']))
1225 $type .= $def['dbType'];
1227 if((strpos($type, 'char') !== false ||
1228 strpos($type, 'text') !== false ||
1232 $str = from_html($this->$key);
1233 // Julian's XSS cleaner
1234 $potentials = clean_xss($str, false);
1236 if(is_array($potentials) && !empty($potentials)) {
1237 foreach($potentials as $bad) {
1238 $str = str_replace($bad, "", $str);
1240 $this->$key = to_html($str);
1247 * Implements a generic insert and update logic for any SugarBean
1248 * This method only works for subclasses that implement the same variable names.
1249 * This method uses the presence of an id field that is not null to signify and update.
1250 * The id field should not be set otherwise.
1252 * @param boolean $check_notify Optional, default false, if set to true assignee of the record is notified via email.
1253 * @todo Add support for field type validation and encoding of parameters.
1255 function save($check_notify = FALSE)
1257 // cn: SECURITY - strip XSS potential vectors
1259 // This is used so custom/3rd-party code can be upgraded with fewer issues, this will be removed in a future release
1260 $this->fixUpFormatting();
1262 global $current_user, $action;
1265 if(empty($this->id))
1270 if ( $this->new_with_id == true )
1274 if(empty($this->date_modified) || $this->update_date_modified)
1276 $this->date_modified = $GLOBALS['timedate']->nowDb();
1279 $this->_checkOptimisticLocking($action, $isUpdate);
1281 if(!empty($this->modified_by_name)) $this->old_modified_by_name = $this->modified_by_name;
1282 if($this->update_modified_by)
1284 $this->modified_user_id = 1;
1286 if (!empty($current_user))
1288 $this->modified_user_id = $current_user->id;
1289 $this->modified_by_name = $current_user->user_name;
1292 if ($this->deleted != 1)
1300 if (empty($this->date_entered))
1302 $this->date_entered = $this->date_modified;
1304 if($this->set_created_by == true)
1306 // created by should always be this user
1307 $this->created_by = (isset($current_user)) ? $current_user->id : "";
1309 if( $this->new_with_id == false)
1311 $this->id = create_guid();
1313 $query = "INSERT into ";
1315 if($isUpdate && !$this->update_date_entered)
1317 unset($this->date_entered);
1319 // call the custom business logic
1320 $custom_logic_arguments['check_notify'] = $check_notify;
1323 $this->call_custom_logic("before_save", $custom_logic_arguments);
1324 unset($custom_logic_arguments);
1326 if(isset($this->custom_fields))
1328 $this->custom_fields->bean = $this;
1329 $this->custom_fields->save($isUpdate);
1332 // use the db independent query generator
1333 $this->preprocess_fields_on_save();
1335 //construct the SQL to create the audit record if auditing is enabled.
1336 $dataChanges=array();
1337 if ($this->is_AuditEnabled())
1339 if ($isUpdate && !isset($this->fetched_row))
1341 $GLOBALS['log']->debug('Auditing: Retrieve was not called, audit record will not be created.');
1345 $dataChanges=$this->dbManager->helper->getDataChanges($this);
1349 $this->_sendNotifications($check_notify);
1351 if ($this->db->dbType == "oci8")
1354 if ($this->db->dbType == 'mysql')
1356 // write out the SQL statement.
1357 $query .= $this->table_name." set ";
1361 foreach($this->field_defs as $field=>$value)
1363 if(!isset($value['source']) || $value['source'] == 'db')
1365 // Do not write out the id field on the update statement.
1366 // We are not allowed to change ids.
1367 if($isUpdate && ('id' == $field))
1369 //custom fields handle there save seperatley
1370 if(isset($this->field_name_map) && !empty($this->field_name_map[$field]['custom_type']))
1373 // Only assign variables that have been set.
1374 if(isset($this->$field))
1376 //bug: 37908 - this is to handle the issue where the bool value is false, but strlen(false) <= so it will
1377 //set the default value. TODO change this code to esend all fields through getFieldValue() like DbHelper->insertSql
1378 if(!empty($value['type']) && $value['type'] == 'bool'){
1379 $this->$field = $this->getFieldValue($field);
1382 if(strlen($this->$field) <= 0)
1384 if(!$isUpdate && isset($value['default']) && (strlen($value['default']) > 0))
1386 $this->$field = $value['default'];
1390 $this->$field = null;
1393 // Try comparing this element with the head element.
1399 if(is_null($this->$field))
1401 $query .= $field."=null";
1405 //added check for ints because sql-server does not like casting varchar with a decimal value
1407 if(isset($value['type']) and $value['type']=='int') {
1408 $query .= $field."=".$this->db->quote($this->$field);
1409 } elseif ( isset($value['len']) ) {
1410 $query .= $field."='".$this->db->quote($this->db->truncate(from_html($this->$field),$value['len']))."'";
1412 $query .= $field."='".$this->db->quote($this->$field)."'";
1421 $query = $query." WHERE ID = '$this->id'";
1422 $GLOBALS['log']->info("Update $this->object_name: ".$query);
1426 $GLOBALS['log']->info("Insert: ".$query);
1428 $GLOBALS['log']->info("Save: $query");
1429 $this->db->query($query, true);
1431 //process if type is set to mssql
1432 if ($this->db->dbType == 'mssql')
1436 // build out the SQL UPDATE statement.
1437 $query = "UPDATE " . $this->table_name." SET ";
1439 foreach($this->field_defs as $field=>$value)
1441 if(!isset($value['source']) || $value['source'] == 'db')
1443 // Do not write out the id field on the update statement.
1444 // We are not allowed to change ids.
1445 if($isUpdate && ('id' == $field))
1448 // If the field is an auto_increment field, then we shouldn't be setting it. This was added
1449 // specially for Bugs and Cases which have a number associated with them.
1450 if ($isUpdate && isset($this->field_name_map[$field]['auto_increment']) &&
1451 $this->field_name_map[$field]['auto_increment'] == true)
1454 //custom fields handle their save seperatley
1455 if(isset($this->field_name_map) && !empty($this->field_name_map[$field]['custom_type']))
1458 // Only assign variables that have been set.
1459 if(isset($this->$field))
1461 //bug: 37908 - this is to handle the issue where the bool value is false, but strlen(false) <= so it will
1462 //set the default value. TODO change this code to esend all fields through getFieldValue() like DbHelper->insertSql
1463 if(!empty($value['type']) && $value['type'] == 'bool'){
1464 $this->$field = $this->getFieldValue($field);
1467 if(strlen($this->$field) <= 0)
1469 if(!$isUpdate && isset($value['default']) && (strlen($value['default']) > 0))
1471 $this->$field = $value['default'];
1475 $this->$field = null;
1478 // Try comparing this element with the head element.
1484 if(is_null($this->$field))
1486 $query .= $field."=null";
1488 elseif ( isset($value['len']) )
1490 $query .= $field."='".$this->db->quote($this->db->truncate(from_html($this->$field),$value['len']))."'";
1494 $query .= $field."='".$this->db->quote($this->$field)."'";
1499 $query = $query." WHERE ID = '$this->id'";
1500 $GLOBALS['log']->info("Update $this->object_name: ".$query);
1506 foreach($this->field_defs as $field=>$value)
1508 if(!isset($value['source']) || $value['source'] == 'db')
1510 // Do not write out the id field on the update statement.
1511 // We are not allowed to change ids.
1512 //if($isUpdate && ('id' == $field)) continue;
1513 //custom fields handle there save seperatley
1515 if(isset($this->field_name_map) && !empty($this->field_name_map[$field]['custom_module']))
1518 // Only assign variables that have been set.
1519 if(isset($this->$field))
1521 //trim the value in case empty space is passed in.
1522 //this will allow default values set in db to take effect, otherwise
1523 //will insert blanks into db
1524 $trimmed_field = trim($this->$field);
1525 //if this value is empty, do not include the field value in statement
1526 if($trimmed_field =='')
1530 //bug: 37908 - this is to handle the issue where the bool value is false, but strlen(false) <= so it will
1531 //set the default value. TODO change this code to esend all fields through getFieldValue() like DbHelper->insertSql
1532 if(!empty($value['type']) && $value['type'] == 'bool'){
1533 $this->$field = $this->getFieldValue($field);
1535 //added check for ints because sql-server does not like casting varchar with a decimal value
1537 if(isset($value['type']) and $value['type']=='int') {
1538 $values[] = $this->db->quote($this->$field);
1539 } elseif ( isset($value['len']) ) {
1540 $values[] = "'".$this->db->quote($this->db->truncate(from_html($this->$field),$value['len']))."'";
1542 $values[] = "'".$this->db->quote($this->$field)."'";
1545 $columns[] = $field;
1549 // build out the SQL INSERT statement.
1550 $query = "INSERT INTO $this->table_name (" .implode("," , $columns). " ) VALUES ( ". implode("," , $values). ')';
1551 $GLOBALS['log']->info("Insert: ".$query);
1554 $GLOBALS['log']->info("Save: $query");
1555 $this->db->query($query, true);
1557 if (!empty($dataChanges) && is_array($dataChanges))
1559 foreach ($dataChanges as $change)
1561 $this->dbManager->helper->save_audit_records($this,$change);
1566 // let subclasses save related field changes
1567 $this->save_relationship_changes($isUpdate);
1569 //If we aren't in setup mode and we have a current user and module, then we track
1570 if(isset($GLOBALS['current_user']) && isset($this->module_dir))
1572 $this->track_view($current_user->id, $this->module_dir, 'save');
1575 $this->call_custom_logic('after_save', '');
1582 * Performs a check if the record has been modified since the specified date
1584 * @param date $date Datetime for verification
1585 * @param string $modified_user_id User modified by
1587 function has_been_modified_since($date, $modified_user_id)
1589 global $current_user;
1590 if (isset($current_user))
1592 if ($this->db->dbType == 'mssql')
1593 $date_modified_string = 'CONVERT(varchar(20), date_modified, 120)';
1595 $date_modified_string = 'date_modified';
1597 $query = "SELECT date_modified FROM $this->table_name WHERE id='$this->id' AND modified_user_id != '$current_user->id' AND (modified_user_id != '$modified_user_id' OR $date_modified_string > " . db_convert("'".$date."'", 'datetime') . ')';
1598 $result = $this->db->query($query);
1600 if($this->db->fetchByAssoc($result))
1609 * Determines which users receive a notification
1611 function get_notification_recipients() {
1612 $notify_user = new User();
1613 $notify_user->retrieve($this->assigned_user_id);
1614 $this->new_assigned_user_name = $notify_user->full_name;
1616 $GLOBALS['log']->info("Notifications: recipient is $this->new_assigned_user_name");
1618 $user_list = array($notify_user);
1621 //send notifications to followers, but ensure to not query for the assigned_user.
1622 return SugarFollowing::getFollowers($this, $notify_user);
1627 * Handles sending out email notifications when items are first assigned to users
1629 * @param string $notify_user user to notify
1630 * @param string $admin the admin user that sends out the notification
1632 function send_assignment_notifications($notify_user, $admin)
1634 global $current_user;
1636 if(($this->object_name == 'Meeting' || $this->object_name == 'Call') || $notify_user->receive_notifications)
1638 $sendToEmail = $notify_user->emailAddress->getPrimaryAddress($notify_user);
1640 if(empty($sendToEmail)) {
1641 $GLOBALS['log']->warn("Notifications: no e-mail address set for user {$notify_user->user_name}, cancelling send");
1645 $notify_mail = $this->create_notification_email($notify_user);
1646 $notify_mail->setMailerForSystem();
1648 if(empty($admin->settings['notify_send_from_assigning_user'])) {
1649 $notify_mail->From = $admin->settings['notify_fromaddress'];
1650 $notify_mail->FromName = (empty($admin->settings['notify_fromname'])) ? "" : $admin->settings['notify_fromname'];
1652 // Send notifications from the current user's e-mail (ifset)
1653 $fromAddress = $current_user->emailAddress->getReplyToAddress($current_user);
1654 $fromAddress = !empty($fromAddress) ? $fromAddress : $admin->settings['notify_fromaddress'];
1655 $notify_mail->From = $fromAddress;
1656 //Use the users full name is available otherwise default to system name
1657 $from_name = !empty($admin->settings['notify_fromname']) ? $admin->settings['notify_fromname'] : "";
1658 $from_name = !empty($current_user->full_name) ? $current_user->full_name : $from_name;
1659 $notify_mail->FromName = $from_name;
1662 if($sendEmail && !$notify_mail->Send()) {
1663 $GLOBALS['log']->fatal("Notifications: error sending e-mail (method: {$notify_mail->Mailer}), (error: {$notify_mail->ErrorInfo})");
1665 $GLOBALS['log']->fatal("Notifications: e-mail successfully sent");
1672 * This function handles create the email notifications email.
1673 * @param string $notify_user the user to send the notification email to
1675 function create_notification_email($notify_user) {
1676 global $sugar_version;
1677 global $sugar_config;
1678 global $app_list_strings;
1679 global $current_user;
1682 $OBCharset = $locale->getPrecedentPreference('default_email_charset');
1685 require_once("include/SugarPHPMailer.php");
1687 $notify_address = $notify_user->emailAddress->getPrimaryAddress($notify_user);
1688 $notify_name = $notify_user->full_name;
1689 $GLOBALS['log']->debug("Notifications: user has e-mail defined");
1691 $notify_mail = new SugarPHPMailer();
1692 $notify_mail->AddAddress($notify_address,$locale->translateCharsetMIME(trim($notify_name), 'UTF-8', $OBCharset));
1694 if(empty($_SESSION['authenticated_user_language'])) {
1695 $current_language = $sugar_config['default_language'];
1697 $current_language = $_SESSION['authenticated_user_language'];
1699 $xtpl = new XTemplate(get_notify_template_file($current_language));
1700 if($this->module_dir == "Cases") {
1701 $template_name = "Case"; //we should use Case, you can refer to the en_us.notify_template.html.
1704 $template_name = $beanList[$this->module_dir]; //bug 20637, in workflow this->object_name = strange chars.
1707 $this->current_notify_user = $notify_user;
1709 if(in_array('set_notification_body', get_class_methods($this))) {
1710 $xtpl = $this->set_notification_body($xtpl, $this);
1712 $xtpl->assign("OBJECT", $this->object_name);
1713 $template_name = "Default";
1715 if(!empty($_SESSION["special_notification"]) && $_SESSION["special_notification"]) {
1716 $template_name = $beanList[$this->module_dir].'Special';
1718 if($this->special_notification) {
1719 $template_name = $beanList[$this->module_dir].'Special';
1721 $xtpl->assign("ASSIGNED_USER", $this->new_assigned_user_name);
1722 $xtpl->assign("ASSIGNER", $current_user->name);
1725 if(isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) {
1726 $port = $_SERVER['SERVER_PORT'];
1729 if (!isset($_SERVER['HTTP_HOST'])) {
1730 $_SERVER['HTTP_HOST'] = '';
1733 $httpHost = $_SERVER['HTTP_HOST'];
1735 if($colon = strpos($httpHost, ':')) {
1736 $httpHost = substr($httpHost, 0, $colon);
1739 $parsedSiteUrl = parse_url($sugar_config['site_url']);
1740 $host = $parsedSiteUrl['host'];
1741 if(!isset($parsedSiteUrl['port'])) {
1742 $parsedSiteUrl['port'] = 80;
1745 $port = ($parsedSiteUrl['port'] != 80) ? ":".$parsedSiteUrl['port'] : '';
1746 $path = !empty($parsedSiteUrl['path']) ? $parsedSiteUrl['path'] : "";
1747 $cleanUrl = "{$parsedSiteUrl['scheme']}://{$host}{$port}{$path}";
1749 $xtpl->assign("URL", $cleanUrl."/index.php?module={$this->module_dir}&action=DetailView&record={$this->id}");
1750 $xtpl->assign("SUGAR", "Sugar v{$sugar_version}");
1751 $xtpl->parse($template_name);
1752 $xtpl->parse($template_name . "_Subject");
1754 $notify_mail->Body = from_html(trim($xtpl->text($template_name)));
1755 $notify_mail->Subject = from_html($xtpl->text($template_name . "_Subject"));
1757 // cn: bug 8568 encode notify email in User's outbound email encoding
1758 $notify_mail->prepForOutbound();
1760 return $notify_mail;
1764 * This function is a good location to save changes that have been made to a relationship.
1765 * This should be overriden in subclasses that have something to save.
1767 * @param $is_update true if this save is an update.
1769 function save_relationship_changes($is_update, $exclude=array())
1771 $new_rel_id = false;
1772 $new_rel_link = false;
1774 //this allows us to dynamically relate modules without adding it to the relationship_fields array
1775 if(!empty($_REQUEST['relate_id']) && !empty($_REQUEST['relate_to']) && !in_array($_REQUEST['relate_to'], $exclude) && $_REQUEST['relate_id'] != $this->id){
1776 $new_rel_id = $_REQUEST['relate_id'];
1777 $new_rel_relname = $_REQUEST['relate_to'];
1778 if(!empty($this->in_workflow) && !empty($this->not_use_rel_in_req)) {
1779 $new_rel_id = $this->new_rel_id;
1780 $new_rel_relname = $this->new_rel_relname;
1782 $new_rel_link = $new_rel_relname;
1783 //Try to find the link in this bean based on the relationship
1784 foreach ( $this->field_defs as $key => $def ) {
1785 if (isset($def['type']) && $def['type'] == 'link'
1786 && isset($def['relationship']) && $def['relationship'] == $new_rel_relname) {
1787 $new_rel_link = $key;
1793 // First we handle the preset fields listed in the fixed relationship_fields array hardcoded into the OOB beans
1794 // TODO: remove this mechanism and replace with mechanism exclusively based on the vardefs
1795 if (isset($this->relationship_fields) && is_array($this->relationship_fields))
1797 foreach ($this->relationship_fields as $id=>$rel_name)
1800 if(in_array($id, $exclude))continue;
1802 if(!empty($this->$id))
1804 $GLOBALS['log']->debug('save_relationship_changes(): From relationship_field array - adding a relationship record: '.$rel_name . ' = ' . $this->$id);
1805 //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
1806 if($this->$id == $new_rel_id){
1807 $new_rel_id = false;
1809 $this->load_relationship($rel_name);
1810 $this->$rel_name->add($this->$id);
1815 //if before value is not empty then attempt to delete relationship
1816 if(!empty($this->rel_fields_before_value[$id]))
1818 $GLOBALS['log']->debug('save_relationship_changes(): From relationship_field array - attempting to remove the relationship record, using relationship attribute'.$rel_name);
1819 $this->load_relationship($rel_name);
1820 $this->$rel_name->delete($this->id,$this->rel_fields_before_value[$id]);
1826 /* Next, we'll attempt to update all of the remaining relate fields in the vardefs that have 'save' set in their field_def
1827 Only the 'save' fields should be saved as some vardef entries today are not for display only purposes and break the application if saved
1828 If the vardef has entries for field <a> of type relate, where a->id_name = <b> and field <b> of type link
1829 then we receive a value for b from the MVC in the _REQUEST, and it should be set in the bean as $this->$b
1832 foreach ( $this->field_defs as $def )
1834 if ($def [ 'type' ] == 'relate' && isset ( $def [ 'id_name'] ) && isset ( $def [ 'link'] ) && isset ( $def[ 'save' ]) )
1836 if ( in_array( $def['id_name'], $exclude) || in_array( $def['id_name'], $this->relationship_fields ) )
1837 continue ; // continue to honor the exclude array and exclude any relationships that will be handled by the relationship_fields mechanism
1839 if (isset( $this->field_defs[ $def [ 'link' ] ] ))
1842 $linkfield = $this->field_defs[$def [ 'link' ]] ;
1844 if ($this->load_relationship ( $def [ 'link' ])){
1845 if (!empty($this->rel_fields_before_value[$def [ 'id_name' ]]))
1847 //if before value is not empty then attempt to delete relationship
1848 $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' ]]}");
1849 $this->$def ['link' ]->delete($this->id, $this->rel_fields_before_value[$def [ 'id_name' ]] );
1851 if (!empty($this->$def['id_name']) && is_string($this->$def['id_name']))
1853 $GLOBALS['log']->debug("save_relationship_changes(): From field_defs - attempting to add a relationship record - {$def [ 'link' ]} = {$this->$def [ 'id_name' ]}" );
1854 $this->$def ['link' ]->add($this->$def['id_name']);
1857 $GLOBALS['log']->fatal("Failed to load relationship {$def [ 'link' ]} while saving {$this->module_dir}");
1863 // Finally, we update a field listed in the _REQUEST['*/relate_id']/_REQUEST['relate_to'] mechanism (if it hasn't already been updated above)
1864 if(!empty($new_rel_id)){
1866 if($this->load_relationship($new_rel_link)){
1867 $this->$new_rel_link->add($new_rel_id);
1870 $lower_link = strtolower($new_rel_link);
1871 if($this->load_relationship($lower_link)){
1872 $this->$lower_link->add($new_rel_id);
1875 require_once('data/Link.php');
1876 $rel = Relationship::retrieve_by_modules($new_rel_link, $this->module_dir, $GLOBALS['db'], 'many-to-many');
1879 foreach($this->field_defs as $field=>$def){
1880 if($def['type'] == 'link' && !empty($def['relationship']) && $def['relationship'] == $rel){
1881 $this->load_relationship($field);
1882 $this->$field->add($new_rel_id);
1888 //ok so we didn't find it in the field defs let's save it anyway if we have the relationshp
1890 $this->$rel=new Link($rel, $this, array());
1891 $this->$rel->add($new_rel_id);
1902 * This function retrieves a record of the appropriate type from the DB.
1903 * It fills in all of the fields from the DB into the object it was called on.
1905 * @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.
1906 * @return this - The object that it was called apon or null if exactly 1 record was not found.
1910 function check_date_relationships_load()
1912 global $disable_date_format;
1914 if (empty($timedate))
1915 $timedate=TimeDate::getInstance();
1917 if(empty($this->field_defs))
1921 foreach($this->field_defs as $fieldDef)
1923 $field = $fieldDef['name'];
1924 if(!isset($this->processed_dates_times[$field]))
1926 $this->processed_dates_times[$field] = '1';
1927 if(empty($this->$field)) continue;
1928 if($field == 'date_modified' || $field == 'date_entered')
1930 $this->$field = from_db_convert($this->$field, 'datetime');
1931 if(empty($disable_date_format)) {
1932 $this->$field = $timedate->to_display_date_time($this->$field);
1935 elseif(isset($this->field_name_map[$field]['type']))
1937 $type = $this->field_name_map[$field]['type'];
1939 if($type == 'relate' && isset($this->field_name_map[$field]['custom_module']))
1941 $type = $this->field_name_map[$field]['type'];
1946 $this->$field = from_db_convert($this->$field, 'date');
1948 if($this->$field == '0000-00-00')
1951 } elseif(!empty($this->field_name_map[$field]['rel_field']))
1953 $rel_field = $this->field_name_map[$field]['rel_field'];
1955 if(!empty($this->$rel_field))
1957 $this->$rel_field=from_db_convert($this->$rel_field, 'time');
1958 if(empty($disable_date_format)) {
1959 $mergetime = $timedate->merge_date_time($this->$field,$this->$rel_field);
1960 $this->$field = $timedate->to_display_date($mergetime);
1961 $this->$rel_field = $timedate->to_display_time($mergetime);
1967 if(empty($disable_date_format)) {
1968 $this->$field = $timedate->to_display_date($this->$field, false);
1971 } elseif($type == 'datetime' || $type == 'datetimecombo')
1973 if($this->$field == '0000-00-00 00:00:00')
1979 $this->$field = from_db_convert($this->$field, 'datetime');
1980 if(empty($disable_date_format)) {
1981 $this->$field = $timedate->to_display_date_time($this->$field, true, true);
1984 } elseif($type == 'time')
1986 if($this->$field == '00:00:00')
1991 //$this->$field = from_db_convert($this->$field, 'time');
1992 if(empty($this->field_name_map[$field]['rel_field']) && empty($disable_date_format))
1994 $this->$field = $timedate->to_display_time($this->$field,true, false);
1997 } elseif($type == 'encrypt' && empty($disable_date_format)){
1998 $this->$field = $this->decrypt_after_retrieve($this->$field);
2006 * This function processes the fields before save.
2007 * Interal function, do not override.
2009 function preprocess_fields_on_save()
2011 $GLOBALS['log']->deprecated('SugarBean.php: preprocess_fields_on_save() is deprecated');
2015 * Removes formatting from values posted from the user interface.
2016 * It only unformats numbers. Function relies on user/system prefernce for format strings.
2018 * Internal Function, do not override.
2020 function unformat_all_fields()
2022 $GLOBALS['log']->deprecated('SugarBean.php: unformat_all_fields() is deprecated');
2026 * This functions adds formatting to all number fields before presenting them to user interface.
2028 * Internal function, do not override.
2030 function format_all_fields()
2032 $GLOBALS['log']->deprecated('SugarBean.php: format_all_fields() is deprecated');
2035 function format_field($fieldDef)
2037 $GLOBALS['log']->deprecated('SugarBean.php: format_field() is deprecated');
2041 * Function corrects any bad formatting done by 3rd party/custom code
2043 * 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
2045 function fixUpFormatting()
2048 static $boolean_false_values = array('off', 'false', '0', 'no');
2051 foreach($this->field_defs as $field=>$def)
2053 if ( !isset($this->$field) ) {
2056 if ( (isset($def['source'])&&$def['source']=='non-db') || $field == 'deleted' ) {
2059 if ( isset($this->fetched_row[$field]) && $this->$field == $this->fetched_row[$field] ) {
2060 // Don't hand out warnings because the field was untouched between retrieval and saving, most database drivers hand pretty much everything back as strings.
2063 $reformatted = false;
2064 switch($def['type']) {
2066 case 'datetimecombo':
2067 if(empty($this->$field)) break;
2068 if ($this->$field == 'NULL') {
2072 if ( ! preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$/',$this->$field) ) {
2073 // This appears to be formatted in user date/time
2074 $this->$field = $timedate->to_db($this->$field);
2075 $reformatted = true;
2079 if(empty($this->$field)) break;
2080 if ($this->$field == 'NULL') {
2084 if ( ! preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/',$this->$field) ) {
2085 // This date appears to be formatted in the user's format
2086 $this->$field = $timedate->to_db_date($this->$field, false);
2087 $reformatted = true;
2091 if(empty($this->$field)) break;
2092 if ($this->$field == 'NULL') {
2096 if ( preg_match('/(am|pm)/i',$this->$field) ) {
2097 // This time appears to be formatted in the user's format
2098 $this->$field = $timedate->fromUserTime($this->$field)->format(TimeDate::DB_TIME_FORMAT);
2099 $reformatted = true;
2106 if ( $this->$field === '' || $this->$field == NULL || $this->$field == 'NULL') {
2109 if ( is_string($this->$field) ) {
2110 $this->$field = (float)unformat_number($this->$field);
2111 $reformatted = true;
2120 if ( $this->$field === '' || $this->$field == NULL || $this->$field == 'NULL') {
2123 if ( is_string($this->$field) ) {
2124 $this->$field = (int)unformat_number($this->$field);
2125 $reformatted = true;
2129 if (empty($this->$field)) {
2130 $this->$field = false;
2131 } else if(true === $this->$field || 1 == $this->$field) {
2132 $this->$field = true;
2133 } else if(in_array(strval($this->$field), $boolean_false_values)) {
2134 $this->$field = false;
2135 $reformatted = true;
2137 $this->$field = true;
2138 $reformatted = true;
2142 $this->$field = $this->encrpyt_before_save($this->$field);
2145 if ( $reformatted ) {
2146 $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');
2153 * Function fetches a single row of data given the primary key value.
2155 * The fetched data is then set into the bean. The function also processes the fetched data by formattig
2156 * date/time and numeric values.
2158 * @param string $id Optional, default -1, is set to -1 id value from the bean is used, else, passed value is used
2159 * @param boolean $encode Optional, default true, encodes the values fetched from the database.
2160 * @param boolean $deleted Optional, default true, if set to false deleted filter will not be added.
2162 * Internal function, do not override.
2164 function retrieve($id = -1, $encode=true,$deleted=true)
2167 $custom_logic_arguments['id'] = $id;
2168 $this->call_custom_logic('before_retrieve', $custom_logic_arguments);
2174 if(isset($this->custom_fields))
2176 $custom_join = $this->custom_fields->getJOIN();
2179 $custom_join = false;
2183 $query = "SELECT $this->table_name.*". $custom_join['select']. " FROM $this->table_name ";
2187 $query = "SELECT $this->table_name.* FROM $this->table_name ";
2192 $query .= ' ' . $custom_join['join'];
2194 $query .= " WHERE $this->table_name.id = '$id' ";
2195 if ($deleted) $query .= " AND $this->table_name.deleted=0";
2196 $GLOBALS['log']->debug("Retrieve $this->object_name : ".$query);
2197 //requireSingleResult has beeen deprecated.
2198 //$result = $this->db->requireSingleResult($query, true, "Retrieving record by id $this->table_name:$id found ");
2199 $result = $this->db->limitQuery($query,0,1,true, "Retrieving record by id $this->table_name:$id found ");
2205 $row = $this->db->fetchByAssoc($result, -1, $encode);
2211 //make copy of the fetched row for construction of audit record and for business logic/workflow
2212 $this->fetched_row=$row;
2213 $this->populateFromRow($row);
2215 global $module, $action;
2216 //Just to get optimistic locking working for this release
2217 if($this->optimistic_lock && $module == $this->module_dir && $action =='EditView' )
2219 $_SESSION['o_lock_id']= $id;
2220 $_SESSION['o_lock_dm']= $this->date_modified;
2221 $_SESSION['o_lock_on'] = $this->object_name;
2223 $this->processed_dates_times = array();
2224 $this->check_date_relationships_load();
2228 $this->custom_fields->fill_relationships();
2231 $this->fill_in_additional_detail_fields();
2232 $this->fill_in_relationship_fields();
2233 //make a copy of fields in the relatiosnhip_fields array. these field values will be used to
2234 //clear relatioship.
2235 foreach ( $this->field_defs as $key => $def )
2237 if ($def [ 'type' ] == 'relate' && isset ( $def [ 'id_name'] ) && isset ( $def [ 'link'] ) && isset ( $def[ 'save' ])) {
2238 if (isset($this->$key)) {
2239 $this->rel_fields_before_value[$key]=$this->$key;
2240 if (isset($this->$def [ 'id_name']))
2241 $this->rel_fields_before_value[$def [ 'id_name']]=$this->$def [ 'id_name'];
2244 $this->rel_fields_before_value[$key]=null;
2247 if (isset($this->relationship_fields) && is_array($this->relationship_fields))
2249 foreach ($this->relationship_fields as $rel_id=>$rel_name)
2251 if (isset($this->$rel_id))
2252 $this->rel_fields_before_value[$rel_id]=$this->$rel_id;
2254 $this->rel_fields_before_value[$rel_id]=null;
2258 // call the custom business logic
2259 $custom_logic_arguments['id'] = $id;
2260 $custom_logic_arguments['encode'] = $encode;
2261 $this->call_custom_logic("after_retrieve", $custom_logic_arguments);
2262 unset($custom_logic_arguments);
2267 * Sets value from fetched row into the bean.
2269 * @param array $row Fetched row
2270 * @todo loop through vardefs instead
2271 * @internal runs into an issue when populating from field_defs for users - corrupts user prefs
2273 * Internal function, do not override.
2275 function populateFromRow($row)
2278 foreach($this->field_defs as $field=>$field_value)
2280 if($field == 'user_preferences' && $this->module_dir == 'Users')
2282 $rfield = $field; // fetch returns it in lowercase only
2283 if(isset($row[$rfield]))
2285 $this->$field = $row[$rfield];
2286 $owner = $rfield . '_owner';
2287 if(!empty($row[$owner])){
2288 $this->$owner = $row[$owner];
2293 $this->$field = $nullvalue;
2301 * Add any required joins to the list count query. The joins are required if there
2302 * is a field in the $where clause that needs to be joined.
2304 * @param string $query
2305 * @param string $where
2307 * Internal Function, do Not override.
2309 function add_list_count_joins(&$query, $where)
2311 $custom_join = $this->custom_fields->getJOIN();
2314 $query .= $custom_join['join'];
2320 * Changes the select expression of the given query to be 'count(*)' so you
2321 * can get the number of items the query will return. This is used to
2322 * populate the upper limit on ListViews.
2324 * @param string $query Select query string
2325 * @return string count query
2327 * Internal function, do not override.
2329 function create_list_count_query($query)
2331 // remove the 'order by' clause which is expected to be at the end of the query
2332 $pattern = '/\sORDER BY.*/is'; // ignores the case
2334 $query = preg_replace($pattern, $replacement, $query);
2335 //handle distinct clause
2337 if(substr_count(strtolower($query), 'distinct')){
2338 if (!empty($this->seed) && !empty($this->seed->table_name ))
2339 $star = 'DISTINCT ' . $this->seed->table_name . '.id';
2341 $star = 'DISTINCT ' . $this->table_name . '.id';
2345 // change the select expression to 'count(*)'
2346 $pattern = '/SELECT(.*?)(\s){1}FROM(\s){1}/is'; // ignores the case
2347 $replacement = 'SELECT count(' . $star . ') c FROM ';
2349 //if the passed query has union clause then replace all instances of the pattern.
2350 //this is very rare. I have seen this happening only from projects module.
2351 //in addition to this added a condition that has union clause and uses
2353 if (strstr($query," UNION ALL ") !== false) {
2355 //seperate out all the queries.
2356 $union_qs=explode(" UNION ALL ", $query);
2357 foreach ($union_qs as $key=>$union_query) {
2359 preg_match($pattern, $union_query, $matches);
2360 if (!empty($matches)) {
2361 if (stristr($matches[0], "distinct")) {
2362 if (!empty($this->seed) && !empty($this->seed->table_name ))
2363 $star = 'DISTINCT ' . $this->seed->table_name . '.id';
2365 $star = 'DISTINCT ' . $this->table_name . '.id';
2368 $replacement = 'SELECT count(' . $star . ') c FROM ';
2369 $union_qs[$key] = preg_replace($pattern, $replacement, $union_query,1);
2371 $modified_select_query=implode(" UNION ALL ",$union_qs);
2373 $modified_select_query = preg_replace($pattern, $replacement, $query,1);
2376 return $modified_select_query;
2380 * This function returns a paged list of the current object type. It is intended to allow for
2381 * hopping back and forth through pages of data. It only retrieves what is on the current page.
2383 * @internal This method must be called on a new instance. It trashes the values of all the fields in the current one.
2384 * @param string $order_by
2385 * @param string $where Additional where clause
2386 * @param int $row_offset Optaional,default 0, starting row number
2387 * @param init $limit Optional, default -1
2388 * @param int $max Optional, default -1
2389 * @param boolean $show_deleted Optioanl, default 0, if set to 1 system will show deleted records.
2390 * @return array Fetched data.
2392 * Internal function, do not override.
2395 function get_list($order_by = "", $where = "", $row_offset = 0, $limit=-1, $max=-1, $show_deleted = 0, $singleSelect=false)
2397 $GLOBALS['log']->debug("get_list: order_by = '$order_by' and where = '$where' and limit = '$limit'");
2398 if(isset($_SESSION['show_deleted']))
2402 $order_by=$this->process_order_by($order_by, null);
2404 if($this->bean_implements('ACL') && ACLController::requireOwner($this->module_dir, 'list') )
2406 global $current_user;
2407 $owner_where = $this->getOwnerWhere($current_user->id);
2409 //rrs - because $this->getOwnerWhere() can return '' we need to be sure to check for it and
2410 //handle it properly else you could get into a situation where you are create a where stmt like
2412 if(!empty($owner_where)){
2414 $where = $owner_where;
2416 $where .= ' AND '. $owner_where;
2420 $query = $this->create_new_list_query($order_by, $where,array(),array(), $show_deleted,'',false,null,$singleSelect);
2421 return $this->process_list_query($query, $row_offset, $limit, $max, $where);
2425 * Prefixes column names with this bean's table name.
2426 * This call can be ignored for mysql since it does a better job than Oracle in resolving ambiguity.
2428 * @param string $order_by Order by clause to be processed
2429 * @param string $submodule name of the module this order by clause is for
2430 * @return string Processed order by clause
2432 * Internal function, do not override.
2434 function process_order_by ($order_by, $submodule)
2436 if (empty($order_by))
2439 //submodule is empty,this is for list object in focus
2440 if (empty($submodule))
2442 $bean_queried = &$this;
2446 //submodule is set, so this is for subpanel, use submodule
2447 $bean_queried = $submodule;
2449 $elements = explode(',',$order_by);
2450 foreach ($elements as $key=>$value)
2452 if (strchr($value,'.') === false)
2454 //value might have ascending and descending decorations
2455 $list_column = explode(' ',trim($value));
2456 if (isset($list_column[0]))
2458 $list_column_name=trim($list_column[0]);
2459 if (isset($bean_queried->field_defs[$list_column_name]))
2461 $source=isset($bean_queried->field_defs[$list_column_name]['source']) ? $bean_queried->field_defs[$list_column_name]['source']:'db';
2462 if (empty($bean_queried->field_defs[$list_column_name]['table']) && $source=='db')
2464 $list_column[0] = $bean_queried->table_name .".".$list_column[0] ;
2466 if (empty($bean_queried->field_defs[$list_column_name]['table']) && $source=='custom_fields')
2468 $list_column[0] = $bean_queried->table_name ."_cstm.".$list_column[0] ;
2470 $value = implode($list_column,' ');
2471 // Bug 38803 - Use CONVERT() function when doing an order by on ntext, text, and image fields
2472 if ( $this->db->dbType == 'mssql'
2473 && $source != 'non-db'
2475 $this->db->getHelper()->getColumnType($this->db->getHelper()->getFieldType($bean_queried->field_defs[$list_column_name])),
2476 array('ntext','text','image')
2479 $value = "CONVERT(varchar(500),{$list_column[0]}) {$list_column[1]}";
2481 // Bug 29011 - Use TO_CHAR() function when doing an order by on a clob field
2482 if ( $this->db->dbType == 'oci8'
2483 && $source != 'non-db'
2485 $this->db->getHelper()->getColumnType($this->db->getHelper()->getFieldType($bean_queried->field_defs[$list_column_name])),
2489 $value = "TO_CHAR({$list_column[0]}) {$list_column[1]}";
2494 $GLOBALS['log']->debug("process_order_by: ($list_column[0]) does not have a vardef entry.");
2498 $elements[$key]=$value;
2500 return implode($elements,',');
2506 * Returns a detail object like retrieving of the current object type.
2508 * It is intended for use in navigation buttons on the DetailView. It will pass an offset and limit argument to the sql query.
2509 * @internal This method must be called on a new instance. It overrides the values of all the fields in the current one.
2511 * @param string $order_by
2512 * @param string $where Additional where clause
2513 * @param int $row_offset Optaional,default 0, starting row number
2514 * @param init $limit Optional, default -1
2515 * @param int $max Optional, default -1
2516 * @param boolean $show_deleted Optioanl, default 0, if set to 1 system will show deleted records.
2517 * @return array Fetched data.
2519 * Internal function, do not override.
2521 function get_detail($order_by = "", $where = "", $offset = 0, $row_offset = 0, $limit=-1, $max=-1, $show_deleted = 0)
2523 $GLOBALS['log']->debug("get_detail: order_by = '$order_by' and where = '$where' and limit = '$limit' and offset = '$offset'");
2524 if(isset($_SESSION['show_deleted']))
2529 if($this->bean_implements('ACL') && ACLController::requireOwner($this->module_dir, 'list') )
2531 global $current_user;
2532 $owner_where = $this->getOwnerWhere($current_user->id);
2536 $where = $owner_where;
2540 $where .= ' AND '. $owner_where;
2543 $query = $this->create_new_list_query($order_by, $where,array(),array(), $show_deleted, $offset);
2545 //Add Limit and Offset to query
2546 //$query .= " LIMIT 1 OFFSET $offset";
2548 return $this->process_detail_query($query, $row_offset, $limit, $max, $where, $offset);
2552 * Fetches data from all related tables.
2554 * @param object $child_seed
2555 * @param string $related_field_name relation to fetch data for
2556 * @param string $order_by Optional, default empty
2557 * @param string $where Optional, additional where clause
2558 * @return array Fetched data.
2560 * Internal function, do not override.
2562 function get_related_list($child_seed,$related_field_name, $order_by = "", $where = "",
2563 $row_offset = 0, $limit=-1, $max=-1, $show_deleted = 0)
2565 global $layout_edit_mode;
2566 if(isset($layout_edit_mode) && $layout_edit_mode)
2568 $response = array();
2569 $child_seed->assign_display_fields($child_seed->module_dir);
2570 $response['list'] = array($child_seed);
2571 $response['row_count'] = 1;
2572 $response['next_offset'] = 0;
2573 $response['previous_offset'] = 0;
2577 $GLOBALS['log']->debug("get_related_list: order_by = '$order_by' and where = '$where' and limit = '$limit'");
2578 if(isset($_SESSION['show_deleted']))
2583 $this->load_relationship($related_field_name);
2584 $query_array = $this->$related_field_name->getQuery(true);
2585 $entire_where = $query_array['where'];
2588 if(empty($entire_where))
2590 $entire_where = ' WHERE ' . $where;
2594 $entire_where .= ' AND ' . $where;
2598 $query = 'SELECT '.$child_seed->table_name.'.* ' . $query_array['from'] . ' ' . $entire_where;
2599 if(!empty($order_by))
2601 $query .= " ORDER BY " . $order_by;
2604 return $child_seed->process_list_query($query, $row_offset, $limit, $max, $where);
2608 protected static function build_sub_queries_for_union($subpanel_list, $subpanel_def, $parentbean, $order_by)
2610 global $layout_edit_mode, $beanFiles, $beanList;
2611 $subqueries = array();
2612 foreach($subpanel_list as $this_subpanel)
2614 if(!$this_subpanel->isDatasourceFunction() || ($this_subpanel->isDatasourceFunction()
2615 && isset($this_subpanel->_instance_properties['generate_select'])
2616 && $this_subpanel->_instance_properties['generate_select']==true))
2618 //the custom query function must return an array with
2619 if ($this_subpanel->isDatasourceFunction()) {
2620 $shortcut_function_name = $this_subpanel->get_data_source_name();
2621 $parameters=$this_subpanel->get_function_parameters();
2622 if (!empty($parameters))
2624 //if the import file function is set, then import the file to call the custom function from
2625 if (is_array($parameters) && isset($parameters['import_function_file'])){
2626 //this call may happen multiple times, so only require if function does not exist
2627 if(!function_exists($shortcut_function_name)){
2628 require_once($parameters['import_function_file']);
2630 //call function from required file
2631 $query_array = $shortcut_function_name($parameters);
2633 //call function from parent bean
2634 $query_array = $parentbean->$shortcut_function_name($parameters);
2639 $query_array = $parentbean->$shortcut_function_name();
2642 $related_field_name = $this_subpanel->get_data_source_name();
2643 if (!$parentbean->load_relationship($related_field_name)){
2644 unset ($parentbean->$related_field_name);
2647 $query_array = $parentbean->$related_field_name->getQuery(true,array(),0,'',true, null, null, true);
2649 $table_where = $this_subpanel->get_where();
2650 $where_definition = $query_array['where'];
2652 if(!empty($table_where))
2654 if(empty($where_definition))
2656 $where_definition = $table_where;
2660 $where_definition .= ' AND ' . $table_where;
2664 $submodulename = $this_subpanel->_instance_properties['module'];
2665 $submoduleclass = $beanList[$submodulename];
2666 //require_once($beanFiles[$submoduleclass]);
2667 $submodule = new $submoduleclass();
2668 $subwhere = $where_definition;
2672 $subwhere = str_replace('WHERE', '', $subwhere);
2673 $list_fields = $this_subpanel->get_list_fields();
2674 foreach($list_fields as $list_key=>$list_field)
2676 if(isset($list_field['usage']) && $list_field['usage'] == 'display_only')
2678 unset($list_fields[$list_key]);
2681 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'))
2683 $order_by = $submodule->table_name .'.'. $order_by;
2685 $table_name = $this_subpanel->table_name;
2686 $panel_name=$this_subpanel->name;
2688 $params['distinct'] = $this_subpanel->distinct_query();
2690 $params['joined_tables'] = $query_array['join_tables'];
2691 $params['include_custom_fields'] = !$subpanel_def->isCollection();
2692 $params['collection_list'] = $subpanel_def->get_inst_prop_value('collection_list');
2694 $subquery = $submodule->create_new_list_query('',$subwhere ,$list_fields,$params, 0,'', true,$parentbean);
2696 $subquery['select'] = $subquery['select']." , '$panel_name' panel_name ";
2697 $subquery['from'] = $subquery['from'].$query_array['join'];
2698 $subquery['query_array'] = $query_array;
2699 $subquery['params'] = $params;
2701 $subqueries[] = $subquery;
2708 * Constructs a query to fetch data for supanels and list views
2710 * It constructs union queries for activities subpanel.
2712 * @param Object $parentbean constructing queries for link attributes in this bean
2713 * @param string $order_by Optional, order by clause
2714 * @param string $sort_order Optional, sort order
2715 * @param string $where Optional, additional where clause
2717 * Internal Function, do not overide.
2719 function get_union_related_list($parentbean, $order_by = "", $sort_order='', $where = "",
2720 $row_offset = 0, $limit=-1, $max=-1, $show_deleted = 0, $subpanel_def)
2722 $secondary_queries = array();
2723 global $layout_edit_mode, $beanFiles, $beanList;
2725 if(isset($_SESSION['show_deleted']))
2730 $final_query_rows = '';
2731 $subpanel_list=array();
2732 if ($subpanel_def->isCollection())
2734 $subpanel_def->load_sub_subpanels();
2735 $subpanel_list=$subpanel_def->sub_subpanels;
2739 $subpanel_list[]=$subpanel_def;
2744 //Breaking the building process into two loops. The first loop gets a list of all the sub-queries.
2745 //The second loop merges the queries and forces them to select the same number of columns
2746 //All columns in a sub-subpanel group must have the same aliases
2747 //If the subpanel is a datasource function, it can't be a collection so we just poll that function for the and return that
2748 foreach($subpanel_list as $this_subpanel)
2750 if($this_subpanel->isDatasourceFunction() && empty($this_subpanel->_instance_properties['generate_select']))
2752 $shortcut_function_name = $this_subpanel->get_data_source_name();
2753 $parameters=$this_subpanel->get_function_parameters();
2754 if (!empty($parameters))
2756 //if the import file function is set, then import the file to call the custom function from
2757 if (is_array($parameters) && isset($parameters['import_function_file'])){
2758 //this call may happen multiple times, so only require if function does not exist
2759 if(!function_exists($shortcut_function_name)){
2760 require_once($parameters['import_function_file']);
2762 //call function from required file
2763 $tmp_final_query = $shortcut_function_name($parameters);
2765 //call function from parent bean
2766 $tmp_final_query = $parentbean->$shortcut_function_name($parameters);
2771 $tmp_final_query = $parentbean->$shortcut_function_name();
2775 $final_query_rows .= ' UNION ALL ( '.$parentbean->create_list_count_query($tmp_final_query, $parameters) . ' )';
2776 $final_query .= ' UNION ALL ( '.$tmp_final_query . ' )';
2778 $final_query_rows = '(' . $parentbean->create_list_count_query($tmp_final_query, $parameters) . ')';
2779 $final_query = '(' . $tmp_final_query . ')';
2784 //If final_query is still empty, its time to build the sub-queries
2785 if (empty($final_query))
2787 $subqueries = SugarBean::build_sub_queries_for_union($subpanel_list, $subpanel_def, $parentbean, $order_by);
2788 $all_fields = array();
2789 foreach($subqueries as $i => $subquery)
2791 $query_fields = $GLOBALS['db']->helper->getSelectFieldsFromQuery($subquery['select']);
2792 foreach($query_fields as $field => $select)
2794 if (!in_array($field, $all_fields))
2795 $all_fields[] = $field;
2797 $subqueries[$i]['query_fields'] = $query_fields;
2800 //Now ensure the queries have the same set of fields in the same order.
2801 foreach($subqueries as $subquery)
2803 $subquery['select'] = "SELECT";
2804 foreach($all_fields as $field)
2806 if (!isset($subquery['query_fields'][$field]))
2808 $subquery['select'] .= " ' ' $field,";
2812 $subquery['select'] .= " {$subquery['query_fields'][$field]},";
2815 $subquery['select'] = substr($subquery['select'], 0 , strlen($subquery['select']) - 1);
2816 //Put the query into the final_query
2817 $query = $subquery['select'] . " " . $subquery['from'] . " " . $subquery['where'];
2820 $query = ' UNION ALL ( '.$query . ' )';
2821 $final_query_rows .= " UNION ALL ";
2823 $query = '(' . $query . ')';
2826 $query_array = $subquery['query_array'];
2827 $select_position=strpos($query_array['select'],"SELECT");
2828 $distinct_position=strpos($query_array['select'],"DISTINCT");
2829 if ($select_position !== false && $distinct_position!= false)
2831 $query_rows = "( ".substr_replace($query_array['select'],"SELECT count(",$select_position,6). ")" . $subquery['from_min'].$query_array['join']. $subquery['where'].' )';
2835 //resort to default behavior.
2836 $query_rows = "( SELECT count(*)". $subquery['from_min'].$query_array['join']. $subquery['where'].' )';
2838 if(!empty($subquery['secondary_select']))
2841 $subquerystring= $subquery['secondary_select'] . $subquery['secondary_from'].$query_array['join']. $subquery['where'];
2842 if (!empty($subquery['secondary_where']))
2844 if (empty($subquery['where']))
2846 $subquerystring.=" WHERE " .$subquery['secondary_where'];
2850 $subquerystring.=" AND " .$subquery['secondary_where'];
2853 $secondary_queries[]=$subquerystring;
2855 $final_query .= $query;
2856 $final_query_rows .= $query_rows;
2860 if(!empty($order_by))
2863 if(!$subpanel_def->isCollection())
2865 $submodulename = $subpanel_def->_instance_properties['module'];
2866 $submoduleclass = $beanList[$submodulename];
2867 $submodule = new $submoduleclass();
2869 if(!empty($submodule) && !empty($submodule->table_name))
2871 $final_query .= " ORDER BY " .$parentbean->process_order_by($order_by, $submodule);
2876 $final_query .= " ORDER BY ". $order_by . ' ';
2878 if(!empty($sort_order))
2880 $final_query .= ' ' .$sort_order;
2885 if(isset($layout_edit_mode) && $layout_edit_mode)
2887 $response = array();
2888 if(!empty($submodule))
2890 $submodule->assign_display_fields($submodule->module_dir);
2891 $response['list'] = array($submodule);
2895 $response['list'] = array();
2897 $response['parent_data'] = array();
2898 $response['row_count'] = 1;
2899 $response['next_offset'] = 0;
2900 $response['previous_offset'] = 0;
2905 return $parentbean->process_union_list_query($parentbean, $final_query, $row_offset, $limit, $max, '',$subpanel_def, $final_query_rows, $secondary_queries);
2910 * Returns a full (ie non-paged) list of the current object type.
2912 * @param string $order_by the order by SQL parameter. defaults to ""
2913 * @param string $where where clause. defaults to ""
2914 * @param boolean $check_dates. defaults to false
2915 * @param int $show_deleted show deleted records. defaults to 0
2917 function get_full_list($order_by = "", $where = "", $check_dates=false, $show_deleted = 0)
2919 $GLOBALS['log']->debug("get_full_list: order_by = '$order_by' and where = '$where'");
2920 if(isset($_SESSION['show_deleted']))
2924 $query = $this->create_new_list_query($order_by, $where,array(),array(), $show_deleted);
2925 return $this->process_full_list_query($query, $check_dates);
2929 * Return the list query used by the list views and export button. Next generation of create_new_list_query function.
2931 * Override this function to return a custom query.
2933 * @param string $order_by custom order by clause
2934 * @param string $where custom where clause
2935 * @param array $filter Optioanal
2936 * @param array $params Optional *
2937 * @param int $show_deleted Optional, default 0, show deleted records is set to 1.
2938 * @param string $join_type
2939 * @param boolean $return_array Optional, default false, response as array
2940 * @param object $parentbean creating a subquery for this bean.
2941 * @param boolean $singleSelect Optional, default false.
2942 * @return String select query string, optionally an array value will be returned if $return_array= true.
2944 function create_new_list_query($order_by, $where,$filter=array(),$params=array(), $show_deleted = 0,$join_type='', $return_array = false,$parentbean=null, $singleSelect = false)
2946 global $beanFiles, $beanList;
2947 $selectedFields = array();
2948 $secondarySelectedFields = array();
2949 $ret_array = array();
2951 if($this->bean_implements('ACL') && ACLController::requireOwner($this->module_dir, 'list') )
2953 global $current_user;
2954 $owner_where = $this->getOwnerWhere($current_user->id);
2957 $where = $owner_where;
2961 $where .= ' AND '. $owner_where;
2964 if(!empty($params['distinct']))
2966 $distinct = ' DISTINCT ';
2970 $ret_array['select'] = " SELECT $distinct $this->table_name.* ";
2974 $ret_array['select'] = " SELECT $distinct $this->table_name.id ";
2976 $ret_array['from'] = " FROM $this->table_name ";
2977 $ret_array['from_min'] = $ret_array['from'];
2978 $ret_array['secondary_from'] = $ret_array['from'] ;
2979 $ret_array['where'] = '';
2980 $ret_array['order_by'] = '';
2981 //secondary selects are selects that need to be run after the primarty query to retrieve additional info on main
2984 $ret_array['secondary_select']=& $ret_array['select'];
2985 $ret_array['secondary_from'] = & $ret_array['from'];
2989 $ret_array['secondary_select'] = '';
2991 $custom_join = false;
2992 if((!isset($params['include_custom_fields']) || $params['include_custom_fields']) && isset($this->custom_fields))
2995 $custom_join = $this->custom_fields->getJOIN( empty($filter)? true: $filter );
2998 $ret_array['select'] .= ' ' .$custom_join['select'];
3003 $ret_array['from'] .= ' ' . $custom_join['join'];
3006 //LOOP AROUND FOR FIXIN VARDEF ISSUES
3007 require('include/VarDefHandler/listvardefoverride.php');
3008 $joined_tables = array();
3009 if(isset($params['joined_tables']))
3011 foreach($params['joined_tables'] as $table)
3013 $joined_tables[$table] = 1;
3019 $filterKeys = array_keys($filter);
3020 if(is_numeric($filterKeys[0]))
3023 foreach($filter as $field)
3025 $field = strtolower($field);
3026 //remove out id field so we don't duplicate it
3027 if ( $field == 'id' && !empty($filter) ) {
3030 if(isset($this->field_defs[$field]))
3032 $fields[$field]= $this->field_defs[$field];
3036 $fields[$field] = array('force_exists'=>true);
3045 $fields = $this->field_defs;
3048 $used_join_key = array();
3050 foreach($fields as $field=>$value)
3052 //alias is used to alias field names
3054 if (isset($value['alias']))
3056 $alias =' as ' . $value['alias'] . ' ';
3059 if(empty($this->field_defs[$field]) || !empty($value['force_blank']) )
3061 if(!empty($filter) && isset($filter[$field]['force_exists']) && $filter[$field]['force_exists'])
3063 if ( isset($filter[$field]['force_default']) )
3064 $ret_array['select'] .= ", {$filter[$field]['force_default']} $field ";
3066 //spaces are a fix for length issue problem with unions. The union only returns the maximum number of characters from the first select statemtn.
3067 $ret_array['select'] .= ", ' ' $field ";
3073 $data = $this->field_defs[$field];
3076 //ignore fields that are a part of the collection and a field has been removed as a result of
3077 //layout customization.. this happens in subpanel customizations, use case, from the contacts subpanel
3078 //in opportunities module remove the contact_role/opportunity_role field.
3079 $process_field=true;
3080 if (isset($data['relationship_fields']) and !empty($data['relationship_fields']))
3082 foreach ($data['relationship_fields'] as $field_name)
3084 if (!isset($fields[$field_name]))
3086 $process_field=false;
3090 if (!$process_field)
3095 if( (!isset($data['source']) || $data['source'] == 'db') && (!empty($alias) || !empty($filter) ))
3097 $ret_array['select'] .= ", $this->table_name.$field $alias";
3098 $selectedFields["$this->table_name.$field"] = true;
3103 if($data['type'] != 'relate' && isset($data['db_concat_fields']))
3105 $ret_array['select'] .= ", " . db_concat($this->table_name, $data['db_concat_fields']) . " as $field";
3106 $selectedFields[db_concat($this->table_name, $data['db_concat_fields'])] = true;
3108 //Custom relate field or relate fields built in module builder which have no link field associated.
3109 if ($data['type'] == 'relate' && (isset($data['custom_module']) || isset($data['ext2']))) {
3110 $joinTableAlias = 'jt' . $jtcount;
3111 $relateJoinInfo = $this->custom_fields->getRelateJoin($data, $joinTableAlias);
3112 $ret_array['select'] .= $relateJoinInfo['select'];
3113 $ret_array['from'] .= $relateJoinInfo['from'];
3114 //Replace any references to the relationship in the where clause with the new alias
3115 //If the link isn't set, assume that search used the local table for the field
3116 $searchTable = isset($data['link']) ? $relateJoinInfo['rel_table'] : $this->table_name;
3117 $field_name = $relateJoinInfo['rel_table'] . '.' . !empty($data['name'])?$data['name']:'name';
3118 $where = preg_replace('/(^|[\s(])' . $field_name . '/' , '${1}' . $relateJoinInfo['name_field'], $where);
3122 if ($data['type'] == 'parent') {
3123 //See if we need to join anything by inspecting the where clause
3124 $match = preg_match('/(^|[\s(])parent_(\w+)_(\w+)\.name/', $where, $matches);
3126 $joinTableAlias = 'jt' . $jtcount;
3127 $joinModule = $matches[2];
3128 $joinTable = $matches[3];
3129 $localTable = $this->table_name;
3130 if (!empty($data['custom_module'])) {
3131 $localTable .= '_cstm';
3133 global $beanFiles, $beanList, $module;
3134 require_once($beanFiles[$beanList[$joinModule]]);
3135 $rel_mod = new $beanList[$joinModule]();
3136 $nameField = "$joinTableAlias.name";
3137 if (isset($rel_mod->field_defs['name']))
3139 $name_field_def = $rel_mod->field_defs['name'];
3140 if(isset($name_field_def['db_concat_fields']))
3142 $nameField = db_concat($joinTableAlias, $name_field_def['db_concat_fields']);
3145 $ret_array['select'] .= ", $nameField {$data['name']} ";
3146 $ret_array['from'] .= " LEFT JOIN $joinTable $joinTableAlias
3147 ON $localTable.{$data['id_name']} = $joinTableAlias.id";
3148 //Replace any references to the relationship in the where clause with the new alias
3149 $where = preg_replace('/(^|[\s(])parent_' . $joinModule . '_' . $joinTable . '\.name/', '${1}' . $nameField, $where);
3153 if($data['type'] == 'relate' && isset($data['link']))
3155 $this->load_relationship($data['link']);
3156 if(!empty($this->$data['link']))
3159 if(empty($join_type))
3161 $params['join_type'] = ' LEFT JOIN ';
3165 $params['join_type'] = $join_type;
3167 if(isset($data['join_name']))
3169 $params['join_table_alias'] = $data['join_name'];
3173 $params['join_table_alias'] = 'jt' . $jtcount;
3176 if(isset($data['join_link_name']))
3178 $params['join_table_link_alias'] = $data['join_link_name'];
3182 $params['join_table_link_alias'] = 'jtl' . $jtcount;
3184 $join_primary = !isset($data['join_primary']) || $data['join_primary'];
3186 $join = $this->$data['link']->getJoin($params, true);
3187 $used_join_key[] = $join['rel_key'];
3188 $rel_module = $this->$data['link']->getRelatedModuleName();
3189 $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');
3191 //if rnanme is set to 'name', and bean files exist, then check if field should be a concatenated name
3192 global $beanFiles, $beanList;
3193 if($data['rname'] && !empty($beanFiles[$beanList[$rel_module]])) {
3195 //create an instance of the related bean
3196 require_once($beanFiles[$beanList[$rel_module]]);
3197 $rel_mod = new $beanList[$rel_module]();
3198 //if bean has first and last name fields, then name should be concatenated
3199 if(isset($rel_mod->field_name_map['first_name']) && isset($rel_mod->field_name_map['last_name'])){
3200 $data['db_concat_fields'] = array(0=>'first_name', 1=>'last_name');
3205 if($join['type'] == 'many-to-many')
3207 if(empty($ret_array['secondary_select']))
3209 $ret_array['secondary_select'] = " SELECT $this->table_name.id ref_id ";
3211 if(!empty($beanFiles[$beanList[$rel_module]]) && $join_primary)
3213 require_once($beanFiles[$beanList[$rel_module]]);
3214 $rel_mod = new $beanList[$rel_module]();
3215 if(isset($rel_mod->field_defs['assigned_user_id']))
3217 $ret_array['secondary_select'].= " , ". $params['join_table_alias'] . ".assigned_user_id {$field}_owner, '$rel_module' {$field}_mod";
3222 if(isset($rel_mod->field_defs['created_by']))
3224 $ret_array['secondary_select'].= " , ". $params['join_table_alias'] . ".created_by {$field}_owner , '$rel_module' {$field}_mod";
3235 if(isset($data['db_concat_fields']))
3237 $ret_array['secondary_select'] .= ' , ' . db_concat($params['join_table_alias'], $data['db_concat_fields']) . ' ' . $field;
3241 if(!isset($data['relationship_fields']))
3243 $ret_array['secondary_select'] .= ' , ' . $params['join_table_alias'] . '.' . $data['rname'] . ' ' . $field;
3248 $ret_array['select'] .= ", ' ' $field ";
3249 $ret_array['select'] .= ", ' ' " . $join['rel_key'] . ' ';
3252 if($this->db->dbType != 'mysql') {//bug 26801, these codes are just used to duplicate rel_key in the select sql, or it will throw error in MSSQL and Oracle.
3253 foreach($used_join_key as $used_key) {
3254 if($used_key == $join['rel_key']) $count_used++;
3257 if($count_used <= 1) {//27416, the $ret_array['secondary_select'] should always generate, regardless the dbtype
3258 $ret_array['secondary_select'] .= ', ' . $params['join_table_link_alias'].'.'. $join['rel_key'] .' ' . $join['rel_key'];
3260 if(isset($data['relationship_fields']))
3262 foreach($data['relationship_fields'] as $r_name=>$alias_name)
3264 if(!empty( $secondarySelectedFields[$alias_name]))continue;
3265 $ret_array['secondary_select'] .= ', ' . $params['join_table_link_alias'].'.'. $r_name .' ' . $alias_name;
3266 $secondarySelectedFields[$alias_name] = true;
3271 $ret_array['secondary_from'] .= ' ' . $join['join']. ' AND ' . $params['join_table_alias'].'.deleted=0';
3272 if (isset($data['link_type']) && $data['link_type'] == 'relationship_info' && ($parentbean instanceOf SugarBean))
3274 $ret_array['secondary_where'] = $params['join_table_link_alias'] . '.' . $join['rel_key']. "='" .$parentbean->id . "'";
3280 if(isset($data['db_concat_fields']))
3282 $ret_array['select'] .= ' , ' . db_concat($params['join_table_alias'], $data['db_concat_fields']) . ' ' . $field;
3286 $ret_array['select'] .= ' , ' . $params['join_table_alias'] . '.' . $data['rname'] . ' ' . $field;
3288 if(isset($data['additionalFields'])){
3289 foreach($data['additionalFields'] as $k=>$v){
3290 $ret_array['select'] .= ' , ' . $params['join_table_alias'] . '.' . $k . ' ' . $v;
3295 $ret_array['from'] .= ' ' . $join['join']. ' AND ' . $params['join_table_alias'].'.deleted=0';
3296 if(!empty($beanList[$rel_module]) && !empty($beanFiles[$beanList[$rel_module]]))
3298 require_once($beanFiles[$beanList[$rel_module]]);
3299 $rel_mod = new $beanList[$rel_module]();
3300 if(isset($value['target_record_key']) && !empty($filter))
3302 $selectedFields[$this->table_name.'.'.$value['target_record_key']] = true;
3303 $ret_array['select'] .= " , $this->table_name.{$value['target_record_key']} ";
3305 if(isset($rel_mod->field_defs['assigned_user_id']))
3307 $ret_array['select'] .= ' , ' .$params['join_table_alias'] . '.assigned_user_id ' . $field . '_owner';
3311 $ret_array['select'] .= ' , ' .$params['join_table_alias'] . '.created_by ' . $field . '_owner';
3313 $ret_array['select'] .= " , '".$rel_module ."' " . $field . '_mod';
3317 //Replace references to this table in the where clause with the new alias
3318 $join_table_name = $this->$data['link']->getRelatedTableName();
3319 // To fix SOAP stuff where we are trying to retrieve all the accounts data where accounts.id = ..
3320 // and this code changes accounts to jt4 as there is a self join with the accounts table.
3322 if(isset($data['db_concat_fields'])){
3323 $buildWhere = false;
3324 if(in_array('first_name', $data['db_concat_fields']) && in_array('last_name', $data['db_concat_fields']))
3326 $exp = '/\(\s*?'.$data['name'].'.*?\%\'\s*?\)/';
3327 if(preg_match($exp, $where, $matches))
3329 $search_expression = $matches[0];
3330 //Create three search conditions - first + last, first, last
3331 $first_name_search = str_replace($data['name'], $params['join_table_alias'] . '.first_name', $search_expression);
3332 $last_name_search = str_replace($data['name'], $params['join_table_alias'] . '.last_name', $search_expression);
3333 $full_name_search = str_replace($data['name'], db_concat($params['join_table_alias'], $data['db_concat_fields']), $search_expression);
3335 $where = str_replace($search_expression, '(' . $full_name_search . ' OR ' . $first_name_search . ' OR ' . $last_name_search . ')', $where);
3341 $db_field = db_concat($params['join_table_alias'], $data['db_concat_fields']);
3342 $where = preg_replace('/'.$data['name'].'/', $db_field, $where);
3345 $where = preg_replace('/(^|[\s(])' . $data['name'] . '/', '${1}' . $params['join_table_alias'] . '.'.$data['rname'], $where);
3349 $joined_tables[$params['join_table_alias']]=1;
3350 $joined_tables[$params['join_table_link_alias']]=1;
3359 if(isset($this->field_defs['assigned_user_id']) && empty($selectedFields[$this->table_name.'.assigned_user_id']))
3361 $ret_array['select'] .= ", $this->table_name.assigned_user_id ";
3363 else if(isset($this->field_defs['created_by']) && empty($selectedFields[$this->table_name.'.created_by']))
3365 $ret_array['select'] .= ", $this->table_name.created_by ";
3367 if(isset($this->field_defs['system_id']) && empty($selectedFields[$this->table_name.'.system_id']))
3369 $ret_array['select'] .= ", $this->table_name.system_id ";
3373 $where_auto = '1=1';
3374 if($show_deleted == 0)
3376 $where_auto = "$this->table_name.deleted=0";
3377 }else if($show_deleted == 1)
3379 $where_auto = "$this->table_name.deleted=1";
3382 $ret_array['where'] = " where ($where) AND $where_auto";
3384 $ret_array['where'] = " where $where_auto";
3385 if(!empty($order_by))
3387 //make call to process the order by clause
3388 $ret_array['order_by'] = " ORDER BY ". $this->process_order_by($order_by, null);
3392 unset($ret_array['secondary_where']);
3393 unset($ret_array['secondary_from']);
3394 unset($ret_array['secondary_select']);
3402 return $ret_array['select'] . $ret_array['from'] . $ret_array['where']. $ret_array['order_by'];
3409 * Returns parent record data for objects that store relationship information
3411 * @param array $type_info
3413 * Interal function, do not override.
3415 function retrieve_parent_fields($type_info)
3418 global $beanList, $beanFiles;
3419 $templates = array();
3420 $parent_child_map = array();
3421 foreach($type_info as $children_info)
3423 foreach($children_info as $child_info)
3425 if($child_info['type'] == 'parent')
3427 if(empty($templates[$child_info['parent_type']]))
3429 //Test emails will have an invalid parent_type, don't try to load the non-existant parent bean
3430 if ($child_info['parent_type'] == 'test') {
3433 $class = $beanList[$child_info['parent_type']];
3434 // Added to avoid error below; just silently fail and write message to log
3435 if ( empty($beanFiles[$class]) ) {
3436 $GLOBALS['log']->error($this->object_name.'::retrieve_parent_fields() - cannot load class "'.$class.'", skip loading.');
3439 require_once($beanFiles[$class]);
3440 $templates[$child_info['parent_type']] = new $class();
3443 if(empty($queries[$child_info['parent_type']]))
3445 $queries[$child_info['parent_type']] = "SELECT id ";
3446 $field_def = $templates[$child_info['parent_type']]->field_defs['name'];
3447 if(isset($field_def['db_concat_fields']))
3449 $queries[$child_info['parent_type']] .= ' , ' . db_concat($templates[$child_info['parent_type']]->table_name, $field_def['db_concat_fields']) . ' parent_name';
3453 $queries[$child_info['parent_type']] .= ' , name parent_name';
3455 if(isset($templates[$child_info['parent_type']]->field_defs['assigned_user_id']))
3457 $queries[$child_info['parent_type']] .= ", assigned_user_id parent_name_owner , '{$child_info['parent_type']}' parent_name_mod";;
3458 }else if(isset($templates[$child_info['parent_type']]->field_defs['created_by']))
3460 $queries[$child_info['parent_type']] .= ", created_by parent_name_owner, '{$child_info['parent_type']}' parent_name_mod";
3462 $queries[$child_info['parent_type']] .= " FROM " . $templates[$child_info['parent_type']]->table_name ." WHERE id IN ('{$child_info['parent_id']}'";
3466 if(empty($parent_child_map[$child_info['parent_id']]))
3467 $queries[$child_info['parent_type']] .= " ,'{$child_info['parent_id']}'";
3469 $parent_child_map[$child_info['parent_id']][] = $child_info['child_id'];
3474 foreach($queries as $query)
3476 $result = $this->db->query($query . ')');
3477 while($row = $this->db->fetchByAssoc($result))
3479 $results[$row['id']] = $row;
3483 $child_results = array();
3484 foreach($parent_child_map as $parent_key=>$parent_child)
3486 foreach($parent_child as $child)
3488 if(isset( $results[$parent_key]))
3490 $child_results[$child] = $results[$parent_key];
3494 return $child_results;
3498 * Processes the list query and return fetched row.
3500 * Internal function, do not override.
3501 * @param string $query select query to be processed.
3502 * @param int $row_offset starting position
3503 * @param int $limit Optioanl, default -1
3504 * @param int $max_per_page Optional, default -1
3505 * @param string $where Optional, additional filter criteria.
3506 * @return array Fetched data
3508 function process_list_query($query, $row_offset, $limit= -1, $max_per_page = -1, $where = '')
3510 global $sugar_config;
3511 $db = &DBManagerFactory::getInstance('listviews');
3513 * if the row_offset is set to 'end' go to the end of the list
3515 $toEnd = strval($row_offset) == 'end';
3516 $GLOBALS['log']->debug("process_list_query: ".$query);
3517 if($max_per_page == -1)
3519 $max_per_page = $sugar_config['list_max_entries_per_page'];
3521 // Check to see if we have a count query available.
3522 if(empty($sugar_config['disable_count_query']) || $toEnd)
3524 $count_query = $this->create_list_count_query($query);
3525 if(!empty($count_query) && (empty($limit) || $limit == -1))
3527 // We have a count query. Run it and get the results.
3528 $result = $db->query($count_query, true, "Error running count query for $this->object_name List: ");
3529 $assoc = $db->fetchByAssoc($result);
3530 if(!empty($assoc['c']))
3532 $rows_found = $assoc['c'];
3533 $limit = $sugar_config['list_max_entries_per_page'];
3537 $row_offset = (floor(($rows_found -1) / $limit)) * $limit;
3543 if((empty($limit) || $limit == -1))
3545 $limit = $max_per_page + 1;
3546 $max_per_page = $limit;
3550 if(empty($row_offset))
3554 if(!empty($limit) && $limit != -1 && $limit != -99)
3556 $result = $db->limitQuery($query, $row_offset, $limit,true,"Error retrieving $this->object_name list: ");
3560 $result = $db->query($query,true,"Error retrieving $this->object_name list: ");
3565 if(empty($rows_found))
3567 $rows_found = $db->getRowCount($result);
3570 $GLOBALS['log']->debug("Found $rows_found ".$this->object_name."s");
3572 $previous_offset = $row_offset - $max_per_page;
3573 $next_offset = $row_offset + $max_per_page;
3575 $class = get_class($this);
3576 if($rows_found != 0 or $db->dbType != 'mysql')
3578 //todo Bug? we should remove the magic number -99
3579 //use -99 to return all
3580 $index = $row_offset;
3581 while ($max_per_page == -99 || ($index < $row_offset + $max_per_page))
3584 if(!empty($sugar_config['disable_count_query']))
3586 $row = $db->fetchByAssoc($result);
3590 $row = $db->fetchByAssoc($result, $index);
3597 //instantiate a new class each time. This is because php5 passes
3598 //by reference by default so if we continually update $this, we will
3599 //at the end have a list of all the same objects
3600 $temp = new $class();
3602 foreach($this->field_defs as $field=>$value)
3604 if (isset($row[$field]))
3606 $temp->$field = $row[$field];
3607 $owner_field = $field . '_owner';
3608 if(isset($row[$owner_field]))
3610 $temp->$owner_field = $row[$owner_field];
3613 $GLOBALS['log']->debug("$temp->object_name({$row['id']}): ".$field." = ".$temp->$field);
3614 }else if (isset($row[$this->table_name .'.'.$field]))
3616 $temp->$field = $row[$this->table_name .'.'.$field];
3624 $temp->check_date_relationships_load();
3625 $temp->fill_in_additional_list_fields();
3626 if($temp->hasCustomFields()) $temp->custom_fields->fill_relationships();
3627 $temp->call_custom_logic("process_record");
3634 if(!empty($sugar_config['disable_count_query']) && !empty($limit))
3637 $rows_found = $row_offset + count($list);
3639 unset($list[$limit - 1]);
3646 $response = Array();
3647 $response['list'] = $list;
3648 $response['row_count'] = $rows_found;
3649 $response['next_offset'] = $next_offset;
3650 $response['previous_offset'] = $previous_offset;
3651 $response['current_offset'] = $row_offset ;
3656 * Returns the number of rows that the given SQL query should produce
3658 * Internal function, do not override.
3659 * @param string $query valid select query
3660 * @param boolean $is_count_query Optional, Default false, set to true if passed query is a count query.
3661 * @return int count of rows found
3663 function _get_num_rows_in_query($query, $is_count_query=false)
3665 $num_rows_in_query = 0;
3666 if (!$is_count_query)
3668 $count_query = SugarBean::create_list_count_query($query);
3670 $count_query=$query;
3672 $result = $this->db->query($count_query, true, "Error running count query for $this->object_name List: ");
3674 $row = $this->db->fetchByAssoc($result, $row_num);
3677 $num_rows_in_query += current($row);
3679 $row = $this->db->fetchByAssoc($result, $row_num);
3682 return $num_rows_in_query;
3686 * Applies pagination window to union queries used by list view and subpanels,
3687 * executes the query and returns fetched data.
3689 * Internal function, do not override.
3690 * @param object $parent_bean
3691 * @param string $query query to be processed.
3692 * @param int $row_offset
3693 * @param int $limit optional, default -1
3694 * @param int $max_per_page Optional, default -1
3695 * @param string $where Custom where clause.
3696 * @param array $subpanel_def definition of sub-panel to be processed
3697 * @param string $query_row_count
3698 * @param array $seconday_queries
3699 * @return array Fetched data.
3701 function process_union_list_query($parent_bean, $query,
3702 $row_offset, $limit= -1, $max_per_page = -1, $where = '', $subpanel_def, $query_row_count='', $secondary_queries = array())
3705 $db = &DBManagerFactory::getInstance('listviews');
3707 * if the row_offset is set to 'end' go to the end of the list
3709 $toEnd = strval($row_offset) == 'end';
3710 global $sugar_config;
3711 $use_count_query=false;
3712 $processing_collection=$subpanel_def->isCollection();
3714 $GLOBALS['log']->debug("process_list_query: ".$query);
3715 if($max_per_page == -1)
3717 $max_per_page = $sugar_config['list_max_entries_per_subpanel'];
3719 if(empty($query_row_count))
3721 $query_row_count = $query;
3723 $distinct_position=strpos($query_row_count,"DISTINCT");
3725 if ($distinct_position!= false)
3727 $use_count_query=true;
3729 $performSecondQuery = true;
3730 if(empty($sugar_config['disable_count_query']) || $toEnd)
3732 $rows_found = $this->_get_num_rows_in_query($query_row_count,$use_count_query);
3735 $performSecondQuery = false;
3737 if(!empty($rows_found) && (empty($limit) || $limit == -1))
3739 $limit = $sugar_config['list_max_entries_per_subpanel'];
3743 $row_offset = (floor(($rows_found -1) / $limit)) * $limit;
3749 if((empty($limit) || $limit == -1))
3751 $limit = $max_per_page + 1;
3752 $max_per_page = $limit;
3756 if(empty($row_offset))
3761 $previous_offset = $row_offset - $max_per_page;
3762 $next_offset = $row_offset + $max_per_page;
3764 if($performSecondQuery)
3766 if(!empty($limit) && $limit != -1 && $limit != -99)
3768 $result = $db->limitQuery($query, $row_offset, $limit,true,"Error retrieving $parent_bean->object_name list: ");
3772 $result = $db->query($query,true,"Error retrieving $this->object_name list: ");
3774 if(empty($rows_found))
3776 $rows_found = $db->getRowCount($result);
3779 $GLOBALS['log']->debug("Found $rows_found ".$parent_bean->object_name."s");
3780 if($rows_found != 0 or $db->dbType != 'mysql')
3782 //use -99 to return all
3784 // get the current row
3785 $index = $row_offset;
3786 if(!empty($sugar_config['disable_count_query']))
3788 $row = $db->fetchByAssoc($result);
3792 $row = $db->fetchByAssoc($result, $index);
3795 $post_retrieve = array();
3796 $isFirstTime = true;
3799 $function_fields = array();
3800 if(($index < $row_offset + $max_per_page || $max_per_page == -99) or ($db->dbType != 'mysql'))
3802 if ($processing_collection)
3804 $current_bean =$subpanel_def->sub_subpanels[$row['panel_name']]->template_instance;
3807 $class = get_class($subpanel_def->sub_subpanels[$row['panel_name']]->template_instance);
3808 $current_bean = new $class();
3811 $current_bean=$subpanel_def->template_instance;
3814 $class = get_class($subpanel_def->template_instance);
3815 $current_bean = new $class();
3818 $isFirstTime = false;
3819 //set the panel name in the bean instance.
3820 if (isset($row['panel_name']))
3822 $current_bean->panel_name=$row['panel_name'];
3824 foreach($current_bean->field_defs as $field=>$value)
3827 if (isset($row[$field]))
3829 $current_bean->$field = $row[$field];
3830 unset($row[$field]);
3832 else if (isset($row[$this->table_name .'.'.$field]))
3834 $current_bean->$field = $row[$current_bean->table_name .'.'.$field];
3835 unset($row[$current_bean->table_name .'.'.$field]);
3839 $current_bean->$field = "";
3840 unset($row[$field]);
3842 if(isset($value['source']) && $value['source'] == 'function')
3844 $function_fields[]=$field;
3847 foreach($row as $key=>$value)
3849 $current_bean->$key = $value;
3851 foreach($function_fields as $function_field)
3853 $value = $current_bean->field_defs[$function_field];
3854 $can_execute = true;
3855 $execute_params = array();
3856 $execute_function = array();
3857 if(!empty($value['function_class']))
3859 $execute_function[] = $value['function_class'];
3860 $execute_function[] = $value['function_name'];
3864 $execute_function = $value['function_name'];
3866 foreach($value['function_params'] as $param )
3868 if (empty($value['function_params_source']) or $value['function_params_source']=='parent')
3870 if(empty($this->$param))
3872 $can_execute = false;
3876 $execute_params[] = $this->$param;
3878 } else if ($value['function_params_source']=='this')
3880 if(empty($current_bean->$param))
3882 $can_execute = false;
3886 $execute_params[] = $current_bean->$param;
3891 $can_execute = false;
3897 if(!empty($value['function_require']))
3899 require_once($value['function_require']);
3901 $current_bean->$function_field = call_user_func_array($execute_function, $execute_params);
3904 if(!empty($current_bean->parent_type) && !empty($current_bean->parent_id))
3906 if(!isset($post_retrieve[$current_bean->parent_type]))
3908 $post_retrieve[$current_bean->parent_type] = array();
3910 $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');
3912 //$current_bean->fill_in_additional_list_fields();
3913 $list[$current_bean->id] = $current_bean;
3915 // go to the next row
3917 $row = $db->fetchByAssoc($result, $index);
3920 //now handle retrieving many-to-many relationships
3923 foreach($secondary_queries as $query2)
3925 $result2 = $db->query($query2);
3927 $row2 = $db->fetchByAssoc($result2);
3930 $id_ref = $row2['ref_id'];
3932 if(isset($list[$id_ref]))
3934 foreach($row2 as $r2key=>$r2value)
3936 if($r2key != 'ref_id')
3938 $list[$id_ref]->$r2key = $r2value;
3942 $row2 = $db->fetchByAssoc($result2);
3948 if(isset($post_retrieve))
3950 $parent_fields = $this->retrieve_parent_fields($post_retrieve);
3954 $parent_fields = array();
3956 if(!empty($sugar_config['disable_count_query']) && !empty($limit))
3958 $rows_found = $row_offset + count($list);
3960 if(count($list) >= $limit)
3974 $parent_fields = array();
3976 $response = array();
3977 $response['list'] = $list;
3978 $response['parent_data'] = $parent_fields;
3979 $response['row_count'] = $rows_found;
3980 $response['next_offset'] = $next_offset;
3981 $response['previous_offset'] = $previous_offset;
3982 $response['current_offset'] = $row_offset ;
3983 $response['query'] = $query;
3989 * Applies pagination window to select queries used by detail view,
3990 * executes the query and returns fetched data.
3992 * Internal function, do not override.
3993 * @param string $query query to be processed.
3994 * @param int $row_offset
3995 * @param int $limit optional, default -1
3996 * @param int $max_per_page Optional, default -1
3997 * @param string $where Custom where clause.
3998 * @param int $offset Optional, default 0
3999 * @return array Fetched data.
4002 function process_detail_query($query, $row_offset, $limit= -1, $max_per_page = -1, $where = '', $offset = 0)
4004 global $sugar_config;
4005 $GLOBALS['log']->debug("process_list_query: ".$query);
4006 if($max_per_page == -1)
4008 $max_per_page = $sugar_config['list_max_entries_per_page'];
4011 // Check to see if we have a count query available.
4012 $count_query = $this->create_list_count_query($query);
4014 if(!empty($count_query) && (empty($limit) || $limit == -1))
4016 // We have a count query. Run it and get the results.
4017 $result = $this->db->query($count_query, true, "Error running count query for $this->object_name List: ");
4018 $assoc = $this->db->fetchByAssoc($result);
4019 if(!empty($assoc['c']))
4021 $total_rows = $assoc['c'];
4025 if(empty($row_offset))
4030 $result = $this->db->limitQuery($query, $offset, 1, true,"Error retrieving $this->object_name list: ");
4032 $rows_found = $this->db->getRowCount($result);
4034 $GLOBALS['log']->debug("Found $rows_found ".$this->object_name."s");
4036 $previous_offset = $row_offset - $max_per_page;
4037 $next_offset = $row_offset + $max_per_page;
4039 if($rows_found != 0 or $this->db->dbType != 'mysql')
4042 $row = $this->db->fetchByAssoc($result, $index);
4043 $this->retrieve($row['id']);
4046 $response = Array();
4047 $response['bean'] = $this;
4048 if (empty($total_rows))
4050 $response['row_count'] = $total_rows;
4051 $response['next_offset'] = $next_offset;
4052 $response['previous_offset'] = $previous_offset;
4058 * Processes fetched list view data
4060 * Internal function, do not override.
4061 * @param string $query query to be processed.
4062 * @param boolean $check_date Optional, default false. if set to true date time values are processed.
4063 * @return array Fetched data.
4066 function process_full_list_query($query, $check_date=false)
4069 $GLOBALS['log']->debug("process_full_list_query: query is ".$query);
4070 $result = $this->db->query($query, false);
4071 $GLOBALS['log']->debug("process_full_list_query: result is ".print_r($result,true));
4072 $class = get_class($this);
4073 $isFirstTime = true;
4074 $bean = new $class();
4076 //if($this->db->getRowCount($result) > 0){
4079 // We have some data.
4080 //while ($row = $this->db->fetchByAssoc($result)) {
4081 while (($row = $bean->db->fetchByAssoc($result)) != null)
4085 $bean = new $class();
4087 $isFirstTime = false;
4089 foreach($bean->field_defs as $field=>$value)
4091 if (isset($row[$field]))
4093 $bean->$field = $row[$field];
4094 $GLOBALS['log']->debug("process_full_list: $bean->object_name({$row['id']}): ".$field." = ".$bean->$field);
4103 $bean->processed_dates_times = array();
4104 $bean->check_date_relationships_load();
4106 $bean->fill_in_additional_list_fields();
4107 $bean->call_custom_logic("process_record");
4108 $bean->fetched_row = $row;
4113 if (isset($list)) return $list;
4118 * Tracks the viewing of a detail record.
4119 * This leverages get_summary_text() which is object specific.
4121 * Internal function, do not override.
4122 * @param string $user_id - String value of the user that is viewing the record.
4123 * @param string $current_module - String value of the module being processed.
4124 * @param string $current_view - String value of the current view
4126 function track_view($user_id, $current_module, $current_view='')
4128 $trackerManager = TrackerManager::getInstance();
4129 if($monitor = $trackerManager->getMonitor('tracker')){
4130 $monitor->setValue('date_modified', $GLOBALS['timedate']->nowDb());
4131 $monitor->setValue('user_id', $user_id);
4132 $monitor->setValue('module_name', $current_module);
4133 $monitor->setValue('action', $current_view);
4134 $monitor->setValue('item_id', $this->id);
4135 $monitor->setValue('item_summary', $this->get_summary_text());
4136 $monitor->setValue('visible', $this->tracker_visibility);
4137 $trackerManager->saveMonitor($monitor);
4142 * Returns the summary text that should show up in the recent history list for this object.
4146 public function get_summary_text()
4148 return "Base Implementation. Should be overridden.";
4152 * This is designed to be overridden and add specific fields to each record.
4153 * This allows the generic query to fill in the major fields, and then targeted
4154 * queries to get related fields and add them to the record. The contact's
4155 * account for instance. This method is only used for populating extra fields
4158 function fill_in_additional_list_fields(){
4159 if(!empty($this->field_defs['parent_name']) && empty($this->parent_name)){
4160 $this->fill_in_additional_parent_fields();
4165 * This is designed to be overridden and add specific fields to each record.
4166 * This allows the generic query to fill in the major fields, and then targeted
4167 * queries to get related fields and add them to the record. The contact's
4168 * account for instance. This method is only used for populating extra fields
4169 * in the detail form
4171 function fill_in_additional_detail_fields()
4173 if(!empty($this->field_defs['assigned_user_name']) && !empty($this->assigned_user_id)){
4175 $this->assigned_user_name = get_assigned_user_name($this->assigned_user_id);
4178 if(!empty($this->field_defs['created_by']) && !empty($this->created_by))
4179 $this->created_by_name = get_assigned_user_name($this->created_by);
4180 if(!empty($this->field_defs['modified_user_id']) && !empty($this->modified_user_id))
4181 $this->modified_by_name = get_assigned_user_name($this->modified_user_id);
4183 if(!empty($this->field_defs['parent_name'])){
4184 $this->fill_in_additional_parent_fields();
4189 * This is desgined to be overridden or called from extending bean. This method
4190 * will fill in any parent_name fields.
4192 function fill_in_additional_parent_fields() {
4194 if(!empty($this->parent_id) && !empty($this->last_parent_id) && $this->last_parent_id == $this->parent_id){
4197 $this->parent_name = '';
4199 if(!empty($this->parent_type)) {
4200 $this->last_parent_id = $this->parent_id;
4201 $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'));
4202 if(!empty($this->parent_first_name) || !empty($this->parent_last_name) ){
4203 $this->parent_name = $GLOBALS['locale']->getLocaleFormattedName($this->parent_first_name, $this->parent_last_name);
4204 } else if(!empty($this->parent_document_name)){
4205 $this->parent_name = $this->parent_document_name;
4211 * Fill in a link field
4214 function fill_in_link_field( $linkFieldName )
4216 if ($this->load_relationship($linkFieldName))
4218 $list=$this->$linkFieldName->get();
4219 $this->$linkFieldName = '' ; // match up with null value in $this->populateFromRow()
4221 $this->$linkFieldName = $list[0];
4226 * Fill in fields where type = relate
4228 function fill_in_relationship_fields(){
4229 if(!empty($this->relDepth)) {
4230 if($this->relDepth > 1)return;
4231 }else $this->relDepth = 0;
4233 foreach($this->field_defs as $field)
4235 if(0 == strcmp($field['type'],'relate') && !empty($field['module']))
4237 $name = $field['name'];
4238 if(empty($this->$name))
4240 // 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']
4241 $related_module = $field['module'];
4242 $id_name = $field['id_name'];
4243 if (empty($this->$id_name)){
4244 $this->fill_in_link_field($id_name);
4246 if(!empty($this->$id_name) && ( $this->object_name != $related_module || ( $this->object_name == $related_module && $this->$id_name != $this->id ))){
4247 if(isset($GLOBALS['beanList'][ $related_module])){
4248 $class = $GLOBALS['beanList'][$related_module];
4250 if(!empty($this->$id_name) && file_exists($GLOBALS['beanFiles'][$class]) && isset($this->$name)){
4251 require_once($GLOBALS['beanFiles'][$class]);
4252 $mod = new $class();
4253 $mod->relDepth = $this->relDepth + 1;
4254 $mod->retrieve($this->$id_name);
4255 if (!empty($field['rname'])) {
4256 $this->$name = $mod->$field['rname'];
4257 } else if (isset($mod->name)) {
4258 $this->$name = $mod->name;
4263 if(!empty($this->$id_name) && isset($this->$name))
4265 if(!isset($field['additionalFields']))
4266 $field['additionalFields'] = array();
4267 if(!empty($field['rname']))
4269 $field['additionalFields'][$field['rname']]= $name;
4273 $field['additionalFields']['name']= $name;
4275 $this->getRelatedFields($related_module, $this->$id_name, $field['additionalFields']);
4283 * This is a helper function that is used to quickly created indexes when creating tables.
4285 function create_index($query)
4287 $GLOBALS['log']->info($query);
4289 $result = $this->db->query($query, true, "Error creating index:");
4293 * This function should be overridden in each module. It marks an item as deleted.
4295 * If it is not overridden, then marking this type of item is not allowed
4297 function mark_deleted($id)
4299 global $current_user;
4300 $date_modified = $GLOBALS['timedate']->nowDb();
4301 if(isset($_SESSION['show_deleted']))
4303 $this->mark_undeleted($id);
4307 // call the custom business logic
4308 $custom_logic_arguments['id'] = $id;
4309 $this->call_custom_logic("before_delete", $custom_logic_arguments);
4311 if ( isset($this->field_defs['modified_user_id']) ) {
4312 if (!empty($current_user)) {
4313 $this->modified_user_id = $current_user->id;
4315 $this->modified_user_id = 1;
4317 $query = "UPDATE $this->table_name set deleted=1 , date_modified = '$date_modified', modified_user_id = '$this->modified_user_id' where id='$id'";
4319 $query = "UPDATE $this->table_name set deleted=1 , date_modified = '$date_modified' where id='$id'";
4321 $this->db->query($query, true,"Error marking record deleted: ");
4323 $this->mark_relationships_deleted($id);
4325 // Take the item off the recently viewed lists
4326 $tracker = new Tracker();
4327 $tracker->makeInvisibleForAll($id);
4329 // call the custom business logic
4330 $this->call_custom_logic("after_delete", $custom_logic_arguments);
4335 * Restores data deleted by call to mark_deleted() function.
4337 * Internal function, do not override.
4339 function mark_undeleted($id)
4341 // call the custom business logic
4342 $custom_logic_arguments['id'] = $id;
4343 $this->call_custom_logic("before_restore", $custom_logic_arguments);
4345 $date_modified = $GLOBALS['timedate']->nowDb();
4346 $query = "UPDATE $this->table_name set deleted=0 , date_modified = '$date_modified' where id='$id'";
4347 $this->db->query($query, true,"Error marking record undeleted: ");
4349 // call the custom business logic
4350 $this->call_custom_logic("after_restore", $custom_logic_arguments);
4354 * This function deletes relationships to this object. It should be overridden
4355 * to handle the relationships of the specific object.
4356 * This function is called when the item itself is being deleted.
4358 * @param int $id id of the relationship to delete
4360 function mark_relationships_deleted($id)
4362 $this->delete_linked($id);
4366 * This function is used to execute the query and create an array template objects
4367 * from the resulting ids from the query.
4368 * It is currently used for building sub-panel arrays.
4370 * @param string $query - the query that should be executed to build the list
4371 * @param object $template - The object that should be used to copy the records.
4372 * @param int $row_offset Optional, default 0
4373 * @param int $limit Optional, default -1
4376 function build_related_list($query, &$template, $row_offset = 0, $limit = -1)
4378 $GLOBALS['log']->debug("Finding linked records $this->object_name: ".$query);
4379 $db = &DBManagerFactory::getInstance('listviews');
4381 if(!empty($row_offset) && $row_offset != 0 && !empty($limit) && $limit != -1)
4383 $result = $db->limitQuery($query, $row_offset, $limit,true,"Error retrieving $template->object_name list: ");
4387 $result = $db->query($query, true);
4391 $isFirstTime = true;
4392 $class = get_class($template);
4393 while($row = $this->db->fetchByAssoc($result))
4397 $template = new $class();
4399 $isFirstTime = false;
4400 $record = $template->retrieve($row['id']);
4404 // this copies the object into the array
4405 $list[] = $template;
4412 * This function is used to execute the query and create an array template objects
4413 * from the resulting ids from the query.
4414 * It is currently used for building sub-panel arrays. It supports an additional
4415 * where clause that is executed as a filter on the results
4417 * @param string $query - the query that should be executed to build the list
4418 * @param object $template - The object that should be used to copy the records.
4420 function build_related_list_where($query, &$template, $where='', $in='', $order_by, $limit='', $row_offset = 0)
4422 $db = &DBManagerFactory::getInstance('listviews');
4423 // No need to do an additional query
4424 $GLOBALS['log']->debug("Finding linked records $this->object_name: ".$query);
4425 if(empty($in) && !empty($query))
4427 $idList = $this->build_related_in($query);
4428 $in = $idList['in'];
4430 // MFH - Added Support For Custom Fields in Searches
4432 if(isset($this->custom_fields)) {
4433 $custom_join = $this->custom_fields->getJOIN();
4436 $query = "SELECT id ";
4438 if (!empty($custom_join)) {
4439 $query .= $custom_join['select'];
4441 $query .= " FROM $this->table_name ";
4443 if (!empty($custom_join) && !empty($custom_join['join'])) {
4444 $query .= " " . $custom_join['join'];
4447 $query .= " WHERE deleted=0 AND id IN $in";
4450 $query .= " AND $where";
4454 if(!empty($order_by))
4456 $query .= "ORDER BY $order_by";
4460 $result = $db->limitQuery($query, $row_offset, $limit,true,"Error retrieving $this->object_name list: ");
4464 $result = $db->query($query, true);
4468 $isFirstTime = true;
4469 $class = get_class($template);
4471 $disable_security_flag = ($template->disable_row_level_security) ? true : false;
4472 while($row = $db->fetchByAssoc($result))
4476 $template = new $class();
4477 $template->disable_row_level_security = $disable_security_flag;
4479 $isFirstTime = false;
4480 $record = $template->retrieve($row['id']);
4483 // this copies the object into the array
4484 $list[] = $template;
4492 * Constructs an comma seperated list of ids from passed query results.
4494 * @param string @query query to be executed.
4497 function build_related_in($query)
4500 $result = $this->db->query($query, true);
4502 while($row = $this->db->fetchByAssoc($result))
4504 $idList[] = $row['id'];
4507 $ids = "('" . $row['id'] . "'";
4511 $ids .= ",'" . $row['id'] . "'";
4521 return array('list'=>$idList, 'in'=>$ids);
4525 * Optionally copies values from fetched row into the bean.
4527 * Internal function, do not override.
4529 * @param string $query - the query that should be executed to build the list
4530 * @param object $template - The object that should be used to copy the records
4531 * @param array $field_list List of fields.
4534 function build_related_list2($query, &$template, &$field_list)
4536 $GLOBALS['log']->debug("Finding linked values $this->object_name: ".$query);
4538 $result = $this->db->query($query, true);
4541 $isFirstTime = true;
4542 $class = get_class($template);
4543 while($row = $this->db->fetchByAssoc($result))
4545 // Create a blank copy
4549 $copy = new $class();
4551 $isFirstTime = false;
4552 foreach($field_list as $field)
4554 // Copy the relevant fields
4555 $copy->$field = $row[$field];
4559 // this copies the object into the array
4567 * Let implementing classes to fill in row specific columns of a list view form
4570 function list_view_parse_additional_sections(&$list_form)
4575 * Assigns all of the values into the template for the list view
4577 function get_list_view_array()
4579 static $cache = array();
4580 // cn: bug 12270 - sensitive fields being passed arbitrarily in listViews
4581 $sensitiveFields = array('user_hash' => '');
4583 $return_array = Array();
4584 global $app_list_strings, $mod_strings;
4585 foreach($this->field_defs as $field=>$value){
4587 if(isset($this->$field)){
4589 // cn: bug 12270 - sensitive fields being passed arbitrarily in listViews
4590 if(isset($sensitiveFields[$field]))
4592 if(!isset($cache[$field]))
4593 $cache[$field] = strtoupper($field);
4595 //Fields hidden by Dependent Fields
4596 if (isset($value['hidden']) && $value['hidden'] === true) {
4597 $return_array[$cache[$field]] = "";
4600 //cn: if $field is a _dom, detect and return VALUE not KEY
4601 //cl: empty function check for meta-data enum types that have values loaded from a function
4602 else if (((!empty($value['type']) && ($value['type'] == 'enum' || $value['type'] == 'radioenum') )) && empty($value['function'])){
4603 if(!empty($app_list_strings[$value['options']][$this->$field])){
4604 $return_array[$cache[$field]] = $app_list_strings[$value['options']][$this->$field];
4606 //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.
4607 elseif(!empty($mod_strings[$value['options']][$this->$field]))
4609 $return_array[$cache[$field]] = $mod_strings[$value['options']][$this->$field];
4612 $return_array[$cache[$field]] = $this->$field;
4615 // tjy: no need to do this str_replace as the changes in 29994 for ListViewGeneric.tpl for translation handle this now
4616 // }elseif(!empty($value['type']) && $value['type'] == 'multienum'&& empty($value['function'])){
4617 // $return_array[strtoupper($field)] = str_replace('^,^', ', ', $this->$field );
4618 }elseif(!empty($value['custom_module']) && $value['type'] != 'currency'){
4619 // $this->format_field($value);
4620 $return_array[$cache[$field]] = $this->$field;
4622 $return_array[$cache[$field]] = $this->$field;
4624 // handle "Assigned User Name"
4625 if($field == 'assigned_user_name'){
4626 $return_array['ASSIGNED_USER_NAME'] = get_assigned_user_name($this->assigned_user_id);
4630 return $return_array;
4633 * Override this function to set values in the array used to render list view data.
4636 function get_list_view_data()
4638 return $this->get_list_view_array();
4642 * Construct where clause from a list of name-value pairs.
4645 function get_where(&$fields_array)
4647 $where_clause = "WHERE ";
4649 foreach ($fields_array as $name=>$value)
4657 $where_clause .= " AND ";
4660 $where_clause .= "$name = '".$this->db->quote($value,false)."'";
4662 $where_clause .= " AND deleted=0";
4663 return $where_clause;
4668 * Constructs a select query and fetch 1 row using this query, and then process the row
4670 * Internal function, do not override.
4671 * @param array @fields_array array of name value pairs used to construct query.
4672 * @param boolean $encode Optional, default true, encode fetched data.
4673 * @return object Instance of this bean with fetched data.
4675 function retrieve_by_string_fields($fields_array, $encode=true)
4677 $where_clause = $this->get_where($fields_array);
4678 if(isset($this->custom_fields))
4679 $custom_join = $this->custom_fields->getJOIN();
4680 else $custom_join = false;
4683 $query = "SELECT $this->table_name.*". $custom_join['select']. " FROM $this->table_name " . $custom_join['join'];
4687 $query = "SELECT $this->table_name.* FROM $this->table_name ";
4689 $query .= " $where_clause";
4690 $GLOBALS['log']->debug("Retrieve $this->object_name: ".$query);
4691 //requireSingleResult has beeen deprecated.
4692 //$result = $this->db->requireSingleResult($query, true, "Retrieving record $where_clause:");
4693 $result = $this->db->limitQuery($query,0,1,true, "Retrieving record $where_clause:");
4700 if($this->db->getRowCount($result) > 1)
4702 $this->duplicates_found = true;
4704 $row = $this->db->fetchByAssoc($result, -1, $encode);
4709 $this->fetched_row = $row;
4710 $this->fromArray($row);
4711 $this->fill_in_additional_detail_fields();
4716 * This method is called during an import before inserting a bean
4717 * Define an associative array called $special_fields
4718 * the keys are user defined, and don't directly map to the bean's fields
4719 * the value is the method name within that bean that will do extra
4720 * processing for that field. example: 'full_name'=>'get_names_from_full_name'
4723 function process_special_fields()
4725 foreach ($this->special_functions as $func_name)
4727 if ( method_exists($this,$func_name) )
4729 $this->$func_name();
4735 * Override this function to build a where clause based on the search criteria set into bean .
4738 function build_generic_where_clause($value)
4742 function getRelatedFields($module, $id, $fields, $return_array = false){
4743 if(empty($GLOBALS['beanList'][$module]))return '';
4744 $object = $GLOBALS['beanList'][$module];
4745 if ($object == 'aCase') {
4749 VardefManager::loadVardef($module, $object);
4750 if(empty($GLOBALS['dictionary'][$object]['table']))return '';
4751 $table = $GLOBALS['dictionary'][$object]['table'];
4752 $query = 'SELECT id';
4753 foreach($fields as $field=>$alias){
4754 if(!empty($GLOBALS['dictionary'][$object]['fields'][$field]['db_concat_fields'])){
4755 $query .= ' ,' .db_concat($table, $GLOBALS['dictionary'][$object]['fields'][$field]['db_concat_fields']) . ' as ' . $alias ;
4756 }else if(!empty($GLOBALS['dictionary'][$object]['fields'][$field]) &&
4757 (empty($GLOBALS['dictionary'][$object]['fields'][$field]['source']) ||
4758 $GLOBALS['dictionary'][$object]['fields'][$field]['source'] != "non-db"))
4760 $query .= ' ,' .$table . '.' . $field . ' as ' . $alias;
4762 if(!$return_array)$this->$alias = '';
4764 if($query == 'SELECT id' || empty($id)){
4769 if(isset($GLOBALS['dictionary'][$object]['fields']['assigned_user_id']))
4771 $query .= " , ". $table . ".assigned_user_id owner";
4774 else if(isset($GLOBALS['dictionary'][$object]['fields']['created_by']))
4776 $query .= " , ". $table . ".created_by owner";
4779 $query .= ' FROM ' . $table . ' WHERE deleted=0 AND id=';
4780 $result = $GLOBALS['db']->query($query . "'$id'" );
4781 $row = $GLOBALS['db']->fetchByAssoc($result);
4785 $owner = (empty($row['owner']))?'':$row['owner'];
4786 foreach($fields as $alias){
4787 $this->$alias = (!empty($row[$alias]))? $row[$alias]: '';
4788 $alias = $alias .'_owner';
4789 $this->$alias = $owner;
4790 $a_mod = $alias .'_mod';
4791 $this->$a_mod = $module;
4798 function &parse_additional_headers(&$list_form, $xTemplateSection)
4803 function assign_display_fields($currentModule)
4806 foreach($this->column_fields as $field)
4808 if(isset($this->field_name_map[$field]) && empty($this->$field))
4810 if($this->field_name_map[$field]['type'] != 'date' && $this->field_name_map[$field]['type'] != 'enum')
4811 $this->$field = $field;
4812 if($this->field_name_map[$field]['type'] == 'date')
4814 $this->$field = $timedate->to_display_date('1980-07-09');
4816 if($this->field_name_map[$field]['type'] == 'enum')
4818 $dom = $this->field_name_map[$field]['options'];
4819 global $current_language, $app_list_strings;
4820 $mod_strings = return_module_language($current_language, $currentModule);
4822 if(isset($mod_strings[$dom]))
4824 $options = $mod_strings[$dom];
4825 foreach($options as $key=>$value)
4827 if(!empty($key) && empty($this->$field ))
4829 $this->$field = $key;
4833 if(isset($app_list_strings[$dom]))
4835 $options = $app_list_strings[$dom];
4836 foreach($options as $key=>$value)
4838 if(!empty($key) && empty($this->$field ))
4840 $this->$field = $key;
4852 * RELATIONSHIP HANDLING
4855 function set_relationship($table, $relate_values, $check_duplicates = true,$do_update=false,$data_values=null)
4859 // make sure there is a date modified
4860 $date_modified = $this->db->convert("'".$GLOBALS['timedate']->nowDb()."'", 'datetime');
4863 if($check_duplicates)
4865 $query = "SELECT * FROM $table ";
4866 $where = "WHERE deleted = '0' ";
4867 foreach($relate_values as $name=>$value)
4869 $where .= " AND $name = '$value' ";
4872 $result = $this->db->query($query, false, "Looking For Duplicate Relationship:" . $query);
4873 $row=$this->db->fetchByAssoc($result);
4876 if(!$check_duplicates || empty($row) )
4878 unset($relate_values['id']);
4879 if ( isset($data_values))
4881 $relate_values = array_merge($relate_values,$data_values);
4883 $query = "INSERT INTO $table (id, ". implode(',', array_keys($relate_values)) . ", date_modified) VALUES ('" . create_guid() . "', " . "'" . implode("', '", $relate_values) . "', ".$date_modified.")" ;
4885 $this->db->query($query, false, "Creating Relationship:" . $query);
4887 else if ($do_update)
4890 foreach($data_values as $key=>$value)
4892 array_push($conds,$key."='".$this->db->quote($value)."'");
4894 $query = "UPDATE $table SET ". implode(',', $conds).",date_modified=".$date_modified." ".$where;
4895 $this->db->query($query, false, "Updating Relationship:" . $query);
4899 function retrieve_relationships($table, $values, $select_id)
4901 $query = "SELECT $select_id FROM $table WHERE deleted = 0 ";
4902 foreach($values as $name=>$value)
4904 $query .= " AND $name = '$value' ";
4906 $query .= " ORDER BY $select_id ";
4907 $result = $this->db->query($query, false, "Retrieving Relationship:" . $query);
4909 while($row = $this->db->fetchByAssoc($result))
4916 // TODO: this function needs adjustment
4917 function loadLayoutDefs()
4919 global $layout_defs;
4920 if(empty( $this->layout_def) && file_exists('modules/'. $this->module_dir . '/layout_defs.php'))
4922 include_once('modules/'. $this->module_dir . '/layout_defs.php');
4923 if(file_exists('custom/modules/'. $this->module_dir . '/Ext/Layoutdefs/layoutdefs.ext.php'))
4925 include_once('custom/modules/'. $this->module_dir . '/Ext/Layoutdefs/layoutdefs.ext.php');
4927 if ( empty( $layout_defs[get_class($this)]))
4929 echo "\$layout_defs[" . get_class($this) . "]; does not exist";
4932 $this->layout_def = $layout_defs[get_class($this)];
4937 * Trigger custom logic for this module that is defined for the provided hook
4938 * The custom logic file is located under custom/modules/[CURRENT_MODULE]/logic_hooks.php.
4939 * That file should define the $hook_version that should be used.
4940 * It should also define the $hook_array. The $hook_array will be a two dimensional array
4941 * the first dimension is the name of the event, the second dimension is the information needed
4942 * to fire the hook. Each entry in the top level array should be defined on a single line to make it
4943 * easier to automatically replace this file. There should be no contents of this file that are not replacable.
4945 * $hook_array['before_save'][] = Array(1, testtype, 'custom/modules/Leads/test12.php', 'TestClass', 'lead_before_save_1');
4946 * This sample line creates a before_save hook. The hooks are procesed in the order in which they
4947 * are added to the array. The second dimension is an array of:
4948 * processing index (for sorting before exporting the array)
4951 * php file to include
4952 * php class the method is in
4953 * php method to call
4955 * The method signature for version 1 hooks is:
4956 * function NAME(&$bean, $event, $arguments)
4957 * $bean - $this bean passed in by reference.
4958 * $event - The string for the current event (i.e. before_save)
4959 * $arguments - An array of arguments that are specific to the event.
4961 function call_custom_logic($event, $arguments = null)
4963 if(!isset($this->processed) || $this->processed == false){
4964 //add some logic to ensure we do not get into an infinite loop
4965 if(!empty($this->logicHookDepth[$event])) {
4966 if($this->logicHookDepth[$event] > 10)
4969 $this->logicHookDepth[$event] = 0;
4971 //we have to put the increment operator here
4972 //otherwise we may never increase the depth for that event in the case
4973 //where one event will trigger another as in the case of before_save and after_save
4974 //Also keeping the depth per event allow any number of hooks to be called on the bean
4975 //and we only will return if one event gets caught in a loop. We do not increment globally
4976 //for each event called.
4977 $this->logicHookDepth[$event]++;
4979 //method defined in 'include/utils/LogicHook.php'
4981 $logicHook = new LogicHook();
4982 $logicHook->setBean($this);
4983 $logicHook->call_custom_logic($this->module_dir, $event, $arguments);
4988 /* When creating a custom field of type Dropdown, it creates an enum row in the DB.
4989 A typical get_list_view_array() result will have the *KEY* value from that drop-down.
4990 Since custom _dom objects are flat-files included in the $app_list_strings variable,
4991 We need to generate a key-key pair to get the true value like so:
4992 ([module]_cstm->fields_meta_data->$app_list_strings->*VALUE*)*/
4993 function getRealKeyFromCustomFieldAssignedKey($name)
4995 if ($this->custom_fields->avail_fields[$name]['ext1'])
4999 elseif ($this->custom_fields->avail_fields[$name]['ext2'])
5003 elseif ($this->custom_fields->avail_fields[$name]['ext3'])
5009 $GLOBALS['log']->fatal("SUGARBEAN: cannot find Real Key for custom field of type dropdown - cannot return Value.");
5014 return $this->custom_fields->avail_fields[$name][$realKey];
5018 function bean_implements($interface)
5023 * Check whether the user has access to a particular view for the current bean/module
5024 * @param $view string required, the view to determine access for i.e. DetailView, ListView...
5025 * @param $is_owner bool optional, this is part of the ACL check if the current user is an owner they will receive different access
5027 function ACLAccess($view,$is_owner='not_set')
5029 global $current_user;
5030 if($current_user->isAdminForModule($this->getACLCategory())) {
5034 if($is_owner == 'not_set')
5037 $is_owner = $this->isOwner($current_user->id);
5040 //if we don't implent acls return true
5041 if(!$this->bean_implements('ACL'))
5043 $view = strtolower($view);
5049 return ACLController::checkAccess($this->module_dir,'list', true);
5052 if( !$is_owner && $not_set && !empty($this->id)){
5053 $class = get_class($this);
5054 $temp = new $class();
5055 if(!empty($this->fetched_row) && !empty($this->fetched_row['id']) && !empty($this->fetched_row['assigned_user_id']) && !empty($this->fetched_row['created_by'])){
5056 $temp->populateFromRow($this->fetched_row);
5058 $temp->retrieve($this->id);
5060 $is_owner = $temp->isOwner($current_user->id);
5062 case 'popupeditview':
5064 return ACLController::checkAccess($this->module_dir,'edit', $is_owner, $this->acltype);
5068 return ACLController::checkAccess($this->module_dir,'view', $is_owner, $this->acltype);
5070 return ACLController::checkAccess($this->module_dir,'delete', $is_owner, $this->acltype);
5072 return ACLController::checkAccess($this->module_dir,'export', $is_owner, $this->acltype);
5074 return ACLController::checkAccess($this->module_dir,'import', true, $this->acltype);
5076 //if it is not one of the above views then it should be implemented on the page level
5080 * Returns true of false if the user_id passed is the owner
5082 * @param GUID $user_id
5085 function isOwner($user_id)
5087 //if we don't have an id we must be the owner as we are creating it
5088 if(!isset($this->id))
5092 //if there is an assigned_user that is the owner
5093 if(isset($this->assigned_user_id))
5095 if($this->assigned_user_id == $user_id) return true;
5100 //other wise if there is a created_by that is the owner
5101 if(isset($this->created_by) && $this->created_by == $user_id)
5109 * Gets there where statement for checking if a user is an owner
5111 * @param GUID $user_id
5114 function getOwnerWhere($user_id)
5116 if(isset($this->field_defs['assigned_user_id']))
5118 return " $this->table_name.assigned_user_id ='$user_id' ";
5120 if(isset($this->field_defs['created_by']))
5122 return " $this->table_name.created_by ='$user_id' ";
5129 * Used in order to manage ListView links and if they should
5130 * links or not based on the ACL permissions of the user
5132 * @return ARRAY of STRINGS
5134 function listviewACLHelper()
5136 $array_assign = array();
5137 if($this->ACLAccess('DetailView'))
5139 $array_assign['MAIN'] = 'a';
5143 $array_assign['MAIN'] = 'span';
5145 return $array_assign;
5149 * returns this bean as an array
5151 * @return array of fields with id, name, access and category
5153 function toArray($dbOnly = false, $stringOnly = false, $upperKeys=false)
5155 static $cache = array();
5158 foreach($this->field_defs as $field=>$data)
5160 if( !$dbOnly || !isset($data['source']) || $data['source'] == 'db')
5161 if(!$stringOnly || is_string($this->$field))
5164 if(!isset($cache[$field])){
5165 $cache[$field] = strtoupper($field);
5167 $arr[$cache[$field]] = $this->$field;
5171 if(isset($this->$field)){
5172 $arr[$field] = $this->$field;
5182 * Converts an array into an acl mapping name value pairs into files
5186 function fromArray($arr)
5188 foreach($arr as $name=>$value)
5190 $this->$name = $value;
5195 * Loads a row of data into instance of a bean. The data is passed as an array to this function
5197 * @param array $arr row of data fetched from the database.
5200 * Internal function do not override.
5202 function loadFromRow($arr)
5204 $this->populateFromRow($arr);
5205 $this->processed_dates_times = array();
5206 $this->check_date_relationships_load();
5208 $this->fill_in_additional_list_fields();
5210 if($this->hasCustomFields())$this->custom_fields->fill_relationships();
5211 $this->call_custom_logic("process_record");
5214 function hasCustomFields(){
5215 return !empty($GLOBALS['dictionary'][$this->object_name]['custom_fields']);
5219 * Ensure that fields within order by clauses are properly qualified with
5220 * their tablename. This qualification is a requirement for sql server support.
5222 * @param string $order_by original order by from the query
5223 * @param string $qualify prefix for columns in the order by list.
5226 * Internal function do not override.
5228 function create_qualified_order_by( $order_by, $qualify)
5229 { // 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
5230 if (empty($order_by))
5234 $order_by_clause = " ORDER BY ";
5235 $tmp = explode(",", $order_by);
5237 foreach ( $tmp as $stmp)
5239 $stmp = (substr_count($stmp, ".") > 0?trim($stmp):"$qualify." . trim($stmp));
5240 $order_by_clause .= $comma . $stmp;
5243 return $order_by_clause;
5247 * Combined the contents of street field 2 thru 4 into the main field
5249 * @param string $street_field
5252 function add_address_streets(
5256 $street_field_2 = $street_field.'_2';
5257 $street_field_3 = $street_field.'_3';
5258 $street_field_4 = $street_field.'_4';
5259 if ( isset($this->$street_field_2)) {
5260 $this->$street_field .= "\n". $this->$street_field_2;
5261 unset($this->$street_field_2);
5263 if ( isset($this->$street_field_3)) {
5264 $this->$street_field .= "\n". $this->$street_field_3;
5265 unset($this->$street_field_3);
5267 if ( isset($this->$street_field_4)) {
5268 $this->$street_field .= "\n". $this->$street_field_4;
5269 unset($this->$street_field_4);
5271 if ( isset($this->$street_field)) {
5272 $this->$street_field = trim($this->$street_field, "\n");
5276 * Encrpyt and base64 encode an 'encrypt' field type in the bean using Blowfish. The default system key is stored in cache/Blowfish/{keytype}
5277 * @param STRING value -plain text value of the bean field.
5280 function encrpyt_before_save($value)
5282 require_once("include/utils/encryption_utils.php");
5283 return blowfishEncode(blowfishGetKey('encrypt_field'),$value);
5287 * Decode and decrypt a base 64 encoded string with field type 'encrypt' in this bean using Blowfish.
5288 * @param STRING value - an encrypted and base 64 encoded string.
5291 function decrypt_after_retrieve($value)
5293 require_once("include/utils/encryption_utils.php");
5294 return blowfishDecode(blowfishGetKey('encrypt_field'), $value);
5298 * Moved from save() method, functionality is the same, but this is intended to handle
5299 * Optimistic locking functionality.
5301 private function _checkOptimisticLocking($action, $isUpdate){
5302 if($this->optimistic_lock && !isset($_SESSION['o_lock_fs'])){
5303 if(isset($_SESSION['o_lock_id']) && $_SESSION['o_lock_id'] == $this->id && $_SESSION['o_lock_on'] == $this->object_name)
5305 if($action == 'Save' && $isUpdate && isset($this->modified_user_id) && $this->has_been_modified_since($_SESSION['o_lock_dm'], $this->modified_user_id))
5307 $_SESSION['o_lock_class'] = get_class($this);
5308 $_SESSION['o_lock_module'] = $this->module_dir;
5309 $_SESSION['o_lock_object'] = $this->toArray();
5310 $saveform = "<form name='save' id='save' method='POST'>";
5311 foreach($_POST as $key=>$arg)
5313 $saveform .= "<input type='hidden' name='". addslashes($key) ."' value='". addslashes($arg) ."'>";
5315 $saveform .= "</form><script>document.getElementById('save').submit();</script>";
5316 $_SESSION['o_lock_save'] = $saveform;
5317 header('Location: index.php?module=OptimisticLock&action=LockResolve');
5322 unset ($_SESSION['o_lock_object']);
5323 unset ($_SESSION['o_lock_id']);
5324 unset ($_SESSION['o_lock_dm']);
5330 if(isset($_SESSION['o_lock_object'])) { unset ($_SESSION['o_lock_object']); }
5331 if(isset($_SESSION['o_lock_id'])) { unset ($_SESSION['o_lock_id']); }
5332 if(isset($_SESSION['o_lock_dm'])) { unset ($_SESSION['o_lock_dm']); }
5333 if(isset($_SESSION['o_lock_fs'])) { unset ($_SESSION['o_lock_fs']); }
5334 if(isset($_SESSION['o_lock_save'])) { unset ($_SESSION['o_lock_save']); }
5339 * Send assignment notifications and invites for meetings and calls
5341 private function _sendNotifications($check_notify){
5342 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.
5343 && !$this->isOwner($this->created_by) ) // cn: bug 42727 no need to send email to owner (within workflow)
5345 $admin = new Administration();
5346 $admin->retrieveSettings();
5347 $sendNotifications = false;
5349 if ($admin->settings['notify_on'])
5351 $GLOBALS['log']->info("Notifications: user assignment has changed, checking if user receives notifications");
5352 $sendNotifications = true;
5354 elseif(isset($_REQUEST['send_invites']) && $_REQUEST['send_invites'] == 1)
5356 // cn: bug 5795 Send Invites failing for Contacts
5357 $sendNotifications = true;
5361 $GLOBALS['log']->info("Notifications: not sending e-mail, notify_on is set to OFF");
5365 if($sendNotifications == true)
5367 $notify_list = $this->get_notification_recipients();
5368 foreach ($notify_list as $notify_user)
5370 $this->send_assignment_notifications($notify_user, $admin);
5378 * Called from ImportFieldSanitize::relate(), when creating a new bean in a related module. Will
5379 * copies fields over from the current bean into the related. Designed to be overriden in child classes.
5381 * @param SugarBean $newbean newly created related bean
5383 public function populateRelatedBean(
5390 * Called during the import process before a bean save, to handle any needed pre-save logic when
5391 * importing a record
5393 public function beforeImportSave()
5398 * Called during the import process after a bean save, to handle any needed post-save logic when
5399 * importing a record
5401 public function afterImportSave()
5406 * This function is designed to cache references to field arrays that were previously stored in the
5407 * bean files and have since been moved to seperate files. Was previously in include/CacheHandler.php
5410 * @param $module_dir string the module directory
5411 * @param $module string the name of the module
5412 * @param $key string the type of field array we are referencing, i.e. list_fields, column_fields, required_fields
5414 private function _loadCachedArray(
5420 static $moduleDefs = array();
5422 $fileName = 'field_arrays.php';
5424 $cache_key = "load_cached_array.$module_dir.$module.$key";
5425 $result = sugar_cache_retrieve($cache_key);
5428 // Use SugarCache::EXTERNAL_CACHE_NULL_VALUE to store null values in the cache.
5429 if($result == SugarCache::EXTERNAL_CACHE_NULL_VALUE)
5437 if(file_exists('modules/'.$module_dir.'/'.$fileName))
5439 // If the data was not loaded, try loading again....
5440 if(!isset($moduleDefs[$module]))
5442 include('modules/'.$module_dir.'/'.$fileName);
5443 $moduleDefs[$module] = $fields_array;
5445 // Now that we have tried loading, make sure it was loaded
5446 if(empty($moduleDefs[$module]) || empty($moduleDefs[$module][$module][$key]))
5448 // It was not loaded.... Fail. Cache null to prevent future repeats of this calculation
5449 sugar_cache_put($cache_key, SugarCache::EXTERNAL_CACHE_NULL_VALUE);
5453 // It has been loaded, cache the result.
5454 sugar_cache_put($cache_key, $moduleDefs[$module][$module][$key]);
5455 return $moduleDefs[$module][$module][$key];
5458 // It was not loaded.... Fail. Cache null to prevent future repeats of this calculation
5459 sugar_cache_put($cache_key, SugarCache::EXTERNAL_CACHE_NULL_VALUE);
5464 * Returns the ACL category for this module; defaults to the SugarBean::$acl_category if defined
5465 * otherwise it is SugarBean::$module_dir
5469 public function getACLCategory()
5471 return !empty($this->acl_category)?$this->acl_category:$this->module_dir;
5475 * Returns the query used for the export functionality for a module. Override this method if you wish
5476 * to have a custom query to pull this data together instead
5478 * @param string $order_by
5479 * @param string $where
5480 * @return string SQL query
5482 public function create_export_query($order_by, $where)
5484 return $this->create_new_list_query($order_by, $where, array(), array(), 0, '', false, $this, true);