Completed
Push — master ( a75ecc...97ae17 )
by Adam
71:21 queued 52:34
created

SugarBean::get_relationship_field()   B

Complexity

Conditions 6
Paths 3

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 13
rs 8.8571
cc 6
eloc 7
nc 3
nop 1
1
<?php
2
if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3
/*********************************************************************************
4
 * SugarCRM Community Edition is a customer relationship management program developed by
5
 * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
6
7
 * SuiteCRM is an extension to SugarCRM Community Edition developed by Salesagility Ltd.
8
 * Copyright (C) 2011 - 2014 Salesagility Ltd.
9
 *
10
 * This program is free software; you can redistribute it and/or modify it under
11
 * the terms of the GNU Affero General Public License version 3 as published by the
12
 * Free Software Foundation with the addition of the following permission added
13
 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
14
 * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
15
 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
16
 *
17
 * This program is distributed in the hope that it will be useful, but WITHOUT
18
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19
 * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
20
 * details.
21
 *
22
 * You should have received a copy of the GNU Affero General Public License along with
23
 * this program; if not, see http://www.gnu.org/licenses or write to the Free
24
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
25
 * 02110-1301 USA.
26
 *
27
 * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
28
 * SW2-130, Cupertino, CA 95014, USA. or at email address [email protected].
29
 *
30
 * The interactive user interfaces in modified source and object code versions
31
 * of this program must display Appropriate Legal Notices, as required under
32
 * Section 5 of the GNU Affero General Public License version 3.
33
 *
34
 * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
35
 * these Appropriate Legal Notices must retain the display of the "Powered by
36
 * SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not
37
 * reasonably feasible for  technical reasons, the Appropriate Legal Notices must
38
 * display the words  "Powered by SugarCRM" and "Supercharged by SuiteCRM".
39
 ********************************************************************************/
40
41
/*********************************************************************************
42
43
 * Description:  Defines the base class for all data entities used throughout the
44
 * application.  The base class including its methods and variables is designed to
45
 * be overloaded with module-specific methods and variables particular to the
46
 * module's base entity class.
47
 * Portions created by SugarCRM are Copyright (C) SugarCRM, Inc.
48
 * All Rights Reserved.
49
 *******************************************************************************/
50
51
require_once('modules/DynamicFields/DynamicField.php');
52
require_once("data/Relationships/RelationshipFactory.php");
53
54
55
56
57
58
/**
59
 * SugarBean is the base class for all business objects in Sugar.  It implements
60
 * the primary functionality needed for manipulating business objects: create,
61
 * retrieve, update, delete.  It allows for searching and retrieving list of records.
62
 * It allows for retrieving related objects (e.g. contacts related to a specific account).
63
 *
64
 * In the current implementation, there can only be one bean per folder.
65
 * Naming convention has the bean name be the same as the module and folder name.
66
 * All bean names should be singular (e.g. Contact).  The primary table name for
67
 * a bean should be plural (e.g. contacts).
68
 * @api
69
 */
70
class SugarBean
71
{
72
    /**
73
     * A pointer to the database object
74
     *
75
     * @var DBManager
76
     */
77
    var $db;
78
79
    /**
80
     * Unique object identifier
81
     *
82
     * @var string
83
     */
84
    public $id;
85
86
    /**
87
	 * When createing a bean, you can specify a value in the id column as
88
	 * long as that value is unique.  During save, if the system finds an
89
	 * id, it assumes it is an update.  Setting new_with_id to true will
90
	 * make sure the system performs an insert instead of an update.
91
	 *
92
	 * @var BOOL -- default false
93
	 */
94
	var $new_with_id = false;
95
96
97
	/**
98
	 * How deep logic hooks can go
99
	 * @var int
100
	 */
101
	protected $max_logic_depth = 10;
102
103
	/**
104
	 * Disble vardefs.  This should be set to true only for beans that do not have varders.  Tracker is an example
105
	 *
106
	 * @var BOOL -- default false
107
	 */
108
    var $disable_vardefs = false;
109
110
111
    /**
112
     * holds the full name of the user that an item is assigned to.  Only used if notifications
113
     * are turned on and going to be sent out.
114
     *
115
     * @var String
116
     */
117
    var $new_assigned_user_name;
118
119
	/**
120
	 * An array of booleans.  This array is cleared out when data is loaded.
121
	 * As date/times are converted, a "1" is placed under the key, the field is converted.
122
	 *
123
	 * @var Array of booleans
124
	 */
125
	var $processed_dates_times = array();
126
127
	/**
128
	 * Whether to process date/time fields for storage in the database in GMT
129
	 *
130
	 * @var BOOL
131
	 */
132
	var $process_save_dates =true;
133
134
    /**
135
     * This signals to the bean that it is being saved in a mass mode.
136
     * Examples of this kind of save are import and mass update.
137
     * We turn off notificaitons of this is the case to make things more efficient.
138
     *
139
     * @var BOOL
140
     */
141
    var $save_from_post = true;
142
143
	/**
144
	 * When running a query on related items using the method: retrieve_by_string_fields
145
	 * this value will be set to true if more than one item matches the search criteria.
146
	 *
147
	 * @var BOOL
148
	 */
149
	var $duplicates_found = false;
150
151
	/**
152
	 * true if this bean has been deleted, false otherwise.
153
	 *
154
	 * @var BOOL
155
	 */
156
	var $deleted = 0;
157
158
    /**
159
     * Should the date modified column of the bean be updated during save?
160
     * This is used for admin level functionality that should not be updating
161
     * the date modified.  This is only used by sync to allow for updates to be
162
     * replicated in a way that will not cause them to be replicated back.
163
     *
164
     * @var BOOL
165
     */
166
    var $update_date_modified = true;
167
168
    /**
169
     * Should the modified by column of the bean be updated during save?
170
     * This is used for admin level functionality that should not be updating
171
     * the modified by column.  This is only used by sync to allow for updates to be
172
     * replicated in a way that will not cause them to be replicated back.
173
     *
174
     * @var BOOL
175
     */
176
    var $update_modified_by = true;
177
178
    /**
179
     * Setting this to true allows for updates to overwrite the date_entered
180
     *
181
     * @var BOOL
182
     */
183
    var $update_date_entered = false;
184
185
    /**
186
     * This allows for seed data to be created without using the current uesr to set the id.
187
     * This should be replaced by altering the current user before the call to save.
188
     *
189
     * @var unknown_type
190
     */
191
    //TODO This should be replaced by altering the current user before the call to save.
192
    var $set_created_by = true;
193
194
    var $team_set_id;
195
196
    /**
197
     * The database table where records of this Bean are stored.
198
     *
199
     * @var String
200
     */
201
    var $table_name = '';
202
203
    /**
204
    * This is the singular name of the bean.  (i.e. Contact).
205
    *
206
    * @var String
207
    */
208
    var $object_name = '';
209
210
    /** Set this to true if you query contains a sub-select and bean is converting both select statements
211
    * into count queries.
212
    */
213
    var $ungreedy_count=false;
214
215
    /**
216
    * The name of the module folder for this type of bean.
217
    *
218
    * @var String
219
    */
220
    var $module_dir = '';
221
    var $module_name = '';
222
    var $field_name_map;
223
    var $field_defs;
224
    var $custom_fields;
225
    var $column_fields = array();
226
    var $list_fields = array();
227
    var $additional_column_fields = array();
228
    var $relationship_fields = array();
229
    var $current_notify_user;
230
    var $fetched_row=false;
231
    var $fetched_rel_row = array();
232
    var $layout_def;
233
    var $force_load_details = false;
234
    var $optimistic_lock = false;
235
    var $disable_custom_fields = false;
236
    var $number_formatting_done = false;
237
    var $process_field_encrypted=false;
238
    /*
239
    * The default ACL type
240
    */
241
    var $acltype = 'module';
242
243
244
    var $additional_meta_fields = array();
245
246
    /**
247
     * Set to true in the child beans if the module supports importing
248
     */
249
    var $importable = false;
250
251
    /**
252
    * Set to true in the child beans if the module use the special notification template
253
    */
254
    var $special_notification = false;
255
256
    /**
257
     * Set to true if the bean is being dealt with in a workflow
258
     */
259
    var $in_workflow = false;
260
261
    /**
262
     *
263
     * By default it will be true but if any module is to be kept non visible
264
     * to tracker, then its value needs to be overriden in that particular module to false.
265
     *
266
     */
267
    var $tracker_visibility = true;
268
269
    /**
270
     * Used to pass inner join string to ListView Data.
271
     */
272
    var $listview_inner_join = array();
273
274
    /**
275
     * Set to true in <modules>/Import/views/view.step4.php if a module is being imported
276
     */
277
    var $in_import = false;
278
    /**
279
     * A way to keep track of the loaded relationships so when we clone the object we can unset them.
280
     *
281
     * @var array
282
     */
283
    protected $loaded_relationships = array();
284
285
	/**
286
     * set to true if dependent fields updated
287
     */
288
    protected $is_updated_dependent_fields = false;
289
290
    /**
291
     * Blowfish encryption key
292
     * @var string
293
     */
294
    static protected $field_key;
295
296
    /**
297
     * Cache of fields which can contain files
298
     *
299
     * @var array
300
     */
301
    static protected $fileFields = array();
302
303
    /**
304
     * Constructor for the bean, it performs following tasks:
305
     *
306
     * 1. Initalized a database connections
307
     * 2. Load the vardefs for the module implemeting the class. cache the entries
308
     *    if needed
309
     * 3. Setup row-level security preference
310
     * All implementing classes  must call this constructor using the parent::SugarBean() class.
311
     *
312
     */
313
    function SugarBean()
314
    {
315
        global  $dictionary, $current_user;
316
        static $loaded_defs = array();
317
        $this->db = DBManagerFactory::getInstance();
318
        if (empty($this->module_name))
319
            $this->module_name = $this->module_dir;
320
        if((false == $this->disable_vardefs && empty($loaded_defs[$this->object_name])) || !empty($GLOBALS['reload_vardefs']))
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
321
        {
322
            VardefManager::loadVardef($this->module_dir, $this->object_name);
323
324
            // build $this->column_fields from the field_defs if they exist
325
            if (!empty($dictionary[$this->object_name]['fields'])) {
326
                foreach ($dictionary[$this->object_name]['fields'] as $key=>$value_array) {
327
                    $column_fields[] = $key;
328
                    if(!empty($value_array['required']) && !empty($value_array['name'])) {
329
                        $this->required_fields[$value_array['name']] = 1;
330
                    }
331
                }
332
                $this->column_fields = $column_fields;
333
            }
334
335
            //setup custom fields
336
            if(!isset($this->custom_fields) &&
337
                empty($this->disable_custom_fields))
338
            {
339
                $this->setupCustomFields($this->module_dir);
340
            }
341
            //load up field_arrays from CacheHandler;
342
            if(empty($this->list_fields))
343
                $this->list_fields = $this->_loadCachedArray($this->module_dir, $this->object_name, 'list_fields');
0 ignored issues
show
Deprecated Code introduced by
The method SugarBean::_loadCachedArray() has been deprecated.

This method has been deprecated.

Loading history...
344
            if(empty($this->column_fields))
345
                $this->column_fields = $this->_loadCachedArray($this->module_dir, $this->object_name, 'column_fields');
0 ignored issues
show
Deprecated Code introduced by
The method SugarBean::_loadCachedArray() has been deprecated.

This method has been deprecated.

Loading history...
346
            if(empty($this->required_fields))
347
                $this->required_fields = $this->_loadCachedArray($this->module_dir, $this->object_name, 'required_fields');
0 ignored issues
show
Deprecated Code introduced by
The method SugarBean::_loadCachedArray() has been deprecated.

This method has been deprecated.

Loading history...
348
349
            if(isset($GLOBALS['dictionary'][$this->object_name]) && !$this->disable_vardefs)
350
            {
351
                $this->field_name_map = $dictionary[$this->object_name]['fields'];
352
                $this->field_defs =	$dictionary[$this->object_name]['fields'];
353
354
                if(!empty($dictionary[$this->object_name]['optimistic_locking']))
355
                {
356
                    $this->optimistic_lock=true;
357
                }
358
            }
359
            $loaded_defs[$this->object_name]['column_fields'] =& $this->column_fields;
360
            $loaded_defs[$this->object_name]['list_fields'] =& $this->list_fields;
361
            $loaded_defs[$this->object_name]['required_fields'] =& $this->required_fields;
362
            $loaded_defs[$this->object_name]['field_name_map'] =& $this->field_name_map;
363
            $loaded_defs[$this->object_name]['field_defs'] =& $this->field_defs;
364
        }
365
        else
366
        {
367
            $this->column_fields =& $loaded_defs[$this->object_name]['column_fields'] ;
368
            $this->list_fields =& $loaded_defs[$this->object_name]['list_fields'];
369
            $this->required_fields =& $loaded_defs[$this->object_name]['required_fields'];
370
            $this->field_name_map =& $loaded_defs[$this->object_name]['field_name_map'];
371
            $this->field_defs =& $loaded_defs[$this->object_name]['field_defs'];
372
            $this->added_custom_field_defs = true;
373
374
            if(!isset($this->custom_fields) &&
375
                empty($this->disable_custom_fields))
376
            {
377
                $this->setupCustomFields($this->module_dir, false);
378
            }
379
            if(!empty($dictionary[$this->object_name]['optimistic_locking']))
380
            {
381
                $this->optimistic_lock=true;
382
            }
383
        }
384
385
        if($this->bean_implements('ACL') && !empty($GLOBALS['current_user'])){
386
            $this->acl_fields = (isset($dictionary[$this->object_name]['acl_fields']) && $dictionary[$this->object_name]['acl_fields'] === false)?false:true;
387
        }
388
        $this->populateDefaultValues();
389
    }
390
391
392
    /**
393
     * Returns the object name. If object_name is not set, table_name is returned.
394
     *
395
     * All implementing classes must set a value for the object_name variable.
396
     *
397
     * @param array $arr row of data fetched from the database.
0 ignored issues
show
Bug introduced by
There is no parameter named $arr. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
398
     * @return  nothing
399
     *
400
     */
401
    function getObjectName()
402
    {
403
        if ($this->object_name)
404
            return $this->object_name;
405
406
        // This is a quick way out. The generated metadata files have the table name
407
        // as the key. The correct way to do this is to override this function
408
        // in bean and return the object name. That requires changing all the beans
409
        // as well as put the object name in the generator.
410
        return $this->table_name;
411
    }
412
413
    /**
414
     * Returns a list of fields with their definitions that have the audited property set to true.
415
     * Before calling this function, check whether audit has been enabled for the table/module or not.
416
     * You would set the audit flag in the implemting module's vardef file.
417
     *
418
     * @return array
419
     * @see is_AuditEnabled
420
     *
421
     * Internal function, do not override.
422
     */
423
    function getAuditEnabledFieldDefinitions()
424
    {
425
        $aclcheck = $this->bean_implements('ACL');
426
        $is_owner = $this->isOwner($GLOBALS['current_user']->id);
427
        if (!isset($this->audit_enabled_fields))
428
        {
429
430
            $this->audit_enabled_fields=array();
431
            foreach ($this->field_defs as $field => $properties)
432
            {
433
434
                if (
435
                (
436
                !empty($properties['Audited']) || !empty($properties['audited']))
437
                )
438
                {
439
440
                    $this->audit_enabled_fields[$field]=$properties;
441
                }
442
            }
443
444
        }
445
        return $this->audit_enabled_fields;
446
    }
447
448
    /**
449
     * Return true if auditing is enabled for this object
450
     * You would set the audit flag in the implemting module's vardef file.
451
     *
452
     * @return boolean
453
     *
454
     * Internal function, do not override.
455
     */
456
    function is_AuditEnabled()
457
    {
458
        global $dictionary;
459
        if (isset($dictionary[$this->getObjectName()]['audited']))
460
        {
461
            return $dictionary[$this->getObjectName()]['audited'];
462
        }
463
        else
464
        {
465
            return false;
466
        }
467
    }
468
469
470
471
    /**
472
     * Returns the name of the audit table.
473
     * Audit table's name is based on implementing class' table name.
474
     *
475
     * @return String Audit table name.
476
     *
477
     * Internal function, do not override.
478
     */
479
    function get_audit_table_name()
480
    {
481
        return $this->getTableName().'_audit';
482
    }
483
484
    /**
485
     * Returns the name of the custom table.
486
     * Custom table's name is based on implementing class' table name.
487
     *
488
     * @return String Custom table name.
489
     *
490
     * Internal function, do not override.
491
     */
492
    public function get_custom_table_name()
493
    {
494
        return $this->getTableName().'_cstm';
495
    }
496
497
    /**
498
     * If auditing is enabled, create the audit table.
499
     *
500
     * Function is used by the install scripts and a repair utility in the admin panel.
501
     *
502
     * Internal function, do not override.
503
     */
504
    function create_audit_table()
505
    {
506
        global $dictionary;
507
        $table_name=$this->get_audit_table_name();
508
509
        require('metadata/audit_templateMetaData.php');
510
511
        // Bug: 52583 Need ability to customize template for audit tables
512
        $custom = 'custom/metadata/audit_templateMetaData_' . $this->getTableName() . '.php';
513
        if (file_exists($custom))
514
        {
515
            require($custom);
516
        }
517
518
        $fieldDefs = $dictionary['audit']['fields'];
519
        $indices = $dictionary['audit']['indices'];
520
521
        // Renaming template indexes to fit the particular audit table (removed the brittle hard coding)
522
        foreach($indices as $nr => $properties){
523
            $indices[$nr]['name'] = 'idx_' . strtolower($this->getTableName()) . '_' . $properties['name'];
524
        }
525
526
        $engine = null;
527
        if(isset($dictionary['audit']['engine'])) {
528
            $engine = $dictionary['audit']['engine'];
529
        } else if(isset($dictionary[$this->getObjectName()]['engine'])) {
530
            $engine = $dictionary[$this->getObjectName()]['engine'];
531
        }
532
533
        $this->db->createTableParams($table_name, $fieldDefs, $indices, $engine);
534
    }
535
536
    /**
537
     * Returns the implementing class' table name.
538
     *
539
     * All implementing classes set a value for the table_name variable. This value is returned as the
540
     * table name. If not set, table name is extracted from the implementing module's vardef.
541
     *
542
     * @return String Table name.
543
     *
544
     * Internal function, do not override.
545
     */
546
    public function getTableName()
547
    {
548
        if(isset($this->table_name))
549
        {
550
            return $this->table_name;
551
        }
552
        global $dictionary;
553
        return $dictionary[$this->getObjectName()]['table'];
554
    }
555
556
    /**
557
     * Returns field definitions for the implementing module.
558
     *
559
     * The definitions were loaded in the constructor.
560
     *
561
     * @return Array Field definitions.
562
     *
563
     * Internal function, do not override.
564
     */
565
    function getFieldDefinitions()
566
    {
567
        return $this->field_defs;
568
    }
569
570
    /**
571
     * Returns index definitions for the implementing module.
572
     *
573
     * The definitions were loaded in the constructor.
574
     *
575
     * @return Array Index definitions.
576
     *
577
     * Internal function, do not override.
578
     */
579
    function getIndices()
580
    {
581
        global $dictionary;
582
        if(isset($dictionary[$this->getObjectName()]['indices']))
583
        {
584
            return $dictionary[$this->getObjectName()]['indices'];
585
        }
586
        return array();
587
    }
588
589
    /**
590
     * Returns field definition for the requested field name.
591
     *
592
     * The definitions were loaded in the constructor.
593
     *
594
     * @param string field name,
595
     * @return Array Field properties or boolean false if the field doesn't exist
596
     *
597
     * Internal function, do not override.
598
     */
599
    function getFieldDefinition($name)
600
    {
601
        if ( !isset($this->field_defs[$name]) )
602
            return false;
603
604
        return $this->field_defs[$name];
605
    }
606
607
    /**
608
     * Returnss  definition for the id field name.
609
     *
610
     * The definitions were loaded in the constructor.
611
     *
612
     * @return Array Field properties.
613
     *
614
     * Internal function, do not override.
615
     */
616
    function getPrimaryFieldDefinition()
617
    {
618
        $def = $this->getFieldDefinition("id");
619
        if(empty($def)) {
620
            $def = $this->getFieldDefinition(0);
621
        }
622
        if (empty($def)) {
623
            $defs = $this->field_defs;
624
            reset($defs);
625
            $def = current($defs);
626
        }
627
        return $def;
628
    }
629
    /**
630
     * Returns the value for the requested field.
631
     *
632
     * When a row of data is fetched using the bean, all fields are created as variables in the context
633
     * of the bean and then fetched values are set in these variables.
634
     *
635
     * @param string field name,
636
     * @return varies Field value.
637
     *
638
     * Internal function, do not override.
639
     */
640
    function getFieldValue($name)
641
    {
642
        if (!isset($this->$name)){
643
            return FALSE;
644
        }
645
        if($this->$name === TRUE){
646
            return 1;
647
        }
648
        if($this->$name === FALSE){
649
            return 0;
650
        }
651
        return $this->$name;
652
    }
653
654
    /**
655
     * Basically undoes the effects of SugarBean::populateDefaultValues(); this method is best called right after object
656
     * initialization.
657
     */
658
    public function unPopulateDefaultValues()
659
    {
660
        if ( !is_array($this->field_defs) )
661
            return;
662
663
        foreach ($this->field_defs as $field => $value) {
664
		    if( !empty($this->$field)
665
                  && ((isset($value['default']) && $this->$field == $value['default']) || (!empty($value['display_default']) && $this->$field == $value['display_default']))
666
                    ) {
667
                $this->$field = null;
668
                continue;
669
            }
670
            if(!empty($this->$field) && !empty($value['display_default']) && in_array($value['type'], array('date', 'datetime', 'datetimecombo')) &&
671
            $this->$field == $this->parseDateDefault($value['display_default'], ($value['type'] != 'date'))) {
672
                $this->$field = null;
673
            }
674
        }
675
    }
676
677
    /**
678
     * Create date string from default value
679
     * like '+1 month'
680
     * @param string $value
681
     * @param bool $time Should be expect time set too?
682
     * @return string
683
     */
684
    protected function parseDateDefault($value, $time = false)
685
    {
686
        global $timedate;
687
        if($time) {
688
            $dtAry = explode('&', $value, 2);
689
            $dateValue = $timedate->getNow(true)->modify($dtAry[0]);
690
            if(!empty($dtAry[1])) {
691
                $timeValue = $timedate->fromString($dtAry[1]);
692
                $dateValue->setTime($timeValue->hour, $timeValue->min, $timeValue->sec);
693
            }
694
            return $timedate->asUser($dateValue);
695
        } else {
696
            return $timedate->asUserDate($timedate->getNow(true)->modify($value));
697
        }
698
    }
699
700
    function populateDefaultValues($force=false){
701
        if ( !is_array($this->field_defs) )
702
            return;
703
        foreach($this->field_defs as $field=>$value){
704
            if((isset($value['default']) || !empty($value['display_default'])) && ($force || empty($this->$field))){
705
                $type = $value['type'];
706
707
                switch($type){
708
                    case 'date':
709
                        if(!empty($value['display_default'])){
710
                            $this->$field = $this->parseDateDefault($value['display_default']);
711
                        }
712
                        break;
713
                   case 'datetime':
714
                   case 'datetimecombo':
715
                        if(!empty($value['display_default'])){
716
                            $this->$field = $this->parseDateDefault($value['display_default'], true);
717
                        }
718
                        break;
719
                    case 'multienum':
720
                        if(empty($value['default']) && !empty($value['display_default']))
721
                            $this->$field = $value['display_default'];
722
                        else
723
                            $this->$field = $value['default'];
724
                        break;
725
                    case 'bool':
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
726
                    	if(isset($this->$field)){
727
                    		break;
728
                    	}
729
                    default:
730
                        if ( isset($value['default']) && $value['default'] !== '' ) {
731
                            $this->$field = htmlentities($value['default'], ENT_QUOTES, 'UTF-8');
732
                        } else {
733
                            $this->$field = '';
734
                        }
735
                } //switch
736
            }
737
        } //foreach
738
    }
739
740
741
    /**
742
     * Removes relationship metadata cache.
743
     *
744
     * Every module that has relationships defined with other modules, has this meta data cached.  The cache is
745
     * stores in 2 locations: relationships table and file system. This method clears the cache from both locations.
746
     *
747
     * @param string $key  module whose meta cache is to be cleared.
748
     * @param string $db database handle.
749
     * @param string $tablename table name
750
     * @param string $dictionary vardef for the module
751
     * @param string $module_dir name of subdirectory where module is installed.
752
     *
753
     * @return Nothing
754
     * @static
755
     *
756
     * Internal function, do not override.
757
     */
758
    static function removeRelationshipMeta($key,$db,$tablename,$dictionary,$module_dir)
759
    {
760
        //load the module dictionary if not supplied.
761
        if ((!isset($dictionary) or empty($dictionary)) && !empty($module_dir))
762
        {
763
            $filename='modules/'. $module_dir . '/vardefs.php';
764
            if(file_exists($filename))
765
            {
766
                include($filename);
767
            }
768
        }
769
        if (!is_array($dictionary) or !array_key_exists($key, $dictionary))
770
        {
771
            $GLOBALS['log']->fatal("removeRelationshipMeta: Metadata for table ".$tablename. " does not exist");
772
            display_notice("meta data absent for table ".$tablename." keyed to $key ");
773
        }
774
        else
775
        {
776
            if (isset($dictionary[$key]['relationships']))
777
            {
778
                $RelationshipDefs = $dictionary[$key]['relationships'];
779
                foreach ($RelationshipDefs as $rel_name)
780
                {
781
                    Relationship::delete($rel_name,$db);
782
                }
783
            }
784
        }
785
    }
786
787
788
    /**
789
     * This method has been deprecated.
790
     *
791
    * @see removeRelationshipMeta()
792
     * @deprecated 4.5.1 - Nov 14, 2006
793
     * @static
794
    */
795
    static function remove_relationship_meta($key,$db,$log,$tablename,$dictionary,$module_dir)
796
    {
797
        SugarBean::removeRelationshipMeta($key,$db,$tablename,$dictionary,$module_dir);
798
    }
799
800
801
    /**
802
     * Populates the relationship meta for a module.
803
     *
804
     * It is called during setup/install. It is used statically to create relationship meta data for many-to-many tables.
805
     *
806
     * 	@param string $key name of the object.
807
     * 	@param object $db database handle.
808
     *  @param string $tablename table, meta data is being populated for.
809
     *  @param array dictionary vardef dictionary for the object.     *
810
     *  @param string module_dir name of subdirectory where module is installed.
811
     *  @param boolean $iscustom Optional,set to true if module is installed in a custom directory. Default value is false.
812
     *  @static
813
     *
814
     *  Internal function, do not override.
815
     */
816
    static function createRelationshipMeta($key,$db,$tablename,$dictionary,$module_dir,$iscustom=false)
817
    {
818
        //load the module dictionary if not supplied.
819
        if (empty($dictionary) && !empty($module_dir))
820
        {
821
            if($iscustom)
822
            {
823
                $filename='custom/modules/' . $module_dir . '/Ext/Vardefs/vardefs.ext.php';
824
            }
825
            else
826
            {
827
                if ($key == 'User')
828
                {
829
                    // a very special case for the Employees module
830
                    // this must be done because the Employees/vardefs.php does an include_once on
831
                    // Users/vardefs.php
832
                    $filename='modules/Users/vardefs.php';
833
                }
834
                else
835
                {
836
                    $filename='modules/'. $module_dir . '/vardefs.php';
837
                }
838
            }
839
840
            if(file_exists($filename))
841
            {
842
                include($filename);
843
                // cn: bug 7679 - dictionary entries defined as $GLOBALS['name'] not found
844
                if(empty($dictionary) || !empty($GLOBALS['dictionary'][$key]))
845
                {
846
                    $dictionary = $GLOBALS['dictionary'];
847
                }
848
            }
849
            else
850
            {
851
                $GLOBALS['log']->debug("createRelationshipMeta: no metadata file found" . $filename);
852
                return;
853
            }
854
        }
855
856
        if (!is_array($dictionary) or !array_key_exists($key, $dictionary))
857
        {
858
            $GLOBALS['log']->fatal("createRelationshipMeta: Metadata for table ".$tablename. " does not exist");
859
            display_notice("meta data absent for table ".$tablename." keyed to $key ");
860
        }
861
        else
862
        {
863
            if (isset($dictionary[$key]['relationships']))
864
            {
865
866
                $RelationshipDefs = $dictionary[$key]['relationships'];
867
868
                $delimiter=',';
869
                global $beanList;
870
                $beanList_ucase=array_change_key_case  ( $beanList ,CASE_UPPER);
871
                foreach ($RelationshipDefs as $rel_name=>$rel_def)
872
                {
873
                    if (isset($rel_def['lhs_module']) and !isset($beanList_ucase[strtoupper($rel_def['lhs_module'])])) {
874
                        $GLOBALS['log']->debug('skipping orphaned relationship record ' . $rel_name . ' lhs module is missing ' . $rel_def['lhs_module']);
875
                        continue;
876
                    }
877
                    if (isset($rel_def['rhs_module']) and !isset($beanList_ucase[strtoupper($rel_def['rhs_module'])])) {
878
                        $GLOBALS['log']->debug('skipping orphaned relationship record ' . $rel_name . ' rhs module is missing ' . $rel_def['rhs_module']);
879
                        continue;
880
                    }
881
882
883
                    //check whether relationship exists or not first.
884
                    if (Relationship::exists($rel_name,$db))
885
                    {
886
                        $GLOBALS['log']->debug('Skipping, reltionship already exists '.$rel_name);
887
                    }
888
                    else
889
                    {
890
                        $seed = BeanFactory::getBean("Relationships");
891
                        $keys = array_keys($seed->field_defs);
892
                        $toInsert = array();
893
                        foreach($keys as $key)
894
                        {
895
                            if ($key == "id")
896
                            {
897
                                $toInsert[$key] = create_guid();
898
                            }
899
                            else if ($key == "relationship_name")
900
                            {
901
                                $toInsert[$key] = $rel_name;
902
                            }
903
                            else if (isset($rel_def[$key]))
904
                            {
905
                                $toInsert[$key] = $rel_def[$key];
906
                            }
907
                            //todo specify defaults if meta not defined.
908
                        }
909
910
911
                        $column_list = implode(",", array_keys($toInsert));
912
                        $value_list = "'" . implode("','", array_values($toInsert)) . "'";
913
914
                        //create the record. todo add error check.
915
                        $insert_string = "INSERT into relationships (" .$column_list. ") values (".$value_list.")";
916
                        $db->query($insert_string, true);
917
                    }
918
                }
919
            }
920
            else
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
921
            {
922
                //todo
923
                //log informational message stating no relationships meta was set for this bean.
924
            }
925
        }
926
    }
927
928
    /**
929
     * This method has been deprecated.
930
    * @see createRelationshipMeta()
931
     * @deprecated 4.5.1 - Nov 14, 2006
932
     * @static
933
    */
934
    static function create_relationship_meta($key,&$db,&$log,$tablename,$dictionary,$module_dir)
935
    {
936
        SugarBean::createRelationshipMeta($key,$db,$tablename,$dictionary,$module_dir);
937
    }
938
939
940
    /**
941
     * Handle the following when a SugarBean object is cloned
942
     *
943
     * Currently all this does it unset any relationships that were created prior to cloning the object
944
     *
945
     * @api
946
     */
947
    public function __clone()
948
    {
949
        if(!empty($this->loaded_relationships)) {
950
            foreach($this->loaded_relationships as $rel) {
951
                unset($this->$rel);
952
            }
953
        }
954
    }
955
956
957
    /**
958
     * Loads the request relationship. This method should be called before performing any operations on the related data.
959
     *
960
     * This method searches the vardef array for the requested attribute's definition. If the attribute is of the type
961
     * link then it creates a similary named variable and loads the relationship definition.
962
     *
963
     * @param string $rel_name  relationship/attribute name.
964
     * @return nothing.
0 ignored issues
show
Documentation introduced by
The doc-type nothing. could not be parsed: Unknown type name "nothing." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
965
     */
966
    function load_relationship($rel_name)
967
    {
968
        $GLOBALS['log']->debug("SugarBean[{$this->object_name}].load_relationships, Loading relationship (".$rel_name.").");
969
970
        if (empty($rel_name))
971
        {
972
            $GLOBALS['log']->error("SugarBean.load_relationships, Null relationship name passed.");
973
            return false;
974
        }
975
        $fieldDefs = $this->getFieldDefinitions();
976
977
        //find all definitions of type link.
978
        if (!empty($fieldDefs[$rel_name]))
979
        {
980
            //initialize a variable of type Link
981
            require_once('data/Link2.php');
982
            $class = load_link_class($fieldDefs[$rel_name]);
983
            if (isset($this->$rel_name) && $this->$rel_name instanceof $class) {
984
                    return true;
985
            }
986
            //if rel_name is provided, search the fieldef array keys by name.
987
            if (isset($fieldDefs[$rel_name]['type']) && $fieldDefs[$rel_name]['type'] == 'link')
988
            {
989
                if ($class == "Link2")
990
                    $this->$rel_name = new $class($rel_name, $this);
991
                else
992
                    $this->$rel_name = new $class($fieldDefs[$rel_name]['relationship'], $this, $fieldDefs[$rel_name]);
993
994
                if (empty($this->$rel_name) ||
995
                        (method_exists($this->$rel_name, "loadedSuccesfully") && !$this->$rel_name->loadedSuccesfully()))
996
                {
997
                    unset($this->$rel_name);
998
                    return false;
999
                }
1000
                // keep track of the loaded relationships
1001
                $this->loaded_relationships[] = $rel_name;
1002
                return true;
1003
            }
1004
        }
1005
        $GLOBALS['log']->debug("SugarBean.load_relationships, Error Loading relationship (".$rel_name.")");
1006
        return false;
1007
    }
1008
1009
    /**
1010
     * Loads all attributes of type link.
1011
     *
1012
     * DO NOT CALL THIS FUNCTION IF YOU CAN AVOID IT. Please use load_relationship directly instead.
1013
     *
1014
     * Method searches the implmenting module's vardef file for attributes of type link, and for each attribute
1015
     * create a similary named variable and load the relationship definition.
1016
     *
1017
     * @return Nothing
1018
     *
1019
     * Internal function, do not override.
1020
     */
1021
    function load_relationships()
1022
    {
1023
        $GLOBALS['log']->debug("SugarBean.load_relationships, Loading all relationships of type link.");
1024
        $linked_fields=$this->get_linked_fields();
1025
        foreach($linked_fields as $name=>$properties)
1026
        {
1027
            $this->load_relationship($name);
1028
        }
1029
    }
1030
1031
    /**
1032
     * Returns an array of beans of related data.
1033
     *
1034
     * For instance, if an account is related to 10 contacts , this function will return an array of contacts beans (10)
1035
     * with each bean representing a contact record.
1036
     * Method will load the relationship if not done so already.
1037
     *
1038
     * @param string $field_name relationship to be loaded.
1039
     * @param string $bean name  class name of the related bean.legacy
0 ignored issues
show
Bug introduced by
There is no parameter named $bean. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
1040
     * @param string $order_by, Optional, default empty.
0 ignored issues
show
Documentation introduced by
There is no parameter named $order_by,. Did you maybe mean $order_by?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
1041
     * @param int $begin_index Optional, default 0, unused.
1042
     * @param int $end_index Optional, default -1
1043
     * @param int $deleted Optional, Default 0, 0  adds deleted=0 filter, 1  adds deleted=1 filter.
1044
     * @param string $optional_where, Optional, default empty.
0 ignored issues
show
Documentation introduced by
There is no parameter named $optional_where,. Did you maybe mean $optional_where?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
1045
     *
1046
     * Internal function, do not override.
1047
     */
1048
    function get_linked_beans($field_name,$bean_name = '', $order_by = '', $begin_index = 0, $end_index = -1, $deleted=0, $optional_where="")
1049
    {
1050
        //if bean_name is Case then use aCase
1051
        if($bean_name=="Case")
1052
            $bean_name = "aCase";
1053
1054
        if($this->load_relationship($field_name)) {
1055
            if ($this->$field_name instanceof Link) {
1056
                // some classes are still based on Link, e.g. TeamSetLink
1057
                return array_values($this->$field_name->getBeans(new $bean_name(), $order_by, $begin_index, $end_index, $deleted, $optional_where));
1058
            } else {
1059
                // Link2 style
1060
                if ($end_index != -1 || !empty($deleted) || !empty($optional_where) || !empty($order_by))
1061
                    return array_values($this->$field_name->getBeans(array(
1062
                        'where' => $optional_where,
1063
                        'deleted' => $deleted,
1064
                        'limit' => ($end_index - $begin_index),
1065
                        'order_by' => $order_by
1066
                    )));
1067
                else
1068
                    return array_values($this->$field_name->getBeans());
1069
            }
1070
        }
1071
        else
1072
            return array();
1073
    }
1074
1075
    /**
1076
     * Returns an array of fields that are of type link.
1077
     *
1078
     * @return array List of fields.
1079
     *
1080
     * Internal function, do not override.
1081
     */
1082
    function get_linked_fields()
1083
    {
1084
1085
        $linked_fields=array();
1086
1087
 //   	require_once('data/Link.php');
1088
1089
        $fieldDefs = $this->getFieldDefinitions();
1090
1091
        //find all definitions of type link.
1092
        if (!empty($fieldDefs))
1093
        {
1094
            foreach ($fieldDefs as $name=>$properties)
1095
            {
1096
                if (array_search('link',$properties) === 'type')
1097
                {
1098
                    $linked_fields[$name]=$properties;
1099
                }
1100
            }
1101
        }
1102
1103
        return $linked_fields;
1104
    }
1105
1106
    /**
1107
     * Returns an array of fields that are able to be Imported into
1108
     * i.e. 'importable' not set to 'false'
1109
     *
1110
     * @return array List of fields.
1111
     *
1112
     * Internal function, do not override.
1113
     */
1114
    function get_importable_fields()
1115
    {
1116
        $importableFields = array();
1117
1118
        $fieldDefs= $this->getFieldDefinitions();
1119
1120
        if (!empty($fieldDefs)) {
1121
            foreach ($fieldDefs as $key=>$value_array) {
1122
                if ( (isset($value_array['importable'])
1123
                        && (is_string($value_array['importable']) && $value_array['importable'] == 'false'
1124
                            || is_bool($value_array['importable']) && $value_array['importable'] == false))
1125
                    || (isset($value_array['type']) && $value_array['type'] == 'link')
1126
                    || (isset($value_array['auto_increment'])
1127
                        && ($value_array['type'] == true || $value_array['type'] == 'true')) ) {
1128
                    // only allow import if we force it
1129
                    if (isset($value_array['importable'])
1130
                        && (is_string($value_array['importable']) && $value_array['importable'] == 'true'
1131
                           || is_bool($value_array['importable']) && $value_array['importable'] == true)) {
1132
                        $importableFields[$key]=$value_array;
1133
                    }
1134
                }
1135
                else {
1136
1137
                    //Expose the cooresponding id field of a relate field if it is only defined as a link so that users can relate records by id during import
1138
                    if( isset($value_array['type']) && ($value_array['type'] == 'relate') && isset($value_array['id_name']) )
1139
                    {
1140
                        $idField = $value_array['id_name'];
1141
                        if( isset($fieldDefs[$idField]) && isset($fieldDefs[$idField]['type'] ) && $fieldDefs[$idField]['type'] == 'link' )
1142
                        {
1143
                            $tmpFieldDefs = $fieldDefs[$idField];
1144
                            $tmpFieldDefs['vname'] = translate($value_array['vname'], $this->module_dir) . " " . $GLOBALS['app_strings']['LBL_ID'];
1145
                            $importableFields[$idField]=$tmpFieldDefs;
1146
                        }
1147
                    }
1148
1149
                    $importableFields[$key]=$value_array;
1150
                }
1151
            }
1152
        }
1153
1154
        return $importableFields;
1155
    }
1156
1157
    /**
1158
     * Returns an array of fields that are of type relate.
1159
     *
1160
     * @return array List of fields.
1161
     *
1162
     * Internal function, do not override.
1163
     */
1164
    function get_related_fields()
1165
    {
1166
1167
        $related_fields=array();
1168
1169
//    	require_once('data/Link.php');
1170
1171
        $fieldDefs = $this->getFieldDefinitions();
1172
1173
        //find all definitions of type link.
1174
        if (!empty($fieldDefs))
1175
        {
1176
            foreach ($fieldDefs as $name=>$properties)
1177
            {
1178
                if (array_search('relate',$properties) === 'type')
1179
                {
1180
                    $related_fields[$name]=$properties;
1181
                }
1182
            }
1183
        }
1184
1185
        return $related_fields;
1186
    }
1187
1188
    /**
1189
     * Returns an array of fields that are required for import
1190
     *
1191
     * @return array
1192
     */
1193
    function get_import_required_fields()
1194
    {
1195
        $importable_fields = $this->get_importable_fields();
1196
        $required_fields   = array();
1197
1198
        foreach ( $importable_fields as $name => $properties ) {
1199
            if ( isset($properties['importable']) && is_string($properties['importable']) && $properties['importable'] == 'required' ) {
1200
                $required_fields[$name] = $properties;
1201
            }
1202
        }
1203
1204
        return $required_fields;
1205
    }
1206
1207
    /**
1208
     * Iterates through all the relationships and deletes all records for reach relationship.
1209
     *
1210
     * @param string $id Primary key value of the parent reocrd
1211
     */
1212
    function delete_linked($id)
1213
    {
1214
        $linked_fields=$this->get_linked_fields();
1215
        foreach ($linked_fields as $name => $value)
1216
        {
1217
            if ($this->load_relationship($name))
1218
            {
1219
                $this->$name->delete($id);
1220
            }
1221
            else
1222
            {
1223
                $GLOBALS['log']->fatal("error loading relationship $name");
1224
            }
1225
        }
1226
    }
1227
1228
    /**
1229
     * Creates tables for the module implementing the class.
1230
     * If you override this function make sure that your code can handles table creation.
1231
     *
1232
     */
1233
    function create_tables()
1234
    {
1235
        global $dictionary;
1236
1237
        $key = $this->getObjectName();
1238
        if (!array_key_exists($key, $dictionary))
1239
        {
1240
            $GLOBALS['log']->fatal("create_tables: Metadata for table ".$this->table_name. " does not exist");
1241
            display_notice("meta data absent for table ".$this->table_name." keyed to $key ");
1242
        }
1243
        else
1244
        {
1245
            if(!$this->db->tableExists($this->table_name))
1246
            {
1247
                $this->db->createTable($this);
1248
                    if($this->bean_implements('ACL')){
1249
                        if(!empty($this->acltype)){
1250
                            ACLAction::addActions($this->getACLCategory(), $this->acltype);
1251
                        }else{
1252
                            ACLAction::addActions($this->getACLCategory());
1253
                        }
1254
                    }
1255
            }
1256
            else
1257
            {
1258
                echo "Table already exists : $this->table_name<br>";
1259
            }
1260
            if($this->is_AuditEnabled()){
1261
                    if (!$this->db->tableExists($this->get_audit_table_name())) {
1262
                        $this->create_audit_table();
1263
                    }
1264
            }
1265
1266
        }
1267
    }
1268
1269
    /**
1270
     * Delete the primary table for the module implementing the class.
1271
     * If custom fields were added to this table/module, the custom table will be removed too, along with the cache
1272
     * entries that define the custom fields.
1273
     *
1274
     */
1275
    function drop_tables()
1276
    {
1277
        global $dictionary;
1278
        $key = $this->getObjectName();
1279
        if (!array_key_exists($key, $dictionary))
1280
        {
1281
            $GLOBALS['log']->fatal("drop_tables: Metadata for table ".$this->table_name. " does not exist");
1282
            echo "meta data absent for table ".$this->table_name."<br>\n";
1283
        } else {
1284
            if(empty($this->table_name))return;
1285
            if ($this->db->tableExists($this->table_name))
1286
1287
                $this->db->dropTable($this);
1288
            if ($this->db->tableExists($this->table_name. '_cstm'))
1289
            {
1290
                $this->db->dropTableName($this->table_name. '_cstm');
1291
                DynamicField::deleteCache();
1292
            }
1293
            if ($this->db->tableExists($this->get_audit_table_name())) {
1294
                $this->db->dropTableName($this->get_audit_table_name());
1295
            }
1296
1297
1298
        }
1299
    }
1300
1301
1302
    /**
1303
     * Loads the definition of custom fields defined for the module.
1304
     * Local file system cache is created as needed.
1305
     *
1306
     * @param string $module_name setting up custom fields for this module.
1307
     * @param boolean $clean_load Optional, default true, rebuilds the cache if set to true.
1308
     */
1309
    function setupCustomFields($module_name, $clean_load=true)
1310
    {
1311
        $this->custom_fields = new DynamicField($module_name);
1312
        $this->custom_fields->setup($this);
1313
1314
    }
1315
1316
    /**
1317
    * Cleans char, varchar, text, etc. fields of XSS type materials
1318
    */
1319
    function cleanBean() {
1320
        foreach($this->field_defs as $key => $def) {
1321
1322
            if (isset($def['type'])) {
1323
                $type=$def['type'];
1324
            }
1325
            if(isset($def['dbType']))
1326
                $type .= $def['dbType'];
1327
1328
            if($def['type'] == 'html' || $def['type'] == 'longhtml') {
1329
                $this->$key = SugarCleaner::cleanHtml($this->$key, true);
1330
            } elseif((strpos($type, 'char') !== false ||
1331
                strpos($type, 'text') !== false ||
1332
                $type == 'enum') &&
1333
                !empty($this->$key)
1334
            ) {
1335
                $this->$key = SugarCleaner::cleanHtml($this->$key);
1336
            }
1337
        }
1338
    }
1339
1340
    /**
1341
    * Implements a generic insert and update logic for any SugarBean
1342
    * This method only works for subclasses that implement the same variable names.
1343
    * This method uses the presence of an id field that is not null to signify and update.
1344
    * The id field should not be set otherwise.
1345
    *
1346
    * @param boolean $check_notify Optional, default false, if set to true assignee of the record is notified via email.
1347
    * @todo Add support for field type validation and encoding of parameters.
1348
    */
1349
    function save($check_notify = FALSE)
1350
    {
1351
        $this->in_save = true;
1352
        // cn: SECURITY - strip XSS potential vectors
1353
        $this->cleanBean();
1354
        // This is used so custom/3rd-party code can be upgraded with fewer issues, this will be removed in a future release
1355
        $this->fixUpFormatting();
1356
        global $timedate;
1357
        global $current_user, $action;
1358
1359
        $isUpdate = true;
1360
        if(empty($this->id))
1361
        {
1362
            $isUpdate = false;
1363
        }
1364
1365
		if ( $this->new_with_id == true )
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1366
		{
1367
			$isUpdate = false;
1368
		}
1369
		if(empty($this->date_modified) || $this->update_date_modified)
1370
		{
1371
			$this->date_modified = $GLOBALS['timedate']->nowDb();
1372
		}
1373
1374
        $this->_checkOptimisticLocking($action, $isUpdate);
1375
1376
        if(!empty($this->modified_by_name)) $this->old_modified_by_name = $this->modified_by_name;
1377
        if($this->update_modified_by)
1378
        {
1379
            $this->modified_user_id = 1;
1380
1381
            if (!empty($current_user))
1382
            {
1383
                $this->modified_user_id = $current_user->id;
1384
                $this->modified_by_name = $current_user->user_name;
1385
            }
1386
        }
1387
        if ($this->deleted != 1)
1388
            $this->deleted = 0;
0 ignored issues
show
Documentation Bug introduced by
The property $deleted was declared of type boolean, but 0 is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
1389
        if(!$isUpdate)
1390
        {
1391
            if (empty($this->date_entered))
1392
            {
1393
                $this->date_entered = $this->date_modified;
1394
            }
1395
            if($this->set_created_by == true)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1396
            {
1397
                // created by should always be this user
1398
                $this->created_by = (isset($current_user)) ? $current_user->id : "";
1399
            }
1400
            if( $this->new_with_id == false)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1401
            {
1402
                $this->id = create_guid();
1403
            }
1404
        }
1405
1406
1407
1408
        require_once("data/BeanFactory.php");
1409
        BeanFactory::registerBean($this->module_name, $this);
1410
1411
        if (empty($GLOBALS['updating_relationships']) && empty($GLOBALS['saving_relationships']) && empty ($GLOBALS['resavingRelatedBeans']))
1412
        {
1413
            $GLOBALS['saving_relationships'] = true;
1414
        // let subclasses save related field changes
1415
            $this->save_relationship_changes($isUpdate);
1416
            $GLOBALS['saving_relationships'] = false;
1417
        }
1418
        if($isUpdate && !$this->update_date_entered)
1419
        {
1420
            unset($this->date_entered);
1421
        }
1422
        // call the custom business logic
1423
        $custom_logic_arguments['check_notify'] = $check_notify;
1424
1425
1426
        $this->call_custom_logic("before_save", $custom_logic_arguments);
1427
        unset($custom_logic_arguments);
1428
1429
        // If we're importing back semi-colon separated non-primary emails
1430
        if ($this->hasEmails() && !empty($this->email_addresses_non_primary) && is_array($this->email_addresses_non_primary))
1431
        {
1432
            // Add each mail to the account
1433
            foreach ($this->email_addresses_non_primary as $mail)
1434
            {
1435
                $this->emailAddress->addAddress($mail);
1436
            }
1437
            $this->emailAddress->save($this->id, $this->module_dir);
1438
        }
1439
1440
        if(isset($this->custom_fields))
1441
        {
1442
            $this->custom_fields->bean = $this;
1443
            $this->custom_fields->save($isUpdate);
1444
        }
1445
1446
        // use the db independent query generator
1447
        $this->preprocess_fields_on_save();
1448
1449
        //construct the SQL to create the audit record if auditing is enabled.
1450
        $auditDataChanges=array();
1451
        if ($this->is_AuditEnabled()) {
1452
            if ($isUpdate && !isset($this->fetched_row)) {
1453
                $GLOBALS['log']->debug('Auditing: Retrieve was not called, audit record will not be created.');
1454
            } else {
1455
                $auditDataChanges=$this->db->getAuditDataChanges($this);
1456
            }
1457
        }
1458
1459
        $this->_sendNotifications($check_notify);
1460
1461
        if ($isUpdate) {
1462
            $this->db->update($this);
1463
        } else {
1464
            $this->db->insert($this);
1465
        }
1466
1467
        if (!empty($auditDataChanges) && is_array($auditDataChanges))
1468
        {
1469
            foreach ($auditDataChanges as $change)
1470
            {
1471
                $this->db->save_audit_records($this,$change);
1472
            }
1473
        }
1474
1475
1476
        if (empty($GLOBALS['resavingRelatedBeans'])){
1477
            SugarRelationship::resaveRelatedBeans();
1478
        }
1479
1480
        // populate fetched row with current bean values
1481
        foreach ($auditDataChanges as $change) {
1482
            $this->fetched_row[$change['field_name']] = $change['after'];
1483
        }
1484
1485
1486
		/* BEGIN - SECURITY GROUPS - inheritance */ 
1487
		require_once('modules/SecurityGroups/SecurityGroup.php');
1488
		SecurityGroup::inherit($this,$isUpdate);
1489
		/* END - SECURITY GROUPS */ 
1490
        //If we aren't in setup mode and we have a current user and module, then we track
1491
        if(isset($GLOBALS['current_user']) && isset($this->module_dir))
1492
        {
1493
            $this->track_view($current_user->id, $this->module_dir, 'save');
1494
        }
1495
1496
        $this->call_custom_logic('after_save', '');
1497
1498
        //Now that the record has been saved, we don't want to insert again on further saves
1499
        $this->new_with_id = false;
1500
        $this->in_save = false;
1501
        return $this->id;
1502
    }
1503
1504
1505
    /**
1506
     * Performs a check if the record has been modified since the specified date
1507
     *
1508
     * @param date $date Datetime for verification
1509
     * @param string $modified_user_id User modified by
1510
     */
1511
    function has_been_modified_since($date, $modified_user_id)
1512
    {
1513
        global $current_user;
1514
        $date = $this->db->convert($this->db->quoted($date), 'datetime');
1515
        if (isset($current_user))
1516
        {
1517
            $query = "SELECT date_modified FROM $this->table_name WHERE id='$this->id' AND modified_user_id != '$current_user->id'
1518
            	AND (modified_user_id != '$modified_user_id' OR date_modified > $date)";
1519
            $result = $this->db->query($query);
1520
1521
            if($this->db->fetchByAssoc($result))
1522
            {
1523
                return true;
1524
            }
1525
        }
1526
        return false;
1527
    }
1528
1529
    /**
1530
    * Determines which users receive a notification
1531
    */
1532
    function get_notification_recipients() {
1533
        $notify_user = new User();
1534
        $notify_user->retrieve($this->assigned_user_id);
1535
        $this->new_assigned_user_name = $notify_user->full_name;
1536
1537
        $GLOBALS['log']->info("Notifications: recipient is $this->new_assigned_user_name");
1538
1539
        $user_list = array($notify_user);
1540
        return $user_list;
1541
        /*
1542
        //send notifications to followers, but ensure to not query for the assigned_user.
1543
        return SugarFollowing::getFollowers($this, $notify_user);
1544
        */
1545
    }
1546
1547
    /**
1548
    * Handles sending out email notifications when items are first assigned to users
1549
    *
1550
    * @param string $notify_user user to notify
1551
    * @param string $admin the admin user that sends out the notification
1552
    */
1553
    function send_assignment_notifications($notify_user, $admin)
1554
    {
1555
        global $current_user;
1556
1557
        if(($this->object_name == 'Meeting' || $this->object_name == 'Call') || $notify_user->receive_notifications)
1558
        {
1559
            $sendToEmail = $notify_user->emailAddress->getPrimaryAddress($notify_user);
1560
            $sendEmail = TRUE;
1561
            if(empty($sendToEmail)) {
1562
                $GLOBALS['log']->warn("Notifications: no e-mail address set for user {$notify_user->user_name}, cancelling send");
1563
                $sendEmail = FALSE;
1564
            }
1565
1566
            $notify_mail = $this->create_notification_email($notify_user);
1567
            $notify_mail->setMailerForSystem();
1568
1569
            if(empty($admin->settings['notify_send_from_assigning_user'])) {
1570
                $notify_mail->From = $admin->settings['notify_fromaddress'];
1571
                $notify_mail->FromName = (empty($admin->settings['notify_fromname'])) ? "" : $admin->settings['notify_fromname'];
1572
            } else {
1573
                // Send notifications from the current user's e-mail (if set)
1574
                $fromAddress = $current_user->emailAddress->getReplyToAddress($current_user);
1575
                $fromAddress = !empty($fromAddress) ? $fromAddress : $admin->settings['notify_fromaddress'];
1576
                $notify_mail->From = $fromAddress;
1577
                //Use the users full name is available otherwise default to system name
1578
                $from_name = !empty($admin->settings['notify_fromname']) ? $admin->settings['notify_fromname'] : "";
1579
                $from_name = !empty($current_user->full_name) ? $current_user->full_name : $from_name;
1580
                $notify_mail->FromName = $from_name;
1581
            }
1582
1583
           $oe = new OutboundEmail();
1584
            $oe = $oe->getUserMailerSettings($current_user);
1585
            //only send if smtp server is defined
1586
            if($sendEmail){
1587
                $smtpVerified = false;
1588
1589
                //first check the user settings
1590
                if(!empty($oe->mail_smtpserver)){
1591
                    $smtpVerified = true;
1592
                }
1593
1594
                //if still not verified, check against the system settings
1595
                if (!$smtpVerified){
1596
                    $oe = $oe->getSystemMailerSettings();
1597
                    if(!empty($oe->mail_smtpserver)){
1598
                        $smtpVerified = true;
1599
                    }
1600
                }
1601
                //if smtp was not verified against user or system, then do not send out email
1602
                if (!$smtpVerified){
1603
                    $GLOBALS['log']->fatal("Notifications: error sending e-mail, smtp server was not found ");
1604
                    //break out
1605
                    return;
1606
                }
1607
1608
                if(!$notify_mail->Send()) {
1609
                    $GLOBALS['log']->fatal("Notifications: error sending e-mail (method: {$notify_mail->Mailer}), (error: {$notify_mail->ErrorInfo})");
1610
                }else{
1611
                    $GLOBALS['log']->info("Notifications: e-mail successfully sent");
1612
                }
1613
            }
1614
1615
        }
1616
    }
1617
1618
    /**
1619
    * This function handles create the email notifications email.
1620
    * @param string $notify_user the user to send the notification email to
1621
    */
1622
    function create_notification_email($notify_user) {
1623
        global $sugar_version;
1624
        global $sugar_config;
1625
        global $app_list_strings;
1626
        global $current_user;
1627
        global $locale;
1628
        global $beanList;
1629
        $OBCharset = $locale->getPrecedentPreference('default_email_charset');
1630
1631
1632
        require_once("include/SugarPHPMailer.php");
1633
1634
        $notify_address = $notify_user->emailAddress->getPrimaryAddress($notify_user);
1635
        $notify_name = $notify_user->full_name;
1636
        $GLOBALS['log']->debug("Notifications: user has e-mail defined");
1637
1638
        $notify_mail = new SugarPHPMailer();
1639
        $notify_mail->AddAddress($notify_address,$locale->translateCharsetMIME(trim($notify_name), 'UTF-8', $OBCharset));
1640
1641
        if(empty($_SESSION['authenticated_user_language'])) {
1642
            $current_language = $sugar_config['default_language'];
1643
        } else {
1644
            $current_language = $_SESSION['authenticated_user_language'];
1645
        }
1646
        $xtpl = new XTemplate(get_notify_template_file($current_language));
1647
        if($this->module_dir == "Cases") {
1648
            $template_name = "Case"; //we should use Case, you can refer to the en_us.notify_template.html.
1649
        }
1650
        else {
1651
            $template_name = $beanList[$this->module_dir]; //bug 20637, in workflow this->object_name = strange chars.
1652
        }
1653
1654
        $this->current_notify_user = $notify_user;
1655
1656
        if(in_array('set_notification_body', get_class_methods($this))) {
1657
            $xtpl = $this->set_notification_body($xtpl, $this);
1658
        } else {
1659
            $xtpl->assign("OBJECT", translate('LBL_MODULE_NAME'));
1660
            $template_name = "Default";
1661
        }
1662
        if(!empty($_SESSION["special_notification"]) && $_SESSION["special_notification"]) {
1663
            $template_name = $beanList[$this->module_dir].'Special';
1664
        }
1665
        if($this->special_notification) {
1666
            $template_name = $beanList[$this->module_dir].'Special';
1667
        }
1668
        $xtpl->assign("ASSIGNED_USER", $this->new_assigned_user_name);
1669
        $xtpl->assign("ASSIGNER", $current_user->name);
1670
        $port = '';
1671
1672
        if(isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) {
1673
            $port = $_SERVER['SERVER_PORT'];
1674
        }
1675
1676
        if (!isset($_SERVER['HTTP_HOST'])) {
1677
            $_SERVER['HTTP_HOST'] = '';
1678
        }
1679
1680
        $httpHost = $_SERVER['HTTP_HOST'];
1681
1682
        if($colon = strpos($httpHost, ':')) {
1683
            $httpHost    = substr($httpHost, 0, $colon);
1684
        }
1685
1686
        $parsedSiteUrl = parse_url($sugar_config['site_url']);
1687
        $host = $parsedSiteUrl['host'];
1688
        if(!isset($parsedSiteUrl['port'])) {
1689
            $parsedSiteUrl['port'] = 80;
1690
        }
1691
1692
        $port		= ($parsedSiteUrl['port'] != 80) ? ":".$parsedSiteUrl['port'] : '';
1693
        $path		= !empty($parsedSiteUrl['path']) ? $parsedSiteUrl['path'] : "";
1694
        $cleanUrl	= "{$parsedSiteUrl['scheme']}://{$host}{$port}{$path}";
1695
1696
        $xtpl->assign("URL", $cleanUrl."/index.php?module={$this->module_dir}&action=DetailView&record={$this->id}");
1697
        $xtpl->assign("SUGAR", "Sugar v{$sugar_version}");
1698
        $xtpl->parse($template_name);
1699
        $xtpl->parse($template_name . "_Subject");
1700
1701
        $notify_mail->Body = from_html(trim($xtpl->text($template_name)));
1702
        $notify_mail->Subject = from_html($xtpl->text($template_name . "_Subject"));
1703
1704
        // cn: bug 8568 encode notify email in User's outbound email encoding
1705
        $notify_mail->prepForOutbound();
1706
1707
        return $notify_mail;
1708
    }
1709
1710
    /**
1711
     * This function is a good location to save changes that have been made to a relationship.
1712
     * This should be overridden in subclasses that have something to save.
1713
     *
1714
     * @param boolean $is_update    true if this save is an update.
1715
     * @param array $exclude        a way to exclude relationships
1716
     */
1717
    public function save_relationship_changes($is_update, $exclude = array())
1718
    {
1719
        list($new_rel_id, $new_rel_link) = $this->set_relationship_info($exclude);
1720
1721
        $new_rel_id = $this->handle_preset_relationships($new_rel_id, $new_rel_link, $exclude);
1722
1723
        $this->handle_remaining_relate_fields($exclude);
1724
1725
        $this->update_parent_relationships($exclude);
1726
1727
        $this->handle_request_relate($new_rel_id, $new_rel_link);
1728
    }
1729
1730
    /**
1731
     * Look in the bean for the new relationship_id and relationship_name if $this->not_use_rel_in_req is set to true,
1732
     * otherwise check the $_REQUEST param for a relate_id and relate_to field.  Once we have that make sure that it's
1733
     * not excluded from the passed in array of relationships to exclude
1734
     *
1735
     * @param array $exclude        any relationship's to exclude
1736
     * @return array                The relationship_id and relationship_name in an array
1737
     */
1738
    protected function set_relationship_info($exclude = array())
1739
    {
1740
1741
        $new_rel_id = false;
1742
        $new_rel_link = false;
1743
        // check incoming data
1744
        if (isset($this->not_use_rel_in_req) && $this->not_use_rel_in_req == true) {
1745
            // if we should use relation data from properties (for REQUEST-independent calls)
1746
            $rel_id = isset($this->new_rel_id) ? $this->new_rel_id : '';
1747
            $rel_link = isset($this->new_rel_relname) ? $this->new_rel_relname : '';
1748
        }
1749
        else
1750
        {
1751
            // if we should use relation data from REQUEST
1752
            $rel_id = isset($_REQUEST['relate_id']) ? $_REQUEST['relate_id'] : '';
1753
            $rel_link = isset($_REQUEST['relate_to']) ? $_REQUEST['relate_to'] : '';
1754
        }
1755
1756
        // filter relation data
1757
        if ($rel_id && $rel_link && !in_array($rel_link, $exclude) && $rel_id != $this->id) {
1758
            $new_rel_id = $rel_id;
1759
            $new_rel_link = $rel_link;
1760
            // Bug #53223 : wrong relationship from subpanel create button
1761
            // if LHSModule and RHSModule are same module use left link to add new item b/s of:
1762
            // $rel_id and $rel_link are not emty - request is from subpanel
1763
            // $rel_link contains relationship name - checked by call load_relationship
1764
            $isRelationshipLoaded = $this->load_relationship($rel_link);
1765
            if ($isRelationshipLoaded && !empty($this->$rel_link) && $this->$rel_link->getRelationshipObject() && $this->$rel_link->getRelationshipObject()->getLHSModule() == $this->$rel_link->getRelationshipObject()->getRHSModule() )
1766
            {
1767
                $new_rel_link = $this->$rel_link->getRelationshipObject()->getLHSLink();
1768
            }
1769
            else
1770
            {
1771
                //Try to find the link in this bean based on the relationship
1772
                foreach ($this->field_defs as $key => $def)
1773
                {
1774
                    if (isset($def['type']) && $def['type'] == 'link' && isset($def['relationship']) && $def['relationship'] == $rel_link)
1775
                    {
1776
                        $new_rel_link = $key;
1777
                    }
1778
                }
1779
            }
1780
        }
1781
1782
        return array($new_rel_id, $new_rel_link);
1783
    }
1784
1785
    /**
1786
     * Handle the preset fields listed in the fixed relationship_fields array hardcoded into the OOB beans
1787
     *
1788
     * TODO: remove this mechanism and replace with mechanism exclusively based on the vardefs
1789
     *
1790
     * @api
1791
     * @see save_relationship_changes
1792
     * @param string|boolean $new_rel_id    String of the ID to add
1793
     * @param string                        Relationship Name
1794
     * @param array $exclude                any relationship's to exclude
1795
     * @return string|boolean               Return the new_rel_id if it was not used.  False if it was used.
1796
     */
1797
    protected function handle_preset_relationships($new_rel_id, $new_rel_link, $exclude = array())
1798
    {
1799
        if (isset($this->relationship_fields) && is_array($this->relationship_fields)) {
1800
            foreach ($this->relationship_fields as $id => $rel_name)
1801
            {
1802
1803
                if (in_array($id, $exclude)) continue;
1804
1805
                if(!empty($this->$id))
1806
                {
1807
                    // Bug #44930 We do not need to update main related field if it is changed from sub-panel.
1808
                    if ($rel_name == $new_rel_link && $this->$id != $new_rel_id)
1809
                    {
1810
                        $new_rel_id = '';
1811
                    }
1812
                    $GLOBALS['log']->debug('save_relationship_changes(): From relationship_field array - adding a relationship record: '.$rel_name . ' = ' . $this->$id);
1813
                    //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
1814
                    $this->load_relationship($rel_name);
1815
                    $rel_add = $this->$rel_name->add($this->$id);
1816
                    // move this around to only take out the id if it was save successfully
1817
                    if ($this->$id == $new_rel_id && $rel_add == true) {
1818
                        $new_rel_id = false;
1819
                    }
1820
                } else {
1821
                    //if before value is not empty then attempt to delete relationship
1822
                    if (!empty($this->rel_fields_before_value[$id])) {
1823
                        $GLOBALS['log']->debug('save_relationship_changes(): From relationship_field array - attempting to remove the relationship record, using relationship attribute' . $rel_name);
1824
                        $this->load_relationship($rel_name);
1825
                        $this->$rel_name->delete($this->id, $this->rel_fields_before_value[$id]);
1826
                    }
1827
                }
1828
            }
1829
        }
1830
1831
        return $new_rel_id;
1832
    }
1833
1834
    /**
1835
     * Next, we'll attempt to update all of the remaining relate fields in the vardefs that have 'save' set in their field_def
1836
     * Only the 'save' fields should be saved as some vardef entries today are not for display only purposes and break the application if saved
1837
     * If the vardef has entries for field <a> of type relate, where a->id_name = <b> and field <b> of type link
1838
     * then we receive a value for b from the MVC in the _REQUEST, and it should be set in the bean as $this->$b
1839
     *
1840
     * @api
1841
     * @see save_relationship_changes
1842
     * @param array $exclude            any relationship's to exclude
1843
     * @return array                    the list of relationships that were added or removed successfully or if they were a failure
1844
     */
1845
    protected function handle_remaining_relate_fields($exclude = array())
1846
    {
1847
1848
        $modified_relationships = array(
1849
            'add' => array('success' => array(), 'failure' => array()),
1850
            'remove' => array('success' => array(), 'failure' => array()),
1851
        );
1852
1853
        foreach ($this->field_defs as $def)
1854
        {
1855
            if ($def ['type'] == 'relate' && isset ($def ['id_name']) && isset ($def ['link']) && isset ($def['save'])) {
1856
                if (in_array($def['id_name'], $exclude) || in_array($def['id_name'], $this->relationship_fields))
1857
                    continue; // continue to honor the exclude array and exclude any relationships that will be handled by the relationship_fields mechanism
1858
1859
                $linkField = $def ['link'];
1860
                if (isset($this->field_defs[$linkField])) {
1861
                    if ($this->load_relationship($linkField)) {
1862
                        $idName = $def['id_name'];
1863
1864
                        if (!empty($this->rel_fields_before_value[$idName]) && empty($this->$idName)) {
1865
                            //if before value is not empty then attempt to delete relationship
1866
                            $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' ]]}");
1867
                            $success = $this->$def ['link']->delete($this->id, $this->rel_fields_before_value[$def ['id_name']]);
1868
                            // just need to make sure it's true and not an array as it's possible to return an array
1869
                            if($success == true) {
1870
                                $modified_relationships['remove']['success'][] = $def['link'];
1871
                            } else {
1872
                                $modified_relationships['remove']['failure'][] = $def['link'];
1873
                            }
1874
                            $GLOBALS['log']->debug("save_relationship_changes(): From field_defs - attempting to remove the relationship record returned " . var_export($success, true));
1875
                        }
1876
1877
                        if (!empty($this->$idName) && is_string($this->$idName)) {
1878
                            $GLOBALS['log']->debug("save_relationship_changes(): From field_defs - attempting to add a relationship record - {$def [ 'link' ]} = {$this->$def [ 'id_name' ]}");
1879
1880
                            $success = $this->$linkField->add($this->$idName);
1881
1882
                            // just need to make sure it's true and not an array as it's possible to return an array
1883
                            if($success == true) {
1884
                                $modified_relationships['add']['success'][] = $linkField;
1885
                            } else {
1886
                                $modified_relationships['add']['failure'][] = $linkField;
1887
                            }
1888
1889
                            $GLOBALS['log']->debug("save_relationship_changes(): From field_defs - add a relationship record returned " . var_export($success, true));
1890
                        }
1891
                    } else {
1892
                        $GLOBALS['log']->fatal("Failed to load relationship {$linkField} while saving {$this->module_dir}");
1893
                    }
1894
                }
1895
            }
1896
        }
1897
1898
        return $modified_relationships;
1899
    }
1900
1901
1902
    /**
1903
     * Updates relationships based on changes to fields of type 'parent' which
1904
     * may or may not have links associated with them
1905
     *
1906
     * @param array $exclude
1907
     */
1908
    protected function update_parent_relationships($exclude = array())
1909
    {
1910
        foreach ($this->field_defs as $def)
1911
        {
1912
            if (!empty($def['type']) && $def['type'] == "parent")
1913
            {
1914
                if (empty($def['type_name']) || empty($def['id_name']))
1915
                    continue;
1916
                $typeField = $def['type_name'];
1917
                $idField = $def['id_name'];
1918
                if (in_array($idField, $exclude))
1919
                    continue;
1920
                //Determine if the parent field has changed.
1921
                if (
1922
                    //First check if the fetched row parent existed and now we no longer have one
1923
                    (!empty($this->fetched_row[$typeField]) && !empty($this->fetched_row[$idField])
1924
                        && (empty($this->$typeField) || empty($this->$idField))
1925
                    ) ||
1926
                    //Next check if we have one now that doesn't match the fetch row
1927
                    (!empty($this->$typeField) && !empty($this->$idField) &&
1928
                        (empty($this->fetched_row[$typeField]) || empty($this->fetched_row[$idField])
1929
                        || $this->fetched_row[$idField] != $this->$idField)
1930
                    ) ||
1931
                    // Check if we are deleting the bean, should remove the bean from any relationships
1932
                    $this->deleted == 1
1933
                ) {
1934
                    $parentLinks = array();
1935
                    //Correlate links to parent field module types
1936
                    foreach ($this->field_defs as $ldef)
1937
                    {
1938
                        if (!empty($ldef['type']) && $ldef['type'] == "link" && !empty($ldef['relationship']))
1939
                        {
1940
                            $relDef = SugarRelationshipFactory::getInstance()->getRelationshipDef($ldef['relationship']);
1941
                            if (!empty($relDef['relationship_role_column']) && $relDef['relationship_role_column'] == $typeField)
1942
                            {
1943
                                $parentLinks[$relDef['lhs_module']] = $ldef;
1944
                            }
1945
                        }
1946
                    }
1947
1948
                    // Save $this->$idField, because it can be resetted in case of link->delete() call
1949
                    $idFieldVal = $this->$idField;
1950
1951
                    //If we used to have a parent, call remove on that relationship
1952
                    if (!empty($this->fetched_row[$typeField]) && !empty($this->fetched_row[$idField])
1953
                        && !empty($parentLinks[$this->fetched_row[$typeField]])
1954
                        && ($this->fetched_row[$idField] != $this->$idField))
1955
                    {
1956
                        $oldParentLink = $parentLinks[$this->fetched_row[$typeField]]['name'];
1957
                        //Load the relationship
1958
                        if ($this->load_relationship($oldParentLink))
1959
                        {
1960
                            $this->$oldParentLink->delete($this->fetched_row[$idField]);
1961
                            // Should resave the old parent
1962
                            SugarRelationship::addToResaveList(BeanFactory::getBean($this->fetched_row[$typeField], $this->fetched_row[$idField]));
1963
                        }
1964
                    }
1965
1966
                    // If both parent type and parent id are set, save it unless the bean is being deleted
1967
                    if (!empty($this->$typeField)
1968
                       && !empty($idFieldVal)
1969
                       && !empty($parentLinks[$this->$typeField]['name'])
1970
                       && $this->deleted != 1
1971
                    ) {
1972
                        //Now add the new parent
1973
                        $parentLink = $parentLinks[$this->$typeField]['name'];
1974
                        if ($this->load_relationship($parentLink))
1975
                        {
1976
                            $this->$parentLink->add($idFieldVal);
1977
                        }
1978
                    }
1979
                }
1980
            }
1981
        }
1982
    }
1983
1984
    /**
1985
     * Finally, we update a field listed in the _REQUEST['%/relate_id']/_REQUEST['relate_to'] mechanism (if it has not already been updated)
1986
     *
1987
     * @api
1988
     * @see save_relationship_changes
1989
     * @param string|boolean $new_rel_id
1990
     * @param string $new_rel_link
1991
     * @return boolean
1992
     */
1993
    protected function handle_request_relate($new_rel_id, $new_rel_link)
1994
    {
1995
        if (!empty($new_rel_id)) {
1996
1997
            if ($this->load_relationship($new_rel_link)) {
1998
                return $this->$new_rel_link->add($new_rel_id);
1999
            } else {
2000
                $lower_link = strtolower($new_rel_link);
2001
                if ($this->load_relationship($lower_link)) {
2002
                    return $this->$lower_link->add($new_rel_id);
2003
2004
                } else {
2005
                    require_once('data/Link2.php');
2006
                    $rel = Relationship::retrieve_by_modules($new_rel_link, $this->module_dir, $this->db, 'many-to-many');
2007
2008
                    if (!empty($rel)) {
2009
                        foreach ($this->field_defs as $field => $def) {
2010
                            if ($def['type'] == 'link' && !empty($def['relationship']) && $def['relationship'] == $rel) {
2011
                                $this->load_relationship($field);
2012
                                return $this->$field->add($new_rel_id);
2013
                            }
2014
2015
                        }
2016
                        //ok so we didn't find it in the field defs let's save it anyway if we have the relationshp
2017
2018
                        $this->$rel = new Link2($rel, $this, array());
2019
                        return $this->$rel->add($new_rel_id);
2020
                    }
2021
                }
2022
            }
2023
        }
2024
2025
        // nothing was saved so just return false;
2026
        return false;
2027
    }
2028
2029
    /**
2030
    * This function retrieves a record of the appropriate type from the DB.
2031
    * It fills in all of the fields from the DB into the object it was called on.
2032
    *
2033
    * @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.
2034
    * @return this - The object that it was called apon or null if exactly 1 record was not found.
2035
    *
2036
	*/
2037
2038
	function check_date_relationships_load()
2039
	{
2040
		global $disable_date_format;
2041
		global $timedate;
2042
		if (empty($timedate))
2043
			$timedate=TimeDate::getInstance();
2044
2045
		if(empty($this->field_defs))
2046
		{
2047
			return;
2048
		}
2049
		foreach($this->field_defs as $fieldDef)
2050
		{
2051
			$field = $fieldDef['name'];
2052
			if(!isset($this->processed_dates_times[$field]))
2053
			{
2054
				$this->processed_dates_times[$field] = '1';
2055
				if(empty($this->$field)) continue;
2056
				if($field == 'date_modified' || $field == 'date_entered')
2057
				{
2058
					$this->$field = $this->db->fromConvert($this->$field, 'datetime');
2059
					if(empty($disable_date_format)) {
2060
						$this->$field = $timedate->to_display_date_time($this->$field);
2061
					}
2062
				}
2063
				elseif(isset($this->field_name_map[$field]['type']))
2064
				{
2065
					$type = $this->field_name_map[$field]['type'];
2066
2067
					if($type == 'relate'  && isset($this->field_name_map[$field]['custom_module']))
2068
					{
2069
						$type = $this->field_name_map[$field]['type'];
2070
					}
2071
2072
					if($type == 'date')
2073
					{
2074
						if($this->$field == '0000-00-00')
2075
						{
2076
							$this->$field = '';
2077
						} elseif(!empty($this->field_name_map[$field]['rel_field']))
2078
						{
2079
							$rel_field = $this->field_name_map[$field]['rel_field'];
2080
2081
							if(!empty($this->$rel_field))
2082
							{
2083
								if(empty($disable_date_format)) {
2084
									$mergetime = $timedate->merge_date_time($this->$field,$this->$rel_field);
2085
									$this->$field = $timedate->to_display_date($mergetime);
2086
									$this->$rel_field = $timedate->to_display_time($mergetime);
2087
								}
2088
							}
2089
						}
2090
						else
2091
						{
2092
							if(empty($disable_date_format)) {
2093
								$this->$field = $timedate->to_display_date($this->$field, false);
2094
							}
2095
						}
2096
					} elseif($type == 'datetime' || $type == 'datetimecombo')
2097
					{
2098
						if($this->$field == '0000-00-00 00:00:00')
2099
						{
2100
							$this->$field = '';
2101
						}
2102
						else
2103
						{
2104
							if(empty($disable_date_format)) {
2105
								$this->$field = $timedate->to_display_date_time($this->$field, true, true);
2106
							}
2107
						}
2108
					} elseif($type == 'time')
2109
					{
2110
						if($this->$field == '00:00:00')
2111
						{
2112
							$this->$field = '';
2113
						} else
2114
						{
2115
							//$this->$field = from_db_convert($this->$field, 'time');
2116
							if(empty($this->field_name_map[$field]['rel_field']) && empty($disable_date_format))
2117
							{
2118
								$this->$field = $timedate->to_display_time($this->$field,true, false);
2119
							}
2120
						}
2121
					} elseif($type == 'encrypt' && empty($disable_date_format)){
2122
						$this->$field = $this->decrypt_after_retrieve($this->$field);
2123
					}
2124
				}
2125
			}
2126
		}
2127
	}
2128
2129
    /**
2130
     * This function processes the fields before save.
2131
     * Interal function, do not override.
2132
     */
2133
    function preprocess_fields_on_save()
2134
    {
2135
        $GLOBALS['log']->deprecated('SugarBean.php: preprocess_fields_on_save() is deprecated');
2136
    }
2137
2138
    /**
2139
    * Removes formatting from values posted from the user interface.
2140
     * It only unformats numbers.  Function relies on user/system prefernce for format strings.
2141
     *
2142
     * Internal Function, do not override.
2143
    */
2144
    function unformat_all_fields()
2145
    {
2146
        $GLOBALS['log']->deprecated('SugarBean.php: unformat_all_fields() is deprecated');
2147
    }
2148
2149
    /**
2150
    * This functions adds formatting to all number fields before presenting them to user interface.
2151
     *
2152
     * Internal function, do not override.
2153
    */
2154
    function format_all_fields()
2155
    {
2156
        $GLOBALS['log']->deprecated('SugarBean.php: format_all_fields() is deprecated');
2157
    }
2158
2159
    function format_field($fieldDef)
2160
        {
2161
        $GLOBALS['log']->deprecated('SugarBean.php: format_field() is deprecated');
2162
        }
2163
2164
    /**
2165
     * Function corrects any bad formatting done by 3rd party/custom code
2166
     *
2167
     * 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
2168
     */
2169
    function fixUpFormatting()
2170
    {
2171
        global $timedate;
2172
        static $boolean_false_values = array('off', 'false', '0', 'no');
2173
2174
2175
        foreach($this->field_defs as $field=>$def)
2176
            {
2177
            if ( !isset($this->$field) ) {
2178
                continue;
2179
                }
2180
            if ( (isset($def['source'])&&$def['source']=='non-db') || $field == 'deleted' ) {
2181
                continue;
2182
            }
2183
            if ( isset($this->fetched_row[$field]) && $this->$field == $this->fetched_row[$field] ) {
2184
                // Don't hand out warnings because the field was untouched between retrieval and saving, most database drivers hand pretty much everything back as strings.
2185
                continue;
2186
            }
2187
            $reformatted = false;
2188
            switch($def['type']) {
2189
                case 'datetime':
2190
                case 'datetimecombo':
2191
                    if(empty($this->$field)) break;
2192
                    if ($this->$field == 'NULL') {
2193
                    	$this->$field = '';
2194
                    	break;
2195
                    }
2196
                    if ( ! preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$/',$this->$field) ) {
2197
                        // This appears to be formatted in user date/time
2198
                        $this->$field = $timedate->to_db($this->$field);
2199
                        $reformatted = true;
2200
                    }
2201
                    break;
2202
                case 'date':
2203
                    if(empty($this->$field)) break;
2204
                    if ($this->$field == 'NULL') {
2205
                    	$this->$field = '';
2206
                    	break;
2207
                    }
2208
                    if ( ! preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/',$this->$field) ) {
2209
                        // This date appears to be formatted in the user's format
2210
                        $this->$field = $timedate->to_db_date($this->$field, false);
2211
                        $reformatted = true;
2212
                    }
2213
                    break;
2214
                case 'time':
2215
                    if(empty($this->$field)) break;
2216
                    if ($this->$field == 'NULL') {
2217
                    	$this->$field = '';
2218
                    	break;
2219
                    }
2220
                    if ( preg_match('/(am|pm)/i',$this->$field) ) {
2221
                        // This time appears to be formatted in the user's format
2222
                        $this->$field = $timedate->fromUserTime($this->$field)->format(TimeDate::DB_TIME_FORMAT);
2223
                        $reformatted = true;
2224
                    }
2225
                    break;
2226
                case 'double':
2227
                case 'decimal':
2228
                case 'currency':
2229
                case 'float':
2230
                    if ( $this->$field === '' || $this->$field == NULL || $this->$field == 'NULL') {
2231
                        continue;
2232
                    }
2233
                    if ( is_string($this->$field) ) {
2234
                        $this->$field = (float)unformat_number($this->$field);
2235
                        $reformatted = true;
2236
                    }
2237
                    break;
2238
               case 'uint':
2239
               case 'ulong':
2240
               case 'long':
2241
               case 'short':
2242
               case 'tinyint':
2243
               case 'int':
2244
                    if ( $this->$field === '' || $this->$field == NULL || $this->$field == 'NULL') {
2245
                        continue;
2246
                    }
2247
                    if ( is_string($this->$field) ) {
2248
                        $this->$field = (int)unformat_number($this->$field);
2249
                        $reformatted = true;
2250
                    }
2251
                   break;
2252
               case 'bool':
2253
                   if (empty($this->$field)) {
2254
                       $this->$field = false;
2255
                   } else if(true === $this->$field || 1 == $this->$field) {
2256
                       $this->$field = true;
2257
                   } else if(in_array(strval($this->$field), $boolean_false_values)) {
2258
                       $this->$field = false;
2259
                       $reformatted = true;
2260
                   } else {
2261
                       $this->$field = true;
2262
                       $reformatted = true;
2263
                   }
2264
                   break;
2265
               case 'encrypt':
2266
                    $this->$field = $this->encrpyt_before_save($this->$field);
2267
                    break;
2268
            }
2269
            if ( $reformatted ) {
2270
                $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');
2271
            }
2272
        }
2273
2274
    }
2275
2276
    /**
2277
     * Function fetches a single row of data given the primary key value.
2278
     *
2279
     * The fetched data is then set into the bean. The function also processes the fetched data by formattig
2280
     * date/time and numeric values.
2281
     *
2282
     * @param string $id Optional, default -1, is set to -1 id value from the bean is used, else, passed value is used
2283
     * @param boolean $encode Optional, default true, encodes the values fetched from the database.
2284
     * @param boolean $deleted Optional, default true, if set to false deleted filter will not be added.
2285
     *
2286
     * Internal function, do not override.
2287
    */
2288
    function retrieve($id = -1, $encode=true,$deleted=true)
2289
    {
2290
2291
        $custom_logic_arguments['id'] = $id;
2292
        $this->call_custom_logic('before_retrieve', $custom_logic_arguments);
2293
2294
        if ($id == -1)
2295
        {
2296
            $id = $this->id;
2297
        }
2298
        $custom_join = $this->getCustomJoin();
2299
2300
        $query = "SELECT $this->table_name.*". $custom_join['select']. " FROM $this->table_name ";
2301
2302
        $query .= $custom_join['join'];
2303
        $query .= " WHERE $this->table_name.id = ".$this->db->quoted($id);
2304
        if ($deleted) $query .= " AND $this->table_name.deleted=0";
2305
        $GLOBALS['log']->debug("Retrieve $this->object_name : ".$query);
2306
        $result = $this->db->limitQuery($query,0,1,true, "Retrieving record by id $this->table_name:$id found ");
2307
        if(empty($result))
2308
        {
2309
            return null;
2310
        }
2311
2312
        $row = $this->db->fetchByAssoc($result, $encode);
2313
        if(empty($row))
2314
        {
2315
            return null;
2316
        }
2317
2318
        //make copy of the fetched row for construction of audit record and for business logic/workflow
2319
        $row = $this->convertRow($row);
2320
        $this->fetched_row=$row;
2321
        $this->populateFromRow($row);
2322
2323
        // fix defect #52438. implement the same logic as sugar_currency_format
2324
        // Smarty modifier does.
2325
        $this->populateCurrencyFields();
2326
2327
        global $module, $action;
2328
        //Just to get optimistic locking working for this release
2329
        if($this->optimistic_lock && $module == $this->module_dir && $action =='EditView' )
2330
        {
2331
            $_SESSION['o_lock_id']= $id;
2332
            $_SESSION['o_lock_dm']= $this->date_modified;
2333
            $_SESSION['o_lock_on'] = $this->object_name;
2334
        }
2335
        $this->processed_dates_times = array();
2336
        $this->check_date_relationships_load();
2337
2338
        if(isset($this->custom_fields))
2339
        {
2340
            $this->custom_fields->fill_relationships();
2341
        }
2342
2343
		$this->is_updated_dependent_fields = false;
2344
        $this->fill_in_additional_detail_fields();
2345
        $this->fill_in_relationship_fields();
2346
// save related fields values for audit
2347
         foreach ($this->get_related_fields() as $rel_field_name)
2348
         {
2349
             $field_name = $rel_field_name['name'];
2350
             if (! empty($this->$field_name))
2351
             {
2352
                 $this->fetched_rel_row[$rel_field_name['name']] = $this->$field_name;
2353
             }
2354
         }
2355
        //make a copy of fields in the relationship_fields array. These field values will be used to
2356
        //clear relationship.
2357
        foreach ( $this->field_defs as $key => $def )
2358
        {
2359
            if (isset($def [ 'type' ]) && $def [ 'type' ] == 'relate' && isset ( $def [ 'id_name'] ) && isset ( $def [ 'link'] ) && isset ( $def[ 'save' ])) {
2360
                if (isset($this->$key)) {
2361
                    $this->rel_fields_before_value[$key]=$this->$key;
2362
                    if (isset($this->$def [ 'id_name']))
2363
                        $this->rel_fields_before_value[$def [ 'id_name']]=$this->$def [ 'id_name'];
2364
                }
2365
                else
2366
                    $this->rel_fields_before_value[$key]=null;
2367
           }
2368
        }
2369
        if (isset($this->relationship_fields) && is_array($this->relationship_fields))
2370
        {
2371
            foreach ($this->relationship_fields as $rel_id=>$rel_name)
2372
            {
2373
                if (isset($this->$rel_id))
2374
                    $this->rel_fields_before_value[$rel_id]=$this->$rel_id;
2375
                else
2376
                    $this->rel_fields_before_value[$rel_id]=null;
2377
            }
2378
        }
2379
2380
        // call the custom business logic
2381
        $custom_logic_arguments['id'] = $id;
2382
        $custom_logic_arguments['encode'] = $encode;
2383
        $this->call_custom_logic("after_retrieve", $custom_logic_arguments);
2384
        unset($custom_logic_arguments);
2385
        return $this;
2386
    }
2387
2388
    /**
2389
     * Sets value from fetched row into the bean.
2390
     *
2391
     * @param array $row Fetched row
2392
     * @todo loop through vardefs instead
2393
     * @internal runs into an issue when populating from field_defs for users - corrupts user prefs
2394
     *
2395
     * Internal function, do not override.
2396
     */
2397
    function populateFromRow($row)
2398
    {
2399
        $nullvalue='';
2400
        foreach($this->field_defs as $field=>$field_value)
2401
        {
2402
            if(($field == 'user_preferences' && $this->module_dir == 'Users') || ($field == 'internal' && $this->module_dir == 'Cases') )
2403
                continue;
2404
            if(isset($row[$field]))
2405
            {
2406
                $this->$field = $row[$field];
2407
                $owner = $field . '_owner';
2408
                if(!empty($row[$owner])){
2409
                    $this->$owner = $row[$owner];
2410
                }
2411
            }
2412
            else
2413
            {
2414
                $this->$field = $nullvalue;
2415
            }
2416
        }
2417
    }
2418
2419
2420
2421
    /**
2422
    * Add any required joins to the list count query.  The joins are required if there
2423
    * is a field in the $where clause that needs to be joined.
2424
    *
2425
    * @param string $query
2426
    * @param string $where
2427
    *
2428
    * Internal Function, do Not override.
2429
    */
2430
    function add_list_count_joins(&$query, $where)
2431
    {
2432
        $custom_join = $this->getCustomJoin();
2433
        $query .= $custom_join['join'];
2434
2435
    }
2436
2437
    /**
2438
    * Changes the select expression of the given query to be 'count(*)' so you
2439
    * can get the number of items the query will return.  This is used to
2440
    * populate the upper limit on ListViews.
2441
     *
2442
     * @param string $query Select query string
2443
     * @return string count query
2444
     *
2445
     * Internal function, do not override.
2446
    */
2447
    function create_list_count_query($query)
2448
    {
2449
        // remove the 'order by' clause which is expected to be at the end of the query
2450
        $pattern = '/\sORDER BY.*/is';  // ignores the case
2451
        $replacement = '';
2452
        $query = preg_replace($pattern, $replacement, $query);
2453
        //handle distinct clause
2454
        $star = '*';
2455
        if(substr_count(strtolower($query), 'distinct')){
2456
            if (!empty($this->seed) && !empty($this->seed->table_name ))
2457
                $star = 'DISTINCT ' . $this->seed->table_name . '.id';
2458
            else
2459
                $star = 'DISTINCT ' . $this->table_name . '.id';
2460
2461
        }
2462
2463
        // change the select expression to 'count(*)'
2464
        $pattern = '/SELECT(.*?)(\s){1}FROM(\s){1}/is';  // ignores the case
2465
        $replacement = 'SELECT count(' . $star . ') c FROM ';
2466
2467
        //if the passed query has union clause then replace all instances of the pattern.
2468
        //this is very rare. I have seen this happening only from projects module.
2469
        //in addition to this added a condition that has  union clause and uses
2470
        //sub-selects.
2471
        if (strstr($query," UNION ALL ") !== false) {
2472
2473
            //separate out all the queries.
2474
            $union_qs=explode(" UNION ALL ", $query);
2475
            foreach ($union_qs as $key=>$union_query) {
2476
                $star = '*';
2477
                preg_match($pattern, $union_query, $matches);
2478
                if (!empty($matches)) {
2479
                    if (stristr($matches[0], "distinct")) {
2480
                        if (!empty($this->seed) && !empty($this->seed->table_name ))
2481
                            $star = 'DISTINCT ' . $this->seed->table_name . '.id';
2482
                        else
2483
                            $star = 'DISTINCT ' . $this->table_name . '.id';
2484
                    }
2485
                } // if
2486
                $replacement = 'SELECT count(' . $star . ') c FROM ';
2487
                $union_qs[$key] = preg_replace($pattern, $replacement, $union_query,1);
2488
            }
2489
            $modified_select_query=implode(" UNION ALL ",$union_qs);
2490
        } else {
2491
            $modified_select_query = preg_replace($pattern, $replacement, $query,1);
2492
        }
2493
2494
2495
        return $modified_select_query;
2496
    }
2497
2498
    /**
2499
    * This function returns a paged list of the current object type.  It is intended to allow for
2500
    * hopping back and forth through pages of data.  It only retrieves what is on the current page.
2501
    *
2502
    * @internal This method must be called on a new instance.  It trashes the values of all the fields in the current one.
2503
    * @param string $order_by
2504
    * @param string $where Additional where clause
2505
    * @param int $row_offset Optaional,default 0, starting row number
2506
    * @param init $limit Optional, default -1
2507
    * @param int $max Optional, default -1
2508
    * @param boolean $show_deleted Optional, default 0, if set to 1 system will show deleted records.
2509
    * @return array Fetched data.
2510
    *
2511
    * Internal function, do not override.
2512
    *
2513
    */
2514
    function get_list($order_by = "", $where = "", $row_offset = 0, $limit=-1, $max=-1, $show_deleted = 0, $singleSelect=false, $select_fields = array())
2515
    {
2516
        $GLOBALS['log']->debug("get_list:  order_by = '$order_by' and where = '$where' and limit = '$limit'");
2517
        if(isset($_SESSION['show_deleted']))
2518
        {
2519
            $show_deleted = 1;
2520
        }
2521
2522
        if($this->bean_implements('ACL') && ACLController::requireOwner($this->module_dir, 'list') )
2523
        {
2524
            global $current_user;
2525
            $owner_where = $this->getOwnerWhere($current_user->id);
2526
2527
            //rrs - because $this->getOwnerWhere() can return '' we need to be sure to check for it and
2528
            //handle it properly else you could get into a situation where you are create a where stmt like
2529
            //WHERE .. AND ''
2530
            if(!empty($owner_where)){
2531
                if(empty($where)){
2532
                    $where = $owner_where;
2533
                }else{
2534
                    $where .= ' AND '.  $owner_where;
2535
                }
2536
            }
2537
        }
2538
        $query = $this->create_new_list_query($order_by, $where,$select_fields,array(), $show_deleted,'',false,null,$singleSelect);
2539
        return $this->process_list_query($query, $row_offset, $limit, $max, $where);
2540
    }
2541
2542
    /**
2543
    * Prefixes column names with this bean's table name.
2544
    *
2545
    * @param string $order_by  Order by clause to be processed
2546
    * @param SugarBean $submodule name of the module this order by clause is for
2547
    * @param boolean $suppress_table_name Whether table name should be suppressed
2548
    * @return string Processed order by clause
2549
    *
2550
    * Internal function, do not override.
2551
    */
2552
    public function process_order_by($order_by, $submodule = null, $suppress_table_name = false)
2553
    {
2554
        if (empty($order_by))
2555
            return $order_by;
2556
        //submodule is empty,this is for list object in focus
2557
        if (empty($submodule))
2558
        {
2559
            $bean_queried = $this;
2560
        }
2561
        else
2562
        {
2563
            //submodule is set, so this is for subpanel, use submodule
2564
            $bean_queried = $submodule;
2565
        }
2566
2567
        $raw_elements = explode(',', $order_by);
2568
        $valid_elements = array();
2569
        foreach ($raw_elements as $key => $value) {
2570
2571
            $is_valid = false;
2572
2573
            //value might have ascending and descending decorations
2574
            $list_column = preg_split('/\s/', trim($value), 2);
2575
            $list_column = array_map('trim', $list_column);
2576
2577
            $list_column_name = $list_column[0];
2578
            if (isset($bean_queried->field_defs[$list_column_name])) {
2579
                $field_defs = $bean_queried->field_defs[$list_column_name];
2580
                $source = isset($field_defs['source']) ? $field_defs['source'] : 'db';
2581
2582
                if (empty($field_defs['table']) && !$suppress_table_name) {
2583
                    if ($source == 'db') {
2584
                        $list_column[0] = $bean_queried->table_name . '.' . $list_column[0] ;
2585
                    } elseif ($source == 'custom_fields') {
2586
                        $list_column[0] = $bean_queried->table_name . '_cstm.' . $list_column[0] ;
2587
                    }
2588
                }
2589
2590
                // Bug 38803 - Use CONVERT() function when doing an order by on ntext, text, and image fields
2591
                if ($source != 'non-db'
2592
                    && $this->db->isTextType($this->db->getFieldType($bean_queried->field_defs[$list_column_name]))) {
2593
                    // array(10000) is for db2 only. It tells db2manager to cast 'clob' to varchar(10000) for this 'sort by' column
2594
                    $list_column[0] = $this->db->convert($list_column[0], "text2char", array(10000));
2595
                }
2596
2597
                $is_valid = true;
2598
2599
                if (isset($list_column[1])) {
2600
                    switch (strtolower($list_column[1])) {
2601
                        case 'asc':
2602
                        case 'desc':
2603
                            break;
2604
                        default:
2605
                            $GLOBALS['log']->debug("process_order_by: ($list_column[1]) is not a valid order.");
2606
                            unset($list_column[1]);
2607
                            break;
2608
                    }
2609
                }
2610
            } else {
2611
                $GLOBALS['log']->debug("process_order_by: ($list_column[0]) does not have a vardef entry.");
2612
            }
2613
2614
            if ($is_valid) {
2615
                $valid_elements[$key] = implode(' ', $list_column);
2616
            }
2617
        }
2618
2619
        return implode(', ', $valid_elements);
2620
2621
    }
2622
2623
2624
    /**
2625
    * Returns a detail object like retrieving of the current object type.
2626
    *
2627
    * It is intended for use in navigation buttons on the DetailView.  It will pass an offset and limit argument to the sql query.
2628
    * @internal This method must be called on a new instance.  It overrides the values of all the fields in the current one.
2629
    *
2630
    * @param string $order_by
2631
    * @param string $where Additional where clause
2632
    * @param int $row_offset Optaional,default 0, starting row number
2633
    * @param init $limit Optional, default -1
2634
    * @param int $max Optional, default -1
2635
    * @param boolean $show_deleted Optioanl, default 0, if set to 1 system will show deleted records.
2636
    * @return array Fetched data.
2637
    *
2638
    * Internal function, do not override.
2639
    */
2640
    function get_detail($order_by = "", $where = "",  $offset = 0, $row_offset = 0, $limit=-1, $max=-1, $show_deleted = 0)
2641
    {
2642
        $GLOBALS['log']->debug("get_detail:  order_by = '$order_by' and where = '$where' and limit = '$limit' and offset = '$offset'");
2643
        if(isset($_SESSION['show_deleted']))
2644
        {
2645
            $show_deleted = 1;
2646
        }
2647
2648
        if($this->bean_implements('ACL') && ACLController::requireOwner($this->module_dir, 'list') )
2649
        {
2650
            global $current_user;
2651
            $owner_where = $this->getOwnerWhere($current_user->id);
2652
2653
            if(empty($where))
2654
            {
2655
                $where = $owner_where;
2656
            }
2657
            else
2658
            {
2659
                $where .= ' AND '.  $owner_where;
2660
            }
2661
        }
2662
2663
		/* BEGIN - SECURITY GROUPS */
2664
    	if($this->bean_implements('ACL') && ACLController::requireSecurityGroup($this->module_dir, 'list') )
2665
    	{
2666
			require_once('modules/SecurityGroups/SecurityGroup.php');
2667
    		global $current_user;
2668
    		$owner_where = $this->getOwnerWhere($current_user->id);
2669
    		$group_where = SecurityGroup::getGroupWhere($this->table_name,$this->module_dir,$current_user->id);
2670
	    	if(!empty($owner_where)){
2671
				if(empty($where))
2672
	    		{
2673
	    			$where = " (".  $owner_where." or ".$group_where.") ";
2674
	    		} else {
2675
	    			$where .= " AND (".  $owner_where." or ".$group_where.") ";
2676
	    		}
2677
			} else {
2678
				$where .= ' AND '.  $group_where;
2679
			}
2680
    	}
2681
    	/* END - SECURITY GROUPS */
2682
        $query = $this->create_new_list_query($order_by, $where,array(),array(), $show_deleted, $offset);
2683
2684
        //Add Limit and Offset to query
2685
        //$query .= " LIMIT 1 OFFSET $offset";
2686
2687
        return $this->process_detail_query($query, $row_offset, $limit, $max, $where, $offset);
2688
    }
2689
2690
    /**
2691
    * Fetches data from all related tables.
2692
    *
2693
    * @param object $child_seed
2694
    * @param string $related_field_name relation to fetch data for
2695
    * @param string $order_by Optional, default empty
2696
    * @param string $where Optional, additional where clause
2697
    * @return array Fetched data.
2698
    *
2699
    * Internal function, do not override.
2700
    */
2701
    function get_related_list($child_seed,$related_field_name, $order_by = "", $where = "",
2702
    $row_offset = 0, $limit=-1, $max=-1, $show_deleted = 0)
2703
    {
2704
        global $layout_edit_mode;
2705
        $query_array = array();
2706
2707
        if(isset($layout_edit_mode) && $layout_edit_mode)
2708
        {
2709
            $response = array();
2710
            $child_seed->assign_display_fields($child_seed->module_dir);
2711
            $response['list'] = array($child_seed);
2712
            $response['row_count'] = 1;
2713
            $response['next_offset'] = 0;
2714
            $response['previous_offset'] = 0;
2715
2716
            return $response;
2717
        }
2718
        $GLOBALS['log']->debug("get_related_list:  order_by = '$order_by' and where = '$where' and limit = '$limit'");
2719
        if(isset($_SESSION['show_deleted']))
2720
        {
2721
            $show_deleted = 1;
2722
        }
2723
2724
        $this->load_relationship($related_field_name);
2725
2726
        if ($this->$related_field_name instanceof Link) {
2727
2728
            $query_array = $this->$related_field_name->getQuery(true);
2729
        } else {
2730
2731
            $query_array = $this->$related_field_name->getQuery(array(
2732
                "return_as_array" => true,
2733
                'where' => '1=1' // hook for 'where' clause in M2MRelationship file
2734
                    ));
2735
        }
2736
2737
        $entire_where = $query_array['where'];
2738
        if(!empty($where))
2739
        {
2740
            if(empty($entire_where))
2741
            {
2742
                $entire_where = ' WHERE ' . $where;
2743
            }
2744
            else
2745
            {
2746
                $entire_where .= ' AND ' . $where;
2747
            }
2748
        }
2749
2750
        $query = 'SELECT '.$child_seed->table_name.'.* ' . $query_array['from'] . ' ' . $entire_where;
2751
        if(!empty($order_by))
2752
        {
2753
            $query .= " ORDER BY " . $order_by;
2754
        }
2755
2756
        return $child_seed->process_list_query($query, $row_offset, $limit, $max, $where);
2757
    }
2758
2759
2760
    protected static function build_sub_queries_for_union($subpanel_list, $subpanel_def, $parentbean, $order_by)
2761
    {
2762
        global $layout_edit_mode, $beanFiles, $beanList;
2763
        $subqueries = array();
2764
        foreach($subpanel_list as $this_subpanel)
2765
        {
2766
            if(!$this_subpanel->isDatasourceFunction() || ($this_subpanel->isDatasourceFunction()
2767
                && isset($this_subpanel->_instance_properties['generate_select'])
2768
                && $this_subpanel->_instance_properties['generate_select']==true))
2769
            {
2770
                //the custom query function must return an array with
2771
                if ($this_subpanel->isDatasourceFunction()) {
2772
                    $shortcut_function_name = $this_subpanel->get_data_source_name();
2773
                    $parameters=$this_subpanel->get_function_parameters();
2774
                    if (!empty($parameters))
2775
                    {
2776
                        //if the import file function is set, then import the file to call the custom function from
2777
                        if (is_array($parameters)  && isset($parameters['import_function_file'])){
2778
                            //this call may happen multiple times, so only require if function does not exist
2779
                            if(!function_exists($shortcut_function_name)){
2780
                                require_once($parameters['import_function_file']);
2781
                            }
2782
                            //call function from required file
2783
                            $query_array = $shortcut_function_name($parameters);
2784
                        }else{
2785
                            //call function from parent bean
2786
                            $query_array = $parentbean->$shortcut_function_name($parameters);
2787
                        }
2788
                    }
2789
                    else
2790
                    {
2791
                        $query_array = $parentbean->$shortcut_function_name();
2792
                    }
2793
                }  else {
2794
                    $related_field_name = $this_subpanel->get_data_source_name();
2795
                    if (!$parentbean->load_relationship($related_field_name)){
2796
                        unset ($parentbean->$related_field_name);
2797
                        continue;
2798
                    }
2799
                    $query_array = $parentbean->$related_field_name->getSubpanelQuery(array(), true);
2800
                }
2801
                $table_where = preg_replace('/^\s*WHERE/i', '', $this_subpanel->get_where());
2802
                $where_definition = preg_replace('/^\s*WHERE/i', '', $query_array['where']);
2803
2804
                if(!empty($table_where))
2805
                {
2806
                    if(empty($where_definition))
2807
                    {
2808
                        $where_definition = $table_where;
2809
                    }
2810
                    else
2811
                    {
2812
                        $where_definition .= ' AND ' . $table_where;
2813
                    }
2814
                }
2815
2816
                $submodulename = $this_subpanel->_instance_properties['module'];
2817
                $submoduleclass = $beanList[$submodulename];
2818
                //require_once($beanFiles[$submoduleclass]);
2819
2820
                /** @var SugarBean $submodule */
2821
                $submodule = new $submoduleclass();
2822
                $subwhere = $where_definition;
2823
2824
2825
2826
                $list_fields = $this_subpanel->get_list_fields();
2827
                foreach($list_fields as $list_key=>$list_field)
2828
                {
2829
                    if(isset($list_field['usage']) && $list_field['usage'] == 'display_only')
2830
                    {
2831
                        unset($list_fields[$list_key]);
2832
                    }
2833
                }
2834
2835
2836
                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'))
2837
                {
2838
                    $order_by = $submodule->table_name .'.'. $order_by;
2839
                }
2840
                $table_name = $this_subpanel->table_name;
2841
                $panel_name=$this_subpanel->name;
2842
                $params = array();
2843
                $params['distinct'] = $this_subpanel->distinct_query();
2844
2845
                $params['joined_tables'] = $query_array['join_tables'];
2846
                $params['include_custom_fields'] = !$subpanel_def->isCollection();
2847
                $params['collection_list'] = $subpanel_def->get_inst_prop_value('collection_list');
2848
2849
                // use single select in case when sorting by relate field
2850
                $singleSelect = $submodule->is_relate_field($order_by);
2851
2852
                $subquery = $submodule->create_new_list_query('',$subwhere ,$list_fields,$params, 0,'', true,$parentbean, $singleSelect);
2853
2854
                $subquery['select'] = $subquery['select']." , '$panel_name' panel_name ";
2855
                $subquery['from'] = $subquery['from'].$query_array['join'];
2856
                $subquery['query_array'] = $query_array;
2857
                $subquery['params'] = $params;
2858
2859
                $subqueries[] = $subquery;
2860
            }
2861
        }
2862
        return $subqueries;
2863
    }
2864
2865
    /**
2866
     * Constructs a query to fetch data for supanels and list views
2867
     *
2868
     * It constructs union queries for activities subpanel.
2869
     *
2870
     * @param SugarBean $parentbean constructing queries for link attributes in this bean
2871
     * @param string $order_by Optional, order by clause
2872
     * @param string $sort_order Optional, sort order
2873
     * @param string $where Optional, additional where clause
2874
     * @param int $row_offset
2875
     * @param int $limit
2876
     * @param int $max
2877
     * @param int $show_deleted
2878
     * @param aSubPanel $subpanel_def
2879
     *
2880
     * @return array
2881
     *
2882
     * Internal Function, do not overide.
2883
     */
2884
    static function get_union_related_list($parentbean, $order_by = "", $sort_order='', $where = "",
2885
    $row_offset = 0, $limit=-1, $max=-1, $show_deleted = 0, $subpanel_def)
2886
    {
2887
        $secondary_queries = array();
2888
        global $layout_edit_mode, $beanFiles, $beanList;
2889
2890
        if(isset($_SESSION['show_deleted']))
2891
        {
2892
            $show_deleted = 1;
2893
        }
2894
        $final_query = '';
2895
        $final_query_rows = '';
2896
        $subpanel_list=array();
2897
        if ($subpanel_def->isCollection())
2898
        {
2899
            $subpanel_def->load_sub_subpanels();
2900
            $subpanel_list=$subpanel_def->sub_subpanels;
2901
        }
2902
        else
2903
        {
2904
            $subpanel_list[]=$subpanel_def;
2905
        }
2906
2907
        $first = true;
2908
2909
        //Breaking the building process into two loops. The first loop gets a list of all the sub-queries.
2910
        //The second loop merges the queries and forces them to select the same number of columns
2911
        //All columns in a sub-subpanel group must have the same aliases
2912
        //If the subpanel is a datasource function, it can't be a collection so we just poll that function for the and return that
2913
        foreach($subpanel_list as $this_subpanel)
2914
        {
2915
            if($this_subpanel->isDatasourceFunction() && empty($this_subpanel->_instance_properties['generate_select']))
2916
            {
2917
                $shortcut_function_name = $this_subpanel->get_data_source_name();
2918
                $parameters=$this_subpanel->get_function_parameters();
2919
                if (!empty($parameters))
2920
                {
2921
                    //if the import file function is set, then import the file to call the custom function from
2922
                    if (is_array($parameters)  && isset($parameters['import_function_file'])){
2923
                        //this call may happen multiple times, so only require if function does not exist
2924
                        if(!function_exists($shortcut_function_name)){
2925
                            require_once($parameters['import_function_file']);
2926
                        }
2927
                        //call function from required file
2928
                        $tmp_final_query =  $shortcut_function_name($parameters);
2929
                    }else{
2930
                        //call function from parent bean
2931
                        $tmp_final_query =  $parentbean->$shortcut_function_name($parameters);
2932
                    }
2933
                } else {
2934
                    $tmp_final_query = $parentbean->$shortcut_function_name();
2935
                }
2936
                if(!$first)
2937
                    {
2938
                        $final_query_rows .= ' UNION ALL ( '.$parentbean->create_list_count_query($tmp_final_query, $parameters) . ' )';
2939
                        $final_query .= ' UNION ALL ( '.$tmp_final_query . ' )';
2940
                    } else {
2941
                        $final_query_rows = '(' . $parentbean->create_list_count_query($tmp_final_query, $parameters) . ')';
2942
                        $final_query = '(' . $tmp_final_query . ')';
2943
                        $first = false;
2944
                    }
2945
                }
2946
        }
2947
        //If final_query is still empty, its time to build the sub-queries
2948
        if (empty($final_query))
2949
        {
2950
            $subqueries = SugarBean::build_sub_queries_for_union($subpanel_list, $subpanel_def, $parentbean, $order_by);
2951
            $all_fields = array();
2952
            foreach($subqueries as $i => $subquery)
2953
            {
2954
                $query_fields = $GLOBALS['db']->getSelectFieldsFromQuery($subquery['select']);
2955
                foreach($query_fields as $field => $select)
2956
                {
2957
                    if (!in_array($field, $all_fields))
2958
                        $all_fields[] = $field;
2959
                }
2960
                $subqueries[$i]['query_fields'] = $query_fields;
2961
            }
2962
            $first = true;
2963
            //Now ensure the queries have the same set of fields in the same order.
2964
            foreach($subqueries as $subquery)
2965
            {
2966
                $subquery['select'] = "SELECT";
2967
                foreach($all_fields as $field)
2968
                {
2969
                    if (!isset($subquery['query_fields'][$field]))
2970
                    {
2971
                        $subquery['select'] .= " NULL $field,";
2972
                    }
2973
                    else
2974
                    {
2975
                        $subquery['select'] .= " {$subquery['query_fields'][$field]},";
2976
                    }
2977
                }
2978
                $subquery['select'] = substr($subquery['select'], 0 , strlen($subquery['select']) - 1);
2979
                //Put the query into the final_query
2980
                $query =  $subquery['select'] . " " . $subquery['from'] . " " . $subquery['where'];
2981
                if(!$first)
2982
                {
2983
                    $query = ' UNION ALL ( '.$query . ' )';
2984
                    $final_query_rows .= " UNION ALL ";
2985
                } else {
2986
                    $query = '(' . $query . ')';
2987
                    $first = false;
2988
                }
2989
                $query_array = $subquery['query_array'];
2990
                $select_position=strpos($query_array['select'],"SELECT");
2991
                $distinct_position=strpos($query_array['select'],"DISTINCT");
2992
                if (!empty($subquery['params']['distinct']) && !empty($subpanel_def->table_name))
2993
                {
2994
                    $query_rows = "( SELECT count(DISTINCT ". $subpanel_def->table_name . ".id)".  $subquery['from_min'].$query_array['join']. $subquery['where'].' )';
2995
                }
2996
                elseif ($select_position !== false && $distinct_position!= false)
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $distinct_position of type integer to the boolean false. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
2997
                {
2998
                    $query_rows = "( ".substr_replace($query_array['select'],"SELECT count(",$select_position,6). ")" .  $subquery['from_min'].$query_array['join']. $subquery['where'].' )';
2999
                }
3000
                else
3001
                {
3002
                    //resort to default behavior.
3003
                    $query_rows = "( SELECT count(*)".  $subquery['from_min'].$query_array['join']. $subquery['where'].' )';
3004
                }
3005
                if(!empty($subquery['secondary_select']))
3006
                {
3007
3008
                    $subquerystring= $subquery['secondary_select'] . $subquery['secondary_from'].$query_array['join']. $subquery['where'];
3009
                    if (!empty($subquery['secondary_where']))
3010
                    {
3011
                        if (empty($subquery['where']))
3012
                        {
3013
                            $subquerystring.=" WHERE " .$subquery['secondary_where'];
3014
                        }
3015
                        else
3016
                        {
3017
                            $subquerystring.=" AND " .$subquery['secondary_where'];
3018
                        }
3019
                    }
3020
                    $secondary_queries[]=$subquerystring;
3021
                }
3022
                $final_query .= $query;
3023
                $final_query_rows .= $query_rows;
3024
            }
3025
        }
3026
3027
        if(!empty($order_by))
3028
        {
3029
            $isCollection = $subpanel_def->isCollection();
3030
            if ($isCollection) {
3031
                /** @var aSubPanel $header */
3032
                $header = $subpanel_def->get_header_panel_def();
3033
                $submodule = $header->template_instance;
3034
                $suppress_table_name = true;
3035
            } else {
3036
                $submodule = $subpanel_def->template_instance;
3037
                $suppress_table_name = false;
3038
            }
3039
3040
            if (!empty($sort_order)) {
3041
                $order_by .= ' ' . $sort_order;
3042
            }
3043
3044
            $order_by = $parentbean->process_order_by($order_by, $submodule, $suppress_table_name);
3045
            if (!empty($order_by)) {
3046
                $final_query .= ' ORDER BY ' . $order_by;
3047
            }
3048
        }
3049
3050
3051
        if(isset($layout_edit_mode) && $layout_edit_mode)
3052
        {
3053
            $response = array();
3054
            if(!empty($submodule))
3055
            {
3056
                $submodule->assign_display_fields($submodule->module_dir);
3057
                $response['list'] = array($submodule);
3058
            }
3059
            else
3060
        {
3061
                $response['list'] = array();
3062
            }
3063
            $response['parent_data'] = array();
3064
            $response['row_count'] = 1;
3065
            $response['next_offset'] = 0;
3066
            $response['previous_offset'] = 0;
3067
3068
            return $response;
3069
        }
3070
3071
        return $parentbean->process_union_list_query($parentbean, $final_query, $row_offset, $limit, $max, '',$subpanel_def, $final_query_rows, $secondary_queries);
3072
    }
3073
3074
3075
    /**
3076
    * Returns a full (ie non-paged) list of the current object type.
3077
    *
3078
    * @param string $order_by the order by SQL parameter. defaults to ""
3079
    * @param string $where where clause. defaults to ""
3080
    * @param boolean $check_dates. defaults to false
0 ignored issues
show
Documentation introduced by
There is no parameter named $check_dates.. Did you maybe mean $check_dates?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
3081
    * @param int $show_deleted show deleted records. defaults to 0
3082
    */
3083
    function get_full_list($order_by = "", $where = "", $check_dates=false, $show_deleted = 0)
3084
    {
3085
        $GLOBALS['log']->debug("get_full_list:  order_by = '$order_by' and where = '$where'");
3086
        if(isset($_SESSION['show_deleted']))
3087
        {
3088
            $show_deleted = 1;
3089
        }
3090
        $query = $this->create_new_list_query($order_by, $where,array(),array(), $show_deleted);
3091
        return $this->process_full_list_query($query, $check_dates);
3092
    }
3093
3094
    /**
3095
     * Return the list query used by the list views and export button. Next generation of create_new_list_query function.
3096
     *
3097
     * Override this function to return a custom query.
3098
     *
3099
     * @param string $order_by custom order by clause
3100
     * @param string $where custom where clause
3101
     * @param array $filter Optioanal
3102
     * @param array $params Optional     *
3103
     * @param int $show_deleted Optional, default 0, show deleted records is set to 1.
3104
     * @param string $join_type
3105
     * @param boolean $return_array Optional, default false, response as array
3106
     * @param object $parentbean creating a subquery for this bean.
3107
     * @param boolean $singleSelect Optional, default false.
3108
     * @return String select query string, optionally an array value will be returned if $return_array= true.
3109
     */
3110
	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)
3111
    {
3112
        global $beanFiles, $beanList;
3113
        $selectedFields = array();
3114
        $secondarySelectedFields = array();
3115
        $ret_array = array();
3116
        $distinct = '';
3117
        if($this->bean_implements('ACL') && ACLController::requireOwner($this->module_dir, 'list') )
3118
        {
3119
            global $current_user;
3120
            $owner_where = $this->getOwnerWhere($current_user->id);
3121
            if(empty($where))
3122
            {
3123
                $where = $owner_where;
3124
            }
3125
            else
3126
            {
3127
                $where .= ' AND '.  $owner_where;
3128
            }
3129
        }
3130
		/* BEGIN - SECURITY GROUPS */
3131
    	global $current_user, $sugar_config;
3132
    	if($this->module_dir == 'Users' && !is_admin($current_user)
3133
				&& isset($sugar_config['securitysuite_filter_user_list'])
3134
				&& $sugar_config['securitysuite_filter_user_list'] == true
3135
    	) {
3136
			require_once('modules/SecurityGroups/SecurityGroup.php');
3137
    		global $current_user;
3138
    		$group_where = SecurityGroup::getGroupUsersWhere($current_user->id);
3139
    		//$group_where = "user_name = 'admin'";
3140
    		if(empty($where))
3141
    		{
3142
    			$where = " (".$group_where.") ";
3143
    		} else {
3144
    			$where .= " AND (".$group_where.") ";
3145
    		}
3146
    	} else
3147
    	if($this->bean_implements('ACL') && ACLController::requireSecurityGroup($this->module_dir, 'list') )
3148
    	{
3149
			require_once('modules/SecurityGroups/SecurityGroup.php');
3150
    		global $current_user;
3151
    		$owner_where = $this->getOwnerWhere($current_user->id);
3152
    		$group_where = SecurityGroup::getGroupWhere($this->table_name,$this->module_dir,$current_user->id);
3153
	    	if(!empty($owner_where)){
3154
				if(empty($where))
3155
	    		{
3156
	    			$where = " (".  $owner_where." or ".$group_where.") ";
3157
	    		} else {
3158
	    			$where .= " AND (".  $owner_where." or ".$group_where.") ";
3159
	    		}
3160
			} else {
3161
				$where .= ' AND '.  $group_where;
3162
			}
3163
    	}
3164
    	/* END - SECURITY GROUPS */
3165
        if(!empty($params['distinct']))
3166
        {
3167
            $distinct = ' DISTINCT ';
3168
        }
3169
        if(empty($filter))
3170
        {
3171
            $ret_array['select'] = " SELECT $distinct $this->table_name.* ";
3172
        }
3173
        else
3174
        {
3175
            $ret_array['select'] = " SELECT $distinct $this->table_name.id ";
3176
        }
3177
        $ret_array['from'] = " FROM $this->table_name ";
3178
        $ret_array['from_min'] = $ret_array['from'];
3179
        $ret_array['secondary_from'] = $ret_array['from'] ;
3180
        $ret_array['where'] = '';
3181
        $ret_array['order_by'] = '';
3182
        //secondary selects are selects that need to be run after the primary query to retrieve additional info on main
3183
        if($singleSelect)
3184
        {
3185
            $ret_array['secondary_select']=& $ret_array['select'];
3186
            $ret_array['secondary_from'] = & $ret_array['from'];
3187
        }
3188
        else
3189
        {
3190
            $ret_array['secondary_select'] = '';
3191
        }
3192
        $custom_join = $this->getCustomJoin( empty($filter)? true: $filter );
3193
        if((!isset($params['include_custom_fields']) || $params['include_custom_fields']))
3194
        {
3195
            $ret_array['select'] .= $custom_join['select'];
3196
        }
3197
        $ret_array['from'] .= $custom_join['join'];
3198
        // Bug 52490 - Captivea (Sve) - To be able to add custom fields inside where clause in a subpanel
3199
        $ret_array['from_min'] .= $custom_join['join'];
3200
        $jtcount = 0;
3201
        //LOOP AROUND FOR FIXIN VARDEF ISSUES
3202
        require('include/VarDefHandler/listvardefoverride.php');
3203
        if (file_exists('custom/include/VarDefHandler/listvardefoverride.php'))
3204
        {
3205
            require('custom/include/VarDefHandler/listvardefoverride.php');
3206
        }
3207
3208
        $joined_tables = array();
3209
        if(!empty($params['joined_tables']))
3210
        {
3211
            foreach($params['joined_tables'] as $table)
3212
            {
3213
                $joined_tables[$table] = 1;
3214
            }
3215
        }
3216
3217
        if(!empty($filter))
3218
        {
3219
            $filterKeys = array_keys($filter);
3220
            if(is_numeric($filterKeys[0]))
3221
            {
3222
                $fields = array();
3223
                foreach($filter as $field)
3224
                {
3225
                    $field = strtolower($field);
3226
                    //remove out id field so we don't duplicate it
3227
                    if ( $field == 'id' && !empty($filter) ) {
3228
                        continue;
3229
                    }
3230
                    if(isset($this->field_defs[$field]))
3231
                    {
3232
                        $fields[$field]= $this->field_defs[$field];
3233
                    }
3234
                    else
3235
                    {
3236
                        $fields[$field] = array('force_exists'=>true);
3237
                    }
3238
                }
3239
            }else{
3240
                $fields = 	$filter;
3241
            }
3242
        }
3243
        else
3244
        {
3245
            $fields = 	$this->field_defs;
3246
        }
3247
3248
        $used_join_key = array();
3249
3250
	//walk through the fields and for every relationship field add their relationship_info field
3251
	//relationshipfield-aliases are resolved in SugarBean::create_new_list_query through their relationship_info field
3252
	$addrelate = array();
3253
	foreach($fields as $field=>$value)
3254
	{
3255
		if (isset($this->field_defs[$field]) && isset($this->field_defs[$field]['source']) && 
3256
			$this->field_defs[$field]['source'] == 'non-db')
3257
		{
3258
			$addrelatefield = $this->get_relationship_field($field);
3259
			if ($addrelatefield)
3260
				$addrelate[$addrelatefield] = true;
3261
		}
3262
		if(!empty($this->field_defs[$field]['id_name'])){
3263
			$addrelate[$this->field_defs[$field]['id_name']] = true;
3264
		}
3265
	}
3266
3267
	$fields = array_merge($addrelate, $fields);
3268
3269
        foreach($fields as $field=>$value)
3270
        {
3271
            //alias is used to alias field names
3272
            $alias='';
3273
            if 	(isset($value['alias']))
3274
            {
3275
                $alias =' as ' . $value['alias'] . ' ';
3276
            }
3277
3278
            if(empty($this->field_defs[$field]) || !empty($value['force_blank']) )
3279
            {
3280
                if(!empty($filter) && isset($filter[$field]['force_exists']) && $filter[$field]['force_exists'])
3281
                {
3282
                    if ( isset($filter[$field]['force_default']) )
3283
                        $ret_array['select'] .= ", {$filter[$field]['force_default']} $field ";
3284
                    else
3285
                    //spaces are a fix for length issue problem with unions.  The union only returns the maximum number of characters from the first select statement.
3286
                        $ret_array['select'] .= ", '                                                                                                                                                                                                                                                              ' $field ";
3287
                }
3288
                continue;
3289
            }
3290
            else
3291
            {
3292
                $data = $this->field_defs[$field];
3293
            }
3294
3295
            //ignore fields that are a part of the collection and a field has been removed as a result of
3296
            //layout customization.. this happens in subpanel customizations, use case, from the contacts subpanel
3297
            //in opportunities module remove the contact_role/opportunity_role field.
3298
            if (isset($data['relationship_fields']) and !empty($data['relationship_fields']))
3299
            {
3300
		$process_field = false;
3301
                foreach ($data['relationship_fields'] as $field_name)
3302
                {
3303
                    if (isset($fields[$field_name]))
3304
                    {
3305
                        $process_field = true;
3306
                        break;
3307
                    }
3308
                }
3309
		
3310
            	if (!$process_field)
3311
                	continue;
3312
            }
3313
3314
            if(  (!isset($data['source']) || $data['source'] == 'db') && (!empty($alias) || !empty($filter) ))
3315
            {
3316
                $ret_array['select'] .= ", $this->table_name.$field $alias";
3317
                $selectedFields["$this->table_name.$field"] = true;
3318
            } else if(  (!isset($data['source']) || $data['source'] == 'custom_fields') && (!empty($alias) || !empty($filter) )) {
3319
                //add this column only if it has NOT already been added to select statement string
3320
                $colPos = strpos($ret_array['select'],"$this->table_name"."_cstm".".$field");
3321
                if(!$colPos || $colPos<0)
3322
                {
3323
                    $ret_array['select'] .= ", $this->table_name"."_cstm".".$field $alias";
3324
                }
3325
3326
                $selectedFields["$this->table_name.$field"] = true;
3327
            }
3328
3329
            if($data['type'] != 'relate' && isset($data['db_concat_fields']))
3330
            {
3331
                $ret_array['select'] .= ", " . $this->db->concat($this->table_name, $data['db_concat_fields']) . " as $field";
3332
                $selectedFields[$this->db->concat($this->table_name, $data['db_concat_fields'])] = true;
3333
            }
3334
            //Custom relate field or relate fields built in module builder which have no link field associated.
3335
            if ($data['type'] == 'relate' && (isset($data['custom_module']) || isset($data['ext2']))) {
3336
                $joinTableAlias = 'jt' . $jtcount;
3337
                $relateJoinInfo = $this->custom_fields->getRelateJoin($data, $joinTableAlias, false);
3338
                $ret_array['select'] .= $relateJoinInfo['select'];
3339
                $ret_array['from'] .= $relateJoinInfo['from'];
3340
                //Replace any references to the relationship in the where clause with the new alias
3341
                //If the link isn't set, assume that search used the local table for the field
3342
                $searchTable = isset($data['link']) ? $relateJoinInfo['rel_table'] : $this->table_name;
3343
                $field_name = $relateJoinInfo['rel_table'] . '.' . !empty($data['name'])?$data['name']:'name';
3344
                $where = preg_replace('/(^|[\s(])' . $field_name . '/' , '${1}' . $relateJoinInfo['name_field'], $where);
3345
                $jtcount++;
3346
            }
3347
            //Parent Field
3348
            if ($data['type'] == 'parent') {
3349
                //See if we need to join anything by inspecting the where clause
3350
                $match = preg_match('/(^|[\s(])parent_([a-zA-Z]+_?[a-zA-Z]+)_([a-zA-Z]+_?[a-zA-Z]+)\.name/', $where, $matches);
3351
                if ($match) {
3352
                    $joinTableAlias = 'jt' . $jtcount;
3353
                    $joinModule = $matches[2];
3354
                    $joinTable = $matches[3];
3355
                    $localTable = $this->table_name;
3356
                    if (!empty($data['custom_module'])) {
3357
                        $localTable .= '_cstm';
3358
                    }
3359
                    global $beanFiles, $beanList, $module;
3360
                    require_once($beanFiles[$beanList[$joinModule]]);
3361
                    $rel_mod = new $beanList[$joinModule]();
3362
                    $nameField = "$joinTableAlias.name";
3363
                    if (isset($rel_mod->field_defs['name']))
3364
                    {
3365
                        $name_field_def = $rel_mod->field_defs['name'];
3366
                        if(isset($name_field_def['db_concat_fields']))
3367
                        {
3368
                            $nameField = $this->db->concat($joinTableAlias, $name_field_def['db_concat_fields']);
3369
                        }
3370
                    }
3371
                    $ret_array['select'] .= ", $nameField {$data['name']} ";
3372
                    $ret_array['from'] .= " LEFT JOIN $joinTable $joinTableAlias
3373
                        ON $localTable.{$data['id_name']} = $joinTableAlias.id";
3374
                    //Replace any references to the relationship in the where clause with the new alias
3375
                    $where = preg_replace('/(^|[\s(])parent_' . $joinModule . '_' . $joinTable . '\.name/', '${1}' . $nameField, $where);
3376
                    $jtcount++;
3377
                }
3378
            }
3379
3380
            if ($this->is_relate_field($field))
3381
            {
3382
                $linkField = $data['link'];
3383
                $this->load_relationship($linkField);
3384
                if(!empty($this->$linkField))
3385
                {
3386
                    $params = array();
3387
                    if(empty($join_type))
3388
                    {
3389
                        $params['join_type'] = ' LEFT JOIN ';
3390
                    }
3391
                    else
3392
                    {
3393
                        $params['join_type'] = $join_type;
3394
                    }
3395
                    if(isset($data['join_name']))
3396
                    {
3397
                        $params['join_table_alias'] = $data['join_name'];
3398
                    }
3399
                    else
3400
                    {
3401
                        $params['join_table_alias']	= 'jt' . $jtcount;
3402
3403
                    }
3404
                    if(isset($data['join_link_name']))
3405
                    {
3406
                        $params['join_table_link_alias'] = $data['join_link_name'];
3407
                    }
3408
                    else
3409
                    {
3410
                        $params['join_table_link_alias'] = 'jtl' . $jtcount;
3411
                    }
3412
                    $join_primary = !isset($data['join_primary']) || $data['join_primary'];
3413
3414
                    $join = $this->$linkField->getJoin($params, true);
3415
                    $used_join_key[] = $join['rel_key'];
3416
                    $rel_module = $this->$linkField->getRelatedModuleName();
3417
                    $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');
3418
3419
					//if rname is set to 'name', and bean files exist, then check if field should be a concatenated name
3420
					global $beanFiles, $beanList;
3421
                    // °3/21/2014 FIX NS-TEAM - Relationship fields could not be displayed in subpanels
3422
                    //if($data['rname'] && !empty($beanFiles[$beanList[$rel_module]])) {
3423
                    if(isset($data['rname']) && $data['rname'] == 'name' && !empty($beanFiles[$beanList[$rel_module]])) {
3424
3425
						//create an instance of the related bean
3426
						require_once($beanFiles[$beanList[$rel_module]]);
3427
						$rel_mod = new $beanList[$rel_module]();
3428
						//if bean has first and last name fields, then name should be concatenated
3429
						if(isset($rel_mod->field_name_map['first_name']) && isset($rel_mod->field_name_map['last_name'])){
3430
								$data['db_concat_fields'] = array(0=>'first_name', 1=>'last_name');
3431
						}
3432
					}
3433
3434
3435
    				if($join['type'] == 'many-to-many')
3436
    				{
3437
    					if(empty($ret_array['secondary_select']))
3438
    					{
3439
    						$ret_array['secondary_select'] = " SELECT $this->table_name.id ref_id  ";
3440
3441
                            if(!empty($beanFiles[$beanList[$rel_module]]) && $join_primary)
3442
                            {
3443
                                require_once($beanFiles[$beanList[$rel_module]]);
3444
                                $rel_mod = new $beanList[$rel_module]();
3445
                                if(isset($rel_mod->field_defs['assigned_user_id']))
3446
                                {
3447
                                    $ret_array['secondary_select'].= " , ".	$params['join_table_alias'] . ".assigned_user_id {$field}_owner, '$rel_module' {$field}_mod";
3448
                                }
3449
                                else
3450
                                {
3451
                                    if(isset($rel_mod->field_defs['created_by']))
3452
                                    {
3453
                                        $ret_array['secondary_select'].= " , ".	$params['join_table_alias'] . ".created_by {$field}_owner , '$rel_module' {$field}_mod";
3454
                                    }
3455
                                }
3456
                            }
3457
                        }
3458
3459
                        if(isset($data['db_concat_fields']))
3460
                        {
3461
                            $ret_array['secondary_select'] .= ' , ' . $this->db->concat($params['join_table_alias'], $data['db_concat_fields']) . ' ' . $field;
3462
                        }
3463
                        else
3464
                        {
3465
                            if(!isset($data['relationship_fields']))
3466
                            {
3467
                                $ret_array['secondary_select'] .= ' , ' . $params['join_table_alias'] . '.' . $data['rname'] . ' ' . $field;
3468
                            }
3469
                        }
3470
                        if(!$singleSelect)
3471
                        {
3472
                            $ret_array['select'] .= ", '                                                                                                                                                                                                                                                              ' $field ";
3473
                        }
3474
                        $count_used =0;
3475
                        foreach($used_join_key as $used_key) {
3476
                            if($used_key == $join['rel_key']) $count_used++;
3477
                        }
3478
                        if($count_used <= 1) {//27416, the $ret_array['secondary_select'] should always generate, regardless the dbtype
3479
                            // add rel_key only if it was not aready added
3480
                            if(!$singleSelect)
3481
                            {
3482
                                $ret_array['select'] .= ", '                                    '  " . $join['rel_key'] . ' ';
3483
                            }
3484
                            $ret_array['secondary_select'] .= ', ' . $params['join_table_link_alias'].'.'. $join['rel_key'] .' ' . $join['rel_key'];
3485
                        }
3486
                        if(isset($data['relationship_fields']))
3487
                        {
3488
                            foreach($data['relationship_fields'] as $r_name=>$alias_name)
3489
                            {
3490
                                if(!empty( $secondarySelectedFields[$alias_name]))continue;
3491
                                $ret_array['secondary_select'] .= ', ' . $params['join_table_link_alias'].'.'. $r_name .' ' . $alias_name;
3492
                                $secondarySelectedFields[$alias_name] = true;
3493
                            }
3494
                        }
3495
                        if(!$table_joined)
3496
                        {
3497
                            $ret_array['secondary_from'] .= ' ' . $join['join']. ' AND ' . $params['join_table_alias'].'.deleted=0';
3498
                            if (isset($data['link_type']) && $data['link_type'] == 'relationship_info' && ($parentbean instanceOf SugarBean))
3499
                            {
3500
                                $ret_array['secondary_where'] = $params['join_table_link_alias'] . '.' . $join['rel_key']. "='" .$parentbean->id . "'";
3501
                            }
3502
                        }
3503
                    }
3504
                    else
3505
                    {
3506
                        if(isset($data['db_concat_fields']))
3507
                        {
3508
                            $ret_array['select'] .= ' , ' . $this->db->concat($params['join_table_alias'], $data['db_concat_fields']) . ' ' . $field;
3509
                        }
3510
                        else
3511
                        {
3512
                            $ret_array['select'] .= ' , ' . $params['join_table_alias'] . '.' . $data['rname'] . ' ' . $field;
3513
                        }
3514
                        if(isset($data['additionalFields'])){
3515
                            foreach($data['additionalFields'] as $k=>$v){
3516
                                if (!empty($data['id_name']) && $data['id_name'] == $v && !empty($fields[$data['id_name']])) {
3517
                                    continue;
3518
                                }
3519
                                $ret_array['select'] .= ' , ' . $params['join_table_alias'] . '.' . $k . ' ' . $v;
3520
                            }
3521
                        }
3522
                        if(!$table_joined)
3523
                        {
3524
                            $ret_array['from'] .= ' ' . $join['join']. ' AND ' . $params['join_table_alias'].'.deleted=0';
3525
                            if(!empty($beanList[$rel_module]) && !empty($beanFiles[$beanList[$rel_module]]))
3526
                            {
3527
                                require_once($beanFiles[$beanList[$rel_module]]);
3528
                                $rel_mod = new $beanList[$rel_module]();
3529
                                if(isset($value['target_record_key']) && !empty($filter))
3530
                                {
3531
                                    $selectedFields[$this->table_name.'.'.$value['target_record_key']] = true;
3532
                                    $ret_array['select'] .= " , $this->table_name.{$value['target_record_key']} ";
3533
                                }
3534
                                if(isset($rel_mod->field_defs['assigned_user_id']))
3535
                                {
3536
                                    $ret_array['select'] .= ' , ' .$params['join_table_alias'] . '.assigned_user_id ' .  $field . '_owner';
3537
                                }
3538
                                else
3539
                                {
3540
                                    $ret_array['select'] .= ' , ' .$params['join_table_alias'] . '.created_by ' .  $field . '_owner';
3541
                                }
3542
                                $ret_array['select'] .= "  , '".$rel_module  ."' " .  $field . '_mod';
3543
3544
                            }
3545
                        }
3546
                    }
3547
                    // To fix SOAP stuff where we are trying to retrieve all the accounts data where accounts.id = ..
3548
                    // and this code changes accounts to jt4 as there is a self join with the accounts table.
3549
                    //Martin fix #27494
3550
                    if(isset($data['db_concat_fields'])){
3551
                    	$buildWhere = false;
3552
                        if(in_array('first_name', $data['db_concat_fields']) && in_array('last_name', $data['db_concat_fields']))
3553
                    	{
3554
                     	   $exp = '/\(\s*?'.$data['name'].'.*?\%\'\s*?\)/';
3555
                    	   if(preg_match($exp, $where, $matches))
3556
                    	   {
3557
                    	   	  $search_expression = $matches[0];
3558
                    	   	  //Create three search conditions - first + last, first, last
3559
                    	   	  $first_name_search = str_replace($data['name'], $params['join_table_alias'] . '.first_name', $search_expression);
3560
                    	   	  $last_name_search = str_replace($data['name'], $params['join_table_alias'] . '.last_name', $search_expression);
3561
							  $full_name_search = str_replace($data['name'], $this->db->concat($params['join_table_alias'], $data['db_concat_fields']), $search_expression);
3562
							  $buildWhere = true;
3563
							  $where = str_replace($search_expression, '(' . $full_name_search . ' OR ' . $first_name_search . ' OR ' . $last_name_search . ')', $where);
3564
                    	   }
3565
                    	}
3566
3567
                    	if(!$buildWhere)
3568
                    	{
3569
	                       $db_field = $this->db->concat($params['join_table_alias'], $data['db_concat_fields']);
3570
	                       $where = preg_replace('/'.$data['name'].'/', $db_field, $where);
3571
3572
				// For relationship fields replace their alias by the corresponsding link table and r_name
3573
				if(isset($data['relationship_fields']))
3574
					foreach($data['relationship_fields'] as $r_name=>$alias_name)
3575
					{
3576
						$db_field = $this->db->concat($params['join_table_link_alias'], $r_name);
3577
						$where = preg_replace('/' . $alias_name . '/', $db_field, $where);
3578
					}
3579
                    	}
3580
                    }else{
3581
                        $where = preg_replace('/(^|[\s(])' . $data['name'] . '/', '${1}' . $params['join_table_alias'] . '.'.$data['rname'], $where);
3582
3583
			// For relationship fields replace their alias by the corresponsding link table and r_name
3584
			if(isset($data['relationship_fields']))
3585
				foreach($data['relationship_fields'] as $r_name=>$alias_name)
3586
					$where = preg_replace('/(^|[\s(])' . $alias_name . '/', '${1}' . $params['join_table_link_alias'] . '.'.$r_name, $where);
3587
                    }
3588
                    if(!$table_joined)
3589
                    {
3590
                        $joined_tables[$params['join_table_alias']]=1;
3591
                        $joined_tables[$params['join_table_link_alias']]=1;
3592
                    }
3593
3594
                    $jtcount++;
3595
                }
3596
            }
3597
        }
3598
        if(!empty($filter))
3599
        {
3600
            if(isset($this->field_defs['assigned_user_id']) && empty($selectedFields[$this->table_name.'.assigned_user_id']))
3601
            {
3602
                $ret_array['select'] .= ", $this->table_name.assigned_user_id ";
3603
            }
3604
            else if(isset($this->field_defs['created_by']) &&  empty($selectedFields[$this->table_name.'.created_by']))
3605
            {
3606
                $ret_array['select'] .= ", $this->table_name.created_by ";
3607
            }
3608
            if(isset($this->field_defs['system_id']) && empty($selectedFields[$this->table_name.'.system_id']))
3609
            {
3610
                $ret_array['select'] .= ", $this->table_name.system_id ";
3611
            }
3612
3613
        }
3614
3615
	if ($ifListForExport) {
3616
		if(isset($this->field_defs['email1'])) {
3617
			$ret_array['select'].= " ,email_addresses.email_address email1";
3618
			$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 ";
3619
		}
3620
	}
3621
3622
        $where_auto = '1=1';
3623
        if($show_deleted == 0)
3624
        {
3625
            $where_auto = "$this->table_name.deleted=0";
3626
        }else if($show_deleted == 1)
3627
        {
3628
            $where_auto = "$this->table_name.deleted=1";
3629
        }
3630
        if($where != "")
3631
            $ret_array['where'] = " where ($where) AND $where_auto";
3632
        else
3633
            $ret_array['where'] = " where $where_auto";
3634
3635
        //make call to process the order by clause
3636
        $order_by = $this->process_order_by($order_by);
3637
        if (!empty($order_by)) {
3638
            $ret_array['order_by'] = " ORDER BY " . $order_by;
3639
        }
3640
        if($singleSelect)
3641
        {
3642
            unset($ret_array['secondary_where']);
3643
            unset($ret_array['secondary_from']);
3644
            unset($ret_array['secondary_select']);
3645
        }
3646
3647
        if($return_array)
3648
        {
3649
            return $ret_array;
3650
        }
3651
3652
        return  $ret_array['select'] . $ret_array['from'] . $ret_array['where']. $ret_array['order_by'];
3653
    }
3654
3655
	// Check if field is defined through a relationship_info field, add this field when not present 
3656
	function get_relationship_field($field)
3657
	{
3658
		foreach ($this->field_defs as $field_def => $value)
3659
		{
3660
			if (isset($value['relationship_fields']) && 
3661
				in_array($field, $value['relationship_fields']) &&
3662
                (!isset($value['link_type']) || $value['link_type'] != 'relationship_info')
3663
            )
3664
				return $field_def;
3665
		}
3666
3667
		return false;
3668
	}
3669
3670
    /**
3671
     * Returns parent record data for objects that store relationship information
3672
     *
3673
     * @param array $type_info
3674
     *
3675
     * Interal function, do not override.
3676
     */
3677
    function retrieve_parent_fields($type_info)
3678
    {
3679
        $queries = array();
3680
        global $beanList, $beanFiles;
3681
        $templates = array();
3682
        $parent_child_map = array();
3683
        foreach($type_info as $children_info)
3684
        {
3685
            foreach($children_info as $child_info)
3686
            {
3687
                if($child_info['type'] == 'parent')
3688
                {
3689
                    if(empty($templates[$child_info['parent_type']]))
3690
                    {
3691
                        //Test emails will have an invalid parent_type, don't try to load the non-existent parent bean
3692
                        if ($child_info['parent_type'] == 'test') {
3693
                            continue;
3694
                        }
3695
                        $class = $beanList[$child_info['parent_type']];
3696
                        // Added to avoid error below; just silently fail and write message to log
3697
                        if ( empty($beanFiles[$class]) ) {
3698
                            $GLOBALS['log']->error($this->object_name.'::retrieve_parent_fields() - cannot load class "'.$class.'", skip loading.');
3699
                            continue;
3700
                        }
3701
                        require_once($beanFiles[$class]);
3702
                        $templates[$child_info['parent_type']] = new $class();
3703
                    }
3704
3705
                    if(empty($queries[$child_info['parent_type']]))
3706
                    {
3707
                        $queries[$child_info['parent_type']] = "SELECT id ";
3708
                        $field_def = $templates[$child_info['parent_type']]->field_defs['name'];
3709
                        if(isset($field_def['db_concat_fields']))
3710
                        {
3711
                            $queries[$child_info['parent_type']] .= ' , ' . $this->db->concat($templates[$child_info['parent_type']]->table_name, $field_def['db_concat_fields']) . ' parent_name';
3712
                        }
3713
                        else
3714
                        {
3715
                            $queries[$child_info['parent_type']] .= ' , name parent_name';
3716
                        }
3717
                        if(isset($templates[$child_info['parent_type']]->field_defs['assigned_user_id']))
3718
                        {
3719
                            $queries[$child_info['parent_type']] .= ", assigned_user_id parent_name_owner , '{$child_info['parent_type']}' parent_name_mod";;
3720
                        }else if(isset($templates[$child_info['parent_type']]->field_defs['created_by']))
3721
                        {
3722
                            $queries[$child_info['parent_type']] .= ", created_by parent_name_owner, '{$child_info['parent_type']}' parent_name_mod";
3723
                        }
3724
                        $queries[$child_info['parent_type']] .= " FROM " . $templates[$child_info['parent_type']]->table_name ." WHERE id IN ('{$child_info['parent_id']}'";
3725
                    }
3726
                    else
3727
                    {
3728
                        if(empty($parent_child_map[$child_info['parent_id']]))
3729
                        $queries[$child_info['parent_type']] .= " ,'{$child_info['parent_id']}'";
3730
                    }
3731
                    $parent_child_map[$child_info['parent_id']][] = $child_info['child_id'];
3732
                }
3733
            }
3734
        }
3735
        $results = array();
3736
        foreach($queries as $query)
3737
        {
3738
            $result = $this->db->query($query . ')');
3739
            while($row = $this->db->fetchByAssoc($result))
3740
            {
3741
                $results[$row['id']] = $row;
3742
            }
3743
        }
3744
3745
        $child_results = array();
3746
        foreach($parent_child_map as $parent_key=>$parent_child)
3747
        {
3748
            foreach($parent_child as $child)
3749
            {
3750
                if(isset( $results[$parent_key]))
3751
                {
3752
                    $child_results[$child] = $results[$parent_key];
3753
                }
3754
            }
3755
        }
3756
        return $child_results;
3757
    }
3758
3759
    /**
3760
     * Processes the list query and return fetched row.
3761
     *
3762
     * Internal function, do not override.
3763
     * @param string $query select query to be processed.
3764
     * @param int $row_offset starting position
3765
     * @param int $limit Optioanl, default -1
3766
     * @param int $max_per_page Optional, default -1
3767
     * @param string $where Optional, additional filter criteria.
3768
     * @return array Fetched data
3769
     */
3770
    function process_list_query($query, $row_offset, $limit= -1, $max_per_page = -1, $where = '')
3771
    {
3772
        global $sugar_config;
3773
        $db = DBManagerFactory::getInstance('listviews');
3774
        /**
3775
        * if the row_offset is set to 'end' go to the end of the list
3776
        */
3777
        $toEnd = strval($row_offset) == 'end';
3778
        $GLOBALS['log']->debug("process_list_query: ".$query);
3779
        if($max_per_page == -1)
3780
        {
3781
            $max_per_page 	= $sugar_config['list_max_entries_per_page'];
3782
        }
3783
        // Check to see if we have a count query available.
3784
        if(empty($sugar_config['disable_count_query']) || $toEnd)
3785
        {
3786
            $count_query = $this->create_list_count_query($query);
3787
            if(!empty($count_query) && (empty($limit) || $limit == -1))
3788
            {
3789
                // We have a count query.  Run it and get the results.
3790
                $result = $db->query($count_query, true, "Error running count query for $this->object_name List: ");
3791
                $assoc = $db->fetchByAssoc($result);
3792
                if(!empty($assoc['c']))
3793
                {
3794
                    $rows_found = $assoc['c'];
3795
                    $limit = $sugar_config['list_max_entries_per_page'];
3796
                }
3797
                if( $toEnd)
3798
                {
3799
                    $row_offset = (floor(($rows_found -1) / $limit)) * $limit;
3800
                }
3801
            }
3802
        }
3803
        else
3804
        {
3805
            if((empty($limit) || $limit == -1))
3806
            {
3807
                $limit = $max_per_page + 1;
3808
                $max_per_page = $limit;
3809
            }
3810
3811
        }
3812
3813
        if(empty($row_offset))
3814
        {
3815
            $row_offset = 0;
3816
        }
3817
        if(!empty($limit) && $limit != -1 && $limit != -99)
3818
        {
3819
            $result = $db->limitQuery($query, $row_offset, $limit,true,"Error retrieving $this->object_name list: ");
3820
        }
3821
        else
3822
        {
3823
            $result = $db->query($query,true,"Error retrieving $this->object_name list: ");
3824
        }
3825
3826
        $list = Array();
3827
3828
        $previous_offset = $row_offset - $max_per_page;
3829
        $next_offset = $row_offset + $max_per_page;
3830
3831
        $class = get_class($this);
3832
            //FIXME: Bug? we should remove the magic number -99
3833
            //use -99 to return all
3834
            $index = $row_offset;
3835
            while ($max_per_page == -99 || ($index < $row_offset + $max_per_page))
3836
            {
3837
                $row = $db->fetchByAssoc($result);
3838
                if (empty($row)) break;
3839
3840
                //instantiate a new class each time. This is because php5 passes
3841
                //by reference by default so if we continually update $this, we will
3842
                //at the end have a list of all the same objects
3843
                /** @var SugarBean $temp */
3844
                $temp = new $class();
3845
3846
                foreach($this->field_defs as $field=>$value)
3847
                {
3848
                    if (isset($row[$field]))
3849
                    {
3850
                        $temp->$field = $row[$field];
3851
                        $owner_field = $field . '_owner';
3852
                        if(isset($row[$owner_field]))
3853
                        {
3854
                            $temp->$owner_field = $row[$owner_field];
3855
                        }
3856
3857
                        $GLOBALS['log']->debug("$temp->object_name({$row['id']}): ".$field." = ".$temp->$field);
3858
                    }else if (isset($row[$this->table_name .'.'.$field]))
3859
                    {
3860
                        $temp->$field = $row[$this->table_name .'.'.$field];
3861
                    }
3862
                    else
3863
                    {
3864
                        $temp->$field = "";
3865
                    }
3866
                }
3867
3868
                $temp->check_date_relationships_load();
3869
                $temp->fill_in_additional_list_fields();
3870
                if($temp->hasCustomFields()) $temp->custom_fields->fill_relationships();
3871
                $temp->call_custom_logic("process_record");
3872
3873
                // fix defect #44206. implement the same logic as sugar_currency_format
3874
                // Smarty modifier does.
3875
                $temp->populateCurrencyFields();
3876
                $list[] = $temp;
3877
3878
                $index++;
3879
            }
3880
        if(!empty($sugar_config['disable_count_query']) && !empty($limit))
3881
        {
3882
3883
            $rows_found = $row_offset + count($list);
3884
3885
            if(!$toEnd)
3886
            {
3887
                $next_offset--;
3888
                $previous_offset++;
3889
            }
3890
        } else if(!isset($rows_found)){
3891
            $rows_found = $row_offset + count($list);
3892
        }
3893
3894
        $response = Array();
3895
        $response['list'] = $list;
3896
        $response['row_count'] = $rows_found;
3897
        $response['next_offset'] = $next_offset;
3898
        $response['previous_offset'] = $previous_offset;
3899
        $response['current_offset'] = $row_offset ;
3900
        return $response;
3901
    }
3902
3903
    /**
3904
    * Returns the number of rows that the given SQL query should produce
3905
     *
3906
     * Internal function, do not override.
3907
     * @param string $query valid select  query
3908
     * @param boolean $is_count_query Optional, Default false, set to true if passed query is a count query.
3909
     * @return int count of rows found
3910
    */
3911
    function _get_num_rows_in_query($query, $is_count_query=false)
3912
    {
3913
        $num_rows_in_query = 0;
3914
        if (!$is_count_query)
3915
        {
3916
            $count_query = SugarBean::create_list_count_query($query);
3917
        } else
3918
            $count_query=$query;
3919
3920
        $result = $this->db->query($count_query, true, "Error running count query for $this->object_name List: ");
3921
        $row_num = 0;
3922
        while($row = $this->db->fetchByAssoc($result, true))
3923
        {
3924
            $num_rows_in_query += current($row);
3925
        }
3926
3927
        return $num_rows_in_query;
3928
    }
3929
3930
    /**
3931
     * Applies pagination window to union queries used by list view and subpanels,
3932
     * executes the query and returns fetched data.
3933
     *
3934
     * Internal function, do not override.
3935
     * @param object $parent_bean
3936
     * @param string $query query to be processed.
3937
     * @param int $row_offset
3938
     * @param int $limit optional, default -1
3939
     * @param int $max_per_page Optional, default -1
3940
     * @param string $where Custom where clause.
3941
     * @param array $subpanel_def definition of sub-panel to be processed
3942
     * @param string $query_row_count
3943
     * @param array $seconday_queries
0 ignored issues
show
Documentation introduced by
There is no parameter named $seconday_queries. Did you maybe mean $secondary_queries?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
3944
     * @return array Fetched data.
3945
     */
3946
    function process_union_list_query($parent_bean, $query,
3947
    $row_offset, $limit= -1, $max_per_page = -1, $where = '', $subpanel_def, $query_row_count='', $secondary_queries = array())
3948
3949
    {
3950
        $db = DBManagerFactory::getInstance('listviews');
3951
        /**
3952
        * if the row_offset is set to 'end' go to the end of the list
3953
        */
3954
        $toEnd = strval($row_offset) == 'end';
3955
        global $sugar_config;
3956
        $use_count_query=false;
3957
        $processing_collection=$subpanel_def->isCollection();
3958
3959
        $GLOBALS['log']->debug("process_union_list_query: ".$query);
3960
        if($max_per_page == -1)
3961
        {
3962
            $max_per_page 	= $sugar_config['list_max_entries_per_subpanel'];
3963
        }
3964
        if(empty($query_row_count))
3965
        {
3966
            $query_row_count = $query;
3967
        }
3968
        $distinct_position=strpos($query_row_count,"DISTINCT");
3969
3970
        if ($distinct_position!= false)
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $distinct_position of type integer to the boolean false. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
3971
        {
3972
            $use_count_query=true;
3973
        }
3974
        $performSecondQuery = true;
3975
        if(empty($sugar_config['disable_count_query']) || $toEnd)
3976
        {
3977
            $rows_found = $this->_get_num_rows_in_query($query_row_count,$use_count_query);
3978
            if($rows_found < 1)
3979
            {
3980
                $performSecondQuery = false;
3981
            }
3982
            if(!empty($rows_found) && (empty($limit) || $limit == -1))
3983
            {
3984
                $limit = $max_per_page;
3985
            }
3986
            if( $toEnd)
3987
            {
3988
                $row_offset = (floor(($rows_found -1) / $limit)) * $limit;
3989
3990
            }
3991
        }
3992
        else
3993
        {
3994
            if((empty($limit) || $limit == -1))
3995
            {
3996
                $limit = $max_per_page + 1;
3997
                $max_per_page = $limit;
3998
            }
3999
        }
4000
4001
        if(empty($row_offset))
4002
        {
4003
            $row_offset = 0;
4004
        }
4005
        $list = array();
4006
        $previous_offset = $row_offset - $max_per_page;
4007
        $next_offset = $row_offset + $max_per_page;
4008
4009
        if($performSecondQuery)
4010
        {
4011
            if(!empty($limit) && $limit != -1 && $limit != -99)
4012
            {
4013
                $result = $db->limitQuery($query, $row_offset, $limit,true,"Error retrieving $parent_bean->object_name list: ");
4014
            }
4015
            else
4016
            {
4017
                $result = $db->query($query,true,"Error retrieving $this->object_name list: ");
4018
            }
4019
                //use -99 to return all
4020
4021
                // get the current row
4022
                $index = $row_offset;
4023
                $row = $db->fetchByAssoc($result);
4024
4025
                $post_retrieve = array();
4026
                $isFirstTime = true;
4027
                while($row)
4028
                {
4029
                    $function_fields = array();
4030
                    if(($index < $row_offset + $max_per_page || $max_per_page == -99))
4031
                    {
4032
                        if ($processing_collection)
4033
                        {
4034
                            $current_bean =$subpanel_def->sub_subpanels[$row['panel_name']]->template_instance;
4035
                            if(!$isFirstTime)
4036
                            {
4037
                                $class = get_class($subpanel_def->sub_subpanels[$row['panel_name']]->template_instance);
4038
                                $current_bean = new $class();
4039
                            }
4040
                        } else {
4041
                            $current_bean=$subpanel_def->template_instance;
4042
                            if(!$isFirstTime)
4043
                            {
4044
                                $class = get_class($subpanel_def->template_instance);
4045
                                $current_bean = new $class();
4046
                            }
4047
                        }
4048
                        $isFirstTime = false;
4049
                        //set the panel name in the bean instance.
4050
                        if (isset($row['panel_name']))
4051
                        {
4052
                            $current_bean->panel_name=$row['panel_name'];
4053
                        }
4054
                        foreach($current_bean->field_defs as $field=>$value)
4055
                        {
4056
4057
                            if (isset($row[$field]))
4058
                            {
4059
                                $current_bean->$field = $this->convertField($row[$field], $value);
4060
                                unset($row[$field]);
4061
                            }
4062
                            else if (isset($row[$this->table_name .'.'.$field]))
4063
                            {
4064
                                $current_bean->$field = $this->convertField($row[$this->table_name .'.'.$field], $value);
4065
                                unset($row[$this->table_name .'.'.$field]);
4066
                            }
4067
                            else
4068
                            {
4069
                                $current_bean->$field = "";
4070
                                unset($row[$field]);
4071
                            }
4072
                            if(isset($value['source']) && $value['source'] == 'function')
4073
                            {
4074
                                $function_fields[]=$field;
4075
                            }
4076
                        }
4077
                        foreach($row as $key=>$value)
4078
                        {
4079
                            $current_bean->$key = $value;
4080
                        }
4081
                        foreach($function_fields as $function_field)
4082
                        {
4083
                            $value = $current_bean->field_defs[$function_field];
4084
                            $can_execute = true;
4085
                            $execute_params = array();
4086
                            $execute_function = array();
4087
                            if(!empty($value['function_class']))
4088
                            {
4089
                                $execute_function[] = 	$value['function_class'];
4090
                                $execute_function[] = 	$value['function_name'];
4091
                            }
4092
                            else
4093
                            {
4094
                                $execute_function	= $value['function_name'];
4095
                            }
4096
                            foreach($value['function_params'] as $param )
4097
                            {
4098
                                if (empty($value['function_params_source']) or $value['function_params_source']=='parent')
4099
                                {
4100
                                    if(empty($this->$param))
4101
                                    {
4102
                                        $can_execute = false;
4103
                                    } else if($param == '$this') {
4104
                                        $execute_params[] = $this;
4105
                                    }
4106
                                    else
4107
                                    {
4108
                                        $execute_params[] = $this->$param;
4109
                                    }
4110
                                } else if ($value['function_params_source']=='this')
4111
                                {
4112
                                    if(empty($current_bean->$param))
4113
                                    {
4114
                                        $can_execute = false;
4115
                                    } else if($param == '$this') {
4116
                                        $execute_params[] = $current_bean;
4117
                                    }
4118
                                    else
4119
                                    {
4120
                                        $execute_params[] = $current_bean->$param;
4121
                                    }
4122
                                }
4123
                                else
4124
                                {
4125
                                    $can_execute = false;
4126
                                }
4127
4128
                            }
4129
                            if($can_execute)
4130
                            {
4131
                                if(!empty($value['function_require']))
4132
                                {
4133
                                    require_once($value['function_require']);
4134
                                }
4135
                                $current_bean->$function_field = call_user_func_array($execute_function, $execute_params);
4136
                            }
4137
                        }
4138
                        if(!empty($current_bean->parent_type) && !empty($current_bean->parent_id))
4139
                        {
4140
                            if(!isset($post_retrieve[$current_bean->parent_type]))
4141
                            {
4142
                                $post_retrieve[$current_bean->parent_type] = array();
4143
                            }
4144
                            $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');
4145
                        }
4146
                        //$current_bean->fill_in_additional_list_fields();
4147
                        $list[$current_bean->id] = $current_bean;
4148
                    }
4149
                    // go to the next row
4150
                    $index++;
4151
                    $row = $db->fetchByAssoc($result);
4152
                }
4153
            //now handle retrieving many-to-many relationships
4154
            if(!empty($list))
4155
            {
4156
                foreach($secondary_queries as $query2)
4157
                {
4158
                    $result2 = $db->query($query2);
4159
4160
                    $row2 = $db->fetchByAssoc($result2);
4161
                    while($row2)
4162
                    {
4163
                        $id_ref = $row2['ref_id'];
4164
4165
                        if(isset($list[$id_ref]))
4166
                        {
4167
                            foreach($row2 as $r2key=>$r2value)
4168
                            {
4169
                                if($r2key != 'ref_id')
4170
                                {
4171
                                    $list[$id_ref]->$r2key = $r2value;
4172
                                }
4173
                            }
4174
                        }
4175
                        $row2 = $db->fetchByAssoc($result2);
4176
                    }
4177
                }
4178
4179
            }
4180
4181
            if(isset($post_retrieve))
4182
            {
4183
                $parent_fields = $this->retrieve_parent_fields($post_retrieve);
4184
            }
4185
            else
4186
            {
4187
                $parent_fields = array();
4188
            }
4189
            if(!empty($sugar_config['disable_count_query']) && !empty($limit))
4190
            {
4191
            	//C.L. Bug 43535 - Use the $index value to set the $rows_found value here
4192
                $rows_found = isset($index) ? $index : $row_offset + count($list);
4193
4194
                if(count($list) >= $limit)
4195
                {
4196
                    array_pop($list);
4197
                }
4198
                if(!$toEnd)
4199
                {
4200
                    $next_offset--;
4201
                    $previous_offset++;
4202
                }
4203
            }
4204
        }
4205
        else
4206
        {
4207
            $row_found 	= 0;
4208
            $parent_fields = array();
4209
        }
4210
        $response = array();
4211
        $response['list'] = $list;
4212
        $response['parent_data'] = $parent_fields;
4213
        $response['row_count'] = $rows_found;
4214
        $response['next_offset'] = $next_offset;
4215
        $response['previous_offset'] = $previous_offset;
4216
        $response['current_offset'] = $row_offset ;
4217
        $response['query'] = $query;
4218
4219
        return $response;
4220
    }
4221
4222
    /**
4223
     * Applies pagination window to select queries used by detail view,
4224
     * executes the query and returns fetched data.
4225
     *
4226
     * Internal function, do not override.
4227
     * @param string $query query to be processed.
4228
     * @param int $row_offset
4229
     * @param int $limit optional, default -1
4230
     * @param int $max_per_page Optional, default -1
4231
     * @param string $where Custom where clause.
4232
     * @param int $offset Optional, default 0
4233
     * @return array Fetched data.
4234
     *
4235
     */
4236
    function process_detail_query($query, $row_offset, $limit= -1, $max_per_page = -1, $where = '', $offset = 0)
4237
    {
4238
        global $sugar_config;
4239
        $GLOBALS['log']->debug("process_detail_query: ".$query);
4240
        if($max_per_page == -1)
4241
        {
4242
            $max_per_page 	= $sugar_config['list_max_entries_per_page'];
4243
        }
4244
4245
        // Check to see if we have a count query available.
4246
        $count_query = $this->create_list_count_query($query);
4247
4248
        if(!empty($count_query) && (empty($limit) || $limit == -1))
4249
        {
4250
            // We have a count query.  Run it and get the results.
4251
            $result = $this->db->query($count_query, true, "Error running count query for $this->object_name List: ");
4252
            $assoc = $this->db->fetchByAssoc($result);
4253
            if(!empty($assoc['c']))
4254
            {
4255
                $total_rows = $assoc['c'];
4256
            }
4257
        }
4258
4259
        if(empty($row_offset))
4260
        {
4261
            $row_offset = 0;
4262
        }
4263
4264
        $result = $this->db->limitQuery($query, $offset, 1, true,"Error retrieving $this->object_name list: ");
4265
4266
        $previous_offset = $row_offset - $max_per_page;
4267
        $next_offset = $row_offset + $max_per_page;
4268
4269
        $row = $this->db->fetchByAssoc($result);
4270
        $this->retrieve($row['id']);
4271
4272
        $response = Array();
4273
        $response['bean'] = $this;
4274
        if (empty($total_rows))
4275
            $total_rows=0;
4276
        $response['row_count'] = $total_rows;
4277
        $response['next_offset'] = $next_offset;
4278
        $response['previous_offset'] = $previous_offset;
4279
4280
        return $response;
4281
    }
4282
4283
    /**
4284
     * Processes fetched list view data
4285
     *
4286
     * Internal function, do not override.
4287
     * @param string $query query to be processed.
4288
     * @param boolean $check_date Optional, default false. if set to true date time values are processed.
4289
     * @return array Fetched data.
4290
     *
4291
     */
4292
    function process_full_list_query($query, $check_date=false)
4293
    {
4294
4295
        $GLOBALS['log']->debug("process_full_list_query: query is ".$query);
4296
        $result = $this->db->query($query, false);
4297
        $GLOBALS['log']->debug("process_full_list_query: result is ".print_r($result,true));
4298
        $class = get_class($this);
4299
        $isFirstTime = true;
4300
        $bean = new $class();
4301
4302
        // We have some data.
4303
        while (($row = $bean->db->fetchByAssoc($result)) != null)
4304
        {
4305
            $row = $this->convertRow($row);
4306
            if(!$isFirstTime)
4307
            {
4308
                $bean = new $class();
4309
            }
4310
            $isFirstTime = false;
4311
4312
            foreach($bean->field_defs as $field=>$value)
4313
            {
4314
                if (isset($row[$field]))
4315
                {
4316
                    $bean->$field = $row[$field];
4317
                    $GLOBALS['log']->debug("process_full_list: $bean->object_name({$row['id']}): ".$field." = ".$bean->$field);
4318
                }
4319
                else
4320
                {
4321
                    $bean->$field = '';
4322
                }
4323
            }
4324
            if($check_date)
4325
            {
4326
                $bean->processed_dates_times = array();
4327
                $bean->check_date_relationships_load();
4328
            }
4329
            $bean->fill_in_additional_list_fields();
4330
            $bean->call_custom_logic("process_record");
4331
            $bean->fetched_row = $row;
4332
4333
            $list[] = $bean;
4334
        }
4335
        //}
4336
        if (isset($list)) return $list;
4337
        else return null;
4338
    }
4339
4340
    /**
4341
    * Tracks the viewing of a detail record.
4342
    * This leverages get_summary_text() which is object specific.
4343
    *
4344
    * Internal function, do not override.
4345
    * @param string $user_id - String value of the user that is viewing the record.
4346
    * @param string $current_module - String value of the module being processed.
4347
    * @param string $current_view - String value of the current view
4348
	*/
4349
	function track_view($user_id, $current_module, $current_view='')
4350
	{
4351
	    $trackerManager = TrackerManager::getInstance();
4352
		if($monitor = $trackerManager->getMonitor('tracker')){
4353
	        $monitor->setValue('date_modified', $GLOBALS['timedate']->nowDb());
4354
	        $monitor->setValue('user_id', $user_id);
4355
	        $monitor->setValue('module_name', $current_module);
4356
	        $monitor->setValue('action', $current_view);
4357
	        $monitor->setValue('item_id', $this->id);
4358
	        $monitor->setValue('item_summary', $this->get_summary_text());
4359
	        $monitor->setValue('visible', $this->tracker_visibility);
4360
	        $trackerManager->saveMonitor($monitor);
4361
		}
4362
	}
4363
4364
    /**
4365
     * Returns the summary text that should show up in the recent history list for this object.
4366
     *
4367
     * @return string
4368
     */
4369
    public function get_summary_text()
4370
    {
4371
        return "Base Implementation.  Should be overridden.";
4372
    }
4373
4374
    /**
4375
    * This is designed to be overridden and add specific fields to each record.
4376
    * This allows the generic query to fill in the major fields, and then targeted
4377
    * queries to get related fields and add them to the record.  The contact's
4378
    * account for instance.  This method is only used for populating extra fields
4379
    * in lists.
4380
    */
4381
    function fill_in_additional_list_fields(){
4382
        if(!empty($this->field_defs['parent_name']) && empty($this->parent_name)){
4383
            $this->fill_in_additional_parent_fields();
4384
        }
4385
    }
4386
4387
    /**
4388
    * This is designed to be overridden and add specific fields to each record.
4389
    * This allows the generic query to fill in the major fields, and then targeted
4390
    * queries to get related fields and add them to the record.  The contact's
4391
    * account for instance.  This method is only used for populating extra fields
4392
    * in the detail form
4393
    */
4394
    function fill_in_additional_detail_fields()
4395
    {
4396
        if(!empty($this->field_defs['assigned_user_name']) && !empty($this->assigned_user_id)){
4397
4398
            $this->assigned_user_name = get_assigned_user_name($this->assigned_user_id);
4399
4400
        }
4401
		if(!empty($this->field_defs['created_by']) && !empty($this->created_by))
4402
		$this->created_by_name = get_assigned_user_name($this->created_by);
4403
		if(!empty($this->field_defs['modified_user_id']) && !empty($this->modified_user_id))
4404
		$this->modified_by_name = get_assigned_user_name($this->modified_user_id);
4405
4406
		if(!empty($this->field_defs['parent_name'])){
4407
			$this->fill_in_additional_parent_fields();
4408
		}
4409
    }
4410
4411
    /**
4412
    * This is desgined to be overridden or called from extending bean. This method
4413
    * will fill in any parent_name fields.
4414
    */
4415
    function fill_in_additional_parent_fields() {
4416
4417
        if(!empty($this->parent_id) && !empty($this->last_parent_id) && $this->last_parent_id == $this->parent_id){
4418
            return false;
4419
        }else{
4420
            $this->parent_name = '';
4421
        }
4422
        if(!empty($this->parent_type)) {
4423
            $this->last_parent_id = $this->parent_id;
4424
            $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'));
4425
            if(!empty($this->parent_first_name) || !empty($this->parent_last_name) ){
4426
                $this->parent_name = $GLOBALS['locale']->getLocaleFormattedName($this->parent_first_name, $this->parent_last_name);
4427
            } else if(!empty($this->parent_document_name)){
4428
                $this->parent_name = $this->parent_document_name;
4429
            }
4430
        }
4431
    }
4432
4433
/*
4434
     * Fill in a link field
4435
     */
4436
4437
    function fill_in_link_field( $linkFieldName , $def)
4438
    {
4439
        $idField = $linkFieldName;
4440
        //If the id_name provided really was an ID, don't try to load it as a link. Use the normal link
4441
        if (!empty($this->field_defs[$linkFieldName]['type']) && $this->field_defs[$linkFieldName]['type'] == "id" && !empty($def['link']))
4442
        {
4443
            $linkFieldName = $def['link'];
4444
        }
4445
        if ($this->load_relationship($linkFieldName))
4446
        {
4447
            $list=$this->$linkFieldName->get();
4448
            $this->$idField = '' ; // match up with null value in $this->populateFromRow()
4449
            if (!empty($list))
4450
                $this->$idField = $list[0];
4451
        }
4452
    }
4453
4454
    /**
4455
    * Fill in fields where type = relate
4456
    */
4457
    function fill_in_relationship_fields(){
4458
        global $fill_in_rel_depth;
4459
        if(empty($fill_in_rel_depth) || $fill_in_rel_depth < 0)
4460
            $fill_in_rel_depth = 0;
4461
4462
        if($fill_in_rel_depth > 1)
4463
            return;
4464
4465
        $fill_in_rel_depth++;
4466
4467
        foreach($this->field_defs as $field)
4468
        {
4469
            if(0 == strcmp($field['type'],'relate') && !empty($field['module']))
4470
            {
4471
                $name = $field['name'];
4472
                if(empty($this->$name))
4473
                {
4474
                    // 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']
4475
                    $related_module = $field['module'];
4476
                    $id_name = $field['id_name'];
4477
4478
                    if (empty($this->$id_name))
4479
                    {
4480
                       $this->fill_in_link_field($id_name, $field);
4481
                    }
4482
                    if(!empty($this->$id_name) && ( $this->object_name != $related_module || ( $this->object_name == $related_module && $this->$id_name != $this->id ))){
4483
                        if(isset($GLOBALS['beanList'][ $related_module])){
4484
                            $class = $GLOBALS['beanList'][$related_module];
4485
4486
                            if(!empty($this->$id_name) && file_exists($GLOBALS['beanFiles'][$class]) && isset($this->$name)){
4487
                                require_once($GLOBALS['beanFiles'][$class]);
4488
                                $mod = new $class();
4489
4490
                                // disable row level security in order to be able
4491
                                // to retrieve related bean properties (bug #44928)
4492
4493
                                $mod->retrieve($this->$id_name);
4494
4495
                                if (!empty($field['rname'])) {
4496
                                    $this->$name = $mod->$field['rname'];
4497
                                } else if (isset($mod->name)) {
4498
                                    $this->$name = $mod->name;
4499
                                }
4500
                            }
4501
                        }
4502
                    }
4503
                    if(!empty($this->$id_name) && isset($this->$name))
4504
                    {
4505
                        if(!isset($field['additionalFields']))
4506
                           $field['additionalFields'] = array();
4507
                        if(!empty($field['rname']))
4508
                        {
4509
                            $field['additionalFields'][$field['rname']]= $name;
4510
                        }
4511
                        else
4512
                        {
4513
                            $field['additionalFields']['name']= $name;
4514
                        }
4515
                        $this->getRelatedFields($related_module, $this->$id_name, $field['additionalFields']);
4516
                    }
4517
                }
4518
            }
4519
        }
4520
        $fill_in_rel_depth--;
4521
    }
4522
4523
    /**
4524
    * This is a helper function that is used to quickly created indexes when creating tables.
4525
    */
4526
    function create_index($query)
4527
    {
4528
        $GLOBALS['log']->info("create_index: $query");
4529
4530
        $result = $this->db->query($query, true, "Error creating index:");
4531
    }
4532
4533
    /**
4534
     * This function should be overridden in each module.  It marks an item as deleted.
4535
     *
4536
     * If it is not overridden, then marking this type of item is not allowed
4537
	 */
4538
	function mark_deleted($id)
4539
	{
4540
		global $current_user;
4541
		$date_modified = $GLOBALS['timedate']->nowDb();
4542
        $id = $this->db->quote($id);
4543
		if(isset($_SESSION['show_deleted']))
4544
		{
4545
			$this->mark_undeleted($id);
4546
		}
4547
		else
4548
		{
4549
			// call the custom business logic
4550
			$custom_logic_arguments['id'] = $id;
4551
			$this->call_custom_logic("before_delete", $custom_logic_arguments);
4552
            $this->deleted = 1;
0 ignored issues
show
Documentation Bug introduced by
The property $deleted was declared of type boolean, but 1 is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
4553
            $this->mark_relationships_deleted($id);
4554
            if ( isset($this->field_defs['modified_user_id']) ) {
4555
                if (!empty($current_user)) {
4556
                    $this->modified_user_id = $current_user->id;
4557
                } else {
4558
                    $this->modified_user_id = 1;
4559
                }
4560
                $query = "UPDATE $this->table_name set deleted=1 , date_modified = '$date_modified', modified_user_id = '$this->modified_user_id' where id='$id'";
4561
            } else {
4562
                $query = "UPDATE $this->table_name set deleted=1 , date_modified = '$date_modified' where id='$id'";
4563
            }
4564
            $this->db->query($query, true,"Error marking record deleted: ");
4565
4566
            SugarRelationship::resaveRelatedBeans();
4567
4568
            // Take the item off the recently viewed lists
4569
            $tracker = new Tracker();
4570
            $tracker->makeInvisibleForAll($id);
4571
4572
4573
            $this->deleteFiles();
4574
4575
            // call the custom business logic
4576
            $this->call_custom_logic("after_delete", $custom_logic_arguments);
4577
        }
4578
    }
4579
4580
    /**
4581
     * Restores data deleted by call to mark_deleted() function.
4582
     *
4583
     * Internal function, do not override.
4584
    */
4585
    function mark_undeleted($id)
4586
    {
4587
        // call the custom business logic
4588
        $custom_logic_arguments['id'] = $id;
4589
        $this->call_custom_logic("before_restore", $custom_logic_arguments);
4590
4591
		$date_modified = $GLOBALS['timedate']->nowDb();
4592
		$query = "UPDATE $this->table_name set deleted=0 , date_modified = '$date_modified' where id='" . $this->db->quote($id) ."'";
4593
		$this->db->query($query, true,"Error marking record undeleted: ");
4594
4595
        $this->restoreFiles();
4596
4597
        // call the custom business logic
4598
        $this->call_custom_logic("after_restore", $custom_logic_arguments);
4599
    }
4600
4601
   /**
4602
    * This function deletes relationships to this object.  It should be overridden
4603
    * to handle the relationships of the specific object.
4604
    * This function is called when the item itself is being deleted.
4605
    *
4606
    * @param int $id id of the relationship to delete
4607
    */
4608
   function mark_relationships_deleted($id)
4609
   {
4610
    $this->delete_linked($id);
4611
   }
4612
4613
    /**
4614
     * Returns path for files of bean or false on error
4615
     *
4616
     * @return bool|string
4617
     */
4618
    public function deleteFileDirectory()
4619
    {
4620
        if (empty($this->id)) {
4621
            return false;
4622
        }
4623
        return preg_replace('/^(..)(..)(..)/', '$1/$2/$3/', $this->id);
4624
    }
4625
4626
    /**
4627
     * Moves file to deleted folder
4628
     *
4629
     * @return bool success of movement
4630
     */
4631
    public function deleteFiles()
4632
    {
4633
        if (!$this->id) {
4634
            return true;
4635
        }
4636
        if (!$this->haveFiles()) {
4637
            return true;
4638
        }
4639
        $files = $this->getFiles();
4640
        if (empty($files)) {
4641
            return true;
4642
        }
4643
4644
        $directory = $this->deleteFileDirectory();
4645
4646
        $isCreated = sugar_is_dir('upload://deleted/' . $directory);
4647
        if (!$isCreated) {
4648
            sugar_mkdir('upload://deleted/' . $directory, 0777, true);
4649
            $isCreated = sugar_is_dir('upload://deleted/' . $directory);
4650
        }
4651
        if (!$isCreated) {
4652
            return false;
4653
        }
4654
4655
        foreach ($files as $file) {
4656
            if (file_exists('upload://' . $file)) {
4657
                if (!sugar_rename('upload://' . $file, 'upload://deleted/' . $directory . '/' . $file)) {
4658
                    $GLOBALS['log']->error('Could not move file ' . $file . ' to deleted directory');
4659
                }
4660
            }
4661
        }
4662
4663
        /**
4664
         * @var DBManager $db
4665
         */
4666
        global $db;
4667
        $record = array(
4668
            'bean_id' => $db->quoted($this->id),
4669
            'module' => $db->quoted($this->module_name),
4670
            'date_modified' => $db->convert($db->quoted(date('Y-m-d H:i:s')), 'datetime')
4671
        );
4672
        $recordDB = $db->fetchOne("SELECT id FROM cron_remove_documents WHERE module={$record['module']} AND bean_id={$record['bean_id']}");
4673
        if (!empty($recordDB)) {
4674
            $record['id'] = $db->quoted($recordDB['id']);
4675
        }
4676
        if (empty($record['id'])) {
4677
            $record['id'] = $db->quoted(create_guid());
4678
            $db->query('INSERT INTO cron_remove_documents (' . implode(', ', array_keys($record)) . ') VALUES(' . implode(', ', $record) . ')');
4679
        } else {
4680
            $db->query("UPDATE cron_remove_documents SET date_modified={$record['date_modified']} WHERE id={$record['id']}");
4681
        }
4682
4683
        return true;
4684
    }
4685
4686
    /**
4687
     * Restores files from deleted folder
4688
     *
4689
     * @return bool success of operation
4690
     */
4691
    protected function restoreFiles()
4692
    {
4693
        if (!$this->id) {
4694
            return true;
4695
        }
4696
        if (!$this->haveFiles()) {
4697
            return true;
4698
        }
4699
        $files = $this->getFiles();
4700
        if (empty($files)) {
4701
            return true;
4702
        }
4703
4704
        $directory = $this->deleteFileDirectory();
4705
4706
        foreach ($files as $file) {
4707
            if (sugar_is_file('upload://deleted/' . $directory . '/' . $file)) {
4708
                if (!sugar_rename('upload://deleted/' . $directory . '/' . $file, 'upload://' . $file)) {
4709
                    $GLOBALS['log']->error('Could not move file ' . $directory . '/' . $file . ' from deleted directory');
4710
                }
4711
            }
4712
        }
4713
4714
        /**
4715
         * @var DBManager $db
4716
         */
4717
        global $db;
4718
        $db->query('DELETE FROM cron_remove_documents WHERE bean_id=' . $db->quoted($this->id));
4719
4720
        return true;
4721
    }
4722
4723
    /**
4724
     * Method returns true if bean has files
4725
     *
4726
     * @return bool
4727
     */
4728
    public function haveFiles()
4729
    {
4730
        $return = false;
4731
        if ($this->bean_implements('FILE')) {
4732
            $return = true;
4733
        } elseif ($this instanceof File) {
4734
            $return = true;
4735
        } elseif (!empty(self::$fileFields[$this->module_name])) {
4736
            $return = true;
4737
        } elseif (!empty($this->field_defs)) {
4738
            foreach ($this->field_defs as $fieldDef) {
4739
                if ($fieldDef['type'] != 'image') {
4740
                    continue;
4741
                }
4742
                $return = true;
4743
                break;
4744
            }
4745
        }
4746
        return $return;
4747
    }
4748
4749
    /**
4750
     * Method returns array of names of files for current bean
4751
     *
4752
     * @return array
4753
     */
4754
    public  function getFiles() {
4755
        $files = array();
4756
        foreach ($this->getFilesFields() as $field) {
4757
            if (!empty($this->$field)) {
4758
                $files[] = $this->$field;
4759
            }
4760
        }
4761
        return $files;
4762
    }
4763
4764
    /**
4765
     * Method returns array of name of fields which contain names of files
4766
     *
4767
     * @param bool $resetCache do not use cache
4768
     * @return array
4769
     */
4770
    public function getFilesFields($resetCache = false)
4771
    {
4772
        if (isset(self::$fileFields[$this->module_name]) && $resetCache == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
4773
            return self::$fileFields[$this->module_name];
4774
        }
4775
4776
        self::$fileFields = array();
4777
        if ($this->bean_implements('FILE') || $this instanceof File) {
4778
            self::$fileFields[$this->module_name][] = 'id';
4779
        }
4780
        foreach ($this->field_defs as $fieldName => $fieldDef) {
4781
            if ($fieldDef['type'] != 'image') {
4782
                continue;
4783
            }
4784
            self::$fileFields[$this->module_name][] = $fieldName;
4785
        }
4786
4787
        return self::$fileFields[$this->module_name];
4788
    }
4789
4790
    /**
4791
    * This function is used to execute the query and create an array template objects
4792
    * from the resulting ids from the query.
4793
    * It is currently used for building sub-panel arrays.
4794
    *
4795
    * @param string $query - the query that should be executed to build the list
4796
    * @param object $template - The object that should be used to copy the records.
4797
    * @param int $row_offset Optional, default 0
4798
    * @param int $limit Optional, default -1
4799
    * @return array
4800
    */
4801
    function build_related_list($query, &$template, $row_offset = 0, $limit = -1)
4802
    {
4803
        $GLOBALS['log']->debug("Finding linked records $this->object_name: ".$query);
4804
        $db = DBManagerFactory::getInstance('listviews');
4805
4806
        if(!empty($row_offset) && $row_offset != 0 && !empty($limit) && $limit != -1)
4807
        {
4808
            $result = $db->limitQuery($query, $row_offset, $limit,true,"Error retrieving $template->object_name list: ");
4809
        }
4810
        else
4811
        {
4812
            $result = $db->query($query, true);
4813
        }
4814
4815
        $list = Array();
4816
        $isFirstTime = true;
4817
        $class = get_class($template);
4818
        while($row = $this->db->fetchByAssoc($result))
4819
        {
4820
            if(!$isFirstTime)
4821
            {
4822
                $template = new $class();
4823
            }
4824
            $isFirstTime = false;
4825
            $record = $template->retrieve($row['id']);
4826
4827
            if($record != null)
4828
            {
4829
                // this copies the object into the array
4830
                $list[] = $template;
4831
            }
4832
        }
4833
        return $list;
4834
    }
4835
4836
  /**
4837
    * This function is used to execute the query and create an array template objects
4838
    * from the resulting ids from the query.
4839
    * It is currently used for building sub-panel arrays. It supports an additional
4840
    * where clause that is executed as a filter on the results
4841
    *
4842
    * @param string $query - the query that should be executed to build the list
4843
    * @param object $template - The object that should be used to copy the records.
4844
    */
4845
  function build_related_list_where($query, &$template, $where='', $in='', $order_by, $limit='', $row_offset = 0)
4846
  {
4847
    $db = DBManagerFactory::getInstance('listviews');
4848
    // No need to do an additional query
4849
    $GLOBALS['log']->debug("Finding linked records $this->object_name: ".$query);
4850
    if(empty($in) && !empty($query))
4851
    {
4852
        $idList = $this->build_related_in($query);
4853
        $in = $idList['in'];
4854
    }
4855
    // MFH - Added Support For Custom Fields in Searches
4856
    $custom_join = $this->getCustomJoin();
4857
4858
    $query = "SELECT id ";
4859
4860
    $query .= $custom_join['select'];
4861
    $query .= " FROM $this->table_name ";
4862
4863
    $query .= $custom_join['join'];
4864
4865
    $query .= " WHERE deleted=0 AND id IN $in";
4866
    if(!empty($where))
4867
    {
4868
        $query .= " AND $where";
4869
    }
4870
4871
4872
    if(!empty($order_by))
4873
    {
4874
        $query .= "ORDER BY $order_by";
4875
    }
4876
    if (!empty($limit))
4877
    {
4878
        $result = $db->limitQuery($query, $row_offset, $limit,true,"Error retrieving $this->object_name list: ");
4879
    }
4880
    else
4881
    {
4882
        $result = $db->query($query, true);
4883
    }
4884
4885
    $list = Array();
4886
    $isFirstTime = true;
4887
    $class = get_class($template);
4888
4889
    $disable_security_flag = ($template->disable_row_level_security) ? true : false;
4890
    while($row = $db->fetchByAssoc($result))
4891
    {
4892
        if(!$isFirstTime)
4893
        {
4894
            $template = new $class();
4895
            $template->disable_row_level_security = $disable_security_flag;
4896
        }
4897
        $isFirstTime = false;
4898
        $record = $template->retrieve($row['id']);
4899
        if($record != null)
4900
        {
4901
            // this copies the object into the array
4902
            $list[] = $template;
4903
        }
4904
    }
4905
4906
    return $list;
4907
  }
4908
4909
    /**
4910
     * Constructs an comma separated list of ids from passed query results.
4911
     *
4912
     * @param string @query query to be executed.
4913
     *
4914
     */
4915
    function build_related_in($query)
4916
    {
4917
        $idList = array();
4918
        $result = $this->db->query($query, true);
4919
        $ids = '';
4920
        while($row = $this->db->fetchByAssoc($result))
4921
        {
4922
            $idList[] = $row['id'];
4923
            if(empty($ids))
4924
            {
4925
                $ids = "('" . $row['id'] . "'";
4926
            }
4927
            else
4928
            {
4929
                $ids .= ",'" . $row['id'] . "'";
4930
            }
4931
        }
4932
        if(empty($ids))
4933
        {
4934
            $ids = "('')";
4935
        }else{
4936
            $ids .= ')';
4937
        }
4938
4939
        return array('list'=>$idList, 'in'=>$ids);
4940
    }
4941
4942
    /**
4943
    * Optionally copies values from fetched row into the bean.
4944
    *
4945
    * Internal function, do not override.
4946
    *
4947
    * @param string $query - the query that should be executed to build the list
4948
    * @param object $template - The object that should be used to copy the records
4949
    * @param array $field_list List of  fields.
4950
    * @return array
4951
    */
4952
    function build_related_list2($query, &$template, &$field_list)
4953
    {
4954
        $GLOBALS['log']->debug("Finding linked values $this->object_name: ".$query);
4955
4956
        $result = $this->db->query($query, true);
4957
4958
        $list = Array();
4959
        $isFirstTime = true;
4960
        $class = get_class($template);
4961
        while($row = $this->db->fetchByAssoc($result))
4962
        {
4963
            // Create a blank copy
4964
            $copy = $template;
4965
            if(!$isFirstTime)
4966
            {
4967
                $copy = new $class();
4968
            }
4969
            $isFirstTime = false;
4970
            foreach($field_list as $field)
4971
            {
4972
                // Copy the relevant fields
4973
                $copy->$field = $row[$field];
4974
4975
            }
4976
4977
            // this copies the object into the array
4978
            $list[] = $copy;
4979
        }
4980
4981
        return $list;
4982
    }
4983
4984
    /**
4985
     * Let implementing classes to fill in row specific columns of a list view form
4986
     *
4987
     */
4988
    function list_view_parse_additional_sections(&$list_form)
4989
    {
4990
    }
4991
    /**
4992
     * Assigns all of the values into the template for the list view
4993
     */
4994
    function get_list_view_array()
4995
    {
4996
        static $cache = array();
4997
        // cn: bug 12270 - sensitive fields being passed arbitrarily in listViews
4998
        $sensitiveFields = array('user_hash' => '');
4999
5000
        $return_array = Array();
5001
        global $app_list_strings, $mod_strings;
5002
        foreach($this->field_defs as $field=>$value){
5003
5004
            if(isset($this->$field)){
5005
5006
                // cn: bug 12270 - sensitive fields being passed arbitrarily in listViews
5007
                if(isset($sensitiveFields[$field]))
5008
                    continue;
5009
                if(!isset($cache[$field]))
5010
                    $cache[$field] = strtoupper($field);
5011
5012
                //Fields hidden by Dependent Fields
5013
                if (isset($value['hidden']) && $value['hidden'] === true) {
5014
                        $return_array[$cache[$field]] = "";
5015
5016
                }
5017
                //cn: if $field is a _dom, detect and return VALUE not KEY
5018
                //cl: empty function check for meta-data enum types that have values loaded from a function
5019
                else if (((!empty($value['type']) && ($value['type'] == 'enum' || $value['type'] == 'radioenum') ))  && empty($value['function'])){
5020
                    if(!empty($value['options']) && !empty($app_list_strings[$value['options']][$this->$field])){
5021
                        $return_array[$cache[$field]] = $app_list_strings[$value['options']][$this->$field];
5022
                    }
5023
                    //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.
5024
                    elseif(!empty($value['options']) && !empty($mod_strings[$value['options']][$this->$field]))
5025
                    {
5026
                        $return_array[$cache[$field]] = $mod_strings[$value['options']][$this->$field];
5027
                    }
5028
                    else{
5029
                        $return_array[$cache[$field]] = $this->$field;
5030
                    }
5031
                    //end bug 21672
5032
// tjy: no need to do this str_replace as the changes in 29994 for ListViewGeneric.tpl for translation handle this now
5033
//				}elseif(!empty($value['type']) && $value['type'] == 'multienum'&& empty($value['function'])){
5034
//					$return_array[strtoupper($field)] = str_replace('^,^', ', ', $this->$field );
5035
                }elseif(!empty($value['custom_module']) && $value['type'] != 'currency'){
5036
//					$this->format_field($value);
5037
                    $return_array[$cache[$field]] = $this->$field;
5038
                }else{
5039
                    $return_array[$cache[$field]] = $this->$field;
5040
                }
5041
                // handle "Assigned User Name"
5042
                if($field == 'assigned_user_name'){
5043
                    $return_array['ASSIGNED_USER_NAME'] = get_assigned_user_name($this->assigned_user_id);
5044
                }
5045
            }
5046
        }
5047
        return $return_array;
5048
    }
5049
    /**
5050
     * Override this function to set values in the array used to render list view data.
5051
     *
5052
     */
5053
    function get_list_view_data()
5054
    {
5055
        return $this->get_list_view_array();
5056
    }
5057
5058
    /**
5059
     * Construct where clause from a list of name-value pairs.
5060
     * @param array $fields_array Name/value pairs for column checks
5061
     * @param boolean $deleted Optional, default true, if set to false deleted filter will not be added.
5062
     * @return string The WHERE clause
5063
     */
5064
    function get_where($fields_array, $deleted=true)
5065
    {
5066
        $where_clause = "";
5067
        foreach ($fields_array as $name=>$value)
5068
        {
5069
            if (!empty($where_clause)) {
5070
                $where_clause .= " AND ";
5071
            }
5072
            $name = $this->db->getValidDBName($name);
5073
5074
            $where_clause .= "$name = ".$this->db->quoted($value,false);
5075
        }
5076
        if(!empty($where_clause)) {
5077
            if($deleted) {
5078
                return "WHERE $where_clause AND deleted=0";
5079
            } else {
5080
                return "WHERE $where_clause";
5081
            }
5082
        } else {
5083
            return "";
5084
        }
5085
    }
5086
5087
5088
    /**
5089
     * Constructs a select query and fetch 1 row using this query, and then process the row
5090
     *
5091
     * Internal function, do not override.
5092
     * @param array @fields_array  array of name value pairs used to construct query.
5093
     * @param boolean $encode Optional, default true, encode fetched data.
5094
     * @param boolean $deleted Optional, default true, if set to false deleted filter will not be added.
5095
     * @return object Instance of this bean with fetched data.
5096
     */
5097
    function retrieve_by_string_fields($fields_array, $encode=true, $deleted=true)
5098
    {
5099
        $where_clause = $this->get_where($fields_array, $deleted);
5100
        $custom_join = $this->getCustomJoin();
5101
        $query = "SELECT $this->table_name.*". $custom_join['select']. " FROM $this->table_name " . $custom_join['join'];
5102
        $query .= " $where_clause";
5103
        $GLOBALS['log']->debug("Retrieve $this->object_name: ".$query);
5104
        //requireSingleResult has been deprecated.
5105
        //$result = $this->db->requireSingleResult($query, true, "Retrieving record $where_clause:");
5106
        $result = $this->db->limitQuery($query,0,1,true, "Retrieving record $where_clause:");
5107
5108
5109
        if( empty($result))
5110
        {
5111
            return null;
5112
        }
5113
        $row = $this->db->fetchByAssoc($result, $encode);
5114
        if(empty($row))
5115
        {
5116
            return null;
5117
        }
5118
        // Removed getRowCount-if-clause earlier and insert duplicates_found here as it seems that we have found something
5119
        // if we didn't return null in the previous clause.
5120
        $this->duplicates_found = true;
5121
        $row = $this->convertRow($row);
5122
        $this->fetched_row = $row;
5123
        $this->fromArray($row);
5124
		$this->is_updated_dependent_fields = false;
5125
        $this->fill_in_additional_detail_fields();
5126
        return $this;
5127
    }
5128
5129
    /**
5130
    * This method is called during an import before inserting a bean
5131
    * Define an associative array called $special_fields
5132
    * the keys are user defined, and don't directly map to the bean's fields
5133
    * the value is the method name within that bean that will do extra
5134
    * processing for that field. example: 'full_name'=>'get_names_from_full_name'
5135
    *
5136
    */
5137
    function process_special_fields()
5138
    {
5139
        foreach ($this->special_functions as $func_name)
5140
        {
5141
            if ( method_exists($this,$func_name) )
5142
            {
5143
                $this->$func_name();
5144
            }
5145
        }
5146
    }
5147
5148
    /**
5149
     * Override this function to build a where clause based on the search criteria set into bean .
5150
     * @abstract
5151
     */
5152
    function build_generic_where_clause($value)
5153
    {
5154
    }
5155
5156
    function getRelatedFields($module, $id, $fields, $return_array = false){
5157
        if(empty($GLOBALS['beanList'][$module]))return '';
5158
        $object = BeanFactory::getObjectName($module);
5159
5160
        VardefManager::loadVardef($module, $object);
5161
        if(empty($GLOBALS['dictionary'][$object]['table']))return '';
5162
        $table = $GLOBALS['dictionary'][$object]['table'];
5163
        $query  = 'SELECT id';
5164
        foreach($fields as $field=>$alias){
5165
            if(!empty($GLOBALS['dictionary'][$object]['fields'][$field]['db_concat_fields'])){
5166
                $query .= ' ,' .$this->db->concat($table, $GLOBALS['dictionary'][$object]['fields'][$field]['db_concat_fields']) .  ' as ' . $alias ;
5167
            }else if(!empty($GLOBALS['dictionary'][$object]['fields'][$field]) &&
5168
                (empty($GLOBALS['dictionary'][$object]['fields'][$field]['source']) ||
5169
                $GLOBALS['dictionary'][$object]['fields'][$field]['source'] != "non-db"))
5170
            {
5171
                $query .= ' ,' .$table . '.' . $field . ' as ' . $alias;
5172
            }
5173
            if(!$return_array)$this->$alias = '';
5174
        }
5175
        if($query == 'SELECT id' || empty($id)){
5176
            return '';
5177
        }
5178
5179
5180
        if(isset($GLOBALS['dictionary'][$object]['fields']['assigned_user_id']))
5181
        {
5182
            $query .= " , ".	$table  . ".assigned_user_id owner";
5183
5184
        }
5185
        else if(isset($GLOBALS['dictionary'][$object]['fields']['created_by']))
5186
        {
5187
            $query .= " , ".	$table . ".created_by owner";
5188
5189
        }
5190
        $query .=  ' FROM ' . $table . ' WHERE deleted=0 AND id=';
5191
        $result = $GLOBALS['db']->query($query . "'$id'" );
5192
        $row = $GLOBALS['db']->fetchByAssoc($result);
5193
        if($return_array){
5194
            return $row;
5195
        }
5196
        $owner = (empty($row['owner']))?'':$row['owner'];
5197
        foreach($fields as $alias){
5198
            $this->$alias = (!empty($row[$alias]))? $row[$alias]: '';
5199
            $alias = $alias  .'_owner';
5200
            $this->$alias = $owner;
5201
            $a_mod = $alias  .'_mod';
5202
            $this->$a_mod = $module;
5203
        }
5204
5205
5206
    }
5207
5208
5209
    function &parse_additional_headers(&$list_form, $xTemplateSection)
5210
    {
5211
        return $list_form;
5212
    }
5213
5214
    function assign_display_fields($currentModule)
5215
    {
5216
        global $timedate;
5217
        foreach($this->column_fields as $field)
5218
        {
5219
            if(isset($this->field_name_map[$field]) && empty($this->$field))
5220
            {
5221
                if($this->field_name_map[$field]['type'] != 'date' && $this->field_name_map[$field]['type'] != 'enum')
5222
                $this->$field = $field;
5223
                if($this->field_name_map[$field]['type'] == 'date')
5224
                {
5225
                    $this->$field = $timedate->to_display_date('1980-07-09');
5226
                }
5227
                if($this->field_name_map[$field]['type'] == 'enum')
5228
                {
5229
                    $dom = $this->field_name_map[$field]['options'];
5230
                    global $current_language, $app_list_strings;
5231
                    $mod_strings = return_module_language($current_language, $currentModule);
5232
5233
                    if(isset($mod_strings[$dom]))
5234
                    {
5235
                        $options = $mod_strings[$dom];
5236
                        foreach($options as $key=>$value)
5237
                        {
5238
                            if(!empty($key) && empty($this->$field ))
5239
                            {
5240
                                $this->$field = $key;
5241
                            }
5242
                        }
5243
                    }
5244
                    if(isset($app_list_strings[$dom]))
5245
                    {
5246
                        $options = $app_list_strings[$dom];
5247
                        foreach($options as $key=>$value)
5248
                        {
5249
                            if(!empty($key) && empty($this->$field ))
5250
                            {
5251
                                $this->$field = $key;
5252
                            }
5253
                        }
5254
                    }
5255
5256
5257
                }
5258
            }
5259
        }
5260
    }
5261
5262
    /*
5263
    * 	RELATIONSHIP HANDLING
5264
    */
5265
5266
    function set_relationship($table, $relate_values, $check_duplicates = true,$do_update=false,$data_values=null)
5267
    {
5268
        $where = '';
5269
5270
		// make sure there is a date modified
5271
		$date_modified = $this->db->convert("'".$GLOBALS['timedate']->nowDb()."'", 'datetime');
5272
5273
        $row=null;
5274
        if($check_duplicates)
5275
        {
5276
            $query = "SELECT * FROM $table ";
5277
            $where = "WHERE deleted = '0'  ";
5278
            foreach($relate_values as $name=>$value)
5279
            {
5280
                $where .= " AND $name = '$value' ";
5281
            }
5282
            $query .= $where;
5283
            $result = $this->db->query($query, false, "Looking For Duplicate Relationship:" . $query);
5284
            $row=$this->db->fetchByAssoc($result);
5285
        }
5286
5287
        if(!$check_duplicates || empty($row) )
5288
        {
5289
            unset($relate_values['id']);
5290
            if ( isset($data_values))
5291
            {
5292
                $relate_values = array_merge($relate_values,$data_values);
5293
            }
5294
            $query = "INSERT INTO $table (id, ". implode(',', array_keys($relate_values)) . ", date_modified) VALUES ('" . create_guid() . "', " . "'" . implode("', '", $relate_values) . "', ".$date_modified.")" ;
5295
5296
            $this->db->query($query, false, "Creating Relationship:" . $query);
5297
        }
5298
        else if ($do_update)
5299
        {
5300
            $conds = array();
5301
            foreach($data_values as $key=>$value)
5302
            {
5303
                array_push($conds,$key."='".$this->db->quote($value)."'");
5304
            }
5305
            $query = "UPDATE $table SET ". implode(',', $conds).",date_modified=".$date_modified." ".$where;
5306
            $this->db->query($query, false, "Updating Relationship:" . $query);
5307
        }
5308
    }
5309
5310
    function retrieve_relationships($table, $values, $select_id)
5311
    {
5312
        $query = "SELECT $select_id FROM $table WHERE deleted = 0  ";
5313
        foreach($values as $name=>$value)
5314
        {
5315
            $query .= " AND $name = '$value' ";
5316
        }
5317
        $query .= " ORDER BY $select_id ";
5318
        $result = $this->db->query($query, false, "Retrieving Relationship:" . $query);
5319
        $ids = array();
5320
        while($row = $this->db->fetchByAssoc($result))
5321
        {
5322
            $ids[] = $row;
5323
        }
5324
        return $ids;
5325
    }
5326
5327
    // TODO: this function needs adjustment
5328
    function loadLayoutDefs()
5329
    {
5330
        global $layout_defs;
5331
        if(empty( $this->layout_def) && file_exists('modules/'. $this->module_dir . '/layout_defs.php'))
5332
        {
5333
            include_once('modules/'. $this->module_dir . '/layout_defs.php');
5334
            if(file_exists('custom/modules/'. $this->module_dir . '/Ext/Layoutdefs/layoutdefs.ext.php'))
5335
            {
5336
                include_once('custom/modules/'. $this->module_dir . '/Ext/Layoutdefs/layoutdefs.ext.php');
5337
            }
5338
            if ( empty( $layout_defs[get_class($this)]))
5339
            {
5340
                echo "\$layout_defs[" . get_class($this) . "]; does not exist";
5341
            }
5342
5343
            $this->layout_def = $layout_defs[get_class($this)];
5344
        }
5345
    }
5346
5347
    /**
5348
    * Trigger custom logic for this module that is defined for the provided hook
5349
    * The custom logic file is located under custom/modules/[CURRENT_MODULE]/logic_hooks.php.
5350
    * That file should define the $hook_version that should be used.
5351
    * It should also define the $hook_array.  The $hook_array will be a two dimensional array
5352
    * the first dimension is the name of the event, the second dimension is the information needed
5353
    * to fire the hook.  Each entry in the top level array should be defined on a single line to make it
5354
    * easier to automatically replace this file.  There should be no contents of this file that are not replacable.
5355
    *
5356
    * $hook_array['before_save'][] = Array(1, testtype, 'custom/modules/Leads/test12.php', 'TestClass', 'lead_before_save_1');
5357
    * This sample line creates a before_save hook.  The hooks are procesed in the order in which they
5358
    * are added to the array.  The second dimension is an array of:
5359
    *		processing index (for sorting before exporting the array)
5360
    *		A logic type hook
5361
    *		label/type
5362
    *		php file to include
5363
    *		php class the method is in
5364
    *		php method to call
5365
    *
5366
    * The method signature for version 1 hooks is:
5367
    * function NAME(&$bean, $event, $arguments)
5368
    * 		$bean - $this bean passed in by reference.
5369
    *		$event - The string for the current event (i.e. before_save)
5370
    * 		$arguments - An array of arguments that are specific to the event.
5371
    */
5372
    function call_custom_logic($event, $arguments = null)
5373
    {
5374
        if(!isset($this->processed) || $this->processed == false){
5375
            //add some logic to ensure we do not get into an infinite loop
5376
            if(!empty($this->logicHookDepth[$event])) {
5377
                if($this->logicHookDepth[$event] > $this->max_logic_depth)
5378
                    return;
5379
            }else
5380
                $this->logicHookDepth[$event] = 0;
5381
5382
            //we have to put the increment operator here
5383
            //otherwise we may never increase the depth for that event in the case
5384
            //where one event will trigger another as in the case of before_save and after_save
5385
            //Also keeping the depth per event allow any number of hooks to be called on the bean
5386
            //and we only will return if one event gets caught in a loop. We do not increment globally
5387
            //for each event called.
5388
            $this->logicHookDepth[$event]++;
5389
5390
            //method defined in 'include/utils/LogicHook.php'
5391
5392
            $logicHook = new LogicHook();
5393
            $logicHook->setBean($this);
5394
            $logicHook->call_custom_logic($this->module_dir, $event, $arguments);
5395
            $this->logicHookDepth[$event]--;
5396
        }
5397
    }
5398
5399
5400
    /*	When creating a custom field of type Dropdown, it creates an enum row in the DB.
5401
     A typical get_list_view_array() result will have the *KEY* value from that drop-down.
5402
     Since custom _dom objects are flat-files included in the $app_list_strings variable,
5403
     We need to generate a key-key pair to get the true value like so:
5404
     ([module]_cstm->fields_meta_data->$app_list_strings->*VALUE*)*/
5405
    function getRealKeyFromCustomFieldAssignedKey($name)
5406
    {
5407
        if ($this->custom_fields->avail_fields[$name]['ext1'])
5408
        {
5409
            $realKey = 'ext1';
5410
        }
5411
        elseif ($this->custom_fields->avail_fields[$name]['ext2'])
5412
        {
5413
            $realKey = 'ext2';
5414
        }
5415
        elseif ($this->custom_fields->avail_fields[$name]['ext3'])
5416
        {
5417
            $realKey = 'ext3';
5418
        }
5419
        else
5420
        {
5421
            $GLOBALS['log']->fatal("SUGARBEAN: cannot find Real Key for custom field of type dropdown - cannot return Value.");
5422
            return false;
5423
        }
5424
        if(isset($realKey))
5425
        {
5426
            return $this->custom_fields->avail_fields[$name][$realKey];
5427
        }
5428
    }
5429
5430
    function bean_implements($interface)
5431
    {
5432
        return false;
5433
    }
5434
    /**
5435
    * Check whether the user has access to a particular view for the current bean/module
5436
    * @param $view string required, the view to determine access for i.e. DetailView, ListView...
5437
    * @param $is_owner bool optional, this is part of the ACL check if the current user is an owner they will receive different access
5438
    */
5439
	/* BEGIN - SECURITY GROUPS - aclaccess */  
5440
	/**
5441
    function ACLAccess($view,$is_owner='not_set')
5442
	*/
5443
    function ACLAccess($view,$is_owner='not_set',$in_group='not_set')
5444
    {
5445
        global $current_user;
5446
        if($current_user->isAdmin()) {
5447
            return true;
5448
        }
5449
        $not_set = false;
5450
		/**
5451
        if($is_owner == 'not_set')
5452
		*/
5453
    	if($is_owner === 'not_set') //eggsurplus: should be ===
5454
        {
5455
            $not_set = true;
5456
            $is_owner = $this->isOwner($current_user->id);
5457
        }
5458
		// DJM - OBS Customizations - May 2009
5459
		// Moved this code to convert to lowercase from below.
5460
		// Added new action variable.
5461
		$view = strtolower($view);
5462
		$action = '';
5463
		// DJM - OBS Customizations - END CHANGE
5464
    	if($in_group === 'not_set')
5465
    	{
5466
			require_once("modules/SecurityGroups/SecurityGroup.php");
5467
			// DJM - OBS Customizations - May 2009
5468
			// Added the following switch statement to convert the view
5469
			// into an action value.  As per the switch below.
5470
			// Added the action parameter to the groupHasAccess call.
5471
    			switch ($view)
5472
    			{
5473
    				case 'list':
5474
    				case 'index':
5475
    				case 'listview':
5476
    					$action = "list";
5477
					break;
5478
    				case 'edit':
5479
    				case 'save':
5480
		    		case 'popupeditview':
5481
 		   		case 'editview':
5482
  		  			$action = "edit";
5483
					break;
5484
 		   		case 'view':
5485
 		   		case 'detail':
5486
 		   		case 'detailview':
5487
 		   			$action = "view";
5488
					break;
5489
 		   		case 'delete':
5490
 		   			$action = "delete" ;
5491
					break;
5492
 		   		case 'export':
5493
 		   			$action = "export";
5494
					break;
5495
 		   		case 'import':
5496
  		  			$action = "import";
5497
					break;
5498
				default:
5499
					$action = "";
5500
					break;
5501
    			}
5502
			$in_group = SecurityGroup::groupHasAccess($this->module_dir,$this->id, $action); 
5503
			// DJM - OBS Customizations - END CHANGE
5504
    	}
5505
        //if we don't implent acls return true
5506
        if(!$this->bean_implements('ACL'))
5507
        return true;
5508
        $view = strtolower($view);
5509
        switch ($view)
5510
        {
5511
            case 'list':
5512
            case 'index':
5513
            case 'listview':
5514
				/**
5515
                return ACLController::checkAccess($this->module_dir,'list', true);
5516
				*/
5517
    			return ACLController::checkAccess($this->module_dir,'list', true, $this->acltype, $in_group);
5518
            case 'edit':
5519
            case 'save':
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
5520
                if( !$is_owner && $not_set && !empty($this->id)){
5521
                    $class = get_class($this);
5522
                    $temp = new $class();
5523
                    if(!empty($this->fetched_row) && !empty($this->fetched_row['id']) && !empty($this->fetched_row['assigned_user_id']) && !empty($this->fetched_row['created_by'])){
5524
                        $temp->populateFromRow($this->fetched_row);
5525
                    }else{
5526
                        $temp->retrieve($this->id);
5527
                    }
5528
                    $is_owner = $temp->isOwner($current_user->id);
5529
                }
5530
            case 'popupeditview':
5531
            case 'editview':
5532
				/**
5533
                return ACLController::checkAccess($this->module_dir,'edit', $is_owner, $this->acltype);
5534
				*/
5535
    			return ACLController::checkAccess($this->module_dir,'edit', $is_owner, $this->acltype, $in_group);
5536
            case 'view':
5537
            case 'detail':
5538
            case 'detailview':
5539
				/**
5540
                return ACLController::checkAccess($this->module_dir,'view', $is_owner, $this->acltype);
5541
				*/
5542
    			return ACLController::checkAccess($this->module_dir,'view', $is_owner, $this->acltype, $in_group);
5543
            case 'delete':
5544
				/**
5545
                return ACLController::checkAccess($this->module_dir,'delete', $is_owner, $this->acltype);
5546
				*/
5547
    			return ACLController::checkAccess($this->module_dir,'delete', $is_owner, $this->acltype, $in_group);
5548
            case 'export':
5549
				/**
5550
                return ACLController::checkAccess($this->module_dir,'export', $is_owner, $this->acltype);
5551
				*/
5552
    			return ACLController::checkAccess($this->module_dir,'export', $is_owner, $this->acltype, $in_group);
5553
            case 'import':
5554
				/**
5555
                return ACLController::checkAccess($this->module_dir,'import', true, $this->acltype);
5556
				*/
5557
    			return ACLController::checkAccess($this->module_dir,'import', true, $this->acltype, $in_group);
5558
        }
5559
        //if it is not one of the above views then it should be implemented on the page level
5560
        return true;
5561
    }
5562
    /* END - SECURITY GROUPS */
5563
5564
    /**
5565
    * Get owner field
5566
    *
5567
    * @return STRING
5568
    */
5569
    function getOwnerField($returnFieldName = false)
5570
    {
5571
        if (isset($this->field_defs['assigned_user_id']))
5572
        {
5573
            return $returnFieldName? 'assigned_user_id': $this->assigned_user_id;
5574
        }
5575
5576
        if (isset($this->field_defs['created_by']))
5577
        {
5578
            return $returnFieldName? 'created_by': $this->created_by;
5579
        }
5580
5581
        return '';
5582
    }
5583
5584
    /**
5585
    * Returns true of false if the user_id passed is the owner
5586
    *
5587
    * @param GUID $user_id
5588
    * @return boolean
5589
    */
5590
    function isOwner($user_id)
5591
    {
5592
        //if we don't have an id we must be the owner as we are creating it
5593
        if(!isset($this->id))
5594
        {
5595
            return true;
5596
        }
5597
        //if there is an assigned_user that is the owner
5598
        if (!empty($this->fetched_row['assigned_user_id'])) {
5599
            if ($this->fetched_row['assigned_user_id'] == $user_id) {
5600
                return true;
5601
            }
5602
            return false;
5603
        } elseif (isset($this->assigned_user_id)) {
5604
            if($this->assigned_user_id == $user_id) return true;
5605
            return false;
5606
        }
5607
        else
5608
        {
5609
            //other wise if there is a created_by that is the owner
5610
            if(isset($this->created_by) && $this->created_by == $user_id)
5611
            {
5612
                return true;
5613
            }
5614
        }
5615
        return false;
5616
    }
5617
    /**
5618
    * Gets there where statement for checking if a user is an owner
5619
    *
5620
    * @param GUID $user_id
5621
    * @return STRING
5622
    */
5623
    function getOwnerWhere($user_id)
5624
    {
5625
        if(isset($this->field_defs['assigned_user_id']))
5626
        {
5627
            return " $this->table_name.assigned_user_id ='$user_id' ";
5628
        }
5629
        if(isset($this->field_defs['created_by']))
5630
        {
5631
            return " $this->table_name.created_by ='$user_id' ";
5632
        }
5633
        return '';
5634
    }
5635
5636
    /**
5637
    *
5638
    * Used in order to manage ListView links and if they should
5639
    * links or not based on the ACL permissions of the user
5640
    *
5641
    * @return ARRAY of STRINGS
5642
    */
5643
    function listviewACLHelper()
5644
    {
5645
        $array_assign = array();
5646
        if($this->ACLAccess('DetailView'))
5647
        {
5648
            $array_assign['MAIN'] = 'a';
5649
        }
5650
        else
5651
        {
5652
            $array_assign['MAIN'] = 'span';
5653
        }
5654
        return $array_assign;
5655
    }
5656
5657
    /**
5658
    * returns this bean as an array
5659
    *
5660
    * @return array of fields with id, name, access and category
5661
    */
5662
    function toArray($dbOnly = false, $stringOnly = false, $upperKeys=false)
5663
    {
5664
        static $cache = array();
5665
        $arr = array();
5666
5667
        foreach($this->field_defs as $field=>$data)
5668
        {
5669
            if( !$dbOnly || !isset($data['source']) || $data['source'] == 'db')
5670
            if(!$stringOnly || is_string($this->$field))
5671
            if($upperKeys)
5672
            {
5673
                                if(!isset($cache[$field])){
5674
                                    $cache[$field] = strtoupper($field);
5675
                                }
5676
                $arr[$cache[$field]] = $this->$field;
5677
            }
5678
            else
5679
            {
5680
                if(isset($this->$field)){
5681
                    $arr[$field] = $this->$field;
5682
                }else{
5683
                    $arr[$field] = '';
5684
                }
5685
            }
5686
        }
5687
        return $arr;
5688
    }
5689
5690
    /**
5691
    * Converts an array into an acl mapping name value pairs into files
5692
    *
5693
    * @param Array $arr
5694
    */
5695
    function fromArray($arr)
5696
    {
5697
        foreach($arr as $name=>$value)
5698
        {
5699
            $this->$name = $value;
5700
        }
5701
    }
5702
5703
    /**
5704
     * Convert row data from DB format to internal format
5705
     * Mostly useful for dates/times
5706
     * @param array $row
5707
     * @return array $row
5708
     */
5709
    public function convertRow($row)
5710
    {
5711
        foreach($this->field_defs as $name => $fieldDef)
5712
		{
5713
		    // skip empty fields and non-db fields
5714
            if (isset($name) && !empty($row[$name])) {
5715
                $row[$name] = $this->convertField($row[$name], $fieldDef);
5716
            }
5717
        }
5718
		return $row;
5719
    }
5720
5721
    /**
5722
     * Converts the field value based on the provided fieldDef
5723
     * @param $fieldvalue
5724
     * @param $fieldDef
5725
     * @return string
5726
     */
5727
    public function convertField($fieldvalue, $fieldDef)
5728
    {
5729
        if (!empty($fieldvalue)) {
5730
            if (!(isset($fieldDef['source']) &&
5731
                !in_array($fieldDef['source'], array('db', 'custom_fields', 'relate'))
5732
                && !isset($fieldDef['dbType']))
5733
            ) {
5734
                // fromConvert other fields
5735
                $fieldvalue = $this->db->fromConvert($fieldvalue, $this->db->getFieldType($fieldDef));
5736
            }
5737
        }
5738
        return $fieldvalue;
5739
    }
5740
5741
    /**
5742
     * Loads a row of data into instance of a bean. The data is passed as an array to this function
5743
     *
5744
     * @param array $arr row of data fetched from the database.
5745
     * @return  nothing
5746
     *
5747
     * Internal function do not override.
5748
     */
5749
    function loadFromRow($arr)
5750
    {
5751
        $this->populateFromRow($arr);
5752
        $this->processed_dates_times = array();
5753
        $this->check_date_relationships_load();
5754
5755
        $this->fill_in_additional_list_fields();
5756
5757
        if($this->hasCustomFields())$this->custom_fields->fill_relationships();
5758
        $this->call_custom_logic("process_record");
5759
    }
5760
5761
    function hasCustomFields()
5762
    {
5763
        return !empty($GLOBALS['dictionary'][$this->object_name]['custom_fields']);
5764
    }
5765
5766
   /**
5767
    * Ensure that fields within order by clauses are properly qualified with
5768
    * their tablename.  This qualification is a requirement for sql server support.
5769
    *
5770
    * @param string $order_by original order by from the query
5771
    * @param string $qualify prefix for columns in the order by list.
5772
    * @return  prefixed
5773
    *
5774
    * Internal function do not override.
5775
    */
5776
   function create_qualified_order_by( $order_by, $qualify)
5777
   {	// 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
5778
    if (empty($order_by))
5779
    {
5780
        return $order_by;
5781
    }
5782
    $order_by_clause = " ORDER BY ";
5783
    $tmp = explode(",", $order_by);
5784
    $comma = ' ';
5785
    foreach ( $tmp as $stmp)
5786
    {
5787
        $stmp = (substr_count($stmp, ".") > 0?trim($stmp):"$qualify." . trim($stmp));
5788
        $order_by_clause .= $comma . $stmp;
5789
        $comma = ", ";
5790
    }
5791
    return $order_by_clause;
5792
   }
5793
5794
   /**
5795
    * Combined the contents of street field 2 thru 4 into the main field
5796
    *
5797
    * @param string $street_field
5798
    */
5799
5800
   function add_address_streets(
5801
       $street_field
5802
       )
5803
    {
5804
        if (isset($this->$street_field)) {
5805
            $street_field_2 = $street_field.'_2';
5806
            $street_field_3 = $street_field.'_3';
5807
            $street_field_4 = $street_field.'_4';
5808
            if ( isset($this->$street_field_2)) {
5809
                $this->$street_field .= "\n". $this->$street_field_2;
5810
                unset($this->$street_field_2);
5811
            }
5812
            if ( isset($this->$street_field_3)) {
5813
                $this->$street_field .= "\n". $this->$street_field_3;
5814
                unset($this->$street_field_3);
5815
            }
5816
            if ( isset($this->$street_field_4)) {
5817
                $this->$street_field .= "\n". $this->$street_field_4;
5818
                unset($this->$street_field_4);
5819
            }
5820
            $this->$street_field = trim($this->$street_field, "\n");
5821
        }
5822
    }
5823
5824
    protected function getEncryptKey()
5825
    {
5826
        if(empty(self::$field_key)) {
5827
            self::$field_key = blowfishGetKey('encrypt_field');
5828
        }
5829
        return self::$field_key;
5830
    }
5831
5832
/**
5833
 * Encrpyt and base64 encode an 'encrypt' field type in the bean using Blowfish. The default system key is stored in cache/Blowfish/{keytype}
5834
 * @param STRING value -plain text value of the bean field.
5835
 * @return string
5836
 */
5837
    function encrpyt_before_save($value)
5838
    {
5839
        require_once("include/utils/encryption_utils.php");
5840
        return blowfishEncode($this->getEncryptKey(), $value);
5841
    }
5842
5843
/**
5844
 * Decode and decrypt a base 64 encoded string with field type 'encrypt' in this bean using Blowfish.
5845
 * @param STRING value - an encrypted and base 64 encoded string.
5846
 * @return string
5847
 */
5848
    function decrypt_after_retrieve($value)
5849
    {
5850
        if(empty($value)) return $value; // no need to decrypt empty
5851
        require_once("include/utils/encryption_utils.php");
5852
        return blowfishDecode($this->getEncryptKey(), $value);
5853
    }
5854
5855
    /**
5856
    * Moved from save() method, functionality is the same, but this is intended to handle
5857
    * Optimistic locking functionality.
5858
    */
5859
    private function _checkOptimisticLocking($action, $isUpdate){
5860
        if($this->optimistic_lock && !isset($_SESSION['o_lock_fs'])){
5861
            if(isset($_SESSION['o_lock_id']) && $_SESSION['o_lock_id'] == $this->id && $_SESSION['o_lock_on'] == $this->object_name)
5862
            {
5863
                if($action == 'Save' && $isUpdate && isset($this->modified_user_id) && $this->has_been_modified_since($_SESSION['o_lock_dm'], $this->modified_user_id))
5864
                {
5865
                    $_SESSION['o_lock_class'] = get_class($this);
5866
                    $_SESSION['o_lock_module'] = $this->module_dir;
5867
                    $_SESSION['o_lock_object'] = $this->toArray();
5868
                    $saveform = "<form name='save' id='save' method='POST'>";
5869
                    foreach($_POST as $key=>$arg)
5870
                    {
5871
                        $saveform .= "<input type='hidden' name='". addslashes($key) ."' value='". addslashes($arg) ."'>";
5872
                    }
5873
                    $saveform .= "</form><script>document.getElementById('save').submit();</script>";
5874
                    $_SESSION['o_lock_save'] = $saveform;
5875
                    header('Location: index.php?module=OptimisticLock&action=LockResolve');
5876
                    die();
5877
                }
5878
                else
5879
                {
5880
                    unset ($_SESSION['o_lock_object']);
5881
                    unset ($_SESSION['o_lock_id']);
5882
                    unset ($_SESSION['o_lock_dm']);
5883
                }
5884
            }
5885
        }
5886
        else
5887
        {
5888
            if(isset($_SESSION['o_lock_object']))	{ unset ($_SESSION['o_lock_object']); }
5889
            if(isset($_SESSION['o_lock_id']))		{ unset ($_SESSION['o_lock_id']); }
5890
            if(isset($_SESSION['o_lock_dm']))		{ unset ($_SESSION['o_lock_dm']); }
5891
            if(isset($_SESSION['o_lock_fs']))		{ unset ($_SESSION['o_lock_fs']); }
5892
            if(isset($_SESSION['o_lock_save']))		{ unset ($_SESSION['o_lock_save']); }
5893
        }
5894
    }
5895
5896
    /**
5897
    * Send assignment notifications and invites for meetings and calls
5898
    */
5899
    private function _sendNotifications($check_notify){
5900
        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.
5901
           && !$this->isOwner($this->created_by) )  // cn: bug 42727 no need to send email to owner (within workflow)
5902
        {
5903
            $admin = new Administration();
5904
            $admin->retrieveSettings();
5905
            $sendNotifications = false;
5906
5907
            if ($admin->settings['notify_on'])
5908
            {
5909
                $GLOBALS['log']->info("Notifications: user assignment has changed, checking if user receives notifications");
5910
                $sendNotifications = true;
5911
            }
5912
            elseif(isset($_REQUEST['send_invites']) && $_REQUEST['send_invites'] == 1)
5913
            {
5914
                // cn: bug 5795 Send Invites failing for Contacts
5915
                $sendNotifications = true;
5916
            }
5917
            else
5918
            {
5919
                $GLOBALS['log']->info("Notifications: not sending e-mail, notify_on is set to OFF");
5920
            }
5921
5922
5923
            if($sendNotifications == true)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
5924
            {
5925
                $notify_list = $this->get_notification_recipients();
5926
                foreach ($notify_list as $notify_user)
5927
                {
5928
                    $this->send_assignment_notifications($notify_user, $admin);
5929
                }
5930
            }
5931
        }
5932
    }
5933
5934
5935
    /**
5936
     * Called from ImportFieldSanitize::relate(), when creating a new bean in a related module. Will
5937
     * copies fields over from the current bean into the related. Designed to be overriden in child classes.
5938
     *
5939
     * @param SugarBean $newbean newly created related bean
5940
     */
5941
    public function populateRelatedBean(
5942
        SugarBean $newbean
5943
        )
5944
    {
5945
    }
5946
5947
    /**
5948
     * Called during the import process before a bean save, to handle any needed pre-save logic when
5949
     * importing a record
5950
     */
5951
    public function beforeImportSave()
5952
    {
5953
    }
5954
5955
    /**
5956
     * Called during the import process after a bean save, to handle any needed post-save logic when
5957
     * importing a record
5958
     */
5959
    public function afterImportSave()
5960
    {
5961
    }
5962
5963
    /**
5964
     * This function is designed to cache references to field arrays that were previously stored in the
5965
     * bean files and have since been moved to separate files. Was previously in include/CacheHandler.php
5966
     *
5967
     * @deprecated
5968
     * @param $module_dir string the module directory
5969
     * @param $module string the name of the module
5970
     * @param $key string the type of field array we are referencing, i.e. list_fields, column_fields, required_fields
5971
     **/
5972
    private function _loadCachedArray(
5973
        $module_dir,
5974
        $module,
5975
        $key
5976
        )
5977
    {
5978
        static $moduleDefs = array();
5979
5980
        $fileName = 'field_arrays.php';
5981
5982
        $cache_key = "load_cached_array.$module_dir.$module.$key";
5983
        $result = sugar_cache_retrieve($cache_key);
5984
        if(!empty($result))
5985
        {
5986
        	// Use SugarCache::EXTERNAL_CACHE_NULL_VALUE to store null values in the cache.
5987
        	if($result == SugarCache::EXTERNAL_CACHE_NULL_VALUE)
5988
        	{
5989
        		return null;
5990
        	}
5991
5992
            return $result;
5993
        }
5994
5995
        if(file_exists('modules/'.$module_dir.'/'.$fileName))
5996
        {
5997
            // If the data was not loaded, try loading again....
5998
            if(!isset($moduleDefs[$module]))
5999
            {
6000
                include('modules/'.$module_dir.'/'.$fileName);
6001
                $moduleDefs[$module] = $fields_array;
6002
            }
6003
            // Now that we have tried loading, make sure it was loaded
6004
            if(empty($moduleDefs[$module]) || empty($moduleDefs[$module][$module][$key]))
6005
            {
6006
                // It was not loaded....  Fail.  Cache null to prevent future repeats of this calculation
6007
				sugar_cache_put($cache_key, SugarCache::EXTERNAL_CACHE_NULL_VALUE);
6008
                return  null;
6009
            }
6010
6011
            // It has been loaded, cache the result.
6012
            sugar_cache_put($cache_key, $moduleDefs[$module][$module][$key]);
6013
            return $moduleDefs[$module][$module][$key];
6014
        }
6015
6016
        // It was not loaded....  Fail.  Cache null to prevent future repeats of this calculation
6017
        sugar_cache_put($cache_key, SugarCache::EXTERNAL_CACHE_NULL_VALUE);
6018
		return null;
6019
	}
6020
6021
    /**
6022
     * Returns the ACL category for this module; defaults to the SugarBean::$acl_category if defined
6023
     * otherwise it is SugarBean::$module_dir
6024
     *
6025
     * @return string
6026
     */
6027
    public function getACLCategory()
6028
    {
6029
        return !empty($this->acl_category)?$this->acl_category:$this->module_dir;
6030
    }
6031
6032
    /**
6033
     * Returns the query used for the export functionality for a module. Override this method if you wish
6034
     * to have a custom query to pull this data together instead
6035
     *
6036
     * @param string $order_by
6037
     * @param string $where
6038
     * @return string SQL query
6039
     */
6040
	public function create_export_query($order_by, $where)
6041
	{
6042
		return $this->create_new_list_query($order_by, $where, array(), array(), 0, '', false, $this, true, true);
6043
	}
6044
6045
    /**
6046
     * Determine whether the given field is a relate field
6047
     *
6048
     * @param string $field Field name
6049
     * @return bool
6050
     */
6051
    protected function is_relate_field($field)
6052
    {
6053
        if (!isset($this->field_defs[$field]))
6054
        {
6055
            return false;
6056
        }
6057
6058
        $field_def = $this->field_defs[$field];
6059
6060
        return isset($field_def['type'])
6061
            && $field_def['type'] == 'relate'
6062
            && isset($field_def['link']);
6063
    }
6064
6065
    /**
6066
     * Proxy method for DynamicField::getJOIN
6067
     * @param bool $expandedList
6068
     * @param bool $includeRelates
6069
     * @param string|bool $where
6070
     * @return array
6071
     */
6072
    public function getCustomJoin($expandedList = false, $includeRelates = false, &$where = false)
6073
    {
6074
        $result = array(
6075
            'select' => '',
6076
            'join' => ''
6077
        );
6078
        if(isset($this->custom_fields))
6079
        {
6080
            $result = $this->custom_fields->getJOIN($expandedList, $includeRelates, $where);
6081
        }
6082
        return $result;
6083
    }
6084
6085
    /**
6086
     * Populates currency fields in case of currency is default and it's
6087
     * attributes are not retrieved from database (bugs ##44206, 52438)
6088
     */
6089
    protected function populateCurrencyFields()
6090
    {
6091
        if (property_exists($this, 'currency_id') && $this->currency_id == -99) {
6092
            // manually retrieve default currency object as long as it's
6093
            // not stored in database and thus cannot be joined in query
6094
            $currency = BeanFactory::getBean('Currencies', $this->currency_id);
6095
6096
            if ($currency) {
6097
                // walk through all currency-related fields
6098
                foreach ($this->field_defs as $this_field) {
6099
                    if (isset($this_field['type']) && $this_field['type'] == 'relate'
6100
                        && isset($this_field['module'])  && $this_field['module'] == 'Currencies'
6101
                        && isset($this_field['id_name']) && $this_field['id_name'] == 'currency_id') {
6102
                        // populate related properties manually
6103
                        $this_property = $this_field['name'];
6104
                        $currency_property = $this_field['rname'];
6105
                        $this->$this_property = $currency->$currency_property;
6106
                    }
6107
                }
6108
            }
6109
        }
6110
    }
6111
6112
    /**
6113
     * Checks if Bean has email defs
6114
     *
6115
     * @return boolean
6116
     */
6117
    public function hasEmails()
6118
    {
6119
        if (!empty($this->field_defs['email_addresses']) && $this->field_defs['email_addresses']['type'] == 'link' &&
6120
            !empty($this->field_defs['email_addresses_non_primary']) && $this->field_defs['email_addresses_non_primary']['type'] == 'email')
6121
        {
6122
            return true;
6123
        }
6124
        else
6125
        {
6126
            return false;
6127
        }
6128
    }
6129
}
6130