]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - data/SugarBean.php
Release 6.1.5
[Github/sugarcrm.git] / data / SugarBean.php
1 <?php
2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4  * SugarCRM is a customer relationship management program developed by
5  * SugarCRM, Inc. Copyright (C) 2004-2011 SugarCRM Inc.
6  * 
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.
13  * 
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
17  * details.
18  * 
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
22  * 02110-1301 USA.
23  * 
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.
26  * 
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.
30  * 
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  ********************************************************************************/
37
38 /*********************************************************************************
39
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  *******************************************************************************/
47
48 require_once('modules/DynamicFields/DynamicField.php');
49
50 /**
51  * SugarBean is the base class for all business objects in Sugar.  It implements
52  * the primary functionality needed for manipulating business objects: create,
53  * retrieve, update, delete.  It allows for searching and retrieving list of records.
54  * It allows for retrieving related objects (e.g. contacts related to a specific account).
55  *
56  * In the current implementation, there can only be one bean per folder.
57  * Naming convention has the bean name be the same as the module and folder name.
58  * All bean names should be singular (e.g. Contact).  The primary table name for
59  * a bean should be plural (e.g. contacts).
60  *
61  */
62 class SugarBean
63 {
64     /**
65      * A pointer to the database helper object DBHelper
66      *
67      * @var DBHelper
68      */
69     var $db;
70
71         /**
72          * When createing a bean, you can specify a value in the id column as
73          * long as that value is unique.  During save, if the system finds an
74          * id, it assumes it is an update.  Setting new_with_id to true will
75          * make sure the system performs an insert instead of an update.
76          *
77          * @var BOOL -- default false
78          */
79         var $new_with_id = false;
80
81
82         /**
83          * Disble vardefs.  This should be set to true only for beans that do not have varders.  Tracker is an example
84          *
85          * @var BOOL -- default false
86          */
87     var $disable_vardefs = false;
88
89
90     /**
91      * holds the full name of the user that an item is assigned to.  Only used if notifications
92      * are turned on and going to be sent out.
93      *
94      * @var String
95      */
96     var $new_assigned_user_name;
97
98         /**
99          * An array of booleans.  This array is cleared out when data is loaded.
100          * As date/times are converted, a "1" is placed under the key, the field is converted.
101          *
102          * @var Array of booleans
103          */
104         var $processed_dates_times = array();
105
106         /**
107          * Whether to process date/time fields for storage in the database in GMT
108          *
109          * @var BOOL
110          */
111         var $process_save_dates =true;
112
113     /**
114      * This signals to the bean that it is being saved in a mass mode.
115      * Examples of this kind of save are import and mass update.
116      * We turn off notificaitons of this is the case to make things more efficient.
117      *
118      * @var BOOL
119      */
120     var $save_from_post = true;
121
122         /**
123          * When running a query on related items using the method: retrieve_by_string_fields
124          * this value will be set to true if more than one item matches the search criteria.
125          *
126          * @var BOOL
127          */
128         var $duplicates_found = false;
129
130         /**
131          * The DBManager instance that was used to load this bean and should be used for
132          * future database interactions.
133          *
134          * @var DBManager
135          */
136         var $dbManager;
137
138         /**
139          * true if this bean has been deleted, false otherwise.
140          *
141          * @var BOOL
142          */
143         var $deleted = 0;
144
145     /**
146      * Should the date modified column of the bean be updated during save?
147      * This is used for admin level functionality that should not be updating
148      * the date modified.  This is only used by sync to allow for updates to be
149      * replicated in a way that will not cause them to be replicated back.
150      *
151      * @var BOOL
152      */
153     var $update_date_modified = true;
154
155     /**
156      * Should the modified by column of the bean be updated during save?
157      * This is used for admin level functionality that should not be updating
158      * the modified by column.  This is only used by sync to allow for updates to be
159      * replicated in a way that will not cause them to be replicated back.
160      *
161      * @var BOOL
162      */
163     var $update_modified_by = true;
164
165     /**
166      * Setting this to true allows for updates to overwrite the date_entered
167      *
168      * @var BOOL
169      */
170     var $update_date_entered = false;
171
172     /**
173      * This allows for seed data to be created without using the current uesr to set the id.
174      * This should be replaced by altering the current user before the call to save.
175      *
176      * @var unknown_type
177      */
178     //TODO This should be replaced by altering the current user before the call to save.
179     var $set_created_by = true;
180
181     var $team_set_id;
182
183     /**
184      * The database table where records of this Bean are stored.
185      *
186      * @var String
187      */
188     var $table_name = '';
189
190         /**
191          * This is the singular name of the bean.  (i.e. Contact).
192          *
193          * @var String
194          */
195         var $object_name = '';
196
197         /** Set this to true if you query contains a sub-select and bean is converting both select statements
198          * into count queries.
199          */
200         var $ungreedy_count=false;
201
202         /**
203          * The name of the module folder for this type of bean.
204          *
205          * @var String
206          */
207         var $module_dir = '';
208         var $field_name_map;
209         var $field_defs;
210         var $custom_fields;
211         var $column_fields = array();
212         var $list_fields = array();
213         var $additional_column_fields = array();
214         var $relationship_fields = array();
215         var $current_notify_user;
216         var $fetched_row=false;
217         var $layout_def;
218         var $force_load_details = false;
219         var $optimistic_lock = false;
220         var $disable_custom_fields = false;
221         var $number_formatting_done = false;
222         var $process_field_encrypted=false;
223         /*
224          * The default ACL type
225          */
226         var $acltype = 'module';
227
228
229     var $additional_meta_fields = array();
230
231     /**
232      * Set to true in the child beans if the module supports importing
233      */
234         var $importable = false;
235
236         /**
237          * Set to true in the child beans if the module use the special notification template
238          */
239         var $special_notification = false;
240
241     /**
242      * Set to true if the bean is being dealt with in a workflow
243      */
244     var $in_workflow = false;
245
246     /**
247      * Used to pass inner join string to ListView Data.
248      */
249     var $listview_inner_join = array();
250
251     /**
252      * Set to true in <modules>/Import/views/view.step4.php if a module is being imported
253      */
254     var $in_import = false;
255     /**
256      * Constructor for the bean, it performs following tasks:
257      *
258      * 1. Initalized a database connections
259      * 2. Load the vardefs for the module implemeting the class. cache the entries
260      *    if needed
261      * 3. Setup row-level security preference
262      * All implementing classes  must call this constructor using the parent::SugarBean() class.
263      *
264      */
265     function SugarBean()
266     {
267         global  $dictionary, $current_user;
268         static $loaded_defs = array();
269         $this->db = DBManagerFactory::getInstance();
270
271         $this->dbManager = & DBManagerFactory::getInstance();
272         if((false == $this->disable_vardefs && empty($loaded_defs[$this->object_name])) || !empty($GLOBALS['reload_vardefs']))
273         {
274                 VardefManager::loadVardef($this->module_dir, $this->object_name);
275
276             // build $this->column_fields from the field_defs if they exist
277             if (!empty($dictionary[$this->object_name]['fields'])) {
278                 foreach ($dictionary[$this->object_name]['fields'] as $key=>$value_array) {
279                     $column_fields[] = $key;
280                 }
281                 $this->column_fields = $column_fields;
282             }
283
284                 //setup custom fields
285                 if(!isset($this->custom_fields) &&
286                         empty($this->disable_custom_fields))
287                 {
288                         $this->setupCustomFields($this->module_dir);
289                 }
290                 //load up field_arrays from CacheHandler;
291                 if(empty($this->list_fields))
292                         $this->list_fields = $this->_loadCachedArray($this->module_dir, $this->object_name, 'list_fields');
293                 if(empty($this->column_fields))
294                         $this->column_fields = $this->_loadCachedArray($this->module_dir, $this->object_name, 'column_fields');
295                 if(empty($this->required_fields))
296                         $this->required_fields = $this->_loadCachedArray($this->module_dir, $this->object_name, 'required_fields');
297
298                 if(isset($GLOBALS['dictionary'][$this->object_name]) && !$this->disable_vardefs)
299                 {
300                         $this->field_name_map = $dictionary[$this->object_name]['fields'];
301                         $this->field_defs =     $dictionary[$this->object_name]['fields'];
302
303                         if(!empty($dictionary[$this->object_name]['optimistic_locking']))
304                         {
305                                 $this->optimistic_lock=true;
306                         }
307                 }
308
309                 $loaded_defs[$this->object_name]['column_fields'] =& $this->column_fields;
310                 $loaded_defs[$this->object_name]['list_fields'] =& $this->list_fields;
311                 $loaded_defs[$this->object_name]['required_fields'] =& $this->required_fields;
312                 $loaded_defs[$this->object_name]['field_name_map'] =& $this->field_name_map;
313                 $loaded_defs[$this->object_name]['field_defs'] =& $this->field_defs;
314         }
315         else
316         {
317                 $this->column_fields =& $loaded_defs[$this->object_name]['column_fields'] ;
318                 $this->list_fields =& $loaded_defs[$this->object_name]['list_fields'];
319                 $this->required_fields =& $loaded_defs[$this->object_name]['required_fields'];
320                 $this->field_name_map =& $loaded_defs[$this->object_name]['field_name_map'];
321                 $this->field_defs =& $loaded_defs[$this->object_name]['field_defs'];
322                 $this->added_custom_field_defs = true;
323
324                 if(!isset($this->custom_fields))
325                 {
326                         $this->setupCustomFields($this->module_dir, false);
327                 }
328                 if(!empty($dictionary[$this->object_name]['optimistic_locking']))
329                 {
330                         $this->optimistic_lock=true;
331                 }
332         }
333
334                 if($this->bean_implements('ACL') && !empty($GLOBALS['current_user'])){
335                 $this->acl_fields = (isset($dictionary[$this->object_name]['acl_fields']) && $dictionary[$this->object_name]['acl_fields'] === false)?false:true;
336         }
337         $this->populateDefaultValues();
338     }
339
340
341     /**
342      * Returns the object name. If object_name is not set, table_name is returned.
343      *
344      * All implementing classes must set a value for the object_name variable.
345      *
346      * @param array $arr row of data fetched from the database.
347      * @return  nothing
348      *
349      */
350     function getObjectName()
351     {
352         if ($this->object_name)
353                 return $this->object_name;
354
355         // This is a quick way out. The generated metadata files have the table name
356         // as the key. The correct way to do this is to override this function
357         // in bean and return the object name. That requires changing all the beans
358         // as well as put the object name in the generator.
359         return $this->table_name;
360     }
361
362     /**
363      * Returns a list of fields with their definitions that have the audited property set to true.
364      * Before calling this function, check whether audit has been enabled for the table/module or not.
365      * You would set the audit flag in the implemting module's vardef file.
366      *
367      * @return an array of
368      * @see is_AuditEnabled
369      *
370      * Internal function, do not override.
371      */
372     function getAuditEnabledFieldDefinitions()
373     {
374         $aclcheck = $this->bean_implements('ACL');
375         $is_owner = $this->isOwner($GLOBALS['current_user']->id);
376         if (!isset($this->audit_enabled_fields))
377         {
378
379                 $this->audit_enabled_fields=array();
380                 foreach ($this->field_defs as $field => $properties)
381                 {
382
383                         if (
384                         (
385                         !empty($properties['Audited']) || !empty($properties['audited']))
386                                 )
387                         {
388
389                                 $this->audit_enabled_fields[$field]=$properties;
390                         }
391                 }
392
393         }
394         return $this->audit_enabled_fields;
395     }
396
397     /**
398      * Return true if auditing is enabled for this object
399      * You would set the audit flag in the implemting module's vardef file.
400      *
401      * @return boolean
402      *
403      * Internal function, do not override.
404      */
405     function is_AuditEnabled()
406     {
407         global $dictionary;
408         if (isset($dictionary[$this->getObjectName()]['audited']))
409         {
410                 return $dictionary[$this->getObjectName()]['audited'];
411         }
412         else
413         {
414                 return false;
415         }
416     }
417
418
419
420     /**
421      * Returns the name of the audit table.
422      * Audit table's name is based on implementing class' table name.
423      *
424      * @return String Audit table name.
425      *
426      * Internal function, do not override.
427      */
428     function get_audit_table_name()
429     {
430         return $this->getTableName().'_audit';
431     }
432
433     /**
434      * If auditing is enabled, create the audit table.
435      *
436      * Function is used by the install scripts and a repair utility in the admin panel.
437      *
438      * Internal function, do not override.
439      */
440     function create_audit_table()
441     {
442         global $dictionary;
443         $table_name=$this->get_audit_table_name();
444
445         require('metadata/audit_templateMetaData.php');
446
447         $fieldDefs = $dictionary['audit']['fields'];
448         $indices = $dictionary['audit']['indices'];
449         // '0' stands for the first index for all the audit tables
450         $indices[0]['name'] = 'idx_' . strtolower($this->getTableName()) . '_' . $indices[0]['name'];
451         $indices[1]['name'] = 'idx_' . strtolower($this->getTableName()) . '_' . $indices[1]['name'];
452
453         $sql=$this->dbManager->helper->createTableSQLParams($table_name, $fieldDefs, $indices);
454
455         $msg = "Error creating table: ".$table_name. ":";
456         $this->dbManager->query($sql,true,$msg);
457     }
458
459     /**
460      * Returns the implementing class' table name.
461      *
462      * All implementing classes set a value for the table_name variable. This value is returned as the
463      * table name. If not set, table name is extracted from the implementing module's vardef.
464      *
465      * @return String Table name.
466      *
467      * Internal function, do not override.
468      */
469     function getTableName()
470     {
471         global $dictionary;
472         if(isset($this->table_name))
473         {
474                 return $this->table_name;
475         }
476         return $dictionary[$this->getObjectName()]['table'];
477     }
478
479     /**
480      * Returns field definitions for the implementing module.
481      *
482      * The definitions were loaded in the constructor.
483      *
484      * @return Array Field definitions.
485      *
486      * Internal function, do not override.
487      */
488     function getFieldDefinitions()
489     {
490         return $this->field_defs;
491     }
492
493     /**
494      * Returns index definitions for the implementing module.
495      *
496      * The definitions were loaded in the constructor.
497      *
498      * @return Array Index definitions.
499      *
500      * Internal function, do not override.
501      */
502     function getIndices()
503     {
504         global $dictionary;
505         if(isset($dictionary[$this->getObjectName()]['indices']))
506         {
507                 return $dictionary[$this->getObjectName()]['indices'];
508         }
509         return array();
510     }
511
512     /**
513      * Returns field definition for the requested field name.
514      *
515      * The definitions were loaded in the constructor.
516      *
517      * @param string field name,
518      * @return Array Field properties or boolean false if the field doesn't exist
519      *
520      * Internal function, do not override.
521      */
522     function getFieldDefinition($name)
523     {
524         if ( !isset($this->field_defs[$name]) )
525             return false;
526
527         return $this->field_defs[$name];
528     }
529
530     /**
531      * Returnss  definition for the id field name.
532      *
533      * The definitions were loaded in the constructor.
534      *
535      * @return Array Field properties.
536      *
537      * Internal function, do not override.
538      */
539     function getPrimaryFieldDefinition()
540     {
541         $def = $this->getFieldDefinition("id");
542         if (!$def)
543                 $def = $this->getFieldDefinition(0);
544         return $def;
545     }
546     /**
547      * Returns the value for the requested field.
548      *
549      * When a row of data is fetched using the bean, all fields are created as variables in the context
550      * of the bean and then fetched values are set in these variables.
551      *
552      * @param string field name,
553      * @return varies Field value.
554      *
555      * Internal function, do not override.
556      */
557     function getFieldValue($name)
558     {
559         if (!isset($this->$name)){
560                 return FALSE;
561         }
562                 if($this->$name === TRUE){
563                         return 1;
564                 }
565                 if($this->$name === FALSE){
566                         return 0;
567                 }
568         return $this->$name;
569     }
570
571     /**
572      * Basically undoes the effects of SugarBean::populateDefaultValues(); this method is best called right after object
573      * initialization.
574      */
575     public function unPopulateDefaultValues()
576     {
577         if ( !is_array($this->field_defs) )
578             return;
579
580         foreach ($this->field_defs as $field => $value) {
581             if( !empty($this->$field)
582                   && ((isset($value['default']) && $this->$field == $value['default']) || (!empty($value['display_default']) && $this->$field == $value['display_default']))
583                     ) {
584                 $this->$field = null;
585             }
586         }
587     }
588
589
590     function populateDefaultValues($force=false){
591         if ( !is_array($this->field_defs) )
592             return;
593         foreach($this->field_defs as $field=>$value){
594                 if((isset($value['default']) || !empty($value['display_default'])) && ($force || empty($this->$field))){
595                     $type = $value['type'];
596
597                         switch($type){
598                                 case 'date':
599                                         if(!empty($value['display_default'])){
600                                                         global $timedate;
601                                                         require_once('modules/DynamicFields/templates/Fields/TemplateDate.php');
602                                                         $td = new TemplateDate();
603                             $timeValue = ($value['display_default'] == 'first of next month') ? $timeValue = strtotime( "+1 month" , strtotime( date("F")."1") ) : strtotime($value['display_default']) ;
604                             $this->$field = $timedate->to_display_date(date($GLOBALS['timedate']->dbDayFormat,$timeValue), false);
605                                                         break;
606                                         }
607                                 case 'datetimecombo':
608                                         if(!empty($value['display_default'])){
609                                                         global $timedate;
610                             $dtAry = explode('&', $value['display_default'] , 2);
611                                                 if(!empty($dtAry[0]) ){
612                                                         $dateValue = ($dtAry[0] == 'first of next month') ? $timeValue = strtotime( "+1 month" , strtotime( date("F")."1") ) : strtotime($dtAry[0]) ;
613                                                         $dateValue = date($GLOBALS['timedate']->dbDayFormat, $dateValue);
614                                                 }else{
615                                                         $dateValue='';
616                                                 }
617                                                 if(!empty($dtAry[1])){
618                                                         $timeValue = date($GLOBALS['timedate']->dbTimeFormat, strtotime($dtAry[1]));
619                                                 }else{
620                                                         $timeValue = '';
621                                                 }
622                             $this->$field = $timedate->to_display_date_time($dateValue.' '.$timeValue , true , false);
623                                                         break;
624                                         }
625                     case 'multienum':
626                         if(empty($value['default']) && !empty($value['display_default']))
627                             $this->$field = $value['display_default'];
628                         else
629                             $this->$field = $value['default'];
630                         break;
631                                         default:
632                         if ( isset($value['default']) && $value['default'] !== '' ) {
633                             $this->$field = htmlentities($value['default'], ENT_QUOTES, 'UTF-8');
634                         } else {
635                             $this->$field = '';
636                         }
637                         } //switch
638                 }
639         } //foreach
640     }
641
642
643     /**
644      * Removes relationship metadata cache.
645      *
646      * Every module that has relationships defined with other modules, has this meta data cached.  The cache is
647      * stores in 2 locations: relationships table and file system. This method clears the cache from both locations.
648      *
649      * @param string $key  module whose meta cache is to be cleared.
650      * @param string $db database handle.
651      * @param string $tablename table name
652      * @param string $dictionary vardef for the module
653      * @param string $module_dir name of subdirectory where module is installed.
654      *
655      * @return Nothing
656      * @static
657      *
658      * Internal function, do not override.
659      */
660     function removeRelationshipMeta($key,$db,$tablename,$dictionary,$module_dir)
661     {
662         //load the module dictionary if not supplied.
663         if ((!isset($dictionary) or empty($dictionary)) && !empty($module_dir))
664         {
665                 $filename='modules/'. $module_dir . '/vardefs.php';
666                 if(file_exists($filename))
667                 {
668                         include($filename);
669                 }
670         }
671         if (!is_array($dictionary) or !array_key_exists($key, $dictionary))
672         {
673                 $GLOBALS['log']->fatal("removeRelationshipMeta: Metadata for table ".$tablename. " does not exist");
674                 display_notice("meta data absent for table ".$tablename." keyed to $key ");
675         }
676         else
677         {
678                 if (isset($dictionary[$key]['relationships']))
679                 {
680                         $RelationshipDefs = $dictionary[$key]['relationships'];
681                         foreach ($RelationshipDefs as $rel_name)
682                         {
683                                 Relationship::delete($rel_name,$db);
684                         }
685                 }
686         }
687     }
688
689
690         /**
691      * This method has been deprecated.
692      *
693          * @see removeRelationshipMeta()
694      * @deprecated 4.5.1 - Nov 14, 2006
695      * @static
696          */
697         function remove_relationship_meta($key,$db,$log,$tablename,$dictionary,$module_dir)
698         {
699                 SugarBean::removeRelationshipMeta($key,$db,$tablename,$dictionary,$module_dir);
700         }
701
702
703     /**
704      * Populates the relationship meta for a module.
705      *
706      * It is called during setup/install. It is used statically to create relationship meta data for many-to-many tables.
707      *
708      *  @param string $key name of the object.
709      *  @param object $db database handle.
710      *  @param string $tablename table, meta data is being populated for.
711      *  @param array dictionary vardef dictionary for the object.     *
712      *  @param string module_dir name of subdirectory where module is installed.
713      *  @param boolean $iscustom Optional,set to true if module is installed in a custom directory. Default value is false.
714      *  @static
715      *
716      *  Internal function, do not override.
717      */
718     function createRelationshipMeta($key,$db,$tablename,$dictionary,$module_dir,$iscustom=false)
719     {
720         //load the module dictionary if not supplied.
721         if (empty($dictionary) && !empty($module_dir))
722         {
723                         if($iscustom)
724                         {
725                                 $filename='custom/modules/' . $module_dir . '/Ext/Vardefs/vardefs.ext.php';
726                         }
727                         else
728                         {
729                                 if ($key == 'User')
730                                 {
731                                         // a very special case for the Employees module
732                                         // this must be done because the Employees/vardefs.php does an include_once on
733                                         // Users/vardefs.php
734                                         $filename='modules/Users/vardefs.php';
735                                 }
736                                 else
737                                 {
738                                         $filename='modules/'. $module_dir . '/vardefs.php';
739                                 }
740                         }
741
742                 if(file_exists($filename))
743                 {
744                         include($filename);
745                         // cn: bug 7679 - dictionary entries defined as $GLOBALS['name'] not found
746                         if(empty($dictionary) || !empty($GLOBALS['dictionary'][$key]))
747                         {
748                                 $dictionary = $GLOBALS['dictionary'];
749                         }
750                 }
751                 else
752                 {
753                         $GLOBALS['log']->debug("createRelationshipMeta: no metadata file found" . $filename);
754                         return;
755                 }
756         }
757
758         if (!is_array($dictionary) or !array_key_exists($key, $dictionary))
759         {
760                 $GLOBALS['log']->fatal("createRelationshipMeta: Metadata for table ".$tablename. " does not exist");
761                 display_notice("meta data absent for table ".$tablename." keyed to $key ");
762         }
763         else
764         {
765                 if (isset($dictionary[$key]['relationships']))
766                 {
767
768                         $RelationshipDefs = $dictionary[$key]['relationships'];
769
770                         $delimiter=',';
771                         global $beanList;
772                         $beanList_ucase=array_change_key_case  ( $beanList ,CASE_UPPER);
773                         foreach ($RelationshipDefs as $rel_name=>$rel_def)
774                         {
775                                 if (isset($rel_def['lhs_module']) and !isset($beanList_ucase[strtoupper($rel_def['lhs_module'])])) {
776                                         $GLOBALS['log']->debug('skipping orphaned relationship record ' . $rel_name . ' lhs module is missing ' . $rel_def['lhs_module']);
777                                                 continue;
778                                 }
779                                 if (isset($rel_def['rhs_module']) and !isset($beanList_ucase[strtoupper($rel_def['rhs_module'])])) {
780                                         $GLOBALS['log']->debug('skipping orphaned relationship record ' . $rel_name . ' rhs module is missing ' . $rel_def['rhs_module']);
781                                                 continue;
782                                 }
783
784
785                                 //check whether relationship exists or not first.
786                                 if (Relationship::exists($rel_name,$db))
787                                 {
788                                         $GLOBALS['log']->debug('Skipping, reltionship already exists '.$rel_name);
789                                 }
790                                 else
791                                 {
792                                         //      add Id to the insert statement.
793                                         $column_list='id';
794                                         $value_list="'".create_guid()."'";
795
796                                         //add relationship name to the insert statement.
797                                         $column_list .= $delimiter.'relationship_name';
798                                         $value_list .= $delimiter."'".$rel_name."'";
799
800                                         //todo check whether $rel_def is an array or not.
801                                         //for now make that assumption.
802                                         //todo specify defaults if meta not defined.
803                                         foreach ($rel_def as $def_key=>$value)
804                                         {
805                                                 $column_list.= $delimiter.$def_key;
806                                                 $value_list.= $delimiter."'".$value."'";
807                                         }
808
809                                         //create the record. todo add error check.
810                                         $insert_string = "INSERT into relationships (" .$column_list. ") values (".$value_list.")";
811                                         $db->query($insert_string, true);
812                                 }
813                         }
814                 }
815                 else
816                 {
817                         //todo
818                         //log informational message stating no relationships meta was set for this bean.
819                 }
820         }
821     }
822
823         /**
824      * This method has been deprecated.
825          * @see createRelationshipMeta()
826      * @deprecated 4.5.1 - Nov 14, 2006
827      * @static
828          */
829         function create_relationship_meta($key,&$db,&$log,$tablename,$dictionary,$module_dir)
830         {
831                 SugarBean::createRelationshipMeta($key,$db,$tablename,$dictionary,$module_dir);
832         }
833
834
835     /**
836      * Loads the request relationship. This method should be called before performing any operations on the related data.
837      *
838      * This method searches the vardef array for the requested attribute's definition. If the attribute is of the type
839      * link then it creates a similary named variable and loads the relationship definition.
840      *
841      * @param string $rel_name  relationship/attribute name.
842      * @return nothing.
843      */
844     function load_relationship($rel_name)
845     {
846         $GLOBALS['log']->debug("SugarBean.load_relationships, Loading relationship (".$rel_name.").");
847
848         if (empty($rel_name))
849         {
850                 $GLOBALS['log']->error("SugarBean.load_relationships, Null relationship name passed.");
851                 return false;
852         }
853         $fieldDefs = $this->getFieldDefinitions();
854
855         //find all definitions of type link.
856         if (!empty($fieldDefs))
857         {
858                 //if rel_name is provided, search the fieldef array keys by name.
859                 if (array_key_exists($rel_name, $fieldDefs))
860                 {
861                         if (array_search('link',$fieldDefs[$rel_name]) === 'type')
862                         {
863                                 //initialize a variable of type Link
864                                 require_once('data/Link.php');
865                                         $class = load_link_class($fieldDefs[$rel_name]);
866
867                                         $this->$rel_name=new $class($fieldDefs[$rel_name]['relationship'], $this, $fieldDefs[$rel_name]);
868
869                                 if (empty($this->$rel_name->_relationship->id)) {
870                                         unset($this->$rel_name);
871                                         return false;
872                                 }
873                                 return true;
874                         }
875                 }
876                 else
877                 {
878                         $GLOBALS['log']->debug("SugarBean.load_relationships, Error Loading relationship (".$rel_name.").");
879                         return false;
880                 }
881         }
882
883         return false;
884     }
885
886     /**
887      * Loads all attributes of type link.
888      *
889      * Method searches the implmenting module's vardef file for attributes of type link, and for each attribute
890      * create a similary named variable and load the relationship definition.
891      *
892      * @return Nothing
893      *
894      * Internal function, do not override.
895      */
896     function load_relationships()
897     {
898
899         $GLOBALS['log']->debug("SugarBean.load_relationships, Loading all relationships of type link.");
900
901         $linked_fields=$this->get_linked_fields();
902         require_once("data/Link.php");
903         foreach($linked_fields as $name=>$properties)
904         {
905                 $class = load_link_class($properties);
906
907                 $this->$name=new $class($properties['relationship'], $this, $properties);
908         }
909     }
910
911     /**
912      * Returns an array of beans of related data.
913      *
914      * For instance, if an account is related to 10 contacts , this function will return an array of contacts beans (10)
915      * with each bean representing a contact record.
916      * Method will load the relationship if not done so already.
917      *
918      * @param string $field_name relationship to be loaded.
919      * @param string $bean name  class name of the related bean.
920      * @param array $sort_array optional, unused
921      * @param int $begin_index Optional, default 0, unused.
922      * @param int $end_index Optional, default -1
923      * @param int $deleted Optional, Default 0, 0  adds deleted=0 filter, 1  adds deleted=1 filter.
924      * @param string $optional_where, Optional, default empty.
925      *
926      * Internal function, do not override.
927      */
928     function get_linked_beans($field_name,$bean_name, $sort_array = array(), $begin_index = 0, $end_index = -1,
929                               $deleted=0, $optional_where="")
930     {
931
932         //if bean_name is Case then use aCase
933         if($bean_name=="Case")
934                 $bean_name = "aCase";
935
936         //add a references to bean_name if it doe not exist aleady.
937         if (!(class_exists($bean_name)))
938         {
939
940                 if (isset($GLOBALS['beanList']) && isset($GLOBALS['beanFiles']))
941                 {
942                         global $beanFiles;
943                 }
944                 else
945                 {
946
947                 }
948                 $bean_file=$beanFiles[$bean_name];
949                 include_once($bean_file);
950         }
951
952         $this->load_relationship($field_name);
953
954         return $this->$field_name->getBeans(new $bean_name(), $sort_array, $begin_index, $end_index, $deleted, $optional_where);
955     }
956
957     /**
958      * Returns an array of fields that are of type link.
959      *
960      * @return array List of fields.
961      *
962      * Internal function, do not override.
963      */
964     function get_linked_fields()
965     {
966
967         $linked_fields=array();
968
969  //     require_once('data/Link.php');
970
971         $fieldDefs = $this->getFieldDefinitions();
972
973         //find all definitions of type link.
974         if (!empty($fieldDefs))
975         {
976                 foreach ($fieldDefs as $name=>$properties)
977                 {
978                         if (array_search('link',$properties) === 'type')
979                         {
980                                 $linked_fields[$name]=$properties;
981                         }
982                 }
983         }
984
985         return $linked_fields;
986     }
987
988     /**
989      * Returns an array of fields that are able to be Imported into
990      * i.e. 'importable' not set to 'false'
991      *
992      * @return array List of fields.
993      *
994      * Internal function, do not override.
995      */
996     function get_importable_fields()
997     {
998         $importableFields = array();
999
1000         $fieldDefs= $this->getFieldDefinitions();
1001
1002         if (!empty($fieldDefs)) {
1003             foreach ($fieldDefs as $key=>$value_array) {
1004                 if ( (isset($value_array['importable'])
1005                         && (is_string($value_array['importable']) && $value_array['importable'] == 'false'
1006                             || is_bool($value_array['importable']) && $value_array['importable'] == false))
1007                     || (isset($value_array['type']) && $value_array['type'] == 'link')
1008                     || (isset($value_array['auto_increment'])
1009                         && ($value_array['type'] == true || $value_array['type'] == 'true')) ) {
1010                     // do not allow import.
1011                 }
1012                 else {
1013                     $importableFields[$key]=$value_array;
1014                 }
1015             }
1016         }
1017
1018         return $importableFields;
1019     }
1020
1021     /**
1022      * Returns an array of fields that are of type relate.
1023      *
1024      * @return array List of fields.
1025      *
1026      * Internal function, do not override.
1027      */
1028     function get_related_fields()
1029     {
1030
1031         $related_fields=array();
1032
1033 //      require_once('data/Link.php');
1034
1035         $fieldDefs = $this->getFieldDefinitions();
1036
1037         //find all definitions of type link.
1038         if (!empty($fieldDefs))
1039         {
1040                 foreach ($fieldDefs as $name=>$properties)
1041                 {
1042                         if (array_search('relate',$properties) === 'type')
1043                         {
1044                                 $related_fields[$name]=$properties;
1045                         }
1046                 }
1047         }
1048
1049         return $related_fields;
1050     }
1051
1052     /**
1053      * Returns an array of fields that are required for import
1054      *
1055      * @return array
1056      */
1057     function get_import_required_fields()
1058     {
1059         $importable_fields = $this->get_importable_fields();
1060         $required_fields   = array();
1061
1062         foreach ( $importable_fields as $name => $properties )
1063             if ( isset($properties['importable']) && $properties['importable'] == 'required' )
1064                 $required_fields[$name] = $properties;
1065
1066         return $required_fields;
1067     }
1068
1069     /**
1070      * Iterates through all the relationships and deletes all records for reach relationship.
1071      *
1072      * @param string $id Primary key value of the parent reocrd
1073      */
1074     function delete_linked($id)
1075     {
1076         $linked_fields=$this->get_linked_fields();
1077
1078         foreach ($linked_fields as $name => $value)
1079         {
1080                 if ($this->load_relationship($name))
1081                 {
1082                         $GLOBALS['log']->debug('relationship loaded');
1083                         $this->$name->delete($id);
1084                 }
1085                 else
1086                 {
1087                         $GLOBALS['log']->error('error loading relationship');
1088                 }
1089         }
1090     }
1091
1092     /**
1093      * Creates tables for the module implementing the class.
1094      * If you override this function make sure that your code can handles table creation.
1095      *
1096      */
1097     function create_tables()
1098     {
1099         global $dictionary;
1100
1101         $key = $this->getObjectName();
1102         if (!array_key_exists($key, $dictionary))
1103         {
1104                 $GLOBALS['log']->fatal("create_tables: Metadata for table ".$this->table_name. " does not exist");
1105                 display_notice("meta data absent for table ".$this->table_name." keyed to $key ");
1106         }
1107         else
1108         {
1109                 if(!$this->db->tableExists($this->table_name))
1110                 {
1111                         $this->dbManager->createTable($this);
1112                             if($this->bean_implements('ACL')){
1113                         if(!empty($this->acltype)){
1114                                 ACLAction::addActions($this->module_dir, $this->acltype);
1115                         }else{
1116                                 ACLAction::addActions($this->module_dir);
1117                         }
1118                     }
1119                 }
1120                 else
1121                 {
1122                         echo "Table already exists : $this->table_name<br>";
1123                 }
1124                 if($this->is_AuditEnabled()){
1125                                 if (!$this->db->tableExists($this->get_audit_table_name())) {
1126                                                 $this->create_audit_table();
1127                                         }
1128                 }
1129
1130         }
1131     }
1132
1133     /**
1134      * Delete the primary table for the module implementing the class.
1135      * If custom fields were added to this table/module, the custom table will be removed too, along with the cache
1136      * entries that define the custom fields.
1137      *
1138      */
1139     function drop_tables()
1140     {
1141         global $dictionary;
1142         $key = $this->getObjectName();
1143         if (!array_key_exists($key, $dictionary))
1144         {
1145                 $GLOBALS['log']->fatal("drop_tables: Metadata for table ".$this->table_name. " does not exist");
1146                 echo "meta data absent for table ".$this->table_name."<br>\n";
1147         } else {
1148                 if(empty($this->table_name))return;
1149                 if ($this->db->tableExists($this->table_name))
1150
1151                         $this->dbManager->dropTable($this);
1152                 if ($this->db->tableExists($this->table_name. '_cstm'))
1153                 {
1154                         $this->dbManager->dropTableName($this->table_name. '_cstm');
1155                         DynamicField::deleteCache();
1156                 }
1157                 if ($this->db->tableExists($this->get_audit_table_name())) {
1158                                 $this->dbManager->dropTableName($this->get_audit_table_name());
1159                         }
1160
1161
1162         }
1163     }
1164
1165
1166     /**
1167      * Loads the definition of custom fields defined for the module.
1168      * Local file system cache is created as needed.
1169      *
1170      * @param string $module_name setting up custom fields for this module.
1171      * @param boolean $clean_load Optional, default true, rebuilds the cache if set to true.
1172      */
1173     function setupCustomFields($module_name, $clean_load=true)
1174     {
1175         $this->custom_fields = new DynamicField($module_name);
1176         $this->custom_fields->setup($this);
1177
1178     }
1179
1180         /**
1181          * Cleans char, varchar, text, etc. fields of XSS type materials
1182          */
1183         function cleanBean() {
1184                 foreach($this->field_defs as $key => $def) {
1185
1186                         if (isset($def['type'])) {
1187                             $type=$def['type'];
1188                         }
1189                         if(isset($def['dbType']))
1190                                 $type .= $def['dbType'];
1191
1192                         if((strpos($type, 'char') !== false ||
1193                                 strpos($type, 'text') !== false ||
1194                                 $type == 'enum') &&
1195                 !empty($this->$key)
1196                         ) {
1197                                 $str = from_html($this->$key);
1198                                 // Julian's XSS cleaner
1199                                 $potentials = clean_xss($str, false);
1200
1201                                 if(is_array($potentials) && !empty($potentials)) {
1202                                         foreach($potentials as $bad) {
1203                         $str = str_replace($bad, "", $str);
1204                                         }
1205                     $this->$key = to_html($str);
1206                                 }
1207                         }
1208                 }
1209         }
1210
1211         /**
1212         * Implements a generic insert and update logic for any SugarBean
1213         * This method only works for subclasses that implement the same variable names.
1214         * This method uses the presence of an id field that is not null to signify and update.
1215         * The id field should not be set otherwise.
1216     *
1217     * @param boolean $check_notify Optional, default false, if set to true assignee of the record is notified via email.
1218         * @todo Add support for field type validation and encoding of parameters.
1219         */
1220         function save($check_notify = FALSE)
1221         {
1222                 // cn: SECURITY - strip XSS potential vectors
1223                 $this->cleanBean();
1224         // This is used so custom/3rd-party code can be upgraded with fewer issues, this will be removed in a future release
1225         $this->fixUpFormatting();
1226                 global $timedate;
1227                 global $current_user, $action;
1228
1229                 $isUpdate = true;
1230                 if(empty($this->id))
1231                 {
1232                         $isUpdate = false;
1233                 }
1234
1235                 if ( $this->new_with_id == true )
1236                 {
1237                         $isUpdate = false;
1238                 }
1239                 if(empty($this->date_modified) || $this->update_date_modified)
1240                 {
1241                         $this->date_modified = gmdate($GLOBALS['timedate']->get_db_date_time_format());
1242                 }
1243
1244                 $this->_checkOptimisticLocking($action, $isUpdate);
1245
1246         if(!empty($this->modified_by_name)) $this->old_modified_by_name = $this->modified_by_name;
1247                 if($this->update_modified_by)
1248                 {
1249                         $this->modified_user_id = 1;
1250
1251                         if (!empty($current_user))
1252                         {
1253                                 $this->modified_user_id = $current_user->id;
1254                 $this->modified_by_name = $current_user->user_name;
1255                         }
1256                 }
1257                 if ($this->deleted != 1)
1258                         $this->deleted = 0;
1259                 if($isUpdate)
1260                 {
1261                         $query = "Update ";
1262                 }
1263                 else
1264                 {
1265                         if (empty($this->date_entered))
1266                         {
1267                                 $this->date_entered = $this->date_modified;
1268                         }
1269                         if($this->set_created_by == true)
1270                         {
1271                                 // created by should always be this user
1272                                 $this->created_by = (isset($current_user)) ? $current_user->id : "";
1273                         }
1274                         if( $this->new_with_id == false)
1275                         {
1276                                 $this->id = create_guid();
1277                         }
1278                         $query = "INSERT into ";
1279                 }
1280                 if($isUpdate && !$this->update_date_entered)
1281                 {
1282                         unset($this->date_entered);
1283                 }
1284                 // call the custom business logic
1285                 $custom_logic_arguments['check_notify'] = $check_notify;
1286
1287                 $this->call_custom_logic("before_save", $custom_logic_arguments);
1288                 unset($custom_logic_arguments);
1289
1290                 if(isset($this->custom_fields))
1291                 {
1292                         $this->custom_fields->bean =& $this;
1293                         $this->custom_fields->save($isUpdate);
1294                 }
1295
1296                 // use the db independent query generator
1297                 $this->preprocess_fields_on_save();
1298
1299                 //construct the SQL to create the audit record if auditing is enabled.
1300                 $dataChanges=array();
1301                 if ($this->is_AuditEnabled())
1302                 {
1303                         if ($isUpdate && !isset($this->fetched_row))
1304                         {
1305                                 $GLOBALS['log']->debug('Auditing: Retrieve was not called, audit record will not be created.');
1306                         }
1307                         else
1308                         {
1309                                 $dataChanges=$this->dbManager->helper->getDataChanges($this);
1310                         }
1311                 }
1312
1313                 $this->_sendNotifications($check_notify);
1314
1315                 if ($this->db->dbType == "oci8")
1316                 {
1317                 }
1318                 if ($this->db->dbType == 'mysql')
1319                 {
1320                         // write out the SQL statement.
1321                         $query .= $this->table_name." set ";
1322
1323                         $firstPass = 0;
1324
1325                         foreach($this->field_defs as $field=>$value)
1326                         {
1327                                 if(!isset($value['source']) || $value['source'] == 'db')
1328                                 {
1329                                         // Do not write out the id field on the update statement.
1330                                         // We are not allowed to change ids.
1331                                         if($isUpdate && ('id' == $field))
1332                                                 continue;
1333                                         //custom fields handle there save seperatley
1334                                         if(isset($this->field_name_map) && !empty($this->field_name_map[$field]['custom_type']))
1335                                                 continue;
1336
1337                                         // Only assign variables that have been set.
1338                                         if(isset($this->$field))
1339                                         {
1340                                                 //bug: 37908 - this is to handle the issue where the bool value is false, but strlen(false) <= so it will
1341                                                 //set the default value. TODO change this code to esend all fields through getFieldValue() like DbHelper->insertSql
1342                                                 if(!empty($value['type']) && $value['type'] == 'bool'){
1343                                                         $this->$field = $this->getFieldValue($field);
1344                                                 }
1345
1346                                                 if(strlen($this->$field) <= 0)
1347                                                 {
1348                                                         if(!$isUpdate && isset($value['default']) && (strlen($value['default']) > 0))
1349                                                         {
1350                                                                 $this->$field = $value['default'];
1351                                                         }
1352                                                         else
1353                                                         {
1354                                                                 $this->$field = null;
1355                                                         }
1356                                                 }
1357                                                 // Try comparing this element with the head element.
1358                                                 if(0 == $firstPass)
1359                                                         $firstPass = 1;
1360                                                 else
1361                                                         $query .= ", ";
1362
1363                                                 if(is_null($this->$field))
1364                                                 {
1365                                                         $query .= $field."=null";
1366                                                 }
1367                                                 else
1368                                                 {
1369                             //added check for ints because sql-server does not like casting varchar with a decimal value
1370                             //into an int.
1371                             if(isset($value['type']) and $value['type']=='int') {
1372                                 $query .= $field."=".$this->db->quote($this->$field);
1373                             } elseif ( isset($value['len']) ) {
1374                                 $query .= $field."='".$this->db->quote($this->db->truncate(from_html($this->$field),$value['len']))."'";
1375                             } else {
1376                                 $query .= $field."='".$this->db->quote($this->$field)."'";
1377                             }
1378                                                 }
1379                                         }
1380                                 }
1381                         }
1382
1383                         if($isUpdate)
1384                         {
1385                                 $query = $query." WHERE ID = '$this->id'";
1386                                 $GLOBALS['log']->info("Update $this->object_name: ".$query);
1387                         }
1388                         else
1389                         {
1390                                 $GLOBALS['log']->info("Insert: ".$query);
1391                         }
1392                         $GLOBALS['log']->info("Save: $query");
1393                         $this->db->query($query, true);
1394                 }
1395                 //process if type is set to mssql
1396                 if ($this->db->dbType == 'mssql')
1397                 {
1398                         if($isUpdate)
1399                         {
1400                                 // build out the SQL UPDATE statement.
1401                                 $query = "UPDATE " . $this->table_name." SET ";
1402                                 $firstPass = 0;
1403                                 foreach($this->field_defs as $field=>$value)
1404                                 {
1405                                         if(!isset($value['source']) || $value['source'] == 'db')
1406                                         {
1407                                                 // Do not write out the id field on the update statement.
1408                                                 // We are not allowed to change ids.
1409                                                 if($isUpdate && ('id' == $field))
1410                                                         continue;
1411
1412                                                 // If the field is an auto_increment field, then we shouldn't be setting it.  This was added
1413                                                 // specially for Bugs and Cases which have a number associated with them.
1414                                                 if ($isUpdate && isset($this->field_name_map[$field]['auto_increment']) &&
1415                                                     $this->field_name_map[$field]['auto_increment'] == true)
1416                                                         continue;
1417
1418                                                 //custom fields handle their save seperatley
1419                                                 if(isset($this->field_name_map) && !empty($this->field_name_map[$field]['custom_type']))
1420                                                         continue;
1421
1422                                                 // Only assign variables that have been set.
1423                                                 if(isset($this->$field))
1424                                                 {
1425                                                         //bug: 37908 - this is to handle the issue where the bool value is false, but strlen(false) <= so it will
1426                                                         //set the default value. TODO change this code to esend all fields through getFieldValue() like DbHelper->insertSql
1427                                                         if(!empty($value['type']) && $value['type'] == 'bool'){
1428                                                                 $this->$field = $this->getFieldValue($field);
1429                                                         }
1430
1431                                                         if(strlen($this->$field) <= 0)
1432                                                         {
1433                                                                 if(!$isUpdate && isset($value['default']) && (strlen($value['default']) > 0))
1434                                                                 {
1435                                                                         $this->$field = $value['default'];
1436                                                                 }
1437                                                                 else
1438                                                                 {
1439                                                                         $this->$field = null;
1440                                                                 }
1441                                                         }
1442                                                         // Try comparing this element with the head element.
1443                                                         if(0 == $firstPass)
1444                                                                 $firstPass = 1;
1445                                                         else
1446                                                                 $query .= ", ";
1447
1448                                                         if(is_null($this->$field))
1449                                                         {
1450                                                                 $query .= $field."=null";
1451                                                         }
1452                                                         elseif ( isset($value['len']) )
1453                            {
1454                                $query .= $field."='".$this->db->quote($this->db->truncate(from_html($this->$field),$value['len']))."'";
1455                            }
1456                            else
1457                                                         {
1458                                                                 $query .= $field."='".$this->db->quote($this->$field)."'";
1459                                                         }
1460                                                 }
1461                                         }
1462                                 }
1463                                 $query = $query." WHERE ID = '$this->id'";
1464                                 $GLOBALS['log']->info("Update $this->object_name: ".$query);
1465                         }
1466                         else
1467                         {
1468                                 $colums = array();
1469                                 $values = array();
1470                                 foreach($this->field_defs as $field=>$value)
1471                                 {
1472                                         if(!isset($value['source']) || $value['source'] == 'db')
1473                                         {
1474                                                 // Do not write out the id field on the update statement.
1475                                                 // We are not allowed to change ids.
1476                                                 //if($isUpdate && ('id' == $field)) continue;
1477                                                 //custom fields handle there save seperatley
1478
1479                                                 if(isset($this->field_name_map) && !empty($this->field_name_map[$field]['custom_module']))
1480                                                 continue;
1481
1482                                                 // Only assign variables that have been set.
1483                                                 if(isset($this->$field))
1484                                                 {
1485                                                         //trim the value in case empty space is passed in.
1486                                                         //this will allow default values set in db to take effect, otherwise
1487                                                         //will insert blanks into db
1488                                                         $trimmed_field = trim($this->$field);
1489                                                         //if this value is empty, do not include the field value in statement
1490                                                         if($trimmed_field =='')
1491                                                         {
1492                                                                 continue;
1493                                                         }
1494                                                         //bug: 37908 - this is to handle the issue where the bool value is false, but strlen(false) <= so it will
1495                                                         //set the default value. TODO change this code to esend all fields through getFieldValue() like DbHelper->insertSql
1496                                                         if(!empty($value['type']) && $value['type'] == 'bool'){
1497                                                                 $this->$field = $this->getFieldValue($field);
1498                                                         }
1499                             //added check for ints because sql-server does not like casting varchar with a decimal value
1500                             //into an int.
1501                             if(isset($value['type']) and $value['type']=='int') {
1502                                 $values[] = $this->db->quote($this->$field);
1503                             } elseif ( isset($value['len']) ) {
1504                                 $values[] = "'".$this->db->quote($this->db->truncate(from_html($this->$field),$value['len']))."'";
1505                             } else {
1506                                 $values[] = "'".$this->db->quote($this->$field)."'";
1507
1508                             }
1509                                                         $columns[] = $field;
1510                                                 }
1511                                         }
1512                                 }
1513                                 // build out the SQL INSERT statement.
1514                                 $query = "INSERT INTO $this->table_name (" .implode("," , $columns). " ) VALUES ( ". implode("," , $values). ')';
1515                                 $GLOBALS['log']->info("Insert: ".$query);
1516                         }
1517
1518                         $GLOBALS['log']->info("Save: $query");
1519                         $this->db->query($query, true);
1520                 }
1521                 if (!empty($dataChanges) && is_array($dataChanges))
1522                 {
1523                         foreach ($dataChanges as $change)
1524                         {
1525                                 $this->dbManager->helper->save_audit_records($this,$change);
1526                         }
1527                 }
1528
1529                 // let subclasses save related field changes
1530                 $this->save_relationship_changes($isUpdate);
1531
1532
1533                 //If we aren't in setup mode and we have a current user and module, then we track
1534                 if(isset($GLOBALS['current_user']) && isset($this->module_dir))
1535                 {
1536                         $this->track_view($current_user->id, $this->module_dir, 'save');
1537                 }
1538
1539                 $this->call_custom_logic('after_save', '');
1540
1541                 return $this->id;
1542         }
1543
1544
1545     /**
1546      * Performs a check if the record has been modified since the specified date
1547      *
1548      * @param date $date Datetime for verification
1549      * @param string $modified_user_id User modified by
1550      */
1551     function has_been_modified_since($date, $modified_user_id)
1552     {
1553         global $current_user;
1554         if (isset($current_user))
1555         {
1556             if ($this->db->dbType == 'mssql')
1557                 $date_modified_string = 'CONVERT(varchar(20), date_modified, 120)';
1558             else
1559                 $date_modified_string = 'date_modified';
1560
1561             $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') . ')';
1562             $result = $this->db->query($query);
1563
1564                 if($this->db->fetchByAssoc($result))
1565                 {
1566                         return true;
1567                 }
1568         }
1569         return false;
1570     }
1571
1572         /**
1573          * Determines which users receive a notification
1574          */
1575         function get_notification_recipients() {
1576                 $notify_user = new User();
1577                 $notify_user->retrieve($this->assigned_user_id);
1578                 $this->new_assigned_user_name = $notify_user->full_name;
1579
1580                 $GLOBALS['log']->info("Notifications: recipient is $this->new_assigned_user_name");
1581
1582                 $user_list = array($notify_user);
1583                 return $user_list;
1584                 /*
1585                 //send notifications to followers, but ensure to not query for the assigned_user.
1586                 return SugarFollowing::getFollowers($this, $notify_user);
1587                 */
1588         }
1589
1590         /**
1591         * Handles sending out email notifications when items are first assigned to users
1592         *
1593         * @param string $notify_user user to notify
1594         * @param string $admin the admin user that sends out the notification
1595         */
1596         function send_assignment_notifications($notify_user, $admin)
1597         {
1598                 global $current_user;
1599
1600                 if(($this->object_name == 'Meeting' || $this->object_name == 'Call') || $notify_user->receive_notifications)
1601                 {
1602                         $sendToEmail = $notify_user->emailAddress->getPrimaryAddress($notify_user);
1603             $sendEmail = TRUE;
1604                         if(empty($sendToEmail)) {
1605                                 $GLOBALS['log']->warn("Notifications: no e-mail address set for user {$notify_user->user_name}, cancelling send");
1606                                 $sendEmail = FALSE;
1607                         }
1608
1609                         $notify_mail = $this->create_notification_email($notify_user);
1610                         $notify_mail->setMailerForSystem();
1611
1612                         if(empty($admin->settings['notify_send_from_assigning_user'])) {
1613                             $notify_mail->From = $admin->settings['notify_fromaddress'];
1614                             $notify_mail->FromName = (empty($admin->settings['notify_fromname'])) ? "" : $admin->settings['notify_fromname'];
1615                         } else {
1616                             // Send notifications from the current user's e-mail (ifset)
1617                             $fromAddress = $current_user->emailAddress->getReplyToAddress($current_user);
1618                             $fromAddress = !empty($fromAddress) ? $fromAddress : $admin->settings['notify_fromaddress'];
1619                             $notify_mail->From = $fromAddress;
1620                 //Use the users full name is available otherwise default to system name
1621                             $from_name = !empty($admin->settings['notify_fromname']) ? $admin->settings['notify_fromname'] : "";
1622                 $from_name = !empty($current_user->full_name) ? $current_user->full_name : $from_name;
1623                             $notify_mail->FromName = $from_name;
1624                         }
1625
1626                         if($sendEmail && !$notify_mail->Send()) {
1627                             $GLOBALS['log']->fatal("Notifications: error sending e-mail (method: {$notify_mail->Mailer}), (error: {$notify_mail->ErrorInfo})");
1628                         } else {
1629                             $GLOBALS['log']->fatal("Notifications: e-mail successfully sent");
1630                         }
1631
1632                 }
1633         }
1634
1635         /**
1636         * This function handles create the email notifications email.
1637         * @param string $notify_user the user to send the notification email to
1638         */
1639         function create_notification_email($notify_user) {
1640                 global $sugar_version;
1641                 global $sugar_config;
1642                 global $app_list_strings;
1643                 global $current_user;
1644                 global $locale;
1645         global $beanList;
1646         $OBCharset = $locale->getPrecedentPreference('default_email_charset');
1647
1648
1649                 require_once("include/SugarPHPMailer.php");
1650
1651                 $notify_address = $notify_user->emailAddress->getPrimaryAddress($notify_user);
1652                 $notify_name = $notify_user->full_name;
1653                 $GLOBALS['log']->debug("Notifications: user has e-mail defined");
1654
1655                 $notify_mail = new SugarPHPMailer();
1656                 $notify_mail->AddAddress($notify_address,$locale->translateCharsetMIME(trim($notify_name), 'UTF-8', $OBCharset));
1657
1658                 if(empty($_SESSION['authenticated_user_language'])) {
1659                         $current_language = $sugar_config['default_language'];
1660                 } else {
1661                         $current_language = $_SESSION['authenticated_user_language'];
1662                 }
1663         $xtpl = new XTemplate(get_notify_template_file($current_language));
1664         if($this->module_dir == "Cases") {
1665             $template_name = "Case"; //we should use Case, you can refer to the en_us.notify_template.html.
1666         }
1667         else {
1668             $template_name = $beanList[$this->module_dir]; //bug 20637, in workflow this->object_name = strange chars.
1669         }
1670
1671                 $this->current_notify_user = $notify_user;
1672
1673                 if(in_array('set_notification_body', get_class_methods($this))) {
1674                         $xtpl = $this->set_notification_body($xtpl, $this);
1675                 } else {
1676                         $xtpl->assign("OBJECT", $this->object_name);
1677                         $template_name = "Default";
1678                 }
1679         if(!empty($_SESSION["special_notification"]) && $_SESSION["special_notification"]) {
1680             $template_name = $beanList[$this->module_dir].'Special';
1681         }
1682         if($this->special_notification) {
1683                 $template_name = $beanList[$this->module_dir].'Special';
1684         }
1685                 $xtpl->assign("ASSIGNED_USER", $this->new_assigned_user_name);
1686                 $xtpl->assign("ASSIGNER", $current_user->name);
1687                 $port = '';
1688
1689                 if(isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) {
1690                         $port = $_SERVER['SERVER_PORT'];
1691                 }
1692
1693                 if (!isset($_SERVER['HTTP_HOST'])) {
1694                         $_SERVER['HTTP_HOST'] = '';
1695                 }
1696
1697                 $httpHost = $_SERVER['HTTP_HOST'];
1698
1699                 if($colon = strpos($httpHost, ':')) {
1700                         $httpHost    = substr($httpHost, 0, $colon);
1701                 }
1702
1703                 $parsedSiteUrl = parse_url($sugar_config['site_url']);
1704                 $host = $parsedSiteUrl['host'];
1705                 if(!isset($parsedSiteUrl['port'])) {
1706                         $parsedSiteUrl['port'] = 80;
1707                 }
1708
1709                 $port           = ($parsedSiteUrl['port'] != 80) ? ":".$parsedSiteUrl['port'] : '';
1710                 $path           = !empty($parsedSiteUrl['path']) ? $parsedSiteUrl['path'] : "";
1711                 $cleanUrl       = "{$parsedSiteUrl['scheme']}://{$host}{$port}{$path}";
1712
1713                 $xtpl->assign("URL", $cleanUrl."/index.php?module={$this->module_dir}&action=DetailView&record={$this->id}");
1714                 $xtpl->assign("SUGAR", "Sugar v{$sugar_version}");
1715                 $xtpl->parse($template_name);
1716                 $xtpl->parse($template_name . "_Subject");
1717
1718                 $notify_mail->Body = from_html(trim($xtpl->text($template_name)));
1719                 $notify_mail->Subject = from_html($xtpl->text($template_name . "_Subject"));
1720
1721                 // cn: bug 8568 encode notify email in User's outbound email encoding
1722                 $notify_mail->prepForOutbound();
1723
1724                 return $notify_mail;
1725         }
1726
1727         /**
1728         * This function is a good location to save changes that have been made to a relationship.
1729         * This should be overriden in subclasses that have something to save.
1730         *
1731         * @param $is_update true if this save is an update.
1732         */
1733 function save_relationship_changes($is_update, $exclude=array())
1734         {
1735                 $new_rel_id = false;
1736                 $new_rel_link = false;
1737                 //this allows us to dynamically relate modules without adding it to the relationship_fields array
1738                 if(!empty($_REQUEST['relate_id']) && !in_array($_REQUEST['relate_to'], $exclude) && $_REQUEST['relate_id'] != $this->id){
1739                         $new_rel_id = $_REQUEST['relate_id'];
1740                         $new_rel_relname = $_REQUEST['relate_to'];
1741             if(!empty($this->in_workflow) && !empty($this->not_use_rel_in_req)) {
1742                 $new_rel_id = $this->new_rel_id;
1743                 $new_rel_relname = $this->new_rel_relname;
1744             }
1745                         $new_rel_link = $new_rel_relname;
1746                         //Try to find the link in this bean based on the relationship
1747                         foreach ( $this->field_defs as $key => $def ) {
1748                                 if (isset($def['type']) && $def['type'] == 'link'
1749                                 && isset($def['relationship']) && $def['relationship'] == $new_rel_relname) {
1750                                         $new_rel_link = $key;
1751                                 }
1752                         }
1753                 }
1754
1755                 // First we handle the preset fields listed in the fixed relationship_fields array hardcoded into the OOB beans
1756                 // TODO: remove this mechanism and replace with mechanism exclusively based on the vardefs
1757                 if (isset($this->relationship_fields) && is_array($this->relationship_fields))
1758                 {
1759                         foreach ($this->relationship_fields as $id=>$rel_name)
1760                         {
1761
1762                                 if(in_array($id, $exclude))continue;
1763
1764                                 if(!empty($this->$id))
1765                                 {
1766                                     $GLOBALS['log']->debug('save_relationship_changes(): From relationship_field array - adding a relationship record: '.$rel_name . ' = ' . $this->$id);
1767                                         //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
1768                                         if($this->$id == $new_rel_id){
1769                                                 $new_rel_id = false;
1770                                         }
1771                                         $this->load_relationship($rel_name);
1772                                         $this->$rel_name->add($this->$id);
1773                                         $related = true;
1774                                 }
1775                                 else
1776                                 {
1777                                         //if before value is not empty then attempt to delete relationship
1778                                         if(!empty($this->rel_fields_before_value[$id]))
1779                                         {
1780                                                 $GLOBALS['log']->debug('save_relationship_changes(): From relationship_field array - attempting to remove the relationship record, using relationship attribute'.$rel_name);
1781                                                 $this->load_relationship($rel_name);
1782                                                 $this->$rel_name->delete($this->id,$this->rel_fields_before_value[$id]);
1783                                         }
1784                                 }
1785                         }
1786                 }
1787
1788 /*      Next, we'll attempt to update all of the remaining relate fields in the vardefs that have 'save' set in their field_def
1789         Only the 'save' fields should be saved as some vardef entries today are not for display only purposes and break the application if saved
1790                 If the vardef has entries for field <a> of type relate, where a->id_name = <b> and field <b> of type link
1791         then we receive a value for b from the MVC in the _REQUEST, and it should be set in the bean as $this->$b
1792 */
1793
1794             foreach ( $this->field_defs as $def )
1795             {
1796            if ($def [ 'type' ] == 'relate' && isset ( $def [ 'id_name'] ) && isset ( $def [ 'link'] ) && isset ( $def[ 'save' ]) )
1797                {
1798                    if (  in_array( $def['id_name'], $exclude) || in_array( $def['id_name'], $this->relationship_fields ) )
1799                                continue ; // continue to honor the exclude array and exclude any relationships that will be handled by the relationship_fields mechanism
1800
1801                    if (isset( $this->field_defs[ $def [ 'link' ] ] ))
1802                    {
1803
1804                     $linkfield = $this->field_defs[$def [ 'link' ]] ;
1805
1806                     if ($this->load_relationship ( $def [ 'link' ])){
1807                                         if (!empty($this->rel_fields_before_value[$def [ 'id_name' ]]))
1808                             {
1809                                                         //if before value is not empty then attempt to delete relationship
1810                                 $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' ]]}");
1811                                 $this->$def ['link' ]->delete($this->id, $this->rel_fields_before_value[$def [ 'id_name' ]] );
1812                             }
1813                             if (!empty($this->$def['id_name']) && is_string($this->$def['id_name']))
1814                             {
1815                                                         $GLOBALS['log']->debug("save_relationship_changes(): From field_defs - attempting to add a relationship record - {$def [ 'link' ]} = {$this->$def [ 'id_name' ]}" );
1816                                 $this->$def ['link' ]->add($this->$def['id_name']);
1817                             }
1818                     } else {
1819                         $GLOBALS['log']->fatal("Failed to load relationship {$def [ 'link' ]} while saving {$this->module_dir}");
1820                     }
1821                     }
1822                }
1823             }
1824
1825                 // Finally, we update a field listed in the _REQUEST['*/relate_id']/_REQUEST['relate_to'] mechanism (if it hasn't already been updated above)
1826                 if(!empty($new_rel_id)){
1827
1828                         if($this->load_relationship($new_rel_link)){
1829                                 $this->$new_rel_link->add($new_rel_id);
1830
1831                         }else{
1832                                 $lower_link = strtolower($new_rel_link);
1833                                 if($this->load_relationship($lower_link)){
1834                                         $this->$lower_link->add($new_rel_id);
1835
1836                                 }else{
1837                                         require_once('data/Link.php');
1838                                         $rel = Relationship::retrieve_by_modules($new_rel_link, $this->module_dir, $GLOBALS['db'], 'many-to-many');
1839
1840                                         if(!empty($rel)){
1841                                                 foreach($this->field_defs as $field=>$def){
1842                                                         if($def['type'] == 'link' && !empty($def['relationship']) && $def['relationship'] == $rel){
1843                                                                 $this->load_relationship($field);
1844                                                                 $this->$field->add($new_rel_id);
1845                                                                 return;
1846
1847                                                         }
1848
1849                                                 }
1850                         //ok so we didn't find it in the field defs let's save it anyway if we have the relationshp
1851
1852                                                 $this->$rel=new Link($rel, $this, array());
1853                                                 $this->$rel->add($new_rel_id);
1854                                         }
1855                                 }
1856
1857                         }
1858
1859                 }
1860
1861         }
1862
1863         /**
1864         * This function retrieves a record of the appropriate type from the DB.
1865         * It fills in all of the fields from the DB into the object it was called on.
1866     *
1867         * @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.
1868         * @return this - The object that it was called apon or null if exactly 1 record was not found.
1869     *
1870         */
1871
1872         function check_date_relationships_load()
1873         {
1874                 global $disable_date_format;
1875                 global $timedate;
1876                 if (empty($timedate))
1877                         $timedate=new TimeDate();
1878
1879                 if(empty($this->field_defs))
1880                 {
1881                         return;
1882                 }
1883                 foreach($this->field_defs as $fieldDef)
1884                 {
1885                         $field = $fieldDef['name'];
1886                         if(!isset($this->processed_dates_times[$field]))
1887                         {
1888                                 $this->processed_dates_times[$field] = '1';
1889                                 if(empty($this->$field)) continue;
1890                                 if($field == 'date_modified' || $field == 'date_entered')
1891                                 {
1892                                         $this->$field = from_db_convert($this->$field, 'datetime');
1893                                         if(empty($disable_date_format)) {
1894                                                 $this->$field = $timedate->to_display_date_time($this->$field);
1895                                         }
1896                                 }
1897                                 elseif(isset($this->field_name_map[$field]['type']))
1898                                 {
1899                                         $type = $this->field_name_map[$field]['type'];
1900
1901                                         if($type == 'relate'  && isset($this->field_name_map[$field]['custom_module']))
1902                                         {
1903                                                 $type = $this->field_name_map[$field]['type'];
1904                                         }
1905
1906                                         if($type == 'date')
1907                                         {
1908                                                 $this->$field = from_db_convert($this->$field, 'date');
1909
1910                                                 if($this->$field == '0000-00-00')
1911                                                 {
1912                                                         $this->$field = '';
1913                                                 } elseif(!empty($this->field_name_map[$field]['rel_field']))
1914                                                 {
1915                                                         $rel_field = $this->field_name_map[$field]['rel_field'];
1916
1917                                                         if(!empty($this->$rel_field))
1918                                                         {
1919                                                                 $this->$rel_field=from_db_convert($this->$rel_field, 'time');
1920                                                                 if(empty($disable_date_format)) {
1921                                                                         $mergetime = $timedate->merge_date_time($this->$field,$this->$rel_field);
1922                                                                         $this->$field = $timedate->to_display_date($mergetime);
1923                                                                         $this->$rel_field = $timedate->to_display_time($mergetime);
1924                                                                 }
1925                                                         }
1926                                                 }
1927                                                 else
1928                                                 {
1929                                                         if(empty($disable_date_format)) {
1930                                                                 $this->$field = $timedate->to_display_date($this->$field, false);
1931                                                         }
1932                                                 }
1933                                         } elseif($type == 'datetime' || $type == 'datetimecombo')
1934                                         {
1935                                                 if($this->$field == '0000-00-00 00:00:00')
1936                                                 {
1937                                                         $this->$field = '';
1938                                                 }
1939                                                 else
1940                                                 {
1941                                                         $this->$field = from_db_convert($this->$field, 'datetime');
1942                                                         if(empty($disable_date_format)) {
1943                                                                 $this->$field = $timedate->to_display_date_time($this->$field, true, true);
1944                                                         }
1945                                                 }
1946                                         } elseif($type == 'time')
1947                                         {
1948                                                 if($this->$field == '00:00:00')
1949                                                 {
1950                                                         $this->$field = '';
1951                                                 } else
1952                                                 {
1953                                                         //$this->$field = from_db_convert($this->$field, 'time');
1954                                                         if(empty($this->field_name_map[$field]['rel_field']) && empty($disable_date_format))
1955                                                         {
1956                                                                 $this->$field = $timedate->to_display_time($this->$field,true, false);
1957                                                         }
1958                                                 }
1959                                         } elseif($type == 'encrypt' && empty($disable_date_format)){
1960                                                 $this->$field = $this->decrypt_after_retrieve($this->$field);
1961                                         }
1962                                 }
1963                         }
1964                 }
1965         }
1966
1967     /**
1968      * This function processes the fields before save.
1969      * Interal function, do not override.
1970      */
1971         function preprocess_fields_on_save()
1972         {
1973         $GLOBALS['log']->deprecated('SugarBean.php: preprocess_fields_on_save() is deprecated');
1974         }
1975
1976         /**
1977          * Removes formatting from values posted from the user interface.
1978      * It only unformats numbers.  Function relies on user/system prefernce for format strings.
1979      *
1980      * Internal Function, do not override.
1981          */
1982         function unformat_all_fields()
1983         {
1984         $GLOBALS['log']->deprecated('SugarBean.php: unformat_all_fields() is deprecated');
1985         }
1986
1987         /**
1988          * This functions adds formatting to all number fields before presenting them to user interface.
1989      *
1990      * Internal function, do not override.
1991          */
1992         function format_all_fields()
1993         {
1994         $GLOBALS['log']->deprecated('SugarBean.php: format_all_fields() is deprecated');
1995         }
1996
1997     function format_field($fieldDef)
1998                 {
1999         $GLOBALS['log']->deprecated('SugarBean.php: format_field() is deprecated');
2000                 }
2001
2002     /**
2003      * Function corrects any bad formatting done by 3rd party/custom code
2004      *
2005      * 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
2006      */
2007     function fixUpFormatting()
2008     {
2009         global $timedate;
2010                 static $boolean_false_values = array('off', 'false', '0', 'no');
2011
2012
2013         foreach($this->field_defs as $field=>$def)
2014                         {
2015             if ( !isset($this->$field) ) {
2016                 continue;
2017                                 }
2018             if ( (isset($def['source'])&&$def['source']=='non-db') || $field == 'deleted' ) {
2019                 continue;
2020             }
2021             if ( isset($this->fetched_row[$field]) && $this->$field == $this->fetched_row[$field] ) {
2022                 // Don't hand out warnings because the field was untouched between retrieval and saving, most database drivers hand pretty much everything back as strings.
2023                 continue;
2024             }
2025             $reformatted = false;
2026             switch($def['type']) {
2027                 case 'datetime':
2028                 case 'datetimecombo':
2029                     if(empty($this->$field)) break;
2030                     if ($this->$field == 'NULL') {
2031                         $this->$field = '';
2032                         break;
2033                     }
2034                     if ( ! preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$/',$this->$field) ) {
2035                         // This appears to be formatted in user date/time
2036                         $this->$field = $timedate->to_db($this->$field);
2037                         $reformatted = true;
2038                     }
2039                     break;
2040                 case 'date':
2041                     if(empty($this->$field)) break;
2042                     if ($this->$field == 'NULL') {
2043                         $this->$field = '';
2044                         break;
2045                     }
2046                     if ( ! preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/',$this->$field) ) {
2047                         // This date appears to be formatted in the user's format
2048                         $this->$field = $timedate->to_db_date($this->$field, false);
2049                         $reformatted = true;
2050                     }
2051                     break;
2052                 case 'time':
2053                     if(empty($this->$field)) break;
2054                     if ($this->$field == 'NULL') {
2055                         $this->$field = '';
2056                         break;
2057                     }
2058                     if ( preg_match('/(am|pm)/i',$this->$field) ) {
2059                         // This time appears to be formatted in the user's format
2060                         $this->$field = $timedate->to_db_time($timedate->to_display_date(gmdate('Y-m-d')).' '.$this->$field);
2061                         $reformatted = true;
2062                     }
2063                     break;
2064                 case 'double':
2065                 case 'decimal':
2066                 case 'currency':
2067                 case 'float':
2068                         if ( $this->$field === '' || $this->$field == NULL || $this->$field == 'NULL') {
2069                         continue;
2070                     }
2071                     if ( is_string($this->$field) ) {
2072                         $this->$field = (float)unformat_number($this->$field);
2073                         $reformatted = true;
2074                     }
2075                     break;
2076                case 'uint':
2077                case 'ulong':
2078                case 'long':
2079                case 'short':
2080                case 'tinyint':
2081                case 'int':
2082                     if ( $this->$field === '' || $this->$field == NULL || $this->$field == 'NULL') {
2083                         continue;
2084                     }
2085                     if ( is_string($this->$field) ) {
2086                         $this->$field = (int)unformat_number($this->$field);
2087                         $reformatted = true;
2088                     }
2089                    break;
2090                case 'bool':
2091                    if (empty($this->$field)) {
2092                        $this->$field = false;
2093                    } else if(true === $this->$field || 1 == $this->$field) {
2094                        $this->$field = true;
2095                    } else if(in_array(strval($this->$field), $boolean_false_values)) {
2096                        $this->$field = false;
2097                        $reformatted = true;
2098                    } else {
2099                        $this->$field = true;
2100                        $reformatted = true;
2101                    }
2102                    break;
2103                case 'encrypt':
2104                     $this->$field = $this->encrpyt_before_save($this->$field);
2105                     break;
2106             }
2107             if ( $reformatted ) {
2108                 $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');
2109                         }
2110                 }
2111
2112     }
2113
2114         /**
2115      * Function fetches a single row of data given the primary key value.
2116      *
2117      * The fetched data is then set into the bean. The function also processes the fetched data by formattig
2118      * date/time and numeric values.
2119      *
2120      * @param string $id Optional, default -1, is set to -1 id value from the bean is used, else, passed value is used
2121      * @param boolean $encode Optional, default true, encodes the values fetched from the database.
2122      * @param boolean $deleted Optional, default true, if set to false deleted filter will not be added.
2123      *
2124      * Internal function, do not override.
2125          */
2126         function retrieve($id = -1, $encode=true,$deleted=true)
2127         {
2128
2129                 $custom_logic_arguments['id'] = $id;
2130                 $this->call_custom_logic('before_retrieve', $custom_logic_arguments);
2131
2132                 if ($id == -1)
2133                 {
2134                         $id = $this->id;
2135                 }
2136                 if(isset($this->custom_fields))
2137                 {
2138                         $custom_join = $this->custom_fields->getJOIN();
2139                 }
2140                 else
2141                         $custom_join = false;
2142
2143                 if($custom_join)
2144                 {
2145                         $query = "SELECT $this->table_name.*". $custom_join['select']. " FROM $this->table_name ";
2146                 }
2147                 else
2148                 {
2149                         $query = "SELECT $this->table_name.* FROM $this->table_name ";
2150                 }
2151
2152                 if($custom_join)
2153                 {
2154                         $query .= ' ' . $custom_join['join'];
2155                 }
2156                 $query .= " WHERE $this->table_name.id = '$id' ";
2157                 if ($deleted) $query .= " AND $this->table_name.deleted=0";
2158                 $GLOBALS['log']->debug("Retrieve $this->object_name : ".$query);
2159                 //requireSingleResult has beeen deprecated.
2160                 //$result = $this->db->requireSingleResult($query, true, "Retrieving record by id $this->table_name:$id found ");
2161                 $result = $this->db->limitQuery($query,0,1,true, "Retrieving record by id $this->table_name:$id found ");
2162                 if(empty($result))
2163                 {
2164                         return null;
2165                 }
2166
2167                 $row = $this->db->fetchByAssoc($result, -1, $encode);
2168                 if(empty($row))
2169                 {
2170                         return null;
2171                 }
2172
2173                 //make copy of the fetched row for construction of audit record and for business logic/workflow
2174                 $this->fetched_row=$row;
2175                 $this->populateFromRow($row);
2176
2177                 global $module, $action;
2178                 //Just to get optimistic locking working for this release
2179                 if($this->optimistic_lock && $module == $this->module_dir && $action =='EditView' )
2180                 {
2181                         $_SESSION['o_lock_id']= $id;
2182                         $_SESSION['o_lock_dm']= $this->date_modified;
2183                         $_SESSION['o_lock_on'] = $this->object_name;
2184                 }
2185                 $this->processed_dates_times = array();
2186                 $this->check_date_relationships_load();
2187
2188                 if($custom_join)
2189                 {
2190                         $this->custom_fields->fill_relationships();
2191                 }
2192
2193                 $this->fill_in_additional_detail_fields();
2194                 $this->fill_in_relationship_fields();
2195                 //make a copy of fields in the relatiosnhip_fields array. these field values will be used to
2196                 //clear relatioship.
2197                 foreach ( $this->field_defs as $key => $def )
2198             {
2199                 if ($def [ 'type' ] == 'relate' && isset ( $def [ 'id_name'] ) && isset ( $def [ 'link'] ) && isset ( $def[ 'save' ])) {
2200                         if (isset($this->$key)) {
2201                                         $this->rel_fields_before_value[$key]=$this->$key;
2202                         if (isset($this->$def [ 'id_name']))
2203                                 $this->rel_fields_before_value[$def [ 'id_name']]=$this->$def [ 'id_name'];
2204                 }
2205                                 else
2206                                         $this->rel_fields_before_value[$key]=null;
2207            }
2208             }
2209             if (isset($this->relationship_fields) && is_array($this->relationship_fields))
2210                 {
2211                         foreach ($this->relationship_fields as $rel_id=>$rel_name)
2212                         {
2213                                 if (isset($this->$rel_id))
2214                                         $this->rel_fields_before_value[$rel_id]=$this->$rel_id;
2215                                 else
2216                                         $this->rel_fields_before_value[$rel_id]=null;
2217                         }
2218                 }
2219
2220                 // call the custom business logic
2221                 $custom_logic_arguments['id'] = $id;
2222                 $custom_logic_arguments['encode'] = $encode;
2223                 $this->call_custom_logic("after_retrieve", $custom_logic_arguments);
2224                 unset($custom_logic_arguments);
2225                 return $this;
2226         }
2227
2228     /**
2229      * Sets value from fetched row into the bean.
2230      *
2231      * @param array $row Fetched row
2232      * @todo loop through vardefs instead
2233      * @internal runs into an issue when populating from field_defs for users - corrupts user prefs
2234      *
2235      * Internal function, do not override.
2236      */
2237     function populateFromRow($row)
2238     {
2239         $nullvalue='';
2240         foreach($this->field_defs as $field=>$field_value)
2241         {
2242                 if($field == 'user_preferences' && $this->module_dir == 'Users')
2243                         continue;
2244                 $rfield = $field; // fetch returns it in lowercase only
2245                 if(isset($row[$rfield]))
2246                 {
2247                         $this->$field = $row[$rfield];
2248                         $owner = $rfield . '_owner';
2249                         if(!empty($row[$owner])){
2250                                 $this->$owner = $row[$owner];
2251                         }
2252                 }
2253                 else
2254                 {
2255                         $this->$field = $nullvalue;
2256                 }
2257         }
2258     }
2259
2260
2261
2262         /**
2263         * Add any required joins to the list count query.  The joins are required if there
2264         * is a field in the $where clause that needs to be joined.
2265     *
2266     * @param string $query
2267     * @param string $where
2268     *
2269     * Internal Function, do Not override.
2270         */
2271         function add_list_count_joins(&$query, $where)
2272         {
2273                 $custom_join = $this->custom_fields->getJOIN();
2274                 if($custom_join)
2275                 {
2276                         $query .= $custom_join['join'];
2277                 }
2278
2279         }
2280
2281         /**
2282          * Changes the select expression of the given query to be 'count(*)' so you
2283          * can get the number of items the query will return.  This is used to
2284          * populate the upper limit on ListViews.
2285      *
2286      * @param string $query Select query string
2287      * @return string count query
2288      *
2289      * Internal function, do not override.
2290          */
2291     function create_list_count_query($query)
2292     {
2293         // remove the 'order by' clause which is expected to be at the end of the query
2294         $pattern = '/\sORDER BY.*/is';  // ignores the case
2295         $replacement = '';
2296         $query = preg_replace($pattern, $replacement, $query);
2297         //handle distinct clause
2298         $star = '*';
2299         if(substr_count(strtolower($query), 'distinct')){
2300                 if (!empty($this->seed) && !empty($this->seed->table_name ))
2301                         $star = 'DISTINCT ' . $this->seed->table_name . '.id';
2302                 else
2303                         $star = 'DISTINCT ' . $this->table_name . '.id';
2304
2305         }
2306
2307         // change the select expression to 'count(*)'
2308         $pattern = '/SELECT(.*?)(\s){1}FROM(\s){1}/is';  // ignores the case
2309         $replacement = 'SELECT count(' . $star . ') c FROM ';
2310
2311         //if the passed query has union clause then replace all instances of the pattern.
2312         //this is very rare. I have seen this happening only from projects module.
2313         //in addition to this added a condition that has  union clause and uses
2314         //sub-selects.
2315         if (strstr($query," UNION ALL ") !== false) {
2316
2317                 //seperate out all the queries.
2318                 $union_qs=explode(" UNION ALL ", $query);
2319                 foreach ($union_qs as $key=>$union_query) {
2320                         $star = '*';
2321                                 preg_match($pattern, $union_query, $matches);
2322                                 if (!empty($matches)) {
2323                                         if (stristr($matches[0], "distinct")) {
2324                                         if (!empty($this->seed) && !empty($this->seed->table_name ))
2325                                                 $star = 'DISTINCT ' . $this->seed->table_name . '.id';
2326                                         else
2327                                                 $star = 'DISTINCT ' . $this->table_name . '.id';
2328                                         }
2329                                 } // if
2330                         $replacement = 'SELECT count(' . $star . ') c FROM ';
2331                         $union_qs[$key] = preg_replace($pattern, $replacement, $union_query,1);
2332                 }
2333                 $modified_select_query=implode(" UNION ALL ",$union_qs);
2334         } else {
2335                 $modified_select_query = preg_replace($pattern, $replacement, $query,1);
2336         }
2337
2338
2339                 return $modified_select_query;
2340     }
2341
2342         /**
2343         * This function returns a paged list of the current object type.  It is intended to allow for
2344         * hopping back and forth through pages of data.  It only retrieves what is on the current page.
2345         *
2346     * @internal This method must be called on a new instance.  It trashes the values of all the fields in the current one.
2347         * @param string $order_by
2348     * @param string $where Additional where clause
2349     * @param int $row_offset Optaional,default 0, starting row number
2350     * @param init $limit Optional, default -1
2351     * @param int $max Optional, default -1
2352     * @param boolean $show_deleted Optioanl, default 0, if set to 1 system will show deleted records.
2353     * @return array Fetched data.
2354     *
2355     * Internal function, do not override.
2356     *
2357         */
2358         function get_list($order_by = "", $where = "", $row_offset = 0, $limit=-1, $max=-1, $show_deleted = 0, $singleSelect=false)
2359         {
2360                 $GLOBALS['log']->debug("get_list:  order_by = '$order_by' and where = '$where' and limit = '$limit'");
2361                 if(isset($_SESSION['show_deleted']))
2362                 {
2363                         $show_deleted = 1;
2364                 }
2365                 $order_by=$this->process_order_by($order_by, null);
2366
2367                 if($this->bean_implements('ACL') && ACLController::requireOwner($this->module_dir, 'list') )
2368                 {
2369                         global $current_user;
2370                         $owner_where = $this->getOwnerWhere($current_user->id);
2371
2372                         //rrs - because $this->getOwnerWhere() can return '' we need to be sure to check for it and
2373                         //handle it properly else you could get into a situation where you are create a where stmt like
2374                         //WHERE .. AND ''
2375                         if(!empty($owner_where)){
2376                                 if(empty($where)){
2377                                         $where = $owner_where;
2378                                 }else{
2379                                         $where .= ' AND '.  $owner_where;
2380                                 }
2381                         }
2382                 }
2383                 $query = $this->create_new_list_query($order_by, $where,array(),array(), $show_deleted,'',false,null,$singleSelect);
2384                 return $this->process_list_query($query, $row_offset, $limit, $max, $where);
2385         }
2386
2387         /**
2388         * Prefixes column names with this bean's table name.
2389         * This call can be ignored for  mysql since it does a better job than Oracle in resolving ambiguity.
2390         *
2391     * @param string $order_by  Order by clause to be processed
2392     * @param string $submodule name of the module this order by clause is for
2393     * @return string Processed order by clause
2394     *
2395     * Internal function, do not override.
2396         */
2397         function process_order_by ($order_by, $submodule)
2398         {
2399                 if (empty($order_by))
2400                         return $order_by;
2401                 $bean_queried = "";
2402                 //submodule is empty,this is for list object in focus
2403                 if (empty($submodule))
2404                 {
2405                         $bean_queried = &$this;
2406                 }
2407                 else
2408                 {
2409                         //submodule is set, so this is for subpanel, use submodule
2410                         $bean_queried = $submodule;
2411                 }
2412                 $elements = explode(',',$order_by);
2413                 foreach ($elements as $key=>$value)
2414                 {
2415                         if (strchr($value,'.') === false)
2416                         {
2417                                 //value might have ascending and descending decorations
2418                                 $list_column = explode(' ',trim($value));
2419                                 if (isset($list_column[0]))
2420                                 {
2421                                         $list_column_name=trim($list_column[0]);
2422                                         if (isset($bean_queried->field_defs[$list_column_name]))
2423                                         {
2424                                                 $source=isset($bean_queried->field_defs[$list_column_name]['source']) ? $bean_queried->field_defs[$list_column_name]['source']:'db';
2425                                                 if (empty($bean_queried->field_defs[$list_column_name]['table']) && $source=='db')
2426                                                 {
2427                                                         $list_column[0] = $bean_queried->table_name .".".$list_column[0] ;
2428                                                 }
2429                                                 if (empty($bean_queried->field_defs[$list_column_name]['table']) && $source=='custom_fields')
2430                                                 {
2431                                                     $list_column[0] = $bean_queried->table_name ."_cstm.".$list_column[0] ;
2432                                                 }
2433                                                 $value = implode($list_column,' ');
2434                                                 // Bug 38803 - Use CONVERT() function when doing an order by on ntext, text, and image fields
2435                                                 if ( $this->db->dbType == 'mssql'
2436                                                      && $source != 'non-db'
2437                             && in_array(
2438                                 $this->db->getHelper()->getColumnType($this->db->getHelper()->getFieldType($bean_queried->field_defs[$list_column_name])),
2439                                 array('ntext','text','image')
2440                                 )
2441                             ) {
2442                         $value = "CONVERT(varchar(500),{$list_column[0]}) {$list_column[1]}";
2443                         }
2444                                                 // Bug 29011 - Use TO_CHAR() function when doing an order by on a clob field
2445                                                 if ( $this->db->dbType == 'oci8'
2446                                                      && $source != 'non-db'
2447                             && in_array(
2448                                 $this->db->getHelper()->getColumnType($this->db->getHelper()->getFieldType($bean_queried->field_defs[$list_column_name])),
2449                                 array('clob')
2450                                 )
2451                             ) {
2452                         $value = "TO_CHAR({$list_column[0]}) {$list_column[1]}";
2453                         }
2454                                         }
2455                                         else
2456                                         {
2457                                                 $GLOBALS['log']->debug("process_order_by: ($list_column[0]) does not have a vardef entry.");
2458                                         }
2459                                 }
2460                         }
2461                         $elements[$key]=$value;
2462                 }
2463                 return implode($elements,',');
2464
2465         }
2466
2467
2468         /**
2469         * Returns a detail object like retrieving of the current object type.
2470     *
2471     * It is intended for use in navigation buttons on the DetailView.  It will pass an offset and limit argument to the sql query.
2472         * @internal This method must be called on a new instance.  It overrides the values of all the fields in the current one.
2473     *
2474     * @param string $order_by
2475     * @param string $where Additional where clause
2476     * @param int $row_offset Optaional,default 0, starting row number
2477     * @param init $limit Optional, default -1
2478     * @param int $max Optional, default -1
2479     * @param boolean $show_deleted Optioanl, default 0, if set to 1 system will show deleted records.
2480     * @return array Fetched data.
2481     *
2482     * Internal function, do not override.
2483         */
2484         function get_detail($order_by = "", $where = "",  $offset = 0, $row_offset = 0, $limit=-1, $max=-1, $show_deleted = 0)
2485         {
2486                 $GLOBALS['log']->debug("get_detail:  order_by = '$order_by' and where = '$where' and limit = '$limit' and offset = '$offset'");
2487                 if(isset($_SESSION['show_deleted']))
2488                 {
2489                         $show_deleted = 1;
2490                 }
2491
2492                 if($this->bean_implements('ACL') && ACLController::requireOwner($this->module_dir, 'list') )
2493                 {
2494                         global $current_user;
2495                         $owner_where = $this->getOwnerWhere($current_user->id);
2496
2497                         if(empty($where))
2498                         {
2499                                 $where = $owner_where;
2500                         }
2501                         else
2502                         {
2503                                 $where .= ' AND '.  $owner_where;
2504                         }
2505                 }
2506                 $query = $this->create_new_list_query($order_by, $where,array(),array(), $show_deleted, $offset);
2507
2508                 //Add Limit and Offset to query
2509                 //$query .= " LIMIT 1 OFFSET $offset";
2510
2511                 return $this->process_detail_query($query, $row_offset, $limit, $max, $where, $offset);
2512         }
2513
2514     /**
2515     * Fetches data from all related tables.
2516     *
2517     * @param object $child_seed
2518     * @param string $related_field_name relation to fetch data for
2519     * @param string $order_by Optional, default empty
2520     * @param string $where Optional, additional where clause
2521     * @return array Fetched data.
2522     *
2523     * Internal function, do not override.
2524     */
2525     function get_related_list($child_seed,$related_field_name, $order_by = "", $where = "",
2526     $row_offset = 0, $limit=-1, $max=-1, $show_deleted = 0)
2527     {
2528         global $layout_edit_mode;
2529         if(isset($layout_edit_mode) && $layout_edit_mode)
2530         {
2531                 $response = array();
2532                 $child_seed->assign_display_fields($child_seed->module_dir);
2533                 $response['list'] = array($child_seed);
2534                 $response['row_count'] = 1;
2535                 $response['next_offset'] = 0;
2536                 $response['previous_offset'] = 0;
2537
2538                 return $response;
2539         }
2540         $GLOBALS['log']->debug("get_related_list:  order_by = '$order_by' and where = '$where' and limit = '$limit'");
2541         if(isset($_SESSION['show_deleted']))
2542         {
2543                 $show_deleted = 1;
2544         }
2545
2546         $this->load_relationship($related_field_name);
2547         $query_array = $this->$related_field_name->getQuery(true);
2548         $entire_where = $query_array['where'];
2549         if(!empty($where))
2550         {
2551                 if(empty($entire_where))
2552                 {
2553                         $entire_where = ' WHERE ' . $where;
2554                 }
2555                 else
2556                 {
2557                         $entire_where .= ' AND ' . $where;
2558                 }
2559         }
2560
2561         $query = 'SELECT '.$child_seed->table_name.'.* ' . $query_array['from'] . ' ' . $entire_where;
2562         if(!empty($order_by))
2563         {
2564                 $query .= " ORDER BY " . $order_by;
2565         }
2566
2567         return $child_seed->process_list_query($query, $row_offset, $limit, $max, $where);
2568     }
2569
2570
2571         protected static function build_sub_queries_for_union($subpanel_list, $subpanel_def, $parentbean, $order_by)
2572     {
2573         global $layout_edit_mode, $beanFiles, $beanList;
2574         $subqueries = array();
2575         foreach($subpanel_list as $this_subpanel)
2576                 {
2577                         if(!$this_subpanel->isDatasourceFunction() || ($this_subpanel->isDatasourceFunction()
2578                                 && isset($this_subpanel->_instance_properties['generate_select'])
2579                                 && $this_subpanel->_instance_properties['generate_select']==true))
2580                         {
2581                                 //the custom query function must return an array with
2582                                 if ($this_subpanel->isDatasourceFunction()) {
2583                                         $shortcut_function_name = $this_subpanel->get_data_source_name();
2584                                         $parameters=$this_subpanel->get_function_parameters();
2585                                         if (!empty($parameters))
2586                                         {
2587                                             //if the import file function is set, then import the file to call the custom function from
2588                                                 if (is_array($parameters)  && isset($parameters['import_function_file'])){
2589                                                     //this call may happen multiple times, so only require if function does not exist
2590                                                         if(!function_exists($shortcut_function_name)){
2591                                                                 require_once($parameters['import_function_file']);
2592                                                         }
2593                                                         //call function from required file
2594                                                         $query_array = $shortcut_function_name($parameters);
2595                                                 }else{
2596                                                         //call function from parent bean
2597                                                         $query_array = $parentbean->$shortcut_function_name($parameters);
2598                                                 }
2599                                         }
2600                                         else
2601                                         {
2602                                                 $query_array = $parentbean->$shortcut_function_name();
2603                                         }
2604                                 }  else {
2605                                         $related_field_name = $this_subpanel->get_data_source_name();
2606                                         if (!$parentbean->load_relationship($related_field_name)){
2607                                                 unset ($parentbean->$related_field_name);
2608                                                 continue;
2609                                         }
2610                                         $query_array = $parentbean->$related_field_name->getQuery(true,array(),0,'',true, null, null, true);
2611                                 }
2612                                 $table_where = $this_subpanel->get_where();
2613                                 $where_definition = $query_array['where'];
2614
2615                                 if(!empty($table_where))
2616                                 {
2617                                         if(empty($where_definition))
2618                                         {
2619                                                 $where_definition = $table_where;
2620                                         }
2621                                         else
2622                                         {
2623                                                 $where_definition .= ' AND ' . $table_where;
2624                                         }
2625                                 }
2626
2627                                 $submodulename = $this_subpanel->_instance_properties['module'];
2628                                 $submoduleclass = $beanList[$submodulename];
2629                                 //require_once($beanFiles[$submoduleclass]);
2630                                 $submodule = new $submoduleclass();
2631                                 $subwhere = $where_definition;
2632
2633
2634
2635                                 $subwhere = str_replace('WHERE', '', $subwhere);
2636                                 $list_fields = $this_subpanel->get_list_fields();
2637                                 foreach($list_fields as $list_key=>$list_field)
2638                                 {
2639                                         if(isset($list_field['usage']) && $list_field['usage'] == 'display_only')
2640                                         {
2641                                                 unset($list_fields[$list_key]);
2642                                         }
2643                                 }
2644                                 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'))
2645                                 {
2646                                         $order_by = $submodule->table_name .'.'. $order_by;
2647                                 }
2648                                 $table_name = $this_subpanel->table_name;
2649                                 $panel_name=$this_subpanel->name;
2650                                 $params = array();
2651                                 $params['distinct'] = $this_subpanel->distinct_query();
2652
2653                                 $params['joined_tables'] = $query_array['join_tables'];
2654                                 $params['include_custom_fields'] = !$subpanel_def->isCollection();
2655                                 $params['collection_list'] = $subpanel_def->get_inst_prop_value('collection_list');
2656
2657                                 $subquery = $submodule->create_new_list_query('',$subwhere ,$list_fields,$params, 0,'', true,$parentbean);
2658
2659                                 $subquery['select'] = $subquery['select']." , '$panel_name' panel_name ";
2660                                 $subquery['from'] = $subquery['from'].$query_array['join'];
2661                                 $subquery['query_array'] = $query_array;
2662                                 $subquery['params'] = $params;
2663
2664                                 $subqueries[] = $subquery;
2665                         }
2666                 }
2667                 return $subqueries;
2668     }
2669
2670         /**
2671          * Constructs a query to fetch data for supanels and list views
2672      *
2673      * It constructs union queries for activities subpanel.
2674      *
2675      * @param Object $parentbean constructing queries for link attributes in this bean
2676      * @param string $order_by Optional, order by clause
2677      * @param string $sort_order Optional, sort order
2678      * @param string $where Optional, additional where clause
2679      *
2680      * Internal Function, do not overide.
2681          */
2682         function get_union_related_list($parentbean, $order_by = "", $sort_order='', $where = "",
2683         $row_offset = 0, $limit=-1, $max=-1, $show_deleted = 0, $subpanel_def)
2684         {
2685                 $secondary_queries = array();
2686                 global $layout_edit_mode, $beanFiles, $beanList;
2687
2688                 if(isset($_SESSION['show_deleted']))
2689                 {
2690                         $show_deleted = 1;
2691                 }
2692                 $final_query = '';
2693                 $final_query_rows = '';
2694                 $subpanel_list=array();
2695                 if ($subpanel_def->isCollection())
2696                 {
2697                         $subpanel_def->load_sub_subpanels();
2698                         $subpanel_list=$subpanel_def->sub_subpanels;
2699                 }
2700                 else
2701                 {
2702                         $subpanel_list[]=$subpanel_def;
2703                 }
2704                 
2705                 $first = true;
2706
2707                 //Breaking the building process into two loops. The first loop gets a list of all the sub-queries.
2708                 //The second loop merges the queries and forces them to select the same number of columns
2709                 //All columns in a sub-subpanel group must have the same aliases
2710                 //If the subpanel is a datasource function, it can't be a collection so we just poll that function for the and return that
2711                 foreach($subpanel_list as $this_subpanel)
2712                 {
2713                         if($this_subpanel->isDatasourceFunction() && empty($this_subpanel->_instance_properties['generate_select']))
2714                         {
2715                                 $shortcut_function_name = $this_subpanel->get_data_source_name();
2716                                 $parameters=$this_subpanel->get_function_parameters();
2717                                 if (!empty($parameters))
2718                                 {
2719                                         //if the import file function is set, then import the file to call the custom function from
2720                                         if (is_array($parameters)  && isset($parameters['import_function_file'])){
2721                                                 //this call may happen multiple times, so only require if function does not exist
2722                                                 if(!function_exists($shortcut_function_name)){
2723                                                         require_once($parameters['import_function_file']);
2724                                                 }
2725                                                 //call function from required file
2726                                                 $tmp_final_query =  $shortcut_function_name($parameters);
2727                                         }else{
2728                                                 //call function from parent bean
2729                                                 $tmp_final_query =  $parentbean->$shortcut_function_name($parameters);
2730                                         }
2731                                 }
2732                                 else
2733                                 {
2734                                         $tmp_final_query = $parentbean->$shortcut_function_name();
2735                                 }
2736                                 if(!$first)
2737                                 {
2738                                         $final_query_rows .= ' UNION ALL ( '.$parentbean->create_list_count_query($tmp_final_query, $parameters) . ' )';
2739                                         $final_query .= ' UNION ALL ( '.$tmp_final_query . ' )';
2740                                 } else {
2741                                         $final_query_rows = '(' . $parentbean->create_list_count_query($tmp_final_query, $parameters) . ')';
2742                                         $final_query = '(' . $tmp_final_query . ')';
2743                                         $first = false;
2744                                 }
2745                         }
2746                 }
2747                 //If final_query is still empty, its time to build the sub-queries
2748                 if (empty($final_query))
2749                 {
2750                         $subqueries = SugarBean::build_sub_queries_for_union($subpanel_list, $subpanel_def, $parentbean, $order_by);
2751                         $all_fields = array();
2752                         foreach($subqueries as $i => $subquery)
2753                         {
2754                                 $query_fields = $GLOBALS['db']->helper->getSelectFieldsFromQuery($subquery['select']);
2755                                 foreach($query_fields as $field => $select)
2756                                 {
2757                                         if (!in_array($field, $all_fields))
2758                                                 $all_fields[] = $field;
2759                                 }
2760                                 $subqueries[$i]['query_fields'] = $query_fields;
2761                         }
2762                         $first = true;
2763                         //Now ensure the queries have the same set of fields in the same order.
2764                         foreach($subqueries as $subquery)
2765                         {
2766                                 $subquery['select'] = "SELECT";
2767                                 foreach($all_fields as $field)
2768                                 {
2769                                         if (!isset($subquery['query_fields'][$field]))
2770                                         {
2771                                                 $subquery['select'] .= " ' ' $field,";
2772                                         }
2773                                         else
2774                                         {
2775                                                 $subquery['select'] .= " {$subquery['query_fields'][$field]},";
2776                                         }
2777                                 }
2778                                 $subquery['select'] = substr($subquery['select'], 0 , strlen($subquery['select']) - 1);
2779                                 //Put the query into the final_query
2780                                 $query =  $subquery['select'] . " " . $subquery['from'] . " " . $subquery['where'];
2781                                 if(!$first)
2782                                 {
2783                                         $query = ' UNION ALL ( '.$query . ' )';
2784                                         $final_query_rows .= " UNION ALL ";
2785                                 } else {
2786                                         $query = '(' . $query . ')';
2787                                         $first = false;
2788                                 }
2789                                 $query_array = $subquery['query_array'];
2790                                 $select_position=strpos($query_array['select'],"SELECT");
2791                                 $distinct_position=strpos($query_array['select'],"DISTINCT");
2792                                 if ($select_position !== false && $distinct_position!= false)
2793                                 {
2794                                         $query_rows = "( ".substr_replace($query_array['select'],"SELECT count(",$select_position,6). ")" .  $subquery['from_min'].$query_array['join']. $subquery['where'].' )';
2795                                 }
2796                                 else
2797                                 {
2798                                         //resort to default behavior.
2799                                         $query_rows = "( SELECT count(*)".  $subquery['from_min'].$query_array['join']. $subquery['where'].' )';
2800
2801                                 }
2802                                 if(!empty($subquery['secondary_select']))
2803                                 {
2804
2805                                         $subquerystring= $subquery['secondary_select'] . $subquery['secondary_from'].$query_array['join']. $subquery['where'];
2806                                         if (!empty($subquery['secondary_where']))
2807                                         {
2808                                                 if (empty($subquery['where']))
2809                                                 {
2810                                                         $subquerystring.=" WHERE " .$subquery['secondary_where'];
2811                                                 }
2812                                                 else
2813                                                 {
2814                                                         $subquerystring.=" AND " .$subquery['secondary_where'];
2815                                                 }
2816                                         }
2817                                         $secondary_queries[]=$subquerystring;
2818                                 }
2819                                 $final_query .= $query;
2820                                 $final_query_rows .= $query_rows;
2821                         }
2822                 }
2823
2824                 if(!empty($order_by))
2825                 {
2826                         $submodule = false;
2827                         if(!$subpanel_def->isCollection())
2828                         {
2829                                 $submodulename = $subpanel_def->_instance_properties['module'];
2830                                 $submoduleclass = $beanList[$submodulename];
2831                                 $submodule = new $submoduleclass();
2832                         }
2833                         if(!empty($submodule) && !empty($submodule->table_name))
2834                         {
2835                                 $final_query .= " ORDER BY " .$parentbean->process_order_by($order_by, $submodule);
2836
2837                         }
2838                         else
2839                         {
2840                                 $final_query .= " ORDER BY ". $order_by . ' ';
2841                         }
2842                         if(!empty($sort_order))
2843                         {
2844                                 $final_query .= ' ' .$sort_order;
2845                         }
2846                 }
2847
2848
2849                 if(isset($layout_edit_mode) && $layout_edit_mode)
2850                 {
2851                         $response = array();
2852                         if(!empty($submodule))
2853                         {
2854                                 $submodule->assign_display_fields($submodule->module_dir);
2855                                 $response['list'] = array($submodule);
2856                         }
2857                         else
2858                 {
2859                                 $response['list'] = array();
2860                         }
2861                         $response['parent_data'] = array();
2862                         $response['row_count'] = 1;
2863                         $response['next_offset'] = 0;
2864                         $response['previous_offset'] = 0;
2865
2866                         return $response;
2867                 }
2868
2869                 return $parentbean->process_union_list_query($parentbean, $final_query, $row_offset, $limit, $max, '',$subpanel_def, $final_query_rows, $secondary_queries);
2870         }
2871
2872
2873         /**
2874         * Returns a full (ie non-paged) list of the current object type.
2875         *
2876         * @param string $order_by the order by SQL parameter. defaults to ""
2877         * @param string $where where clause. defaults to ""
2878         * @param boolean $check_dates. defaults to false
2879         * @param int $show_deleted show deleted records. defaults to 0
2880         */
2881         function get_full_list($order_by = "", $where = "", $check_dates=false, $show_deleted = 0)
2882         {
2883                 $GLOBALS['log']->debug("get_full_list:  order_by = '$order_by' and where = '$where'");
2884                 if(isset($_SESSION['show_deleted']))
2885                 {
2886                         $show_deleted = 1;
2887                 }
2888                 $query = $this->create_new_list_query($order_by, $where,array(),array(), $show_deleted);
2889                 return $this->process_full_list_query($query, $check_dates);
2890         }
2891
2892     /**
2893      * Return the list query used by the list views and export button. Next generation of create_new_list_query function.
2894      *
2895      * Override this function to return a custom query.
2896      *
2897      * @param string $order_by custom order by clause
2898      * @param string $where custom where clause
2899      * @param array $filter Optioanal
2900      * @param array $params Optional     *
2901      * @param int $show_deleted Optional, default 0, show deleted records is set to 1.
2902      * @param string $join_type
2903      * @param boolean $return_array Optional, default false, response as array
2904      * @param object $parentbean creating a subquery for this bean.
2905      * @param boolean $singleSelect Optional, default false.
2906      * @return String select query string, optionally an array value will be returned if $return_array= true.
2907      */
2908     function create_new_list_query($order_by, $where,$filter=array(),$params=array(), $show_deleted = 0,$join_type='', $return_array = false,$parentbean=null, $singleSelect = false)
2909     {
2910         global $beanFiles, $beanList;
2911         $selectedFields = array();
2912         $secondarySelectedFields = array();
2913         $ret_array = array();
2914         $distinct = '';
2915         if($this->bean_implements('ACL') && ACLController::requireOwner($this->module_dir, 'list') )
2916         {
2917                 global $current_user;
2918                 $owner_where = $this->getOwnerWhere($current_user->id);
2919                 if(empty($where))
2920                 {
2921                         $where = $owner_where;
2922                 }
2923                 else
2924                 {
2925                         $where .= ' AND '.  $owner_where;
2926                 }
2927         }
2928         if(!empty($params['distinct']))
2929         {
2930                 $distinct = ' DISTINCT ';
2931         }
2932         if(empty($filter))
2933         {
2934                 $ret_array['select'] = " SELECT $distinct $this->table_name.* ";
2935         }
2936         else
2937         {
2938                 $ret_array['select'] = " SELECT $distinct $this->table_name.id ";
2939         }
2940         $ret_array['from'] = " FROM $this->table_name ";
2941         $ret_array['from_min'] = $ret_array['from'];
2942         $ret_array['secondary_from'] = $ret_array['from'] ;
2943         $ret_array['where'] = '';
2944         $ret_array['order_by'] = '';
2945         //secondary selects are selects that need to be run after the primarty query to retrieve additional info on main
2946         if($singleSelect)
2947         {
2948                 $ret_array['secondary_select']=& $ret_array['select'];
2949                 $ret_array['secondary_from'] = & $ret_array['from'];
2950         }
2951         else
2952         {
2953                 $ret_array['secondary_select'] = '';
2954         }
2955         $custom_join = false;
2956         if((!isset($params['include_custom_fields']) || $params['include_custom_fields']) &&  isset($this->custom_fields))
2957         {
2958
2959                 $custom_join = $this->custom_fields->getJOIN( empty($filter)? true: $filter );
2960                 if($custom_join)
2961                 {
2962                         $ret_array['select'] .= ' ' .$custom_join['select'];
2963                 }
2964         }
2965         if($custom_join)
2966         {
2967                 $ret_array['from'] .= ' ' . $custom_join['join'];
2968         }
2969         $jtcount = 0;
2970         //LOOP AROUND FOR FIXIN VARDEF ISSUES
2971         require('include/VarDefHandler/listvardefoverride.php');
2972         $joined_tables = array();
2973         if(isset($params['joined_tables']))
2974         {
2975                 foreach($params['joined_tables'] as $table)
2976                 {
2977                         $joined_tables[$table] = 1;
2978                 }
2979         }
2980
2981         if(!empty($filter))
2982         {
2983                 $filterKeys = array_keys($filter);
2984                 if(is_numeric($filterKeys[0]))
2985                 {
2986                         $fields = array();
2987                         foreach($filter as $field)
2988                         {
2989                                 $field = strtolower($field);
2990                                 if(isset($this->field_defs[$field]))
2991                                 {
2992                                         $fields[$field]= $this->field_defs[$field];
2993                                 }
2994                                 else
2995                                 {
2996                                         $fields[$field] = array('force_exists'=>true);
2997                                 }
2998                         }
2999                 }else{
3000                         $fields =       $filter;
3001                 }
3002         }
3003         else
3004         {
3005                 $fields =       $this->field_defs;
3006         }
3007
3008         $used_join_key = array();
3009
3010         foreach($fields as $field=>$value)
3011         {
3012                 //alias is used to alias field names
3013                 $alias='';
3014                 if      (isset($value['alias']))
3015                 {
3016                         $alias =' as ' . $value['alias'] . ' ';
3017                 }
3018
3019                 if(empty($this->field_defs[$field]) || !empty($value['force_blank']) )
3020                 {
3021                         if(!empty($filter) && isset($filter[$field]['force_exists']) && $filter[$field]['force_exists'])
3022                         {
3023                                 if ( isset($filter[$field]['force_default']) )
3024                                         $ret_array['select'] .= ", {$filter[$field]['force_default']} $field ";
3025                                 else
3026                                 //spaces are a fix for length issue problem with unions.  The union only returns the maximum number of characters from the first select statemtn.
3027                                         $ret_array['select'] .= ", '                                                                                                                                                                                                                                                              ' $field ";
3028                         }
3029                         continue;
3030                 }
3031                 else
3032                 {
3033                         $data = $this->field_defs[$field];
3034                 }
3035
3036                 //ignore fields that are a part of the collection and a field has been removed as a result of
3037                 //layout customization.. this happens in subpanel customizations, use case, from the contacts subpanel
3038                 //in opportunities module remove the contact_role/opportunity_role field.
3039                 $process_field=true;
3040                 if (isset($data['relationship_fields']) and !empty($data['relationship_fields']))
3041                 {
3042                         foreach ($data['relationship_fields'] as $field_name)
3043                         {
3044                                 if (!isset($fields[$field_name]))
3045                                 {
3046                                         $process_field=false;
3047                                 }
3048                         }
3049                 }
3050                 if (!$process_field)
3051                 {
3052                         continue;
3053                 }
3054
3055                 if(  (!isset($data['source']) || $data['source'] == 'db') && (!empty($alias) || !empty($filter) ))
3056                 {
3057                         $ret_array['select'] .= ", $this->table_name.$field $alias";
3058                                 $selectedFields["$this->table_name.$field"] = true;
3059                 }
3060
3061                 if($data['type'] != 'relate' && isset($data['db_concat_fields']))
3062                 {
3063                         $ret_array['select'] .= ", " . db_concat($this->table_name, $data['db_concat_fields']) . " as $field";
3064                                 $selectedFields[db_concat($this->table_name, $data['db_concat_fields'])] = true;
3065                 }
3066                 //Custom relate field or relate fields built in module builder which have no link field associated.
3067                 if ($data['type'] == 'relate' && (isset($data['custom_module']) || isset($data['ext2']))) {
3068                         $joinTableAlias = 'jt' . $jtcount;
3069                         $relateJoinInfo = $this->custom_fields->getRelateJoin($data, $joinTableAlias);
3070                                 $ret_array['select'] .= $relateJoinInfo['select'];
3071                                 $ret_array['from'] .= $relateJoinInfo['from'];
3072                                 //Replace any references to the relationship in the where clause with the new alias
3073                                 //If the link isn't set, assume that search used the local table for the field
3074                                 $searchTable = isset($data['link']) ? $relateJoinInfo['rel_table'] : $this->table_name;
3075                                 $field_name = $relateJoinInfo['rel_table'] . '.' . !empty($data['name'])?$data['name']:'name';
3076                                 $where = preg_replace('/(^|[\s(])' . $field_name . '/' , '${1}' . $relateJoinInfo['name_field'], $where);
3077                                 $jtcount++;
3078                 }
3079                 //Parent Field
3080                 if ($data['type'] == 'parent') {
3081                         //See if we need to join anything by inspecting the where clause
3082                         $match = preg_match('/(^|[\s(])parent_(\w+)_(\w+)\.name/', $where, $matches);
3083                         if ($match) {
3084                                 $joinTableAlias = 'jt' . $jtcount;
3085                                 $joinModule = $matches[2];
3086                                 $joinTable = $matches[3];
3087                                 $localTable = $this->table_name;
3088                                 if (!empty($data['custom_module'])) {
3089                                         $localTable .= '_cstm';
3090                                 }
3091                                 global $beanFiles, $beanList, $module;
3092                                         require_once($beanFiles[$beanList[$joinModule]]);
3093                                     $rel_mod = new $beanList[$joinModule]();
3094                                     $nameField = "$joinTableAlias.name";
3095                                 if (isset($rel_mod->field_defs['name']))
3096                                     {
3097                                                 $name_field_def = $rel_mod->field_defs['name'];
3098                                         if(isset($name_field_def['db_concat_fields']))
3099                                         {
3100                                                 $nameField = db_concat($joinTableAlias, $name_field_def['db_concat_fields']);
3101                                         }
3102                                 }
3103                                 $ret_array['select'] .= ", $nameField {$data['name']} ";
3104                                 $ret_array['from'] .= " LEFT JOIN $joinTable $joinTableAlias
3105                                         ON $localTable.{$data['id_name']} = $joinTableAlias.id";
3106                                 //Replace any references to the relationship in the where clause with the new alias
3107                                         $where = preg_replace('/(^|[\s(])parent_' . $joinModule . '_' . $joinTable . '\.name/', '${1}' . $nameField, $where);
3108                                         $jtcount++;
3109                         }
3110                 }
3111                 if($data['type'] == 'relate' && isset($data['link']))
3112                 {
3113                         $this->load_relationship($data['link']);
3114
3115                         if(!empty($this->$data['link']))
3116                         {
3117                                 $params = array();
3118                                 if(empty($join_type))
3119                                 {
3120                                         $params['join_type'] = ' LEFT JOIN ';
3121                                 }
3122                                 else
3123                                 {
3124                                         $params['join_type'] = $join_type;
3125                                 }
3126                                 if(isset($data['join_name']))
3127                                 {
3128                                         $params['join_table_alias'] = $data['join_name'];
3129                                 }
3130                                 else
3131                                 {
3132                                         $params['join_table_alias']     = 'jt' . $jtcount;
3133
3134                                 }
3135                                 if(isset($data['join_link_name']))
3136                                 {
3137                                         $params['join_table_link_alias'] = $data['join_link_name'];
3138                                 }
3139                                 else
3140                                 {
3141                                         $params['join_table_link_alias'] = 'jtl' . $jtcount;
3142                                 }
3143                     $join_primary = !isset($data['join_primary']) || $data['join_primary'];
3144
3145                                 $join = $this->$data['link']->getJoin($params, true);
3146                     $used_join_key[] = $join['rel_key'];
3147                                 $rel_module = $this->$data['link']->getRelatedModuleName();
3148                                 $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');
3149
3150                                         //if rnanme is set to 'name', and bean files exist, then check if field should be a concatenated name
3151                                         global $beanFiles, $beanList;
3152                                         if($data['rname'] && !empty($beanFiles[$beanList[$rel_module]])) {
3153
3154                                                 //create an instance of the related bean
3155                                                 require_once($beanFiles[$beanList[$rel_module]]);
3156                                                 $rel_mod = new $beanList[$rel_module]();
3157                                                 //if bean has first and last name fields, then name should be concatenated
3158                                                 if(isset($rel_mod->field_name_map['first_name']) && isset($rel_mod->field_name_map['last_name'])){
3159                                                                 $data['db_concat_fields'] = array(0=>'first_name', 1=>'last_name');
3160                                                 }
3161                                         }
3162
3163
3164                                 if($join['type'] == 'many-to-many')
3165                                 {
3166                                         if(empty($ret_array['secondary_select']))
3167                                         {
3168                                                 $ret_array['secondary_select'] = " SELECT $this->table_name.id ref_id  ";
3169
3170                                                 if(!empty($beanFiles[$beanList[$rel_module]]) && $join_primary)
3171                                                 {
3172                                                         require_once($beanFiles[$beanList[$rel_module]]);
3173                                                         $rel_mod = new $beanList[$rel_module]();
3174                                                         if(isset($rel_mod->field_defs['assigned_user_id']))
3175                                                         {
3176                                                                 $ret_array['secondary_select'].= " , ". $params['join_table_alias'] . ".assigned_user_id {$field}_owner, '$rel_module' {$field}_mod";
3177
3178                                                         }
3179                                                         else
3180                                                         {
3181                                                                 if(isset($rel_mod->field_defs['created_by']))
3182                                                                 {
3183                                                                         $ret_array['secondary_select'].= " , ". $params['join_table_alias'] . ".created_by {$field}_owner , '$rel_module' {$field}_mod";
3184
3185                                                                 }
3186                                                         }
3187
3188
3189                                                 }
3190                                         }
3191
3192
3193
3194                                         if(isset($data['db_concat_fields']))
3195                                         {
3196                                                 $ret_array['secondary_select'] .= ' , ' . db_concat($params['join_table_alias'], $data['db_concat_fields']) . ' ' . $field;
3197                                         }
3198                                         else
3199                                         {
3200                                                 if(!isset($data['relationship_fields']))
3201                                                 {
3202                                                         $ret_array['secondary_select'] .= ' , ' . $params['join_table_alias'] . '.' . $data['rname'] . ' ' . $field;
3203                                                 }
3204                                         }
3205                                         if(!$singleSelect)
3206                                         {
3207                                                 $ret_array['select'] .= ", '                                                                                                                                                                                                                                                              ' $field ";
3208                                                 $ret_array['select'] .= ", '                                    '  " . $join['rel_key'] . ' ';
3209                                         }
3210                         $count_used =0;
3211                         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.
3212                             foreach($used_join_key as $used_key) {
3213                                if($used_key == $join['rel_key']) $count_used++;
3214                             }
3215                         }
3216                         if($count_used <= 1) {//27416, the $ret_array['secondary_select'] should always generate, regardless the dbtype
3217                             $ret_array['secondary_select'] .= ', ' . $params['join_table_link_alias'].'.'. $join['rel_key'] .' ' . $join['rel_key'];
3218                         }
3219                         if(isset($data['relationship_fields']))
3220                                         {
3221                                                 foreach($data['relationship_fields'] as $r_name=>$alias_name)
3222                                                 {
3223                                                         if(!empty( $secondarySelectedFields[$alias_name]))continue;
3224                                                         $ret_array['secondary_select'] .= ', ' . $params['join_table_link_alias'].'.'. $r_name .' ' . $alias_name;
3225                                                         $secondarySelectedFields[$alias_name] = true;
3226                                                 }
3227                                         }
3228                                         if(!$table_joined)
3229                                         {
3230                                                 $ret_array['secondary_from'] .= ' ' . $join['join']. ' AND ' . $params['join_table_alias'].'.deleted=0';
3231                                                 if (isset($data['link_type']) && $data['link_type'] == 'relationship_info' && ($parentbean instanceOf SugarBean))
3232                                                 {
3233                                                         $ret_array['secondary_where'] = $params['join_table_link_alias'] . '.' . $join['rel_key']. "='" .$parentbean->id . "'";
3234                                                 }
3235                                         }
3236                                 }
3237                                 else
3238                                 {
3239                                         if(isset($data['db_concat_fields']))
3240                                         {
3241                                                 $ret_array['select'] .= ' , ' . db_concat($params['join_table_alias'], $data['db_concat_fields']) . ' ' . $field;
3242                                         }
3243                                         else
3244                                         {
3245                                                 $ret_array['select'] .= ' , ' . $params['join_table_alias'] . '.' . $data['rname'] . ' ' . $field;
3246                                         }
3247                                         if(isset($data['additionalFields'])){
3248                                                 foreach($data['additionalFields'] as $k=>$v){
3249                                                         $ret_array['select'] .= ' , ' . $params['join_table_alias'] . '.' . $k . ' ' . $v;
3250                                                 }
3251                                         }
3252                                         if(!$table_joined)
3253                                         {
3254                                                 $ret_array['from'] .= ' ' . $join['join']. ' AND ' . $params['join_table_alias'].'.deleted=0';
3255                                                 if(!empty($beanList[$rel_module]) && !empty($beanFiles[$beanList[$rel_module]]))
3256                                                 {
3257                                                         require_once($beanFiles[$beanList[$rel_module]]);
3258                                                         $rel_mod = new $beanList[$rel_module]();
3259                                                         if(isset($value['target_record_key']) && !empty($filter))
3260                                                         {
3261                                                                 $selectedFields[$this->table_name.'.'.$value['target_record_key']] = true;
3262                                                                 $ret_array['select'] .= " , $this->table_name.{$value['target_record_key']} ";
3263                                                         }
3264                                                         if(isset($rel_mod->field_defs['assigned_user_id']))
3265                                                         {
3266                                                                 $ret_array['select'] .= ' , ' .$params['join_table_alias'] . '.assigned_user_id ' .  $field . '_owner';
3267                                                         }
3268                                                         else
3269                                                         {
3270                                                                 $ret_array['select'] .= ' , ' .$params['join_table_alias'] . '.created_by ' .  $field . '_owner';
3271                                                         }
3272                                                         $ret_array['select'] .= "  , '".$rel_module  ."' " .  $field . '_mod';
3273
3274                                                 }
3275                                         }
3276                                 }
3277                                 //Replace references to this table in the where clause with the new alias
3278                                 $join_table_name = $this->$data['link']->getRelatedTableName();
3279                                 // To fix SOAP stuff where we are trying to retrieve all the accounts data where accounts.id = ..
3280                                 // and this code changes accounts to jt4 as there is a self join with the accounts table.
3281                             //Martin fix #27494
3282                                         if(isset($data['db_concat_fields'])){
3283                                                 $db_field = $db_field = db_concat($params['join_table_alias'], $data['db_concat_fields']);
3284                                                 $where = preg_replace('/'.$data['name'].'/', $db_field, $where);
3285                                         }else{
3286                                                 $where = preg_replace('/(^|[\s(])' . $data['name'] . '/', '${1}' . $params['join_table_alias'] . '.'.$data['rname'], $where);
3287                         /**
3288                          * Bug #39988 - Added the line below. create_export_query_relate_link_patch() in export_utils.php
3289                          *    generates a where clause that works for stock modules. They all have a
3290                          *    custom create_export_query function on the bean. However, the Basic() object calls this
3291                          *    function for it's export_query. So, we have to handle the where clause that comes in.
3292                          */
3293                                                 $where = preg_replace('/(^|[\s(])join_' . $data['name'] . '/', '${1}' . $params['join_table_alias'], $where);
3294                                         }
3295                                 if(!$table_joined)
3296                                 {
3297                                         $joined_tables[$params['join_table_alias']]=1;
3298                                         $joined_tables[$params['join_table_link_alias']]=1;
3299                                 }
3300
3301                                 $jtcount++;
3302                         }
3303                 }
3304         }
3305         if(!empty($filter))
3306         {
3307                 if(isset($this->field_defs['assigned_user_id']) && empty($selectedFields[$this->table_name.'.assigned_user_id']))
3308                 {
3309                         $ret_array['select'] .= ", $this->table_name.assigned_user_id ";
3310                 }
3311                 else if(isset($this->field_defs['created_by']) &&  empty($selectedFields[$this->table_name.'.created_by']))
3312                 {
3313                         $ret_array['select'] .= ", $this->table_name.created_by ";
3314                 }
3315                 if(isset($this->field_defs['system_id']) && empty($selectedFields[$this->table_name.'.system_id']))
3316                 {
3317                         $ret_array['select'] .= ", $this->table_name.system_id ";
3318                 }
3319
3320         }
3321         $where_auto = '1=1';
3322         if($show_deleted == 0)
3323         {
3324                 $where_auto = "$this->table_name.deleted=0";
3325         }else if($show_deleted == 1)
3326         {
3327                 $where_auto = "$this->table_name.deleted=1";
3328         }
3329         if($where != "")
3330                 $ret_array['where'] = " where ($where) AND $where_auto";
3331         else
3332                 $ret_array['where'] = " where $where_auto";
3333         if(!empty($order_by))
3334         {
3335                 //make call to process the order by clause
3336                 $ret_array['order_by'] = " ORDER BY ". $this->process_order_by($order_by, null);
3337         }
3338         if($singleSelect)
3339         {
3340                 unset($ret_array['secondary_where']);
3341                 unset($ret_array['secondary_from']);
3342                 unset($ret_array['secondary_select']);
3343         }
3344
3345         if($return_array)
3346         {
3347                 return $ret_array;
3348         }
3349
3350         return  $ret_array['select'] . $ret_array['from'] . $ret_array['where']. $ret_array['order_by'];
3351
3352
3353
3354
3355     }
3356     /**
3357      * Returns parent record data for objects that store relationship information
3358      *
3359      * @param array $type_info
3360      *
3361      * Interal function, do not override.
3362      */
3363     function retrieve_parent_fields($type_info)
3364     {
3365         $queries = array();
3366         global $beanList, $beanFiles;
3367         $templates = array();
3368         $parent_child_map = array();
3369         foreach($type_info as $children_info)
3370         {
3371                 foreach($children_info as $child_info)
3372                 {
3373                         if($child_info['type'] == 'parent')
3374                         {
3375                                 if(empty($templates[$child_info['parent_type']]))
3376                                 {
3377                                         //Test emails will have an invalid parent_type, don't try to load the non-existant parent bean
3378                                         if ($child_info['parent_type'] == 'test') {
3379                                             continue;
3380                                         }
3381                                         $class = $beanList[$child_info['parent_type']];
3382                                         // Added to avoid error below; just silently fail and write message to log
3383                                         if ( empty($beanFiles[$class]) ) {
3384                                             $GLOBALS['log']->error($this->object_name.'::retrieve_parent_fields() - cannot load class "'.$class.'", skip loading.');
3385                                             continue;
3386                                         }
3387                                         require_once($beanFiles[$class]);
3388                                         $templates[$child_info['parent_type']] = new $class();
3389                                 }
3390
3391                                 if(empty($queries[$child_info['parent_type']]))
3392                                 {
3393                                         $queries[$child_info['parent_type']] = "SELECT id ";
3394                                         $field_def = $templates[$child_info['parent_type']]->field_defs['name'];
3395                                         if(isset($field_def['db_concat_fields']))
3396                                         {
3397                                                 $queries[$child_info['parent_type']] .= ' , ' . db_concat($templates[$child_info['parent_type']]->table_name, $field_def['db_concat_fields']) . ' parent_name';
3398                                         }
3399                                         else
3400                                         {
3401                                                 $queries[$child_info['parent_type']] .= ' , name parent_name';
3402                                         }
3403                                         if(isset($templates[$child_info['parent_type']]->field_defs['assigned_user_id']))
3404                                         {
3405                                                 $queries[$child_info['parent_type']] .= ", assigned_user_id parent_name_owner , '{$child_info['parent_type']}' parent_name_mod";;
3406                                         }else if(isset($templates[$child_info['parent_type']]->field_defs['created_by']))
3407                                         {
3408                                                 $queries[$child_info['parent_type']] .= ", created_by parent_name_owner, '{$child_info['parent_type']}' parent_name_mod";
3409                                         }
3410                                         $queries[$child_info['parent_type']] .= " FROM " . $templates[$child_info['parent_type']]->table_name ." WHERE id IN ('{$child_info['parent_id']}'";
3411                                 }
3412                                 else
3413                                 {
3414                                         if(empty($parent_child_map[$child_info['parent_id']]))
3415                                         $queries[$child_info['parent_type']] .= " ,'{$child_info['parent_id']}'";
3416                                 }
3417                                 $parent_child_map[$child_info['parent_id']][] = $child_info['child_id'];
3418                         }
3419                 }
3420         }
3421         $results = array();
3422         foreach($queries as $query)
3423         {
3424                 $result = $this->db->query($query . ')');
3425                 while($row = $this->db->fetchByAssoc($result))
3426                 {
3427                         $results[$row['id']] = $row;
3428                 }
3429         }
3430
3431         $child_results = array();
3432         foreach($parent_child_map as $parent_key=>$parent_child)
3433         {
3434                 foreach($parent_child as $child)
3435                 {
3436                         if(isset( $results[$parent_key]))
3437                         {
3438                                 $child_results[$child] = $results[$parent_key];
3439                         }
3440                 }
3441         }
3442         return $child_results;
3443     }
3444
3445     /**
3446      * Processes the list query and return fetched row.
3447      *
3448      * Internal function, do not override.
3449      * @param string $query select query to be processed.
3450      * @param int $row_offset starting position
3451      * @param int $limit Optioanl, default -1
3452      * @param int $max_per_page Optional, default -1
3453      * @param string $where Optional, additional filter criteria.
3454      * @return array Fetched data
3455      */
3456     function process_list_query($query, $row_offset, $limit= -1, $max_per_page = -1, $where = '')
3457     {
3458         global $sugar_config;
3459         $db = &DBManagerFactory::getInstance('listviews');
3460         /**
3461                  * if the row_offset is set to 'end' go to the end of the list
3462                  */
3463         $toEnd = strval($row_offset) == 'end';
3464         $GLOBALS['log']->debug("process_list_query: ".$query);
3465         if($max_per_page == -1)
3466         {
3467                 $max_per_page   = $sugar_config['list_max_entries_per_page'];
3468         }
3469         // Check to see if we have a count query available.
3470         if(empty($sugar_config['disable_count_query']) || $toEnd)
3471         {
3472                 $count_query = $this->create_list_count_query($query);
3473                 if(!empty($count_query) && (empty($limit) || $limit == -1))
3474                 {
3475                         // We have a count query.  Run it and get the results.
3476                         $result = $db->query($count_query, true, "Error running count query for $this->object_name List: ");
3477                         $assoc = $db->fetchByAssoc($result);
3478                         if(!empty($assoc['c']))
3479                         {
3480                                 $rows_found = $assoc['c'];
3481                                 $limit = $sugar_config['list_max_entries_per_page'];
3482                         }
3483                         if( $toEnd)
3484                         {
3485                                 $row_offset = (floor(($rows_found -1) / $limit)) * $limit;
3486                         }
3487                 }
3488         }
3489         else
3490         {
3491                 if((empty($limit) || $limit == -1))
3492                 {
3493                         $limit = $max_per_page + 1;
3494                         $max_per_page = $limit;
3495                 }
3496
3497         }
3498
3499         if(empty($row_offset))
3500         {
3501                 $row_offset = 0;
3502         }
3503         if(!empty($limit) && $limit != -1 && $limit != -99)
3504         {
3505                 $result = $db->limitQuery($query, $row_offset, $limit,true,"Error retrieving $this->object_name list: ");
3506         }
3507         else
3508         {
3509                 $result = $db->query($query,true,"Error retrieving $this->object_name list: ");
3510         }
3511
3512         $list = Array();
3513
3514         if(empty($rows_found))
3515         {
3516                 $rows_found =  $db->getRowCount($result);
3517         }
3518
3519         $GLOBALS['log']->debug("Found $rows_found ".$this->object_name."s");
3520
3521         $previous_offset = $row_offset - $max_per_page;
3522         $next_offset = $row_offset + $max_per_page;
3523
3524         $class = get_class($this);
3525         if($rows_found != 0 or $db->dbType != 'mysql')
3526         {
3527                 //todo Bug? we should remove the magic number -99
3528                 //use -99 to return all
3529                 $index = $row_offset;
3530                 while ($max_per_page == -99 || ($index < $row_offset + $max_per_page))
3531                 {
3532
3533                         if(!empty($sugar_config['disable_count_query']))
3534                         {
3535                                 $row = $db->fetchByAssoc($result);
3536                         }
3537                         else
3538                         {
3539                                 $row = $db->fetchByAssoc($result, $index);
3540                         }
3541                         if (empty($row))
3542                         {
3543                                 break;
3544                         }
3545
3546                         //instantiate a new class each time. This is because php5 passes
3547                         //by reference by default so if we continually update $this, we will
3548                         //at the end have a list of all the same objects
3549                         $temp = new $class();
3550
3551                         foreach($this->field_defs as $field=>$value)
3552                         {
3553                                 if (isset($row[$field]))
3554                                 {
3555                                         $temp->$field = $row[$field];
3556                                         $owner_field = $field . '_owner';
3557                                         if(isset($row[$owner_field]))
3558                                         {
3559                                                 $temp->$owner_field = $row[$owner_field];
3560                                         }
3561
3562                                         $GLOBALS['log']->debug("$temp->object_name({$row['id']}): ".$field." = ".$temp->$field);
3563                                 }else if (isset($row[$this->table_name .'.'.$field]))
3564                                 {
3565                                         $temp->$field = $row[$this->table_name .'.'.$field];
3566                                 }
3567                                 else
3568                                 {
3569                                         $temp->$field = "";
3570                                 }
3571                         }
3572
3573                         $temp->check_date_relationships_load();
3574                         $temp->fill_in_additional_list_fields();
3575                                 if($temp->hasCustomFields()) $temp->custom_fields->fill_relationships();
3576                                 $temp->call_custom_logic("process_record");
3577
3578                         $list[] = $temp;
3579
3580                         $index++;
3581                 }
3582         }
3583         if(!empty($sugar_config['disable_count_query']) && !empty($limit))
3584         {
3585
3586                 $rows_found = $row_offset + count($list);
3587
3588                 unset($list[$limit - 1]);
3589                 if(!$toEnd)
3590                 {
3591                         $next_offset--;
3592                         $previous_offset++;
3593                 }
3594         }
3595         $response = Array();
3596         $response['list'] = $list;
3597         $response['row_count'] = $rows_found;
3598         $response['next_offset'] = $next_offset;
3599         $response['previous_offset'] = $previous_offset;
3600         $response['current_offset'] = $row_offset ;
3601         return $response;
3602     }
3603
3604         /**
3605          * Returns the number of rows that the given SQL query should produce
3606      *
3607      * Internal function, do not override.
3608      * @param string $query valid select  query
3609      * @param boolean $is_count_query Optional, Default false, set to true if passed query is a count query.
3610      * @return int count of rows found
3611          */
3612         function _get_num_rows_in_query($query, $is_count_query=false)
3613         {
3614                 $num_rows_in_query = 0;
3615                 if (!$is_count_query)
3616                 {
3617                         $count_query = SugarBean::create_list_count_query($query);
3618                 } else
3619                         $count_query=$query;
3620
3621                 $result = $this->db->query($count_query, true, "Error running count query for $this->object_name List: ");
3622                 $row_num = 0;
3623                 $row = $this->db->fetchByAssoc($result, $row_num);
3624                 while($row)
3625                 {
3626                         $num_rows_in_query += current($row);
3627                         $row_num++;
3628                         $row = $this->db->fetchByAssoc($result, $row_num);
3629                 }
3630
3631                 return $num_rows_in_query;
3632         }
3633
3634     /**
3635      * Applies pagination window to union queries used by list view and subpanels,
3636      * executes the query and returns fetched data.
3637      *
3638      * Internal function, do not override.
3639      * @param object $parent_bean
3640      * @param string $query query to be processed.
3641      * @param int $row_offset
3642      * @param int $limit optional, default -1
3643      * @param int $max_per_page Optional, default -1
3644      * @param string $where Custom where clause.
3645      * @param array $subpanel_def definition of sub-panel to be processed
3646      * @param string $query_row_count
3647      * @param array $seconday_queries
3648      * @return array Fetched data.
3649      */
3650     function process_union_list_query($parent_bean, $query,
3651     $row_offset, $limit= -1, $max_per_page = -1, $where = '', $subpanel_def, $query_row_count='', $secondary_queries = array())
3652
3653     {
3654                 $db = &DBManagerFactory::getInstance('listviews');
3655                 /**
3656                  * if the row_offset is set to 'end' go to the end of the list
3657                  */
3658                 $toEnd = strval($row_offset) == 'end';
3659                 global $sugar_config;
3660                 $use_count_query=false;
3661                 $processing_collection=$subpanel_def->isCollection();
3662
3663                 $GLOBALS['log']->debug("process_list_query: ".$query);
3664                 if($max_per_page == -1)
3665                 {
3666                         $max_per_page   = $sugar_config['list_max_entries_per_subpanel'];
3667                 }
3668                 if(empty($query_row_count))
3669                 {
3670                         $query_row_count = $query;
3671                 }
3672                 $distinct_position=strpos($query_row_count,"DISTINCT");
3673
3674                 if ($distinct_position!= false)
3675                 {
3676                         $use_count_query=true;
3677                 }
3678                 $performSecondQuery = true;
3679                 if(empty($sugar_config['disable_count_query']) || $toEnd)
3680                 {
3681                         $rows_found = $this->_get_num_rows_in_query($query_row_count,$use_count_query);
3682                         if($rows_found < 1)
3683                         {
3684                                 $performSecondQuery = false;
3685                         }
3686                         if(!empty($rows_found) && (empty($limit) || $limit == -1))
3687                         {
3688                                 $limit = $sugar_config['list_max_entries_per_subpanel'];
3689                         }
3690                         if( $toEnd)
3691                         {
3692                                 $row_offset = (floor(($rows_found -1) / $limit)) * $limit;
3693
3694                         }
3695                 }
3696                 else
3697                 {
3698                         if((empty($limit) || $limit == -1))
3699                         {
3700                                 $limit = $max_per_page + 1;
3701                                 $max_per_page = $limit;
3702                         }
3703                 }
3704
3705                 if(empty($row_offset))
3706                 {
3707                         $row_offset = 0;
3708                 }
3709                 $list = array();
3710                 $previous_offset = $row_offset - $max_per_page;
3711                 $next_offset = $row_offset + $max_per_page;
3712
3713                 if($performSecondQuery)
3714                 {
3715                         if(!empty($limit) && $limit != -1 && $limit != -99)
3716                         {
3717                                 $result = $db->limitQuery($query, $row_offset, $limit,true,"Error retrieving $parent_bean->object_name list: ");
3718                         }
3719                         else
3720                         {
3721                                 $result = $db->query($query,true,"Error retrieving $this->object_name list: ");
3722                         }
3723                         if(empty($rows_found))
3724                         {
3725                                 $rows_found =  $db->getRowCount($result);
3726                         }
3727
3728                         $GLOBALS['log']->debug("Found $rows_found ".$parent_bean->object_name."s");
3729                         if($rows_found != 0 or $db->dbType != 'mysql')
3730                         {
3731                                 //use -99 to return all
3732
3733                                 // get the current row
3734                                 $index = $row_offset;
3735                                 if(!empty($sugar_config['disable_count_query']))
3736                                 {
3737                                         $row = $db->fetchByAssoc($result);
3738                                 }
3739                                 else
3740                                 {
3741                                         $row = $db->fetchByAssoc($result, $index);
3742                                 }
3743
3744                                 $post_retrieve = array();
3745                                 $isFirstTime = true;
3746                                 while($row)
3747                                 {
3748                                         $function_fields = array();
3749                                         if(($index < $row_offset + $max_per_page || $max_per_page == -99) or ($db->dbType != 'mysql'))
3750                                         {
3751                                                 if ($processing_collection)
3752                                                 {
3753                                                         $current_bean =$subpanel_def->sub_subpanels[$row['panel_name']]->template_instance;
3754                                                         if(!$isFirstTime)
3755                                                         {
3756                                                                 $class = get_class($subpanel_def->sub_subpanels[$row['panel_name']]->template_instance);
3757                                                                 $current_bean = new $class();
3758                                                         }
3759                                                 } else {
3760                                                         $current_bean=$subpanel_def->template_instance;
3761                                                         if(!$isFirstTime)
3762                                                         {
3763                                                                 $class = get_class($subpanel_def->template_instance);
3764                                                                 $current_bean = new $class();
3765                                                         }
3766                                                 }
3767                                                 $isFirstTime = false;
3768                                                 //set the panel name in the bean instance.
3769                                                 if (isset($row['panel_name']))
3770                                                 {
3771                                                         $current_bean->panel_name=$row['panel_name'];
3772                                                 }
3773                                                 foreach($current_bean->field_defs as $field=>$value)
3774                                                 {
3775
3776                                                         if (!empty($row[$field]))
3777                                                         {
3778                                                                 $current_bean->$field = $row[$field];
3779
3780                                                                 unset($row[$field]);
3781                                                                 //$GLOBALS['log']->debug("$current_bean->object_name({$row['id']}): ".$field." = ".$current_bean->$field);
3782                                                         }
3783                                                         else if (!empty($row[$this->table_name .'.'.$field]))
3784                                                         {
3785                                                                 $current_bean->$field = $row[$current_bean->table_name .'.'.$field];
3786                                                                 unset($row[$current_bean->table_name .'.'.$field]);
3787                                                         }
3788                                                         else
3789                                                         {
3790                                                                 $current_bean->$field = "";
3791                                                                 unset($row[$field]);
3792                                                         }
3793                                                         if(isset($value['source']) && $value['source'] == 'function')
3794                                                         {
3795                                                                 $function_fields[]=$field;
3796                                                         }
3797                                                 }
3798                                                 foreach($row as $key=>$value)
3799                                                 {
3800                                                         $current_bean->$key = $value;
3801                                                 }
3802                                                 foreach($function_fields as $function_field)
3803                                                 {
3804                                                         $value = $current_bean->field_defs[$function_field];
3805                                                         $can_execute = true;
3806                                                         $execute_params = array();
3807                                                         $execute_function = array();
3808                                                         if(!empty($value['function_class']))
3809                                                         {
3810                                                                 $execute_function[] =   $value['function_class'];
3811                                                                 $execute_function[] =   $value['function_name'];
3812                                                         }
3813                                                         else
3814                                                         {
3815                                                                 $execute_function       = $value['function_name'];
3816                                                         }
3817                                                         foreach($value['function_params'] as $param )
3818                                                         {
3819                                                                 if (empty($value['function_params_source']) or $value['function_params_source']=='parent')
3820                                                                 {
3821                                                                         if(empty($this->$param))
3822                                                                         {
3823                                                                                 $can_execute = false;
3824                                                                         }
3825                                                                         else
3826                                                                         {
3827                                                                                 $execute_params[] = $this->$param;
3828                                                                         }
3829                                                                 } else if ($value['function_params_source']=='this')
3830                                                                 {
3831                                                                         if(empty($current_bean->$param))
3832                                                                         {
3833                                                                                 $can_execute = false;
3834                                                                         }
3835                                                                         else
3836                                                                         {
3837                                                                                 $execute_params[] = $current_bean->$param;
3838                                                                         }
3839                                                                 }
3840                                                                 else
3841                                                                 {
3842                                                                         $can_execute = false;
3843                                                                 }
3844
3845                                                         }
3846                                                         if($can_execute)
3847                                                         {
3848                                                                 if(!empty($value['function_require']))
3849                                                                 {
3850                                                                         require_once($value['function_require']);
3851                                                                 }
3852                                                                 $current_bean->$function_field = call_user_func_array($execute_function, $execute_params);
3853                                                         }
3854                                                 }
3855                                                 if(!empty($current_bean->parent_type) && !empty($current_bean->parent_id))
3856                                                 {
3857                                                         if(!isset($post_retrieve[$current_bean->parent_type]))
3858                                                         {
3859                                                                 $post_retrieve[$current_bean->parent_type] = array();
3860                                                         }
3861                                                         $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');
3862                                                 }
3863                                                 //$current_bean->fill_in_additional_list_fields();
3864                                                 $list[$current_bean->id] = $current_bean;
3865                                         }
3866                                         // go to the next row
3867                                         $index++;
3868                                         $row = $db->fetchByAssoc($result, $index);
3869                                 }
3870                         }
3871                         //now handle retrieving many-to-many relationships
3872                         if(!empty($list))
3873                         {
3874                                 foreach($secondary_queries as $query2)
3875                                 {
3876                                         $result2 = $db->query($query2);
3877
3878                                         $row2 = $db->fetchByAssoc($result2);
3879                                         while($row2)
3880                                         {
3881                                                 $id_ref = $row2['ref_id'];
3882
3883                                                 if(isset($list[$id_ref]))
3884                                                 {
3885                                                         foreach($row2 as $r2key=>$r2value)
3886                                                         {
3887                                                                 if($r2key != 'ref_id')
3888                                                                 {
3889                                                                         $list[$id_ref]->$r2key = $r2value;
3890                                                                 }
3891                                                         }
3892                                                 }
3893                                                 $row2 = $db->fetchByAssoc($result2);
3894                                         }
3895                                 }
3896
3897                         }
3898
3899                         if(isset($post_retrieve))
3900                         {
3901                                 $parent_fields = $this->retrieve_parent_fields($post_retrieve);
3902                         }
3903                         else
3904                         {
3905                                 $parent_fields = array();
3906                         }
3907                         if(!empty($sugar_config['disable_count_query']) && !empty($limit))
3908                         {
3909                                 $rows_found = $row_offset + count($list);
3910                                 if(count($list) >= $limit)
3911                                 {
3912                                         array_pop($list);
3913                                 }
3914                                 if(!$toEnd)
3915                                 {
3916                                         $next_offset--;
3917                                         $previous_offset++;
3918                                 }
3919                         }
3920                 }
3921                 else
3922                 {
3923                         $row_found      = 0;
3924                         $parent_fields = array();
3925                 }
3926                 $response = array();
3927                 $response['list'] = $list;
3928                 $response['parent_data'] = $parent_fields;
3929                 $response['row_count'] = $rows_found;
3930                 $response['next_offset'] = $next_offset;
3931                 $response['previous_offset'] = $previous_offset;
3932                 $response['current_offset'] = $row_offset ;
3933                 $response['query'] = $query;
3934
3935                 return $response;
3936     }
3937
3938     /**
3939      * Applies pagination window to select queries used by detail view,
3940      * executes the query and returns fetched data.
3941      *
3942      * Internal function, do not override.
3943      * @param string $query query to be processed.
3944      * @param int $row_offset
3945      * @param int $limit optional, default -1
3946      * @param int $max_per_page Optional, default -1
3947      * @param string $where Custom where clause.
3948      * @param int $offset Optional, default 0
3949      * @return array Fetched data.
3950      *
3951      */
3952     function process_detail_query($query, $row_offset, $limit= -1, $max_per_page = -1, $where = '', $offset = 0)
3953     {
3954         global $sugar_config;
3955         $GLOBALS['log']->debug("process_list_query: ".$query);
3956         if($max_per_page == -1)
3957         {
3958                 $max_per_page   = $sugar_config['list_max_entries_per_page'];
3959         }
3960
3961         // Check to see if we have a count query available.
3962         $count_query = $this->create_list_count_query($query);
3963
3964         if(!empty($count_query) && (empty($limit) || $limit == -1))
3965         {
3966                 // We have a count query.  Run it and get the results.
3967                 $result = $this->db->query($count_query, true, "Error running count query for $this->object_name List: ");
3968                 $assoc = $this->db->fetchByAssoc($result);
3969                 if(!empty($assoc['c']))
3970                 {
3971                         $total_rows = $assoc['c'];
3972                 }
3973         }
3974
3975         if(empty($row_offset))
3976         {
3977                 $row_offset = 0;
3978         }
3979
3980         $result = $this->db->limitQuery($query, $offset, 1, true,"Error retrieving $this->object_name list: ");
3981
3982         $rows_found = $this->db->getRowCount($result);
3983
3984         $GLOBALS['log']->debug("Found $rows_found ".$this->object_name."s");
3985
3986         $previous_offset = $row_offset - $max_per_page;
3987         $next_offset = $row_offset + $max_per_page;
3988
3989         if($rows_found != 0 or $this->db->dbType != 'mysql')
3990         {
3991                 $index = 0;
3992                 $row = $this->db->fetchByAssoc($result, $index);
3993                 $this->retrieve($row['id']);
3994         }
3995
3996         $response = Array();
3997         $response['bean'] = $this;
3998         if (empty($total_rows))
3999                 $total_rows=0;
4000         $response['row_count'] = $total_rows;
4001         $response['next_offset'] = $next_offset;
4002         $response['previous_offset'] = $previous_offset;
4003
4004         return $response;
4005     }
4006
4007     /**
4008      * Processes fetched list view data
4009      *
4010      * Internal function, do not override.
4011      * @param string $query query to be processed.
4012      * @param boolean $check_date Optional, default false. if set to true date time values are processed.
4013      * @return array Fetched data.
4014      *
4015      */
4016     function process_full_list_query($query, $check_date=false)
4017     {
4018
4019         $GLOBALS['log']->debug("process_full_list_query: query is ".$query);
4020         $result = $this->db->query($query, false);
4021         $GLOBALS['log']->debug("process_full_list_query: result is ".print_r($result,true));
4022         $class = get_class($this);
4023         $isFirstTime = true;
4024         $bean = new $class();
4025
4026         //if($this->db->getRowCount($result) > 0){
4027
4028
4029         // We have some data.
4030         //while ($row = $this->db->fetchByAssoc($result)) {
4031         while (($row = $bean->db->fetchByAssoc($result)) != null)
4032         {
4033                 if(!$isFirstTime)
4034                 {
4035                         $bean = new $class();
4036                 }
4037                 $isFirstTime = false;
4038
4039                 foreach($bean->field_defs as $field=>$value)
4040                 {
4041                         if (isset($row[$field]))
4042                         {
4043                                 $bean->$field = $row[$field];
4044                                 $GLOBALS['log']->debug("process_full_list: $bean->object_name({$row['id']}): ".$field." = ".$bean->$field);
4045                         }
4046                         else
4047                         {
4048                                 $bean->$field = '';
4049                         }
4050                 }
4051                 if($check_date)
4052                 {
4053                         $bean->processed_dates_times = array();
4054                         $bean->check_date_relationships_load();
4055                 }
4056                 $bean->fill_in_additional_list_fields();
4057                         $bean->call_custom_logic("process_record");
4058
4059                 $list[] = $bean;
4060         }
4061         //}
4062         if (isset($list)) return $list;
4063         else return null;
4064     }
4065
4066         /**
4067         * Tracks the viewing of a detail record.
4068         * This leverages get_summary_text() which is object specific.
4069     *
4070     * Internal function, do not override.
4071         * @param $user_id - String value of the user that is viewing the record.
4072     * @param $current_module - String value of the module being processed.
4073     * @param $current_view - String value of the current view
4074         */
4075         function track_view($user_id, $current_module, $current_view='')
4076         {
4077             $trackerManager = TrackerManager::getInstance();
4078                 if($monitor = $trackerManager->getMonitor('tracker')){
4079                 $monitor->setValue('date_modified', gmdate($GLOBALS['timedate']->get_db_date_time_format()));
4080                 $monitor->setValue('user_id', $user_id);
4081                 $monitor->setValue('module_name', $current_module);
4082                 $monitor->setValue('action', $current_view);
4083                 $monitor->setValue('item_id', $this->id);
4084                 $monitor->setValue('item_summary', $this->get_summary_text());
4085                 $monitor->setValue('visible',true);
4086                 $trackerManager->saveMonitor($monitor);
4087                 }
4088         }
4089
4090         /**
4091         * Returns the summary text that should show up in the recent history list for this object.
4092     *
4093     * Internal function, do not override.
4094         */
4095         function get_summary_text()
4096         {
4097                 return "Base Implementation.  Should be overridden.";
4098         }
4099
4100         /**
4101         * This is designed to be overridden and add specific fields to each record.
4102         * This allows the generic query to fill in the major fields, and then targeted
4103         * queries to get related fields and add them to the record.  The contact's
4104         * account for instance.  This method is only used for populating extra fields
4105         * in lists.
4106         */
4107         function fill_in_additional_list_fields(){
4108                 if(!empty($this->field_defs['parent_name']) && empty($this->parent_name)){
4109                         $this->fill_in_additional_parent_fields();
4110                 }
4111         }
4112
4113         /**
4114         * This is designed to be overridden and add specific fields to each record.
4115         * This allows the generic query to fill in the major fields, and then targeted
4116         * queries to get related fields and add them to the record.  The contact's
4117         * account for instance.  This method is only used for populating extra fields
4118         * in the detail form
4119         */
4120         function fill_in_additional_detail_fields()
4121         {
4122                 if(!empty($this->field_defs['assigned_user_name']) && !empty($this->assigned_user_id)){
4123
4124                         $this->assigned_user_name = get_assigned_user_name($this->assigned_user_id);
4125
4126                 }
4127                 if(!empty($this->field_defs['created_by']) && !empty($this->created_by))
4128                 $this->created_by_name = get_assigned_user_name($this->created_by);
4129                 if(!empty($this->field_defs['modified_user_id']) && !empty($this->modified_user_id))
4130                 $this->modified_by_name = get_assigned_user_name($this->modified_user_id);
4131
4132                 if(!empty($this->field_defs['parent_name'])){
4133                         $this->fill_in_additional_parent_fields();
4134                 }
4135         }
4136
4137         /**
4138          * This is desgined to be overridden or called from extending bean. This method
4139          * will fill in any parent_name fields.
4140          */
4141         function fill_in_additional_parent_fields() {
4142
4143                 if(!empty($this->parent_id) && !empty($this->last_parent_id) && $this->last_parent_id == $this->parent_id){
4144                         return false;
4145                 }else{
4146                         $this->parent_name = '';
4147                 }
4148                 if(!empty($this->parent_type)) {
4149                         $this->last_parent_id = $this->parent_id;
4150                         $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'));
4151                         if(!empty($this->parent_first_name) || !empty($this->parent_last_name) ){
4152                                 $this->parent_name = $GLOBALS['locale']->getLocaleFormattedName($this->parent_first_name, $this->parent_last_name);
4153                         } else if(!empty($this->parent_document_name)){
4154                                 $this->parent_name = $this->parent_document_name;
4155                         }
4156                 }
4157         }
4158
4159 /*
4160      * Fill in a link field
4161      */
4162
4163     function fill_in_link_field( $linkFieldName )
4164     {
4165         if ($this->load_relationship($linkFieldName))
4166         {
4167             $list=$this->$linkFieldName->get();
4168             $this->$linkFieldName = '' ; // match up with null value in $this->populateFromRow()
4169             if (!empty($list))
4170                 $this->$linkFieldName = $list[0];
4171         }
4172     }
4173
4174         /**
4175          * Fill in fields where type = relate
4176          */
4177     function fill_in_relationship_fields(){
4178                 if(!empty($this->relDepth)) {
4179                         if($this->relDepth > 1)return;
4180                 }else $this->relDepth = 0;
4181
4182         foreach($this->field_defs as $field)
4183         {
4184             if(0 == strcmp($field['type'],'relate') && !empty($field['module']))
4185             {
4186                 $name = $field['name'];
4187                 if(empty($this->$name))
4188                 {
4189                     // 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']
4190                     $related_module = $field['module'];
4191                     $id_name = $field['id_name'];
4192                     if (empty($this->$id_name)){
4193                        $this->fill_in_link_field($id_name);
4194                     }
4195                         if(!empty($this->$id_name) && ( $this->object_name != $related_module || ( $this->object_name == $related_module && $this->$id_name != $this->id ))){
4196                                 if(isset($GLOBALS['beanList'][ $related_module])){
4197                                 $class = $GLOBALS['beanList'][$related_module];
4198
4199                                 if(!empty($this->$id_name) && file_exists($GLOBALS['beanFiles'][$class]) && isset($this->$name)){
4200                                         require_once($GLOBALS['beanFiles'][$class]);
4201                                                                 $mod = new $class();
4202                                         $mod->relDepth = $this->relDepth + 1;
4203                                         $mod->retrieve($this->$id_name);
4204                                 if (!empty($field['rname'])) {
4205                                     $this->$name = $mod->$field['rname'];
4206                                 } else if (isset($mod->name)) {
4207                                                 $this->$name = $mod->name;
4208                                         }
4209                                 }
4210                             }
4211                     }
4212                     if(!empty($this->$id_name) && isset($this->$name))
4213                     {
4214                         if(!isset($field['additionalFields']))
4215                            $field['additionalFields'] = array();
4216                         if(!empty($field['rname']))
4217                         {
4218                             $field['additionalFields'][$field['rname']]= $name;
4219                         }
4220                         else
4221                         {
4222                             $field['additionalFields']['name']= $name;
4223                         }
4224                         $this->getRelatedFields($related_module, $this->$id_name, $field['additionalFields']);
4225                     }
4226                 }
4227             }
4228         }
4229     }
4230
4231         /**
4232         * This is a helper function that is used to quickly created indexes when creating tables.
4233         */
4234         function create_index($query)
4235         {
4236                 $GLOBALS['log']->info($query);
4237
4238                 $result = $this->db->query($query, true, "Error creating index:");
4239         }
4240
4241         /**
4242      * This function should be overridden in each module.  It marks an item as deleted.
4243      *
4244      * If it is not overridden, then marking this type of item is not allowed
4245          */
4246         function mark_deleted($id)
4247         {
4248                 global $current_user;
4249                 $date_modified = gmdate($GLOBALS['timedate']->get_db_date_time_format());
4250                 if(isset($_SESSION['show_deleted']))
4251                 {
4252                         $this->mark_undeleted($id);
4253                 }
4254                 else
4255                 {
4256                         // call the custom business logic
4257                         $custom_logic_arguments['id'] = $id;
4258                         $this->call_custom_logic("before_delete", $custom_logic_arguments);
4259
4260                         if ( isset($this->field_defs['modified_user_id']) ) {
4261                 if (!empty($current_user)) {
4262                     $this->modified_user_id = $current_user->id;
4263                 } else {
4264                     $this->modified_user_id = 1;
4265                 }
4266                 $query = "UPDATE $this->table_name set deleted=1 , date_modified = '$date_modified', modified_user_id = '$this->modified_user_id' where id='$id'";
4267                         } else
4268                 $query = "UPDATE $this->table_name set deleted=1 , date_modified = '$date_modified' where id='$id'";
4269                         $this->db->query($query, true,"Error marking record deleted: ");
4270                         $this->mark_relationships_deleted($id);
4271
4272                         // Take the item off the recently viewed lists
4273                         $tracker = new Tracker();
4274                         $tracker->makeInvisibleForAll($id);
4275
4276                         // call the custom business logic
4277                         $this->call_custom_logic("after_delete", $custom_logic_arguments);
4278                 }
4279         }
4280
4281         /**
4282      * Restores data deleted by call to mark_deleted() function.
4283      *
4284      * Internal function, do not override.
4285          */
4286         function mark_undeleted($id)
4287         {
4288                 // call the custom business logic
4289                 $custom_logic_arguments['id'] = $id;
4290                 $this->call_custom_logic("before_restore", $custom_logic_arguments);
4291
4292                 $date_modified = gmdate($GLOBALS['timedate']->get_db_date_time_format());
4293                 $query = "UPDATE $this->table_name set deleted=0 , date_modified = '$date_modified' where id='$id'";
4294                 $this->db->query($query, true,"Error marking record undeleted: ");
4295
4296                 // call the custom business logic
4297                 $this->call_custom_logic("after_restore", $custom_logic_arguments);
4298         }
4299
4300    /**
4301     * This function deletes relationships to this object.  It should be overridden
4302     * to handle the relationships of the specific object.
4303         * This function is called when the item itself is being deleted.
4304     *
4305     * @param int $id id of the relationship to delete
4306         */
4307    function mark_relationships_deleted($id)
4308    {
4309         $this->delete_linked($id);
4310    }
4311
4312         /**
4313         * This function is used to execute the query and create an array template objects
4314         * from the resulting ids from the query.
4315         * It is currently used for building sub-panel arrays.
4316     *
4317         * @param string $query - the query that should be executed to build the list
4318         * @param object $template - The object that should be used to copy the records.
4319     * @param int $row_offset Optional, default 0
4320     * @param int $limit Optional, default -1
4321     * @return array
4322         */
4323         function build_related_list($query, &$template, $row_offset = 0, $limit = -1)
4324         {
4325                 $GLOBALS['log']->debug("Finding linked records $this->object_name: ".$query);
4326                 $db = &DBManagerFactory::getInstance('listviews');
4327
4328                 if(!empty($row_offset) && $row_offset != 0 && !empty($limit) && $limit != -1)
4329                 {
4330                         $result = $db->limitQuery($query, $row_offset, $limit,true,"Error retrieving $template->object_name list: ");
4331                 }
4332                 else
4333                 {
4334                         $result = $db->query($query, true);
4335                 }
4336
4337                 $list = Array();
4338                 $isFirstTime = true;
4339                 $class = get_class($template);
4340                 while($row = $this->db->fetchByAssoc($result))
4341                 {
4342                         if(!$isFirstTime)
4343                         {
4344                                 $template = new $class();
4345                         }
4346                         $isFirstTime = false;
4347                         $record = $template->retrieve($row['id']);
4348
4349                         if($record != null)
4350                         {
4351                                 // this copies the object into the array
4352                                 $list[] = $template;
4353                         }
4354                 }
4355                 return $list;
4356         }
4357
4358   /**
4359         * This function is used to execute the query and create an array template objects
4360         * from the resulting ids from the query.
4361         * It is currently used for building sub-panel arrays. It supports an additional
4362         * where clause that is executed as a filter on the results
4363         *
4364         * @param string $query - the query that should be executed to build the list
4365         * @param object $template - The object that should be used to copy the records.
4366         */
4367   function build_related_list_where($query, &$template, $where='', $in='', $order_by, $limit='', $row_offset = 0)
4368   {
4369         $db = &DBManagerFactory::getInstance('listviews');
4370         // No need to do an additional query
4371         $GLOBALS['log']->debug("Finding linked records $this->object_name: ".$query);
4372         if(empty($in) && !empty($query))
4373         {
4374                 $idList = $this->build_related_in($query);
4375                 $in = $idList['in'];
4376         }
4377         // MFH - Added Support For Custom Fields in Searches
4378         $custom_join="";
4379         if(isset($this->custom_fields)) {
4380                 $custom_join = $this->custom_fields->getJOIN();
4381         }
4382
4383         $query = "SELECT id ";
4384
4385         if (!empty($custom_join)) {
4386                 $query .= $custom_join['select'];
4387         }
4388     $query .= " FROM $this->table_name ";
4389
4390     if (!empty($custom_join) && !empty($custom_join['join'])) {
4391         $query .= " " . $custom_join['join'];
4392     }
4393
4394     $query .= " WHERE deleted=0 AND id IN $in";
4395         if(!empty($where))
4396         {
4397                 $query .= " AND $where";
4398         }
4399
4400
4401         if(!empty($order_by))
4402         {
4403                 $query .= "ORDER BY $order_by";
4404         }
4405         if (!empty($limit))
4406         {
4407                 $result = $db->limitQuery($query, $row_offset, $limit,true,"Error retrieving $this->object_name list: ");
4408         }
4409         else
4410         {
4411                 $result = $db->query($query, true);
4412         }
4413
4414         $list = Array();
4415         $isFirstTime = true;
4416         $class = get_class($template);
4417
4418         $disable_security_flag = ($template->disable_row_level_security) ? true : false;
4419         while($row = $db->fetchByAssoc($result))
4420         {
4421                 if(!$isFirstTime)
4422                 {
4423                         $template = new $class();
4424                         $template->disable_row_level_security = $disable_security_flag;
4425                 }
4426                 $isFirstTime = false;
4427                 $record = $template->retrieve($row['id']);
4428                 if($record != null)
4429                 {
4430                         // this copies the object into the array
4431                         $list[] = $template;
4432                 }
4433         }
4434
4435         return $list;
4436   }
4437
4438     /**
4439      * Constructs an comma seperated list of ids from passed query results.
4440      *
4441      * @param string @query query to be executed.
4442      *
4443      */
4444     function build_related_in($query)
4445     {
4446         $idList = array();
4447         $result = $this->db->query($query, true);
4448         $ids = '';
4449         while($row = $this->db->fetchByAssoc($result))
4450         {
4451             $idList[] = $row['id'];
4452             if(empty($ids))
4453             {
4454                 $ids = "('" . $row['id'] . "'";
4455             }
4456             else
4457             {
4458                 $ids .= ",'" . $row['id'] . "'";
4459             }
4460         }
4461         if(empty($ids))
4462         {
4463             $ids = "('')";
4464         }else{
4465             $ids .= ')';
4466         }
4467         
4468         return array('list'=>$idList, 'in'=>$ids);
4469     }
4470
4471     /**
4472     * Optionally copies values from fetched row into the bean.
4473     *
4474     * Internal function, do not override.
4475     *
4476     * @param string $query - the query that should be executed to build the list
4477     * @param object $template - The object that should be used to copy the records
4478     * @param array $field_list List of  fields.
4479     * @return array
4480     */
4481     function build_related_list2($query, &$template, &$field_list)
4482     {
4483         $GLOBALS['log']->debug("Finding linked values $this->object_name: ".$query);
4484
4485         $result = $this->db->query($query, true);
4486
4487         $list = Array();
4488         $isFirstTime = true;
4489         $class = get_class($template);
4490         while($row = $this->db->fetchByAssoc($result))
4491         {
4492                 // Create a blank copy
4493                 $copy = $template;
4494                 if(!$isFirstTime)
4495                 {
4496                         $copy = new $class();
4497                 }
4498                 $isFirstTime = false;
4499                 foreach($field_list as $field)
4500                 {
4501                         // Copy the relevant fields
4502                         $copy->$field = $row[$field];
4503
4504                 }
4505
4506                 // this copies the object into the array
4507                 $list[] = $copy;
4508         }
4509
4510         return $list;
4511     }
4512
4513         /**
4514      * Let implementing classes to fill in row specific columns of a list view form
4515      *
4516      */
4517         function list_view_parse_additional_sections(&$list_form)
4518         {
4519         }
4520
4521         /**
4522      * Assigns all of the values into the template for the list view
4523      */
4524         function get_list_view_array()
4525         {
4526         static $cache = array();
4527                 // cn: bug 12270 - sensitive fields being passed arbitrarily in listViews
4528                 $sensitiveFields = array('user_hash' => '');
4529
4530                 $return_array = Array();
4531                 global $app_list_strings, $mod_strings;
4532                 foreach($this->field_defs as $field=>$value){
4533
4534                         if(isset($this->$field)){
4535
4536                                 // cn: bug 12270 - sensitive fields being passed arbitrarily in listViews
4537                                 if(isset($sensitiveFields[$field]))
4538                                         continue;
4539                 if(!isset($cache[$field]))
4540                         $cache[$field] = strtoupper($field);
4541
4542                 //Fields hidden by Dependent Fields
4543                 if (isset($value['hidden']) && $value['hidden'] === true) {
4544                         $return_array[$cache[$field]] = "";
4545                 }
4546                                 //cn: if $field is a _dom, detect and return VALUE not KEY
4547                                 //cl: empty function check for meta-data enum types that have values loaded from a function
4548                                 else if (((!empty($value['type']) && ($value['type'] == 'enum' || $value['type'] == 'radioenum') ))  && empty($value['function'])){
4549                                         if(!empty($app_list_strings[$value['options']][$this->$field])){
4550                                                 $return_array[$cache[$field]] = $app_list_strings[$value['options']][$this->$field];
4551                                         }
4552                                         //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.
4553                                         elseif(!empty($mod_strings[$value['options']][$this->$field]))
4554                                         {
4555                                                 $return_array[$cache[$field]] = $mod_strings[$value['options']][$this->$field];
4556                                         }
4557                                         else{
4558                                                 $return_array[$cache[$field]] = $this->$field;
4559                                         }
4560                                         //end bug 21672
4561 // tjy: no need to do this str_replace as the changes in 29994 for ListViewGeneric.tpl for translation handle this now
4562 //                              }elseif(!empty($value['type']) && $value['type'] == 'multienum'&& empty($value['function'])){
4563 //                                      $return_array[strtoupper($field)] = str_replace('^,^', ', ', $this->$field );
4564                                 }elseif(!empty($value['custom_module']) && $value['type'] != 'currency'){
4565 //                                      $this->format_field($value);
4566                                         $return_array[$cache[$field]] = $this->$field;
4567                                 }else{
4568                                         $return_array[$cache[$field]] = $this->$field;
4569                                 }
4570                                 // handle "Assigned User Name"
4571                                 if($field == 'assigned_user_name'){
4572                                         $return_array['ASSIGNED_USER_NAME'] = get_assigned_user_name($this->assigned_user_id);
4573                                 }
4574                         }
4575                 }
4576                 return $return_array;
4577         }
4578     /**
4579      * Override this function to set values in the array used to render list view data.
4580      *
4581      */
4582     function get_list_view_data()
4583     {
4584         return $this->get_list_view_array();
4585     }
4586
4587     /**
4588      * Construct where clause from a list of name-value pairs.
4589      *
4590      */
4591     function get_where(&$fields_array)
4592     {
4593         $where_clause = "WHERE ";
4594         $first = 1;
4595         foreach ($fields_array as $name=>$value)
4596         {
4597                 if ($first)
4598                 {
4599                         $first = 0;
4600                 }
4601                 else
4602                 {
4603                         $where_clause .= " AND ";
4604                 }
4605
4606                 $where_clause .= "$name = '".$this->db->quote($value,false)."'";
4607         }
4608         $where_clause .= " AND deleted=0";
4609         return $where_clause;
4610     }
4611
4612
4613     /**
4614      * Constructs a select query and fetch 1 row using this query, and then process the row
4615      *
4616      * Internal function, do not override.
4617      * @param array @fields_array  array of name value pairs used to construct query.
4618      * @param boolean $encode Optional, default true, encode fetched data.
4619      * @return object Instance of this bean with fetched data.
4620      */
4621     function retrieve_by_string_fields($fields_array, $encode=true)
4622     {
4623         $where_clause = $this->get_where($fields_array);
4624         if(isset($this->custom_fields))
4625         $custom_join = $this->custom_fields->getJOIN();
4626         else $custom_join = false;
4627         if($custom_join)
4628         {
4629                 $query = "SELECT $this->table_name.*". $custom_join['select']. " FROM $this->table_name " . $custom_join['join'];
4630         }
4631         else
4632         {
4633                 $query = "SELECT $this->table_name.* FROM $this->table_name ";
4634         }
4635         $query .= " $where_clause";
4636         $GLOBALS['log']->debug("Retrieve $this->object_name: ".$query);
4637         //requireSingleResult has beeen deprecated.
4638         //$result = $this->db->requireSingleResult($query, true, "Retrieving record $where_clause:");
4639         $result = $this->db->limitQuery($query,0,1,true, "Retrieving record $where_clause:");
4640
4641
4642         if( empty($result))
4643         {
4644                 return null;
4645         }
4646         if($this->db->getRowCount($result) > 1)
4647         {
4648                 $this->duplicates_found = true;
4649         }
4650         $row = $this->db->fetchByAssoc($result, -1, $encode);
4651         if(empty($row))
4652         {
4653                 return null;
4654         }
4655         $this->fromArray($row);
4656         $this->fill_in_additional_detail_fields();
4657         return $this;
4658     }
4659
4660     /**
4661     * This method is called during an import before inserting a bean
4662         * Define an associative array called $special_fields
4663         * the keys are user defined, and don't directly map to the bean's fields
4664         * the value is the method name within that bean that will do extra
4665         * processing for that field. example: 'full_name'=>'get_names_from_full_name'
4666     *
4667     */
4668     function process_special_fields()
4669     {
4670         foreach ($this->special_functions as $func_name)
4671         {
4672                 if ( method_exists($this,$func_name) )
4673                 {
4674                         $this->$func_name();
4675                 }
4676         }
4677     }
4678
4679     /**
4680      * Override this function to build a where clause based on the search criteria set into bean .
4681      * @abstract
4682      */
4683     function build_generic_where_clause($value)
4684     {
4685     }
4686
4687         function getRelatedFields($module, $id, $fields, $return_array = false){
4688                 if(empty($GLOBALS['beanList'][$module]))return '';
4689                 $object = $GLOBALS['beanList'][$module];
4690                 if ($object == 'aCase') {
4691                         $object = 'Case';
4692                 }
4693
4694                 VardefManager::loadVardef($module, $object);
4695                 if(empty($GLOBALS['dictionary'][$object]['table']))return '';
4696                 $table = $GLOBALS['dictionary'][$object]['table'];
4697                 $query  = 'SELECT id';
4698                 foreach($fields as $field=>$alias){
4699                         if(!empty($GLOBALS['dictionary'][$object]['fields'][$field]['db_concat_fields'])){
4700                                 $query .= ' ,' .db_concat($table, $GLOBALS['dictionary'][$object]['fields'][$field]['db_concat_fields']) .  ' as ' . $alias ;
4701                         }else if(!empty($GLOBALS['dictionary'][$object]['fields'][$field]) &&
4702                                 (empty($GLOBALS['dictionary'][$object]['fields'][$field]['source']) ||
4703                                 $GLOBALS['dictionary'][$object]['fields'][$field]['source'] != "non-db"))
4704                         {
4705                                 $query .= ' ,' .$table . '.' . $field . ' as ' . $alias;
4706                         }
4707                         if(!$return_array)$this->$alias = '';
4708                 }
4709                 if($query == 'SELECT id' || empty($id)){
4710                         return '';
4711                 }
4712
4713
4714                 if(isset($GLOBALS['dictionary'][$object]['fields']['assigned_user_id']))
4715         {
4716                 $query .= " , ".        $table  . ".assigned_user_id owner";
4717
4718         }
4719         else if(isset($GLOBALS['dictionary'][$object]['fields']['created_by']))
4720         {
4721                 $query .= " , ".        $table . ".created_by owner";
4722
4723         }
4724                 $query .=  ' FROM ' . $table . ' WHERE deleted=0 AND id=';
4725         $result = $GLOBALS['db']->query($query . "'$id'" );
4726                 $row = $GLOBALS['db']->fetchByAssoc($result);
4727                 if($return_array){
4728                         return $row;
4729                 }
4730                 $owner = (empty($row['owner']))?'':$row['owner'];
4731                 foreach($fields as $alias){
4732                         $this->$alias = (!empty($row[$alias]))? $row[$alias]: '';
4733                         $alias = $alias  .'_owner';
4734                         $this->$alias = $owner;
4735                         $a_mod = $alias  .'_mod';
4736                         $this->$a_mod = $module;
4737                 }
4738
4739
4740         }
4741
4742
4743         function &parse_additional_headers(&$list_form, $xTemplateSection)
4744         {
4745                 return $list_form;
4746         }
4747
4748         function assign_display_fields($currentModule)
4749         {
4750                 global $timedate;
4751                 foreach($this->column_fields as $field)
4752                 {
4753                         if(isset($this->field_name_map[$field]) && empty($this->$field))
4754                         {
4755                                 if($this->field_name_map[$field]['type'] != 'date' && $this->field_name_map[$field]['type'] != 'enum')
4756                                 $this->$field = $field;
4757                                 if($this->field_name_map[$field]['type'] == 'date')
4758                                 {
4759                                         $this->$field = $timedate->to_display_date('1980-07-09');
4760                                 }
4761                                 if($this->field_name_map[$field]['type'] == 'enum')
4762                                 {
4763                                         $dom = $this->field_name_map[$field]['options'];
4764                                         global $current_language, $app_list_strings;
4765                                         $mod_strings = return_module_language($current_language, $currentModule);
4766
4767                                         if(isset($mod_strings[$dom]))
4768                                         {
4769                                                 $options = $mod_strings[$dom];
4770                                                 foreach($options as $key=>$value)
4771                                                 {
4772                                                         if(!empty($key) && empty($this->$field ))
4773                                                         {
4774                                                                 $this->$field = $key;
4775                                                         }
4776                                                 }
4777                                         }
4778                                         if(isset($app_list_strings[$dom]))
4779                                         {
4780                                                 $options = $app_list_strings[$dom];
4781                                                 foreach($options as $key=>$value)
4782                                                 {
4783                                                         if(!empty($key) && empty($this->$field ))
4784                                                         {
4785                                                                 $this->$field = $key;
4786                                                         }
4787                                                 }
4788                                         }
4789
4790
4791                                 }
4792                         }
4793                 }
4794         }
4795
4796         /*
4797          *      RELATIONSHIP HANDLING
4798          */
4799
4800         function set_relationship($table, $relate_values, $check_duplicates = true,$do_update=false,$data_values=null)
4801         {
4802                 $where = '';
4803
4804                 // make sure there is a date modified
4805                 $date_modified = db_convert("'".gmdate($GLOBALS['timedate']->get_db_date_time_format())."'", 'datetime');
4806
4807                 $row=null;
4808                 if($check_duplicates)
4809                 {
4810                         $query = "SELECT * FROM $table ";
4811                         $where = "WHERE deleted = '0'  ";
4812                         foreach($relate_values as $name=>$value)
4813                         {
4814                                 $where .= " AND $name = '$value' ";
4815                         }
4816                         $query .= $where;
4817                         $result = $this->db->query($query, false, "Looking For Duplicate Relationship:" . $query);
4818                         $row=$this->db->fetchByAssoc($result);
4819                 }
4820
4821                 if(!$check_duplicates || empty($row) )
4822                 {
4823                         unset($relate_values['id']);
4824                         if ( isset($data_values))
4825                         {
4826                                 $relate_values = array_merge($relate_values,$data_values);
4827                         }
4828                         $query = "INSERT INTO $table (id, ". implode(',', array_keys($relate_values)) . ", date_modified) VALUES ('" . create_guid() . "', " . "'" . implode("', '", $relate_values) . "', ".$date_modified.")" ;
4829
4830                         $this->db->query($query, false, "Creating Relationship:" . $query);
4831                 }
4832                 else if ($do_update)
4833                 {
4834                         $conds = array();
4835                         foreach($data_values as $key=>$value)
4836                         {
4837                                 array_push($conds,$key."='".$this->db->quote($value)."'");
4838                         }
4839                         $query = "UPDATE $table SET ". implode(',', $conds).",date_modified=".$date_modified." ".$where;
4840                         $this->db->query($query, false, "Updating Relationship:" . $query);
4841                 }
4842         }
4843
4844         function retrieve_relationships($table, $values, $select_id)
4845         {
4846                 $query = "SELECT $select_id FROM $table WHERE deleted = 0  ";
4847                 foreach($values as $name=>$value)
4848                 {
4849                         $query .= " AND $name = '$value' ";
4850                 }
4851                 $query .= " ORDER BY $select_id ";
4852                 $result = $this->db->query($query, false, "Retrieving Relationship:" . $query);
4853                 $ids = array();
4854                 while($row = $this->db->fetchByAssoc($result))
4855                 {
4856                         $ids[] = $row;
4857                 }
4858                 return $ids;
4859         }
4860
4861         // TODO: this function needs adjustment
4862         function loadLayoutDefs()
4863         {
4864                 global $layout_defs;
4865                 if(empty( $this->layout_def) && file_exists('modules/'. $this->module_dir . '/layout_defs.php'))
4866                 {
4867                         include_once('modules/'. $this->module_dir . '/layout_defs.php');
4868                         if(file_exists('custom/modules/'. $this->module_dir . '/Ext/Layoutdefs/layoutdefs.ext.php'))
4869                         {
4870                                 include_once('custom/modules/'. $this->module_dir . '/Ext/Layoutdefs/layoutdefs.ext.php');
4871                         }
4872                         if ( empty( $layout_defs[get_class($this)]))
4873                         {
4874                                 echo "\$layout_defs[" . get_class($this) . "]; does not exist";
4875                         }
4876
4877                         $this->layout_def = $layout_defs[get_class($this)];
4878                 }
4879         }
4880
4881         /**
4882          * Trigger custom logic for this module that is defined for the provided hook
4883          * The custom logic file is located under custom/modules/[CURRENT_MODULE]/logic_hooks.php.
4884          * That file should define the $hook_version that should be used.
4885          * It should also define the $hook_array.  The $hook_array will be a two dimensional array
4886          * the first dimension is the name of the event, the second dimension is the information needed
4887          * to fire the hook.  Each entry in the top level array should be defined on a single line to make it
4888          * easier to automatically replace this file.  There should be no contents of this file that are not replacable.
4889          *
4890          * $hook_array['before_save'][] = Array(1, testtype, 'custom/modules/Leads/test12.php', 'TestClass', 'lead_before_save_1');
4891          * This sample line creates a before_save hook.  The hooks are procesed in the order in which they
4892          * are added to the array.  The second dimension is an array of:
4893          *              processing index (for sorting before exporting the array)
4894          *              A logic type hook
4895          *              label/type
4896          *              php file to include
4897          *              php class the method is in
4898          *              php method to call
4899          *
4900          * The method signature for version 1 hooks is:
4901          * function NAME(&$bean, $event, $arguments)
4902          *              $bean - $this bean passed in by reference.
4903          *              $event - The string for the current event (i.e. before_save)
4904          *              $arguments - An array of arguments that are specific to the event.
4905          */
4906         function call_custom_logic($event, $arguments = null)
4907         {
4908                 if(!isset($this->processed) || $this->processed == false){
4909                         //add some logic to ensure we do not get into an infinite loop
4910                         if(!empty($this->logicHookDepth[$event])) {
4911                                 if($this->logicHookDepth[$event] > 10)
4912                                         return;
4913                         }else
4914                                 $this->logicHookDepth[$event] = 0;
4915
4916                         //we have to put the increment operator here
4917                         //otherwise we may never increase the depth for that event in the case
4918                         //where one event will trigger another as in the case of before_save and after_save
4919                         //Also keeping the depth per event allow any number of hooks to be called on the bean
4920                         //and we only will return if one event gets caught in a loop. We do not increment globally
4921                         //for each event called.
4922                         $this->logicHookDepth[$event]++;
4923
4924                         //method defined in 'include/utils/LogicHook.php'
4925
4926                         $logicHook = new LogicHook();
4927                         $logicHook->setBean($this);
4928                         $logicHook->call_custom_logic($this->module_dir, $event, $arguments);
4929                 }
4930         }
4931
4932
4933     /*  When creating a custom field of type Dropdown, it creates an enum row in the DB.
4934      A typical get_list_view_array() result will have the *KEY* value from that drop-down.
4935      Since custom _dom objects are flat-files included in the $app_list_strings variable,
4936      We need to generate a key-key pair to get the true value like so:
4937      ([module]_cstm->fields_meta_data->$app_list_strings->*VALUE*)*/
4938     function getRealKeyFromCustomFieldAssignedKey($name)
4939     {
4940         if ($this->custom_fields->avail_fields[$name]['ext1'])
4941         {
4942                 $realKey = 'ext1';
4943         }
4944         elseif ($this->custom_fields->avail_fields[$name]['ext2'])
4945         {
4946                 $realKey = 'ext2';
4947         }
4948         elseif ($this->custom_fields->avail_fields[$name]['ext3'])
4949         {
4950                 $realKey = 'ext3';
4951         }
4952         else
4953         {
4954                 $GLOBALS['log']->fatal("SUGARBEAN: cannot find Real Key for custom field of type dropdown - cannot return Value.");
4955                 return false;
4956         }
4957         if(isset($realKey))
4958         {
4959                 return $this->custom_fields->avail_fields[$name][$realKey];
4960         }
4961     }
4962
4963     function bean_implements($interface)
4964     {
4965         return false;
4966     }
4967         /**
4968          * Check whether the user has access to a particular view for the current bean/module
4969          * @param $view string required, the view to determine access for i.e. DetailView, ListView...
4970          * @param $is_owner bool optional, this is part of the ACL check if the current user is an owner they will receive different access
4971          */
4972     function ACLAccess($view,$is_owner='not_set')
4973     {
4974         global $current_user;
4975                 if(is_admin($current_user))return true;
4976         $not_set = false;
4977         if($is_owner == 'not_set')
4978         {
4979                 $not_set = true;
4980                 $is_owner = $this->isOwner($current_user->id);
4981         }
4982
4983         //if we don't implent acls return true
4984         if(!$this->bean_implements('ACL'))
4985         return true;
4986                 $view = strtolower($view);
4987         switch ($view)
4988         {
4989                 case 'list':
4990                 case 'index':
4991                 case 'listview':
4992                         return ACLController::checkAccess($this->module_dir,'list', true);
4993                 case 'edit':
4994                 case 'save':
4995                         if( !$is_owner && $not_set && !empty($this->id)){
4996                                 $class = get_class($this);
4997                                 $temp = new $class();
4998                                 if(!empty($this->fetched_row) && !empty($this->fetched_row['id']) && !empty($this->fetched_row['assigned_user_id']) && !empty($this->fetched_row['created_by'])){
4999                                         $temp->populateFromRow($this->fetched_row);
5000                                 }else{
5001                                         $temp->retrieve($this->id);
5002                                 }
5003                                 $is_owner = $temp->isOwner($current_user->id);
5004                         }
5005                 case 'popupeditview':
5006                 case 'editview':
5007                         return ACLController::checkAccess($this->module_dir,'edit', $is_owner, $this->acltype);
5008                 case 'view':
5009                 case 'detail':
5010                 case 'detailview':
5011                         return ACLController::checkAccess($this->module_dir,'view', $is_owner, $this->acltype);
5012                 case 'delete':
5013                         return ACLController::checkAccess($this->module_dir,'delete', $is_owner, $this->acltype);
5014                 case 'export':
5015                         return ACLController::checkAccess($this->module_dir,'export', $is_owner, $this->acltype);
5016                 case 'import':
5017                         return ACLController::checkAccess($this->module_dir,'import', true, $this->acltype);
5018         }
5019         //if it is not one of the above views then it should be implemented on the page level
5020         return true;
5021     }
5022         /**
5023          * Returns true of false if the user_id passed is the owner
5024          *
5025          * @param GUID $user_id
5026          * @return boolean
5027          */
5028         function isOwner($user_id)
5029         {
5030                 //if we don't have an id we must be the owner as we are creating it
5031                 if(!isset($this->id))
5032                 {
5033                         return true;
5034                 }
5035                 //if there is an assigned_user that is the owner
5036                 if(isset($this->assigned_user_id))
5037                 {
5038                         if($this->assigned_user_id == $user_id) return true;
5039                         return false;
5040                 }
5041                 else
5042                 {
5043                         //other wise if there is a created_by that is the owner
5044                         if(isset($this->created_by) && $this->created_by == $user_id)
5045                         {
5046                                 return true;
5047                         }
5048                 }
5049                 return false;
5050         }
5051         /**
5052          * Gets there where statement for checking if a user is an owner
5053          *
5054          * @param GUID $user_id
5055          * @return STRING
5056          */
5057         function getOwnerWhere($user_id)
5058         {
5059                 if(isset($this->field_defs['assigned_user_id']))
5060                 {
5061                         return " $this->table_name.assigned_user_id ='$user_id' ";
5062                 }
5063                 if(isset($this->field_defs['created_by']))
5064                 {
5065                         return " $this->table_name.created_by ='$user_id' ";
5066                 }
5067                 return '';
5068         }
5069
5070         /**
5071          *
5072          * Used in order to manage ListView links and if they should
5073          * links or not based on the ACL permissions of the user
5074          *
5075          * @return ARRAY of STRINGS
5076          */
5077         function listviewACLHelper()
5078         {
5079                 $array_assign = array();
5080                 if($this->ACLAccess('DetailView'))
5081                 {
5082                         $array_assign['MAIN'] = 'a';
5083                 }
5084                 else
5085                 {
5086                         $array_assign['MAIN'] = 'span';
5087                 }
5088                 return $array_assign;
5089         }
5090
5091         /**
5092          * returns this bean as an array
5093          *
5094          * @return array of fields with id, name, access and category
5095          */
5096         function toArray($dbOnly = false, $stringOnly = false, $upperKeys=false)
5097         {
5098         static $cache = array();
5099                 $arr = array();
5100
5101                 foreach($this->field_defs as $field=>$data)
5102                 {
5103                         if( !$dbOnly || !isset($data['source']) || $data['source'] == 'db')
5104                         if(!$stringOnly || is_string($this->$field))
5105                         if($upperKeys)
5106                         {
5107                                 if(!isset($cache[$field])){
5108                                     $cache[$field] = strtoupper($field);
5109                                 }
5110                                 $arr[$cache[$field]] = $this->$field;
5111                         }
5112                         else
5113                         {
5114                                 if(isset($this->$field)){
5115                                         $arr[$field] = $this->$field;
5116                                 }else{
5117                                         $arr[$field] = '';
5118                                 }
5119                         }
5120                 }
5121                 return $arr;
5122         }
5123
5124         /**
5125          * Converts an array into an acl mapping name value pairs into files
5126          *
5127          * @param Array $arr
5128          */
5129         function fromArray($arr)
5130         {
5131                 foreach($arr as $name=>$value)
5132                 {
5133                         $this->$name = $value;
5134                 }
5135         }
5136
5137     /**
5138      * Loads a row of data into instance of a bean. The data is passed as an array to this function
5139      *
5140      * @param array $arr row of data fetched from the database.
5141      * @return  nothing
5142      *
5143      * Internal function do not override.
5144      */
5145     function loadFromRow($arr)
5146     {
5147         $this->populateFromRow($arr);
5148         $this->processed_dates_times = array();
5149         $this->check_date_relationships_load();
5150
5151         $this->fill_in_additional_list_fields();
5152
5153         if($this->hasCustomFields())$this->custom_fields->fill_relationships();
5154         $this->call_custom_logic("process_record");
5155     }
5156
5157     function hasCustomFields(){
5158         return !empty($GLOBALS['dictionary'][$this->object_name]['custom_fields']);
5159     }
5160
5161    /**
5162     * Ensure that fields within order by clauses are properly qualified with
5163     * their tablename.  This qualification is a requirement for sql server support.
5164     *
5165     * @param string $order_by original order by from the query
5166     * @param string $qualify prefix for columns in the order by list.
5167     * @return  prefixed
5168     *
5169     * Internal function do not override.
5170     */
5171    function create_qualified_order_by( $order_by, $qualify)
5172    {    // 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
5173         if (empty($order_by))
5174         {
5175                 return $order_by;
5176         }
5177         $order_by_clause = " ORDER BY ";
5178         $tmp = explode(",", $order_by);
5179         $comma = ' ';
5180         foreach ( $tmp as $stmp)
5181         {
5182                 $stmp = (substr_count($stmp, ".") > 0?trim($stmp):"$qualify." . trim($stmp));
5183                 $order_by_clause .= $comma . $stmp;
5184                 $comma = ", ";
5185         }
5186         return $order_by_clause;
5187    }
5188
5189    /**
5190     * Combined the contents of street field 2 thru 4 into the main field
5191     *
5192     * @param string $street_field
5193     */
5194
5195    function add_address_streets(
5196        $street_field
5197        )
5198         {
5199         $street_field_2 = $street_field.'_2';
5200         $street_field_3 = $street_field.'_3';
5201         $street_field_4 = $street_field.'_4';
5202                 if ( isset($this->$street_field_2)) {
5203                         $this->$street_field .= "\n". $this->$street_field_2;
5204                         unset($this->$street_field_2);
5205                 }
5206                 if ( isset($this->$street_field_3)) {
5207                         $this->$street_field .= "\n". $this->$street_field_3;
5208                         unset($this->$street_field_3);
5209                 }
5210         if ( isset($this->$street_field_4)) {
5211                         $this->$street_field .= "\n". $this->$street_field_4;
5212                         unset($this->$street_field_4);
5213                 }
5214                 if ( isset($this->$street_field)) {
5215                     $this->$street_field = trim($this->$street_field, "\n");
5216                 }
5217         }
5218 /**
5219  * Encrpyt and base64 encode an 'encrypt' field type in the bean using Blowfish. The default system key is stored in cache/Blowfish/{keytype}
5220  * @param STRING value -plain text value of the bean field.
5221  * @return string
5222  */
5223         function encrpyt_before_save($value)
5224         {
5225                 require_once("include/utils/encryption_utils.php");
5226                 return blowfishEncode(blowfishGetKey('encrypt_field'),$value);
5227         }
5228
5229 /**
5230  * Decode and decrypt a base 64 encoded string with field type 'encrypt' in this bean using Blowfish.
5231  * @param STRING value - an encrypted and base 64 encoded string.
5232  * @return string
5233  */
5234         function decrypt_after_retrieve($value)
5235         {
5236                 require_once("include/utils/encryption_utils.php");
5237                 return blowfishDecode(blowfishGetKey('encrypt_field'), $value);
5238         }
5239
5240         /**
5241          * Moved from save() method, functionality is the same, but this is intended to handle
5242          * Optimistic locking functionality.
5243          */
5244         private function _checkOptimisticLocking($action, $isUpdate){
5245                 if($this->optimistic_lock && !isset($_SESSION['o_lock_fs'])){
5246                         if(isset($_SESSION['o_lock_id']) && $_SESSION['o_lock_id'] == $this->id && $_SESSION['o_lock_on'] == $this->object_name)
5247                         {
5248                                 if($action == 'Save' && $isUpdate && isset($this->modified_user_id) && $this->has_been_modified_since($_SESSION['o_lock_dm'], $this->modified_user_id))
5249                                 {
5250                                         $_SESSION['o_lock_class'] = get_class($this);
5251                                         $_SESSION['o_lock_module'] = $this->module_dir;
5252                                         $_SESSION['o_lock_object'] = $this->toArray();
5253                                         $saveform = "<form name='save' id='save' method='POST'>";
5254                                         foreach($_POST as $key=>$arg)
5255                                         {
5256                                                 $saveform .= "<input type='hidden' name='". addslashes($key) ."' value='". addslashes($arg) ."'>";
5257                                         }
5258                                         $saveform .= "</form><script>document.getElementById('save').submit();</script>";
5259                                         $_SESSION['o_lock_save'] = $saveform;
5260                                         header('Location: index.php?module=OptimisticLock&action=LockResolve');
5261                                         die();
5262                                 }
5263                                 else
5264                                 {
5265                                         unset ($_SESSION['o_lock_object']);
5266                                         unset ($_SESSION['o_lock_id']);
5267                                         unset ($_SESSION['o_lock_dm']);
5268                                 }
5269                         }
5270                 }
5271                 else
5272                 {
5273                         if(isset($_SESSION['o_lock_object']))   { unset ($_SESSION['o_lock_object']); }
5274                         if(isset($_SESSION['o_lock_id']))               { unset ($_SESSION['o_lock_id']); }
5275                         if(isset($_SESSION['o_lock_dm']))               { unset ($_SESSION['o_lock_dm']); }
5276                         if(isset($_SESSION['o_lock_fs']))               { unset ($_SESSION['o_lock_fs']); }
5277                         if(isset($_SESSION['o_lock_save']))             { unset ($_SESSION['o_lock_save']); }
5278                 }
5279         }
5280
5281         /**
5282          * Send assignment notifications and invites for meetings and calls
5283          */
5284         private function _sendNotifications($check_notify){
5285                 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.
5286
5287                         $admin = new Administration();
5288                         $admin->retrieveSettings();
5289                         $sendNotifications = false;
5290
5291                         if ($admin->settings['notify_on'])
5292                         {
5293                                 $GLOBALS['log']->info("Notifications: user assignment has changed, checking if user receives notifications");
5294                                 $sendNotifications = true;
5295                         }
5296                         elseif(isset($_REQUEST['send_invites']) && $_REQUEST['send_invites'] == 1)
5297                         {
5298                                 // cn: bug 5795 Send Invites failing for Contacts
5299                                 $sendNotifications = true;
5300                         }
5301                         else
5302                         {
5303                                 $GLOBALS['log']->info("Notifications: not sending e-mail, notify_on is set to OFF");
5304                         }
5305
5306
5307                         if($sendNotifications == true)
5308                         {
5309                                 $notify_list = $this->get_notification_recipients();
5310                                 foreach ($notify_list as $notify_user)
5311                                 {
5312                                         $this->send_assignment_notifications($notify_user, $admin);
5313                                 }
5314                         }
5315                 }
5316         }
5317
5318
5319     /**
5320      * Called from ImportFieldSanitize::relate(), when creating a new bean in a related module. Will
5321      * copies fields over from the current bean into the related. Designed to be overriden in child classes.
5322      *
5323      * @param SugarBean $newbean newly created related bean
5324      */
5325     public function populateRelatedBean(
5326         SugarBean $newbean
5327         )
5328     {
5329     }
5330
5331     /**
5332      * Called during the import process before a bean save, to handle any needed pre-save logic when
5333      * importing a record
5334      */
5335     public function beforeImportSave()
5336     {
5337     }
5338
5339     /**
5340      * Called during the import process after a bean save, to handle any needed post-save logic when
5341      * importing a record
5342      */
5343     public function afterImportSave()
5344     {
5345     }
5346
5347     /**
5348      * This function is designed to cache references to field arrays that were previously stored in the
5349      * bean files and have since been moved to seperate files. Was previously in include/CacheHandler.php
5350      *
5351      * @deprecated
5352      * @param $module_dir string the module directory
5353      * @param $module string the name of the module
5354      * @param $key string the type of field array we are referencing, i.e. list_fields, column_fields, required_fields
5355      **/
5356         private function _loadCachedArray(
5357             $module_dir,
5358             $module,
5359             $key
5360             )
5361         {
5362         static $moduleDefs = array();
5363
5364         $fileName = 'field_arrays.php';
5365
5366         $cache_key = "load_cached_array.$module_dir.$module.$key";
5367         $result = sugar_cache_retrieve($cache_key);
5368         if(!empty($result))
5369         {
5370                 // Use EXTERNAL_CACHE_NULL_VALUE to store null values in the cache.
5371                 if($result == EXTERNAL_CACHE_NULL_VALUE)
5372                 {
5373                         return null;
5374                 }
5375
5376                 return $result;
5377         }
5378
5379         if(file_exists('modules/'.$module_dir.'/'.$fileName))
5380         {
5381             // If the data was not loaded, try loading again....
5382             if(!isset($moduleDefs[$module]))
5383             {
5384                 include('modules/'.$module_dir.'/'.$fileName);
5385                 $moduleDefs[$module] = $fields_array;
5386                     }
5387                     // Now that we have tried loading, make sure it was loaded
5388             if(empty($moduleDefs[$module]) || empty($moduleDefs[$module][$module][$key]))
5389             {
5390                 // It was not loaded....  Fail.  Cache null to prevent future repeats of this calculation
5391                                 sugar_cache_put($cache_key, EXTERNAL_CACHE_NULL_VALUE);
5392                 return  null;
5393             }
5394
5395             // It has been loaded, cache the result.
5396             sugar_cache_put($cache_key, $moduleDefs[$module][$module][$key]);
5397             return $moduleDefs[$module][$module][$key];
5398         }
5399
5400         // It was not loaded....  Fail.  Cache null to prevent future repeats of this calculation
5401         sugar_cache_put($cache_key, EXTERNAL_CACHE_NULL_VALUE);
5402                 return null;
5403         }
5404 }