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