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