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