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