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