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