2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4 * SugarCRM Community Edition is a customer relationship management program developed by
5 * SugarCRM, Inc. Copyright (C) 2004-2012 SugarCRM Inc.
7 * This program is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU Affero General Public License version 3 as published by the
9 * Free Software Foundation with the addition of the following permission added
10 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11 * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
12 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
14 * This program is distributed in the hope that it will be useful, but WITHOUT
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
19 * You should have received a copy of the GNU Affero General Public License along with
20 * this program; if not, see http://www.gnu.org/licenses or write to the Free
21 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
24 * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
25 * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
27 * The interactive user interfaces in modified source and object code versions
28 * of this program must display Appropriate Legal Notices, as required under
29 * Section 5 of the GNU Affero General Public License version 3.
31 * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32 * these Appropriate Legal Notices must retain the display of the "Powered by
33 * SugarCRM" logo. If the display of the logo is not reasonably feasible for
34 * technical reasons, the Appropriate Legal Notices must display the words
35 * "Powered by SugarCRM".
36 ********************************************************************************/
38 /*********************************************************************************
40 * Description: Defines the base class for all data entities used throughout the
41 * application. The base class including its methods and variables is designed to
42 * be overloaded with module-specific methods and variables particular to the
43 * module's base entity class.
44 * Portions created by SugarCRM are Copyright (C) SugarCRM, Inc.
45 * All Rights Reserved.
46 *******************************************************************************/
48 require_once('modules/DynamicFields/DynamicField.php');
49 require_once("data/Relationships/RelationshipFactory.php");
56 * SugarBean is the base class for all business objects in Sugar. It implements
57 * the primary functionality needed for manipulating business objects: create,
58 * retrieve, update, delete. It allows for searching and retrieving list of records.
59 * It allows for retrieving related objects (e.g. contacts related to a specific account).
61 * In the current implementation, there can only be one bean per folder.
62 * Naming convention has the bean name be the same as the module and folder name.
63 * All bean names should be singular (e.g. Contact). The primary table name for
64 * a bean should be plural (e.g. contacts).
70 * A pointer to the database object
77 * When createing a bean, you can specify a value in the id column as
78 * long as that value is unique. During save, if the system finds an
79 * id, it assumes it is an update. Setting new_with_id to true will
80 * make sure the system performs an insert instead of an update.
82 * @var BOOL -- default false
84 var $new_with_id = false;
88 * Disble vardefs. This should be set to true only for beans that do not have varders. Tracker is an example
90 * @var BOOL -- default false
92 var $disable_vardefs = false;
96 * holds the full name of the user that an item is assigned to. Only used if notifications
97 * are turned on and going to be sent out.
101 var $new_assigned_user_name;
104 * An array of booleans. This array is cleared out when data is loaded.
105 * As date/times are converted, a "1" is placed under the key, the field is converted.
107 * @var Array of booleans
109 var $processed_dates_times = array();
112 * Whether to process date/time fields for storage in the database in GMT
116 var $process_save_dates =true;
119 * This signals to the bean that it is being saved in a mass mode.
120 * Examples of this kind of save are import and mass update.
121 * We turn off notificaitons of this is the case to make things more efficient.
125 var $save_from_post = true;
128 * When running a query on related items using the method: retrieve_by_string_fields
129 * this value will be set to true if more than one item matches the search criteria.
133 var $duplicates_found = false;
136 * true if this bean has been deleted, false otherwise.
143 * Should the date modified column of the bean be updated during save?
144 * This is used for admin level functionality that should not be updating
145 * the date modified. This is only used by sync to allow for updates to be
146 * replicated in a way that will not cause them to be replicated back.
150 var $update_date_modified = true;
153 * Should the modified by column of the bean be updated during save?
154 * This is used for admin level functionality that should not be updating
155 * the modified by column. This is only used by sync to allow for updates to be
156 * replicated in a way that will not cause them to be replicated back.
160 var $update_modified_by = true;
163 * Setting this to true allows for updates to overwrite the date_entered
167 var $update_date_entered = false;
170 * This allows for seed data to be created without using the current uesr to set the id.
171 * This should be replaced by altering the current user before the call to save.
175 //TODO This should be replaced by altering the current user before the call to save.
176 var $set_created_by = true;
181 * The database table where records of this Bean are stored.
185 var $table_name = '';
188 * This is the singular name of the bean. (i.e. Contact).
192 var $object_name = '';
194 /** Set this to true if you query contains a sub-select and bean is converting both select statements
195 * into count queries.
197 var $ungreedy_count=false;
200 * The name of the module folder for this type of bean.
204 var $module_dir = '';
205 var $module_name = '';
209 var $column_fields = array();
210 var $list_fields = array();
211 var $additional_column_fields = array();
212 var $relationship_fields = array();
213 var $current_notify_user;
214 var $fetched_row=false;
216 var $force_load_details = false;
217 var $optimistic_lock = false;
218 var $disable_custom_fields = false;
219 var $number_formatting_done = false;
220 var $process_field_encrypted=false;
222 * The default ACL type
224 var $acltype = 'module';
227 var $additional_meta_fields = array();
230 * Set to true in the child beans if the module supports importing
232 var $importable = false;
235 * Set to true in the child beans if the module use the special notification template
237 var $special_notification = false;
240 * Set to true if the bean is being dealt with in a workflow
242 var $in_workflow = false;
246 * By default it will be true but if any module is to be kept non visible
247 * to tracker, then its value needs to be overriden in that particular module to false.
250 var $tracker_visibility = true;
253 * Used to pass inner join string to ListView Data.
255 var $listview_inner_join = array();
258 * Set to true in <modules>/Import/views/view.step4.php if a module is being imported
260 var $in_import = false;
262 * Constructor for the bean, it performs following tasks:
264 * 1. Initalized a database connections
265 * 2. Load the vardefs for the module implemeting the class. cache the entries
267 * 3. Setup row-level security preference
268 * All implementing classes must call this constructor using the parent::SugarBean() class.
273 global $dictionary, $current_user;
274 static $loaded_defs = array();
275 $this->db = DBManagerFactory::getInstance();
276 if (empty($this->module_name))
277 $this->module_name = $this->module_dir;
278 if((false == $this->disable_vardefs && empty($loaded_defs[$this->object_name])) || !empty($GLOBALS['reload_vardefs']))
280 VardefManager::loadVardef($this->module_dir, $this->object_name);
282 // build $this->column_fields from the field_defs if they exist
283 if (!empty($dictionary[$this->object_name]['fields'])) {
284 foreach ($dictionary[$this->object_name]['fields'] as $key=>$value_array) {
285 $column_fields[] = $key;
286 if(!empty($value_array['required']) && !empty($value_array['name'])) {
287 $this->required_fields[$value_array['name']] = 1;
290 $this->column_fields = $column_fields;
293 //setup custom fields
294 if(!isset($this->custom_fields) &&
295 empty($this->disable_custom_fields))
297 $this->setupCustomFields($this->module_dir);
299 //load up field_arrays from CacheHandler;
300 if(empty($this->list_fields))
301 $this->list_fields = $this->_loadCachedArray($this->module_dir, $this->object_name, 'list_fields');
302 if(empty($this->column_fields))
303 $this->column_fields = $this->_loadCachedArray($this->module_dir, $this->object_name, 'column_fields');
304 if(empty($this->required_fields))
305 $this->required_fields = $this->_loadCachedArray($this->module_dir, $this->object_name, 'required_fields');
307 if(isset($GLOBALS['dictionary'][$this->object_name]) && !$this->disable_vardefs)
309 $this->field_name_map = $dictionary[$this->object_name]['fields'];
310 $this->field_defs = $dictionary[$this->object_name]['fields'];
312 if(!empty($dictionary[$this->object_name]['optimistic_locking']))
314 $this->optimistic_lock=true;
317 $loaded_defs[$this->object_name]['column_fields'] =& $this->column_fields;
318 $loaded_defs[$this->object_name]['list_fields'] =& $this->list_fields;
319 $loaded_defs[$this->object_name]['required_fields'] =& $this->required_fields;
320 $loaded_defs[$this->object_name]['field_name_map'] =& $this->field_name_map;
321 $loaded_defs[$this->object_name]['field_defs'] =& $this->field_defs;
325 $this->column_fields =& $loaded_defs[$this->object_name]['column_fields'] ;
326 $this->list_fields =& $loaded_defs[$this->object_name]['list_fields'];
327 $this->required_fields =& $loaded_defs[$this->object_name]['required_fields'];
328 $this->field_name_map =& $loaded_defs[$this->object_name]['field_name_map'];
329 $this->field_defs =& $loaded_defs[$this->object_name]['field_defs'];
330 $this->added_custom_field_defs = true;
332 if(!isset($this->custom_fields) &&
333 empty($this->disable_custom_fields))
335 $this->setupCustomFields($this->module_dir, false);
337 if(!empty($dictionary[$this->object_name]['optimistic_locking']))
339 $this->optimistic_lock=true;
343 if($this->bean_implements('ACL') && !empty($GLOBALS['current_user'])){
344 $this->acl_fields = (isset($dictionary[$this->object_name]['acl_fields']) && $dictionary[$this->object_name]['acl_fields'] === false)?false:true;
346 $this->populateDefaultValues();
351 * Returns the object name. If object_name is not set, table_name is returned.
353 * All implementing classes must set a value for the object_name variable.
355 * @param array $arr row of data fetched from the database.
359 function getObjectName()
361 if ($this->object_name)
362 return $this->object_name;
364 // This is a quick way out. The generated metadata files have the table name
365 // as the key. The correct way to do this is to override this function
366 // in bean and return the object name. That requires changing all the beans
367 // as well as put the object name in the generator.
368 return $this->table_name;
372 * Returns a list of fields with their definitions that have the audited property set to true.
373 * Before calling this function, check whether audit has been enabled for the table/module or not.
374 * You would set the audit flag in the implemting module's vardef file.
376 * @return an array of
377 * @see is_AuditEnabled
379 * Internal function, do not override.
381 function getAuditEnabledFieldDefinitions()
383 $aclcheck = $this->bean_implements('ACL');
384 $is_owner = $this->isOwner($GLOBALS['current_user']->id);
385 if (!isset($this->audit_enabled_fields))
388 $this->audit_enabled_fields=array();
389 foreach ($this->field_defs as $field => $properties)
394 !empty($properties['Audited']) || !empty($properties['audited']))
398 $this->audit_enabled_fields[$field]=$properties;
403 return $this->audit_enabled_fields;
407 * Return true if auditing is enabled for this object
408 * You would set the audit flag in the implemting module's vardef file.
412 * Internal function, do not override.
414 function is_AuditEnabled()
417 if (isset($dictionary[$this->getObjectName()]['audited']))
419 return $dictionary[$this->getObjectName()]['audited'];
430 * Returns the name of the audit table.
431 * Audit table's name is based on implementing class' table name.
433 * @return String Audit table name.
435 * Internal function, do not override.
437 function get_audit_table_name()
439 return $this->getTableName().'_audit';
443 * Returns the name of the custom table.
444 * Custom table's name is based on implementing class' table name.
446 * @return String Custom table name.
448 * Internal function, do not override.
450 public function get_custom_table_name()
452 return $this->getTableName().'_cstm';
456 * If auditing is enabled, create the audit table.
458 * Function is used by the install scripts and a repair utility in the admin panel.
460 * Internal function, do not override.
462 function create_audit_table()
465 $table_name=$this->get_audit_table_name();
467 require('metadata/audit_templateMetaData.php');
469 $fieldDefs = $dictionary['audit']['fields'];
470 $indices = $dictionary['audit']['indices'];
471 // '0' stands for the first index for all the audit tables
472 $indices[0]['name'] = 'idx_' . strtolower($this->getTableName()) . '_' . $indices[0]['name'];
473 $indices[1]['name'] = 'idx_' . strtolower($this->getTableName()) . '_' . $indices[1]['name'];
475 if(isset($dictionary['audit']['engine'])) {
476 $engine = $dictionary['audit']['engine'];
477 } else if(isset($dictionary[$this->getObjectName()]['engine'])) {
478 $engine = $dictionary[$this->getObjectName()]['engine'];
481 $sql=$this->db->createTableSQLParams($table_name, $fieldDefs, $indices, $engine);
483 $msg = "Error creating table: ".$table_name. ":";
484 $this->db->query($sql,true,$msg);
488 * Returns the implementing class' table name.
490 * All implementing classes set a value for the table_name variable. This value is returned as the
491 * table name. If not set, table name is extracted from the implementing module's vardef.
493 * @return String Table name.
495 * Internal function, do not override.
497 public function getTableName()
499 if(isset($this->table_name))
501 return $this->table_name;
504 return $dictionary[$this->getObjectName()]['table'];
508 * Returns field definitions for the implementing module.
510 * The definitions were loaded in the constructor.
512 * @return Array Field definitions.
514 * Internal function, do not override.
516 function getFieldDefinitions()
518 return $this->field_defs;
522 * Returns index definitions for the implementing module.
524 * The definitions were loaded in the constructor.
526 * @return Array Index definitions.
528 * Internal function, do not override.
530 function getIndices()
533 if(isset($dictionary[$this->getObjectName()]['indices']))
535 return $dictionary[$this->getObjectName()]['indices'];
541 * Returns field definition for the requested field name.
543 * The definitions were loaded in the constructor.
545 * @param string field name,
546 * @return Array Field properties or boolean false if the field doesn't exist
548 * Internal function, do not override.
550 function getFieldDefinition($name)
552 if ( !isset($this->field_defs[$name]) )
555 return $this->field_defs[$name];
559 * Returnss definition for the id field name.
561 * The definitions were loaded in the constructor.
563 * @return Array Field properties.
565 * Internal function, do not override.
567 function getPrimaryFieldDefinition()
569 $def = $this->getFieldDefinition("id");
571 $def = $this->getFieldDefinition(0);
574 $defs = $this->field_defs;
576 $def = current($defs);
581 * Returns the value for the requested field.
583 * When a row of data is fetched using the bean, all fields are created as variables in the context
584 * of the bean and then fetched values are set in these variables.
586 * @param string field name,
587 * @return varies Field value.
589 * Internal function, do not override.
591 function getFieldValue($name)
593 if (!isset($this->$name)){
596 if($this->$name === TRUE){
599 if($this->$name === FALSE){
606 * Basically undoes the effects of SugarBean::populateDefaultValues(); this method is best called right after object
609 public function unPopulateDefaultValues()
611 if ( !is_array($this->field_defs) )
614 foreach ($this->field_defs as $field => $value) {
615 if( !empty($this->$field)
616 && ((isset($value['default']) && $this->$field == $value['default']) || (!empty($value['display_default']) && $this->$field == $value['display_default']))
618 $this->$field = null;
621 if(!empty($this->$field) && !empty($value['display_default']) && in_array($value['type'], array('date', 'datetime', 'datetimecombo')) &&
622 $this->$field == $this->parseDateDefault($value['display_default'], ($value['type'] != 'date'))) {
623 $this->$field = null;
629 * Create date string from default value
631 * @param string $value
632 * @param bool $time Should be expect time set too?
635 protected function parseDateDefault($value, $time = false)
639 $dtAry = explode('&', $value, 2);
640 $dateValue = $timedate->getNow(true)->modify($dtAry[0]);
641 if(!empty($dtAry[1])) {
642 $timeValue = $timedate->fromString($dtAry[1]);
643 $dateValue->setTime($timeValue->hour, $timeValue->min, $timeValue->sec);
645 return $timedate->asUser($dateValue);
647 return $timedate->asUserDate($timedate->getNow(true)->modify($value));
651 function populateDefaultValues($force=false){
652 if ( !is_array($this->field_defs) )
654 foreach($this->field_defs as $field=>$value){
655 if((isset($value['default']) || !empty($value['display_default'])) && ($force || empty($this->$field))){
656 $type = $value['type'];
660 if(!empty($value['display_default'])){
661 $this->$field = $this->parseDateDefault($value['display_default']);
665 case 'datetimecombo':
666 if(!empty($value['display_default'])){
667 $this->$field = $this->parseDateDefault($value['display_default'], true);
671 if(empty($value['default']) && !empty($value['display_default']))
672 $this->$field = $value['display_default'];
674 $this->$field = $value['default'];
677 if(isset($this->$field)){
681 if ( isset($value['default']) && $value['default'] !== '' ) {
682 $this->$field = htmlentities($value['default'], ENT_QUOTES, 'UTF-8');
693 * Removes relationship metadata cache.
695 * Every module that has relationships defined with other modules, has this meta data cached. The cache is
696 * stores in 2 locations: relationships table and file system. This method clears the cache from both locations.
698 * @param string $key module whose meta cache is to be cleared.
699 * @param string $db database handle.
700 * @param string $tablename table name
701 * @param string $dictionary vardef for the module
702 * @param string $module_dir name of subdirectory where module is installed.
707 * Internal function, do not override.
709 function removeRelationshipMeta($key,$db,$tablename,$dictionary,$module_dir)
711 //load the module dictionary if not supplied.
712 if ((!isset($dictionary) or empty($dictionary)) && !empty($module_dir))
714 $filename='modules/'. $module_dir . '/vardefs.php';
715 if(file_exists($filename))
720 if (!is_array($dictionary) or !array_key_exists($key, $dictionary))
722 $GLOBALS['log']->fatal("removeRelationshipMeta: Metadata for table ".$tablename. " does not exist");
723 display_notice("meta data absent for table ".$tablename." keyed to $key ");
727 if (isset($dictionary[$key]['relationships']))
729 $RelationshipDefs = $dictionary[$key]['relationships'];
730 foreach ($RelationshipDefs as $rel_name)
732 Relationship::delete($rel_name,$db);
740 * This method has been deprecated.
742 * @see removeRelationshipMeta()
743 * @deprecated 4.5.1 - Nov 14, 2006
746 function remove_relationship_meta($key,$db,$log,$tablename,$dictionary,$module_dir)
748 SugarBean::removeRelationshipMeta($key,$db,$tablename,$dictionary,$module_dir);
753 * Populates the relationship meta for a module.
755 * It is called during setup/install. It is used statically to create relationship meta data for many-to-many tables.
757 * @param string $key name of the object.
758 * @param object $db database handle.
759 * @param string $tablename table, meta data is being populated for.
760 * @param array dictionary vardef dictionary for the object. *
761 * @param string module_dir name of subdirectory where module is installed.
762 * @param boolean $iscustom Optional,set to true if module is installed in a custom directory. Default value is false.
765 * Internal function, do not override.
767 function createRelationshipMeta($key,$db,$tablename,$dictionary,$module_dir,$iscustom=false)
769 //load the module dictionary if not supplied.
770 if (empty($dictionary) && !empty($module_dir))
774 $filename='custom/modules/' . $module_dir . '/Ext/Vardefs/vardefs.ext.php';
780 // a very special case for the Employees module
781 // this must be done because the Employees/vardefs.php does an include_once on
783 $filename='modules/Users/vardefs.php';
787 $filename='modules/'. $module_dir . '/vardefs.php';
791 if(file_exists($filename))
794 // cn: bug 7679 - dictionary entries defined as $GLOBALS['name'] not found
795 if(empty($dictionary) || !empty($GLOBALS['dictionary'][$key]))
797 $dictionary = $GLOBALS['dictionary'];
802 $GLOBALS['log']->debug("createRelationshipMeta: no metadata file found" . $filename);
807 if (!is_array($dictionary) or !array_key_exists($key, $dictionary))
809 $GLOBALS['log']->fatal("createRelationshipMeta: Metadata for table ".$tablename. " does not exist");
810 display_notice("meta data absent for table ".$tablename." keyed to $key ");
814 if (isset($dictionary[$key]['relationships']))
817 $RelationshipDefs = $dictionary[$key]['relationships'];
821 $beanList_ucase=array_change_key_case ( $beanList ,CASE_UPPER);
822 foreach ($RelationshipDefs as $rel_name=>$rel_def)
824 if (isset($rel_def['lhs_module']) and !isset($beanList_ucase[strtoupper($rel_def['lhs_module'])])) {
825 $GLOBALS['log']->debug('skipping orphaned relationship record ' . $rel_name . ' lhs module is missing ' . $rel_def['lhs_module']);
828 if (isset($rel_def['rhs_module']) and !isset($beanList_ucase[strtoupper($rel_def['rhs_module'])])) {
829 $GLOBALS['log']->debug('skipping orphaned relationship record ' . $rel_name . ' rhs module is missing ' . $rel_def['rhs_module']);
834 //check whether relationship exists or not first.
835 if (Relationship::exists($rel_name,$db))
837 $GLOBALS['log']->debug('Skipping, reltionship already exists '.$rel_name);
841 $seed = BeanFactory::getBean("Relationships");
842 $keys = array_keys($seed->field_defs);
844 foreach($keys as $key)
848 $toInsert[$key] = create_guid();
850 else if ($key == "relationship_name")
852 $toInsert[$key] = $rel_name;
854 else if (isset($rel_def[$key]))
856 $toInsert[$key] = $rel_def[$key];
858 //todo specify defaults if meta not defined.
862 $column_list = implode(",", array_keys($toInsert));
863 $value_list = "'" . implode("','", array_values($toInsert)) . "'";
865 //create the record. todo add error check.
866 $insert_string = "INSERT into relationships (" .$column_list. ") values (".$value_list.")";
867 $db->query($insert_string, true);
874 //log informational message stating no relationships meta was set for this bean.
880 * This method has been deprecated.
881 * @see createRelationshipMeta()
882 * @deprecated 4.5.1 - Nov 14, 2006
885 function create_relationship_meta($key,&$db,&$log,$tablename,$dictionary,$module_dir)
887 SugarBean::createRelationshipMeta($key,$db,$tablename,$dictionary,$module_dir);
892 * Loads the request relationship. This method should be called before performing any operations on the related data.
894 * This method searches the vardef array for the requested attribute's definition. If the attribute is of the type
895 * link then it creates a similary named variable and loads the relationship definition.
897 * @param string $rel_name relationship/attribute name.
900 function load_relationship($rel_name)
902 $GLOBALS['log']->debug("SugarBean[{$this->object_name}].load_relationships, Loading relationship (".$rel_name.").");
904 if (empty($rel_name))
906 $GLOBALS['log']->error("SugarBean.load_relationships, Null relationship name passed.");
909 $fieldDefs = $this->getFieldDefinitions();
911 //find all definitions of type link.
912 if (!empty($fieldDefs[$rel_name]))
914 //initialize a variable of type Link
915 require_once('data/Link2.php');
916 $class = load_link_class($fieldDefs[$rel_name]);
917 if (isset($this->$rel_name) && $this->$rel_name instanceof $class) {
920 //if rel_name is provided, search the fieldef array keys by name.
921 if (isset($fieldDefs[$rel_name]['type']) && $fieldDefs[$rel_name]['type'] == 'link')
923 if ($class == "Link2")
924 $this->$rel_name = new $class($rel_name, $this);
926 $this->$rel_name = new $class($fieldDefs[$rel_name]['relationship'], $this, $fieldDefs[$rel_name]);
928 if (empty($this->$rel_name) ||
929 (method_exists($this->$rel_name, "loadedSuccesfully") && !$this->$rel_name->loadedSuccesfully()))
931 unset($this->$rel_name);
937 $GLOBALS['log']->debug("SugarBean.load_relationships, Error Loading relationship (".$rel_name.")");
942 * Loads all attributes of type link.
944 * DO NOT CALL THIS FUNCTION IF YOU CAN AVOID IT. Please use load_relationship directly instead.
946 * Method searches the implmenting module's vardef file for attributes of type link, and for each attribute
947 * create a similary named variable and load the relationship definition.
951 * Internal function, do not override.
953 function load_relationships()
955 $GLOBALS['log']->debug("SugarBean.load_relationships, Loading all relationships of type link.");
956 $linked_fields=$this->get_linked_fields();
957 foreach($linked_fields as $name=>$properties)
959 $this->load_relationship($name);
964 * Returns an array of beans of related data.
966 * For instance, if an account is related to 10 contacts , this function will return an array of contacts beans (10)
967 * with each bean representing a contact record.
968 * Method will load the relationship if not done so already.
970 * @param string $field_name relationship to be loaded.
971 * @param string $bean name class name of the related bean.
972 * @param array $sort_array optional, unused
973 * @param int $begin_index Optional, default 0, unused.
974 * @param int $end_index Optional, default -1
975 * @param int $deleted Optional, Default 0, 0 adds deleted=0 filter, 1 adds deleted=1 filter.
976 * @param string $optional_where, Optional, default empty.
978 * Internal function, do not override.
980 function get_linked_beans($field_name,$bean_name, $sort_array = array(), $begin_index = 0, $end_index = -1,
981 $deleted=0, $optional_where="")
983 //if bean_name is Case then use aCase
984 if($bean_name=="Case")
985 $bean_name = "aCase";
987 if($this->load_relationship($field_name)) {
988 if ($this->$field_name instanceof Link) {
989 // some classes are still based on Link, e.g. TeamSetLink
990 return array_values($this->$field_name->getBeans(new $bean_name(), $sort_array, $begin_index, $end_index, $deleted, $optional_where));
993 if ($end_index != -1 || !empty($deleted) || !empty($optional_where))
994 return array_values($this->$field_name->getBeans(array(
995 'where' => $optional_where,
996 'deleted' => $deleted,
997 'limit' => ($end_index - $begin_index)
1000 return array_values($this->$field_name->getBeans());
1008 * Returns an array of fields that are of type link.
1010 * @return array List of fields.
1012 * Internal function, do not override.
1014 function get_linked_fields()
1017 $linked_fields=array();
1019 // require_once('data/Link.php');
1021 $fieldDefs = $this->getFieldDefinitions();
1023 //find all definitions of type link.
1024 if (!empty($fieldDefs))
1026 foreach ($fieldDefs as $name=>$properties)
1028 if (array_search('link',$properties) === 'type')
1030 $linked_fields[$name]=$properties;
1035 return $linked_fields;
1039 * Returns an array of fields that are able to be Imported into
1040 * i.e. 'importable' not set to 'false'
1042 * @return array List of fields.
1044 * Internal function, do not override.
1046 function get_importable_fields()
1048 $importableFields = array();
1050 $fieldDefs= $this->getFieldDefinitions();
1052 if (!empty($fieldDefs)) {
1053 foreach ($fieldDefs as $key=>$value_array) {
1054 if ( (isset($value_array['importable'])
1055 && (is_string($value_array['importable']) && $value_array['importable'] == 'false'
1056 || is_bool($value_array['importable']) && $value_array['importable'] == false))
1057 || (isset($value_array['type']) && $value_array['type'] == 'link')
1058 || (isset($value_array['auto_increment'])
1059 && ($value_array['type'] == true || $value_array['type'] == 'true')) ) {
1060 // only allow import if we force it
1061 if (isset($value_array['importable'])
1062 && (is_string($value_array['importable']) && $value_array['importable'] == 'true'
1063 || is_bool($value_array['importable']) && $value_array['importable'] == true)) {
1064 $importableFields[$key]=$value_array;
1068 $importableFields[$key]=$value_array;
1073 return $importableFields;
1077 * Returns an array of fields that are of type relate.
1079 * @return array List of fields.
1081 * Internal function, do not override.
1083 function get_related_fields()
1086 $related_fields=array();
1088 // require_once('data/Link.php');
1090 $fieldDefs = $this->getFieldDefinitions();
1092 //find all definitions of type link.
1093 if (!empty($fieldDefs))
1095 foreach ($fieldDefs as $name=>$properties)
1097 if (array_search('relate',$properties) === 'type')
1099 $related_fields[$name]=$properties;
1104 return $related_fields;
1108 * Returns an array of fields that are required for import
1112 function get_import_required_fields()
1114 $importable_fields = $this->get_importable_fields();
1115 $required_fields = array();
1117 foreach ( $importable_fields as $name => $properties ) {
1118 if ( isset($properties['importable']) && is_string($properties['importable']) && $properties['importable'] == 'required' ) {
1119 $required_fields[$name] = $properties;
1123 return $required_fields;
1127 * Iterates through all the relationships and deletes all records for reach relationship.
1129 * @param string $id Primary key value of the parent reocrd
1131 function delete_linked($id)
1133 $linked_fields=$this->get_linked_fields();
1134 foreach ($linked_fields as $name => $value)
1136 if ($this->load_relationship($name))
1138 $this->$name->delete($id);
1142 $GLOBALS['log']->fatal("error loading relationship $name");
1148 * Creates tables for the module implementing the class.
1149 * If you override this function make sure that your code can handles table creation.
1152 function create_tables()
1156 $key = $this->getObjectName();
1157 if (!array_key_exists($key, $dictionary))
1159 $GLOBALS['log']->fatal("create_tables: Metadata for table ".$this->table_name. " does not exist");
1160 display_notice("meta data absent for table ".$this->table_name." keyed to $key ");
1164 if(!$this->db->tableExists($this->table_name))
1166 $this->db->createTable($this);
1167 if($this->bean_implements('ACL')){
1168 if(!empty($this->acltype)){
1169 ACLAction::addActions($this->getACLCategory(), $this->acltype);
1171 ACLAction::addActions($this->getACLCategory());
1177 echo "Table already exists : $this->table_name<br>";
1179 if($this->is_AuditEnabled()){
1180 if (!$this->db->tableExists($this->get_audit_table_name())) {
1181 $this->create_audit_table();
1189 * Delete the primary table for the module implementing the class.
1190 * If custom fields were added to this table/module, the custom table will be removed too, along with the cache
1191 * entries that define the custom fields.
1194 function drop_tables()
1197 $key = $this->getObjectName();
1198 if (!array_key_exists($key, $dictionary))
1200 $GLOBALS['log']->fatal("drop_tables: Metadata for table ".$this->table_name. " does not exist");
1201 echo "meta data absent for table ".$this->table_name."<br>\n";
1203 if(empty($this->table_name))return;
1204 if ($this->db->tableExists($this->table_name))
1206 $this->db->dropTable($this);
1207 if ($this->db->tableExists($this->table_name. '_cstm'))
1209 $this->db->dropTableName($this->table_name. '_cstm');
1210 DynamicField::deleteCache();
1212 if ($this->db->tableExists($this->get_audit_table_name())) {
1213 $this->db->dropTableName($this->get_audit_table_name());
1222 * Loads the definition of custom fields defined for the module.
1223 * Local file system cache is created as needed.
1225 * @param string $module_name setting up custom fields for this module.
1226 * @param boolean $clean_load Optional, default true, rebuilds the cache if set to true.
1228 function setupCustomFields($module_name, $clean_load=true)
1230 $this->custom_fields = new DynamicField($module_name);
1231 $this->custom_fields->setup($this);
1236 * Cleans char, varchar, text, etc. fields of XSS type materials
1238 function cleanBean() {
1239 foreach($this->field_defs as $key => $def) {
1241 if (isset($def['type'])) {
1244 if(isset($def['dbType']))
1245 $type .= $def['dbType'];
1247 if($def['type'] == 'html') {
1248 $this->$key = SugarCleaner::cleanHtml($this->$key, true);
1249 } elseif((strpos($type, 'char') !== false ||
1250 strpos($type, 'text') !== false ||
1254 $this->$key = SugarCleaner::cleanHtml($this->$key);
1260 * Implements a generic insert and update logic for any SugarBean
1261 * This method only works for subclasses that implement the same variable names.
1262 * This method uses the presence of an id field that is not null to signify and update.
1263 * The id field should not be set otherwise.
1265 * @param boolean $check_notify Optional, default false, if set to true assignee of the record is notified via email.
1266 * @todo Add support for field type validation and encoding of parameters.
1268 function save($check_notify = FALSE)
1270 $this->in_save = true;
1271 // cn: SECURITY - strip XSS potential vectors
1273 // This is used so custom/3rd-party code can be upgraded with fewer issues, this will be removed in a future release
1274 $this->fixUpFormatting();
1276 global $current_user, $action;
1279 if(empty($this->id))
1284 if ( $this->new_with_id == true )
1288 if(empty($this->date_modified) || $this->update_date_modified)
1290 $this->date_modified = $GLOBALS['timedate']->nowDb();
1293 $this->_checkOptimisticLocking($action, $isUpdate);
1295 if(!empty($this->modified_by_name)) $this->old_modified_by_name = $this->modified_by_name;
1296 if($this->update_modified_by)
1298 $this->modified_user_id = 1;
1300 if (!empty($current_user))
1302 $this->modified_user_id = $current_user->id;
1303 $this->modified_by_name = $current_user->user_name;
1306 if ($this->deleted != 1)
1310 if (empty($this->date_entered))
1312 $this->date_entered = $this->date_modified;
1314 if($this->set_created_by == true)
1316 // created by should always be this user
1317 $this->created_by = (isset($current_user)) ? $current_user->id : "";
1319 if( $this->new_with_id == false)
1321 $this->id = create_guid();
1327 require_once("data/BeanFactory.php");
1328 BeanFactory::registerBean($this->module_name, $this);
1330 if (empty($GLOBALS['updating_relationships']) && empty($GLOBALS['saving_relationships']) && empty ($GLOBALS['resavingRelatedBeans']))
1332 $GLOBALS['saving_relationships'] = true;
1333 // let subclasses save related field changes
1334 $this->save_relationship_changes($isUpdate);
1335 $GLOBALS['saving_relationships'] = false;
1337 if($isUpdate && !$this->update_date_entered)
1339 unset($this->date_entered);
1341 // call the custom business logic
1342 $custom_logic_arguments['check_notify'] = $check_notify;
1345 $this->call_custom_logic("before_save", $custom_logic_arguments);
1346 unset($custom_logic_arguments);
1348 if(isset($this->custom_fields))
1350 $this->custom_fields->bean = $this;
1351 $this->custom_fields->save($isUpdate);
1354 // use the db independent query generator
1355 $this->preprocess_fields_on_save();
1357 //construct the SQL to create the audit record if auditing is enabled.
1358 $dataChanges=array();
1359 if ($this->is_AuditEnabled()) {
1360 if ($isUpdate && !isset($this->fetched_row)) {
1361 $GLOBALS['log']->debug('Auditing: Retrieve was not called, audit record will not be created.');
1363 $dataChanges=$this->db->getDataChanges($this);
1367 $this->_sendNotifications($check_notify);
1370 $this->db->update($this);
1372 $this->db->insert($this);
1375 if (!empty($dataChanges) && is_array($dataChanges))
1377 foreach ($dataChanges as $change)
1379 $this->db->save_audit_records($this,$change);
1384 if (empty($GLOBALS['resavingRelatedBeans'])){
1385 SugarRelationship::resaveRelatedBeans();
1389 //If we aren't in setup mode and we have a current user and module, then we track
1390 if(isset($GLOBALS['current_user']) && isset($this->module_dir))
1392 $this->track_view($current_user->id, $this->module_dir, 'save');
1395 $this->call_custom_logic('after_save', '');
1397 //Now that the record has been saved, we don't want to insert again on further saves
1398 $this->new_with_id = false;
1399 $this->in_save = false;
1405 * Performs a check if the record has been modified since the specified date
1407 * @param date $date Datetime for verification
1408 * @param string $modified_user_id User modified by
1410 function has_been_modified_since($date, $modified_user_id)
1412 global $current_user;
1413 $date = $this->db->convert($this->db->quoted($date), 'datetime');
1414 if (isset($current_user))
1416 $query = "SELECT date_modified FROM $this->table_name WHERE id='$this->id' AND modified_user_id != '$current_user->id'
1417 AND (modified_user_id != '$modified_user_id' OR date_modified > $date)";
1418 $result = $this->db->query($query);
1420 if($this->db->fetchByAssoc($result))
1429 * Determines which users receive a notification
1431 function get_notification_recipients() {
1432 $notify_user = new User();
1433 $notify_user->retrieve($this->assigned_user_id);
1434 $this->new_assigned_user_name = $notify_user->full_name;
1436 $GLOBALS['log']->info("Notifications: recipient is $this->new_assigned_user_name");
1438 $user_list = array($notify_user);
1441 //send notifications to followers, but ensure to not query for the assigned_user.
1442 return SugarFollowing::getFollowers($this, $notify_user);
1447 * Handles sending out email notifications when items are first assigned to users
1449 * @param string $notify_user user to notify
1450 * @param string $admin the admin user that sends out the notification
1452 function send_assignment_notifications($notify_user, $admin)
1454 global $current_user;
1456 if(($this->object_name == 'Meeting' || $this->object_name == 'Call') || $notify_user->receive_notifications)
1458 $sendToEmail = $notify_user->emailAddress->getPrimaryAddress($notify_user);
1460 if(empty($sendToEmail)) {
1461 $GLOBALS['log']->warn("Notifications: no e-mail address set for user {$notify_user->user_name}, cancelling send");
1465 $notify_mail = $this->create_notification_email($notify_user);
1466 $notify_mail->setMailerForSystem();
1468 if(empty($admin->settings['notify_send_from_assigning_user'])) {
1469 $notify_mail->From = $admin->settings['notify_fromaddress'];
1470 $notify_mail->FromName = (empty($admin->settings['notify_fromname'])) ? "" : $admin->settings['notify_fromname'];
1472 // Send notifications from the current user's e-mail (if set)
1473 $fromAddress = $current_user->emailAddress->getReplyToAddress($current_user);
1474 $fromAddress = !empty($fromAddress) ? $fromAddress : $admin->settings['notify_fromaddress'];
1475 $notify_mail->From = $fromAddress;
1476 //Use the users full name is available otherwise default to system name
1477 $from_name = !empty($admin->settings['notify_fromname']) ? $admin->settings['notify_fromname'] : "";
1478 $from_name = !empty($current_user->full_name) ? $current_user->full_name : $from_name;
1479 $notify_mail->FromName = $from_name;
1482 $oe = new OutboundEmail();
1483 $oe = $oe->getUserMailerSettings($current_user);
1484 //only send if smtp server is defined
1486 $smtpVerified = false;
1488 //first check the user settings
1489 if(!empty($oe->mail_smtpserver)){
1490 $smtpVerified = true;
1493 //if still not verified, check against the system settings
1494 if (!$smtpVerified){
1495 $oe = $oe->getSystemMailerSettings();
1496 if(!empty($oe->mail_smtpserver)){
1497 $smtpVerified = true;
1500 //if smtp was not verified against user or system, then do not send out email
1501 if (!$smtpVerified){
1502 $GLOBALS['log']->fatal("Notifications: error sending e-mail, smtp server was not found ");
1507 if(!$notify_mail->Send()) {
1508 $GLOBALS['log']->fatal("Notifications: error sending e-mail (method: {$notify_mail->Mailer}), (error: {$notify_mail->ErrorInfo})");
1510 $GLOBALS['log']->info("Notifications: e-mail successfully sent");
1518 * This function handles create the email notifications email.
1519 * @param string $notify_user the user to send the notification email to
1521 function create_notification_email($notify_user) {
1522 global $sugar_version;
1523 global $sugar_config;
1524 global $app_list_strings;
1525 global $current_user;
1528 $OBCharset = $locale->getPrecedentPreference('default_email_charset');
1531 require_once("include/SugarPHPMailer.php");
1533 $notify_address = $notify_user->emailAddress->getPrimaryAddress($notify_user);
1534 $notify_name = $notify_user->full_name;
1535 $GLOBALS['log']->debug("Notifications: user has e-mail defined");
1537 $notify_mail = new SugarPHPMailer();
1538 $notify_mail->AddAddress($notify_address,$locale->translateCharsetMIME(trim($notify_name), 'UTF-8', $OBCharset));
1540 if(empty($_SESSION['authenticated_user_language'])) {
1541 $current_language = $sugar_config['default_language'];
1543 $current_language = $_SESSION['authenticated_user_language'];
1545 $xtpl = new XTemplate(get_notify_template_file($current_language));
1546 if($this->module_dir == "Cases") {
1547 $template_name = "Case"; //we should use Case, you can refer to the en_us.notify_template.html.
1550 $template_name = $beanList[$this->module_dir]; //bug 20637, in workflow this->object_name = strange chars.
1553 $this->current_notify_user = $notify_user;
1555 if(in_array('set_notification_body', get_class_methods($this))) {
1556 $xtpl = $this->set_notification_body($xtpl, $this);
1558 $xtpl->assign("OBJECT", translate('LBL_MODULE_NAME'));
1559 $template_name = "Default";
1561 if(!empty($_SESSION["special_notification"]) && $_SESSION["special_notification"]) {
1562 $template_name = $beanList[$this->module_dir].'Special';
1564 if($this->special_notification) {
1565 $template_name = $beanList[$this->module_dir].'Special';
1567 $xtpl->assign("ASSIGNED_USER", $this->new_assigned_user_name);
1568 $xtpl->assign("ASSIGNER", $current_user->name);
1571 if(isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) {
1572 $port = $_SERVER['SERVER_PORT'];
1575 if (!isset($_SERVER['HTTP_HOST'])) {
1576 $_SERVER['HTTP_HOST'] = '';
1579 $httpHost = $_SERVER['HTTP_HOST'];
1581 if($colon = strpos($httpHost, ':')) {
1582 $httpHost = substr($httpHost, 0, $colon);
1585 $parsedSiteUrl = parse_url($sugar_config['site_url']);
1586 $host = $parsedSiteUrl['host'];
1587 if(!isset($parsedSiteUrl['port'])) {
1588 $parsedSiteUrl['port'] = 80;
1591 $port = ($parsedSiteUrl['port'] != 80) ? ":".$parsedSiteUrl['port'] : '';
1592 $path = !empty($parsedSiteUrl['path']) ? $parsedSiteUrl['path'] : "";
1593 $cleanUrl = "{$parsedSiteUrl['scheme']}://{$host}{$port}{$path}";
1595 $xtpl->assign("URL", $cleanUrl."/index.php?module={$this->module_dir}&action=DetailView&record={$this->id}");
1596 $xtpl->assign("SUGAR", "Sugar v{$sugar_version}");
1597 $xtpl->parse($template_name);
1598 $xtpl->parse($template_name . "_Subject");
1600 $notify_mail->Body = from_html(trim($xtpl->text($template_name)));
1601 $notify_mail->Subject = from_html($xtpl->text($template_name . "_Subject"));
1603 // cn: bug 8568 encode notify email in User's outbound email encoding
1604 $notify_mail->prepForOutbound();
1606 return $notify_mail;
1610 * This function is a good location to save changes that have been made to a relationship.
1611 * This should be overriden in subclasses that have something to save.
1613 * @param $is_update true if this save is an update.
1615 function save_relationship_changes($is_update, $exclude=array())
1617 $new_rel_id = false;
1618 $new_rel_link = false;
1620 // check incoming data
1621 if(isset($this->not_use_rel_in_req) && $this->not_use_rel_in_req)
1623 // if we should use relation data from properties (for REQUEST-independent calls)
1624 $rel_id=isset($this->new_rel_id) ? $this->new_rel_id : '';
1625 $rel_link=isset($this->new_rel_relname) ? $this->new_rel_relname : '';
1629 // if we should use relation data from REQUEST
1630 $rel_id=isset($_REQUEST['relate_id']) ? $_REQUEST['relate_id'] : '';
1631 $rel_link=isset($_REQUEST['relate_to']) ? $_REQUEST['relate_to'] : '';
1634 // filter relation data
1635 if($rel_id && $rel_link && !in_array($rel_link, $exclude) && $rel_id != $this->id)
1637 $new_rel_id = $rel_id;
1638 $new_rel_link = $rel_link;
1639 //Try to find the link in this bean based on the relationship
1640 foreach ($this->field_defs as $key => $def)
1642 if (isset($def['type']) && $def['type'] == 'link' && isset($def['relationship']) && $def['relationship'] == $rel_link)
1644 $new_rel_link = $key;
1650 // First we handle the preset fields listed in the fixed relationship_fields array hardcoded into the OOB beans
1651 // TODO: remove this mechanism and replace with mechanism exclusively based on the vardefs
1652 if (isset($this->relationship_fields) && is_array($this->relationship_fields))
1654 foreach ($this->relationship_fields as $id=>$rel_name)
1657 if(in_array($id, $exclude))continue;
1659 if(!empty($this->$id))
1661 // Bug #44930 We do not need to update main related field if it is changed from sub-panel.
1662 if ($rel_name == $new_rel_link && $this->$id != $new_rel_id)
1666 $GLOBALS['log']->debug('save_relationship_changes(): From relationship_field array - adding a relationship record: '.$rel_name . ' = ' . $this->$id);
1667 //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
1668 if($this->$id == $new_rel_id){
1669 $new_rel_id = false;
1671 $this->load_relationship($rel_name);
1672 $this->$rel_name->add($this->$id);
1677 //if before value is not empty then attempt to delete relationship
1678 if(!empty($this->rel_fields_before_value[$id]))
1680 $GLOBALS['log']->debug('save_relationship_changes(): From relationship_field array - attempting to remove the relationship record, using relationship attribute'.$rel_name);
1681 $this->load_relationship($rel_name);
1682 $this->$rel_name->delete($this->id,$this->rel_fields_before_value[$id]);
1688 /* Next, we'll attempt to update all of the remaining relate fields in the vardefs that have 'save' set in their field_def
1689 Only the 'save' fields should be saved as some vardef entries today are not for display only purposes and break the application if saved
1690 If the vardef has entries for field <a> of type relate, where a->id_name = <b> and field <b> of type link
1691 then we receive a value for b from the MVC in the _REQUEST, and it should be set in the bean as $this->$b
1694 foreach ( $this->field_defs as $def )
1696 if ($def [ 'type' ] == 'relate' && isset ( $def [ 'id_name'] ) && isset ( $def [ 'link'] ) && isset ( $def[ 'save' ]) )
1698 if ( in_array( $def['id_name'], $exclude) || in_array( $def['id_name'], $this->relationship_fields ) )
1699 continue ; // continue to honor the exclude array and exclude any relationships that will be handled by the relationship_fields mechanism
1701 $linkField = $def [ 'link' ] ;
1702 if (isset( $this->field_defs[$linkField ] ))
1704 $linkfield = $this->field_defs[$linkField] ;
1706 if ($this->load_relationship ( $linkField))
1708 $idName = $def['id_name'];
1710 if (!empty($this->rel_fields_before_value[$idName]) && empty($this->$idName))
1712 //if before value is not empty then attempt to delete relationship
1713 $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' ]]}");
1714 $this->$def ['link' ]->delete($this->id, $this->rel_fields_before_value[$def [ 'id_name' ]] );
1717 if (!empty($this->$idName) && is_string($this->$idName))
1719 $GLOBALS['log']->debug("save_relationship_changes(): From field_defs - attempting to add a relationship record - {$def [ 'link' ]} = {$this->$def [ 'id_name' ]}" );
1721 $this->$linkField->add($this->$idName);
1724 $GLOBALS['log']->fatal("Failed to load relationship {$linkField} while saving {$this->module_dir}");
1730 // Finally, we update a field listed in the _REQUEST['*/relate_id']/_REQUEST['relate_to'] mechanism (if it hasn't already been updated above)
1731 if(!empty($new_rel_id)){
1733 if($this->load_relationship($new_rel_link)){
1734 $this->$new_rel_link->add($new_rel_id);
1737 $lower_link = strtolower($new_rel_link);
1738 if($this->load_relationship($lower_link)){
1739 $this->$lower_link->add($new_rel_id);
1742 require_once('data/Link2.php');
1743 $rel = Relationship::retrieve_by_modules($new_rel_link, $this->module_dir, $GLOBALS['db'], 'many-to-many');
1746 foreach($this->field_defs as $field=>$def){
1747 if($def['type'] == 'link' && !empty($def['relationship']) && $def['relationship'] == $rel){
1748 $this->load_relationship($field);
1749 $this->$field->add($new_rel_id);
1755 //ok so we didn't find it in the field defs let's save it anyway if we have the relationshp
1757 $this->$rel=new Link2($rel, $this, array());
1758 $this->$rel->add($new_rel_id);
1767 * This function retrieves a record of the appropriate type from the DB.
1768 * It fills in all of the fields from the DB into the object it was called on.
1770 * @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.
1771 * @return this - The object that it was called apon or null if exactly 1 record was not found.
1775 function check_date_relationships_load()
1777 global $disable_date_format;
1779 if (empty($timedate))
1780 $timedate=TimeDate::getInstance();
1782 if(empty($this->field_defs))
1786 foreach($this->field_defs as $fieldDef)
1788 $field = $fieldDef['name'];
1789 if(!isset($this->processed_dates_times[$field]))
1791 $this->processed_dates_times[$field] = '1';
1792 if(empty($this->$field)) continue;
1793 if($field == 'date_modified' || $field == 'date_entered')
1795 $this->$field = $this->db->fromConvert($this->$field, 'datetime');
1796 if(empty($disable_date_format)) {
1797 $this->$field = $timedate->to_display_date_time($this->$field);
1800 elseif(isset($this->field_name_map[$field]['type']))
1802 $type = $this->field_name_map[$field]['type'];
1804 if($type == 'relate' && isset($this->field_name_map[$field]['custom_module']))
1806 $type = $this->field_name_map[$field]['type'];
1811 if($this->$field == '0000-00-00')
1814 } elseif(!empty($this->field_name_map[$field]['rel_field']))
1816 $rel_field = $this->field_name_map[$field]['rel_field'];
1818 if(!empty($this->$rel_field))
1820 if(empty($disable_date_format)) {
1821 $mergetime = $timedate->merge_date_time($this->$field,$this->$rel_field);
1822 $this->$field = $timedate->to_display_date($mergetime);
1823 $this->$rel_field = $timedate->to_display_time($mergetime);
1829 if(empty($disable_date_format)) {
1830 $this->$field = $timedate->to_display_date($this->$field, false);
1833 } elseif($type == 'datetime' || $type == 'datetimecombo')
1835 if($this->$field == '0000-00-00 00:00:00')
1841 if(empty($disable_date_format)) {
1842 $this->$field = $timedate->to_display_date_time($this->$field, true, true);
1845 } elseif($type == 'time')
1847 if($this->$field == '00:00:00')
1852 //$this->$field = from_db_convert($this->$field, 'time');
1853 if(empty($this->field_name_map[$field]['rel_field']) && empty($disable_date_format))
1855 $this->$field = $timedate->to_display_time($this->$field,true, false);
1858 } elseif($type == 'encrypt' && empty($disable_date_format)){
1859 $this->$field = $this->decrypt_after_retrieve($this->$field);
1867 * This function processes the fields before save.
1868 * Interal function, do not override.
1870 function preprocess_fields_on_save()
1872 $GLOBALS['log']->deprecated('SugarBean.php: preprocess_fields_on_save() is deprecated');
1876 * Removes formatting from values posted from the user interface.
1877 * It only unformats numbers. Function relies on user/system prefernce for format strings.
1879 * Internal Function, do not override.
1881 function unformat_all_fields()
1883 $GLOBALS['log']->deprecated('SugarBean.php: unformat_all_fields() is deprecated');
1887 * This functions adds formatting to all number fields before presenting them to user interface.
1889 * Internal function, do not override.
1891 function format_all_fields()
1893 $GLOBALS['log']->deprecated('SugarBean.php: format_all_fields() is deprecated');
1896 function format_field($fieldDef)
1898 $GLOBALS['log']->deprecated('SugarBean.php: format_field() is deprecated');
1902 * Function corrects any bad formatting done by 3rd party/custom code
1904 * 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
1906 function fixUpFormatting()
1909 static $boolean_false_values = array('off', 'false', '0', 'no');
1912 foreach($this->field_defs as $field=>$def)
1914 if ( !isset($this->$field) ) {
1917 if ( (isset($def['source'])&&$def['source']=='non-db') || $field == 'deleted' ) {
1920 if ( isset($this->fetched_row[$field]) && $this->$field == $this->fetched_row[$field] ) {
1921 // Don't hand out warnings because the field was untouched between retrieval and saving, most database drivers hand pretty much everything back as strings.
1924 $reformatted = false;
1925 switch($def['type']) {
1927 case 'datetimecombo':
1928 if(empty($this->$field)) break;
1929 if ($this->$field == 'NULL') {
1933 if ( ! preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$/',$this->$field) ) {
1934 // This appears to be formatted in user date/time
1935 $this->$field = $timedate->to_db($this->$field);
1936 $reformatted = true;
1940 if(empty($this->$field)) break;
1941 if ($this->$field == 'NULL') {
1945 if ( ! preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/',$this->$field) ) {
1946 // This date appears to be formatted in the user's format
1947 $this->$field = $timedate->to_db_date($this->$field, false);
1948 $reformatted = true;
1952 if(empty($this->$field)) break;
1953 if ($this->$field == 'NULL') {
1957 if ( preg_match('/(am|pm)/i',$this->$field) ) {
1958 // This time appears to be formatted in the user's format
1959 $this->$field = $timedate->fromUserTime($this->$field)->format(TimeDate::DB_TIME_FORMAT);
1960 $reformatted = true;
1967 if ( $this->$field === '' || $this->$field == NULL || $this->$field == 'NULL') {
1970 if ( is_string($this->$field) ) {
1971 $this->$field = (float)unformat_number($this->$field);
1972 $reformatted = true;
1981 if ( $this->$field === '' || $this->$field == NULL || $this->$field == 'NULL') {
1984 if ( is_string($this->$field) ) {
1985 $this->$field = (int)unformat_number($this->$field);
1986 $reformatted = true;
1990 if (empty($this->$field)) {
1991 $this->$field = false;
1992 } else if(true === $this->$field || 1 == $this->$field) {
1993 $this->$field = true;
1994 } else if(in_array(strval($this->$field), $boolean_false_values)) {
1995 $this->$field = false;
1996 $reformatted = true;
1998 $this->$field = true;
1999 $reformatted = true;
2003 $this->$field = $this->encrpyt_before_save($this->$field);
2006 if ( $reformatted ) {
2007 $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');
2014 * Function fetches a single row of data given the primary key value.
2016 * The fetched data is then set into the bean. The function also processes the fetched data by formattig
2017 * date/time and numeric values.
2019 * @param string $id Optional, default -1, is set to -1 id value from the bean is used, else, passed value is used
2020 * @param boolean $encode Optional, default true, encodes the values fetched from the database.
2021 * @param boolean $deleted Optional, default true, if set to false deleted filter will not be added.
2023 * Internal function, do not override.
2025 function retrieve($id = -1, $encode=true,$deleted=true)
2028 $custom_logic_arguments['id'] = $id;
2029 $this->call_custom_logic('before_retrieve', $custom_logic_arguments);
2035 if(isset($this->custom_fields))
2037 $custom_join = $this->custom_fields->getJOIN();
2040 $custom_join = false;
2044 $query = "SELECT $this->table_name.*". $custom_join['select']. " FROM $this->table_name ";
2048 $query = "SELECT $this->table_name.* FROM $this->table_name ";
2053 $query .= ' ' . $custom_join['join'];
2055 $query .= " WHERE $this->table_name.id = ".$this->db->quoted($id);
2056 if ($deleted) $query .= " AND $this->table_name.deleted=0";
2057 $GLOBALS['log']->debug("Retrieve $this->object_name : ".$query);
2058 $result = $this->db->limitQuery($query,0,1,true, "Retrieving record by id $this->table_name:$id found ");
2064 $row = $this->db->fetchByAssoc($result, $encode);
2070 //make copy of the fetched row for construction of audit record and for business logic/workflow
2071 $row = $this->convertRow($row);
2072 $this->fetched_row=$row;
2073 $this->populateFromRow($row);
2075 global $module, $action;
2076 //Just to get optimistic locking working for this release
2077 if($this->optimistic_lock && $module == $this->module_dir && $action =='EditView' )
2079 $_SESSION['o_lock_id']= $id;
2080 $_SESSION['o_lock_dm']= $this->date_modified;
2081 $_SESSION['o_lock_on'] = $this->object_name;
2083 $this->processed_dates_times = array();
2084 $this->check_date_relationships_load();
2088 $this->custom_fields->fill_relationships();
2091 $this->fill_in_additional_detail_fields();
2092 $this->fill_in_relationship_fields();
2093 //make a copy of fields in the relationship_fields array. These field values will be used to
2094 //clear relationship.
2095 foreach ( $this->field_defs as $key => $def )
2097 if ($def [ 'type' ] == 'relate' && isset ( $def [ 'id_name'] ) && isset ( $def [ 'link'] ) && isset ( $def[ 'save' ])) {
2098 if (isset($this->$key)) {
2099 $this->rel_fields_before_value[$key]=$this->$key;
2100 if (isset($this->$def [ 'id_name']))
2101 $this->rel_fields_before_value[$def [ 'id_name']]=$this->$def [ 'id_name'];
2104 $this->rel_fields_before_value[$key]=null;
2107 if (isset($this->relationship_fields) && is_array($this->relationship_fields))
2109 foreach ($this->relationship_fields as $rel_id=>$rel_name)
2111 if (isset($this->$rel_id))
2112 $this->rel_fields_before_value[$rel_id]=$this->$rel_id;
2114 $this->rel_fields_before_value[$rel_id]=null;
2118 // call the custom business logic
2119 $custom_logic_arguments['id'] = $id;
2120 $custom_logic_arguments['encode'] = $encode;
2121 $this->call_custom_logic("after_retrieve", $custom_logic_arguments);
2122 unset($custom_logic_arguments);
2127 * Sets value from fetched row into the bean.
2129 * @param array $row Fetched row
2130 * @todo loop through vardefs instead
2131 * @internal runs into an issue when populating from field_defs for users - corrupts user prefs
2133 * Internal function, do not override.
2135 function populateFromRow($row)
2138 foreach($this->field_defs as $field=>$field_value)
2140 if($field == 'user_preferences' && $this->module_dir == 'Users')
2142 if(isset($row[$field]))
2144 $this->$field = $row[$field];
2145 $owner = $field . '_owner';
2146 if(!empty($row[$owner])){
2147 $this->$owner = $row[$owner];
2152 $this->$field = $nullvalue;
2160 * Add any required joins to the list count query. The joins are required if there
2161 * is a field in the $where clause that needs to be joined.
2163 * @param string $query
2164 * @param string $where
2166 * Internal Function, do Not override.
2168 function add_list_count_joins(&$query, $where)
2170 $custom_join = $this->custom_fields->getJOIN();
2173 $query .= $custom_join['join'];
2179 * Changes the select expression of the given query to be 'count(*)' so you
2180 * can get the number of items the query will return. This is used to
2181 * populate the upper limit on ListViews.
2183 * @param string $query Select query string
2184 * @return string count query
2186 * Internal function, do not override.
2188 function create_list_count_query($query)
2190 // remove the 'order by' clause which is expected to be at the end of the query
2191 $pattern = '/\sORDER BY.*/is'; // ignores the case
2193 $query = preg_replace($pattern, $replacement, $query);
2194 //handle distinct clause
2196 if(substr_count(strtolower($query), 'distinct')){
2197 if (!empty($this->seed) && !empty($this->seed->table_name ))
2198 $star = 'DISTINCT ' . $this->seed->table_name . '.id';
2200 $star = 'DISTINCT ' . $this->table_name . '.id';
2204 // change the select expression to 'count(*)'
2205 $pattern = '/SELECT(.*?)(\s){1}FROM(\s){1}/is'; // ignores the case
2206 $replacement = 'SELECT count(' . $star . ') c FROM ';
2208 //if the passed query has union clause then replace all instances of the pattern.
2209 //this is very rare. I have seen this happening only from projects module.
2210 //in addition to this added a condition that has union clause and uses
2212 if (strstr($query," UNION ALL ") !== false) {
2214 //separate out all the queries.
2215 $union_qs=explode(" UNION ALL ", $query);
2216 foreach ($union_qs as $key=>$union_query) {
2218 preg_match($pattern, $union_query, $matches);
2219 if (!empty($matches)) {
2220 if (stristr($matches[0], "distinct")) {
2221 if (!empty($this->seed) && !empty($this->seed->table_name ))
2222 $star = 'DISTINCT ' . $this->seed->table_name . '.id';
2224 $star = 'DISTINCT ' . $this->table_name . '.id';
2227 $replacement = 'SELECT count(' . $star . ') c FROM ';
2228 $union_qs[$key] = preg_replace($pattern, $replacement, $union_query,1);
2230 $modified_select_query=implode(" UNION ALL ",$union_qs);
2232 $modified_select_query = preg_replace($pattern, $replacement, $query,1);
2236 return $modified_select_query;
2240 * This function returns a paged list of the current object type. It is intended to allow for
2241 * hopping back and forth through pages of data. It only retrieves what is on the current page.
2243 * @internal This method must be called on a new instance. It trashes the values of all the fields in the current one.
2244 * @param string $order_by
2245 * @param string $where Additional where clause
2246 * @param int $row_offset Optaional,default 0, starting row number
2247 * @param init $limit Optional, default -1
2248 * @param int $max Optional, default -1
2249 * @param boolean $show_deleted Optional, default 0, if set to 1 system will show deleted records.
2250 * @return array Fetched data.
2252 * Internal function, do not override.
2255 function get_list($order_by = "", $where = "", $row_offset = 0, $limit=-1, $max=-1, $show_deleted = 0, $singleSelect=false, $select_fields = array())
2257 $GLOBALS['log']->debug("get_list: order_by = '$order_by' and where = '$where' and limit = '$limit'");
2258 if(isset($_SESSION['show_deleted']))
2262 $order_by=$this->process_order_by($order_by);
2264 if($this->bean_implements('ACL') && ACLController::requireOwner($this->module_dir, 'list') )
2266 global $current_user;
2267 $owner_where = $this->getOwnerWhere($current_user->id);
2269 //rrs - because $this->getOwnerWhere() can return '' we need to be sure to check for it and
2270 //handle it properly else you could get into a situation where you are create a where stmt like
2272 if(!empty($owner_where)){
2274 $where = $owner_where;
2276 $where .= ' AND '. $owner_where;
2280 $query = $this->create_new_list_query($order_by, $where,$select_fields,array(), $show_deleted,'',false,null,$singleSelect);
2281 return $this->process_list_query($query, $row_offset, $limit, $max, $where);
2285 * Prefixes column names with this bean's table name.
2287 * @param string $order_by Order by clause to be processed
2288 * @param SugarBean $submodule name of the module this order by clause is for
2289 * @return string Processed order by clause
2291 * Internal function, do not override.
2293 function process_order_by ($order_by, $submodule = null)
2295 if (empty($order_by))
2297 //submodule is empty,this is for list object in focus
2298 if (empty($submodule))
2300 $bean_queried = $this;
2304 //submodule is set, so this is for subpanel, use submodule
2305 $bean_queried = $submodule;
2307 $elements = explode(',',$order_by);
2308 foreach ($elements as $key=>$value)
2310 if (strchr($value,'.') === false)
2312 //value might have ascending and descending decorations
2313 $list_column = explode(' ',trim($value));
2314 if (isset($list_column[0]))
2316 $list_column_name=trim($list_column[0]);
2317 if (isset($bean_queried->field_defs[$list_column_name]))
2319 $source=isset($bean_queried->field_defs[$list_column_name]['source']) ? $bean_queried->field_defs[$list_column_name]['source']:'db';
2320 if (empty($bean_queried->field_defs[$list_column_name]['table']) && $source=='db')
2322 $list_column[0] = $bean_queried->table_name .".".$list_column[0] ;
2324 if (empty($bean_queried->field_defs[$list_column_name]['table']) && $source=='custom_fields')
2326 $list_column[0] = $bean_queried->table_name ."_cstm.".$list_column[0] ;
2328 // Bug 38803 - Use CONVERT() function when doing an order by on ntext, text, and image fields
2329 if ($source != 'non-db' && $this->db->isTextType($this->db->getFieldType($bean_queried->field_defs[$list_column_name]))) {
2330 $list_column[0] = $this->db->convert($list_column[0], "text2char");
2332 $value = implode(' ',$list_column);
2334 $GLOBALS['log']->debug("process_order_by: ($list_column[0]) does not have a vardef entry.");
2338 $elements[$key]=$value;
2340 return implode(',', $elements);
2346 * Returns a detail object like retrieving of the current object type.
2348 * It is intended for use in navigation buttons on the DetailView. It will pass an offset and limit argument to the sql query.
2349 * @internal This method must be called on a new instance. It overrides the values of all the fields in the current one.
2351 * @param string $order_by
2352 * @param string $where Additional where clause
2353 * @param int $row_offset Optaional,default 0, starting row number
2354 * @param init $limit Optional, default -1
2355 * @param int $max Optional, default -1
2356 * @param boolean $show_deleted Optioanl, default 0, if set to 1 system will show deleted records.
2357 * @return array Fetched data.
2359 * Internal function, do not override.
2361 function get_detail($order_by = "", $where = "", $offset = 0, $row_offset = 0, $limit=-1, $max=-1, $show_deleted = 0)
2363 $GLOBALS['log']->debug("get_detail: order_by = '$order_by' and where = '$where' and limit = '$limit' and offset = '$offset'");
2364 if(isset($_SESSION['show_deleted']))
2369 if($this->bean_implements('ACL') && ACLController::requireOwner($this->module_dir, 'list') )
2371 global $current_user;
2372 $owner_where = $this->getOwnerWhere($current_user->id);
2376 $where = $owner_where;
2380 $where .= ' AND '. $owner_where;
2383 $query = $this->create_new_list_query($order_by, $where,array(),array(), $show_deleted, $offset);
2385 //Add Limit and Offset to query
2386 //$query .= " LIMIT 1 OFFSET $offset";
2388 return $this->process_detail_query($query, $row_offset, $limit, $max, $where, $offset);
2392 * Fetches data from all related tables.
2394 * @param object $child_seed
2395 * @param string $related_field_name relation to fetch data for
2396 * @param string $order_by Optional, default empty
2397 * @param string $where Optional, additional where clause
2398 * @return array Fetched data.
2400 * Internal function, do not override.
2402 function get_related_list($child_seed,$related_field_name, $order_by = "", $where = "",
2403 $row_offset = 0, $limit=-1, $max=-1, $show_deleted = 0)
2405 global $layout_edit_mode;
2406 if(isset($layout_edit_mode) && $layout_edit_mode)
2408 $response = array();
2409 $child_seed->assign_display_fields($child_seed->module_dir);
2410 $response['list'] = array($child_seed);
2411 $response['row_count'] = 1;
2412 $response['next_offset'] = 0;
2413 $response['previous_offset'] = 0;
2417 $GLOBALS['log']->debug("get_related_list: order_by = '$order_by' and where = '$where' and limit = '$limit'");
2418 if(isset($_SESSION['show_deleted']))
2423 $this->load_relationship($related_field_name);
2424 $query_array = $this->$related_field_name->getQuery(true);
2425 $entire_where = $query_array['where'];
2428 if(empty($entire_where))
2430 $entire_where = ' WHERE ' . $where;
2434 $entire_where .= ' AND ' . $where;
2438 $query = 'SELECT '.$child_seed->table_name.'.* ' . $query_array['from'] . ' ' . $entire_where;
2439 if(!empty($order_by))
2441 $query .= " ORDER BY " . $order_by;
2444 return $child_seed->process_list_query($query, $row_offset, $limit, $max, $where);
2448 protected static function build_sub_queries_for_union($subpanel_list, $subpanel_def, $parentbean, $order_by)
2450 global $layout_edit_mode, $beanFiles, $beanList;
2451 $subqueries = array();
2452 foreach($subpanel_list as $this_subpanel)
2454 if(!$this_subpanel->isDatasourceFunction() || ($this_subpanel->isDatasourceFunction()
2455 && isset($this_subpanel->_instance_properties['generate_select'])
2456 && $this_subpanel->_instance_properties['generate_select']==true))
2458 //the custom query function must return an array with
2459 if ($this_subpanel->isDatasourceFunction()) {
2460 $shortcut_function_name = $this_subpanel->get_data_source_name();
2461 $parameters=$this_subpanel->get_function_parameters();
2462 if (!empty($parameters))
2464 //if the import file function is set, then import the file to call the custom function from
2465 if (is_array($parameters) && isset($parameters['import_function_file'])){
2466 //this call may happen multiple times, so only require if function does not exist
2467 if(!function_exists($shortcut_function_name)){
2468 require_once($parameters['import_function_file']);
2470 //call function from required file
2471 $query_array = $shortcut_function_name($parameters);
2473 //call function from parent bean
2474 $query_array = $parentbean->$shortcut_function_name($parameters);
2479 $query_array = $parentbean->$shortcut_function_name();
2482 $related_field_name = $this_subpanel->get_data_source_name();
2483 if (!$parentbean->load_relationship($related_field_name)){
2484 unset ($parentbean->$related_field_name);
2487 $query_array = $parentbean->$related_field_name->getSubpanelQuery(array(), true);
2489 $table_where = $this_subpanel->get_where();
2490 $where_definition = $query_array['where'];
2492 if(!empty($table_where))
2494 if(empty($where_definition))
2496 $where_definition = $table_where;
2500 $where_definition .= ' AND ' . $table_where;
2504 $submodulename = $this_subpanel->_instance_properties['module'];
2505 $submoduleclass = $beanList[$submodulename];
2506 //require_once($beanFiles[$submoduleclass]);
2507 $submodule = new $submoduleclass();
2508 $subwhere = $where_definition;
2512 $subwhere = str_replace('WHERE', '', $subwhere);
2513 $list_fields = $this_subpanel->get_list_fields();
2514 foreach($list_fields as $list_key=>$list_field)
2516 if(isset($list_field['usage']) && $list_field['usage'] == 'display_only')
2518 unset($list_fields[$list_key]);
2523 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'))
2525 $order_by = $submodule->table_name .'.'. $order_by;
2527 $table_name = $this_subpanel->table_name;
2528 $panel_name=$this_subpanel->name;
2530 $params['distinct'] = $this_subpanel->distinct_query();
2532 $params['joined_tables'] = $query_array['join_tables'];
2533 $params['include_custom_fields'] = !$subpanel_def->isCollection();
2534 $params['collection_list'] = $subpanel_def->get_inst_prop_value('collection_list');
2536 $subquery = $submodule->create_new_list_query('',$subwhere ,$list_fields,$params, 0,'', true,$parentbean);
2538 $subquery['select'] = $subquery['select']." , '$panel_name' panel_name ";
2539 $subquery['from'] = $subquery['from'].$query_array['join'];
2540 $subquery['query_array'] = $query_array;
2541 $subquery['params'] = $params;
2543 $subqueries[] = $subquery;
2550 * Constructs a query to fetch data for supanels and list views
2552 * It constructs union queries for activities subpanel.
2554 * @param SugarBean $parentbean constructing queries for link attributes in this bean
2555 * @param string $order_by Optional, order by clause
2556 * @param string $sort_order Optional, sort order
2557 * @param string $where Optional, additional where clause
2559 * Internal Function, do not overide.
2561 function get_union_related_list($parentbean, $order_by = "", $sort_order='', $where = "",
2562 $row_offset = 0, $limit=-1, $max=-1, $show_deleted = 0, $subpanel_def)
2564 $secondary_queries = array();
2565 global $layout_edit_mode, $beanFiles, $beanList;
2567 if(isset($_SESSION['show_deleted']))
2572 $final_query_rows = '';
2573 $subpanel_list=array();
2574 if ($subpanel_def->isCollection())
2576 $subpanel_def->load_sub_subpanels();
2577 $subpanel_list=$subpanel_def->sub_subpanels;
2581 $subpanel_list[]=$subpanel_def;
2586 //Breaking the building process into two loops. The first loop gets a list of all the sub-queries.
2587 //The second loop merges the queries and forces them to select the same number of columns
2588 //All columns in a sub-subpanel group must have the same aliases
2589 //If the subpanel is a datasource function, it can't be a collection so we just poll that function for the and return that
2590 foreach($subpanel_list as $this_subpanel)
2592 if($this_subpanel->isDatasourceFunction() && empty($this_subpanel->_instance_properties['generate_select']))
2594 $shortcut_function_name = $this_subpanel->get_data_source_name();
2595 $parameters=$this_subpanel->get_function_parameters();
2596 if (!empty($parameters))
2598 //if the import file function is set, then import the file to call the custom function from
2599 if (is_array($parameters) && isset($parameters['import_function_file'])){
2600 //this call may happen multiple times, so only require if function does not exist
2601 if(!function_exists($shortcut_function_name)){
2602 require_once($parameters['import_function_file']);
2604 //call function from required file
2605 $tmp_final_query = $shortcut_function_name($parameters);
2607 //call function from parent bean
2608 $tmp_final_query = $parentbean->$shortcut_function_name($parameters);
2611 $tmp_final_query = $parentbean->$shortcut_function_name();
2615 $final_query_rows .= ' UNION ALL ( '.$parentbean->create_list_count_query($tmp_final_query, $parameters) . ' )';
2616 $final_query .= ' UNION ALL ( '.$tmp_final_query . ' )';
2618 $final_query_rows = '(' . $parentbean->create_list_count_query($tmp_final_query, $parameters) . ')';
2619 $final_query = '(' . $tmp_final_query . ')';
2624 //If final_query is still empty, its time to build the sub-queries
2625 if (empty($final_query))
2627 $subqueries = SugarBean::build_sub_queries_for_union($subpanel_list, $subpanel_def, $parentbean, $order_by);
2628 $all_fields = array();
2629 foreach($subqueries as $i => $subquery)
2631 $query_fields = $GLOBALS['db']->getSelectFieldsFromQuery($subquery['select']);
2632 foreach($query_fields as $field => $select)
2634 if (!in_array($field, $all_fields))
2635 $all_fields[] = $field;
2637 $subqueries[$i]['query_fields'] = $query_fields;
2640 //Now ensure the queries have the same set of fields in the same order.
2641 foreach($subqueries as $subquery)
2643 $subquery['select'] = "SELECT";
2644 foreach($all_fields as $field)
2646 if (!isset($subquery['query_fields'][$field]))
2648 $subquery['select'] .= " ' ' $field,";
2652 $subquery['select'] .= " {$subquery['query_fields'][$field]},";
2655 $subquery['select'] = substr($subquery['select'], 0 , strlen($subquery['select']) - 1);
2656 //Put the query into the final_query
2657 $query = $subquery['select'] . " " . $subquery['from'] . " " . $subquery['where'];
2660 $query = ' UNION ALL ( '.$query . ' )';
2661 $final_query_rows .= " UNION ALL ";
2663 $query = '(' . $query . ')';
2666 $query_array = $subquery['query_array'];
2667 $select_position=strpos($query_array['select'],"SELECT");
2668 $distinct_position=strpos($query_array['select'],"DISTINCT");
2669 if ($select_position !== false && $distinct_position!= false)
2671 $query_rows = "( ".substr_replace($query_array['select'],"SELECT count(",$select_position,6). ")" . $subquery['from_min'].$query_array['join']. $subquery['where'].' )';
2675 //resort to default behavior.
2676 $query_rows = "( SELECT count(*)". $subquery['from_min'].$query_array['join']. $subquery['where'].' )';
2678 if(!empty($subquery['secondary_select']))
2681 $subquerystring= $subquery['secondary_select'] . $subquery['secondary_from'].$query_array['join']. $subquery['where'];
2682 if (!empty($subquery['secondary_where']))
2684 if (empty($subquery['where']))
2686 $subquerystring.=" WHERE " .$subquery['secondary_where'];
2690 $subquerystring.=" AND " .$subquery['secondary_where'];
2693 $secondary_queries[]=$subquerystring;
2695 $final_query .= $query;
2696 $final_query_rows .= $query_rows;
2700 if(!empty($order_by))
2703 if(!$subpanel_def->isCollection())
2705 $submodulename = $subpanel_def->_instance_properties['module'];
2706 $submoduleclass = $beanList[$submodulename];
2707 $submodule = new $submoduleclass();
2709 if(!empty($submodule) && !empty($submodule->table_name))
2711 $final_query .= " ORDER BY " .$parentbean->process_order_by($order_by, $submodule);
2716 $final_query .= " ORDER BY ". $order_by . ' ';
2718 if(!empty($sort_order))
2720 $final_query .= ' ' .$sort_order;
2725 if(isset($layout_edit_mode) && $layout_edit_mode)
2727 $response = array();
2728 if(!empty($submodule))
2730 $submodule->assign_display_fields($submodule->module_dir);
2731 $response['list'] = array($submodule);
2735 $response['list'] = array();
2737 $response['parent_data'] = array();
2738 $response['row_count'] = 1;
2739 $response['next_offset'] = 0;
2740 $response['previous_offset'] = 0;
2745 return $parentbean->process_union_list_query($parentbean, $final_query, $row_offset, $limit, $max, '',$subpanel_def, $final_query_rows, $secondary_queries);
2750 * Returns a full (ie non-paged) list of the current object type.
2752 * @param string $order_by the order by SQL parameter. defaults to ""
2753 * @param string $where where clause. defaults to ""
2754 * @param boolean $check_dates. defaults to false
2755 * @param int $show_deleted show deleted records. defaults to 0
2757 function get_full_list($order_by = "", $where = "", $check_dates=false, $show_deleted = 0)
2759 $GLOBALS['log']->debug("get_full_list: order_by = '$order_by' and where = '$where'");
2760 if(isset($_SESSION['show_deleted']))
2764 $query = $this->create_new_list_query($order_by, $where,array(),array(), $show_deleted);
2765 return $this->process_full_list_query($query, $check_dates);
2769 * Return the list query used by the list views and export button. Next generation of create_new_list_query function.
2771 * Override this function to return a custom query.
2773 * @param string $order_by custom order by clause
2774 * @param string $where custom where clause
2775 * @param array $filter Optioanal
2776 * @param array $params Optional *
2777 * @param int $show_deleted Optional, default 0, show deleted records is set to 1.
2778 * @param string $join_type
2779 * @param boolean $return_array Optional, default false, response as array
2780 * @param object $parentbean creating a subquery for this bean.
2781 * @param boolean $singleSelect Optional, default false.
2782 * @return String select query string, optionally an array value will be returned if $return_array= true.
2784 function create_new_list_query($order_by, $where,$filter=array(),$params=array(), $show_deleted = 0,$join_type='', $return_array = false,$parentbean=null, $singleSelect = false, $ifListForExport = false)
2786 global $beanFiles, $beanList;
2787 $selectedFields = array();
2788 $secondarySelectedFields = array();
2789 $ret_array = array();
2791 if($this->bean_implements('ACL') && ACLController::requireOwner($this->module_dir, 'list') )
2793 global $current_user;
2794 $owner_where = $this->getOwnerWhere($current_user->id);
2797 $where = $owner_where;
2801 $where .= ' AND '. $owner_where;
2804 if(!empty($params['distinct']))
2806 $distinct = ' DISTINCT ';
2810 $ret_array['select'] = " SELECT $distinct $this->table_name.* ";
2814 $ret_array['select'] = " SELECT $distinct $this->table_name.id ";
2816 $ret_array['from'] = " FROM $this->table_name ";
2817 $ret_array['from_min'] = $ret_array['from'];
2818 $ret_array['secondary_from'] = $ret_array['from'] ;
2819 $ret_array['where'] = '';
2820 $ret_array['order_by'] = '';
2821 //secondary selects are selects that need to be run after the primary query to retrieve additional info on main
2824 $ret_array['secondary_select']=& $ret_array['select'];
2825 $ret_array['secondary_from'] = & $ret_array['from'];
2829 $ret_array['secondary_select'] = '';
2831 $custom_join = false;
2832 if((!isset($params['include_custom_fields']) || $params['include_custom_fields']) && isset($this->custom_fields))
2835 $custom_join = $this->custom_fields->getJOIN( empty($filter)? true: $filter );
2838 $ret_array['select'] .= ' ' .$custom_join['select'];
2843 $ret_array['from'] .= ' ' . $custom_join['join'];
2846 //LOOP AROUND FOR FIXIN VARDEF ISSUES
2847 require('include/VarDefHandler/listvardefoverride.php');
2848 if (file_exists('custom/include/VarDefHandler/listvardefoverride.php'))
2850 require('custom/include/VarDefHandler/listvardefoverride.php');
2853 $joined_tables = array();
2854 if(!empty($params['joined_tables']))
2856 foreach($params['joined_tables'] as $table)
2858 $joined_tables[$table] = 1;
2864 $filterKeys = array_keys($filter);
2865 if(is_numeric($filterKeys[0]))
2868 foreach($filter as $field)
2870 $field = strtolower($field);
2871 //remove out id field so we don't duplicate it
2872 if ( $field == 'id' && !empty($filter) ) {
2875 if(isset($this->field_defs[$field]))
2877 $fields[$field]= $this->field_defs[$field];
2881 $fields[$field] = array('force_exists'=>true);
2890 $fields = $this->field_defs;
2893 $used_join_key = array();
2895 foreach($fields as $field=>$value)
2897 //alias is used to alias field names
2899 if (isset($value['alias']))
2901 $alias =' as ' . $value['alias'] . ' ';
2904 if(empty($this->field_defs[$field]) || !empty($value['force_blank']) )
2906 if(!empty($filter) && isset($filter[$field]['force_exists']) && $filter[$field]['force_exists'])
2908 if ( isset($filter[$field]['force_default']) )
2909 $ret_array['select'] .= ", {$filter[$field]['force_default']} $field ";
2911 //spaces are a fix for length issue problem with unions. The union only returns the maximum number of characters from the first select statement.
2912 $ret_array['select'] .= ", ' ' $field ";
2918 $data = $this->field_defs[$field];
2921 //ignore fields that are a part of the collection and a field has been removed as a result of
2922 //layout customization.. this happens in subpanel customizations, use case, from the contacts subpanel
2923 //in opportunities module remove the contact_role/opportunity_role field.
2924 $process_field=true;
2925 if (isset($data['relationship_fields']) and !empty($data['relationship_fields']))
2927 foreach ($data['relationship_fields'] as $field_name)
2929 if (!isset($fields[$field_name]))
2931 $process_field=false;
2935 if (!$process_field)
2940 if( (!isset($data['source']) || $data['source'] == 'db') && (!empty($alias) || !empty($filter) ))
2942 $ret_array['select'] .= ", $this->table_name.$field $alias";
2943 $selectedFields["$this->table_name.$field"] = true;
2944 } else if( (!isset($data['source']) || $data['source'] == 'custom_fields') && (!empty($alias) || !empty($filter) )) {
2945 $ret_array['select'] .= ", $this->table_name"."_cstm".".$field $alias";
2946 $selectedFields["$this->table_name.$field"] = true;
2951 if($data['type'] != 'relate' && isset($data['db_concat_fields']))
2953 $ret_array['select'] .= ", " . $this->db->concat($this->table_name, $data['db_concat_fields']) . " as $field";
2954 $selectedFields[$this->db->concat($this->table_name, $data['db_concat_fields'])] = true;
2956 //Custom relate field or relate fields built in module builder which have no link field associated.
2957 if ($data['type'] == 'relate' && (isset($data['custom_module']) || isset($data['ext2']))) {
2958 $joinTableAlias = 'jt' . $jtcount;
2959 $relateJoinInfo = $this->custom_fields->getRelateJoin($data, $joinTableAlias);
2960 $ret_array['select'] .= $relateJoinInfo['select'];
2961 $ret_array['from'] .= $relateJoinInfo['from'];
2962 //Replace any references to the relationship in the where clause with the new alias
2963 //If the link isn't set, assume that search used the local table for the field
2964 $searchTable = isset($data['link']) ? $relateJoinInfo['rel_table'] : $this->table_name;
2965 $field_name = $relateJoinInfo['rel_table'] . '.' . !empty($data['name'])?$data['name']:'name';
2966 $where = preg_replace('/(^|[\s(])' . $field_name . '/' , '${1}' . $relateJoinInfo['name_field'], $where);
2970 if ($data['type'] == 'parent') {
2971 //See if we need to join anything by inspecting the where clause
2972 $match = preg_match('/(^|[\s(])parent_(\w+)_(\w+)\.name/', $where, $matches);
2974 $joinTableAlias = 'jt' . $jtcount;
2975 $joinModule = $matches[2];
2976 $joinTable = $matches[3];
2977 $localTable = $this->table_name;
2978 if (!empty($data['custom_module'])) {
2979 $localTable .= '_cstm';
2981 global $beanFiles, $beanList, $module;
2982 require_once($beanFiles[$beanList[$joinModule]]);
2983 $rel_mod = new $beanList[$joinModule]();
2984 $nameField = "$joinTableAlias.name";
2985 if (isset($rel_mod->field_defs['name']))
2987 $name_field_def = $rel_mod->field_defs['name'];
2988 if(isset($name_field_def['db_concat_fields']))
2990 $nameField = $this->db->concat($joinTableAlias, $name_field_def['db_concat_fields']);
2993 $ret_array['select'] .= ", $nameField {$data['name']} ";
2994 $ret_array['from'] .= " LEFT JOIN $joinTable $joinTableAlias
2995 ON $localTable.{$data['id_name']} = $joinTableAlias.id";
2996 //Replace any references to the relationship in the where clause with the new alias
2997 $where = preg_replace('/(^|[\s(])parent_' . $joinModule . '_' . $joinTable . '\.name/', '${1}' . $nameField, $where);
3001 if($data['type'] == 'relate' && isset($data['link']))
3003 $this->load_relationship($data['link']);
3004 if(!empty($this->$data['link']))
3007 if(empty($join_type))
3009 $params['join_type'] = ' LEFT JOIN ';
3013 $params['join_type'] = $join_type;
3015 if(isset($data['join_name']))
3017 $params['join_table_alias'] = $data['join_name'];
3021 $params['join_table_alias'] = 'jt' . $jtcount;
3024 if(isset($data['join_link_name']))
3026 $params['join_table_link_alias'] = $data['join_link_name'];
3030 $params['join_table_link_alias'] = 'jtl' . $jtcount;
3032 $join_primary = !isset($data['join_primary']) || $data['join_primary'];
3034 $join = $this->$data['link']->getJoin($params, true);
3035 $used_join_key[] = $join['rel_key'];
3036 $rel_module = $this->$data['link']->getRelatedModuleName();
3037 $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');
3039 //if rname is set to 'name', and bean files exist, then check if field should be a concatenated name
3040 global $beanFiles, $beanList;
3041 if($data['rname'] && !empty($beanFiles[$beanList[$rel_module]])) {
3043 //create an instance of the related bean
3044 require_once($beanFiles[$beanList[$rel_module]]);
3045 $rel_mod = new $beanList[$rel_module]();
3046 //if bean has first and last name fields, then name should be concatenated
3047 if(isset($rel_mod->field_name_map['first_name']) && isset($rel_mod->field_name_map['last_name'])){
3048 $data['db_concat_fields'] = array(0=>'first_name', 1=>'last_name');
3053 if($join['type'] == 'many-to-many')
3055 if(empty($ret_array['secondary_select']))
3057 $ret_array['secondary_select'] = " SELECT $this->table_name.id ref_id ";
3059 if(!empty($beanFiles[$beanList[$rel_module]]) && $join_primary)
3061 require_once($beanFiles[$beanList[$rel_module]]);
3062 $rel_mod = new $beanList[$rel_module]();
3063 if(isset($rel_mod->field_defs['assigned_user_id']))
3065 $ret_array['secondary_select'].= " , ". $params['join_table_alias'] . ".assigned_user_id {$field}_owner, '$rel_module' {$field}_mod";
3069 if(isset($rel_mod->field_defs['created_by']))
3071 $ret_array['secondary_select'].= " , ". $params['join_table_alias'] . ".created_by {$field}_owner , '$rel_module' {$field}_mod";
3077 if(isset($data['db_concat_fields']))
3079 $ret_array['secondary_select'] .= ' , ' . $this->db->concat($params['join_table_alias'], $data['db_concat_fields']) . ' ' . $field;
3083 if(!isset($data['relationship_fields']))
3085 $ret_array['secondary_select'] .= ' , ' . $params['join_table_alias'] . '.' . $data['rname'] . ' ' . $field;
3090 $ret_array['select'] .= ", ' ' $field ";
3093 foreach($used_join_key as $used_key) {
3094 if($used_key == $join['rel_key']) $count_used++;
3096 if($count_used <= 1) {//27416, the $ret_array['secondary_select'] should always generate, regardless the dbtype
3097 // add rel_key only if it was not aready added
3100 $ret_array['select'] .= ", ' ' " . $join['rel_key'] . ' ';
3102 $ret_array['secondary_select'] .= ', ' . $params['join_table_link_alias'].'.'. $join['rel_key'] .' ' . $join['rel_key'];
3104 if(isset($data['relationship_fields']))
3106 foreach($data['relationship_fields'] as $r_name=>$alias_name)
3108 if(!empty( $secondarySelectedFields[$alias_name]))continue;
3109 $ret_array['secondary_select'] .= ', ' . $params['join_table_link_alias'].'.'. $r_name .' ' . $alias_name;
3110 $secondarySelectedFields[$alias_name] = true;
3115 $ret_array['secondary_from'] .= ' ' . $join['join']. ' AND ' . $params['join_table_alias'].'.deleted=0';
3116 if (isset($data['link_type']) && $data['link_type'] == 'relationship_info' && ($parentbean instanceOf SugarBean))
3118 $ret_array['secondary_where'] = $params['join_table_link_alias'] . '.' . $join['rel_key']. "='" .$parentbean->id . "'";
3124 if(isset($data['db_concat_fields']))
3126 $ret_array['select'] .= ' , ' . $this->db->concat($params['join_table_alias'], $data['db_concat_fields']) . ' ' . $field;
3130 $ret_array['select'] .= ' , ' . $params['join_table_alias'] . '.' . $data['rname'] . ' ' . $field;
3132 if(isset($data['additionalFields'])){
3133 foreach($data['additionalFields'] as $k=>$v){
3134 $ret_array['select'] .= ' , ' . $params['join_table_alias'] . '.' . $k . ' ' . $v;
3139 $ret_array['from'] .= ' ' . $join['join']. ' AND ' . $params['join_table_alias'].'.deleted=0';
3140 if(!empty($beanList[$rel_module]) && !empty($beanFiles[$beanList[$rel_module]]))
3142 require_once($beanFiles[$beanList[$rel_module]]);
3143 $rel_mod = new $beanList[$rel_module]();
3144 if(isset($value['target_record_key']) && !empty($filter))
3146 $selectedFields[$this->table_name.'.'.$value['target_record_key']] = true;
3147 $ret_array['select'] .= " , $this->table_name.{$value['target_record_key']} ";
3149 if(isset($rel_mod->field_defs['assigned_user_id']))
3151 $ret_array['select'] .= ' , ' .$params['join_table_alias'] . '.assigned_user_id ' . $field . '_owner';
3155 $ret_array['select'] .= ' , ' .$params['join_table_alias'] . '.created_by ' . $field . '_owner';
3157 $ret_array['select'] .= " , '".$rel_module ."' " . $field . '_mod';
3162 // To fix SOAP stuff where we are trying to retrieve all the accounts data where accounts.id = ..
3163 // and this code changes accounts to jt4 as there is a self join with the accounts table.
3165 if(isset($data['db_concat_fields'])){
3166 $buildWhere = false;
3167 if(in_array('first_name', $data['db_concat_fields']) && in_array('last_name', $data['db_concat_fields']))
3169 $exp = '/\(\s*?'.$data['name'].'.*?\%\'\s*?\)/';
3170 if(preg_match($exp, $where, $matches))
3172 $search_expression = $matches[0];
3173 //Create three search conditions - first + last, first, last
3174 $first_name_search = str_replace($data['name'], $params['join_table_alias'] . '.first_name', $search_expression);
3175 $last_name_search = str_replace($data['name'], $params['join_table_alias'] . '.last_name', $search_expression);
3176 $full_name_search = str_replace($data['name'], $this->db->concat($params['join_table_alias'], $data['db_concat_fields']), $search_expression);
3178 $where = str_replace($search_expression, '(' . $full_name_search . ' OR ' . $first_name_search . ' OR ' . $last_name_search . ')', $where);
3184 $db_field = $this->db->concat($params['join_table_alias'], $data['db_concat_fields']);
3185 $where = preg_replace('/'.$data['name'].'/', $db_field, $where);
3188 $where = preg_replace('/(^|[\s(])' . $data['name'] . '/', '${1}' . $params['join_table_alias'] . '.'.$data['rname'], $where);
3192 $joined_tables[$params['join_table_alias']]=1;
3193 $joined_tables[$params['join_table_link_alias']]=1;
3202 if(isset($this->field_defs['assigned_user_id']) && empty($selectedFields[$this->table_name.'.assigned_user_id']))
3204 $ret_array['select'] .= ", $this->table_name.assigned_user_id ";
3206 else if(isset($this->field_defs['created_by']) && empty($selectedFields[$this->table_name.'.created_by']))
3208 $ret_array['select'] .= ", $this->table_name.created_by ";
3210 if(isset($this->field_defs['system_id']) && empty($selectedFields[$this->table_name.'.system_id']))
3212 $ret_array['select'] .= ", $this->table_name.system_id ";
3217 if ($ifListForExport) {
3218 if(isset($this->field_defs['email1'])) {
3219 $ret_array['select'].= " ,email_addresses.email_address email1";
3220 $ret_array['from'].= " LEFT JOIN email_addr_bean_rel on {$this->table_name}.id = email_addr_bean_rel.bean_id and email_addr_bean_rel.bean_module='{$this->module_dir}' and email_addr_bean_rel.deleted=0 and email_addr_bean_rel.primary_address=1 LEFT JOIN email_addresses on email_addresses.id = email_addr_bean_rel.email_address_id ";
3224 $where_auto = '1=1';
3225 if($show_deleted == 0)
3227 $where_auto = "$this->table_name.deleted=0";
3228 }else if($show_deleted == 1)
3230 $where_auto = "$this->table_name.deleted=1";
3233 $ret_array['where'] = " where ($where) AND $where_auto";
3235 $ret_array['where'] = " where $where_auto";
3236 if(!empty($order_by))
3238 //make call to process the order by clause
3239 $ret_array['order_by'] = " ORDER BY ". $this->process_order_by($order_by);
3243 unset($ret_array['secondary_where']);
3244 unset($ret_array['secondary_from']);
3245 unset($ret_array['secondary_select']);
3253 return $ret_array['select'] . $ret_array['from'] . $ret_array['where']. $ret_array['order_by'];
3256 * Returns parent record data for objects that store relationship information
3258 * @param array $type_info
3260 * Interal function, do not override.
3262 function retrieve_parent_fields($type_info)
3265 global $beanList, $beanFiles;
3266 $templates = array();
3267 $parent_child_map = array();
3268 foreach($type_info as $children_info)
3270 foreach($children_info as $child_info)
3272 if($child_info['type'] == 'parent')
3274 if(empty($templates[$child_info['parent_type']]))
3276 //Test emails will have an invalid parent_type, don't try to load the non-existent parent bean
3277 if ($child_info['parent_type'] == 'test') {
3280 $class = $beanList[$child_info['parent_type']];
3281 // Added to avoid error below; just silently fail and write message to log
3282 if ( empty($beanFiles[$class]) ) {
3283 $GLOBALS['log']->error($this->object_name.'::retrieve_parent_fields() - cannot load class "'.$class.'", skip loading.');
3286 require_once($beanFiles[$class]);
3287 $templates[$child_info['parent_type']] = new $class();
3290 if(empty($queries[$child_info['parent_type']]))
3292 $queries[$child_info['parent_type']] = "SELECT id ";
3293 $field_def = $templates[$child_info['parent_type']]->field_defs['name'];
3294 if(isset($field_def['db_concat_fields']))
3296 $queries[$child_info['parent_type']] .= ' , ' . $this->db->concat($templates[$child_info['parent_type']]->table_name, $field_def['db_concat_fields']) . ' parent_name';
3300 $queries[$child_info['parent_type']] .= ' , name parent_name';
3302 if(isset($templates[$child_info['parent_type']]->field_defs['assigned_user_id']))
3304 $queries[$child_info['parent_type']] .= ", assigned_user_id parent_name_owner , '{$child_info['parent_type']}' parent_name_mod";;
3305 }else if(isset($templates[$child_info['parent_type']]->field_defs['created_by']))
3307 $queries[$child_info['parent_type']] .= ", created_by parent_name_owner, '{$child_info['parent_type']}' parent_name_mod";
3309 $queries[$child_info['parent_type']] .= " FROM " . $templates[$child_info['parent_type']]->table_name ." WHERE id IN ('{$child_info['parent_id']}'";
3313 if(empty($parent_child_map[$child_info['parent_id']]))
3314 $queries[$child_info['parent_type']] .= " ,'{$child_info['parent_id']}'";
3316 $parent_child_map[$child_info['parent_id']][] = $child_info['child_id'];
3321 foreach($queries as $query)
3323 $result = $this->db->query($query . ')');
3324 while($row = $this->db->fetchByAssoc($result))
3326 $results[$row['id']] = $row;
3330 $child_results = array();
3331 foreach($parent_child_map as $parent_key=>$parent_child)
3333 foreach($parent_child as $child)
3335 if(isset( $results[$parent_key]))
3337 $child_results[$child] = $results[$parent_key];
3341 return $child_results;
3345 * Processes the list query and return fetched row.
3347 * Internal function, do not override.
3348 * @param string $query select query to be processed.
3349 * @param int $row_offset starting position
3350 * @param int $limit Optioanl, default -1
3351 * @param int $max_per_page Optional, default -1
3352 * @param string $where Optional, additional filter criteria.
3353 * @return array Fetched data
3355 function process_list_query($query, $row_offset, $limit= -1, $max_per_page = -1, $where = '')
3357 global $sugar_config;
3358 $db = DBManagerFactory::getInstance('listviews');
3360 * if the row_offset is set to 'end' go to the end of the list
3362 $toEnd = strval($row_offset) == 'end';
3363 $GLOBALS['log']->debug("process_list_query: ".$query);
3364 if($max_per_page == -1)
3366 $max_per_page = $sugar_config['list_max_entries_per_page'];
3368 // Check to see if we have a count query available.
3369 if(empty($sugar_config['disable_count_query']) || $toEnd)
3371 $count_query = $this->create_list_count_query($query);
3372 if(!empty($count_query) && (empty($limit) || $limit == -1))
3374 // We have a count query. Run it and get the results.
3375 $result = $db->query($count_query, true, "Error running count query for $this->object_name List: ");
3376 $assoc = $db->fetchByAssoc($result);
3377 if(!empty($assoc['c']))
3379 $rows_found = $assoc['c'];
3380 $limit = $sugar_config['list_max_entries_per_page'];
3384 $row_offset = (floor(($rows_found -1) / $limit)) * $limit;
3390 if((empty($limit) || $limit == -1))
3392 $limit = $max_per_page + 1;
3393 $max_per_page = $limit;
3398 if(empty($row_offset))
3402 if(!empty($limit) && $limit != -1 && $limit != -99)
3404 $result = $db->limitQuery($query, $row_offset, $limit,true,"Error retrieving $this->object_name list: ");
3408 $result = $db->query($query,true,"Error retrieving $this->object_name list: ");
3413 $previous_offset = $row_offset - $max_per_page;
3414 $next_offset = $row_offset + $max_per_page;
3416 $class = get_class($this);
3417 //FIXME: Bug? we should remove the magic number -99
3418 //use -99 to return all
3419 $index = $row_offset;
3420 while ($max_per_page == -99 || ($index < $row_offset + $max_per_page))
3422 $row = $db->fetchByAssoc($result);
3423 if (empty($row)) break;
3425 //instantiate a new class each time. This is because php5 passes
3426 //by reference by default so if we continually update $this, we will
3427 //at the end have a list of all the same objects
3428 $temp = new $class();
3430 foreach($this->field_defs as $field=>$value)
3432 if (isset($row[$field]))
3434 $temp->$field = $row[$field];
3435 $owner_field = $field . '_owner';
3436 if(isset($row[$owner_field]))
3438 $temp->$owner_field = $row[$owner_field];
3441 $GLOBALS['log']->debug("$temp->object_name({$row['id']}): ".$field." = ".$temp->$field);
3442 }else if (isset($row[$this->table_name .'.'.$field]))
3444 $temp->$field = $row[$this->table_name .'.'.$field];
3452 $temp->check_date_relationships_load();
3453 $temp->fill_in_additional_list_fields();
3454 if($temp->hasCustomFields()) $temp->custom_fields->fill_relationships();
3455 $temp->call_custom_logic("process_record");
3457 // fix defect #44206. implement the same logic as sugar_currency_format
3458 // Smarty modifier does.
3459 if (property_exists($temp, 'currency_id') && -99 == $temp->currency_id)
3461 // manually retrieve default currency object as long as it's
3462 // not stored in database and thus cannot be joined in query
3463 require_once 'modules/Currencies/Currency.php';
3464 $currency = new Currency();
3465 $currency->retrieve($temp->currency_id);
3467 // walk through all currency-related fields
3468 foreach ($temp->field_defs as $temp_field)
3470 if (isset($temp_field['type']) && 'relate' == $temp_field['type']
3471 && isset($temp_field['module']) && 'Currencies' == $temp_field['module']
3472 && isset($temp_field['id_name']) && 'currency_id' == $temp_field['id_name'])
3474 // populate related properties manually
3475 $temp_property = $temp_field['name'];
3476 $currency_property = $temp_field['rname'];
3477 $temp->$temp_property = $currency->$currency_property;
3486 if(!empty($sugar_config['disable_count_query']) && !empty($limit))
3489 $rows_found = $row_offset + count($list);
3491 unset($list[$limit - 1]);
3497 } else if(!isset($rows_found)){
3498 $rows_found = $row_offset + count($list);
3501 $response = Array();
3502 $response['list'] = $list;
3503 $response['row_count'] = $rows_found;
3504 $response['next_offset'] = $next_offset;
3505 $response['previous_offset'] = $previous_offset;
3506 $response['current_offset'] = $row_offset ;
3511 * Returns the number of rows that the given SQL query should produce
3513 * Internal function, do not override.
3514 * @param string $query valid select query
3515 * @param boolean $is_count_query Optional, Default false, set to true if passed query is a count query.
3516 * @return int count of rows found
3518 function _get_num_rows_in_query($query, $is_count_query=false)
3520 $num_rows_in_query = 0;
3521 if (!$is_count_query)
3523 $count_query = SugarBean::create_list_count_query($query);
3525 $count_query=$query;
3527 $result = $this->db->query($count_query, true, "Error running count query for $this->object_name List: ");
3529 while($row = $this->db->fetchByAssoc($result, true))
3531 $num_rows_in_query += current($row);
3534 return $num_rows_in_query;
3538 * Applies pagination window to union queries used by list view and subpanels,
3539 * executes the query and returns fetched data.
3541 * Internal function, do not override.
3542 * @param object $parent_bean
3543 * @param string $query query to be processed.
3544 * @param int $row_offset
3545 * @param int $limit optional, default -1
3546 * @param int $max_per_page Optional, default -1
3547 * @param string $where Custom where clause.
3548 * @param array $subpanel_def definition of sub-panel to be processed
3549 * @param string $query_row_count
3550 * @param array $seconday_queries
3551 * @return array Fetched data.
3553 function process_union_list_query($parent_bean, $query,
3554 $row_offset, $limit= -1, $max_per_page = -1, $where = '', $subpanel_def, $query_row_count='', $secondary_queries = array())
3557 $db = DBManagerFactory::getInstance('listviews');
3559 * if the row_offset is set to 'end' go to the end of the list
3561 $toEnd = strval($row_offset) == 'end';
3562 global $sugar_config;
3563 $use_count_query=false;
3564 $processing_collection=$subpanel_def->isCollection();
3566 $GLOBALS['log']->debug("process_union_list_query: ".$query);
3567 if($max_per_page == -1)
3569 $max_per_page = $sugar_config['list_max_entries_per_subpanel'];
3571 if(empty($query_row_count))
3573 $query_row_count = $query;
3575 $distinct_position=strpos($query_row_count,"DISTINCT");
3577 if ($distinct_position!= false)
3579 $use_count_query=true;
3581 $performSecondQuery = true;
3582 if(empty($sugar_config['disable_count_query']) || $toEnd)
3584 $rows_found = $this->_get_num_rows_in_query($query_row_count,$use_count_query);
3587 $performSecondQuery = false;
3589 if(!empty($rows_found) && (empty($limit) || $limit == -1))
3591 $limit = $sugar_config['list_max_entries_per_subpanel'];
3595 $row_offset = (floor(($rows_found -1) / $limit)) * $limit;
3601 if((empty($limit) || $limit == -1))
3603 $limit = $max_per_page + 1;
3604 $max_per_page = $limit;
3608 if(empty($row_offset))
3613 $previous_offset = $row_offset - $max_per_page;
3614 $next_offset = $row_offset + $max_per_page;
3616 if($performSecondQuery)
3618 if(!empty($limit) && $limit != -1 && $limit != -99)
3620 $result = $db->limitQuery($query, $row_offset, $limit,true,"Error retrieving $parent_bean->object_name list: ");
3624 $result = $db->query($query,true,"Error retrieving $this->object_name list: ");
3626 //use -99 to return all
3628 // get the current row
3629 $index = $row_offset;
3630 $row = $db->fetchByAssoc($result);
3632 $post_retrieve = array();
3633 $isFirstTime = true;
3636 $function_fields = array();
3637 if(($index < $row_offset + $max_per_page || $max_per_page == -99))
3639 if ($processing_collection)
3641 $current_bean =$subpanel_def->sub_subpanels[$row['panel_name']]->template_instance;
3644 $class = get_class($subpanel_def->sub_subpanels[$row['panel_name']]->template_instance);
3645 $current_bean = new $class();
3648 $current_bean=$subpanel_def->template_instance;
3651 $class = get_class($subpanel_def->template_instance);
3652 $current_bean = new $class();
3655 $isFirstTime = false;
3656 //set the panel name in the bean instance.
3657 if (isset($row['panel_name']))
3659 $current_bean->panel_name=$row['panel_name'];
3661 foreach($current_bean->field_defs as $field=>$value)
3664 if (isset($row[$field]))
3666 $current_bean->$field = $this->convertField($row[$field], $value);
3667 unset($row[$field]);
3669 else if (isset($row[$this->table_name .'.'.$field]))
3671 $current_bean->$field = $this->convertField($row[$this->table_name .'.'.$field], $value);
3672 unset($row[$this->table_name .'.'.$field]);
3676 $current_bean->$field = "";
3677 unset($row[$field]);
3679 if(isset($value['source']) && $value['source'] == 'function')
3681 $function_fields[]=$field;
3684 foreach($row as $key=>$value)
3686 $current_bean->$key = $value;
3688 foreach($function_fields as $function_field)
3690 $value = $current_bean->field_defs[$function_field];
3691 $can_execute = true;
3692 $execute_params = array();
3693 $execute_function = array();
3694 if(!empty($value['function_class']))
3696 $execute_function[] = $value['function_class'];
3697 $execute_function[] = $value['function_name'];
3701 $execute_function = $value['function_name'];
3703 foreach($value['function_params'] as $param )
3705 if (empty($value['function_params_source']) or $value['function_params_source']=='parent')
3707 if(empty($this->$param))
3709 $can_execute = false;
3710 } else if($param == '$this') {
3711 $execute_params[] = $this;
3715 $execute_params[] = $this->$param;
3717 } else if ($value['function_params_source']=='this')
3719 if(empty($current_bean->$param))
3721 $can_execute = false;
3722 } else if($param == '$this') {
3723 $execute_params[] = $current_bean;
3727 $execute_params[] = $current_bean->$param;
3732 $can_execute = false;
3738 if(!empty($value['function_require']))
3740 require_once($value['function_require']);
3742 $current_bean->$function_field = call_user_func_array($execute_function, $execute_params);
3745 if(!empty($current_bean->parent_type) && !empty($current_bean->parent_id))
3747 if(!isset($post_retrieve[$current_bean->parent_type]))
3749 $post_retrieve[$current_bean->parent_type] = array();
3751 $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');
3753 //$current_bean->fill_in_additional_list_fields();
3754 $list[$current_bean->id] = $current_bean;
3756 // go to the next row
3758 $row = $db->fetchByAssoc($result);
3760 //now handle retrieving many-to-many relationships
3763 foreach($secondary_queries as $query2)
3765 $result2 = $db->query($query2);
3767 $row2 = $db->fetchByAssoc($result2);
3770 $id_ref = $row2['ref_id'];
3772 if(isset($list[$id_ref]))
3774 foreach($row2 as $r2key=>$r2value)
3776 if($r2key != 'ref_id')
3778 $list[$id_ref]->$r2key = $r2value;
3782 $row2 = $db->fetchByAssoc($result2);
3788 if(isset($post_retrieve))
3790 $parent_fields = $this->retrieve_parent_fields($post_retrieve);
3794 $parent_fields = array();
3796 if(!empty($sugar_config['disable_count_query']) && !empty($limit))
3798 //C.L. Bug 43535 - Use the $index value to set the $rows_found value here
3799 $rows_found = isset($index) ? $index : $row_offset + count($list);
3801 if(count($list) >= $limit)
3815 $parent_fields = array();
3817 $response = array();
3818 $response['list'] = $list;
3819 $response['parent_data'] = $parent_fields;
3820 $response['row_count'] = $rows_found;
3821 $response['next_offset'] = $next_offset;
3822 $response['previous_offset'] = $previous_offset;
3823 $response['current_offset'] = $row_offset ;
3824 $response['query'] = $query;
3830 * Applies pagination window to select queries used by detail view,
3831 * executes the query and returns fetched data.
3833 * Internal function, do not override.
3834 * @param string $query query to be processed.
3835 * @param int $row_offset
3836 * @param int $limit optional, default -1
3837 * @param int $max_per_page Optional, default -1
3838 * @param string $where Custom where clause.
3839 * @param int $offset Optional, default 0
3840 * @return array Fetched data.
3843 function process_detail_query($query, $row_offset, $limit= -1, $max_per_page = -1, $where = '', $offset = 0)
3845 global $sugar_config;
3846 $GLOBALS['log']->debug("process_detail_query: ".$query);
3847 if($max_per_page == -1)
3849 $max_per_page = $sugar_config['list_max_entries_per_page'];
3852 // Check to see if we have a count query available.
3853 $count_query = $this->create_list_count_query($query);
3855 if(!empty($count_query) && (empty($limit) || $limit == -1))
3857 // We have a count query. Run it and get the results.
3858 $result = $this->db->query($count_query, true, "Error running count query for $this->object_name List: ");
3859 $assoc = $this->db->fetchByAssoc($result);
3860 if(!empty($assoc['c']))
3862 $total_rows = $assoc['c'];
3866 if(empty($row_offset))
3871 $result = $this->db->limitQuery($query, $offset, 1, true,"Error retrieving $this->object_name list: ");
3873 $previous_offset = $row_offset - $max_per_page;
3874 $next_offset = $row_offset + $max_per_page;
3876 $row = $this->db->fetchByAssoc($result);
3877 $this->retrieve($row['id']);
3879 $response = Array();
3880 $response['bean'] = $this;
3881 if (empty($total_rows))
3883 $response['row_count'] = $total_rows;
3884 $response['next_offset'] = $next_offset;
3885 $response['previous_offset'] = $previous_offset;
3891 * Processes fetched list view data
3893 * Internal function, do not override.
3894 * @param string $query query to be processed.
3895 * @param boolean $check_date Optional, default false. if set to true date time values are processed.
3896 * @return array Fetched data.
3899 function process_full_list_query($query, $check_date=false)
3902 $GLOBALS['log']->debug("process_full_list_query: query is ".$query);
3903 $result = $this->db->query($query, false);
3904 $GLOBALS['log']->debug("process_full_list_query: result is ".print_r($result,true));
3905 $class = get_class($this);
3906 $isFirstTime = true;
3907 $bean = new $class();
3909 // We have some data.
3910 while (($row = $bean->db->fetchByAssoc($result)) != null)
3912 $row = $this->convertRow($row);
3915 $bean = new $class();
3917 $isFirstTime = false;
3919 foreach($bean->field_defs as $field=>$value)
3921 if (isset($row[$field]))
3923 $bean->$field = $row[$field];
3924 $GLOBALS['log']->debug("process_full_list: $bean->object_name({$row['id']}): ".$field." = ".$bean->$field);
3933 $bean->processed_dates_times = array();
3934 $bean->check_date_relationships_load();
3936 $bean->fill_in_additional_list_fields();
3937 $bean->call_custom_logic("process_record");
3938 $bean->fetched_row = $row;
3943 if (isset($list)) return $list;
3948 * Tracks the viewing of a detail record.
3949 * This leverages get_summary_text() which is object specific.
3951 * Internal function, do not override.
3952 * @param string $user_id - String value of the user that is viewing the record.
3953 * @param string $current_module - String value of the module being processed.
3954 * @param string $current_view - String value of the current view
3956 function track_view($user_id, $current_module, $current_view='')
3958 $trackerManager = TrackerManager::getInstance();
3959 if($monitor = $trackerManager->getMonitor('tracker')){
3960 $monitor->setValue('date_modified', $GLOBALS['timedate']->nowDb());
3961 $monitor->setValue('user_id', $user_id);
3962 $monitor->setValue('module_name', $current_module);
3963 $monitor->setValue('action', $current_view);
3964 $monitor->setValue('item_id', $this->id);
3965 $monitor->setValue('item_summary', $this->get_summary_text());
3966 $monitor->setValue('visible', $this->tracker_visibility);
3967 $trackerManager->saveMonitor($monitor);
3972 * Returns the summary text that should show up in the recent history list for this object.
3976 public function get_summary_text()
3978 return "Base Implementation. Should be overridden.";
3982 * This is designed to be overridden and add specific fields to each record.
3983 * This allows the generic query to fill in the major fields, and then targeted
3984 * queries to get related fields and add them to the record. The contact's
3985 * account for instance. This method is only used for populating extra fields
3988 function fill_in_additional_list_fields(){
3989 if(!empty($this->field_defs['parent_name']) && empty($this->parent_name)){
3990 $this->fill_in_additional_parent_fields();
3995 * This is designed to be overridden and add specific fields to each record.
3996 * This allows the generic query to fill in the major fields, and then targeted
3997 * queries to get related fields and add them to the record. The contact's
3998 * account for instance. This method is only used for populating extra fields
3999 * in the detail form
4001 function fill_in_additional_detail_fields()
4003 if(!empty($this->field_defs['assigned_user_name']) && !empty($this->assigned_user_id)){
4005 $this->assigned_user_name = get_assigned_user_name($this->assigned_user_id);
4008 if(!empty($this->field_defs['created_by']) && !empty($this->created_by))
4009 $this->created_by_name = get_assigned_user_name($this->created_by);
4010 if(!empty($this->field_defs['modified_user_id']) && !empty($this->modified_user_id))
4011 $this->modified_by_name = get_assigned_user_name($this->modified_user_id);
4013 if(!empty($this->field_defs['parent_name'])){
4014 $this->fill_in_additional_parent_fields();
4019 * This is desgined to be overridden or called from extending bean. This method
4020 * will fill in any parent_name fields.
4022 function fill_in_additional_parent_fields() {
4024 if(!empty($this->parent_id) && !empty($this->last_parent_id) && $this->last_parent_id == $this->parent_id){
4027 $this->parent_name = '';
4029 if(!empty($this->parent_type)) {
4030 $this->last_parent_id = $this->parent_id;
4031 $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'));
4032 if(!empty($this->parent_first_name) || !empty($this->parent_last_name) ){
4033 $this->parent_name = $GLOBALS['locale']->getLocaleFormattedName($this->parent_first_name, $this->parent_last_name);
4034 } else if(!empty($this->parent_document_name)){
4035 $this->parent_name = $this->parent_document_name;
4041 * Fill in a link field
4044 function fill_in_link_field( $linkFieldName , $def)
4046 $idField = $linkFieldName;
4047 //If the id_name provided really was an ID, don't try to load it as a link. Use the normal link
4048 if (!empty($this->field_defs[$linkFieldName]['type']) && $this->field_defs[$linkFieldName]['type'] == "id" && !empty($def['link']))
4050 $linkFieldName = $def['link'];
4052 if ($this->load_relationship($linkFieldName))
4054 $list=$this->$linkFieldName->get();
4055 $this->$idField = '' ; // match up with null value in $this->populateFromRow()
4057 $this->$idField = $list[0];
4062 * Fill in fields where type = relate
4064 function fill_in_relationship_fields(){
4065 global $fill_in_rel_depth;
4066 if(empty($fill_in_rel_depth) || $fill_in_rel_depth < 0)
4067 $fill_in_rel_depth = 0;
4069 if($fill_in_rel_depth > 1)
4072 $fill_in_rel_depth++;
4074 foreach($this->field_defs as $field)
4076 if(0 == strcmp($field['type'],'relate') && !empty($field['module']))
4078 $name = $field['name'];
4079 if(empty($this->$name))
4081 // 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']
4082 $related_module = $field['module'];
4083 $id_name = $field['id_name'];
4085 if (empty($this->$id_name))
4087 $this->fill_in_link_field($id_name, $field);
4089 if(!empty($this->$id_name) && ( $this->object_name != $related_module || ( $this->object_name == $related_module && $this->$id_name != $this->id ))){
4090 if(isset($GLOBALS['beanList'][ $related_module])){
4091 $class = $GLOBALS['beanList'][$related_module];
4093 if(!empty($this->$id_name) && file_exists($GLOBALS['beanFiles'][$class]) && isset($this->$name)){
4094 require_once($GLOBALS['beanFiles'][$class]);
4095 $mod = new $class();
4097 // disable row level security in order to be able
4098 // to retrieve related bean properties (bug #44928)
4100 $mod->retrieve($this->$id_name);
4102 if (!empty($field['rname'])) {
4103 $this->$name = $mod->$field['rname'];
4104 } else if (isset($mod->name)) {
4105 $this->$name = $mod->name;
4110 if(!empty($this->$id_name) && isset($this->$name))
4112 if(!isset($field['additionalFields']))
4113 $field['additionalFields'] = array();
4114 if(!empty($field['rname']))
4116 $field['additionalFields'][$field['rname']]= $name;
4120 $field['additionalFields']['name']= $name;
4122 $this->getRelatedFields($related_module, $this->$id_name, $field['additionalFields']);
4127 $fill_in_rel_depth--;
4131 * This is a helper function that is used to quickly created indexes when creating tables.
4133 function create_index($query)
4135 $GLOBALS['log']->info("create_index: $query");
4137 $result = $this->db->query($query, true, "Error creating index:");
4141 * This function should be overridden in each module. It marks an item as deleted.
4143 * If it is not overridden, then marking this type of item is not allowed
4145 function mark_deleted($id)
4147 global $current_user;
4148 $date_modified = $GLOBALS['timedate']->nowDb();
4149 if(isset($_SESSION['show_deleted']))
4151 $this->mark_undeleted($id);
4155 // call the custom business logic
4156 $custom_logic_arguments['id'] = $id;
4157 $this->call_custom_logic("before_delete", $custom_logic_arguments);
4159 $this->mark_relationships_deleted($id);
4160 if ( isset($this->field_defs['modified_user_id']) ) {
4161 if (!empty($current_user)) {
4162 $this->modified_user_id = $current_user->id;
4164 $this->modified_user_id = 1;
4166 $query = "UPDATE $this->table_name set deleted=1 , date_modified = '$date_modified', modified_user_id = '$this->modified_user_id' where id='$id'";
4168 $query = "UPDATE $this->table_name set deleted=1 , date_modified = '$date_modified' where id='$id'";
4170 $this->db->query($query, true,"Error marking record deleted: ");
4172 SugarRelationship::resaveRelatedBeans();
4174 // Take the item off the recently viewed lists
4175 $tracker = new Tracker();
4176 $tracker->makeInvisibleForAll($id);
4178 // call the custom business logic
4179 $this->call_custom_logic("after_delete", $custom_logic_arguments);
4184 * Restores data deleted by call to mark_deleted() function.
4186 * Internal function, do not override.
4188 function mark_undeleted($id)
4190 // call the custom business logic
4191 $custom_logic_arguments['id'] = $id;
4192 $this->call_custom_logic("before_restore", $custom_logic_arguments);
4194 $date_modified = $GLOBALS['timedate']->nowDb();
4195 $query = "UPDATE $this->table_name set deleted=0 , date_modified = '$date_modified' where id='$id'";
4196 $this->db->query($query, true,"Error marking record undeleted: ");
4198 // call the custom business logic
4199 $this->call_custom_logic("after_restore", $custom_logic_arguments);
4203 * This function deletes relationships to this object. It should be overridden
4204 * to handle the relationships of the specific object.
4205 * This function is called when the item itself is being deleted.
4207 * @param int $id id of the relationship to delete
4209 function mark_relationships_deleted($id)
4211 $this->delete_linked($id);
4215 * This function is used to execute the query and create an array template objects
4216 * from the resulting ids from the query.
4217 * It is currently used for building sub-panel arrays.
4219 * @param string $query - the query that should be executed to build the list
4220 * @param object $template - The object that should be used to copy the records.
4221 * @param int $row_offset Optional, default 0
4222 * @param int $limit Optional, default -1
4225 function build_related_list($query, &$template, $row_offset = 0, $limit = -1)
4227 $GLOBALS['log']->debug("Finding linked records $this->object_name: ".$query);
4228 $db = DBManagerFactory::getInstance('listviews');
4230 if(!empty($row_offset) && $row_offset != 0 && !empty($limit) && $limit != -1)
4232 $result = $db->limitQuery($query, $row_offset, $limit,true,"Error retrieving $template->object_name list: ");
4236 $result = $db->query($query, true);
4240 $isFirstTime = true;
4241 $class = get_class($template);
4242 while($row = $this->db->fetchByAssoc($result))
4246 $template = new $class();
4248 $isFirstTime = false;
4249 $record = $template->retrieve($row['id']);
4253 // this copies the object into the array
4254 $list[] = $template;
4261 * This function is used to execute the query and create an array template objects
4262 * from the resulting ids from the query.
4263 * It is currently used for building sub-panel arrays. It supports an additional
4264 * where clause that is executed as a filter on the results
4266 * @param string $query - the query that should be executed to build the list
4267 * @param object $template - The object that should be used to copy the records.
4269 function build_related_list_where($query, &$template, $where='', $in='', $order_by, $limit='', $row_offset = 0)
4271 $db = DBManagerFactory::getInstance('listviews');
4272 // No need to do an additional query
4273 $GLOBALS['log']->debug("Finding linked records $this->object_name: ".$query);
4274 if(empty($in) && !empty($query))
4276 $idList = $this->build_related_in($query);
4277 $in = $idList['in'];
4279 // MFH - Added Support For Custom Fields in Searches
4281 if(isset($this->custom_fields)) {
4282 $custom_join = $this->custom_fields->getJOIN();
4285 $query = "SELECT id ";
4287 if (!empty($custom_join)) {
4288 $query .= $custom_join['select'];
4290 $query .= " FROM $this->table_name ";
4292 if (!empty($custom_join) && !empty($custom_join['join'])) {
4293 $query .= " " . $custom_join['join'];
4296 $query .= " WHERE deleted=0 AND id IN $in";
4299 $query .= " AND $where";
4303 if(!empty($order_by))
4305 $query .= "ORDER BY $order_by";
4309 $result = $db->limitQuery($query, $row_offset, $limit,true,"Error retrieving $this->object_name list: ");
4313 $result = $db->query($query, true);
4317 $isFirstTime = true;
4318 $class = get_class($template);
4320 $disable_security_flag = ($template->disable_row_level_security) ? true : false;
4321 while($row = $db->fetchByAssoc($result))
4325 $template = new $class();
4326 $template->disable_row_level_security = $disable_security_flag;
4328 $isFirstTime = false;
4329 $record = $template->retrieve($row['id']);
4332 // this copies the object into the array
4333 $list[] = $template;
4341 * Constructs an comma separated list of ids from passed query results.
4343 * @param string @query query to be executed.
4346 function build_related_in($query)
4349 $result = $this->db->query($query, true);
4351 while($row = $this->db->fetchByAssoc($result))
4353 $idList[] = $row['id'];
4356 $ids = "('" . $row['id'] . "'";
4360 $ids .= ",'" . $row['id'] . "'";
4370 return array('list'=>$idList, 'in'=>$ids);
4374 * Optionally copies values from fetched row into the bean.
4376 * Internal function, do not override.
4378 * @param string $query - the query that should be executed to build the list
4379 * @param object $template - The object that should be used to copy the records
4380 * @param array $field_list List of fields.
4383 function build_related_list2($query, &$template, &$field_list)
4385 $GLOBALS['log']->debug("Finding linked values $this->object_name: ".$query);
4387 $result = $this->db->query($query, true);
4390 $isFirstTime = true;
4391 $class = get_class($template);
4392 while($row = $this->db->fetchByAssoc($result))
4394 // Create a blank copy
4398 $copy = new $class();
4400 $isFirstTime = false;
4401 foreach($field_list as $field)
4403 // Copy the relevant fields
4404 $copy->$field = $row[$field];
4408 // this copies the object into the array
4416 * Let implementing classes to fill in row specific columns of a list view form
4419 function list_view_parse_additional_sections(&$list_form)
4424 * Assigns all of the values into the template for the list view
4426 function get_list_view_array()
4428 static $cache = array();
4429 // cn: bug 12270 - sensitive fields being passed arbitrarily in listViews
4430 $sensitiveFields = array('user_hash' => '');
4433 $return_array = Array();
4434 global $app_list_strings, $mod_strings;
4435 foreach($this->field_defs as $field=>$value){
4437 if(isset($this->$field)){
4439 // cn: bug 12270 - sensitive fields being passed arbitrarily in listViews
4440 if(isset($sensitiveFields[$field]))
4442 if(!isset($cache[$field]))
4443 $cache[$field] = strtoupper($field);
4445 //Fields hidden by Dependent Fields
4446 if (isset($value['hidden']) && $value['hidden'] === true) {
4447 $return_array[$cache[$field]] = "";
4450 //cn: if $field is a _dom, detect and return VALUE not KEY
4451 //cl: empty function check for meta-data enum types that have values loaded from a function
4452 else if (((!empty($value['type']) && ($value['type'] == 'enum' || $value['type'] == 'radioenum') )) && empty($value['function'])){
4453 if(!empty($value['options']) && !empty($app_list_strings[$value['options']][$this->$field])){
4454 $return_array[$cache[$field]] = $app_list_strings[$value['options']][$this->$field];
4456 //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.
4457 elseif(!empty($value['options']) && !empty($mod_strings[$value['options']][$this->$field]))
4459 $return_array[$cache[$field]] = $mod_strings[$value['options']][$this->$field];
4462 $return_array[$cache[$field]] = $this->$field;
4465 // tjy: no need to do this str_replace as the changes in 29994 for ListViewGeneric.tpl for translation handle this now
4466 // }elseif(!empty($value['type']) && $value['type'] == 'multienum'&& empty($value['function'])){
4467 // $return_array[strtoupper($field)] = str_replace('^,^', ', ', $this->$field );
4468 }elseif(!empty($value['custom_module']) && $value['type'] != 'currency'){
4469 // $this->format_field($value);
4470 $return_array[$cache[$field]] = $this->$field;
4472 $return_array[$cache[$field]] = $this->$field;
4474 // handle "Assigned User Name"
4475 if($field == 'assigned_user_name'){
4476 $return_array['ASSIGNED_USER_NAME'] = get_assigned_user_name($this->assigned_user_id);
4480 return $return_array;
4483 * Override this function to set values in the array used to render list view data.
4486 function get_list_view_data()
4488 return $this->get_list_view_array();
4492 * Construct where clause from a list of name-value pairs.
4493 * @param array $fields_array Name/value pairs for column checks
4494 * @return string The WHERE clause
4496 function get_where($fields_array)
4499 foreach ($fields_array as $name=>$value)
4501 if (!empty($where_clause)) {
4502 $where_clause .= " AND ";
4504 $name = $this->db->getValidDBName($name);
4506 $where_clause .= "$name = ".$this->db->quoted($value,false);
4508 if(!empty($where_clause)) {
4509 return "WHERE $where_clause AND deleted=0";
4511 return "WHERE deteled=0";
4517 * Constructs a select query and fetch 1 row using this query, and then process the row
4519 * Internal function, do not override.
4520 * @param array @fields_array array of name value pairs used to construct query.
4521 * @param boolean $encode Optional, default true, encode fetched data.
4522 * @return object Instance of this bean with fetched data.
4524 function retrieve_by_string_fields($fields_array, $encode=true)
4526 $where_clause = $this->get_where($fields_array);
4527 if(isset($this->custom_fields))
4528 $custom_join = $this->custom_fields->getJOIN();
4529 else $custom_join = false;
4532 $query = "SELECT $this->table_name.*". $custom_join['select']. " FROM $this->table_name " . $custom_join['join'];
4536 $query = "SELECT $this->table_name.* FROM $this->table_name ";
4538 $query .= " $where_clause";
4539 $GLOBALS['log']->debug("Retrieve $this->object_name: ".$query);
4540 //requireSingleResult has been deprecated.
4541 //$result = $this->db->requireSingleResult($query, true, "Retrieving record $where_clause:");
4542 $result = $this->db->limitQuery($query,0,1,true, "Retrieving record $where_clause:");
4549 $row = $this->db->fetchByAssoc($result, $encode);
4554 // Removed getRowCount-if-clause earlier and insert duplicates_found here as it seems that we have found something
4555 // if we didn't return null in the previous clause.
4556 $this->duplicates_found = true;
4557 $row = $this->convertRow($row);
4558 $this->fetched_row = $row;
4559 $this->fromArray($row);
4560 $this->fill_in_additional_detail_fields();
4565 * This method is called during an import before inserting a bean
4566 * Define an associative array called $special_fields
4567 * the keys are user defined, and don't directly map to the bean's fields
4568 * the value is the method name within that bean that will do extra
4569 * processing for that field. example: 'full_name'=>'get_names_from_full_name'
4572 function process_special_fields()
4574 foreach ($this->special_functions as $func_name)
4576 if ( method_exists($this,$func_name) )
4578 $this->$func_name();
4584 * Override this function to build a where clause based on the search criteria set into bean .
4587 function build_generic_where_clause($value)
4591 function getRelatedFields($module, $id, $fields, $return_array = false){
4592 if(empty($GLOBALS['beanList'][$module]))return '';
4593 $object = BeanFactory::getObjectName($module);
4595 VardefManager::loadVardef($module, $object);
4596 if(empty($GLOBALS['dictionary'][$object]['table']))return '';
4597 $table = $GLOBALS['dictionary'][$object]['table'];
4598 $query = 'SELECT id';
4599 foreach($fields as $field=>$alias){
4600 if(!empty($GLOBALS['dictionary'][$object]['fields'][$field]['db_concat_fields'])){
4601 $query .= ' ,' .$this->db->concat($table, $GLOBALS['dictionary'][$object]['fields'][$field]['db_concat_fields']) . ' as ' . $alias ;
4602 }else if(!empty($GLOBALS['dictionary'][$object]['fields'][$field]) &&
4603 (empty($GLOBALS['dictionary'][$object]['fields'][$field]['source']) ||
4604 $GLOBALS['dictionary'][$object]['fields'][$field]['source'] != "non-db"))
4606 $query .= ' ,' .$table . '.' . $field . ' as ' . $alias;
4608 if(!$return_array)$this->$alias = '';
4610 if($query == 'SELECT id' || empty($id)){
4615 if(isset($GLOBALS['dictionary'][$object]['fields']['assigned_user_id']))
4617 $query .= " , ". $table . ".assigned_user_id owner";
4620 else if(isset($GLOBALS['dictionary'][$object]['fields']['created_by']))
4622 $query .= " , ". $table . ".created_by owner";
4625 $query .= ' FROM ' . $table . ' WHERE deleted=0 AND id=';
4626 $result = $GLOBALS['db']->query($query . "'$id'" );
4627 $row = $GLOBALS['db']->fetchByAssoc($result);
4631 $owner = (empty($row['owner']))?'':$row['owner'];
4632 foreach($fields as $alias){
4633 $this->$alias = (!empty($row[$alias]))? $row[$alias]: '';
4634 $alias = $alias .'_owner';
4635 $this->$alias = $owner;
4636 $a_mod = $alias .'_mod';
4637 $this->$a_mod = $module;
4644 function &parse_additional_headers(&$list_form, $xTemplateSection)
4649 function assign_display_fields($currentModule)
4652 foreach($this->column_fields as $field)
4654 if(isset($this->field_name_map[$field]) && empty($this->$field))
4656 if($this->field_name_map[$field]['type'] != 'date' && $this->field_name_map[$field]['type'] != 'enum')
4657 $this->$field = $field;
4658 if($this->field_name_map[$field]['type'] == 'date')
4660 $this->$field = $timedate->to_display_date('1980-07-09');
4662 if($this->field_name_map[$field]['type'] == 'enum')
4664 $dom = $this->field_name_map[$field]['options'];
4665 global $current_language, $app_list_strings;
4666 $mod_strings = return_module_language($current_language, $currentModule);
4668 if(isset($mod_strings[$dom]))
4670 $options = $mod_strings[$dom];
4671 foreach($options as $key=>$value)
4673 if(!empty($key) && empty($this->$field ))
4675 $this->$field = $key;
4679 if(isset($app_list_strings[$dom]))
4681 $options = $app_list_strings[$dom];
4682 foreach($options as $key=>$value)
4684 if(!empty($key) && empty($this->$field ))
4686 $this->$field = $key;
4698 * RELATIONSHIP HANDLING
4701 function set_relationship($table, $relate_values, $check_duplicates = true,$do_update=false,$data_values=null)
4705 // make sure there is a date modified
4706 $date_modified = $this->db->convert("'".$GLOBALS['timedate']->nowDb()."'", 'datetime');
4709 if($check_duplicates)
4711 $query = "SELECT * FROM $table ";
4712 $where = "WHERE deleted = '0' ";
4713 foreach($relate_values as $name=>$value)
4715 $where .= " AND $name = '$value' ";
4718 $result = $this->db->query($query, false, "Looking For Duplicate Relationship:" . $query);
4719 $row=$this->db->fetchByAssoc($result);
4722 if(!$check_duplicates || empty($row) )
4724 unset($relate_values['id']);
4725 if ( isset($data_values))
4727 $relate_values = array_merge($relate_values,$data_values);
4729 $query = "INSERT INTO $table (id, ". implode(',', array_keys($relate_values)) . ", date_modified) VALUES ('" . create_guid() . "', " . "'" . implode("', '", $relate_values) . "', ".$date_modified.")" ;
4731 $this->db->query($query, false, "Creating Relationship:" . $query);
4733 else if ($do_update)
4736 foreach($data_values as $key=>$value)
4738 array_push($conds,$key."='".$this->db->quote($value)."'");
4740 $query = "UPDATE $table SET ". implode(',', $conds).",date_modified=".$date_modified." ".$where;
4741 $this->db->query($query, false, "Updating Relationship:" . $query);
4745 function retrieve_relationships($table, $values, $select_id)
4747 $query = "SELECT $select_id FROM $table WHERE deleted = 0 ";
4748 foreach($values as $name=>$value)
4750 $query .= " AND $name = '$value' ";
4752 $query .= " ORDER BY $select_id ";
4753 $result = $this->db->query($query, false, "Retrieving Relationship:" . $query);
4755 while($row = $this->db->fetchByAssoc($result))
4762 // TODO: this function needs adjustment
4763 function loadLayoutDefs()
4765 global $layout_defs;
4766 if(empty( $this->layout_def) && file_exists('modules/'. $this->module_dir . '/layout_defs.php'))
4768 include_once('modules/'. $this->module_dir . '/layout_defs.php');
4769 if(file_exists('custom/modules/'. $this->module_dir . '/Ext/Layoutdefs/layoutdefs.ext.php'))
4771 include_once('custom/modules/'. $this->module_dir . '/Ext/Layoutdefs/layoutdefs.ext.php');
4773 if ( empty( $layout_defs[get_class($this)]))
4775 echo "\$layout_defs[" . get_class($this) . "]; does not exist";
4778 $this->layout_def = $layout_defs[get_class($this)];
4783 * Trigger custom logic for this module that is defined for the provided hook
4784 * The custom logic file is located under custom/modules/[CURRENT_MODULE]/logic_hooks.php.
4785 * That file should define the $hook_version that should be used.
4786 * It should also define the $hook_array. The $hook_array will be a two dimensional array
4787 * the first dimension is the name of the event, the second dimension is the information needed
4788 * to fire the hook. Each entry in the top level array should be defined on a single line to make it
4789 * easier to automatically replace this file. There should be no contents of this file that are not replacable.
4791 * $hook_array['before_save'][] = Array(1, testtype, 'custom/modules/Leads/test12.php', 'TestClass', 'lead_before_save_1');
4792 * This sample line creates a before_save hook. The hooks are procesed in the order in which they
4793 * are added to the array. The second dimension is an array of:
4794 * processing index (for sorting before exporting the array)
4797 * php file to include
4798 * php class the method is in
4799 * php method to call
4801 * The method signature for version 1 hooks is:
4802 * function NAME(&$bean, $event, $arguments)
4803 * $bean - $this bean passed in by reference.
4804 * $event - The string for the current event (i.e. before_save)
4805 * $arguments - An array of arguments that are specific to the event.
4807 function call_custom_logic($event, $arguments = null)
4809 if(!isset($this->processed) || $this->processed == false){
4810 //add some logic to ensure we do not get into an infinite loop
4811 if(!empty($this->logicHookDepth[$event])) {
4812 if($this->logicHookDepth[$event] > 10)
4815 $this->logicHookDepth[$event] = 0;
4817 //we have to put the increment operator here
4818 //otherwise we may never increase the depth for that event in the case
4819 //where one event will trigger another as in the case of before_save and after_save
4820 //Also keeping the depth per event allow any number of hooks to be called on the bean
4821 //and we only will return if one event gets caught in a loop. We do not increment globally
4822 //for each event called.
4823 $this->logicHookDepth[$event]++;
4825 //method defined in 'include/utils/LogicHook.php'
4827 $logicHook = new LogicHook();
4828 $logicHook->setBean($this);
4829 $logicHook->call_custom_logic($this->module_dir, $event, $arguments);
4834 /* When creating a custom field of type Dropdown, it creates an enum row in the DB.
4835 A typical get_list_view_array() result will have the *KEY* value from that drop-down.
4836 Since custom _dom objects are flat-files included in the $app_list_strings variable,
4837 We need to generate a key-key pair to get the true value like so:
4838 ([module]_cstm->fields_meta_data->$app_list_strings->*VALUE*)*/
4839 function getRealKeyFromCustomFieldAssignedKey($name)
4841 if ($this->custom_fields->avail_fields[$name]['ext1'])
4845 elseif ($this->custom_fields->avail_fields[$name]['ext2'])
4849 elseif ($this->custom_fields->avail_fields[$name]['ext3'])
4855 $GLOBALS['log']->fatal("SUGARBEAN: cannot find Real Key for custom field of type dropdown - cannot return Value.");
4860 return $this->custom_fields->avail_fields[$name][$realKey];
4864 function bean_implements($interface)
4869 * Check whether the user has access to a particular view for the current bean/module
4870 * @param $view string required, the view to determine access for i.e. DetailView, ListView...
4871 * @param $is_owner bool optional, this is part of the ACL check if the current user is an owner they will receive different access
4873 function ACLAccess($view,$is_owner='not_set')
4875 global $current_user;
4876 if($current_user->isAdminForModule($this->getACLCategory())) {
4880 if($is_owner == 'not_set')
4883 $is_owner = $this->isOwner($current_user->id);
4886 // If we don't implement ACLs, return true.
4887 if(!$this->bean_implements('ACL'))
4889 $view = strtolower($view);
4895 return ACLController::checkAccess($this->module_dir,'list', true);
4898 if( !$is_owner && $not_set && !empty($this->id)){
4899 $class = get_class($this);
4900 $temp = new $class();
4901 if(!empty($this->fetched_row) && !empty($this->fetched_row['id']) && !empty($this->fetched_row['assigned_user_id']) && !empty($this->fetched_row['created_by'])){
4902 $temp->populateFromRow($this->fetched_row);
4904 $temp->retrieve($this->id);
4906 $is_owner = $temp->isOwner($current_user->id);
4908 case 'popupeditview':
4910 return ACLController::checkAccess($this->module_dir,'edit', $is_owner, $this->acltype);
4914 return ACLController::checkAccess($this->module_dir,'view', $is_owner, $this->acltype);
4916 return ACLController::checkAccess($this->module_dir,'delete', $is_owner, $this->acltype);
4918 return ACLController::checkAccess($this->module_dir,'export', $is_owner, $this->acltype);
4920 return ACLController::checkAccess($this->module_dir,'import', true, $this->acltype);
4922 //if it is not one of the above views then it should be implemented on the page level
4931 function getOwnerField($returnFieldName = false)
4933 if (isset($this->field_defs['assigned_user_id']))
4935 return $returnFieldName? 'assigned_user_id': $this->assigned_user_id;
4938 if (isset($this->field_defs['created_by']))
4940 return $returnFieldName? 'created_by': $this->created_by;
4947 * Returns true of false if the user_id passed is the owner
4949 * @param GUID $user_id
4952 function isOwner($user_id)
4954 //if we don't have an id we must be the owner as we are creating it
4955 if(!isset($this->id))
4959 //if there is an assigned_user that is the owner
4960 if(isset($this->assigned_user_id))
4962 if($this->assigned_user_id == $user_id) return true;
4967 //other wise if there is a created_by that is the owner
4968 if(isset($this->created_by) && $this->created_by == $user_id)
4976 * Gets there where statement for checking if a user is an owner
4978 * @param GUID $user_id
4981 function getOwnerWhere($user_id)
4983 if(isset($this->field_defs['assigned_user_id']))
4985 return " $this->table_name.assigned_user_id ='$user_id' ";
4987 if(isset($this->field_defs['created_by']))
4989 return " $this->table_name.created_by ='$user_id' ";
4996 * Used in order to manage ListView links and if they should
4997 * links or not based on the ACL permissions of the user
4999 * @return ARRAY of STRINGS
5001 function listviewACLHelper()
5003 $array_assign = array();
5004 if($this->ACLAccess('DetailView'))
5006 $array_assign['MAIN'] = 'a';
5010 $array_assign['MAIN'] = 'span';
5012 return $array_assign;
5016 * returns this bean as an array
5018 * @return array of fields with id, name, access and category
5020 function toArray($dbOnly = false, $stringOnly = false, $upperKeys=false)
5022 static $cache = array();
5025 foreach($this->field_defs as $field=>$data)
5027 if( !$dbOnly || !isset($data['source']) || $data['source'] == 'db')
5028 if(!$stringOnly || is_string($this->$field))
5031 if(!isset($cache[$field])){
5032 $cache[$field] = strtoupper($field);
5034 $arr[$cache[$field]] = $this->$field;
5038 if(isset($this->$field)){
5039 $arr[$field] = $this->$field;
5049 * Converts an array into an acl mapping name value pairs into files
5053 function fromArray($arr)
5055 foreach($arr as $name=>$value)
5057 $this->$name = $value;
5062 * Convert row data from DB format to internal format
5063 * Mostly useful for dates/times
5065 * @return array $row
5067 public function convertRow($row)
5069 foreach($this->field_defs as $name => $fieldDef)
5071 // skip empty fields and non-db fields
5072 if (isset($name) && !empty($row[$name])) {
5073 $row[$name] = $this->convertField($row[$name], $fieldDef);
5080 * Converts the field value based on the provided fieldDef
5081 * @param $fieldvalue
5085 public function convertField($fieldvalue, $fieldDef)
5087 if (!empty($fieldvalue)) {
5088 if (!(isset($fieldDef['source']) &&
5089 !in_array($fieldDef['source'], array('db', 'custom_fields', 'relate'))
5090 && !isset($fieldDef['dbType']))
5092 // fromConvert other fields
5093 $fieldvalue = $this->db->fromConvert($fieldvalue, $this->db->getFieldType($fieldDef));
5100 * Loads a row of data into instance of a bean. The data is passed as an array to this function
5102 * @param array $arr row of data fetched from the database.
5105 * Internal function do not override.
5107 function loadFromRow($arr)
5109 $this->populateFromRow($arr);
5110 $this->processed_dates_times = array();
5111 $this->check_date_relationships_load();
5113 $this->fill_in_additional_list_fields();
5115 if($this->hasCustomFields())$this->custom_fields->fill_relationships();
5116 $this->call_custom_logic("process_record");
5119 function hasCustomFields()
5121 return !empty($GLOBALS['dictionary'][$this->object_name]['custom_fields']);
5125 * Ensure that fields within order by clauses are properly qualified with
5126 * their tablename. This qualification is a requirement for sql server support.
5128 * @param string $order_by original order by from the query
5129 * @param string $qualify prefix for columns in the order by list.
5132 * Internal function do not override.
5134 function create_qualified_order_by( $order_by, $qualify)
5135 { // 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
5136 if (empty($order_by))
5140 $order_by_clause = " ORDER BY ";
5141 $tmp = explode(",", $order_by);
5143 foreach ( $tmp as $stmp)
5145 $stmp = (substr_count($stmp, ".") > 0?trim($stmp):"$qualify." . trim($stmp));
5146 $order_by_clause .= $comma . $stmp;
5149 return $order_by_clause;
5153 * Combined the contents of street field 2 thru 4 into the main field
5155 * @param string $street_field
5158 function add_address_streets(
5162 $street_field_2 = $street_field.'_2';
5163 $street_field_3 = $street_field.'_3';
5164 $street_field_4 = $street_field.'_4';
5165 if ( isset($this->$street_field_2)) {
5166 $this->$street_field .= "\n". $this->$street_field_2;
5167 unset($this->$street_field_2);
5169 if ( isset($this->$street_field_3)) {
5170 $this->$street_field .= "\n". $this->$street_field_3;
5171 unset($this->$street_field_3);
5173 if ( isset($this->$street_field_4)) {
5174 $this->$street_field .= "\n". $this->$street_field_4;
5175 unset($this->$street_field_4);
5177 if ( isset($this->$street_field)) {
5178 $this->$street_field = trim($this->$street_field, "\n");
5182 * Encrpyt and base64 encode an 'encrypt' field type in the bean using Blowfish. The default system key is stored in cache/Blowfish/{keytype}
5183 * @param STRING value -plain text value of the bean field.
5186 function encrpyt_before_save($value)
5188 require_once("include/utils/encryption_utils.php");
5189 return blowfishEncode(blowfishGetKey('encrypt_field'),$value);
5193 * Decode and decrypt a base 64 encoded string with field type 'encrypt' in this bean using Blowfish.
5194 * @param STRING value - an encrypted and base 64 encoded string.
5197 function decrypt_after_retrieve($value)
5199 require_once("include/utils/encryption_utils.php");
5200 return blowfishDecode(blowfishGetKey('encrypt_field'), $value);
5204 * Moved from save() method, functionality is the same, but this is intended to handle
5205 * Optimistic locking functionality.
5207 private function _checkOptimisticLocking($action, $isUpdate){
5208 if($this->optimistic_lock && !isset($_SESSION['o_lock_fs'])){
5209 if(isset($_SESSION['o_lock_id']) && $_SESSION['o_lock_id'] == $this->id && $_SESSION['o_lock_on'] == $this->object_name)
5211 if($action == 'Save' && $isUpdate && isset($this->modified_user_id) && $this->has_been_modified_since($_SESSION['o_lock_dm'], $this->modified_user_id))
5213 $_SESSION['o_lock_class'] = get_class($this);
5214 $_SESSION['o_lock_module'] = $this->module_dir;
5215 $_SESSION['o_lock_object'] = $this->toArray();
5216 $saveform = "<form name='save' id='save' method='POST'>";
5217 foreach($_POST as $key=>$arg)
5219 $saveform .= "<input type='hidden' name='". addslashes($key) ."' value='". addslashes($arg) ."'>";
5221 $saveform .= "</form><script>document.getElementById('save').submit();</script>";
5222 $_SESSION['o_lock_save'] = $saveform;
5223 header('Location: index.php?module=OptimisticLock&action=LockResolve');
5228 unset ($_SESSION['o_lock_object']);
5229 unset ($_SESSION['o_lock_id']);
5230 unset ($_SESSION['o_lock_dm']);
5236 if(isset($_SESSION['o_lock_object'])) { unset ($_SESSION['o_lock_object']); }
5237 if(isset($_SESSION['o_lock_id'])) { unset ($_SESSION['o_lock_id']); }
5238 if(isset($_SESSION['o_lock_dm'])) { unset ($_SESSION['o_lock_dm']); }
5239 if(isset($_SESSION['o_lock_fs'])) { unset ($_SESSION['o_lock_fs']); }
5240 if(isset($_SESSION['o_lock_save'])) { unset ($_SESSION['o_lock_save']); }
5245 * Send assignment notifications and invites for meetings and calls
5247 private function _sendNotifications($check_notify){
5248 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.
5249 && !$this->isOwner($this->created_by) ) // cn: bug 42727 no need to send email to owner (within workflow)
5251 $admin = new Administration();
5252 $admin->retrieveSettings();
5253 $sendNotifications = false;
5255 if ($admin->settings['notify_on'])
5257 $GLOBALS['log']->info("Notifications: user assignment has changed, checking if user receives notifications");
5258 $sendNotifications = true;
5260 elseif(isset($_REQUEST['send_invites']) && $_REQUEST['send_invites'] == 1)
5262 // cn: bug 5795 Send Invites failing for Contacts
5263 $sendNotifications = true;
5267 $GLOBALS['log']->info("Notifications: not sending e-mail, notify_on is set to OFF");
5271 if($sendNotifications == true)
5273 $notify_list = $this->get_notification_recipients();
5274 foreach ($notify_list as $notify_user)
5276 $this->send_assignment_notifications($notify_user, $admin);
5284 * Called from ImportFieldSanitize::relate(), when creating a new bean in a related module. Will
5285 * copies fields over from the current bean into the related. Designed to be overriden in child classes.
5287 * @param SugarBean $newbean newly created related bean
5289 public function populateRelatedBean(
5296 * Called during the import process before a bean save, to handle any needed pre-save logic when
5297 * importing a record
5299 public function beforeImportSave()
5304 * Called during the import process after a bean save, to handle any needed post-save logic when
5305 * importing a record
5307 public function afterImportSave()
5312 * This function is designed to cache references to field arrays that were previously stored in the
5313 * bean files and have since been moved to separate files. Was previously in include/CacheHandler.php
5316 * @param $module_dir string the module directory
5317 * @param $module string the name of the module
5318 * @param $key string the type of field array we are referencing, i.e. list_fields, column_fields, required_fields
5320 private function _loadCachedArray(
5326 static $moduleDefs = array();
5328 $fileName = 'field_arrays.php';
5330 $cache_key = "load_cached_array.$module_dir.$module.$key";
5331 $result = sugar_cache_retrieve($cache_key);
5334 // Use SugarCache::EXTERNAL_CACHE_NULL_VALUE to store null values in the cache.
5335 if($result == SugarCache::EXTERNAL_CACHE_NULL_VALUE)
5343 if(file_exists('modules/'.$module_dir.'/'.$fileName))
5345 // If the data was not loaded, try loading again....
5346 if(!isset($moduleDefs[$module]))
5348 include('modules/'.$module_dir.'/'.$fileName);
5349 $moduleDefs[$module] = $fields_array;
5351 // Now that we have tried loading, make sure it was loaded
5352 if(empty($moduleDefs[$module]) || empty($moduleDefs[$module][$module][$key]))
5354 // It was not loaded.... Fail. Cache null to prevent future repeats of this calculation
5355 sugar_cache_put($cache_key, SugarCache::EXTERNAL_CACHE_NULL_VALUE);
5359 // It has been loaded, cache the result.
5360 sugar_cache_put($cache_key, $moduleDefs[$module][$module][$key]);
5361 return $moduleDefs[$module][$module][$key];
5364 // It was not loaded.... Fail. Cache null to prevent future repeats of this calculation
5365 sugar_cache_put($cache_key, SugarCache::EXTERNAL_CACHE_NULL_VALUE);
5370 * Returns the ACL category for this module; defaults to the SugarBean::$acl_category if defined
5371 * otherwise it is SugarBean::$module_dir
5375 public function getACLCategory()
5377 return !empty($this->acl_category)?$this->acl_category:$this->module_dir;
5381 * Returns the query used for the export functionality for a module. Override this method if you wish
5382 * to have a custom query to pull this data together instead
5384 * @param string $order_by
5385 * @param string $where
5386 * @return string SQL query
5388 public function create_export_query($order_by, $where)
5390 return $this->create_new_list_query($order_by, $where, array(), array(), 0, '', false, $this, true, true);