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