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