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