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