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