Completed
Push — console-installer ( 186d32...cb4007 )
by Adam
91:14 queued 78:40
created

SugarBean::load_relationship()   C

Complexity

Conditions 11
Paths 8

Size

Total Lines 40
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 24
nc 8
nop 1
dl 0
loc 40
rs 5.2653
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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