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