Completed
Push — master ( a833c2...4c7587 )
by Adam
18:52
created

DynamicField::fieldExists()   D

Complexity

Conditions 10
Paths 8

Size

Total Lines 26
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 3 Features 0
Metric Value
cc 10
eloc 17
nc 8
nop 2
dl 0
loc 26
rs 4.8196
c 3
b 3
f 0

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
if (!defined('sugarEntry') || !sugarEntry) {
4
    die('Not A Valid Entry Point');
5
}
6
7
/**
8
 *
9
 * SugarCRM Community Edition is a customer relationship management program developed by
10
 * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
11
 *
12
 * SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd.
13
 * Copyright (C) 2011 - 2016 SalesAgility Ltd.
14
 *
15
 * This program is free software; you can redistribute it and/or modify it under
16
 * the terms of the GNU Affero General Public License version 3 as published by the
17
 * Free Software Foundation with the addition of the following permission added
18
 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
19
 * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
20
 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
21
 *
22
 * This program is distributed in the hope that it will be useful, but WITHOUT
23
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
24
 * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
25
 * details.
26
 *
27
 * You should have received a copy of the GNU Affero General Public License along with
28
 * this program; if not, see http://www.gnu.org/licenses or write to the Free
29
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
30
 * 02110-1301 USA.
31
 *
32
 * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
33
 * SW2-130, Cupertino, CA 95014, USA. or at email address [email protected].
34
 *
35
 * The interactive user interfaces in modified source and object code versions
36
 * of this program must display Appropriate Legal Notices, as required under
37
 * Section 5 of the GNU Affero General Public License version 3.
38
 *
39
 * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
40
 * these Appropriate Legal Notices must retain the display of the "Powered by
41
 * SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not
42
 * reasonably feasible for  technical reasons, the Appropriate Legal Notices must
43
 * display the words  "Powered by SugarCRM" and "Supercharged by SuiteCRM".
44
 */
45
class DynamicField
46
{
47
    public $use_existing_labels = false; // this value is set to true by install_custom_fields() in ModuleInstaller.php; everything else expects it to be false
48
    public $base_path = '';
49
    /**
50
     * @var DBManager
51
     */
52
    public $db;
53
    /**
54
     * @var SugarBean
55
     */
56
    public $bean;
57
58
    /**
59
     * DynamicField constructor.
60
     * @param string $module
61
     */
62
    public function __construct($module = '')
63
    {
64
        global $sugar_config;
65
        $this->module = (!empty($module)) ? $module : ((isset($_REQUEST['module']) && !empty($_REQUEST['module'])) ? $_REQUEST ['module'] : '');
66
        $this->base_path = "custom/Extension/modules/{$this->module}/Ext/Vardefs";
67
        if(isset($sugar_config['dbconfig'])) {
68
            $this->db = DBManagerFactory::getInstance();
69
        }
70
    }
71
72
    /**
73
     * @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
74
     * @param string $module
75
     */
76
    public function DynamicField($module = '')
77
    {
78
        $deprecatedMessage = 'PHP4 Style Constructors are deprecated and will be remove in 7.8, please update your code';
79
        if (isset($GLOBALS['log'])) {
80
            $GLOBALS['log']->deprecated($deprecatedMessage);
81
        } else {
82
            trigger_error($deprecatedMessage, E_USER_DEPRECATED);
83
        }
84
        self::__construct($module);
85
    }
86
87
    /**
88
     * @return array|bool|int|mixed|string
89
     */
90
    public function getModuleName()
91
    {
92
        return $this->module;
93
    }
94
95
    /*
96
     * As DynamicField has a counterpart in MBModule, provide the MBModule function getPackageName() here also
97
     */
98
    public function getPackageName()
99
    {
100
        return;
101
    }
102
103
    /**
104
     *
105
     */
106
    public function deleteCache()
107
    {
108
    }
109
110
    /**
111
     * This will add the bean as a reference in this object as well as building the custom field cache if it has not been built
112
     * LOADS THE BEAN IF THE BEAN IS NOT BEING PASSED ALONG IN SETUP IT SHOULD BE SET PRIOR TO SETUP.
113
     *
114
     * @param SugarBean $bean
115
     */
116
    public function setup($bean = null)
117
    {
118
        if ($bean) {
119
            $this->bean = $bean;
120
        }
121
        if (isset($this->bean->module_dir)) {
122
            $this->module = $this->bean->module_dir;
123
        }
124
        if (!isset($GLOBALS['dictionary'][$this->bean->object_name]['custom_fields'])) {
125
            $this->buildCache($this->module);
126
        }
127
    }
128
129
    /**
130
     * @param string $language
131
     * @param $key
132
     * @param $value
133
     */
134
    public function setLabel($language = 'en_us', $key, $value)
135
    {
136
        $params ['label_' . $key] = $value;
137
        require_once 'modules/ModuleBuilder/parsers/parser.label.php';
138
        $parser = new ParserLabel($this->module);
139
        $parser->handleSave($params, $language);
140
    }
141
142
    /**
143
     * Builds the cache for custom fields based on the vardefs.
144
     *
145
     * @param string|bool $module
146
     * @param bool $saveCache Boolean value indicating whether or not to pass saveCache value to saveToVardef, defaults to true
147
     *
148
     * @return bool
149
     */
150
    public function buildCache($module = false, $saveCache = true)
151
    {
152
        //We can't build the cache while installing as the required database tables may not exist.
153
        if (!empty($GLOBALS['installing']) && $GLOBALS['installing'] == true || empty($this->db)) {
154
            return false;
155
        }
156
        if ($module == '../data') {
157
            return false;
158
        }
159
160
        static $results = array();
161
162
        $where = '';
163
        if (!empty($module)) {
164
            $where .= " custom_module='$module' AND ";
165
            unset($results[$module]); // clear out any old results for the module as $results is declared static
166
        } else {
167
            $results = array(); // clear out results - if we remove a module we don't want to have its old vardefs hanging around
168
            return false;
169
        }
170
171
        $GLOBALS['log']->debug('rebuilding cache for ' . $module);
172
        $query = "SELECT * FROM fields_meta_data WHERE $where deleted = 0";
173
174
        $result = $this->db->query($query);
175
        require_once 'modules/DynamicFields/FieldCases.php';
176
177
        // retrieve the field definition from the fields_meta_data table
178
        // using 'encode'=false to fetchByAssoc to prevent any pre-formatting of the base metadata
179
        // for immediate use in HTML. This metadata will be further massaged by get_field_def() and so should not be pre-formatted
180
        while ($row = $this->db->fetchByAssoc($result, false)) {
181
            $field = get_widget($row ['type']);
182
183
            foreach ($row as $key => $value) {
184
                $field->$key = $value;
185
            }
186
            $field->default = $field->default_value;
187
            $vardef = $field->get_field_def();
188
            $vardef ['id'] = $row ['id'];
189
            $vardef ['custom_module'] = $row ['custom_module'];
190
            if (empty($vardef ['source'])) {
191
                $vardef ['source'] = 'custom_fields';
192
            }
193
            if (empty($results [$row ['custom_module']])) {
194
                $results [$row ['custom_module']] = array();
195
            }
196
            $results [$row ['custom_module']] [$row ['name']] = $vardef;
197
        }
198
        if (empty($module)) {
199
            foreach ($results as $module => $result) {
200
                $this->saveToVardef($module, $result, $saveCache);
201
            }
202
        } else {
203
            if (!empty($results [$module])) {
204
                $this->saveToVardef($module, $results [$module], $saveCache);
205
            } else {
206
                $this->saveToVardef($module, array(), $saveCache);
207
            }
208
        }
209
210
        return true;
211
    }
212
213
214
    /**
215
     * Returns the widget for a custom field from the fields_meta_data table.
216
     *
217
     * @param $module
218
     * @param $fieldName
219
     * @return null|TemplateDate|TemplateDecimal|TemplateFloat|TemplateInt|TemplateText|TemplateTextArea
220
     * @throws Exception
221
     */
222
    public function getFieldWidget($module, $fieldName)
223
    {
224
        if (empty($module) || empty($fieldName)) {
225
            sugar_die("Unable to load widget for '$module' : '$fieldName'");
226
        }
227
        $query = "SELECT * FROM fields_meta_data WHERE custom_module='$module' AND name='$fieldName' AND deleted = 0";
228
        $result = $this->db->query($query);
229
        require_once 'modules/DynamicFields/FieldCases.php';
230
        if ($row = $this->db->fetchByAssoc($result)) {
231
            $field = get_widget($row ['type']);
232
            $field->populateFromRow($row);
233
234
            return $field;
235
        }
236
    }
237
238
    /**
239
     * Updates the cached vardefs with the custom field information stored in result.
240
     *
241
     * @param string $module
242
     * @param array $result
243
     * @param bool $saveCache Boolean value indicating whether or not to call VardefManager::saveCache, defaults to true
244
     */
245
    public function saveToVardef($module, $result, $saveCache = true)
246
    {
247
        global $beanList;
248
        if (!empty($beanList [$module])) {
249
            $object = BeanFactory::getObjectName($module);
250
251
            if (empty($GLOBALS['dictionary'][$object]['fields'])) {
252
                //if the vardef isn't loaded let's try loading it.
253
                VardefManager::refreshVardefs($module, $object, null, false);
254
                //if it's still not loaded we really don't have anything useful to cache
255
                if (empty($GLOBALS['dictionary'][$object]['fields'])) {
256
                    return;
257
                }
258
            }
259
            if (!isset($GLOBALS['dictionary'][$object]['custom_fields'])) {
260
                $GLOBALS['dictionary'][$object]['custom_fields'] = false;
261
            }
262
            if (!empty($GLOBALS ['dictionary'] [$object])) {
263
                if (!empty($result)) {
264
                    // First loop to add
265
266
                    foreach ($result as $field) {
267
                        foreach ($field as $k => $v) {
268
                            //allows values for custom fields to be defined outside of the scope of studio
269
                            if (!isset($GLOBALS ['dictionary'] [$object] ['fields'] [$field ['name']][$k])) {
270
                                $GLOBALS ['dictionary'] [$object] ['fields'] [$field ['name']][$k] = $v;
271
                            }
272
                        }
273
                    }
274
275
                    // Second loop to remove
276
                    foreach ($GLOBALS ['dictionary'] [$object] ['fields'] as $name => $fieldDef) {
277
                        if (isset($fieldDef ['custom_module'])) {
278
                            if (!isset($result [$name])) {
279
                                unset($GLOBALS ['dictionary'] [$object] ['fields'] [$name]);
280
                            } else {
281
                                $GLOBALS ['dictionary'] [$object] ['custom_fields'] = true;
282
                            }
283
                        }
284
                    } //if
285
                }
286
            }
287
288
            $manager = new VardefManager();
289
            if ($saveCache) {
290
                $manager->saveCache($this->module, $object);
291
            }
292
293
            // Everything works off of vardefs, so let's have it save the users vardefs
294
            // to the employees module, because they both use the same table behind
295
            // the scenes
296
            if ($module == 'Users') {
297
                $manager->loadVardef('Employees', 'Employee', true);
298
299
                return;
300
            }
301
        }
302
    }
303
304
    /**
305
     * returns either false or an array containing the select and join parameter for a query using custom fields.
306
     *
307
     * @param $expandedList boolean    If true, return a list of all fields with source=custom_fields in the select instead of the standard _cstm.*
308
     *     This is required for any downstream construction of a SQL statement where we need to manipulate the select list,
309
     *     for example, listViews with custom relate fields where the value comes from join rather than from the custom table
310
     * @param bool $includeRelates
311
     * @param bool $where
312
     *
313
     * @return array select=>select columns, join=>prebuilt join statement
314
     */
315
    public function getJOIN($expandedList = false, $includeRelates = false, $where = false)
316
    {
317
        if (!$this->bean->hasCustomFields()) {
318
            return array(
319
                'select' => '',
320
                'join' => '',
321
            );
322
        }
323
324
        if (empty($expandedList)) {
325
            $select = ",{$this->bean->table_name}_cstm.*";
326
        } else {
327
            $select = '';
328
            $isList = is_array($expandedList);
329
            foreach ($this->bean->field_defs as $name => $field) {
330
                if (!empty($field['source']) && $field['source'] == 'custom_fields' && (!$isList || !empty($expandedList[$name]))) {
331
                    // assumption: that the column name in _cstm is the same as the field name. Currently true.
332
                    // however, two types of dynamic fields do not have columns in the custom table - html fields (they're readonly) and flex relates (parent_name doesn't exist)
333
                    if ($field['type'] != 'html' && $name != 'parent_name') {
334
                        $select .= ",{$this->bean->table_name}_cstm.{$name}";
335
                    }
336
                }
337
            }
338
        }
339
        $join = ' LEFT JOIN ' . $this->bean->table_name . '_cstm ON ' . $this->bean->table_name . '.id = ' . $this->bean->table_name . '_cstm.id_c ';
340
341
        if ($includeRelates) {
342
            $jtAlias = 'relJoin';
343
            $jtCount = 1;
344
            foreach ($this->bean->field_defs as $name => $field) {
345
                if ($field['type'] == 'relate' && isset($field['custom_module'])) {
346
                    $relateJoinInfo = $this->getRelateJoin($field, $jtAlias . $jtCount);
347
                    $select .= $relateJoinInfo['select'];
348
                    $join .= $relateJoinInfo['from'];
349
                    //bug 27654 martin
350
                    if ($where) {
351
                        $pattern = '/' . $field['name'] . '\slike/i';
352
                        $replacement = $relateJoinInfo['name_field'] . ' like';
353
                        $where = preg_replace($pattern, $replacement, $where);
354
                    }
355
                    ++$jtCount;
356
                }
357
            }
358
        }
359
360
        return array('select' => $select, 'join' => $join);
361
    }
362
363
    /**
364
     * @param array $field_def
365
     * @param string $joinTableAlias
366
     * @param bool $withIdName
367
     * @return array|bool
368
     */
369
    public function getRelateJoin($field_def, $joinTableAlias, $withIdName = true)
370
    {
371
        if (empty($field_def['type']) || $field_def['type'] != 'relate') {
372
            return false;
373
        }
374
375
        $rel_mod = BeanFactory::getBean($field_def['module']);
376
        if (!$rel_mod) {
377
            return false;
378
        }
379
380
        $rel_table = $rel_mod->table_name;
381
        $name_field = '';
382
        if (isset($rel_mod->field_defs['name'])) {
383
            $name_field_def = $rel_mod->field_defs['name'];
384
            if (isset($name_field_def['db_concat_fields'])) {
385
                $name_field = $this->db->concat($joinTableAlias, $name_field_def['db_concat_fields']);
386
            } //If the name field is non-db, we need to find another field to display
387
            elseif (!empty($rel_mod->field_defs['name']['source']) && $rel_mod->field_defs['name']['source'] == 'non-db' && !empty($field_def['rname'])) {
388
                $name_field = "$joinTableAlias." . $field_def['rname'];
389
            } else {
390
                $name_field = "$joinTableAlias.name";
391
            }
392
        }
393
        $tableName = isset($field_def['custom_module']) ? "{$this->bean->table_name}_cstm" : $this->bean->table_name;
394
        $relID = $field_def['id_name'];
395
        $ret_array['rel_table'] = $rel_table;
396
        $ret_array['name_field'] = $name_field;
397
        $ret_array['select'] = ($withIdName ? ", {$tableName}.{$relID}" : '') . ", {$name_field} {$field_def['name']} ";
398
        $ret_array['from'] = " LEFT JOIN $rel_table $joinTableAlias ON $tableName.$relID = $joinTableAlias.id"
399
            . " AND $joinTableAlias.deleted=0 ";
400
401
        return $ret_array;
402
    }
403
404
    /**
405
     * Fills in all the custom fields of type relate relationships for an object.
406
     */
407
    public function fill_relationships()
408
    {
409
        if (!empty($this->bean->relDepth)) {
410
            if ($this->bean->relDepth > 1) {
411
                return;
412
            }
413
        } else {
414
            $this->bean->relDepth = 0;
415
        }
416
        foreach ($this->bean->field_defs as $field) {
417
            if (empty($field['source']) || $field['source'] != 'custom_fields') {
418
                continue;
419
            }
420
            if ($field['type'] == 'relate') {
421
                $related_module = $field['ext2'];
422
                $name = $field['name'];
423
                if (empty($this->bean->$name)) { //Don't load the relationship twice
424
                    $id_name = $field['id_name'];
425
                    $mod = BeanFactory::getBean($related_module);
426
                    if (is_object($mod) && isset($this->bean->$name)) {
427
                        $mod->relDepth = $this->bean->relDepth + 1;
428
                        $mod->retrieve($this->bean->$id_name);
429
                        $this->bean->$name = $mod->name;
430
                    }
431
                }
432
            }
433
        }
434
    }
435
436
    /**
437
     * Process the save action for sugar bean custom fields.
438
     *
439
     * @param bool $isUpdate
440
     */
441
    public function save($isUpdate)
442
    {
443
        if ($this->bean->hasCustomFields() && isset($this->bean->id)) {
444
            $query = '';
445
            if ($isUpdate) {
446
                $query = 'UPDATE ' . $this->bean->table_name . '_cstm SET ';
447
            }
448
            $queryInsert = 'INSERT INTO ' . $this->bean->table_name . '_cstm (id_c';
449
            $values = "('" . $this->bean->id . "'";
450
            $first = true;
451
            foreach ($this->bean->field_defs as $name => $field) {
452
                if (empty($field['source']) || $field['source'] != 'custom_fields') {
453
                    continue;
454
                }
455
                if ($field['type'] == 'html' || $field['type'] == 'parent') {
456
                    continue;
457
                }
458
                if (isset($this->bean->$name)) {
459
                    $quote = "'";
460
461
                    if (in_array($field['type'], array('int', 'float', 'double', 'uint', 'ulong', 'long', 'short', 'tinyint', 'currency', 'decimal'))) {
462
                        $quote = '';
463
                        if (!isset($this->bean->$name) || !is_numeric($this->bean->$name)) {
464
                            if ($field['required']) {
465
                                $this->bean->$name = 0;
466
                            } else {
467
                                $this->bean->$name = 'NULL';
468
                            }
469
                        }
470
                    }
471
                    if ($field['type'] == 'bool') {
472
                        if ($this->bean->$name === false) {
473
                            $this->bean->$name = '0';
474
                        } elseif ($this->bean->$name === true) {
475
                            $this->bean->$name = '1';
476
                        }
477
                    }
478
479
                    $val = $this->bean->$name;
480
                    if (($field['type'] == 'date' || $field['type'] == 'datetimecombo') && (empty($this->bean->$name) || $this->bean->$name == '1900-01-01')) {
481
                        $quote = '';
482
                        $val = 'NULL';
483
                        $this->bean->$name = ''; // do not set it to string 'NULL'
484
                    }
485
                    if ($isUpdate) {
486
                        if ($first) {
487
                            $query .= " $name=$quote" . $this->db->quote($val) . "$quote";
488
                        } else {
489
                            $query .= " ,$name=$quote" . $this->db->quote($val) . "$quote";
490
                        }
491
                    }
492
                    $first = false;
493
                    $queryInsert .= " ,$name";
494
                    $values .= " ,$quote" . $this->db->quote($val) . "$quote";
495
                }
496
            }
497
            if ($isUpdate) {
498
                $query .= " WHERE id_c='" . $this->bean->id . "'";
499
            }
500
501
            $queryInsert .= " ) VALUES $values )";
502
503
            if (!$first) {
504
                if (!$isUpdate) {
505
                    $this->db->query($queryInsert);
506
                } else {
507
                    $checkQuery = "SELECT id_c FROM {$this->bean->table_name}_cstm WHERE id_c = '{$this->bean->id}'";
508
                    if ($this->db->getOne($checkQuery)) {
509
                        $this->db->query($query);
510
                    } else {
511
                        $this->db->query($queryInsert);
512
                    }
513
                }
514
            }
515
        }
516
    }
517
518
    /**
519
     * Deletes the field from fields_meta_data and drops the database column then it rebuilds the cache
520
     * Use the widgets get_db_modify_alter_table() method to get the table sql - some widgets do not need any custom table modifications.
521
     *
522
     * @param TemplateField $widget
523
     */
524
    public function deleteField($widget)
525
    {
526
        require_once 'modules/DynamicFields/templates/Fields/TemplateField.php';
527
        global $beanList;
528
        if (!($widget instanceof TemplateField)) {
529
            $field_name = $widget;
530
            $widget = new TemplateField();
531
            $widget->name = $field_name;
532
        }
533
        $object_name = $beanList[$this->module];
534
535
        //Some modules like cases have a bean name that doesn't match the object name
536
        if (empty($GLOBALS['dictionary'][$object_name])) {
537
            $newName = BeanFactory::getObjectName($this->module);
538
            $object_name = $newName != false ? $newName : $object_name;
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...
539
        }
540
541
        $this->db->query("DELETE FROM fields_meta_data WHERE id='" . $this->module . $widget->name . "'");
542
        $sql = $widget->get_db_delete_alter_table($this->bean->table_name . '_cstm');
543
        if (!empty($sql)) {
544
            $this->db->query($sql);
545
        }
546
547
        $this->removeVardefExtension($widget);
548
        VardefManager::clearVardef();
549
        VardefManager::refreshVardefs($this->module, $object_name);
550
    }
551
552
    /*
553
     * Method required by the TemplateRelatedTextField->save() method
554
     * Taken from MBModule's implementation
555
     */
556
    /**
557
     * @param string $name
558
     * @param string $type
559
     * @return bool
560
     */
561
    public function fieldExists($name = '', $type = '')
562
    {
563
        // must get the vardefs from the GLOBAL array as $bean->field_defs does not contain the values from the cache at this point
564
        // TODO: fix this - saveToVardefs() updates GLOBAL['dictionary'] correctly, obtaining its information directly from the fields_meta_data table via buildCache()...
565
        $name = $this->getDBName($name);
566
        $vardefs = $GLOBALS['dictionary'][$this->bean->object_name]['fields'];
567
        if (!empty($vardefs)) {
568
            if (empty($type) && empty($name)) {
569
                return false;
570
            } elseif (empty($type)) {
571
                return !empty($vardefs[$name]);
572
            } elseif (empty($name)) {
573
                foreach ($vardefs as $def) {
574
                    if (!empty($def['type']) && $def['type'] == $type) {
575
                        return true;
576
                    }
577
                }
578
579
                return false;
580
            } else {
581
                return !empty($vardefs[$name]) && ($vardefs[$name]['type'] == $type);
582
            }
583
        } else {
584
            return false;
585
        }
586
    }
587
588
    /**
589
     * Adds a custom field using a field object.
590
     *
591
     * @param TemplateField $field
592
     *
593
     * @return bool
594
     */
595
    public function addFieldObject(&$field)
596
    {
597
        $GLOBALS['log']->debug('adding field');
598
        $object_name = $this->module;
599
        $db_name = $field->name;
600
601
        $fmd = new FieldsMetaData();
602
        $id = $fmd->retrieve($object_name . $db_name, true, false);
603
        $is_update = false;
604
        $label = strtoupper($field->label);
605
        if (!empty($id)) {
606
            $is_update = true;
607
        } else {
608
            $db_name = $this->getDBName($field->name);
609
            $field->name = $db_name;
610
        }
611
        $this->createCustomTable();
612
        $fmd->id = $object_name . $db_name;
613
        $fmd->custom_module = $object_name;
614
        $fmd->name = $db_name;
615
        $fmd->vname = $label;
616
        $fmd->type = $field->type;
617
        $fmd->help = $field->help;
618
        if (!empty($field->len)) {
619
            $fmd->len = $field->len;
620
        } // tyoung bug 15407 - was being set to $field->size so changes weren't being saved
621
        $fmd->required = ($field->required ? 1 : 0);
622
        $fmd->default_value = $field->default;
623
        $fmd->ext1 = $field->ext1;
624
        $fmd->ext2 = $field->ext2;
625
        $fmd->ext3 = $field->ext3;
626
        $fmd->ext4 = (isset($field->ext4) ? $field->ext4 : '');
627
        $fmd->comments = $field->comment;
628
        $fmd->massupdate = $field->massupdate;
629
        $fmd->importable = (isset($field->importable)) ? $field->importable : null;
0 ignored issues
show
Documentation Bug introduced by
It seems like isset($field->importable...ield->importable : null can also be of type string. However, the property $importable is declared as type boolean. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
630
        $fmd->duplicate_merge = $field->duplicate_merge;
631
        $fmd->audited = $field->audited;
632
        $fmd->inline_edit = $field->inline_edit;
633
        $fmd->reportable = ($field->reportable ? 1 : 0);
634
        if (!$is_update) {
635
            $fmd->new_with_id = true;
636
        }
637
        if ($field) {
638
            if (!$is_update) {
639
                //Do two SQL calls here in this case
640
                //The first is to create the column in the custom table without the default value
641
                //The second is to modify the column created in the custom table to set the default value
642
                //We do this so that the existing entries in the custom table don't have the default value set
643
                $field->default = '';
644
                $field->default_value = '';
645
                // resetting default and default_value does not work for multienum and causes trouble for MsSQL
646
                // so using a temporary variable here to indicate that we don't want default for this query
647
                $field->no_default = 1;
648
                $query = $field->get_db_add_alter_table($this->bean->table_name . '_cstm');
649
                // unset temporary member variable
650
                unset($field->no_default);
651
                if (!empty($query)) {
652
                    $this->db->query($query, true, 'Cannot create column');
653
                    $field->default = $fmd->default_value;
654
                    $field->default_value = $fmd->default_value;
655
                    $query = $field->get_db_modify_alter_table($this->bean->table_name . '_cstm');
656
                    if (!empty($query)) {
657
                        $this->db->query($query, true, 'Cannot set default');
658
                    }
659
                }
660
            } else {
661
                $query = $field->get_db_modify_alter_table($this->bean->table_name . '_cstm');
662
                if (!empty($query)) {
663
                    $this->db->query($query, true, 'Cannot modify field');
664
                }
665
            }
666
            $fmd->save();
667
            $this->buildCache($this->module);
668
            $this->saveExtendedAttributes($field, array_keys($fmd->field_defs));
669
        }
670
671
        return true;
672
    }
673
674
    /**
675
     * @param $field
676
     * @param $column_fields
677
     */
678
    public function saveExtendedAttributes($field, $column_fields)
679
    {
680
        require_once 'modules/ModuleBuilder/parsers/StandardField.php';
681
        require_once 'modules/DynamicFields/FieldCases.php';
682
        global $beanList;
683
684
        $to_save = array();
685
        $base_field = get_widget($field->type);
686
        foreach ($field->vardef_map as $property => $fmd_col) {
687
            //Skip over attributes that are either the default or part of the normal attributes stored in the DB
688
            if (!isset($field->$property) || in_array($fmd_col, $column_fields) || in_array($property, $column_fields)
689
                || $this->isDefaultValue($property, $field->$property, $base_field)
690
                || $property == 'action' || $property == 'label_value' || $property == 'label'
691
                || (substr($property, 0, 3) == 'ext' && strlen($property) == 4)
692
            ) {
693
                continue;
694
            }
695
            $to_save[$property] =
696
                is_string($field->$property) ? htmlspecialchars_decode($field->$property, ENT_QUOTES) : $field->$property;
697
        }
698
        $bean_name = $beanList[$this->module];
699
700
        $this->writeVardefExtension($bean_name, $field, $to_save);
701
    }
702
703
    /**
704
     * @param $property
705
     * @param $value
706
     * @param $baseField
707
     * @return bool
708
     */
709
    protected function isDefaultValue($property, $value, $baseField)
710
    {
711
        switch ($property) {
712
            case 'importable':
713
                return $value === 'true' || $value === '1' || $value === true || $value === 1;
714
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
715
            case 'required':
716
            case 'audited':
717
            case 'inline_edit':
718
            case 'massupdate':
719
                return $value === 'false' || $value === '0' || $value === false || $value === 0;
720
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
721
            case 'default_value':
722
            case 'default':
723
            case 'help':
724
            case 'comments':
725
                return $value == '';
726
            case 'duplicate_merge':
727
                return $value === 'false' || $value === '0' || $value === false || $value === 0 || $value === 'disabled';
728
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
729
        }
730
731
        if (isset($baseField->$property)) {
732
            return $baseField->$property == $value;
733
        }
734
735
        return false;
736
    }
737
738
    /**
739
     * @param $bean_name
740
     * @param $field
741
     * @param $def_override
742
     * @return bool
743
     */
744
    protected function writeVardefExtension($bean_name, $field, $def_override)
745
    {
746
        //Hack for the broken cases module
747
        $vBean = $bean_name == 'aCase' ? 'Case' : $bean_name;
748
        $file_loc = "$this->base_path/sugarfield_{$field->name}.php";
749
750
        $out = "<?php\n // created: " . date('Y-m-d H:i:s') . "\n";
751
        foreach ($def_override as $property => $val) {
752
            $out .= override_value_to_string_recursive(array($vBean, 'fields', $field->name, $property), 'dictionary', $val) . "\n";
753
        }
754
755
        $out .= "\n ?>";
756
757
        if (!file_exists($this->base_path)) {
758
            mkdir_recursive($this->base_path);
759
        }
760
761
        if ($fh = @sugar_fopen($file_loc, 'w')) {
762
            fputs($fh, $out);
763
            fclose($fh);
764
765
            return true;
766
        } else {
767
            return false;
768
        }
769
    }
770
771
    /**
772
     * @param $field
773
     */
774
    protected function removeVardefExtension($field)
775
    {
776
        $file_loc = "$this->base_path/sugarfield_{$field->name}.php";
777
778
        if (is_file($file_loc)) {
779
            unlink($file_loc);
780
        }
781
    }
782
783
    /**
784
     * DEPRECIATED: Use addFieldObject instead.
785
     * Adds a Custom Field using parameters.
786
     *
787
     * @param string $name
788
     * @param string $label
789
     * @param string $type
790
     * @param string $max_size
791
     * @param string $required_option
792
     * @param string $default_value
793
     * @param string $ext1
794
     * @param string $ext2
795
     * @param string $ext3
796
     * @param int $audited
797
     * @param int $inline_edit
798
     * @param int $mass_update
799
     * @param string $ext4
800
     * @param string $help
801
     * @param int $duplicate_merge
802
     * @param string $comment
803
     *
804
     * @return bool
805
     */
806
    public function addField($name, $label = '', $type = 'Text', $max_size = '255', $required_option = 'optional', $default_value = '', $ext1 = '', $ext2 = '', $ext3 = '', $audited = 0, $inline_edit = 1, $mass_update = 0, $ext4 = '', $help = '', $duplicate_merge = 0, $comment = '')
807
    {
808
        require_once 'modules/DynamicFields/templates/Fields/TemplateField.php';
809
        $field = new TemplateField();
810
        $field->label = $label;
811
        if (empty($field->label)) {
812
            $field->label = $name;
813
        }
814
        $field->name = $name;
815
        $field->type = $type;
816
        $field->len = $max_size;
817
        $field->required = (!empty($required_option) && $required_option != 'optional');
818
        $field->default = $default_value;
819
        $field->ext1 = $ext1;
820
        $field->ext2 = $ext2;
821
        $field->ext3 = $ext3;
822
        $field->ext4 = $ext4;
823
        $field->help = $help;
824
        $field->comments = $comment;
825
        $field->massupdate = $mass_update;
826
        $field->duplicate_merge = $duplicate_merge;
827
        $field->audited = $audited;
828
        $field->inline_edit = $inline_edit;
829
        $field->reportable = 1;
0 ignored issues
show
Documentation Bug introduced by
The property $reportable was declared of type boolean, but 1 is of type integer. Maybe add a type cast?

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

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

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
830
831
        return $this->addFieldObject($field);
832
    }
833
834
    /**
835
     * Creates the custom table with an id of id_c.
836
     *
837
     * @param bool $execute
838
     * @return string
839
     */
840
    public function createCustomTable($execute = true)
841
    {
842
        $out = '';
843
        if (!$this->db->tableExists($this->bean->table_name . '_cstm')) {
844
            $GLOBALS['log']->debug('creating custom table for ' . $this->bean->table_name);
845
            $idDef = array(
846
                'id_c' => array(
847
                    'name' => 'id_c',
848
                    'type' => 'id',
849
                    'required' => 1,
850
                ),
851
            );
852
            $idIdx = array(
853
                'id' => array(
854
                    'name' => $this->bean->table_name . '_cstm_pk',
855
                    'type' => 'primary',
856
                    'fields' => array('id_c'),
857
                ),
858
            );
859
860
            $query = $this->db->createTableSQLParams($this->bean->table_name . '_cstm', $idDef, $idIdx);
861
            if (!$this->db->supports('inline_keys')) {
862
                $indicesArr = $this->db->getConstraintSql($idIdx, $this->bean->table_name . '_cstm');
863
            } else {
864
                $indicesArr = array();
865
            }
866
            if ($execute) {
867
                $this->db->query($query);
868
                if (!empty($indicesArr)) {
869
                    foreach ($indicesArr as $idxq) {
870
                        $this->db->query($idxq);
871
                    }
872
                }
873
            }
874
            $out = $query . "\n";
875
            if (!empty($indicesArr)) {
876
                $out .= implode("\n", $indicesArr) . "\n";
877
            }
878
879
            $out .= $this->add_existing_custom_fields($execute);
880
        }
881
882
        return $out;
883
    }
884
885
886
    /**
887
     * Updates the db schema and adds any custom fields we have used if the custom table was dropped.
888
     *
889
     * @param bool $execute
890
     * @return string
891
     */
892
    public function add_existing_custom_fields($execute = true)
893
    {
894
        $out = '';
895
        if ($this->bean->hasCustomFields()) {
896
            foreach ($this->bean->field_defs as $name => $data) {
897
                if (empty($data['source']) || $data['source'] != 'custom_fields') {
898
                    continue;
899
                }
900
                $out .= $this->add_existing_custom_field($data, $execute);
901
            }
902
        }
903
904
        return $out;
905
    }
906
907
    /**
908
     * @param array $data
909
     * @param bool $execute
910
     * @return string
911
     */
912
    public function add_existing_custom_field($data, $execute = true)
913
    {
914
        $field = get_widget($data ['type']);
915
        $field->populateFromRow($data);
916
        $query = "/*MISSING IN DATABASE - {$data['name']} -  ROW*/\n"
917
            . $field->get_db_add_alter_table($this->bean->table_name . '_cstm');
918
        $out = $query . "\n";
919
        if ($execute) {
920
            $this->db->query($query);
921
        }
922
923
        return $out;
924
    }
925
926
    /**
927
     * @param bool $execute
928
     * @return string
929
     */
930
    public function repairCustomFields($execute = true)
931
    {
932
        $out = $this->createCustomTable($execute);
933
        //If the table didn't exist, createCustomTable will have returned all the SQL to create and populate it
934
        if (!empty($out)) {
935
            return "/*Checking Custom Fields for module : {$this->module} */\n$out";
936
        }
937
        //Otherwise make sure all the custom fields defined in the vardefs exist in the custom table.
938
        //We aren't checking for data types, just that the column exists.
939
        $db = $this->db;
940
        $tablename = $this->bean->table_name . '_cstm';
941
        $compareFieldDefs = $db->get_columns($tablename);
942
        foreach ($this->bean->field_defs as $name => $data) {
943
            if (empty($data['source']) || $data['source'] != 'custom_fields') {
944
                continue;
945
            }
946
            /*
947
             * @bug 43471
948
             * @issue 43471
949
             * @itr 23441
950
             *
951
             * force the name to be lower as it needs to be lower since that is how it's put into the key
952
             * in the get_columns() call above.
953
             */
954
            if (!empty($compareFieldDefs[strtolower($name)])) {
955
                continue;
956
            }
957
            $out .= $this->add_existing_custom_field($data, $execute);
958
        }
959
        if (!empty($out)) {
960
            $out = "/*Checking Custom Fields for module : {$this->module} */\n$out";
961
        }
962
963
        return $out;
964
    }
965
966
    /**
967
     * Adds a label to the module's mod_strings for the current language
968
     * Note that the system label name.
969
     *
970
     * @param string $displayLabel The label value to be displayed
971
     *
972
     * @return string The core of the system label name - returns currency_id5 for example, when the full label would then be LBL_CURRENCY_ID5
973
     *                TODO: Only the core is returned for historical reasons - switch to return the real system label
974
     */
975
    public function addLabel($displayLabel)
976
    {
977
        $mod_strings = return_module_language($GLOBALS['current_language'], $this->module);
978
        $limit = 10;
979
        $count = 0;
980
        $field_key = $this->getDBName($displayLabel, false);
981
        $systemLabel = $field_key;
982
        if (!$this->use_existing_labels) { // use_existing_labels defaults to false in this module; as of today, only set to true by ModuleInstaller.php
983
            while (isset($mod_strings [$systemLabel]) && $count <= $limit) {
984
                $systemLabel = $field_key . "_$count";
985
                ++$count;
986
            }
987
        }
988
        $selMod = (!empty($_REQUEST['view_module'])) ? $_REQUEST['view_module'] : $this->module;
989
        require_once 'modules/ModuleBuilder/parsers/parser.label.php';
990
        $parser = new ParserLabel($selMod, isset($_REQUEST ['view_package']) ? $_REQUEST ['view_package'] : null);
991
        $parser->handleSave(array('label_' . $systemLabel => $displayLabel), $GLOBALS ['current_language']);
992
993
        return $systemLabel;
994
    }
995
996
    /**
997
     * Returns a Database Safe Name.
998
     *
999
     * @param string $name
1000
     * @param bool $_C do we append _c to the name
1001
     *
1002
     * @return string
1003
     */
1004
    public function getDBName($name, $_C = true)
1005
    {
1006
        static $cached_results = array();
1007
        if (!empty($cached_results[$name])) {
1008
            return $cached_results[$name];
1009
        }
1010
        $exclusions = array('parent_type', 'parent_id', 'currency_id', 'parent_name');
1011
        // Remove any non-db friendly characters
1012
        $return_value = preg_replace("/[^\w]+/", '_', $name);
1013
        if ($_C == true && !in_array($return_value, $exclusions) && substr($return_value, -2) != '_c') {
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...
1014
            $return_value .= '_c';
1015
        }
1016
        $cached_results[$name] = $return_value;
1017
1018
        return $return_value;
1019
    }
1020
1021
    /**
1022
     * @param $where_clauses
1023
     */
1024
    public function setWhereClauses(&$where_clauses)
1025
    {
1026
        if (isset($this->avail_fields)) {
1027
            foreach ($this->avail_fields as $name => $value) {
1028
                if (!empty($_REQUEST[$name])) {
1029
                    $where_clauses[] = $this->bean->table_name . "_cstm.$name LIKE '" . $this->db->quote($_REQUEST[$name]) . "%'";
1030
                }
1031
            }
1032
        }
1033
    }
1034
1035
    /////////////////////////BACKWARDS COMPATIBILITY MODE FOR PRE 5.0 MODULES\\\\\\\\\\\\\\\\\\\\\\\\\\\
1036
    ////////////////////////////END BACKWARDS COMPATIBILITY MODE FOR PRE 5.0 MODULES\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
1037
1038
    /**
1039
     * DEPRECATED.
1040
     */
1041
    public function retrieve()
1042
    {
1043
        if (!isset($this->bean)) {
1044
            $GLOBALS['log']->fatal('DynamicField retrieve, bean not instantiated');
1045
1046
            return false;
1047
        }
1048
1049
        if (!$this->bean->hasCustomFields()) {
1050
            return false;
1051
        }
1052
1053
        $query = 'SELECT * FROM ' . $this->bean->table_name . "_cstm WHERE id_c='" . $this->bean->id . "'";
1054
        $result = $this->db->query($query);
1055
        $row = $this->db->fetchByAssoc($result);
1056
1057
        if ($row) {
1058
            foreach ($row as $name => $value) {
1059
                // originally in pre-r30895 we checked if this field was in avail_fields i.e., in fields_meta_data and not deleted
1060
                // with the removal of avail_fields post-r30895 we have simplified this - we now retrieve every custom field even if previously deleted
1061
                // this is considered harmless as the value although set in the bean will not otherwise be used (nothing else works off the list of fields in the bean)
1062
                $this->bean->$name = $value;
1063
            }
1064
        }
1065
        return true;
1066
    }
1067
1068
    /**
1069
     * @param XTemplate $xtpl
1070
     * @param string $view
1071
     */
1072
    public function populateXTPL($xtpl, $view)
1073
    {
1074
        if ($this->bean->hasCustomFields()) {
1075
            $results = $this->getAllFieldsView($view, 'xtpl');
1076
            foreach ($results as $name => $value) {
1077
                if (is_array($value['xtpl'])) {
1078
                    foreach ($value['xtpl'] as $xName => $xValue) {
1079
                        $xtpl->assign(strtoupper($xName), $xValue);
1080
                    }
1081
                } else {
1082
                    $xtpl->assign(strtoupper($name), $value['xtpl']);
1083
                }
1084
            }
1085
        }
1086
    }
1087
1088
1089
    /**
1090
     * @param XTemplate $xtpl
1091
     * @param string $view
1092
     */
1093
    public function populateAllXTPL($xtpl, $view)
1094
    {
1095
        $this->populateXTPL($xtpl, $view);
1096
    }
1097
1098
    /**
1099
     * @param $view
1100
     * @param $type
1101
     * @return array
1102
     */
1103
    public function getAllFieldsView($view, $type)
1104
    {
1105
        require_once 'modules/DynamicFields/FieldCases.php';
1106
        $results = array();
1107
        foreach ($this->bean->field_defs as $name => $data) {
1108
            if (empty($data['source']) || $data['source'] != 'custom_fields') {
1109
                continue;
1110
            }
1111
            $field = get_widget($data ['type']);
1112
            $field->populateFromRow($data);
1113
            $field->view = $view;
1114
            $field->bean = $this->bean;
1115
            switch (strtolower($type)) {
1116
                case 'xtpl':
1117
                    $results[$name] = array('xtpl' => $field->get_xtpl());
1118
                    break;
1119
                case 'html':
1120
                    $results[$name] = array('html' => $field->get_html(), 'label' => $field->get_html_label(), 'fieldType' => $field->data_type, 'isCustom' => true);
1121
                    break;
1122
            }
1123
        }
1124
1125
        return $results;
1126
    }
1127
1128
    ////////////////////////////END BACKWARDS COMPATIBILITY MODE FOR PRE 5.0 MODULES\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
1129
}
1130