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