Issues (4069)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

data/SugarBean.php (10 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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