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