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