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