2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4 * SugarCRM Community Edition is a customer relationship management program developed by
5 * SugarCRM, Inc. Copyright (C) 2004-2011 SugarCRM Inc.
7 * This program is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU Affero General Public License version 3 as published by the
9 * Free Software Foundation with the addition of the following permission added
10 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11 * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
12 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
14 * This program is distributed in the hope that it will be useful, but WITHOUT
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
19 * You should have received a copy of the GNU Affero General Public License along with
20 * this program; if not, see http://www.gnu.org/licenses or write to the Free
21 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
24 * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
25 * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
27 * The interactive user interfaces in modified source and object code versions
28 * of this program must display Appropriate Legal Notices, as required under
29 * Section 5 of the GNU Affero General Public License version 3.
31 * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32 * these Appropriate Legal Notices must retain the display of the "Powered by
33 * SugarCRM" logo. If the display of the logo is not reasonably feasible for
34 * technical reasons, the Appropriate Legal Notices must display the words
35 * "Powered by SugarCRM".
36 ********************************************************************************/
38 /*********************************************************************************
40 * Description: Defines the base class for all data entities used throughout the
41 * application. The base class including its methods and variables is designed to
42 * be overloaded with module-specific methods and variables particular to the
43 * module's base entity class.
44 * Portions created by SugarCRM are Copyright (C) SugarCRM, Inc.
45 * All Rights Reserved.
46 *******************************************************************************/
48 require_once('modules/DynamicFields/DynamicField.php');
49 require_once("data/Relationships/RelationshipFactory.php");
52 * SugarBean is the base class for all business objects in Sugar. It implements
53 * the primary functionality needed for manipulating business objects: create,
54 * retrieve, update, delete. It allows for searching and retrieving list of records.
55 * It allows for retrieving related objects (e.g. contacts related to a specific account).
57 * In the current implementation, there can only be one bean per folder.
58 * Naming convention has the bean name be the same as the module and folder name.
59 * All bean names should be singular (e.g. Contact). The primary table name for
60 * a bean should be plural (e.g. contacts).
66 * A pointer to the database helper object DBHelper
73 * When createing a bean, you can specify a value in the id column as
74 * long as that value is unique. During save, if the system finds an
75 * id, it assumes it is an update. Setting new_with_id to true will
76 * make sure the system performs an insert instead of an update.
78 * @var BOOL -- default false
80 var $new_with_id = false;
84 * Disble vardefs. This should be set to true only for beans that do not have varders. Tracker is an example
86 * @var BOOL -- default false
88 var $disable_vardefs = false;
92 * holds the full name of the user that an item is assigned to. Only used if notifications
93 * are turned on and going to be sent out.
97 var $new_assigned_user_name;
100 * An array of booleans. This array is cleared out when data is loaded.
101 * As date/times are converted, a "1" is placed under the key, the field is converted.
103 * @var Array of booleans
105 var $processed_dates_times = array();
108 * Whether to process date/time fields for storage in the database in GMT
112 var $process_save_dates =true;
115 * This signals to the bean that it is being saved in a mass mode.
116 * Examples of this kind of save are import and mass update.
117 * We turn off notificaitons of this is the case to make things more efficient.
121 var $save_from_post = true;
124 * When running a query on related items using the method: retrieve_by_string_fields
125 * this value will be set to true if more than one item matches the search criteria.
129 var $duplicates_found = false;
132 * The DBManager instance that was used to load this bean and should be used for
133 * future database interactions.
140 * true if this bean has been deleted, false otherwise.
147 * Should the date modified column of the bean be updated during save?
148 * This is used for admin level functionality that should not be updating
149 * the date modified. This is only used by sync to allow for updates to be
150 * replicated in a way that will not cause them to be replicated back.
154 var $update_date_modified = true;
157 * Should the modified by column of the bean be updated during save?
158 * This is used for admin level functionality that should not be updating
159 * the modified by column. This is only used by sync to allow for updates to be
160 * replicated in a way that will not cause them to be replicated back.
164 var $update_modified_by = true;
167 * Setting this to true allows for updates to overwrite the date_entered
171 var $update_date_entered = false;
174 * This allows for seed data to be created without using the current uesr to set the id.
175 * This should be replaced by altering the current user before the call to save.
179 //TODO This should be replaced by altering the current user before the call to save.
180 var $set_created_by = true;
185 * The database table where records of this Bean are stored.
189 var $table_name = '';
192 * This is the singular name of the bean. (i.e. Contact).
196 var $object_name = '';
198 /** Set this to true if you query contains a sub-select and bean is converting both select statements
199 * into count queries.
201 var $ungreedy_count=false;
204 * The name of the module folder for this type of bean.
208 var $module_dir = '';
209 var $module_name = '';
213 var $column_fields = array();
214 var $list_fields = array();
215 var $additional_column_fields = array();
216 var $relationship_fields = array();
217 var $current_notify_user;
218 var $fetched_row=false;
220 var $force_load_details = false;
221 var $optimistic_lock = false;
222 var $disable_custom_fields = false;
223 var $number_formatting_done = false;
224 var $process_field_encrypted=false;
226 * The default ACL type
228 var $acltype = 'module';
231 var $additional_meta_fields = array();
234 * Set to true in the child beans if the module supports importing
236 var $importable = false;
239 * Set to true in the child beans if the module use the special notification template
241 var $special_notification = false;
244 * Set to true if the bean is being dealt with in a workflow
246 var $in_workflow = false;
250 * By default it will be true but if any module is to be kept non visible
251 * to tracker, then its value needs to be overriden in that particular module to false.
254 var $tracker_visibility = true;
257 * Used to pass inner join string to ListView Data.
259 var $listview_inner_join = array();
262 * Set to true in <modules>/Import/views/view.step4.php if a module is being imported
264 var $in_import = false;
266 * Constructor for the bean, it performs following tasks:
268 * 1. Initalized a database connections
269 * 2. Load the vardefs for the module implemeting the class. cache the entries
271 * 3. Setup row-level security preference
272 * All implementing classes must call this constructor using the parent::SugarBean() class.
277 global $dictionary, $current_user;
278 static $loaded_defs = array();
279 $this->db = DBManagerFactory::getInstance();
280 $this->dbManager = DBManagerFactory::getInstance();
281 if (empty($this->module_name))
282 $this->module_name = $this->module_dir;
283 if((false == $this->disable_vardefs && empty($loaded_defs[$this->object_name])) || !empty($GLOBALS['reload_vardefs']))
285 VardefManager::loadVardef($this->module_dir, $this->object_name);
287 // build $this->column_fields from the field_defs if they exist
288 if (!empty($dictionary[$this->object_name]['fields'])) {
289 foreach ($dictionary[$this->object_name]['fields'] as $key=>$value_array) {
290 $column_fields[] = $key;
291 if(!empty($value_array['required']) && !empty($value_array['name'])) {
292 $this->required_fields[$value_array['name']] = 1;
295 $this->column_fields = $column_fields;
298 //setup custom fields
299 if(!isset($this->custom_fields) &&
300 empty($this->disable_custom_fields))
302 $this->setupCustomFields($this->module_dir);
304 //load up field_arrays from CacheHandler;
305 if(empty($this->list_fields))
306 $this->list_fields = $this->_loadCachedArray($this->module_dir, $this->object_name, 'list_fields');
307 if(empty($this->column_fields))
308 $this->column_fields = $this->_loadCachedArray($this->module_dir, $this->object_name, 'column_fields');
309 if(empty($this->required_fields))
310 $this->required_fields = $this->_loadCachedArray($this->module_dir, $this->object_name, 'required_fields');
312 if(isset($GLOBALS['dictionary'][$this->object_name]) && !$this->disable_vardefs)
314 $this->field_name_map = $dictionary[$this->object_name]['fields'];
315 $this->field_defs = $dictionary[$this->object_name]['fields'];
317 if(!empty($dictionary[$this->object_name]['optimistic_locking']))
319 $this->optimistic_lock=true;
322 $loaded_defs[$this->object_name]['column_fields'] =& $this->column_fields;
323 $loaded_defs[$this->object_name]['list_fields'] =& $this->list_fields;
324 $loaded_defs[$this->object_name]['required_fields'] =& $this->required_fields;
325 $loaded_defs[$this->object_name]['field_name_map'] =& $this->field_name_map;
326 $loaded_defs[$this->object_name]['field_defs'] =& $this->field_defs;
330 $this->column_fields =& $loaded_defs[$this->object_name]['column_fields'] ;
331 $this->list_fields =& $loaded_defs[$this->object_name]['list_fields'];
332 $this->required_fields =& $loaded_defs[$this->object_name]['required_fields'];
333 $this->field_name_map =& $loaded_defs[$this->object_name]['field_name_map'];
334 $this->field_defs =& $loaded_defs[$this->object_name]['field_defs'];
335 $this->added_custom_field_defs = true;
337 if(!isset($this->custom_fields) &&
338 empty($this->disable_custom_fields))
340 $this->setupCustomFields($this->module_dir, false);
342 if(!empty($dictionary[$this->object_name]['optimistic_locking']))
344 $this->optimistic_lock=true;
348 if($this->bean_implements('ACL') && !empty($GLOBALS['current_user'])){
349 $this->acl_fields = (isset($dictionary[$this->object_name]['acl_fields']) && $dictionary[$this->object_name]['acl_fields'] === false)?false:true;
351 $this->populateDefaultValues();
356 * Returns the object name. If object_name is not set, table_name is returned.
358 * All implementing classes must set a value for the object_name variable.
360 * @param array $arr row of data fetched from the database.
364 function getObjectName()
366 if ($this->object_name)
367 return $this->object_name;
369 // This is a quick way out. The generated metadata files have the table name
370 // as the key. The correct way to do this is to override this function
371 // in bean and return the object name. That requires changing all the beans
372 // as well as put the object name in the generator.
373 return $this->table_name;
377 * Returns a list of fields with their definitions that have the audited property set to true.
378 * Before calling this function, check whether audit has been enabled for the table/module or not.
379 * You would set the audit flag in the implemting module's vardef file.
381 * @return an array of
382 * @see is_AuditEnabled
384 * Internal function, do not override.
386 function getAuditEnabledFieldDefinitions()
388 $aclcheck = $this->bean_implements('ACL');
389 $is_owner = $this->isOwner($GLOBALS['current_user']->id);
390 if (!isset($this->audit_enabled_fields))
393 $this->audit_enabled_fields=array();
394 foreach ($this->field_defs as $field => $properties)
399 !empty($properties['Audited']) || !empty($properties['audited']))
403 $this->audit_enabled_fields[$field]=$properties;
408 return $this->audit_enabled_fields;
412 * Return true if auditing is enabled for this object
413 * You would set the audit flag in the implemting module's vardef file.
417 * Internal function, do not override.
419 function is_AuditEnabled()
422 if (isset($dictionary[$this->getObjectName()]['audited']))
424 return $dictionary[$this->getObjectName()]['audited'];
435 * Returns the name of the audit table.
436 * Audit table's name is based on implementing class' table name.
438 * @return String Audit table name.
440 * Internal function, do not override.
442 function get_audit_table_name()
444 return $this->getTableName().'_audit';
448 * If auditing is enabled, create the audit table.
450 * Function is used by the install scripts and a repair utility in the admin panel.
452 * Internal function, do not override.
454 function create_audit_table()
457 $table_name=$this->get_audit_table_name();
459 require('metadata/audit_templateMetaData.php');
461 $fieldDefs = $dictionary['audit']['fields'];
462 $indices = $dictionary['audit']['indices'];
463 // '0' stands for the first index for all the audit tables
464 $indices[0]['name'] = 'idx_' . strtolower($this->getTableName()) . '_' . $indices[0]['name'];
465 $indices[1]['name'] = 'idx_' . strtolower($this->getTableName()) . '_' . $indices[1]['name'];
467 if(isset($dictionary['audit']['engine'])) {
468 $engine = $dictionary['audit']['engine'];
469 } else if(isset($dictionary[$this->getObjectName()]['engine'])) {
470 $engine = $dictionary[$this->getObjectName()]['engine'];
473 $sql=$this->dbManager->helper->createTableSQLParams($table_name, $fieldDefs, $indices, $engine);
475 $msg = "Error creating table: ".$table_name. ":";
476 $this->dbManager->query($sql,true,$msg);
480 * Returns the implementing class' table name.
482 * All implementing classes set a value for the table_name variable. This value is returned as the
483 * table name. If not set, table name is extracted from the implementing module's vardef.
485 * @return String Table name.
487 * Internal function, do not override.
489 function getTableName()
492 if(isset($this->table_name))
494 return $this->table_name;
496 return $dictionary[$this->getObjectName()]['table'];
500 * Returns field definitions for the implementing module.
502 * The definitions were loaded in the constructor.
504 * @return Array Field definitions.
506 * Internal function, do not override.
508 function getFieldDefinitions()
510 return $this->field_defs;
514 * Returns index definitions for the implementing module.
516 * The definitions were loaded in the constructor.
518 * @return Array Index definitions.
520 * Internal function, do not override.
522 function getIndices()
525 if(isset($dictionary[$this->getObjectName()]['indices']))
527 return $dictionary[$this->getObjectName()]['indices'];
533 * Returns field definition for the requested field name.
535 * The definitions were loaded in the constructor.
537 * @param string field name,
538 * @return Array Field properties or boolean false if the field doesn't exist
540 * Internal function, do not override.
542 function getFieldDefinition($name)
544 if ( !isset($this->field_defs[$name]) )
547 return $this->field_defs[$name];
551 * Returnss definition for the id field name.
553 * The definitions were loaded in the constructor.
555 * @return Array Field properties.
557 * Internal function, do not override.
559 function getPrimaryFieldDefinition()
561 $def = $this->getFieldDefinition("id");
563 $def = $this->getFieldDefinition(0);
567 * Returns the value for the requested field.
569 * When a row of data is fetched using the bean, all fields are created as variables in the context
570 * of the bean and then fetched values are set in these variables.
572 * @param string field name,
573 * @return varies Field value.
575 * Internal function, do not override.
577 function getFieldValue($name)
579 if (!isset($this->$name)){
582 if($this->$name === TRUE){
585 if($this->$name === FALSE){
592 * Basically undoes the effects of SugarBean::populateDefaultValues(); this method is best called right after object
595 public function unPopulateDefaultValues()
597 if ( !is_array($this->field_defs) )
600 foreach ($this->field_defs as $field => $value) {
601 if( !empty($this->$field)
602 && ((isset($value['default']) && $this->$field == $value['default']) || (!empty($value['display_default']) && $this->$field == $value['display_default']))
604 $this->$field = null;
607 if(!empty($this->$field) && !empty($value['display_default']) && in_array($value['type'], array('date', 'datetime', 'datetimecombo')) &&
608 $this->$field == $this->parseDateDefault($value['display_default'], ($value['type'] != 'date'))) {
609 $this->$field = null;
615 * Create date string from default value
617 * @param string $value
618 * @param bool $time Should be expect time set too?
621 protected function parseDateDefault($value, $time = false)
625 $dtAry = explode('&', $value, 2);
626 $dateValue = $timedate->getNow(true)->modify($dtAry[0]);
627 if(!empty($dtAry[1])) {
628 $timeValue = $timedate->fromString($dtAry[1]);
629 $dateValue->setTime($timeValue->hour, $timeValue->min, $timeValue->sec);
631 return $timedate->asUser($dateValue);
633 return $timedate->asUserDate($timedate->getNow(true)->modify($value));
637 function populateDefaultValues($force=false){
638 if ( !is_array($this->field_defs) )
640 foreach($this->field_defs as $field=>$value){
641 if((isset($value['default']) || !empty($value['display_default'])) && ($force || empty($this->$field))){
642 $type = $value['type'];
646 if(!empty($value['display_default'])){
647 $this->$field = $this->parseDateDefault($value['display_default']);
651 case 'datetimecombo':
652 if(!empty($value['display_default'])){
653 $this->$field = $this->parseDateDefault($value['display_default'], true);
657 if(empty($value['default']) && !empty($value['display_default']))
658 $this->$field = $value['display_default'];
660 $this->$field = $value['default'];
663 if(isset($this->$field)){
667 if ( isset($value['default']) && $value['default'] !== '' ) {
668 $this->$field = htmlentities($value['default'], ENT_QUOTES, 'UTF-8');
679 * Removes relationship metadata cache.
681 * Every module that has relationships defined with other modules, has this meta data cached. The cache is
682 * stores in 2 locations: relationships table and file system. This method clears the cache from both locations.
684 * @param string $key module whose meta cache is to be cleared.
685 * @param string $db database handle.
686 * @param string $tablename table name
687 * @param string $dictionary vardef for the module
688 * @param string $module_dir name of subdirectory where module is installed.
693 * Internal function, do not override.
695 function removeRelationshipMeta($key,$db,$tablename,$dictionary,$module_dir)
697 //load the module dictionary if not supplied.
698 if ((!isset($dictionary) or empty($dictionary)) && !empty($module_dir))
700 $filename='modules/'. $module_dir . '/vardefs.php';
701 if(file_exists($filename))
706 if (!is_array($dictionary) or !array_key_exists($key, $dictionary))
708 $GLOBALS['log']->fatal("removeRelationshipMeta: Metadata for table ".$tablename. " does not exist");
709 display_notice("meta data absent for table ".$tablename." keyed to $key ");
713 if (isset($dictionary[$key]['relationships']))
715 $RelationshipDefs = $dictionary[$key]['relationships'];
716 foreach ($RelationshipDefs as $rel_name)
718 Relationship::delete($rel_name,$db);
726 * This method has been deprecated.
728 * @see removeRelationshipMeta()
729 * @deprecated 4.5.1 - Nov 14, 2006
732 function remove_relationship_meta($key,$db,$log,$tablename,$dictionary,$module_dir)
734 SugarBean::removeRelationshipMeta($key,$db,$tablename,$dictionary,$module_dir);
739 * Populates the relationship meta for a module.
741 * It is called during setup/install. It is used statically to create relationship meta data for many-to-many tables.
743 * @param string $key name of the object.
744 * @param object $db database handle.
745 * @param string $tablename table, meta data is being populated for.
746 * @param array dictionary vardef dictionary for the object. *
747 * @param string module_dir name of subdirectory where module is installed.
748 * @param boolean $iscustom Optional,set to true if module is installed in a custom directory. Default value is false.
751 * Internal function, do not override.
753 function createRelationshipMeta($key,$db,$tablename,$dictionary,$module_dir,$iscustom=false)
755 //load the module dictionary if not supplied.
756 if (empty($dictionary) && !empty($module_dir))
760 $filename='custom/modules/' . $module_dir . '/Ext/Vardefs/vardefs.ext.php';
766 // a very special case for the Employees module
767 // this must be done because the Employees/vardefs.php does an include_once on
769 $filename='modules/Users/vardefs.php';
773 $filename='modules/'. $module_dir . '/vardefs.php';
777 if(file_exists($filename))
780 // cn: bug 7679 - dictionary entries defined as $GLOBALS['name'] not found
781 if(empty($dictionary) || !empty($GLOBALS['dictionary'][$key]))
783 $dictionary = $GLOBALS['dictionary'];
788 $GLOBALS['log']->debug("createRelationshipMeta: no metadata file found" . $filename);
793 if (!is_array($dictionary) or !array_key_exists($key, $dictionary))
795 $GLOBALS['log']->fatal("createRelationshipMeta: Metadata for table ".$tablename. " does not exist");
796 display_notice("meta data absent for table ".$tablename." keyed to $key ");
800 if (isset($dictionary[$key]['relationships']))
803 $RelationshipDefs = $dictionary[$key]['relationships'];
807 $beanList_ucase=array_change_key_case ( $beanList ,CASE_UPPER);
808 foreach ($RelationshipDefs as $rel_name=>$rel_def)
810 if (isset($rel_def['lhs_module']) and !isset($beanList_ucase[strtoupper($rel_def['lhs_module'])])) {
811 $GLOBALS['log']->debug('skipping orphaned relationship record ' . $rel_name . ' lhs module is missing ' . $rel_def['lhs_module']);
814 if (isset($rel_def['rhs_module']) and !isset($beanList_ucase[strtoupper($rel_def['rhs_module'])])) {
815 $GLOBALS['log']->debug('skipping orphaned relationship record ' . $rel_name . ' rhs module is missing ' . $rel_def['rhs_module']);
820 //check whether relationship exists or not first.
821 if (Relationship::exists($rel_name,$db))
823 $GLOBALS['log']->debug('Skipping, reltionship already exists '.$rel_name);
827 $seed = BeanFactory::getBean("Relationships");
828 $keys = array_keys($seed->field_defs);
830 foreach($keys as $key)
834 $toInsert[$key] = create_guid();
836 else if ($key == "relationship_name")
838 $toInsert[$key] = $rel_name;
840 else if (isset($rel_def[$key]))
842 $toInsert[$key] = $rel_def[$key];
844 //todo specify defaults if meta not defined.
848 $column_list = implode(",", array_keys($toInsert));
849 $value_list = "'" . implode("','", array_values($toInsert)) . "'";
851 //create the record. todo add error check.
852 $insert_string = "INSERT into relationships (" .$column_list. ") values (".$value_list.")";
853 $db->query($insert_string, true);
860 //log informational message stating no relationships meta was set for this bean.
866 * This method has been deprecated.
867 * @see createRelationshipMeta()
868 * @deprecated 4.5.1 - Nov 14, 2006
871 function create_relationship_meta($key,&$db,&$log,$tablename,$dictionary,$module_dir)
873 SugarBean::createRelationshipMeta($key,$db,$tablename,$dictionary,$module_dir);
878 * Loads the request relationship. This method should be called before performing any operations on the related data.
880 * This method searches the vardef array for the requested attribute's definition. If the attribute is of the type
881 * link then it creates a similary named variable and loads the relationship definition.
883 * @param string $rel_name relationship/attribute name.
886 function load_relationship($rel_name)
888 $GLOBALS['log']->debug("SugarBean[{$this->object_name}].load_relationships, Loading relationship (".$rel_name.").");
890 if (empty($rel_name))
892 $GLOBALS['log']->error("SugarBean.load_relationships, Null relationship name passed.");
895 $fieldDefs = $this->getFieldDefinitions();
897 //find all definitions of type link.
898 if (!empty($fieldDefs[$rel_name]))
900 //initialize a variable of type Link
901 require_once('data/Link2.php');
902 $class = load_link_class($fieldDefs[$rel_name]);
903 if (isset($this->$rel_name) && $this->$rel_name instanceof $class) {
906 //if rel_name is provided, search the fieldef array keys by name.
907 if (isset($fieldDefs[$rel_name]['type']) && $fieldDefs[$rel_name]['type'] == 'link')
909 if ($class == "Link2")
910 $this->$rel_name = new $class($rel_name, $this);
912 $this->$rel_name = new $class($fieldDefs[$rel_name]['relationship'], $this, $fieldDefs[$rel_name]);
914 if (empty($this->$rel_name) ||
915 (method_exists($this->$rel_name, "loadedSuccesfully") && !$this->$rel_name->loadedSuccesfully()))
917 unset($this->$rel_name);
923 $GLOBALS['log']->debug("SugarBean.load_relationships, Error Loading relationship (".$rel_name.")");
928 * Loads all attributes of type link.
930 * DO NOT CALL THIS FUNCTION IF YOU CAN AVOID IT. Please use load_relationship directly instead.
932 * Method searches the implmenting module's vardef file for attributes of type link, and for each attribute
933 * create a similary named variable and load the relationship definition.
937 * Internal function, do not override.
939 function load_relationships()
941 $GLOBALS['log']->debug("SugarBean.load_relationships, Loading all relationships of type link.");
942 $linked_fields=$this->get_linked_fields();
943 foreach($linked_fields as $name=>$properties)
945 $this->load_relationship($name);
950 * Returns an array of beans of related data.
952 * For instance, if an account is related to 10 contacts , this function will return an array of contacts beans (10)
953 * with each bean representing a contact record.
954 * Method will load the relationship if not done so already.
956 * @param string $field_name relationship to be loaded.
957 * @param string $bean name class name of the related bean.
958 * @param array $sort_array optional, unused
959 * @param int $begin_index Optional, default 0, unused.
960 * @param int $end_index Optional, default -1
961 * @param int $deleted Optional, Default 0, 0 adds deleted=0 filter, 1 adds deleted=1 filter.
962 * @param string $optional_where, Optional, default empty.
964 * Internal function, do not override.
966 function get_linked_beans($field_name,$bean_name, $sort_array = array(), $begin_index = 0, $end_index = -1,
967 $deleted=0, $optional_where="")
969 //if bean_name is Case then use aCase
970 if($bean_name=="Case")
971 $bean_name = "aCase";
973 if($this->load_relationship($field_name)) {
974 if ($this->$field_name instanceof Link) {
975 // some classes are still based on Link, e.g. TeamSetLink
976 return array_values($this->$field_name->getBeans(new $bean_name(), $sort_array, $begin_index, $end_index, $deleted, $optional_where));
979 return array_values($this->$field_name->getBeans());
987 * Returns an array of fields that are of type link.
989 * @return array List of fields.
991 * Internal function, do not override.
993 function get_linked_fields()
996 $linked_fields=array();
998 // require_once('data/Link.php');
1000 $fieldDefs = $this->getFieldDefinitions();
1002 //find all definitions of type link.
1003 if (!empty($fieldDefs))
1005 foreach ($fieldDefs as $name=>$properties)
1007 if (array_search('link',$properties) === 'type')
1009 $linked_fields[$name]=$properties;
1014 return $linked_fields;
1018 * Returns an array of fields that are able to be Imported into
1019 * i.e. 'importable' not set to 'false'
1021 * @return array List of fields.
1023 * Internal function, do not override.
1025 function get_importable_fields()
1027 $importableFields = array();
1029 $fieldDefs= $this->getFieldDefinitions();
1031 if (!empty($fieldDefs)) {
1032 foreach ($fieldDefs as $key=>$value_array)
1034 if ( (isset($value_array['importable'])
1035 && (is_string($value_array['importable']) && $value_array['importable'] == 'false'
1036 || is_bool($value_array['importable']) && $value_array['importable'] == false))
1037 || (isset($value_array['type']) && $value_array['type'] == 'link')
1038 || (isset($value_array['auto_increment'])
1039 && ($value_array['type'] == true || $value_array['type'] == 'true')) ) {
1040 // only allow import if we force it
1041 if (isset($value_array['importable'])
1042 && (is_string($value_array['importable']) && $value_array['importable'] == 'true'
1043 || is_bool($value_array['importable']) && $value_array['importable'] == true)) {
1044 $importableFields[$key]=$value_array;
1049 //Expose the cooresponding id field of a relate field if it is only defined as a link so that users can relate records by id during import
1050 if( isset($value_array['type']) && ($value_array['type'] == 'relate') && isset($value_array['id_name']) )
1052 $idField = $value_array['id_name'];
1053 if( isset($fieldDefs[$idField]) && isset($fieldDefs[$idField]['type'] ) && $fieldDefs[$idField]['type'] == 'link' )
1055 $tmpFieldDefs = $fieldDefs[$idField];
1056 $tmpFieldDefs['vname'] = translate($value_array['vname'], $this->module_dir) . " " . $GLOBALS['app_strings']['LBL_ID'];
1057 $importableFields[$idField]=$tmpFieldDefs;
1061 $importableFields[$key]=$value_array;
1066 return $importableFields;
1070 * Returns an array of fields that are of type relate.
1072 * @return array List of fields.
1074 * Internal function, do not override.
1076 function get_related_fields()
1079 $related_fields=array();
1081 // require_once('data/Link.php');
1083 $fieldDefs = $this->getFieldDefinitions();
1085 //find all definitions of type link.
1086 if (!empty($fieldDefs))
1088 foreach ($fieldDefs as $name=>$properties)
1090 if (array_search('relate',$properties) === 'type')
1092 $related_fields[$name]=$properties;
1097 return $related_fields;
1101 * Returns an array of fields that are required for import
1105 function get_import_required_fields()
1107 $importable_fields = $this->get_importable_fields();
1108 $required_fields = array();
1110 foreach ( $importable_fields as $name => $properties ) {
1111 if ( isset($properties['importable']) && is_string($properties['importable']) && $properties['importable'] == 'required' ) {
1112 $required_fields[$name] = $properties;
1116 return $required_fields;
1120 * Iterates through all the relationships and deletes all records for reach relationship.
1122 * @param string $id Primary key value of the parent reocrd
1124 function delete_linked($id)
1126 $linked_fields=$this->get_linked_fields();
1127 foreach ($linked_fields as $name => $value)
1129 if ($this->load_relationship($name))
1131 $this->$name->delete($id);
1135 $GLOBALS['log']->fatal("error loading relationship $name");
1141 * Creates tables for the module implementing the class.
1142 * If you override this function make sure that your code can handles table creation.
1145 function create_tables()
1149 $key = $this->getObjectName();
1150 if (!array_key_exists($key, $dictionary))
1152 $GLOBALS['log']->fatal("create_tables: Metadata for table ".$this->table_name. " does not exist");
1153 display_notice("meta data absent for table ".$this->table_name." keyed to $key ");
1157 if(!$this->db->tableExists($this->table_name))
1159 $this->dbManager->createTable($this);
1160 if($this->bean_implements('ACL')){
1161 if(!empty($this->acltype)){
1162 ACLAction::addActions($this->getACLCategory(), $this->acltype);
1164 ACLAction::addActions($this->getACLCategory());
1170 echo "Table already exists : $this->table_name<br>";
1172 if($this->is_AuditEnabled()){
1173 if (!$this->db->tableExists($this->get_audit_table_name())) {
1174 $this->create_audit_table();
1182 * Delete the primary table for the module implementing the class.
1183 * If custom fields were added to this table/module, the custom table will be removed too, along with the cache
1184 * entries that define the custom fields.
1187 function drop_tables()
1190 $key = $this->getObjectName();
1191 if (!array_key_exists($key, $dictionary))
1193 $GLOBALS['log']->fatal("drop_tables: Metadata for table ".$this->table_name. " does not exist");
1194 echo "meta data absent for table ".$this->table_name."<br>\n";
1196 if(empty($this->table_name))return;
1197 if ($this->db->tableExists($this->table_name))
1199 $this->dbManager->dropTable($this);
1200 if ($this->db->tableExists($this->table_name. '_cstm'))
1202 $this->dbManager->dropTableName($this->table_name. '_cstm');
1203 DynamicField::deleteCache();
1205 if ($this->db->tableExists($this->get_audit_table_name())) {
1206 $this->dbManager->dropTableName($this->get_audit_table_name());
1215 * Loads the definition of custom fields defined for the module.
1216 * Local file system cache is created as needed.
1218 * @param string $module_name setting up custom fields for this module.
1219 * @param boolean $clean_load Optional, default true, rebuilds the cache if set to true.
1221 function setupCustomFields($module_name, $clean_load=true)
1223 $this->custom_fields = new DynamicField($module_name);
1224 $this->custom_fields->setup($this);
1229 * Cleans char, varchar, text, etc. fields of XSS type materials
1231 function cleanBean() {
1232 foreach($this->field_defs as $key => $def) {
1234 if (isset($def['type'])) {
1237 if(isset($def['dbType']))
1238 $type .= $def['dbType'];
1240 if((strpos($type, 'char') !== false ||
1241 strpos($type, 'text') !== false ||
1245 $str = from_html($this->$key);
1246 // Julian's XSS cleaner
1247 $potentials = clean_xss($str, false);
1249 if(is_array($potentials) && !empty($potentials)) {
1250 foreach($potentials as $bad) {
1251 $str = str_replace($bad, "", $str);
1253 $this->$key = to_html($str);
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)
1314 if (empty($this->date_entered))
1316 $this->date_entered = $this->date_modified;
1318 if($this->set_created_by == true)
1320 // created by should always be this user
1321 $this->created_by = (isset($current_user)) ? $current_user->id : "";
1323 if( $this->new_with_id == false)
1325 $this->id = create_guid();
1327 $query = "INSERT into ";
1332 require_once("data/BeanFactory.php");
1333 BeanFactory::registerBean($this->module_name, $this);
1335 if (empty($GLOBALS['updating_relationships']) && empty($GLOBALS['saving_relationships']) && empty ($GLOBALS['resavingRelatedBeans']))
1337 $GLOBALS['saving_relationships'] = true;
1338 // let subclasses save related field changes
1339 $this->save_relationship_changes($isUpdate);
1340 $GLOBALS['saving_relationships'] = false;
1342 if($isUpdate && !$this->update_date_entered)
1344 unset($this->date_entered);
1346 // call the custom business logic
1347 $custom_logic_arguments['check_notify'] = $check_notify;
1350 $this->call_custom_logic("before_save", $custom_logic_arguments);
1351 unset($custom_logic_arguments);
1353 if(isset($this->custom_fields))
1355 $this->custom_fields->bean = $this;
1356 $this->custom_fields->save($isUpdate);
1359 // use the db independent query generator
1360 $this->preprocess_fields_on_save();
1362 //construct the SQL to create the audit record if auditing is enabled.
1363 $dataChanges=array();
1364 if ($this->is_AuditEnabled())
1366 if ($isUpdate && !isset($this->fetched_row))
1368 $GLOBALS['log']->debug('Auditing: Retrieve was not called, audit record will not be created.');
1372 $dataChanges=$this->dbManager->helper->getDataChanges($this);
1376 $this->_sendNotifications($check_notify);
1378 if ($this->db->dbType == "oci8")
1381 if ($this->db->dbType == 'mysql')
1383 // write out the SQL statement.
1384 $query .= $this->table_name." set ";
1388 foreach($this->field_defs as $field=>$value)
1390 if(!isset($value['source']) || $value['source'] == 'db')
1392 // Do not write out the id field on the update statement.
1393 // We are not allowed to change ids.
1394 if($isUpdate && ('id' == $field))
1396 //custom fields handle there save seperatley
1397 if(isset($this->field_name_map) && !empty($this->field_name_map[$field]['custom_type']))
1400 // Only assign variables that have been set.
1401 if(isset($this->$field))
1403 //bug: 37908 - this is to handle the issue where the bool value is false, but strlen(false) <= so it will
1404 //set the default value. TODO change this code to esend all fields through getFieldValue() like DbHelper->insertSql
1405 if(!empty($value['type']) && $value['type'] == 'bool'){
1406 $this->$field = $this->getFieldValue($field);
1409 if(strlen($this->$field) <= 0)
1411 if(!$isUpdate && isset($value['default']) && (strlen($value['default']) > 0))
1413 $this->$field = $value['default'];
1417 $this->$field = null;
1420 // Try comparing this element with the head element.
1426 if(is_null($this->$field))
1428 $query .= $field."=null";
1432 //added check for ints because sql-server does not like casting varchar with a decimal value
1434 if(isset($value['type']) and $value['type']=='int') {
1435 $query .= $field."=".$this->db->quote($this->$field);
1436 } elseif ( isset($value['len']) ) {
1437 $query .= $field."='".$this->db->quote($this->db->truncate(from_html($this->$field),$value['len']))."'";
1439 $query .= $field."='".$this->db->quote($this->$field)."'";
1448 $query = $query." WHERE ID = '$this->id'";
1449 $GLOBALS['log']->info("Update $this->object_name: ".$query);
1453 $GLOBALS['log']->info("Insert: ".$query);
1455 $GLOBALS['log']->info("Save: $query");
1456 $this->db->query($query, true);
1458 //process if type is set to mssql
1459 if ($this->db->dbType == 'mssql')
1463 // build out the SQL UPDATE statement.
1464 $query = "UPDATE " . $this->table_name." SET ";
1466 foreach($this->field_defs as $field=>$value)
1468 if(!isset($value['source']) || $value['source'] == 'db')
1470 // Do not write out the id field on the update statement.
1471 // We are not allowed to change ids.
1472 if($isUpdate && ('id' == $field))
1475 // If the field is an auto_increment field, then we shouldn't be setting it. This was added
1476 // specially for Bugs and Cases which have a number associated with them.
1477 if ($isUpdate && isset($this->field_name_map[$field]['auto_increment']) &&
1478 $this->field_name_map[$field]['auto_increment'] == true)
1481 //custom fields handle their save seperatley
1482 if(isset($this->field_name_map) && !empty($this->field_name_map[$field]['custom_type']))
1485 // Only assign variables that have been set.
1486 if(isset($this->$field))
1488 //bug: 37908 - this is to handle the issue where the bool value is false, but strlen(false) <= so it will
1489 //set the default value. TODO change this code to esend all fields through getFieldValue() like DbHelper->insertSql
1490 if(!empty($value['type']) && $value['type'] == 'bool'){
1491 $this->$field = $this->getFieldValue($field);
1494 if(strlen($this->$field) <= 0)
1496 if(!$isUpdate && isset($value['default']) && (strlen($value['default']) > 0))
1498 $this->$field = $value['default'];
1502 $this->$field = null;
1505 // Try comparing this element with the head element.
1511 if(is_null($this->$field))
1513 $query .= $field."=null";
1515 elseif ( isset($value['len']) )
1517 $query .= $field."='".$this->db->quote($this->db->truncate(from_html($this->$field),$value['len']))."'";
1521 $query .= $field."='".$this->db->quote($this->$field)."'";
1526 $query = $query." WHERE ID = '$this->id'";
1527 $GLOBALS['log']->info("Update $this->object_name: ".$query);
1533 foreach($this->field_defs as $field=>$value)
1535 if(!isset($value['source']) || $value['source'] == 'db')
1537 // Do not write out the id field on the update statement.
1538 // We are not allowed to change ids.
1539 //if($isUpdate && ('id' == $field)) continue;
1540 //custom fields handle there save seperatley
1542 if(isset($this->field_name_map) && !empty($this->field_name_map[$field]['custom_module']))
1545 // Only assign variables that have been set.
1546 if(isset($this->$field))
1548 //trim the value in case empty space is passed in.
1549 //this will allow default values set in db to take effect, otherwise
1550 //will insert blanks into db
1551 $trimmed_field = trim($this->$field);
1552 //if this value is empty, do not include the field value in statement
1553 if($trimmed_field =='')
1557 //bug: 37908 - this is to handle the issue where the bool value is false, but strlen(false) <= so it will
1558 //set the default value. TODO change this code to esend all fields through getFieldValue() like DbHelper->insertSql
1559 if(!empty($value['type']) && $value['type'] == 'bool'){
1560 $this->$field = $this->getFieldValue($field);
1562 //added check for ints because sql-server does not like casting varchar with a decimal value
1564 if(isset($value['type']) and $value['type']=='int') {
1565 $values[] = $this->db->quote($this->$field);
1566 } elseif ( isset($value['len']) ) {
1567 $values[] = "'".$this->db->quote($this->db->truncate(from_html($this->$field),$value['len']))."'";
1569 $values[] = "'".$this->db->quote($this->$field)."'";
1572 $columns[] = $field;
1576 // build out the SQL INSERT statement.
1577 $query = "INSERT INTO $this->table_name (" .implode("," , $columns). " ) VALUES ( ". implode("," , $values). ')';
1578 $GLOBALS['log']->info("Insert: ".$query);
1581 $GLOBALS['log']->info("Save: $query");
1582 $this->db->query($query, true);
1584 if (!empty($dataChanges) && is_array($dataChanges))
1586 foreach ($dataChanges as $change)
1588 $this->dbManager->helper->save_audit_records($this,$change);
1593 if (empty($GLOBALS['resavingRelatedBeans'])){
1594 SugarRelationship::resaveRelatedBeans();
1598 //If we aren't in setup mode and we have a current user and module, then we track
1599 if(isset($GLOBALS['current_user']) && isset($this->module_dir))
1601 $this->track_view($current_user->id, $this->module_dir, 'save');
1604 $this->call_custom_logic('after_save', '');
1606 //Now that the record has been saved, we don't want to insert again on further saves
1607 $this->new_with_id = false;
1608 $this->in_save = false;
1614 * Performs a check if the record has been modified since the specified date
1616 * @param date $date Datetime for verification
1617 * @param string $modified_user_id User modified by
1619 function has_been_modified_since($date, $modified_user_id)
1621 global $current_user;
1622 if (isset($current_user))
1624 if ($this->db->dbType == 'mssql')
1625 $date_modified_string = 'CONVERT(varchar(20), date_modified, 120)';
1627 $date_modified_string = 'date_modified';
1629 $query = "SELECT date_modified FROM $this->table_name WHERE id='$this->id' AND modified_user_id != '$current_user->id' AND (modified_user_id != '$modified_user_id' OR $date_modified_string > " . db_convert("'".$date."'", 'datetime') . ')';
1630 $result = $this->db->query($query);
1632 if($this->db->fetchByAssoc($result))
1641 * Determines which users receive a notification
1643 function get_notification_recipients() {
1644 $notify_user = new User();
1645 $notify_user->retrieve($this->assigned_user_id);
1646 $this->new_assigned_user_name = $notify_user->full_name;
1648 $GLOBALS['log']->info("Notifications: recipient is $this->new_assigned_user_name");
1650 $user_list = array($notify_user);
1653 //send notifications to followers, but ensure to not query for the assigned_user.
1654 return SugarFollowing::getFollowers($this, $notify_user);
1659 * Handles sending out email notifications when items are first assigned to users
1661 * @param string $notify_user user to notify
1662 * @param string $admin the admin user that sends out the notification
1664 function send_assignment_notifications($notify_user, $admin)
1666 global $current_user;
1668 if(($this->object_name == 'Meeting' || $this->object_name == 'Call') || $notify_user->receive_notifications)
1670 $sendToEmail = $notify_user->emailAddress->getPrimaryAddress($notify_user);
1672 if(empty($sendToEmail)) {
1673 $GLOBALS['log']->warn("Notifications: no e-mail address set for user {$notify_user->user_name}, cancelling send");
1677 $notify_mail = $this->create_notification_email($notify_user);
1678 $notify_mail->setMailerForSystem();
1680 if(empty($admin->settings['notify_send_from_assigning_user'])) {
1681 $notify_mail->From = $admin->settings['notify_fromaddress'];
1682 $notify_mail->FromName = (empty($admin->settings['notify_fromname'])) ? "" : $admin->settings['notify_fromname'];
1684 // Send notifications from the current user's e-mail (ifset)
1685 $fromAddress = $current_user->emailAddress->getReplyToAddress($current_user);
1686 $fromAddress = !empty($fromAddress) ? $fromAddress : $admin->settings['notify_fromaddress'];
1687 $notify_mail->From = $fromAddress;
1688 //Use the users full name is available otherwise default to system name
1689 $from_name = !empty($admin->settings['notify_fromname']) ? $admin->settings['notify_fromname'] : "";
1690 $from_name = !empty($current_user->full_name) ? $current_user->full_name : $from_name;
1691 $notify_mail->FromName = $from_name;
1694 $oe = new OutboundEmail();
1695 $oe = $oe->getUserMailerSettings($current_user);
1696 //only send if smtp server is defined
1698 $smtpVerified = false;
1700 //first check the user settings
1701 if(!empty($oe->mail_smtpserver)){
1702 $smtpVerified = true;
1705 //if still not verified, check against the system settings
1706 if (!$smtpVerified){
1707 $oe = $oe->getSystemMailerSettings();
1708 if(!empty($oe->mail_smtpserver)){
1709 $smtpVerified = true;
1712 //if smtp was not verified against user or system, then do not send out email
1713 if (!$smtpVerified){
1714 $GLOBALS['log']->fatal("Notifications: error sending e-mail, smtp server was not found ");
1719 if(!$notify_mail->Send()) {
1720 $GLOBALS['log']->fatal("Notifications: error sending e-mail (method: {$notify_mail->Mailer}), (error: {$notify_mail->ErrorInfo})");
1722 $GLOBALS['log']->fatal("Notifications: e-mail successfully sent");
1730 * This function handles create the email notifications email.
1731 * @param string $notify_user the user to send the notification email to
1733 function create_notification_email($notify_user) {
1734 global $sugar_version;
1735 global $sugar_config;
1736 global $app_list_strings;
1737 global $current_user;
1740 $OBCharset = $locale->getPrecedentPreference('default_email_charset');
1743 require_once("include/SugarPHPMailer.php");
1745 $notify_address = $notify_user->emailAddress->getPrimaryAddress($notify_user);
1746 $notify_name = $notify_user->full_name;
1747 $GLOBALS['log']->debug("Notifications: user has e-mail defined");
1749 $notify_mail = new SugarPHPMailer();
1750 $notify_mail->AddAddress($notify_address,$locale->translateCharsetMIME(trim($notify_name), 'UTF-8', $OBCharset));
1752 if(empty($_SESSION['authenticated_user_language'])) {
1753 $current_language = $sugar_config['default_language'];
1755 $current_language = $_SESSION['authenticated_user_language'];
1757 $xtpl = new XTemplate(get_notify_template_file($current_language));
1758 if($this->module_dir == "Cases") {
1759 $template_name = "Case"; //we should use Case, you can refer to the en_us.notify_template.html.
1762 $template_name = $beanList[$this->module_dir]; //bug 20637, in workflow this->object_name = strange chars.
1765 $this->current_notify_user = $notify_user;
1767 if(in_array('set_notification_body', get_class_methods($this))) {
1768 $xtpl = $this->set_notification_body($xtpl, $this);
1770 $xtpl->assign("OBJECT", $this->object_name);
1771 $template_name = "Default";
1773 if(!empty($_SESSION["special_notification"]) && $_SESSION["special_notification"]) {
1774 $template_name = $beanList[$this->module_dir].'Special';
1776 if($this->special_notification) {
1777 $template_name = $beanList[$this->module_dir].'Special';
1779 $xtpl->assign("ASSIGNED_USER", $this->new_assigned_user_name);
1780 $xtpl->assign("ASSIGNER", $current_user->name);
1783 if(isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) {
1784 $port = $_SERVER['SERVER_PORT'];
1787 if (!isset($_SERVER['HTTP_HOST'])) {
1788 $_SERVER['HTTP_HOST'] = '';
1791 $httpHost = $_SERVER['HTTP_HOST'];
1793 if($colon = strpos($httpHost, ':')) {
1794 $httpHost = substr($httpHost, 0, $colon);
1797 $parsedSiteUrl = parse_url($sugar_config['site_url']);
1798 $host = $parsedSiteUrl['host'];
1799 if(!isset($parsedSiteUrl['port'])) {
1800 $parsedSiteUrl['port'] = 80;
1803 $port = ($parsedSiteUrl['port'] != 80) ? ":".$parsedSiteUrl['port'] : '';
1804 $path = !empty($parsedSiteUrl['path']) ? $parsedSiteUrl['path'] : "";
1805 $cleanUrl = "{$parsedSiteUrl['scheme']}://{$host}{$port}{$path}";
1807 $xtpl->assign("URL", $cleanUrl."/index.php?module={$this->module_dir}&action=DetailView&record={$this->id}");
1808 $xtpl->assign("SUGAR", "Sugar v{$sugar_version}");
1809 $xtpl->parse($template_name);
1810 $xtpl->parse($template_name . "_Subject");
1812 $notify_mail->Body = from_html(trim($xtpl->text($template_name)));
1813 $notify_mail->Subject = from_html($xtpl->text($template_name . "_Subject"));
1815 // cn: bug 8568 encode notify email in User's outbound email encoding
1816 $notify_mail->prepForOutbound();
1818 return $notify_mail;
1822 * This function is a good location to save changes that have been made to a relationship.
1823 * This should be overriden in subclasses that have something to save.
1825 * @param $is_update true if this save is an update.
1827 function save_relationship_changes($is_update, $exclude=array())
1829 $new_rel_id = false;
1830 $new_rel_link = false;
1832 //this allows us to dynamically relate modules without adding it to the relationship_fields array
1833 if(!empty($_REQUEST['relate_id']) && !empty($_REQUEST['relate_to']) && !in_array($_REQUEST['relate_to'], $exclude) && $_REQUEST['relate_id'] != $this->id){
1834 $new_rel_id = $_REQUEST['relate_id'];
1835 $new_rel_relname = $_REQUEST['relate_to'];
1836 if(!empty($this->in_workflow) && !empty($this->not_use_rel_in_req)) {
1837 $new_rel_id = !empty($this->new_rel_id) ? $this->new_rel_id : '';
1838 $new_rel_relname = !empty($this->new_rel_relname) ? $this->new_rel_relname : '';
1840 $new_rel_link = $new_rel_relname;
1841 //Try to find the link in this bean based on the relationship
1842 foreach ( $this->field_defs as $key => $def ) {
1843 if (isset($def['type']) && $def['type'] == 'link'
1844 && isset($def['relationship']) && $def['relationship'] == $new_rel_relname) {
1845 $new_rel_link = $key;
1851 // First we handle the preset fields listed in the fixed relationship_fields array hardcoded into the OOB beans
1852 // TODO: remove this mechanism and replace with mechanism exclusively based on the vardefs
1853 if (isset($this->relationship_fields) && is_array($this->relationship_fields))
1855 foreach ($this->relationship_fields as $id=>$rel_name)
1858 if(in_array($id, $exclude))continue;
1860 if(!empty($this->$id))
1862 $GLOBALS['log']->debug('save_relationship_changes(): From relationship_field array - adding a relationship record: '.$rel_name . ' = ' . $this->$id);
1863 //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
1864 if($this->$id == $new_rel_id){
1865 $new_rel_id = false;
1867 $this->load_relationship($rel_name);
1868 $this->$rel_name->add($this->$id);
1873 //if before value is not empty then attempt to delete relationship
1874 if(!empty($this->rel_fields_before_value[$id]))
1876 $GLOBALS['log']->debug('save_relationship_changes(): From relationship_field array - attempting to remove the relationship record, using relationship attribute'.$rel_name);
1877 $this->load_relationship($rel_name);
1878 $this->$rel_name->delete($this->id,$this->rel_fields_before_value[$id]);
1884 /* Next, we'll attempt to update all of the remaining relate fields in the vardefs that have 'save' set in their field_def
1885 Only the 'save' fields should be saved as some vardef entries today are not for display only purposes and break the application if saved
1886 If the vardef has entries for field <a> of type relate, where a->id_name = <b> and field <b> of type link
1887 then we receive a value for b from the MVC in the _REQUEST, and it should be set in the bean as $this->$b
1890 foreach ( $this->field_defs as $def )
1892 if ($def [ 'type' ] == 'relate' && isset ( $def [ 'id_name'] ) && isset ( $def [ 'link'] ) && isset ( $def[ 'save' ]) )
1894 if ( in_array( $def['id_name'], $exclude) || in_array( $def['id_name'], $this->relationship_fields ) )
1895 continue ; // continue to honor the exclude array and exclude any relationships that will be handled by the relationship_fields mechanism
1897 $linkField = $def [ 'link' ] ;
1898 if (isset( $this->field_defs[$linkField ] ))
1900 $linkfield = $this->field_defs[$linkField] ;
1902 if ($this->load_relationship ( $linkField))
1904 $idName = $def['id_name'];
1906 if (!empty($this->rel_fields_before_value[$idName]) && empty($this->$idName))
1908 //if before value is not empty then attempt to delete relationship
1909 $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' ]]}");
1910 $this->$def ['link' ]->delete($this->id, $this->rel_fields_before_value[$def [ 'id_name' ]] );
1913 if (!empty($this->$idName) && is_string($this->$idName))
1915 $GLOBALS['log']->debug("save_relationship_changes(): From field_defs - attempting to add a relationship record - {$def [ 'link' ]} = {$this->$def [ 'id_name' ]}" );
1917 $this->$linkField->add($this->$idName);
1920 $GLOBALS['log']->fatal("Failed to load relationship {$linkField} while saving {$this->module_dir}");
1926 // Finally, we update a field listed in the _REQUEST['*/relate_id']/_REQUEST['relate_to'] mechanism (if it hasn't already been updated above)
1927 if(!empty($new_rel_id)){
1929 if($this->load_relationship($new_rel_link)){
1930 $this->$new_rel_link->add($new_rel_id);
1933 $lower_link = strtolower($new_rel_link);
1934 if($this->load_relationship($lower_link)){
1935 $this->$lower_link->add($new_rel_id);
1938 require_once('data/Link2.php');
1939 $rel = Relationship::retrieve_by_modules($new_rel_link, $this->module_dir, $GLOBALS['db'], 'many-to-many');
1942 foreach($this->field_defs as $field=>$def){
1943 if($def['type'] == 'link' && !empty($def['relationship']) && $def['relationship'] == $rel){
1944 $this->load_relationship($field);
1945 $this->$field->add($new_rel_id);
1951 //ok so we didn't find it in the field defs let's save it anyway if we have the relationshp
1953 $this->$rel=new Link2($rel, $this, array());
1954 $this->$rel->add($new_rel_id);
1963 * This function retrieves a record of the appropriate type from the DB.
1964 * It fills in all of the fields from the DB into the object it was called on.
1966 * @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.
1967 * @return this - The object that it was called apon or null if exactly 1 record was not found.
1971 function check_date_relationships_load()
1973 global $disable_date_format;
1975 if (empty($timedate))
1976 $timedate=TimeDate::getInstance();
1978 if(empty($this->field_defs))
1982 foreach($this->field_defs as $fieldDef)
1984 $field = $fieldDef['name'];
1985 if(!isset($this->processed_dates_times[$field]))
1987 $this->processed_dates_times[$field] = '1';
1988 if(empty($this->$field)) continue;
1989 if($field == 'date_modified' || $field == 'date_entered')
1991 $this->$field = from_db_convert($this->$field, 'datetime');
1992 if(empty($disable_date_format)) {
1993 $this->$field = $timedate->to_display_date_time($this->$field);
1996 elseif(isset($this->field_name_map[$field]['type']))
1998 $type = $this->field_name_map[$field]['type'];
2000 if($type == 'relate' && isset($this->field_name_map[$field]['custom_module']))
2002 $type = $this->field_name_map[$field]['type'];
2007 $this->$field = from_db_convert($this->$field, 'date');
2009 if($this->$field == '0000-00-00')
2012 } elseif(!empty($this->field_name_map[$field]['rel_field']))
2014 $rel_field = $this->field_name_map[$field]['rel_field'];
2016 if(!empty($this->$rel_field))
2018 $this->$rel_field=from_db_convert($this->$rel_field, 'time');
2019 if(empty($disable_date_format)) {
2020 $mergetime = $timedate->merge_date_time($this->$field,$this->$rel_field);
2021 $this->$field = $timedate->to_display_date($mergetime);
2022 $this->$rel_field = $timedate->to_display_time($mergetime);
2028 if(empty($disable_date_format)) {
2029 $this->$field = $timedate->to_display_date($this->$field, false);
2032 } elseif($type == 'datetime' || $type == 'datetimecombo')
2034 if($this->$field == '0000-00-00 00:00:00')
2040 $this->$field = from_db_convert($this->$field, 'datetime');
2041 if(empty($disable_date_format)) {
2042 $this->$field = $timedate->to_display_date_time($this->$field, true, true);
2045 } elseif($type == 'time')
2047 if($this->$field == '00:00:00')
2052 //$this->$field = from_db_convert($this->$field, 'time');
2053 if(empty($this->field_name_map[$field]['rel_field']) && empty($disable_date_format))
2055 $this->$field = $timedate->to_display_time($this->$field,true, false);
2058 } elseif($type == 'encrypt' && empty($disable_date_format)){
2059 $this->$field = $this->decrypt_after_retrieve($this->$field);
2067 * This function processes the fields before save.
2068 * Interal function, do not override.
2070 function preprocess_fields_on_save()
2072 $GLOBALS['log']->deprecated('SugarBean.php: preprocess_fields_on_save() is deprecated');
2076 * Removes formatting from values posted from the user interface.
2077 * It only unformats numbers. Function relies on user/system prefernce for format strings.
2079 * Internal Function, do not override.
2081 function unformat_all_fields()
2083 $GLOBALS['log']->deprecated('SugarBean.php: unformat_all_fields() is deprecated');
2087 * This functions adds formatting to all number fields before presenting them to user interface.
2089 * Internal function, do not override.
2091 function format_all_fields()
2093 $GLOBALS['log']->deprecated('SugarBean.php: format_all_fields() is deprecated');
2096 function format_field($fieldDef)
2098 $GLOBALS['log']->deprecated('SugarBean.php: format_field() is deprecated');
2102 * Function corrects any bad formatting done by 3rd party/custom code
2104 * 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
2106 function fixUpFormatting()
2109 static $boolean_false_values = array('off', 'false', '0', 'no');
2112 foreach($this->field_defs as $field=>$def)
2114 if ( !isset($this->$field) ) {
2117 if ( (isset($def['source'])&&$def['source']=='non-db') || $field == 'deleted' ) {
2120 if ( isset($this->fetched_row[$field]) && $this->$field == $this->fetched_row[$field] ) {
2121 // Don't hand out warnings because the field was untouched between retrieval and saving, most database drivers hand pretty much everything back as strings.
2124 $reformatted = false;
2125 switch($def['type']) {
2127 case 'datetimecombo':
2128 if(empty($this->$field)) break;
2129 if ($this->$field == 'NULL') {
2133 if ( ! preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$/',$this->$field) ) {
2134 // This appears to be formatted in user date/time
2135 $this->$field = $timedate->to_db($this->$field);
2136 $reformatted = true;
2140 if(empty($this->$field)) break;
2141 if ($this->$field == 'NULL') {
2145 if ( ! preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/',$this->$field) ) {
2146 // This date appears to be formatted in the user's format
2147 $this->$field = $timedate->to_db_date($this->$field, false);
2148 $reformatted = true;
2152 if(empty($this->$field)) break;
2153 if ($this->$field == 'NULL') {
2157 if ( preg_match('/(am|pm)/i',$this->$field) ) {
2158 // This time appears to be formatted in the user's format
2159 $this->$field = $timedate->fromUserTime($this->$field)->format(TimeDate::DB_TIME_FORMAT);
2160 $reformatted = true;
2167 if ( $this->$field === '' || $this->$field == NULL || $this->$field == 'NULL') {
2170 if ( is_string($this->$field) ) {
2171 $this->$field = (float)unformat_number($this->$field);
2172 $reformatted = true;
2181 if ( $this->$field === '' || $this->$field == NULL || $this->$field == 'NULL') {
2184 if ( is_string($this->$field) ) {
2185 $this->$field = (int)unformat_number($this->$field);
2186 $reformatted = true;
2190 if (empty($this->$field)) {
2191 $this->$field = false;
2192 } else if(true === $this->$field || 1 == $this->$field) {
2193 $this->$field = true;
2194 } else if(in_array(strval($this->$field), $boolean_false_values)) {
2195 $this->$field = false;
2196 $reformatted = true;
2198 $this->$field = true;
2199 $reformatted = true;
2203 $this->$field = $this->encrpyt_before_save($this->$field);
2206 if ( $reformatted ) {
2207 $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');
2214 * Function fetches a single row of data given the primary key value.
2216 * The fetched data is then set into the bean. The function also processes the fetched data by formattig
2217 * date/time and numeric values.
2219 * @param string $id Optional, default -1, is set to -1 id value from the bean is used, else, passed value is used
2220 * @param boolean $encode Optional, default true, encodes the values fetched from the database.
2221 * @param boolean $deleted Optional, default true, if set to false deleted filter will not be added.
2223 * Internal function, do not override.
2225 function retrieve($id = -1, $encode=true,$deleted=true)
2228 $custom_logic_arguments['id'] = $id;
2229 $this->call_custom_logic('before_retrieve', $custom_logic_arguments);
2235 if(isset($this->custom_fields))
2237 $custom_join = $this->custom_fields->getJOIN();
2240 $custom_join = false;
2244 $query = "SELECT $this->table_name.*". $custom_join['select']. " FROM $this->table_name ";
2248 $query = "SELECT $this->table_name.* FROM $this->table_name ";
2253 $query .= ' ' . $custom_join['join'];
2255 $query .= " WHERE $this->table_name.id = '$id' ";
2256 if ($deleted) $query .= " AND $this->table_name.deleted=0";
2257 $GLOBALS['log']->debug("Retrieve $this->object_name : ".$query);
2258 //requireSingleResult has beeen deprecated.
2259 //$result = $this->db->requireSingleResult($query, true, "Retrieving record by id $this->table_name:$id found ");
2260 $result = $this->db->limitQuery($query,0,1,true, "Retrieving record by id $this->table_name:$id found ");
2266 $row = $this->db->fetchByAssoc($result, -1, $encode);
2272 //make copy of the fetched row for construction of audit record and for business logic/workflow
2273 $this->fetched_row=$row;
2274 $this->populateFromRow($row);
2276 global $module, $action;
2277 //Just to get optimistic locking working for this release
2278 if($this->optimistic_lock && $module == $this->module_dir && $action =='EditView' )
2280 $_SESSION['o_lock_id']= $id;
2281 $_SESSION['o_lock_dm']= $this->date_modified;
2282 $_SESSION['o_lock_on'] = $this->object_name;
2284 $this->processed_dates_times = array();
2285 $this->check_date_relationships_load();
2289 $this->custom_fields->fill_relationships();
2292 $this->fill_in_additional_detail_fields();
2293 $this->fill_in_relationship_fields();
2294 //make a copy of fields in the relatiosnhip_fields array. these field values will be used to
2295 //clear relatioship.
2296 foreach ( $this->field_defs as $key => $def )
2298 if ($def [ 'type' ] == 'relate' && isset ( $def [ 'id_name'] ) && isset ( $def [ 'link'] ) && isset ( $def[ 'save' ])) {
2299 if (isset($this->$key)) {
2300 $this->rel_fields_before_value[$key]=$this->$key;
2301 if (isset($this->$def [ 'id_name']))
2302 $this->rel_fields_before_value[$def [ 'id_name']]=$this->$def [ 'id_name'];
2305 $this->rel_fields_before_value[$key]=null;
2308 if (isset($this->relationship_fields) && is_array($this->relationship_fields))
2310 foreach ($this->relationship_fields as $rel_id=>$rel_name)
2312 if (isset($this->$rel_id))
2313 $this->rel_fields_before_value[$rel_id]=$this->$rel_id;
2315 $this->rel_fields_before_value[$rel_id]=null;
2319 // call the custom business logic
2320 $custom_logic_arguments['id'] = $id;
2321 $custom_logic_arguments['encode'] = $encode;
2322 $this->call_custom_logic("after_retrieve", $custom_logic_arguments);
2323 unset($custom_logic_arguments);
2328 * Sets value from fetched row into the bean.
2330 * @param array $row Fetched row
2331 * @todo loop through vardefs instead
2332 * @internal runs into an issue when populating from field_defs for users - corrupts user prefs
2334 * Internal function, do not override.
2336 function populateFromRow($row)
2339 foreach($this->field_defs as $field=>$field_value)
2341 if($field == 'user_preferences' && $this->module_dir == 'Users')
2343 $rfield = $field; // fetch returns it in lowercase only
2344 if(isset($row[$rfield]))
2346 $this->$field = $row[$rfield];
2347 $owner = $rfield . '_owner';
2348 if(!empty($row[$owner])){
2349 $this->$owner = $row[$owner];
2354 $this->$field = $nullvalue;
2362 * Add any required joins to the list count query. The joins are required if there
2363 * is a field in the $where clause that needs to be joined.
2365 * @param string $query
2366 * @param string $where
2368 * Internal Function, do Not override.
2370 function add_list_count_joins(&$query, $where)
2372 $custom_join = $this->custom_fields->getJOIN();
2375 $query .= $custom_join['join'];
2381 * Changes the select expression of the given query to be 'count(*)' so you
2382 * can get the number of items the query will return. This is used to
2383 * populate the upper limit on ListViews.
2385 * @param string $query Select query string
2386 * @return string count query
2388 * Internal function, do not override.
2390 function create_list_count_query($query)
2392 // remove the 'order by' clause which is expected to be at the end of the query
2393 $pattern = '/\sORDER BY.*/is'; // ignores the case
2395 $query = preg_replace($pattern, $replacement, $query);
2396 //handle distinct clause
2398 if(substr_count(strtolower($query), 'distinct')){
2399 if (!empty($this->seed) && !empty($this->seed->table_name ))
2400 $star = 'DISTINCT ' . $this->seed->table_name . '.id';
2402 $star = 'DISTINCT ' . $this->table_name . '.id';
2406 // change the select expression to 'count(*)'
2407 $pattern = '/SELECT(.*?)(\s){1}FROM(\s){1}/is'; // ignores the case
2408 $replacement = 'SELECT count(' . $star . ') c FROM ';
2410 //if the passed query has union clause then replace all instances of the pattern.
2411 //this is very rare. I have seen this happening only from projects module.
2412 //in addition to this added a condition that has union clause and uses
2414 if (strstr($query," UNION ALL ") !== false) {
2416 //seperate out all the queries.
2417 $union_qs=explode(" UNION ALL ", $query);
2418 foreach ($union_qs as $key=>$union_query) {
2420 preg_match($pattern, $union_query, $matches);
2421 if (!empty($matches)) {
2422 if (stristr($matches[0], "distinct")) {
2423 if (!empty($this->seed) && !empty($this->seed->table_name ))
2424 $star = 'DISTINCT ' . $this->seed->table_name . '.id';
2426 $star = 'DISTINCT ' . $this->table_name . '.id';
2429 $replacement = 'SELECT count(' . $star . ') c FROM ';
2430 $union_qs[$key] = preg_replace($pattern, $replacement, $union_query,1);
2432 $modified_select_query=implode(" UNION ALL ",$union_qs);
2434 $modified_select_query = preg_replace($pattern, $replacement, $query,1);
2438 return $modified_select_query;
2442 * This function returns a paged list of the current object type. It is intended to allow for
2443 * hopping back and forth through pages of data. It only retrieves what is on the current page.
2445 * @internal This method must be called on a new instance. It trashes the values of all the fields in the current one.
2446 * @param string $order_by
2447 * @param string $where Additional where clause
2448 * @param int $row_offset Optaional,default 0, starting row number
2449 * @param init $limit Optional, default -1
2450 * @param int $max Optional, default -1
2451 * @param boolean $show_deleted Optioanl, default 0, if set to 1 system will show deleted records.
2452 * @return array Fetched data.
2454 * Internal function, do not override.
2457 function get_list($order_by = "", $where = "", $row_offset = 0, $limit=-1, $max=-1, $show_deleted = 0, $singleSelect=false)
2459 $GLOBALS['log']->debug("get_list: order_by = '$order_by' and where = '$where' and limit = '$limit'");
2460 if(isset($_SESSION['show_deleted']))
2464 $order_by=$this->process_order_by($order_by, null);
2466 if($this->bean_implements('ACL') && ACLController::requireOwner($this->module_dir, 'list') )
2468 global $current_user;
2469 $owner_where = $this->getOwnerWhere($current_user->id);
2471 //rrs - because $this->getOwnerWhere() can return '' we need to be sure to check for it and
2472 //handle it properly else you could get into a situation where you are create a where stmt like
2474 if(!empty($owner_where)){
2476 $where = $owner_where;
2478 $where .= ' AND '. $owner_where;
2482 $query = $this->create_new_list_query($order_by, $where,array(),array(), $show_deleted,'',false,null,$singleSelect);
2483 return $this->process_list_query($query, $row_offset, $limit, $max, $where);
2487 * Prefixes column names with this bean's table name.
2488 * This call can be ignored for mysql since it does a better job than Oracle in resolving ambiguity.
2490 * @param string $order_by Order by clause to be processed
2491 * @param string $submodule name of the module this order by clause is for
2492 * @return string Processed order by clause
2494 * Internal function, do not override.
2496 function process_order_by ($order_by, $submodule)
2498 if (empty($order_by))
2501 //submodule is empty,this is for list object in focus
2502 if (empty($submodule))
2504 $bean_queried = &$this;
2508 //submodule is set, so this is for subpanel, use submodule
2509 $bean_queried = $submodule;
2511 $elements = explode(',',$order_by);
2512 foreach ($elements as $key=>$value)
2514 if (strchr($value,'.') === false)
2516 //value might have ascending and descending decorations
2517 $list_column = explode(' ',trim($value));
2518 if (isset($list_column[0]))
2520 $list_column_name=trim($list_column[0]);
2521 if (isset($bean_queried->field_defs[$list_column_name]))
2523 $source=isset($bean_queried->field_defs[$list_column_name]['source']) ? $bean_queried->field_defs[$list_column_name]['source']:'db';
2524 if (empty($bean_queried->field_defs[$list_column_name]['table']) && $source=='db')
2526 $list_column[0] = $bean_queried->table_name .".".$list_column[0] ;
2528 if (empty($bean_queried->field_defs[$list_column_name]['table']) && $source=='custom_fields')
2530 $list_column[0] = $bean_queried->table_name ."_cstm.".$list_column[0] ;
2532 $value = implode($list_column,' ');
2533 // Bug 38803 - Use CONVERT() function when doing an order by on ntext, text, and image fields
2534 if ( $this->db->dbType == 'mssql'
2535 && $source != 'non-db'
2537 $this->db->getHelper()->getColumnType($this->db->getHelper()->getFieldType($bean_queried->field_defs[$list_column_name])),
2538 array('ntext','text','image')
2541 $value = "CONVERT(varchar(500),{$list_column[0]}) {$list_column[1]}";
2543 // Bug 29011 - Use TO_CHAR() function when doing an order by on a clob field
2544 if ( $this->db->dbType == 'oci8'
2545 && $source != 'non-db'
2547 $this->db->getHelper()->getColumnType($this->db->getHelper()->getFieldType($bean_queried->field_defs[$list_column_name])),
2551 $value = "TO_CHAR({$list_column[0]}) {$list_column[1]}";
2556 $GLOBALS['log']->debug("process_order_by: ($list_column[0]) does not have a vardef entry.");
2560 $elements[$key]=$value;
2562 return implode($elements,',');
2568 * Returns a detail object like retrieving of the current object type.
2570 * It is intended for use in navigation buttons on the DetailView. It will pass an offset and limit argument to the sql query.
2571 * @internal This method must be called on a new instance. It overrides the values of all the fields in the current one.
2573 * @param string $order_by
2574 * @param string $where Additional where clause
2575 * @param int $row_offset Optaional,default 0, starting row number
2576 * @param init $limit Optional, default -1
2577 * @param int $max Optional, default -1
2578 * @param boolean $show_deleted Optioanl, default 0, if set to 1 system will show deleted records.
2579 * @return array Fetched data.
2581 * Internal function, do not override.
2583 function get_detail($order_by = "", $where = "", $offset = 0, $row_offset = 0, $limit=-1, $max=-1, $show_deleted = 0)
2585 $GLOBALS['log']->debug("get_detail: order_by = '$order_by' and where = '$where' and limit = '$limit' and offset = '$offset'");
2586 if(isset($_SESSION['show_deleted']))
2591 if($this->bean_implements('ACL') && ACLController::requireOwner($this->module_dir, 'list') )
2593 global $current_user;
2594 $owner_where = $this->getOwnerWhere($current_user->id);
2598 $where = $owner_where;
2602 $where .= ' AND '. $owner_where;
2605 $query = $this->create_new_list_query($order_by, $where,array(),array(), $show_deleted, $offset);
2607 //Add Limit and Offset to query
2608 //$query .= " LIMIT 1 OFFSET $offset";
2610 return $this->process_detail_query($query, $row_offset, $limit, $max, $where, $offset);
2614 * Fetches data from all related tables.
2616 * @param object $child_seed
2617 * @param string $related_field_name relation to fetch data for
2618 * @param string $order_by Optional, default empty
2619 * @param string $where Optional, additional where clause
2620 * @return array Fetched data.
2622 * Internal function, do not override.
2624 function get_related_list($child_seed,$related_field_name, $order_by = "", $where = "",
2625 $row_offset = 0, $limit=-1, $max=-1, $show_deleted = 0)
2627 global $layout_edit_mode;
2628 if(isset($layout_edit_mode) && $layout_edit_mode)
2630 $response = array();
2631 $child_seed->assign_display_fields($child_seed->module_dir);
2632 $response['list'] = array($child_seed);
2633 $response['row_count'] = 1;
2634 $response['next_offset'] = 0;
2635 $response['previous_offset'] = 0;
2639 $GLOBALS['log']->debug("get_related_list: order_by = '$order_by' and where = '$where' and limit = '$limit'");
2640 if(isset($_SESSION['show_deleted']))
2645 $this->load_relationship($related_field_name);
2646 $query_array = $this->$related_field_name->getQuery(true);
2647 $entire_where = $query_array['where'];
2650 if(empty($entire_where))
2652 $entire_where = ' WHERE ' . $where;
2656 $entire_where .= ' AND ' . $where;
2660 $query = 'SELECT '.$child_seed->table_name.'.* ' . $query_array['from'] . ' ' . $entire_where;
2661 if(!empty($order_by))
2663 $query .= " ORDER BY " . $order_by;
2666 return $child_seed->process_list_query($query, $row_offset, $limit, $max, $where);
2670 protected static function build_sub_queries_for_union($subpanel_list, $subpanel_def, $parentbean, $order_by)
2672 global $layout_edit_mode, $beanFiles, $beanList;
2673 $subqueries = array();
2674 foreach($subpanel_list as $this_subpanel)
2676 if(!$this_subpanel->isDatasourceFunction() || ($this_subpanel->isDatasourceFunction()
2677 && isset($this_subpanel->_instance_properties['generate_select'])
2678 && $this_subpanel->_instance_properties['generate_select']==true))
2680 //the custom query function must return an array with
2681 if ($this_subpanel->isDatasourceFunction()) {
2682 $shortcut_function_name = $this_subpanel->get_data_source_name();
2683 $parameters=$this_subpanel->get_function_parameters();
2684 if (!empty($parameters))
2686 //if the import file function is set, then import the file to call the custom function from
2687 if (is_array($parameters) && isset($parameters['import_function_file'])){
2688 //this call may happen multiple times, so only require if function does not exist
2689 if(!function_exists($shortcut_function_name)){
2690 require_once($parameters['import_function_file']);
2692 //call function from required file
2693 $query_array = $shortcut_function_name($parameters);
2695 //call function from parent bean
2696 $query_array = $parentbean->$shortcut_function_name($parameters);
2701 $query_array = $parentbean->$shortcut_function_name();
2704 $related_field_name = $this_subpanel->get_data_source_name();
2705 if (!$parentbean->load_relationship($related_field_name)){
2706 unset ($parentbean->$related_field_name);
2709 $query_array = $parentbean->$related_field_name->getSubpanelQuery(array(), true);
2711 $table_where = $this_subpanel->get_where();
2712 $where_definition = $query_array['where'];
2714 if(!empty($table_where))
2716 if(empty($where_definition))
2718 $where_definition = $table_where;
2722 $where_definition .= ' AND ' . $table_where;
2726 $submodulename = $this_subpanel->_instance_properties['module'];
2727 $submoduleclass = $beanList[$submodulename];
2728 //require_once($beanFiles[$submoduleclass]);
2729 $submodule = new $submoduleclass();
2730 $subwhere = $where_definition;
2734 $subwhere = str_replace('WHERE', '', $subwhere);
2735 $list_fields = $this_subpanel->get_list_fields();
2736 foreach($list_fields as $list_key=>$list_field)
2738 if(isset($list_field['usage']) && $list_field['usage'] == 'display_only')
2740 unset($list_fields[$list_key]);
2745 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'))
2747 $order_by = $submodule->table_name .'.'. $order_by;
2749 $table_name = $this_subpanel->table_name;
2750 $panel_name=$this_subpanel->name;
2752 $params['distinct'] = $this_subpanel->distinct_query();
2754 $params['joined_tables'] = $query_array['join_tables'];
2755 $params['include_custom_fields'] = !$subpanel_def->isCollection();
2756 $params['collection_list'] = $subpanel_def->get_inst_prop_value('collection_list');
2758 $subquery = $submodule->create_new_list_query('',$subwhere ,$list_fields,$params, 0,'', true,$parentbean);
2760 $subquery['select'] = $subquery['select']." , '$panel_name' panel_name ";
2761 $subquery['from'] = $subquery['from'].$query_array['join'];
2762 $subquery['query_array'] = $query_array;
2763 $subquery['params'] = $params;
2765 $subqueries[] = $subquery;
2772 * Constructs a query to fetch data for supanels and list views
2774 * It constructs union queries for activities subpanel.
2776 * @param SugarBean $parentbean constructing queries for link attributes in this bean
2777 * @param string $order_by Optional, order by clause
2778 * @param string $sort_order Optional, sort order
2779 * @param string $where Optional, additional where clause
2781 * Internal Function, do not overide.
2783 function get_union_related_list($parentbean, $order_by = "", $sort_order='', $where = "",
2784 $row_offset = 0, $limit=-1, $max=-1, $show_deleted = 0, $subpanel_def)
2786 $secondary_queries = array();
2787 global $layout_edit_mode, $beanFiles, $beanList;
2789 if(isset($_SESSION['show_deleted']))
2794 $final_query_rows = '';
2795 $subpanel_list=array();
2796 if ($subpanel_def->isCollection())
2798 $subpanel_def->load_sub_subpanels();
2799 $subpanel_list=$subpanel_def->sub_subpanels;
2803 $subpanel_list[]=$subpanel_def;
2808 //Breaking the building process into two loops. The first loop gets a list of all the sub-queries.
2809 //The second loop merges the queries and forces them to select the same number of columns
2810 //All columns in a sub-subpanel group must have the same aliases
2811 //If the subpanel is a datasource function, it can't be a collection so we just poll that function for the and return that
2812 foreach($subpanel_list as $this_subpanel)
2814 if($this_subpanel->isDatasourceFunction() && empty($this_subpanel->_instance_properties['generate_select']))
2816 $shortcut_function_name = $this_subpanel->get_data_source_name();
2817 $parameters=$this_subpanel->get_function_parameters();
2818 if (!empty($parameters))
2820 //if the import file function is set, then import the file to call the custom function from
2821 if (is_array($parameters) && isset($parameters['import_function_file'])){
2822 //this call may happen multiple times, so only require if function does not exist
2823 if(!function_exists($shortcut_function_name)){
2824 require_once($parameters['import_function_file']);
2826 //call function from required file
2827 $tmp_final_query = $shortcut_function_name($parameters);
2829 //call function from parent bean
2830 $tmp_final_query = $parentbean->$shortcut_function_name($parameters);
2833 $tmp_final_query = $parentbean->$shortcut_function_name();
2837 $final_query_rows .= ' UNION ALL ( '.$parentbean->create_list_count_query($tmp_final_query, $parameters) . ' )';
2838 $final_query .= ' UNION ALL ( '.$tmp_final_query . ' )';
2840 $final_query_rows = '(' . $parentbean->create_list_count_query($tmp_final_query, $parameters) . ')';
2841 $final_query = '(' . $tmp_final_query . ')';
2846 //If final_query is still empty, its time to build the sub-queries
2847 if (empty($final_query))
2849 $subqueries = SugarBean::build_sub_queries_for_union($subpanel_list, $subpanel_def, $parentbean, $order_by);
2850 $all_fields = array();
2851 foreach($subqueries as $i => $subquery)
2853 $query_fields = $GLOBALS['db']->helper->getSelectFieldsFromQuery($subquery['select']);
2854 foreach($query_fields as $field => $select)
2856 if (!in_array($field, $all_fields))
2857 $all_fields[] = $field;
2859 $subqueries[$i]['query_fields'] = $query_fields;
2862 //Now ensure the queries have the same set of fields in the same order.
2863 foreach($subqueries as $subquery)
2865 $subquery['select'] = "SELECT";
2866 foreach($all_fields as $field)
2868 if (!isset($subquery['query_fields'][$field]))
2870 $subquery['select'] .= " ' ' $field,";
2874 $subquery['select'] .= " {$subquery['query_fields'][$field]},";
2877 $subquery['select'] = substr($subquery['select'], 0 , strlen($subquery['select']) - 1);
2878 //Put the query into the final_query
2879 $query = $subquery['select'] . " " . $subquery['from'] . " " . $subquery['where'];
2882 $query = ' UNION ALL ( '.$query . ' )';
2883 $final_query_rows .= " UNION ALL ";
2885 $query = '(' . $query . ')';
2888 $query_array = $subquery['query_array'];
2889 $select_position=strpos($query_array['select'],"SELECT");
2890 $distinct_position=strpos($query_array['select'],"DISTINCT");
2891 if ($select_position !== false && $distinct_position!= false)
2893 $query_rows = "( ".substr_replace($query_array['select'],"SELECT count(",$select_position,6). ")" . $subquery['from_min'].$query_array['join']. $subquery['where'].' )';
2897 //resort to default behavior.
2898 $query_rows = "( SELECT count(*)". $subquery['from_min'].$query_array['join']. $subquery['where'].' )';
2900 if(!empty($subquery['secondary_select']))
2903 $subquerystring= $subquery['secondary_select'] . $subquery['secondary_from'].$query_array['join']. $subquery['where'];
2904 if (!empty($subquery['secondary_where']))
2906 if (empty($subquery['where']))
2908 $subquerystring.=" WHERE " .$subquery['secondary_where'];
2912 $subquerystring.=" AND " .$subquery['secondary_where'];
2915 $secondary_queries[]=$subquerystring;
2917 $final_query .= $query;
2918 $final_query_rows .= $query_rows;
2922 if(!empty($order_by))
2925 if(!$subpanel_def->isCollection())
2927 $submodulename = $subpanel_def->_instance_properties['module'];
2928 $submoduleclass = $beanList[$submodulename];
2929 $submodule = new $submoduleclass();
2931 if(!empty($submodule) && !empty($submodule->table_name))
2933 $final_query .= " ORDER BY " .$parentbean->process_order_by($order_by, $submodule);
2938 $final_query .= " ORDER BY ". $order_by . ' ';
2940 if(!empty($sort_order))
2942 $final_query .= ' ' .$sort_order;
2947 if(isset($layout_edit_mode) && $layout_edit_mode)
2949 $response = array();
2950 if(!empty($submodule))
2952 $submodule->assign_display_fields($submodule->module_dir);
2953 $response['list'] = array($submodule);
2957 $response['list'] = array();
2959 $response['parent_data'] = array();
2960 $response['row_count'] = 1;
2961 $response['next_offset'] = 0;
2962 $response['previous_offset'] = 0;
2967 return $parentbean->process_union_list_query($parentbean, $final_query, $row_offset, $limit, $max, '',$subpanel_def, $final_query_rows, $secondary_queries);
2972 * Returns a full (ie non-paged) list of the current object type.
2974 * @param string $order_by the order by SQL parameter. defaults to ""
2975 * @param string $where where clause. defaults to ""
2976 * @param boolean $check_dates. defaults to false
2977 * @param int $show_deleted show deleted records. defaults to 0
2979 function get_full_list($order_by = "", $where = "", $check_dates=false, $show_deleted = 0)
2981 $GLOBALS['log']->debug("get_full_list: order_by = '$order_by' and where = '$where'");
2982 if(isset($_SESSION['show_deleted']))
2986 $query = $this->create_new_list_query($order_by, $where,array(),array(), $show_deleted);
2987 return $this->process_full_list_query($query, $check_dates);
2991 * Return the list query used by the list views and export button. Next generation of create_new_list_query function.
2993 * Override this function to return a custom query.
2995 * @param string $order_by custom order by clause
2996 * @param string $where custom where clause
2997 * @param array $filter Optioanal
2998 * @param array $params Optional *
2999 * @param int $show_deleted Optional, default 0, show deleted records is set to 1.
3000 * @param string $join_type
3001 * @param boolean $return_array Optional, default false, response as array
3002 * @param object $parentbean creating a subquery for this bean.
3003 * @param boolean $singleSelect Optional, default false.
3004 * @return String select query string, optionally an array value will be returned if $return_array= true.
3006 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)
3008 global $beanFiles, $beanList;
3009 $selectedFields = array();
3010 $secondarySelectedFields = array();
3011 $ret_array = array();
3013 if($this->bean_implements('ACL') && ACLController::requireOwner($this->module_dir, 'list') )
3015 global $current_user;
3016 $owner_where = $this->getOwnerWhere($current_user->id);
3019 $where = $owner_where;
3023 $where .= ' AND '. $owner_where;
3026 if(!empty($params['distinct']))
3028 $distinct = ' DISTINCT ';
3032 $ret_array['select'] = " SELECT $distinct $this->table_name.* ";
3036 $ret_array['select'] = " SELECT $distinct $this->table_name.id ";
3038 $ret_array['from'] = " FROM $this->table_name ";
3039 $ret_array['from_min'] = $ret_array['from'];
3040 $ret_array['secondary_from'] = $ret_array['from'] ;
3041 $ret_array['where'] = '';
3042 $ret_array['order_by'] = '';
3043 //secondary selects are selects that need to be run after the primarty query to retrieve additional info on main
3046 $ret_array['secondary_select']=& $ret_array['select'];
3047 $ret_array['secondary_from'] = & $ret_array['from'];
3051 $ret_array['secondary_select'] = '';
3053 $custom_join = false;
3054 if((!isset($params['include_custom_fields']) || $params['include_custom_fields']) && isset($this->custom_fields))
3057 $custom_join = $this->custom_fields->getJOIN( empty($filter)? true: $filter );
3060 $ret_array['select'] .= ' ' .$custom_join['select'];
3065 $ret_array['from'] .= ' ' . $custom_join['join'];
3068 //LOOP AROUND FOR FIXIN VARDEF ISSUES
3069 require('include/VarDefHandler/listvardefoverride.php');
3070 $joined_tables = array();
3071 if(isset($params['joined_tables']))
3073 foreach($params['joined_tables'] as $table)
3075 $joined_tables[$table] = 1;
3081 $filterKeys = array_keys($filter);
3082 if(is_numeric($filterKeys[0]))
3085 foreach($filter as $field)
3087 $field = strtolower($field);
3088 //remove out id field so we don't duplicate it
3089 if ( $field == 'id' && !empty($filter) ) {
3092 if(isset($this->field_defs[$field]))
3094 $fields[$field]= $this->field_defs[$field];
3098 $fields[$field] = array('force_exists'=>true);
3107 $fields = $this->field_defs;
3110 $used_join_key = array();
3112 foreach($fields as $field=>$value)
3114 //alias is used to alias field names
3116 if (isset($value['alias']))
3118 $alias =' as ' . $value['alias'] . ' ';
3121 if(empty($this->field_defs[$field]) || !empty($value['force_blank']) )
3123 if(!empty($filter) && isset($filter[$field]['force_exists']) && $filter[$field]['force_exists'])
3125 if ( isset($filter[$field]['force_default']) )
3126 $ret_array['select'] .= ", {$filter[$field]['force_default']} $field ";
3128 //spaces are a fix for length issue problem with unions. The union only returns the maximum number of characters from the first select statemtn.
3129 $ret_array['select'] .= ", ' ' $field ";
3135 $data = $this->field_defs[$field];
3138 //ignore fields that are a part of the collection and a field has been removed as a result of
3139 //layout customization.. this happens in subpanel customizations, use case, from the contacts subpanel
3140 //in opportunities module remove the contact_role/opportunity_role field.
3141 $process_field=true;
3142 if (isset($data['relationship_fields']) and !empty($data['relationship_fields']))
3144 foreach ($data['relationship_fields'] as $field_name)
3146 if (!isset($fields[$field_name]))
3148 $process_field=false;
3152 if (!$process_field)
3157 if( (!isset($data['source']) || $data['source'] == 'db') && (!empty($alias) || !empty($filter) ))
3159 $ret_array['select'] .= ", $this->table_name.$field $alias";
3160 $selectedFields["$this->table_name.$field"] = true;
3165 if($data['type'] != 'relate' && isset($data['db_concat_fields']))
3167 $ret_array['select'] .= ", " . db_concat($this->table_name, $data['db_concat_fields']) . " as $field";
3168 $selectedFields[db_concat($this->table_name, $data['db_concat_fields'])] = true;
3170 //Custom relate field or relate fields built in module builder which have no link field associated.
3171 if ($data['type'] == 'relate' && (isset($data['custom_module']) || isset($data['ext2']))) {
3172 $joinTableAlias = 'jt' . $jtcount;
3173 $relateJoinInfo = $this->custom_fields->getRelateJoin($data, $joinTableAlias);
3174 $ret_array['select'] .= $relateJoinInfo['select'];
3175 $ret_array['from'] .= $relateJoinInfo['from'];
3176 //Replace any references to the relationship in the where clause with the new alias
3177 //If the link isn't set, assume that search used the local table for the field
3178 $searchTable = isset($data['link']) ? $relateJoinInfo['rel_table'] : $this->table_name;
3179 $field_name = $relateJoinInfo['rel_table'] . '.' . !empty($data['name'])?$data['name']:'name';
3180 $where = preg_replace('/(^|[\s(])' . $field_name . '/' , '${1}' . $relateJoinInfo['name_field'], $where);
3184 if ($data['type'] == 'parent') {
3185 //See if we need to join anything by inspecting the where clause
3186 $match = preg_match('/(^|[\s(])parent_(\w+)_(\w+)\.name/', $where, $matches);
3188 $joinTableAlias = 'jt' . $jtcount;
3189 $joinModule = $matches[2];
3190 $joinTable = $matches[3];
3191 $localTable = $this->table_name;
3192 if (!empty($data['custom_module'])) {
3193 $localTable .= '_cstm';
3195 global $beanFiles, $beanList, $module;
3196 require_once($beanFiles[$beanList[$joinModule]]);
3197 $rel_mod = new $beanList[$joinModule]();
3198 $nameField = "$joinTableAlias.name";
3199 if (isset($rel_mod->field_defs['name']))
3201 $name_field_def = $rel_mod->field_defs['name'];
3202 if(isset($name_field_def['db_concat_fields']))
3204 $nameField = db_concat($joinTableAlias, $name_field_def['db_concat_fields']);
3207 $ret_array['select'] .= ", $nameField {$data['name']} ";
3208 $ret_array['from'] .= " LEFT JOIN $joinTable $joinTableAlias
3209 ON $localTable.{$data['id_name']} = $joinTableAlias.id";
3210 //Replace any references to the relationship in the where clause with the new alias
3211 $where = preg_replace('/(^|[\s(])parent_' . $joinModule . '_' . $joinTable . '\.name/', '${1}' . $nameField, $where);
3215 if($data['type'] == 'relate' && isset($data['link']))
3217 $this->load_relationship($data['link']);
3218 if(!empty($this->$data['link']))
3221 if(empty($join_type))
3223 $params['join_type'] = ' LEFT JOIN ';
3227 $params['join_type'] = $join_type;
3229 if(isset($data['join_name']))
3231 $params['join_table_alias'] = $data['join_name'];
3235 $params['join_table_alias'] = 'jt' . $jtcount;
3238 if(isset($data['join_link_name']))
3240 $params['join_table_link_alias'] = $data['join_link_name'];
3244 $params['join_table_link_alias'] = 'jtl' . $jtcount;
3246 $join_primary = !isset($data['join_primary']) || $data['join_primary'];
3248 $join = $this->$data['link']->getJoin($params, true);
3249 $used_join_key[] = $join['rel_key'];
3250 $rel_module = $this->$data['link']->getRelatedModuleName();
3251 $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');
3253 //if rnanme is set to 'name', and bean files exist, then check if field should be a concatenated name
3254 global $beanFiles, $beanList;
3255 if($data['rname'] && !empty($beanFiles[$beanList[$rel_module]])) {
3257 //create an instance of the related bean
3258 require_once($beanFiles[$beanList[$rel_module]]);
3259 $rel_mod = new $beanList[$rel_module]();
3260 //if bean has first and last name fields, then name should be concatenated
3261 if(isset($rel_mod->field_name_map['first_name']) && isset($rel_mod->field_name_map['last_name'])){
3262 $data['db_concat_fields'] = array(0=>'first_name', 1=>'last_name');
3267 if($join['type'] == 'many-to-many')
3269 if(empty($ret_array['secondary_select']))
3271 $ret_array['secondary_select'] = " SELECT $this->table_name.id ref_id ";
3273 if(!empty($beanFiles[$beanList[$rel_module]]) && $join_primary)
3275 require_once($beanFiles[$beanList[$rel_module]]);
3276 $rel_mod = new $beanList[$rel_module]();
3277 if(isset($rel_mod->field_defs['assigned_user_id']))
3279 $ret_array['secondary_select'].= " , ". $params['join_table_alias'] . ".assigned_user_id {$field}_owner, '$rel_module' {$field}_mod";
3284 if(isset($rel_mod->field_defs['created_by']))
3286 $ret_array['secondary_select'].= " , ". $params['join_table_alias'] . ".created_by {$field}_owner , '$rel_module' {$field}_mod";
3297 if(isset($data['db_concat_fields']))
3299 $ret_array['secondary_select'] .= ' , ' . db_concat($params['join_table_alias'], $data['db_concat_fields']) . ' ' . $field;
3303 if(!isset($data['relationship_fields']))
3305 $ret_array['secondary_select'] .= ' , ' . $params['join_table_alias'] . '.' . $data['rname'] . ' ' . $field;
3310 $ret_array['select'] .= ", ' ' $field ";
3311 $ret_array['select'] .= ", ' ' " . $join['rel_key'] . ' ';
3314 if($this->db->dbType != 'mysql') {//bug 26801, these codes are just used to duplicate rel_key in the select sql, or it will throw error in MSSQL and Oracle.
3315 foreach($used_join_key as $used_key) {
3316 if($used_key == $join['rel_key']) $count_used++;
3319 if($count_used <= 1) {//27416, the $ret_array['secondary_select'] should always generate, regardless the dbtype
3320 $ret_array['secondary_select'] .= ', ' . $params['join_table_link_alias'].'.'. $join['rel_key'] .' ' . $join['rel_key'];
3322 if(isset($data['relationship_fields']))
3324 foreach($data['relationship_fields'] as $r_name=>$alias_name)
3326 if(!empty( $secondarySelectedFields[$alias_name]))continue;
3327 $ret_array['secondary_select'] .= ', ' . $params['join_table_link_alias'].'.'. $r_name .' ' . $alias_name;
3328 $secondarySelectedFields[$alias_name] = true;
3333 $ret_array['secondary_from'] .= ' ' . $join['join']. ' AND ' . $params['join_table_alias'].'.deleted=0';
3334 if (isset($data['link_type']) && $data['link_type'] == 'relationship_info' && ($parentbean instanceOf SugarBean))
3336 $ret_array['secondary_where'] = $params['join_table_link_alias'] . '.' . $join['rel_key']. "='" .$parentbean->id . "'";
3342 if(isset($data['db_concat_fields']))
3344 $ret_array['select'] .= ' , ' . db_concat($params['join_table_alias'], $data['db_concat_fields']) . ' ' . $field;
3348 $ret_array['select'] .= ' , ' . $params['join_table_alias'] . '.' . $data['rname'] . ' ' . $field;
3350 if(isset($data['additionalFields'])){
3351 foreach($data['additionalFields'] as $k=>$v){
3352 $ret_array['select'] .= ' , ' . $params['join_table_alias'] . '.' . $k . ' ' . $v;
3357 $ret_array['from'] .= ' ' . $join['join']. ' AND ' . $params['join_table_alias'].'.deleted=0';
3358 if(!empty($beanList[$rel_module]) && !empty($beanFiles[$beanList[$rel_module]]))
3360 require_once($beanFiles[$beanList[$rel_module]]);
3361 $rel_mod = new $beanList[$rel_module]();
3362 if(isset($value['target_record_key']) && !empty($filter))
3364 $selectedFields[$this->table_name.'.'.$value['target_record_key']] = true;
3365 $ret_array['select'] .= " , $this->table_name.{$value['target_record_key']} ";
3367 if(isset($rel_mod->field_defs['assigned_user_id']))
3369 $ret_array['select'] .= ' , ' .$params['join_table_alias'] . '.assigned_user_id ' . $field . '_owner';
3373 $ret_array['select'] .= ' , ' .$params['join_table_alias'] . '.created_by ' . $field . '_owner';
3375 $ret_array['select'] .= " , '".$rel_module ."' " . $field . '_mod';
3380 // To fix SOAP stuff where we are trying to retrieve all the accounts data where accounts.id = ..
3381 // and this code changes accounts to jt4 as there is a self join with the accounts table.
3383 if(isset($data['db_concat_fields'])){
3384 $buildWhere = false;
3385 if(in_array('first_name', $data['db_concat_fields']) && in_array('last_name', $data['db_concat_fields']))
3387 $exp = '/\(\s*?'.$data['name'].'.*?\%\'\s*?\)/';
3388 if(preg_match($exp, $where, $matches))
3390 $search_expression = $matches[0];
3391 //Create three search conditions - first + last, first, last
3392 $first_name_search = str_replace($data['name'], $params['join_table_alias'] . '.first_name', $search_expression);
3393 $last_name_search = str_replace($data['name'], $params['join_table_alias'] . '.last_name', $search_expression);
3394 $full_name_search = str_replace($data['name'], db_concat($params['join_table_alias'], $data['db_concat_fields']), $search_expression);
3396 $where = str_replace($search_expression, '(' . $full_name_search . ' OR ' . $first_name_search . ' OR ' . $last_name_search . ')', $where);
3402 $db_field = db_concat($params['join_table_alias'], $data['db_concat_fields']);
3403 $where = preg_replace('/'.$data['name'].'/', $db_field, $where);
3406 $where = preg_replace('/(^|[\s(])' . $data['name'] . '/', '${1}' . $params['join_table_alias'] . '.'.$data['rname'], $where);
3410 $joined_tables[$params['join_table_alias']]=1;
3411 $joined_tables[$params['join_table_link_alias']]=1;
3420 if(isset($this->field_defs['assigned_user_id']) && empty($selectedFields[$this->table_name.'.assigned_user_id']))
3422 $ret_array['select'] .= ", $this->table_name.assigned_user_id ";
3424 else if(isset($this->field_defs['created_by']) && empty($selectedFields[$this->table_name.'.created_by']))
3426 $ret_array['select'] .= ", $this->table_name.created_by ";
3428 if(isset($this->field_defs['system_id']) && empty($selectedFields[$this->table_name.'.system_id']))
3430 $ret_array['select'] .= ", $this->table_name.system_id ";
3435 if ($ifListForExport) {
3436 if(isset($this->field_defs['email1'])) {
3437 $ret_array['select'].= " ,email_addresses.email_address email1";
3438 $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 ";
3442 $where_auto = '1=1';
3443 if($show_deleted == 0)
3445 $where_auto = "$this->table_name.deleted=0";
3446 }else if($show_deleted == 1)
3448 $where_auto = "$this->table_name.deleted=1";
3451 $ret_array['where'] = " where ($where) AND $where_auto";
3453 $ret_array['where'] = " where $where_auto";
3454 if(!empty($order_by))
3456 //make call to process the order by clause
3457 $ret_array['order_by'] = " ORDER BY ". $this->process_order_by($order_by, null);
3461 unset($ret_array['secondary_where']);
3462 unset($ret_array['secondary_from']);
3463 unset($ret_array['secondary_select']);
3471 return $ret_array['select'] . $ret_array['from'] . $ret_array['where']. $ret_array['order_by'];
3474 * Returns parent record data for objects that store relationship information
3476 * @param array $type_info
3478 * Interal function, do not override.
3480 function retrieve_parent_fields($type_info)
3483 global $beanList, $beanFiles;
3484 $templates = array();
3485 $parent_child_map = array();
3486 foreach($type_info as $children_info)
3488 foreach($children_info as $child_info)
3490 if($child_info['type'] == 'parent')
3492 if(empty($templates[$child_info['parent_type']]))
3494 //Test emails will have an invalid parent_type, don't try to load the non-existant parent bean
3495 if ($child_info['parent_type'] == 'test') {
3498 $class = $beanList[$child_info['parent_type']];
3499 // Added to avoid error below; just silently fail and write message to log
3500 if ( empty($beanFiles[$class]) ) {
3501 $GLOBALS['log']->error($this->object_name.'::retrieve_parent_fields() - cannot load class "'.$class.'", skip loading.');
3504 require_once($beanFiles[$class]);
3505 $templates[$child_info['parent_type']] = new $class();
3508 if(empty($queries[$child_info['parent_type']]))
3510 $queries[$child_info['parent_type']] = "SELECT id ";
3511 $field_def = $templates[$child_info['parent_type']]->field_defs['name'];
3512 if(isset($field_def['db_concat_fields']))
3514 $queries[$child_info['parent_type']] .= ' , ' . db_concat($templates[$child_info['parent_type']]->table_name, $field_def['db_concat_fields']) . ' parent_name';
3518 $queries[$child_info['parent_type']] .= ' , name parent_name';
3520 if(isset($templates[$child_info['parent_type']]->field_defs['assigned_user_id']))
3522 $queries[$child_info['parent_type']] .= ", assigned_user_id parent_name_owner , '{$child_info['parent_type']}' parent_name_mod";;
3523 }else if(isset($templates[$child_info['parent_type']]->field_defs['created_by']))
3525 $queries[$child_info['parent_type']] .= ", created_by parent_name_owner, '{$child_info['parent_type']}' parent_name_mod";
3527 $queries[$child_info['parent_type']] .= " FROM " . $templates[$child_info['parent_type']]->table_name ." WHERE id IN ('{$child_info['parent_id']}'";
3531 if(empty($parent_child_map[$child_info['parent_id']]))
3532 $queries[$child_info['parent_type']] .= " ,'{$child_info['parent_id']}'";
3534 $parent_child_map[$child_info['parent_id']][] = $child_info['child_id'];
3539 foreach($queries as $query)
3541 $result = $this->db->query($query . ')');
3542 while($row = $this->db->fetchByAssoc($result))
3544 $results[$row['id']] = $row;
3548 $child_results = array();
3549 foreach($parent_child_map as $parent_key=>$parent_child)
3551 foreach($parent_child as $child)
3553 if(isset( $results[$parent_key]))
3555 $child_results[$child] = $results[$parent_key];
3559 return $child_results;
3563 * Processes the list query and return fetched row.
3565 * Internal function, do not override.
3566 * @param string $query select query to be processed.
3567 * @param int $row_offset starting position
3568 * @param int $limit Optioanl, default -1
3569 * @param int $max_per_page Optional, default -1
3570 * @param string $where Optional, additional filter criteria.
3571 * @return array Fetched data
3573 function process_list_query($query, $row_offset, $limit= -1, $max_per_page = -1, $where = '')
3575 global $sugar_config;
3576 $db = &DBManagerFactory::getInstance('listviews');
3578 * if the row_offset is set to 'end' go to the end of the list
3580 $toEnd = strval($row_offset) == 'end';
3581 $GLOBALS['log']->debug("process_list_query: ".$query);
3582 if($max_per_page == -1)
3584 $max_per_page = $sugar_config['list_max_entries_per_page'];
3586 // Check to see if we have a count query available.
3587 if(empty($sugar_config['disable_count_query']) || $toEnd)
3589 $count_query = $this->create_list_count_query($query);
3590 if(!empty($count_query) && (empty($limit) || $limit == -1))
3592 // We have a count query. Run it and get the results.
3593 $result = $db->query($count_query, true, "Error running count query for $this->object_name List: ");
3594 $assoc = $db->fetchByAssoc($result);
3595 if(!empty($assoc['c']))
3597 $rows_found = $assoc['c'];
3598 $limit = $sugar_config['list_max_entries_per_page'];
3602 $row_offset = (floor(($rows_found -1) / $limit)) * $limit;
3608 if((empty($limit) || $limit == -1))
3610 $limit = $max_per_page + 1;
3611 $max_per_page = $limit;
3616 if(empty($row_offset))
3620 if(!empty($limit) && $limit != -1 && $limit != -99)
3622 $result = $db->limitQuery($query, $row_offset, $limit,true,"Error retrieving $this->object_name list: ");
3626 $result = $db->query($query,true,"Error retrieving $this->object_name list: ");
3631 if(empty($rows_found))
3633 $rows_found = $db->getRowCount($result);
3636 $GLOBALS['log']->debug("Found $rows_found ".$this->object_name."s");
3638 $previous_offset = $row_offset - $max_per_page;
3639 $next_offset = $row_offset + $max_per_page;
3641 $class = get_class($this);
3642 if($rows_found != 0 or $db->dbType != 'mysql')
3644 //todo Bug? we should remove the magic number -99
3645 //use -99 to return all
3646 $index = $row_offset;
3647 while ($max_per_page == -99 || ($index < $row_offset + $max_per_page))
3650 if(!empty($sugar_config['disable_count_query']))
3652 $row = $db->fetchByAssoc($result);
3656 $row = $db->fetchByAssoc($result, $index);
3663 //instantiate a new class each time. This is because php5 passes
3664 //by reference by default so if we continually update $this, we will
3665 //at the end have a list of all the same objects
3666 $temp = new $class();
3668 foreach($this->field_defs as $field=>$value)
3670 if (isset($row[$field]))
3672 $temp->$field = $row[$field];
3673 $owner_field = $field . '_owner';
3674 if(isset($row[$owner_field]))
3676 $temp->$owner_field = $row[$owner_field];
3679 $GLOBALS['log']->debug("$temp->object_name({$row['id']}): ".$field." = ".$temp->$field);
3680 }else if (isset($row[$this->table_name .'.'.$field]))
3682 $temp->$field = $row[$this->table_name .'.'.$field];
3690 $temp->check_date_relationships_load();
3691 $temp->fill_in_additional_list_fields();
3692 if($temp->hasCustomFields()) $temp->custom_fields->fill_relationships();
3693 $temp->call_custom_logic("process_record");
3700 if(!empty($sugar_config['disable_count_query']) && !empty($limit))
3703 $rows_found = $row_offset + count($list);
3705 unset($list[$limit - 1]);
3712 $response = Array();
3713 $response['list'] = $list;
3714 $response['row_count'] = $rows_found;
3715 $response['next_offset'] = $next_offset;
3716 $response['previous_offset'] = $previous_offset;
3717 $response['current_offset'] = $row_offset ;
3722 * Returns the number of rows that the given SQL query should produce
3724 * Internal function, do not override.
3725 * @param string $query valid select query
3726 * @param boolean $is_count_query Optional, Default false, set to true if passed query is a count query.
3727 * @return int count of rows found
3729 function _get_num_rows_in_query($query, $is_count_query=false)
3731 $num_rows_in_query = 0;
3732 if (!$is_count_query)
3734 $count_query = SugarBean::create_list_count_query($query);
3736 $count_query=$query;
3738 $result = $this->db->query($count_query, true, "Error running count query for $this->object_name List: ");
3740 $row = $this->db->fetchByAssoc($result, $row_num);
3743 $num_rows_in_query += current($row);
3745 $row = $this->db->fetchByAssoc($result, $row_num);
3748 return $num_rows_in_query;
3752 * Applies pagination window to union queries used by list view and subpanels,
3753 * executes the query and returns fetched data.
3755 * Internal function, do not override.
3756 * @param object $parent_bean
3757 * @param string $query query to be processed.
3758 * @param int $row_offset
3759 * @param int $limit optional, default -1
3760 * @param int $max_per_page Optional, default -1
3761 * @param string $where Custom where clause.
3762 * @param array $subpanel_def definition of sub-panel to be processed
3763 * @param string $query_row_count
3764 * @param array $seconday_queries
3765 * @return array Fetched data.
3767 function process_union_list_query($parent_bean, $query,
3768 $row_offset, $limit= -1, $max_per_page = -1, $where = '', $subpanel_def, $query_row_count='', $secondary_queries = array())
3771 $db = &DBManagerFactory::getInstance('listviews');
3773 * if the row_offset is set to 'end' go to the end of the list
3775 $toEnd = strval($row_offset) == 'end';
3776 global $sugar_config;
3777 $use_count_query=false;
3778 $processing_collection=$subpanel_def->isCollection();
3780 $GLOBALS['log']->debug("process_list_query: ".$query);
3781 if($max_per_page == -1)
3783 $max_per_page = $sugar_config['list_max_entries_per_subpanel'];
3785 if(empty($query_row_count))
3787 $query_row_count = $query;
3789 $distinct_position=strpos($query_row_count,"DISTINCT");
3791 if ($distinct_position!= false)
3793 $use_count_query=true;
3795 $performSecondQuery = true;
3796 if(empty($sugar_config['disable_count_query']) || $toEnd)
3798 $rows_found = $this->_get_num_rows_in_query($query_row_count,$use_count_query);
3801 $performSecondQuery = false;
3803 if(!empty($rows_found) && (empty($limit) || $limit == -1))
3805 $limit = $sugar_config['list_max_entries_per_subpanel'];
3809 $row_offset = (floor(($rows_found -1) / $limit)) * $limit;
3815 if((empty($limit) || $limit == -1))
3817 $limit = $max_per_page + 1;
3818 $max_per_page = $limit;
3822 if(empty($row_offset))
3827 $previous_offset = $row_offset - $max_per_page;
3828 $next_offset = $row_offset + $max_per_page;
3830 if($performSecondQuery)
3832 if(!empty($limit) && $limit != -1 && $limit != -99)
3834 $result = $db->limitQuery($query, $row_offset, $limit,true,"Error retrieving $parent_bean->object_name list: ");
3838 $result = $db->query($query,true,"Error retrieving $this->object_name list: ");
3840 if(empty($rows_found))
3842 $rows_found = $db->getRowCount($result);
3845 $GLOBALS['log']->debug("Found $rows_found ".$parent_bean->object_name."s");
3846 if($rows_found != 0 or $db->dbType != 'mysql')
3848 //use -99 to return all
3850 // get the current row
3851 $index = $row_offset;
3852 if(!empty($sugar_config['disable_count_query']))
3854 $row = $db->fetchByAssoc($result);
3858 $row = $db->fetchByAssoc($result, $index);
3861 $post_retrieve = array();
3862 $isFirstTime = true;
3865 $function_fields = array();
3866 if(($index < $row_offset + $max_per_page || $max_per_page == -99) or ($db->dbType != 'mysql'))
3868 if ($processing_collection)
3870 $current_bean =$subpanel_def->sub_subpanels[$row['panel_name']]->template_instance;
3873 $class = get_class($subpanel_def->sub_subpanels[$row['panel_name']]->template_instance);
3874 $current_bean = new $class();
3877 $current_bean=$subpanel_def->template_instance;
3880 $class = get_class($subpanel_def->template_instance);
3881 $current_bean = new $class();
3884 $isFirstTime = false;
3885 //set the panel name in the bean instance.
3886 if (isset($row['panel_name']))
3888 $current_bean->panel_name=$row['panel_name'];
3890 foreach($current_bean->field_defs as $field=>$value)
3893 if (isset($row[$field]))
3895 $current_bean->$field = $row[$field];
3896 unset($row[$field]);
3898 else if (isset($row[$this->table_name .'.'.$field]))
3900 $current_bean->$field = $row[$current_bean->table_name .'.'.$field];
3901 unset($row[$current_bean->table_name .'.'.$field]);
3905 $current_bean->$field = "";
3906 unset($row[$field]);
3908 if(isset($value['source']) && $value['source'] == 'function')
3910 $function_fields[]=$field;
3913 foreach($row as $key=>$value)
3915 $current_bean->$key = $value;
3917 foreach($function_fields as $function_field)
3919 $value = $current_bean->field_defs[$function_field];
3920 $can_execute = true;
3921 $execute_params = array();
3922 $execute_function = array();
3923 if(!empty($value['function_class']))
3925 $execute_function[] = $value['function_class'];
3926 $execute_function[] = $value['function_name'];
3930 $execute_function = $value['function_name'];
3932 foreach($value['function_params'] as $param )
3934 if (empty($value['function_params_source']) or $value['function_params_source']=='parent')
3936 if(empty($this->$param))
3938 $can_execute = false;
3942 $execute_params[] = $this->$param;
3944 } else if ($value['function_params_source']=='this')
3946 if(empty($current_bean->$param))
3948 $can_execute = false;
3952 $execute_params[] = $current_bean->$param;
3957 $can_execute = false;
3963 if(!empty($value['function_require']))
3965 require_once($value['function_require']);
3967 $current_bean->$function_field = call_user_func_array($execute_function, $execute_params);
3970 if(!empty($current_bean->parent_type) && !empty($current_bean->parent_id))
3972 if(!isset($post_retrieve[$current_bean->parent_type]))
3974 $post_retrieve[$current_bean->parent_type] = array();
3976 $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');
3978 //$current_bean->fill_in_additional_list_fields();
3979 $list[$current_bean->id] = $current_bean;
3981 // go to the next row
3983 $row = $db->fetchByAssoc($result, $index);
3986 //now handle retrieving many-to-many relationships
3989 foreach($secondary_queries as $query2)
3991 $result2 = $db->query($query2);
3993 $row2 = $db->fetchByAssoc($result2);
3996 $id_ref = $row2['ref_id'];
3998 if(isset($list[$id_ref]))
4000 foreach($row2 as $r2key=>$r2value)
4002 if($r2key != 'ref_id')
4004 $list[$id_ref]->$r2key = $r2value;
4008 $row2 = $db->fetchByAssoc($result2);
4014 if(isset($post_retrieve))
4016 $parent_fields = $this->retrieve_parent_fields($post_retrieve);
4020 $parent_fields = array();
4022 if(!empty($sugar_config['disable_count_query']) && !empty($limit))
4025 //C.L. Bug 43535 - Use the $index value to set the $rows_found value here
4026 $rows_found = isset($index) ? $index : $row_offset + count($list);
4028 if(count($list) >= $limit)
4042 $parent_fields = array();
4044 $response = array();
4045 $response['list'] = $list;
4046 $response['parent_data'] = $parent_fields;
4047 $response['row_count'] = $rows_found;
4048 $response['next_offset'] = $next_offset;
4049 $response['previous_offset'] = $previous_offset;
4050 $response['current_offset'] = $row_offset ;
4051 $response['query'] = $query;
4057 * Applies pagination window to select queries used by detail view,
4058 * executes the query and returns fetched data.
4060 * Internal function, do not override.
4061 * @param string $query query to be processed.
4062 * @param int $row_offset
4063 * @param int $limit optional, default -1
4064 * @param int $max_per_page Optional, default -1
4065 * @param string $where Custom where clause.
4066 * @param int $offset Optional, default 0
4067 * @return array Fetched data.
4070 function process_detail_query($query, $row_offset, $limit= -1, $max_per_page = -1, $where = '', $offset = 0)
4072 global $sugar_config;
4073 $GLOBALS['log']->debug("process_list_query: ".$query);
4074 if($max_per_page == -1)
4076 $max_per_page = $sugar_config['list_max_entries_per_page'];
4079 // Check to see if we have a count query available.
4080 $count_query = $this->create_list_count_query($query);
4082 if(!empty($count_query) && (empty($limit) || $limit == -1))
4084 // We have a count query. Run it and get the results.
4085 $result = $this->db->query($count_query, true, "Error running count query for $this->object_name List: ");
4086 $assoc = $this->db->fetchByAssoc($result);
4087 if(!empty($assoc['c']))
4089 $total_rows = $assoc['c'];
4093 if(empty($row_offset))
4098 $result = $this->db->limitQuery($query, $offset, 1, true,"Error retrieving $this->object_name list: ");
4100 $rows_found = $this->db->getRowCount($result);
4102 $GLOBALS['log']->debug("Found $rows_found ".$this->object_name."s");
4104 $previous_offset = $row_offset - $max_per_page;
4105 $next_offset = $row_offset + $max_per_page;
4107 if($rows_found != 0 or $this->db->dbType != 'mysql')
4110 $row = $this->db->fetchByAssoc($result, $index);
4111 $this->retrieve($row['id']);
4114 $response = Array();
4115 $response['bean'] = $this;
4116 if (empty($total_rows))
4118 $response['row_count'] = $total_rows;
4119 $response['next_offset'] = $next_offset;
4120 $response['previous_offset'] = $previous_offset;
4126 * Processes fetched list view data
4128 * Internal function, do not override.
4129 * @param string $query query to be processed.
4130 * @param boolean $check_date Optional, default false. if set to true date time values are processed.
4131 * @return array Fetched data.
4134 function process_full_list_query($query, $check_date=false)
4137 $GLOBALS['log']->debug("process_full_list_query: query is ".$query);
4138 $result = $this->db->query($query, false);
4139 $GLOBALS['log']->debug("process_full_list_query: result is ".print_r($result,true));
4140 $class = get_class($this);
4141 $isFirstTime = true;
4142 $bean = new $class();
4144 //if($this->db->getRowCount($result) > 0){
4147 // We have some data.
4148 //while ($row = $this->db->fetchByAssoc($result)) {
4149 while (($row = $bean->db->fetchByAssoc($result)) != null)
4153 $bean = new $class();
4155 $isFirstTime = false;
4157 foreach($bean->field_defs as $field=>$value)
4159 if (isset($row[$field]))
4161 $bean->$field = $row[$field];
4162 $GLOBALS['log']->debug("process_full_list: $bean->object_name({$row['id']}): ".$field." = ".$bean->$field);
4171 $bean->processed_dates_times = array();
4172 $bean->check_date_relationships_load();
4174 $bean->fill_in_additional_list_fields();
4175 $bean->call_custom_logic("process_record");
4176 $bean->fetched_row = $row;
4181 if (isset($list)) return $list;
4186 * Tracks the viewing of a detail record.
4187 * This leverages get_summary_text() which is object specific.
4189 * Internal function, do not override.
4190 * @param string $user_id - String value of the user that is viewing the record.
4191 * @param string $current_module - String value of the module being processed.
4192 * @param string $current_view - String value of the current view
4194 function track_view($user_id, $current_module, $current_view='')
4196 $trackerManager = TrackerManager::getInstance();
4197 if($monitor = $trackerManager->getMonitor('tracker')){
4198 $monitor->setValue('date_modified', $GLOBALS['timedate']->nowDb());
4199 $monitor->setValue('user_id', $user_id);
4200 $monitor->setValue('module_name', $current_module);
4201 $monitor->setValue('action', $current_view);
4202 $monitor->setValue('item_id', $this->id);
4203 $monitor->setValue('item_summary', $this->get_summary_text());
4204 $monitor->setValue('visible', $this->tracker_visibility);
4205 $trackerManager->saveMonitor($monitor);
4210 * Returns the summary text that should show up in the recent history list for this object.
4214 public function get_summary_text()
4216 return "Base Implementation. Should be overridden.";
4220 * This is designed to be overridden and add specific fields to each record.
4221 * This allows the generic query to fill in the major fields, and then targeted
4222 * queries to get related fields and add them to the record. The contact's
4223 * account for instance. This method is only used for populating extra fields
4226 function fill_in_additional_list_fields(){
4227 if(!empty($this->field_defs['parent_name']) && empty($this->parent_name)){
4228 $this->fill_in_additional_parent_fields();
4233 * This is designed to be overridden and add specific fields to each record.
4234 * This allows the generic query to fill in the major fields, and then targeted
4235 * queries to get related fields and add them to the record. The contact's
4236 * account for instance. This method is only used for populating extra fields
4237 * in the detail form
4239 function fill_in_additional_detail_fields()
4241 if(!empty($this->field_defs['assigned_user_name']) && !empty($this->assigned_user_id)){
4243 $this->assigned_user_name = get_assigned_user_name($this->assigned_user_id);
4246 if(!empty($this->field_defs['created_by']) && !empty($this->created_by))
4247 $this->created_by_name = get_assigned_user_name($this->created_by);
4248 if(!empty($this->field_defs['modified_user_id']) && !empty($this->modified_user_id))
4249 $this->modified_by_name = get_assigned_user_name($this->modified_user_id);
4251 if(!empty($this->field_defs['parent_name'])){
4252 $this->fill_in_additional_parent_fields();
4257 * This is desgined to be overridden or called from extending bean. This method
4258 * will fill in any parent_name fields.
4260 function fill_in_additional_parent_fields() {
4262 if(!empty($this->parent_id) && !empty($this->last_parent_id) && $this->last_parent_id == $this->parent_id){
4265 $this->parent_name = '';
4267 if(!empty($this->parent_type)) {
4268 $this->last_parent_id = $this->parent_id;
4269 $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'));
4270 if(!empty($this->parent_first_name) || !empty($this->parent_last_name) ){
4271 $this->parent_name = $GLOBALS['locale']->getLocaleFormattedName($this->parent_first_name, $this->parent_last_name);
4272 } else if(!empty($this->parent_document_name)){
4273 $this->parent_name = $this->parent_document_name;
4279 * Fill in a link field
4282 function fill_in_link_field( $linkFieldName )
4284 if ($this->load_relationship($linkFieldName))
4286 $list=$this->$linkFieldName->get();
4287 $this->$linkFieldName = '' ; // match up with null value in $this->populateFromRow()
4289 $this->$linkFieldName = $list[0];
4294 * Fill in fields where type = relate
4296 function fill_in_relationship_fields(){
4297 global $fill_in_rel_depth;
4298 if(empty($fill_in_rel_depth) || $fill_in_rel_depth < 0)
4299 $fill_in_rel_depth = 0;
4300 if($fill_in_rel_depth > 1)
4302 $fill_in_rel_depth++;
4304 foreach($this->field_defs as $field)
4306 if(0 == strcmp($field['type'],'relate') && !empty($field['module']))
4308 $name = $field['name'];
4309 if(empty($this->$name))
4311 // 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']
4312 $related_module = $field['module'];
4313 $id_name = $field['id_name'];
4314 if (empty($this->$id_name)){
4315 $this->fill_in_link_field($id_name);
4317 if(!empty($this->$id_name) && ( $this->object_name != $related_module || ( $this->object_name == $related_module && $this->$id_name != $this->id ))){
4318 if(isset($GLOBALS['beanList'][ $related_module])){
4319 $class = $GLOBALS['beanList'][$related_module];
4321 if(!empty($this->$id_name) && file_exists($GLOBALS['beanFiles'][$class]) && isset($this->$name)){
4322 require_once($GLOBALS['beanFiles'][$class]);
4323 $mod = new $class();
4324 $mod->retrieve($this->$id_name);
4325 if (!empty($field['rname'])) {
4326 $this->$name = $mod->$field['rname'];
4327 } else if (isset($mod->name)) {
4328 $this->$name = $mod->name;
4333 if(!empty($this->$id_name) && isset($this->$name))
4335 if(!isset($field['additionalFields']))
4336 $field['additionalFields'] = array();
4337 if(!empty($field['rname']))
4339 $field['additionalFields'][$field['rname']]= $name;
4343 $field['additionalFields']['name']= $name;
4345 $this->getRelatedFields($related_module, $this->$id_name, $field['additionalFields']);
4350 $fill_in_rel_depth--;
4354 * This is a helper function that is used to quickly created indexes when creating tables.
4356 function create_index($query)
4358 $GLOBALS['log']->info($query);
4360 $result = $this->db->query($query, true, "Error creating index:");
4364 * This function should be overridden in each module. It marks an item as deleted.
4366 * If it is not overridden, then marking this type of item is not allowed
4368 function mark_deleted($id)
4370 global $current_user;
4371 $date_modified = $GLOBALS['timedate']->nowDb();
4372 if(isset($_SESSION['show_deleted']))
4374 $this->mark_undeleted($id);
4378 // call the custom business logic
4379 $custom_logic_arguments['id'] = $id;
4380 $this->call_custom_logic("before_delete", $custom_logic_arguments);
4382 $this->mark_relationships_deleted($id);
4383 if ( isset($this->field_defs['modified_user_id']) ) {
4384 if (!empty($current_user)) {
4385 $this->modified_user_id = $current_user->id;
4387 $this->modified_user_id = 1;
4389 $query = "UPDATE $this->table_name set deleted=1 , date_modified = '$date_modified', modified_user_id = '$this->modified_user_id' where id='$id'";
4391 $query = "UPDATE $this->table_name set deleted=1 , date_modified = '$date_modified' where id='$id'";
4393 $this->db->query($query, true,"Error marking record deleted: ");
4395 SugarRelationship::resaveRelatedBeans();
4397 // Take the item off the recently viewed lists
4398 $tracker = new Tracker();
4399 $tracker->makeInvisibleForAll($id);
4401 // call the custom business logic
4402 $this->call_custom_logic("after_delete", $custom_logic_arguments);
4407 * Restores data deleted by call to mark_deleted() function.
4409 * Internal function, do not override.
4411 function mark_undeleted($id)
4413 // call the custom business logic
4414 $custom_logic_arguments['id'] = $id;
4415 $this->call_custom_logic("before_restore", $custom_logic_arguments);
4417 $date_modified = $GLOBALS['timedate']->nowDb();
4418 $query = "UPDATE $this->table_name set deleted=0 , date_modified = '$date_modified' where id='$id'";
4419 $this->db->query($query, true,"Error marking record undeleted: ");
4421 // call the custom business logic
4422 $this->call_custom_logic("after_restore", $custom_logic_arguments);
4426 * This function deletes relationships to this object. It should be overridden
4427 * to handle the relationships of the specific object.
4428 * This function is called when the item itself is being deleted.
4430 * @param int $id id of the relationship to delete
4432 function mark_relationships_deleted($id)
4434 $this->delete_linked($id);
4438 * This function is used to execute the query and create an array template objects
4439 * from the resulting ids from the query.
4440 * It is currently used for building sub-panel arrays.
4442 * @param string $query - the query that should be executed to build the list
4443 * @param object $template - The object that should be used to copy the records.
4444 * @param int $row_offset Optional, default 0
4445 * @param int $limit Optional, default -1
4448 function build_related_list($query, &$template, $row_offset = 0, $limit = -1)
4450 $GLOBALS['log']->debug("Finding linked records $this->object_name: ".$query);
4451 $db = &DBManagerFactory::getInstance('listviews');
4453 if(!empty($row_offset) && $row_offset != 0 && !empty($limit) && $limit != -1)
4455 $result = $db->limitQuery($query, $row_offset, $limit,true,"Error retrieving $template->object_name list: ");
4459 $result = $db->query($query, true);
4463 $isFirstTime = true;
4464 $class = get_class($template);
4465 while($row = $this->db->fetchByAssoc($result))
4469 $template = new $class();
4471 $isFirstTime = false;
4472 $record = $template->retrieve($row['id']);
4476 // this copies the object into the array
4477 $list[] = $template;
4484 * This function is used to execute the query and create an array template objects
4485 * from the resulting ids from the query.
4486 * It is currently used for building sub-panel arrays. It supports an additional
4487 * where clause that is executed as a filter on the results
4489 * @param string $query - the query that should be executed to build the list
4490 * @param object $template - The object that should be used to copy the records.
4492 function build_related_list_where($query, &$template, $where='', $in='', $order_by, $limit='', $row_offset = 0)
4494 $db = &DBManagerFactory::getInstance('listviews');
4495 // No need to do an additional query
4496 $GLOBALS['log']->debug("Finding linked records $this->object_name: ".$query);
4497 if(empty($in) && !empty($query))
4499 $idList = $this->build_related_in($query);
4500 $in = $idList['in'];
4502 // MFH - Added Support For Custom Fields in Searches
4504 if(isset($this->custom_fields)) {
4505 $custom_join = $this->custom_fields->getJOIN();
4508 $query = "SELECT id ";
4510 if (!empty($custom_join)) {
4511 $query .= $custom_join['select'];
4513 $query .= " FROM $this->table_name ";
4515 if (!empty($custom_join) && !empty($custom_join['join'])) {
4516 $query .= " " . $custom_join['join'];
4519 $query .= " WHERE deleted=0 AND id IN $in";
4522 $query .= " AND $where";
4526 if(!empty($order_by))
4528 $query .= "ORDER BY $order_by";
4532 $result = $db->limitQuery($query, $row_offset, $limit,true,"Error retrieving $this->object_name list: ");
4536 $result = $db->query($query, true);
4540 $isFirstTime = true;
4541 $class = get_class($template);
4543 $disable_security_flag = ($template->disable_row_level_security) ? true : false;
4544 while($row = $db->fetchByAssoc($result))
4548 $template = new $class();
4549 $template->disable_row_level_security = $disable_security_flag;
4551 $isFirstTime = false;
4552 $record = $template->retrieve($row['id']);
4555 // this copies the object into the array
4556 $list[] = $template;
4564 * Constructs an comma seperated list of ids from passed query results.
4566 * @param string @query query to be executed.
4569 function build_related_in($query)
4572 $result = $this->db->query($query, true);
4574 while($row = $this->db->fetchByAssoc($result))
4576 $idList[] = $row['id'];
4579 $ids = "('" . $row['id'] . "'";
4583 $ids .= ",'" . $row['id'] . "'";
4593 return array('list'=>$idList, 'in'=>$ids);
4597 * Optionally copies values from fetched row into the bean.
4599 * Internal function, do not override.
4601 * @param string $query - the query that should be executed to build the list
4602 * @param object $template - The object that should be used to copy the records
4603 * @param array $field_list List of fields.
4606 function build_related_list2($query, &$template, &$field_list)
4608 $GLOBALS['log']->debug("Finding linked values $this->object_name: ".$query);
4610 $result = $this->db->query($query, true);
4613 $isFirstTime = true;
4614 $class = get_class($template);
4615 while($row = $this->db->fetchByAssoc($result))
4617 // Create a blank copy
4621 $copy = new $class();
4623 $isFirstTime = false;
4624 foreach($field_list as $field)
4626 // Copy the relevant fields
4627 $copy->$field = $row[$field];
4631 // this copies the object into the array
4639 * Let implementing classes to fill in row specific columns of a list view form
4642 function list_view_parse_additional_sections(&$list_form)
4647 * Assigns all of the values into the template for the list view
4649 function get_list_view_array()
4651 static $cache = array();
4652 // cn: bug 12270 - sensitive fields being passed arbitrarily in listViews
4653 $sensitiveFields = array('user_hash' => '');
4655 $return_array = Array();
4656 global $app_list_strings, $mod_strings;
4657 foreach($this->field_defs as $field=>$value){
4659 if(isset($this->$field)){
4661 // cn: bug 12270 - sensitive fields being passed arbitrarily in listViews
4662 if(isset($sensitiveFields[$field]))
4664 if(!isset($cache[$field]))
4665 $cache[$field] = strtoupper($field);
4667 //Fields hidden by Dependent Fields
4668 if (isset($value['hidden']) && $value['hidden'] === true) {
4669 $return_array[$cache[$field]] = "";
4672 //cn: if $field is a _dom, detect and return VALUE not KEY
4673 //cl: empty function check for meta-data enum types that have values loaded from a function
4674 else if (((!empty($value['type']) && ($value['type'] == 'enum' || $value['type'] == 'radioenum') )) && empty($value['function'])){
4675 if(!empty($app_list_strings[$value['options']][$this->$field])){
4676 $return_array[$cache[$field]] = $app_list_strings[$value['options']][$this->$field];
4678 //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.
4679 elseif(!empty($mod_strings[$value['options']][$this->$field]))
4681 $return_array[$cache[$field]] = $mod_strings[$value['options']][$this->$field];
4684 $return_array[$cache[$field]] = $this->$field;
4687 // tjy: no need to do this str_replace as the changes in 29994 for ListViewGeneric.tpl for translation handle this now
4688 // }elseif(!empty($value['type']) && $value['type'] == 'multienum'&& empty($value['function'])){
4689 // $return_array[strtoupper($field)] = str_replace('^,^', ', ', $this->$field );
4690 }elseif(!empty($value['custom_module']) && $value['type'] != 'currency'){
4691 // $this->format_field($value);
4692 $return_array[$cache[$field]] = $this->$field;
4694 $return_array[$cache[$field]] = $this->$field;
4696 // handle "Assigned User Name"
4697 if($field == 'assigned_user_name'){
4698 $return_array['ASSIGNED_USER_NAME'] = get_assigned_user_name($this->assigned_user_id);
4702 return $return_array;
4705 * Override this function to set values in the array used to render list view data.
4708 function get_list_view_data()
4710 return $this->get_list_view_array();
4714 * Construct where clause from a list of name-value pairs.
4717 function get_where(&$fields_array)
4719 $where_clause = "WHERE ";
4721 foreach ($fields_array as $name=>$value)
4729 $where_clause .= " AND ";
4732 $where_clause .= "$name = '".$this->db->quote($value,false)."'";
4734 $where_clause .= " AND deleted=0";
4735 return $where_clause;
4740 * Constructs a select query and fetch 1 row using this query, and then process the row
4742 * Internal function, do not override.
4743 * @param array @fields_array array of name value pairs used to construct query.
4744 * @param boolean $encode Optional, default true, encode fetched data.
4745 * @return object Instance of this bean with fetched data.
4747 function retrieve_by_string_fields($fields_array, $encode=true)
4749 $where_clause = $this->get_where($fields_array);
4750 if(isset($this->custom_fields))
4751 $custom_join = $this->custom_fields->getJOIN();
4752 else $custom_join = false;
4755 $query = "SELECT $this->table_name.*". $custom_join['select']. " FROM $this->table_name " . $custom_join['join'];
4759 $query = "SELECT $this->table_name.* FROM $this->table_name ";
4761 $query .= " $where_clause";
4762 $GLOBALS['log']->debug("Retrieve $this->object_name: ".$query);
4763 //requireSingleResult has beeen deprecated.
4764 //$result = $this->db->requireSingleResult($query, true, "Retrieving record $where_clause:");
4765 $result = $this->db->limitQuery($query,0,1,true, "Retrieving record $where_clause:");
4772 if($this->db->getRowCount($result) > 1)
4774 $this->duplicates_found = true;
4776 $row = $this->db->fetchByAssoc($result, -1, $encode);
4781 $this->fetched_row = $row;
4782 $this->fromArray($row);
4783 $this->fill_in_additional_detail_fields();
4788 * This method is called during an import before inserting a bean
4789 * Define an associative array called $special_fields
4790 * the keys are user defined, and don't directly map to the bean's fields
4791 * the value is the method name within that bean that will do extra
4792 * processing for that field. example: 'full_name'=>'get_names_from_full_name'
4795 function process_special_fields()
4797 foreach ($this->special_functions as $func_name)
4799 if ( method_exists($this,$func_name) )
4801 $this->$func_name();
4807 * Override this function to build a where clause based on the search criteria set into bean .
4810 function build_generic_where_clause($value)
4814 function getRelatedFields($module, $id, $fields, $return_array = false){
4815 if(empty($GLOBALS['beanList'][$module]))return '';
4816 $object = BeanFactory::getObjectName($module);
4818 VardefManager::loadVardef($module, $object);
4819 if(empty($GLOBALS['dictionary'][$object]['table']))return '';
4820 $table = $GLOBALS['dictionary'][$object]['table'];
4821 $query = 'SELECT id';
4822 foreach($fields as $field=>$alias){
4823 if(!empty($GLOBALS['dictionary'][$object]['fields'][$field]['db_concat_fields'])){
4824 $query .= ' ,' .db_concat($table, $GLOBALS['dictionary'][$object]['fields'][$field]['db_concat_fields']) . ' as ' . $alias ;
4825 }else if(!empty($GLOBALS['dictionary'][$object]['fields'][$field]) &&
4826 (empty($GLOBALS['dictionary'][$object]['fields'][$field]['source']) ||
4827 $GLOBALS['dictionary'][$object]['fields'][$field]['source'] != "non-db"))
4829 $query .= ' ,' .$table . '.' . $field . ' as ' . $alias;
4831 if(!$return_array)$this->$alias = '';
4833 if($query == 'SELECT id' || empty($id)){
4838 if(isset($GLOBALS['dictionary'][$object]['fields']['assigned_user_id']))
4840 $query .= " , ". $table . ".assigned_user_id owner";
4843 else if(isset($GLOBALS['dictionary'][$object]['fields']['created_by']))
4845 $query .= " , ". $table . ".created_by owner";
4848 $query .= ' FROM ' . $table . ' WHERE deleted=0 AND id=';
4849 $result = $GLOBALS['db']->query($query . "'$id'" );
4850 $row = $GLOBALS['db']->fetchByAssoc($result);
4854 $owner = (empty($row['owner']))?'':$row['owner'];
4855 foreach($fields as $alias){
4856 $this->$alias = (!empty($row[$alias]))? $row[$alias]: '';
4857 $alias = $alias .'_owner';
4858 $this->$alias = $owner;
4859 $a_mod = $alias .'_mod';
4860 $this->$a_mod = $module;
4867 function &parse_additional_headers(&$list_form, $xTemplateSection)
4872 function assign_display_fields($currentModule)
4875 foreach($this->column_fields as $field)
4877 if(isset($this->field_name_map[$field]) && empty($this->$field))
4879 if($this->field_name_map[$field]['type'] != 'date' && $this->field_name_map[$field]['type'] != 'enum')
4880 $this->$field = $field;
4881 if($this->field_name_map[$field]['type'] == 'date')
4883 $this->$field = $timedate->to_display_date('1980-07-09');
4885 if($this->field_name_map[$field]['type'] == 'enum')
4887 $dom = $this->field_name_map[$field]['options'];
4888 global $current_language, $app_list_strings;
4889 $mod_strings = return_module_language($current_language, $currentModule);
4891 if(isset($mod_strings[$dom]))
4893 $options = $mod_strings[$dom];
4894 foreach($options as $key=>$value)
4896 if(!empty($key) && empty($this->$field ))
4898 $this->$field = $key;
4902 if(isset($app_list_strings[$dom]))
4904 $options = $app_list_strings[$dom];
4905 foreach($options as $key=>$value)
4907 if(!empty($key) && empty($this->$field ))
4909 $this->$field = $key;
4921 * RELATIONSHIP HANDLING
4924 function set_relationship($table, $relate_values, $check_duplicates = true,$do_update=false,$data_values=null)
4928 // make sure there is a date modified
4929 $date_modified = $this->db->convert("'".$GLOBALS['timedate']->nowDb()."'", 'datetime');
4932 if($check_duplicates)
4934 $query = "SELECT * FROM $table ";
4935 $where = "WHERE deleted = '0' ";
4936 foreach($relate_values as $name=>$value)
4938 $where .= " AND $name = '$value' ";
4941 $result = $this->db->query($query, false, "Looking For Duplicate Relationship:" . $query);
4942 $row=$this->db->fetchByAssoc($result);
4945 if(!$check_duplicates || empty($row) )
4947 unset($relate_values['id']);
4948 if ( isset($data_values))
4950 $relate_values = array_merge($relate_values,$data_values);
4952 $query = "INSERT INTO $table (id, ". implode(',', array_keys($relate_values)) . ", date_modified) VALUES ('" . create_guid() . "', " . "'" . implode("', '", $relate_values) . "', ".$date_modified.")" ;
4954 $this->db->query($query, false, "Creating Relationship:" . $query);
4956 else if ($do_update)
4959 foreach($data_values as $key=>$value)
4961 array_push($conds,$key."='".$this->db->quote($value)."'");
4963 $query = "UPDATE $table SET ". implode(',', $conds).",date_modified=".$date_modified." ".$where;
4964 $this->db->query($query, false, "Updating Relationship:" . $query);
4968 function retrieve_relationships($table, $values, $select_id)
4970 $query = "SELECT $select_id FROM $table WHERE deleted = 0 ";
4971 foreach($values as $name=>$value)
4973 $query .= " AND $name = '$value' ";
4975 $query .= " ORDER BY $select_id ";
4976 $result = $this->db->query($query, false, "Retrieving Relationship:" . $query);
4978 while($row = $this->db->fetchByAssoc($result))
4985 // TODO: this function needs adjustment
4986 function loadLayoutDefs()
4988 global $layout_defs;
4989 if(empty( $this->layout_def) && file_exists('modules/'. $this->module_dir . '/layout_defs.php'))
4991 include_once('modules/'. $this->module_dir . '/layout_defs.php');
4992 if(file_exists('custom/modules/'. $this->module_dir . '/Ext/Layoutdefs/layoutdefs.ext.php'))
4994 include_once('custom/modules/'. $this->module_dir . '/Ext/Layoutdefs/layoutdefs.ext.php');
4996 if ( empty( $layout_defs[get_class($this)]))
4998 echo "\$layout_defs[" . get_class($this) . "]; does not exist";
5001 $this->layout_def = $layout_defs[get_class($this)];
5006 * Trigger custom logic for this module that is defined for the provided hook
5007 * The custom logic file is located under custom/modules/[CURRENT_MODULE]/logic_hooks.php.
5008 * That file should define the $hook_version that should be used.
5009 * It should also define the $hook_array. The $hook_array will be a two dimensional array
5010 * the first dimension is the name of the event, the second dimension is the information needed
5011 * to fire the hook. Each entry in the top level array should be defined on a single line to make it
5012 * easier to automatically replace this file. There should be no contents of this file that are not replacable.
5014 * $hook_array['before_save'][] = Array(1, testtype, 'custom/modules/Leads/test12.php', 'TestClass', 'lead_before_save_1');
5015 * This sample line creates a before_save hook. The hooks are procesed in the order in which they
5016 * are added to the array. The second dimension is an array of:
5017 * processing index (for sorting before exporting the array)
5020 * php file to include
5021 * php class the method is in
5022 * php method to call
5024 * The method signature for version 1 hooks is:
5025 * function NAME(&$bean, $event, $arguments)
5026 * $bean - $this bean passed in by reference.
5027 * $event - The string for the current event (i.e. before_save)
5028 * $arguments - An array of arguments that are specific to the event.
5030 function call_custom_logic($event, $arguments = null)
5032 if(!isset($this->processed) || $this->processed == false){
5033 //add some logic to ensure we do not get into an infinite loop
5034 if(!empty($this->logicHookDepth[$event])) {
5035 if($this->logicHookDepth[$event] > 10)
5038 $this->logicHookDepth[$event] = 0;
5040 //we have to put the increment operator here
5041 //otherwise we may never increase the depth for that event in the case
5042 //where one event will trigger another as in the case of before_save and after_save
5043 //Also keeping the depth per event allow any number of hooks to be called on the bean
5044 //and we only will return if one event gets caught in a loop. We do not increment globally
5045 //for each event called.
5046 $this->logicHookDepth[$event]++;
5048 //method defined in 'include/utils/LogicHook.php'
5050 $logicHook = new LogicHook();
5051 $logicHook->setBean($this);
5052 $logicHook->call_custom_logic($this->module_dir, $event, $arguments);
5057 /* When creating a custom field of type Dropdown, it creates an enum row in the DB.
5058 A typical get_list_view_array() result will have the *KEY* value from that drop-down.
5059 Since custom _dom objects are flat-files included in the $app_list_strings variable,
5060 We need to generate a key-key pair to get the true value like so:
5061 ([module]_cstm->fields_meta_data->$app_list_strings->*VALUE*)*/
5062 function getRealKeyFromCustomFieldAssignedKey($name)
5064 if ($this->custom_fields->avail_fields[$name]['ext1'])
5068 elseif ($this->custom_fields->avail_fields[$name]['ext2'])
5072 elseif ($this->custom_fields->avail_fields[$name]['ext3'])
5078 $GLOBALS['log']->fatal("SUGARBEAN: cannot find Real Key for custom field of type dropdown - cannot return Value.");
5083 return $this->custom_fields->avail_fields[$name][$realKey];
5087 function bean_implements($interface)
5092 * Check whether the user has access to a particular view for the current bean/module
5093 * @param $view string required, the view to determine access for i.e. DetailView, ListView...
5094 * @param $is_owner bool optional, this is part of the ACL check if the current user is an owner they will receive different access
5096 function ACLAccess($view,$is_owner='not_set')
5098 global $current_user;
5099 if($current_user->isAdminForModule($this->getACLCategory())) {
5103 if($is_owner == 'not_set')
5106 $is_owner = $this->isOwner($current_user->id);
5109 //if we don't implent acls return true
5110 if(!$this->bean_implements('ACL'))
5112 $view = strtolower($view);
5118 return ACLController::checkAccess($this->module_dir,'list', true);
5121 if( !$is_owner && $not_set && !empty($this->id)){
5122 $class = get_class($this);
5123 $temp = new $class();
5124 if(!empty($this->fetched_row) && !empty($this->fetched_row['id']) && !empty($this->fetched_row['assigned_user_id']) && !empty($this->fetched_row['created_by'])){
5125 $temp->populateFromRow($this->fetched_row);
5127 $temp->retrieve($this->id);
5129 $is_owner = $temp->isOwner($current_user->id);
5131 case 'popupeditview':
5133 return ACLController::checkAccess($this->module_dir,'edit', $is_owner, $this->acltype);
5137 return ACLController::checkAccess($this->module_dir,'view', $is_owner, $this->acltype);
5139 return ACLController::checkAccess($this->module_dir,'delete', $is_owner, $this->acltype);
5141 return ACLController::checkAccess($this->module_dir,'export', $is_owner, $this->acltype);
5143 return ACLController::checkAccess($this->module_dir,'import', true, $this->acltype);
5145 //if it is not one of the above views then it should be implemented on the page level
5149 * Returns true of false if the user_id passed is the owner
5151 * @param GUID $user_id
5154 function isOwner($user_id)
5156 //if we don't have an id we must be the owner as we are creating it
5157 if(!isset($this->id))
5161 //if there is an assigned_user that is the owner
5162 if(isset($this->assigned_user_id))
5164 if($this->assigned_user_id == $user_id) return true;
5169 //other wise if there is a created_by that is the owner
5170 if(isset($this->created_by) && $this->created_by == $user_id)
5178 * Gets there where statement for checking if a user is an owner
5180 * @param GUID $user_id
5183 function getOwnerWhere($user_id)
5185 if(isset($this->field_defs['assigned_user_id']))
5187 return " $this->table_name.assigned_user_id ='$user_id' ";
5189 if(isset($this->field_defs['created_by']))
5191 return " $this->table_name.created_by ='$user_id' ";
5198 * Used in order to manage ListView links and if they should
5199 * links or not based on the ACL permissions of the user
5201 * @return ARRAY of STRINGS
5203 function listviewACLHelper()
5205 $array_assign = array();
5206 if($this->ACLAccess('DetailView'))
5208 $array_assign['MAIN'] = 'a';
5212 $array_assign['MAIN'] = 'span';
5214 return $array_assign;
5218 * returns this bean as an array
5220 * @return array of fields with id, name, access and category
5222 function toArray($dbOnly = false, $stringOnly = false, $upperKeys=false)
5224 static $cache = array();
5227 foreach($this->field_defs as $field=>$data)
5229 if( !$dbOnly || !isset($data['source']) || $data['source'] == 'db')
5230 if(!$stringOnly || is_string($this->$field))
5233 if(!isset($cache[$field])){
5234 $cache[$field] = strtoupper($field);
5236 $arr[$cache[$field]] = $this->$field;
5240 if(isset($this->$field)){
5241 $arr[$field] = $this->$field;
5251 * Converts an array into an acl mapping name value pairs into files
5255 function fromArray($arr)
5257 foreach($arr as $name=>$value)
5259 $this->$name = $value;
5264 * Loads a row of data into instance of a bean. The data is passed as an array to this function
5266 * @param array $arr row of data fetched from the database.
5269 * Internal function do not override.
5271 function loadFromRow($arr)
5273 $this->populateFromRow($arr);
5274 $this->processed_dates_times = array();
5275 $this->check_date_relationships_load();
5277 $this->fill_in_additional_list_fields();
5279 if($this->hasCustomFields())$this->custom_fields->fill_relationships();
5280 $this->call_custom_logic("process_record");
5283 function hasCustomFields(){
5284 return !empty($GLOBALS['dictionary'][$this->object_name]['custom_fields']);
5288 * Ensure that fields within order by clauses are properly qualified with
5289 * their tablename. This qualification is a requirement for sql server support.
5291 * @param string $order_by original order by from the query
5292 * @param string $qualify prefix for columns in the order by list.
5295 * Internal function do not override.
5297 function create_qualified_order_by( $order_by, $qualify)
5298 { // 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
5299 if (empty($order_by))
5303 $order_by_clause = " ORDER BY ";
5304 $tmp = explode(",", $order_by);
5306 foreach ( $tmp as $stmp)
5308 $stmp = (substr_count($stmp, ".") > 0?trim($stmp):"$qualify." . trim($stmp));
5309 $order_by_clause .= $comma . $stmp;
5312 return $order_by_clause;
5316 * Combined the contents of street field 2 thru 4 into the main field
5318 * @param string $street_field
5321 function add_address_streets(
5325 $street_field_2 = $street_field.'_2';
5326 $street_field_3 = $street_field.'_3';
5327 $street_field_4 = $street_field.'_4';
5328 if ( isset($this->$street_field_2)) {
5329 $this->$street_field .= "\n". $this->$street_field_2;
5330 unset($this->$street_field_2);
5332 if ( isset($this->$street_field_3)) {
5333 $this->$street_field .= "\n". $this->$street_field_3;
5334 unset($this->$street_field_3);
5336 if ( isset($this->$street_field_4)) {
5337 $this->$street_field .= "\n". $this->$street_field_4;
5338 unset($this->$street_field_4);
5340 if ( isset($this->$street_field)) {
5341 $this->$street_field = trim($this->$street_field, "\n");
5345 * Encrpyt and base64 encode an 'encrypt' field type in the bean using Blowfish. The default system key is stored in cache/Blowfish/{keytype}
5346 * @param STRING value -plain text value of the bean field.
5349 function encrpyt_before_save($value)
5351 require_once("include/utils/encryption_utils.php");
5352 return blowfishEncode(blowfishGetKey('encrypt_field'),$value);
5356 * Decode and decrypt a base 64 encoded string with field type 'encrypt' in this bean using Blowfish.
5357 * @param STRING value - an encrypted and base 64 encoded string.
5360 function decrypt_after_retrieve($value)
5362 require_once("include/utils/encryption_utils.php");
5363 return blowfishDecode(blowfishGetKey('encrypt_field'), $value);
5367 * Moved from save() method, functionality is the same, but this is intended to handle
5368 * Optimistic locking functionality.
5370 private function _checkOptimisticLocking($action, $isUpdate){
5371 if($this->optimistic_lock && !isset($_SESSION['o_lock_fs'])){
5372 if(isset($_SESSION['o_lock_id']) && $_SESSION['o_lock_id'] == $this->id && $_SESSION['o_lock_on'] == $this->object_name)
5374 if($action == 'Save' && $isUpdate && isset($this->modified_user_id) && $this->has_been_modified_since($_SESSION['o_lock_dm'], $this->modified_user_id))
5376 $_SESSION['o_lock_class'] = get_class($this);
5377 $_SESSION['o_lock_module'] = $this->module_dir;
5378 $_SESSION['o_lock_object'] = $this->toArray();
5379 $saveform = "<form name='save' id='save' method='POST'>";
5380 foreach($_POST as $key=>$arg)
5382 $saveform .= "<input type='hidden' name='". addslashes($key) ."' value='". addslashes($arg) ."'>";
5384 $saveform .= "</form><script>document.getElementById('save').submit();</script>";
5385 $_SESSION['o_lock_save'] = $saveform;
5386 header('Location: index.php?module=OptimisticLock&action=LockResolve');
5391 unset ($_SESSION['o_lock_object']);
5392 unset ($_SESSION['o_lock_id']);
5393 unset ($_SESSION['o_lock_dm']);
5399 if(isset($_SESSION['o_lock_object'])) { unset ($_SESSION['o_lock_object']); }
5400 if(isset($_SESSION['o_lock_id'])) { unset ($_SESSION['o_lock_id']); }
5401 if(isset($_SESSION['o_lock_dm'])) { unset ($_SESSION['o_lock_dm']); }
5402 if(isset($_SESSION['o_lock_fs'])) { unset ($_SESSION['o_lock_fs']); }
5403 if(isset($_SESSION['o_lock_save'])) { unset ($_SESSION['o_lock_save']); }
5408 * Send assignment notifications and invites for meetings and calls
5410 private function _sendNotifications($check_notify){
5411 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.
5412 && !$this->isOwner($this->created_by) ) // cn: bug 42727 no need to send email to owner (within workflow)
5414 $admin = new Administration();
5415 $admin->retrieveSettings();
5416 $sendNotifications = false;
5418 if ($admin->settings['notify_on'])
5420 $GLOBALS['log']->info("Notifications: user assignment has changed, checking if user receives notifications");
5421 $sendNotifications = true;
5423 elseif(isset($_REQUEST['send_invites']) && $_REQUEST['send_invites'] == 1)
5425 // cn: bug 5795 Send Invites failing for Contacts
5426 $sendNotifications = true;
5430 $GLOBALS['log']->info("Notifications: not sending e-mail, notify_on is set to OFF");
5434 if($sendNotifications == true)
5436 $notify_list = $this->get_notification_recipients();
5437 foreach ($notify_list as $notify_user)
5439 $this->send_assignment_notifications($notify_user, $admin);
5447 * Called from ImportFieldSanitize::relate(), when creating a new bean in a related module. Will
5448 * copies fields over from the current bean into the related. Designed to be overriden in child classes.
5450 * @param SugarBean $newbean newly created related bean
5452 public function populateRelatedBean(
5459 * Called during the import process before a bean save, to handle any needed pre-save logic when
5460 * importing a record
5462 public function beforeImportSave()
5467 * Called during the import process after a bean save, to handle any needed post-save logic when
5468 * importing a record
5470 public function afterImportSave()
5475 * This function is designed to cache references to field arrays that were previously stored in the
5476 * bean files and have since been moved to seperate files. Was previously in include/CacheHandler.php
5479 * @param $module_dir string the module directory
5480 * @param $module string the name of the module
5481 * @param $key string the type of field array we are referencing, i.e. list_fields, column_fields, required_fields
5483 private function _loadCachedArray(
5489 static $moduleDefs = array();
5491 $fileName = 'field_arrays.php';
5493 $cache_key = "load_cached_array.$module_dir.$module.$key";
5494 $result = sugar_cache_retrieve($cache_key);
5497 // Use SugarCache::EXTERNAL_CACHE_NULL_VALUE to store null values in the cache.
5498 if($result == SugarCache::EXTERNAL_CACHE_NULL_VALUE)
5506 if(file_exists('modules/'.$module_dir.'/'.$fileName))
5508 // If the data was not loaded, try loading again....
5509 if(!isset($moduleDefs[$module]))
5511 include('modules/'.$module_dir.'/'.$fileName);
5512 $moduleDefs[$module] = $fields_array;
5514 // Now that we have tried loading, make sure it was loaded
5515 if(empty($moduleDefs[$module]) || empty($moduleDefs[$module][$module][$key]))
5517 // It was not loaded.... Fail. Cache null to prevent future repeats of this calculation
5518 sugar_cache_put($cache_key, SugarCache::EXTERNAL_CACHE_NULL_VALUE);
5522 // It has been loaded, cache the result.
5523 sugar_cache_put($cache_key, $moduleDefs[$module][$module][$key]);
5524 return $moduleDefs[$module][$module][$key];
5527 // It was not loaded.... Fail. Cache null to prevent future repeats of this calculation
5528 sugar_cache_put($cache_key, SugarCache::EXTERNAL_CACHE_NULL_VALUE);
5533 * Returns the ACL category for this module; defaults to the SugarBean::$acl_category if defined
5534 * otherwise it is SugarBean::$module_dir
5538 public function getACLCategory()
5540 return !empty($this->acl_category)?$this->acl_category:$this->module_dir;
5544 * Returns the query used for the export functionality for a module. Override this method if you wish
5545 * to have a custom query to pull this data together instead
5547 * @param string $order_by
5548 * @param string $where
5549 * @return string SQL query
5551 public function create_export_query($order_by, $where)
5553 return $this->create_new_list_query($order_by, $where, array(), array(), 0, '', false, $this, true, true);