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