]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - modules/DynamicFields/DynamicField.php
Release 6.5.16
[Github/sugarcrm.git] / modules / DynamicFields / DynamicField.php
1 <?php
2 if (! defined ( 'sugarEntry' ) || ! sugarEntry)
3     die ( 'Not A Valid Entry Point' ) ;
4
5 /*********************************************************************************
6  * SugarCRM Community Edition is a customer relationship management program developed by
7  * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
8  * 
9  * This program is free software; you can redistribute it and/or modify it under
10  * the terms of the GNU Affero General Public License version 3 as published by the
11  * Free Software Foundation with the addition of the following permission added
12  * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
13  * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
14  * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
15  * 
16  * This program is distributed in the hope that it will be useful, but WITHOUT
17  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18  * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
19  * details.
20  * 
21  * You should have received a copy of the GNU Affero General Public License along with
22  * this program; if not, see http://www.gnu.org/licenses or write to the Free
23  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
24  * 02110-1301 USA.
25  * 
26  * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
27  * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
28  * 
29  * The interactive user interfaces in modified source and object code versions
30  * of this program must display Appropriate Legal Notices, as required under
31  * Section 5 of the GNU Affero General Public License version 3.
32  * 
33  * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
34  * these Appropriate Legal Notices must retain the display of the "Powered by
35  * SugarCRM" logo. If the display of the logo is not reasonably feasible for
36  * technical reasons, the Appropriate Legal Notices must display the words
37  * "Powered by SugarCRM".
38  ********************************************************************************/
39
40
41 class DynamicField {
42
43     var $use_existing_labels = false; // this value is set to true by install_custom_fields() in ModuleInstaller.php; everything else expects it to be false
44     var $base_path = "";
45
46     function DynamicField($module = '') {
47         $this->module = (! empty ( $module )) ? $module :( (isset($_REQUEST['module']) && ! empty($_REQUEST['module'])) ? $_REQUEST ['module'] : '');
48         $this->base_path = "custom/Extension/modules/{$this->module}/Ext/Vardefs";
49     }
50
51    function getModuleName()
52     {
53         return $this->module ;
54     }
55
56     /*
57      * As DynamicField has a counterpart in MBModule, provide the MBModule function getPackagename() here also
58      */
59     function getPackageName()
60     {
61         return null ;
62     }
63
64     function deleteCache(){
65     }
66
67
68     /**
69     * 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
70     * LOADS THE BEAN IF THE BEAN IS NOT BEING PASSED ALONG IN SETUP IT SHOULD BE SET PRIOR TO SETUP
71     *
72     * @param SUGARBEAN $bean
73     */
74     function setup($bean = null) {
75         if ($bean) {
76             $this->bean = $bean;
77         }
78         if (isset ( $this->bean->module_dir )) {
79             $this->module = $this->bean->module_dir;
80         }
81         if(!isset($GLOBALS['dictionary'][$this->bean->object_name]['custom_fields'])){
82             $this->buildCache ( $this->module );
83         }
84     }
85
86     function setLabel( $language='en_us' , $key , $value )
87     {
88         $params [ "label_" . $key ] = $value;
89         require_once 'modules/ModuleBuilder/parsers/parser.label.php' ;
90         $parser = new ParserLabel ( $this->module ) ;
91         $parser->handleSave( $params , $language);
92     }
93
94     /**
95     * Builds the cache for custom fields based on the vardefs
96     *
97     * @param STRING $module
98     * @param boolean saveCache Boolean value indicating whether or not to pass saveCache value to saveToVardef, defaults to true
99     * @return unknown
100     */
101     function buildCache($module = false, $saveCache=true) {
102         //We can't build the cache while installing as the required database tables may not exist.
103         if (!empty($GLOBALS['installing']) && $GLOBALS['installing'] == true|| empty($GLOBALS['db']))
104             return false;
105         if($module == '../data')return false;
106
107         static $results = array ( ) ;
108
109         $where = '';
110         if (! empty ( $module )) {
111             $where .= " custom_module='$module' AND ";
112             unset( $results[ $module ] ) ; // clear out any old results for the module as $results is declared static
113         }
114         else
115         {
116             $results = array ( ) ; // clear out results - if we remove a module we don't want to have its old vardefs hanging around
117         }
118
119         $GLOBALS['log']->debug('rebuilding cache for ' . $module);
120         $query = "SELECT * FROM fields_meta_data WHERE $where deleted = 0";
121
122         $result = $GLOBALS['db']->query ( $query );
123         require_once ('modules/DynamicFields/FieldCases.php');
124
125         // retrieve the field definition from the fields_meta_data table
126         // using 'encode'=false to fetchByAssoc to prevent any pre-formatting of the base metadata
127         // for immediate use in HTML. This metadata will be further massaged by get_field_def() and so should not be pre-formatted
128         while ( $row = $GLOBALS['db']->fetchByAssoc ( $result, false ) ) {
129             $field = get_widget ( $row ['type'] );
130
131             foreach ( $row as $key => $value ) {
132                 $field->$key = $value;
133             }
134             $field->default = $field->default_value;
135             $vardef = $field->get_field_def ();
136             $vardef ['id'] = $row ['id'];
137             $vardef ['custom_module'] = $row ['custom_module'];
138             if (empty ( $vardef ['source'] ))
139                 $vardef ['source'] = 'custom_fields';
140             if (empty ( $results [$row ['custom_module']] ))
141                 $results [$row ['custom_module']] = array ( );
142             $results [$row ['custom_module']] [$row ['name']] = $vardef;
143         }
144         if (empty ( $module )) {
145             foreach ( $results as $module => $result ) {
146                 $this->saveToVardef ( $module, $result, $saveCache);
147             }
148         } else {
149             if (! empty ( $results [$module] )) {
150                 $this->saveToVardef ( $module, $results [$module], $saveCache);
151             }else{
152                 $this->saveToVardef ( $module, false, $saveCache);
153             }
154         }
155
156         return true;
157
158     }
159
160     /**
161     * Returns the widget for a custom field from the fields_meta_data table.
162     */
163     function getFieldWidget($module, $fieldName) {
164         if (empty($module) || empty($fieldName)){
165             sugar_die("Unable to load widget for '$module' : '$fieldName'");
166         }
167         $query = "SELECT * FROM fields_meta_data WHERE custom_module='$module' AND name='$fieldName' AND deleted = 0";
168         $result = $GLOBALS['db']->query ( $query );
169         require_once ('modules/DynamicFields/FieldCases.php');
170         if ( $row = $GLOBALS['db']->fetchByAssoc ( $result ) ) {
171             $field = get_widget ( $row ['type'] );
172             $field->populateFromRow($row);
173             return $field;
174         }
175     }
176
177
178     /**
179     * Updates the cached vardefs with the custom field information stored in result
180     *
181     * @param string $module
182     * @param array $result
183     * @param boolean saveCache Boolean value indicating whether or not to call VardefManager::saveCache, defaults to true
184     */
185     function saveToVardef($module,$result,$saveCache=true) {
186
187
188         global $beanList;
189         if (! empty ( $beanList [$module] )) {
190             $object = BeanFactory::getObjectName($module);
191
192             if(empty($GLOBALS['dictionary'][$object]['fields'])){
193                 //if the vardef isn't loaded let's try loading it.
194                 VardefManager::refreshVardefs($module,$object, null, false);
195                 //if it's still not loaded we really don't have anything useful to cache
196                 if(empty($GLOBALS['dictionary'][$object]['fields']))return;
197             }
198             if (!isset($GLOBALS['dictionary'][$object]['custom_fields'])) {
199                 $GLOBALS['dictionary'][$object]['custom_fields'] = false;
200             }
201             if (! empty ( $GLOBALS ['dictionary'] [$object] )) {
202                 if (! empty ( $result )) {
203                     // First loop to add
204
205                 foreach ( $result as $field ) {
206                     foreach($field as $k=>$v){
207                         //allows values for custom fields to be defined outside of the scope of studio
208                         if(!isset($GLOBALS ['dictionary'] [$object] ['fields'] [$field ['name']][$k])){
209                             $GLOBALS ['dictionary'] [$object] ['fields'] [$field ['name']][$k] = $v;
210                         }
211                     }
212                 }
213
214                     // Second loop to remove
215                     foreach ( $GLOBALS ['dictionary'] [$object] ['fields'] as $name => $fieldDef ) {
216
217                         if (isset ( $fieldDef ['custom_module'] )) {
218                             if (! isset ( $result [$name] )) {
219                                 unset ( $GLOBALS ['dictionary'] [$object] ['fields'] [$name] );
220                             } else {
221                                 $GLOBALS ['dictionary'] [$object] ['custom_fields'] = true;
222                             }
223                         }
224
225                     } //if
226                 }
227             }
228
229             $manager = new VardefManager();
230             if($saveCache)
231             {
232                 $manager->saveCache ($this->module, $object);
233             }
234
235             // Everything works off of vardefs, so let's have it save the users vardefs
236             // to the employees module, because they both use the same table behind
237             // the scenes
238             if ($module == 'Users')
239             {
240                 $manager->loadVardef('Employees', 'Employee', true);
241                 return;
242             }
243
244         }
245     }
246
247     /**
248     * returns either false or an array containing the select and join parameter for a query using custom fields
249     * @param $expandedList boolean      If true, return a list of all fields with source=custom_fields in the select instead of the standard _cstm.*
250     *     This is required for any downstream construction of a SQL statement where we need to manipulate the select list,
251     *     for example, listviews with custom relate fields where the value comes from join rather than from the custom table
252     *
253     * @return array select=>select columns, join=>prebuilt join statement
254     */
255   function getJOIN( $expandedList = false , $includeRelates = false, &$where = false){
256         if(!$this->bean->hasCustomFields()){
257             return array(
258                 'select' => '',
259                 'join' => ''
260             );
261         }
262
263         if (empty($expandedList) )
264         {
265             $select = ",{$this->bean->table_name}_cstm.*" ;
266         }
267         else
268         {
269             $select = '';
270             $isList = is_array($expandedList);
271             foreach($this->bean->field_defs as $name=>$field)
272             {
273                 if (!empty($field['source']) && $field['source'] == 'custom_fields' && (!$isList || !empty($expandedList[$name]))){
274                     // assumption: that the column name in _cstm is the same as the field name. Currently true.
275                     // 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)
276                     if ( $field['type'] != 'html' && $name != 'parent_name')
277                         $select .= ",{$this->bean->table_name}_cstm.{$name}" ;
278                 }
279             }
280         }
281         $join = " LEFT JOIN " .$this->bean->table_name. "_cstm ON " .$this->bean->table_name. ".id = ". $this->bean->table_name. "_cstm.id_c ";
282
283         if ($includeRelates) {
284             $jtAlias = "relJoin";
285             $jtCount = 1;
286             foreach($this->bean->field_defs as $name=>$field)
287             {
288                 if ($field['type'] == 'relate' && isset($field['custom_module'])) {
289                     $relateJoinInfo = $this->getRelateJoin($field, $jtAlias.$jtCount);
290                     $select .= $relateJoinInfo['select'];
291                     $join .= $relateJoinInfo['from'];
292                     //bug 27654 martin
293                     if ($where)
294                     {
295                         $pattern = '/'.$field['name'].'\slike/i';
296                         $replacement = $relateJoinInfo['name_field'].' like';
297                         $where = preg_replace($pattern,$replacement,$where);
298                     }
299                     $jtCount++;
300                 }
301             }
302         }
303
304         return array('select'=>$select, 'join'=>$join);
305
306     }
307
308    function getRelateJoin($field_def, $joinTableAlias, $withIdName = true) {
309         if (empty($field_def['type']) || $field_def['type'] != "relate") {
310             return false;
311         }
312         global $beanFiles, $beanList, $module;
313         $rel_module = $field_def['module'];
314         if(empty($beanFiles[$beanList[$rel_module]])) {
315             return false;
316         }
317
318         require_once($beanFiles[$beanList[$rel_module]]);
319         $rel_mod = new $beanList[$rel_module]();
320         $rel_table = $rel_mod->table_name;
321         if (isset($rel_mod->field_defs['name']))
322         {
323             $name_field_def = $rel_mod->field_defs['name'];
324             if(isset($name_field_def['db_concat_fields']))
325             {
326                 $name_field = db_concat($joinTableAlias, $name_field_def['db_concat_fields']);
327             }
328             //If the name field is non-db, we need to find another field to display
329             else if(!empty($rel_mod->field_defs['name']['source']) && $rel_mod->field_defs['name']['source'] == "non-db" && !empty($field_def['rname']))
330             {
331                 $name_field = "$joinTableAlias." . $field_def['rname'];
332             }
333             else
334             {
335                 $name_field = "$joinTableAlias.name";
336             }
337         }
338         $tableName = isset($field_def['custom_module']) ? "{$this->bean->table_name}_cstm" : $this->bean->table_name ;
339         $relID = $field_def['id_name'];
340         $ret_array['rel_table'] = $rel_table;
341         $ret_array['name_field'] = $name_field;
342         $ret_array['select'] = ($withIdName ? ", {$tableName}.{$relID}" : "") . ", {$name_field} {$field_def['name']} ";
343         $ret_array['from'] = " LEFT JOIN $rel_table $joinTableAlias ON $tableName.$relID = $joinTableAlias.id"
344                             . " AND $joinTableAlias.deleted=0 ";
345         return $ret_array;
346    }
347
348    /**
349     * Fills in all the custom fields of type relate relationships for an object
350     *
351     */
352    function fill_relationships(){
353         global $beanList, $beanFiles;
354         if(!empty($this->bean->relDepth)) {
355             if($this->bean->relDepth > 1)return;
356         }else{
357             $this->bean->relDepth = 0;
358         }
359         foreach($this->bean->field_defs as $field){
360             if(empty($field['source']) || $field['source'] != 'custom_fields')continue;
361             if($field['type'] == 'relate'){
362                 $related_module =$field['ext2'];
363                 $name = $field['name'];
364                 if (empty($this->bean->$name)) { //Don't load the relationship twice
365                     $id_name = $field['id_name'];
366                     if(isset($beanList[ $related_module])){
367                         $class = $beanList[$related_module];
368
369                         if(file_exists($beanFiles[$class]) && isset($this->bean->$name)){
370                             require_once($beanFiles[$class]);
371                             $mod = new $class();
372                             $mod->relDepth = $this->bean->relDepth + 1;
373                             $mod->retrieve($this->bean->$id_name);
374                             $this->bean->$name = $mod->name;
375                         }
376                     }
377                 }
378             }
379         }
380     }
381
382     /**
383      * Process the save action for sugar bean custom fields
384      *
385      * @param boolean $isUpdate
386      */
387      function save($isUpdate){
388
389         if($this->bean->hasCustomFields() && isset($this->bean->id)){
390
391             if($isUpdate){
392                 $query = "UPDATE ". $this->bean->table_name. "_cstm SET ";
393             }
394             $queryInsert = "INSERT INTO ". $this->bean->table_name. "_cstm (id_c";
395             $values = "('".$this->bean->id."'";
396             $first = true;
397             foreach($this->bean->field_defs as $name=>$field){
398
399                 if(empty($field['source']) || $field['source'] != 'custom_fields')continue;
400                 if($field['type'] == 'html' || $field['type'] == 'parent')continue;
401                 if(isset($this->bean->$name)){
402                     $quote = "'";
403
404                     if(in_array($field['type'], array('int', 'float', 'double', 'uint', 'ulong', 'long', 'short', 'tinyint', 'currency', 'decimal'))) {
405                         $quote = '';
406                         if(!isset($this->bean->$name) || !is_numeric($this->bean->$name) ){
407                             if($field['required']){
408                                 $this->bean->$name = 0;
409                             }else{
410                                 $this->bean->$name = 'NULL';
411                             }
412                         }
413                     }
414                     if ( $field['type'] == 'bool' ) {
415                         if ( $this->bean->$name === FALSE )
416                             $this->bean->$name = '0';
417                         elseif ( $this->bean->$name === TRUE )
418                             $this->bean->$name = '1';
419                     }
420
421                     $val = $this->bean->$name;
422                                         if(($field['type'] == 'date' || $field['type'] == 'datetimecombo') && (empty($this->bean->$name )|| $this->bean->$name == '1900-01-01')){
423                         $quote = '';
424                         $val = 'NULL';
425                         $this->bean->$name = ''; // do not set it to string 'NULL'
426                     }
427                     if($isUpdate){
428                         if($first){
429                             $query .= " $name=$quote".$GLOBALS['db']->quote($val)."$quote";
430
431                         }else{
432                             $query .= " ,$name=$quote".$GLOBALS['db']->quote($val)."$quote";
433                         }
434                     }
435                     $first = false;
436                     $queryInsert .= " ,$name";
437                     $values .= " ,$quote". $GLOBALS['db']->quote($val). "$quote";
438                 }
439             }
440             if($isUpdate){
441                 $query.= " WHERE id_c='" . $this->bean->id ."'";
442
443             }
444
445             $queryInsert .= " ) VALUES $values )";
446
447             if(!$first){
448                 if(!$isUpdate){
449                     $GLOBALS['db']->query($queryInsert);
450                 }else{
451                     $checkquery = "SELECT id_c FROM {$this->bean->table_name}_cstm WHERE id_c = '{$this->bean->id}'";
452                     if ( $GLOBALS['db']->getOne($checkquery) ) {
453                         $result = $GLOBALS['db']->query($query);
454                     } else {
455                         $GLOBALS['db']->query($queryInsert);
456                     }
457                 }
458             }
459         }
460
461     }
462     /**
463      * Deletes the field from fields_meta_data and drops the database column then it rebuilds the cache
464      * Use the widgets get_db_modify_alter_table() method to get the table sql - some widgets do not need any custom table modifications
465      * @param STRING $name - field name
466      */
467     function deleteField($widget){
468         require_once('modules/DynamicFields/templates/Fields/TemplateField.php');
469         global $beanList;
470         if (!($widget instanceof TemplateField)) {
471             $field_name = $widget;
472             $widget = new TemplateField();
473             $widget->name = $field_name;
474         }
475         $object_name = $beanList[$this->module];
476
477         //Some modules like cases have a bean name that doesn't match the object name
478         if (empty($GLOBALS['dictionary'][$object_name])) {
479             $newName = BeanFactory::getObjectName($this->module);
480             $object_name = $newName != false ? $newName : $object_name;
481         }
482
483         $GLOBALS['db']->query("DELETE FROM fields_meta_data WHERE id='" . $this->module . $widget->name . "'");
484         $sql = $widget->get_db_delete_alter_table( $this->bean->table_name . "_cstm" ) ;
485         if (! empty( $sql ) )
486             $GLOBALS['db']->query( $sql );
487
488         $this->removeVardefExtension($widget);
489         VardefManager::clearVardef();
490         VardefManager::refreshVardefs($this->module, $object_name);
491
492     }
493
494     /*
495      * Method required by the TemplateRelatedTextField->save() method
496      * Taken from MBModule's implementation
497      */
498     function fieldExists($name = '', $type = ''){
499         // must get the vardefs from the GLOBAL array as $bean->field_defs does not contain the values from the cache at this point
500         // TODO: fix this - saveToVardefs() updates GLOBAL['dictionary'] correctly, obtaining its information directly from the fields_meta_data table via buildCache()...
501         $name = $this->getDBName($name);
502         $vardefs = $GLOBALS['dictionary'][$this->bean->object_name]['fields'];
503         if(!empty($vardefs)){
504             if(empty($type) && empty($name))
505                 return false;
506             else if(empty($type))
507                 return !empty($vardefs[$name]);
508             else if(empty($name)){
509                 foreach($vardefs as $def){
510                     if(!empty($def['type']) && $def['type'] == $type)
511                         return true;
512                 }
513                 return false;
514             }else
515                 return (!empty($vardefs[$name]) && ($vardefs[$name]['type'] == $type));
516         }else{
517             return false;
518         }
519     }
520
521
522     /**
523      * Adds a custom field using a field object
524      *
525      * @param Field Object $field
526      * @return boolean
527      */
528     function addFieldObject(&$field){
529         $GLOBALS['log']->debug('adding field');
530         $object_name = $this->module;
531         $db_name = $field->name;
532
533         $fmd = new FieldsMetaData();
534         $id =  $fmd->retrieve($object_name.$db_name,true, false);
535         $is_update = false;
536         $label = strtoupper( $field->label );
537         if(!empty($id)){
538             $is_update = true;
539         }else{
540             $db_name = $this->getDBName($field->name);
541             $field->name = $db_name;
542         }
543         $this->createCustomTable();
544         $fmd->id = $object_name.$db_name;
545         $fmd->custom_module= $object_name;
546         $fmd->name = $db_name;
547         $fmd->vname = $label;
548         $fmd->type = $field->type;
549         $fmd->help = $field->help;
550         if (!empty($field->len))
551             $fmd->len = $field->len; // tyoung bug 15407 - was being set to $field->size so changes weren't being saved
552         $fmd->required = ($field->required ? 1 : 0);
553         $fmd->default_value = $field->default;
554         $fmd->ext1 = $field->ext1;
555         $fmd->ext2 = $field->ext2;
556         $fmd->ext3 = $field->ext3;
557         $fmd->ext4 = (isset($field->ext4) ? $field->ext4 : '');
558         $fmd->comments = $field->comment;
559         $fmd->massupdate = $field->massupdate;
560         $fmd->importable = ( isset ( $field->importable ) ) ? $field->importable : null ;
561         $fmd->duplicate_merge = $field->duplicate_merge;
562         $fmd->audited =$field->audited;
563         $fmd->reportable = ($field->reportable ? 1 : 0);
564         if(!$is_update){
565             $fmd->new_with_id=true;
566         }
567         if($field){
568             if(!$is_update){
569                 //Do two SQL calls here in this case
570                 //The first is to create the column in the custom table without the default value
571                 //The second is to modify the column created in the custom table to set the default value
572                 //We do this so that the existing entries in the custom table don't have the default value set
573                 $field->default = '';
574                 $field->default_value = '';
575                 // resetting default and default_value does not work for multienum and causes trouble for mssql
576                 // so using a temporary variable here to indicate that we don't want default for this query
577                 $field->no_default = 1;
578                 $query = $field->get_db_add_alter_table($this->bean->table_name . '_cstm');
579                 // unsetting temporary member variable
580                 unset($field->no_default);
581                 if(!empty($query)){
582                         $GLOBALS['db']->query($query, true, "Cannot create column");
583                         $field->default = $fmd->default_value;
584                         $field->default_value = $fmd->default_value;
585                         $query = $field->get_db_modify_alter_table($this->bean->table_name . '_cstm');
586                         if(!empty($query)){
587                                 $GLOBALS['db']->query($query, true, "Cannot set default");
588                         }
589                 }
590             }else{
591                 $query = $field->get_db_modify_alter_table($this->bean->table_name . '_cstm');
592                 if(!empty($query)){
593                         $GLOBALS['db']->query($query, true, "Cannot modify field");
594                 }
595             }
596             $fmd->save();
597             $this->buildCache($this->module);
598             $this->saveExtendedAttributes($field, array_keys($fmd->field_defs));
599         }
600
601         return true;
602     }
603
604     function saveExtendedAttributes($field, $column_fields)
605     {
606             require_once ('modules/ModuleBuilder/parsers/StandardField.php') ;
607             require_once ('modules/DynamicFields/FieldCases.php') ;
608             global $beanList;
609
610             $to_save = array();
611             $base_field = get_widget ( $field->type) ;
612         foreach ($field->vardef_map as $property => $fmd_col){
613             //Skip over attribes that are either the default or part of the normal attributes stored in the DB
614             if (!isset($field->$property) || in_array($fmd_col, $column_fields) || in_array($property, $column_fields)
615                 || $this->isDefaultValue($property, $field->$property, $base_field)
616                 || $property == "action" || $property == "label_value" || $property == "label"
617                 || (substr($property, 0,3) == 'ext' && strlen($property) == 4))
618             {
619                 continue;
620             }
621             $to_save[$property] =
622                 is_string($field->$property) ? htmlspecialchars_decode($field->$property, ENT_QUOTES) : $field->$property;
623         }
624         $bean_name = $beanList[$this->module];
625
626         $this->writeVardefExtension($bean_name, $field, $to_save);
627     }
628
629     protected function isDefaultValue($property, $value, $baseField)
630     {
631         switch ($property) {
632             case "importable":
633                 return ( $value === 'true' || $value === '1' || $value === true || $value === 1 ); break;
634             case "required":
635             case "audited":
636             case "massupdate":
637                 return ( $value === 'false' || $value === '0' || $value === false || $value === 0); break;
638             case "default_value":
639             case "default":
640             case "help":
641             case "comments":
642                 return ($value == "");
643             case "duplicate_merge":
644                 return ( $value === 'false' || $value === '0' || $value === false || $value === 0 || $value === "disabled"); break;
645         }
646
647         if (isset($baseField->$property))
648         {
649             return $baseField->$property == $value;
650         }
651
652         return false;
653     }
654
655     protected function writeVardefExtension($bean_name, $field, $def_override)
656     {
657         //Hack for the broken cases module
658         $vBean = $bean_name == "aCase" ? "Case" : $bean_name;
659         $file_loc = "$this->base_path/sugarfield_{$field->name}.php";
660
661         $out =  "<?php\n // created: " . date('Y-m-d H:i:s') . "\n";
662         foreach ($def_override as $property => $val)
663         {
664             $out .= override_value_to_string_recursive(array($vBean, "fields", $field->name, $property), "dictionary", $val) . "\n";
665         }
666
667         $out .= "\n ?>";
668
669         if (!file_exists($this->base_path))
670             mkdir_recursive($this->base_path);
671
672         if( $fh = @sugar_fopen( $file_loc, 'w' ) )
673         {
674             fputs( $fh, $out);
675             fclose( $fh );
676             return true ;
677         }
678         else
679         {
680             return false ;
681         }
682     }
683
684     protected function removeVardefExtension($field)
685     {
686         $file_loc = "$this->base_path/sugarfield_{$field->name}.php";
687
688         if (is_file($file_loc))
689         {
690             unlink($file_loc);
691         }
692     }
693
694
695     /**
696      * DEPRECIATED: Use addFieldObject instead.
697      * Adds a Custom Field using parameters
698      *
699      * @param unknown_type $name
700      * @param unknown_type $label
701      * @param unknown_type $type
702      * @param unknown_type $max_size
703      * @param unknown_type $required_option
704      * @param unknown_type $default_value
705      * @param unknown_type $ext1
706      * @param unknown_type $ext2
707      * @param unknown_type $ext3
708      * @param unknown_type $audited
709      * @param unknown_type $mass_update
710      * @param unknown_type $ext4
711      * @param unknown_type $help
712      * @param unknown_type $duplicate_merge
713      * @param unknown_type $comment
714      * @return boolean
715      */
716     function addField($name,$label='', $type='Text',$max_size='255',$required_option='optional', $default_value='', $ext1='', $ext2='', $ext3='',$audited=0, $mass_update = 0 , $ext4='', $help='',$duplicate_merge=0, $comment=''){
717         require_once('modules/DynamicFields/templates/Fields/TemplateField.php');
718         $field = new TemplateField();
719         $field->label = $label;
720         if(empty($field->label)){
721             $field->label = $name;
722         }
723         $field->name = $name;
724         $field->type = $type;
725         $field->len = $max_size;
726         $field->required = (!empty($required_option) && $required_option != 'optional');
727         $field->default = $default_value;
728         $field->ext1 = $ext1;
729         $field->ext2 = $ext2;
730         $field->ext3 = $ext3;
731         $field->ext4 = $ext4;
732         $field->help = $help;
733         $field->comments = $comment;
734         $field->massupdate = $mass_update;
735         $field->duplicate_merge = $duplicate_merge;
736         $field->audited = $audited;
737         $field->reportable = 1;
738         return $this->addFieldObject($field);
739     }
740
741     /**
742      * Creates the custom table with an id of id_c
743      *
744      */
745     function createCustomTable($execute = true){
746         $out = "";
747         if (!$GLOBALS['db']->tableExists($this->bean->table_name."_cstm")) {
748             $GLOBALS['log']->debug('creating custom table for '. $this->bean->table_name);
749             $iddef = array(
750                 "id_c" => array(
751                     "name" => "id_c",
752                     "type" => "id",
753                     "required" => 1,
754                 )
755             );
756             $ididx = array(
757                         'id'=>array(
758                                 'name' =>$this->bean->table_name."_cstm_pk",
759                                 'type' =>'primary',
760                                 'fields'=>array('id_c')
761                 ),
762            );
763
764             $query = $GLOBALS['db']->createTableSQLParams($this->bean->table_name."_cstm", $iddef, $ididx);
765             if(!$GLOBALS['db']->supports("inline_keys")) {
766                 $indicesArr = $GLOBALS['db']->getConstraintSql($ididx, $this->bean->table_name."_cstm");
767             } else {
768                 $indicesArr = array();
769             }
770             if($execute) {
771                 $GLOBALS['db']->query($query);
772                 if(!empty($indicesArr)) {
773                     foreach($indicesArr as $idxq) {
774                         $GLOBALS['db']->query($idxq);
775                     }
776                 }
777             }
778             $out = $query . "\n";
779             if(!empty($indicesArr)) {
780                 $out .= join("\n", $indicesArr)."\n";
781             }
782
783             $out .= $this->add_existing_custom_fields($execute);
784         }
785
786         return $out;
787     }
788
789     /**
790      * Updates the db schema and adds any custom fields we have used if the custom table was dropped
791      *
792      */
793     function add_existing_custom_fields($execute = true){
794         $out = "";
795         if($this->bean->hasCustomFields()){
796                 foreach($this->bean->field_defs as $name=>$data){
797                         if(empty($data['source']) || $data['source'] != 'custom_fields')
798                     continue;
799                     $out .= $this->add_existing_custom_field($data, $execute);
800                 }
801         }
802         return $out;
803     }
804
805     function add_existing_custom_field($data, $execute = true)
806     {
807
808         $field = get_widget ( $data ['type'] );
809         $field->populateFromRow($data);
810         $query = "/*MISSING IN DATABASE - {$data['name']} -  ROW*/\n"
811                 . $field->get_db_add_alter_table($this->bean->table_name . '_cstm');
812         $out = $query . "\n";
813         if ($execute)
814             $GLOBALS['db']->query($query);
815
816         return $out;
817     }
818
819     public function repairCustomFields($execute = true)
820     {
821         $out = $this->createCustomTable($execute);
822         //If the table didn't exist, createCustomTable will have returned all the SQL to create and populate it
823         if (!empty($out))
824             return "/*Checking Custom Fields for module : {$this->module} */\n$out";
825         //Otherwise make sure all the custom fields defined in the vardefs exist in the custom table.
826         //We aren't checking for data types, just that the column exists.
827         $db = $GLOBALS['db'];
828         $tablename = $this->bean->table_name."_cstm";
829         $compareFieldDefs = $db->get_columns($tablename);
830         foreach($this->bean->field_defs as $name=>$data){
831             if(empty($data['source']) || $data['source'] != 'custom_fields')
832                 continue;
833             /**
834              * @bug 43471
835              * @issue 43471
836              * @itr 23441
837              *
838              * force the name to be lower as it needs to be lower since that is how it's put into the key
839              * in the get_columns() call above.
840              */
841             if(!empty($compareFieldDefs[strtolower($name)])) {
842                 continue;
843             }
844             $out .= $this->add_existing_custom_field($data, $execute);
845         }
846         if (!empty($out))
847             $out = "/*Checking Custom Fields for module : {$this->module} */\n$out";
848
849         return $out;
850     }
851
852     /**
853      * Adds a label to the module's mod_strings for the current language
854      * Note that the system label name
855      *
856      * @param string $displayLabel The label value to be displayed
857      * @return string The core of the system label name - returns currency_id5 for example, when the full label would then be LBL_CURRENCY_ID5
858      * TODO: Only the core is returned for historical reasons - switch to return the real system label
859      */
860     function addLabel ( $displayLabel )
861     {
862         $mod_strings = return_module_language($GLOBALS[ 'current_language' ], $this->module);
863         $limit = 10;
864         $count = 0;
865         $field_key = $this->getDBName($displayLabel, false);
866         $systemLabel = $field_key;
867         if(!$this->use_existing_labels){ // use_existing_labels defaults to false in this module; as of today, only set to true by ModuleInstaller.php
868             while( isset( $mod_strings [ $systemLabel ] ) && $count <= $limit )
869             {
870                 $systemLabel = $field_key. "_$count";
871                 $count++;
872             }
873         }
874         $selMod = (!empty($_REQUEST['view_module'])) ? $_REQUEST['view_module'] : $this->module;
875         require_once 'modules/ModuleBuilder/parsers/parser.label.php' ;
876         $parser = new ParserLabel ( $selMod , isset ( $_REQUEST [ 'view_package' ] ) ? $_REQUEST [ 'view_package' ] : null ) ;
877         $parser->handleSave ( array('label_'. $systemLabel => $displayLabel ) , $GLOBALS [ 'current_language' ] ) ;
878
879         return $systemLabel;
880     }
881
882     /**
883      * Returns a Database Safe Name
884      *
885      * @param STRING $name
886      * @param BOOLEAN $_C do we append _c to the name
887      * @return STRING
888      */
889     function getDBName($name, $_C= true){
890         static $cached_results = array();
891         if(!empty($cached_results[$name]))
892         {
893             return $cached_results[$name];
894         }
895         $exclusions = array('parent_type', 'parent_id', 'currency_id', 'parent_name');
896         // Remove any non-db friendly characters
897         $return_value = preg_replace("/[^\w]+/","_",$name);
898         if($_C == true && !in_array($return_value, $exclusions) && substr($return_value, -2) != '_c'){
899             $return_value .= '_c';
900         }
901         $cached_results[$name] = $return_value;
902         return $return_value;
903     }
904
905     function setWhereClauses(&$where_clauses){
906         if (isset($this->avail_fields)) {
907             foreach($this->avail_fields as $name=>$value){
908                 if(!empty($_REQUEST[$name])){
909                     $where_clauses[] = $this->bean->table_name . "_cstm.$name LIKE '". $GLOBALS['db']->quote($_REQUEST[$name]). "%'";
910                 }
911             }
912         }
913
914     }
915
916     /////////////////////////BACKWARDS COMPATABILITY MODE FOR PRE 5.0 MODULES\\\\\\\\\\\\\\\\\\\\\\\\\\\
917     ////////////////////////////END BACKWARDS COMPATABILITY MODE FOR PRE 5.0 MODULES\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
918
919     /**
920      *
921      * DEPRECATED
922      loads fields into the bean
923      This used to be called during the retrieve process now it is done through a join
924      Restored from pre-r30895 to maintain support for custom code that may have called retrieve() directly
925      */
926
927     function retrieve()
928     {
929         if(!isset($this->bean)){
930             $GLOBALS['log']->fatal("DynamicField retrieve, bean not instantiated: ".var_export(debug_print_backtrace(), true));
931             return false;
932         }
933
934         if(!$this->bean->hasCustomFields()){
935             return false;
936         }
937
938         $query = "SELECT * FROM ".$this->bean->table_name."_cstm WHERE id_c='".$this->bean->id."'";
939         $result = $GLOBALS['db']->query($query);
940         $row = $GLOBALS['db']->fetchByAssoc($result);
941
942         if($row)
943         {
944             foreach($row as $name=>$value)
945             {
946                 // originally in pre-r30895 we checked if this field was in avail_fields i.e., in fields_meta_data and not deleted
947                 // with the removal of avail_fields post-r30895 we have simplified this - we now retrieve every custom field even if previously deleted
948                 // 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)
949                 $this->bean->$name = $value;
950             }
951         }
952
953     }
954
955    function populateXTPL($xtpl, $view) {
956
957         if($this->bean->hasCustomFields()){
958             $results = $this->getAllFieldsView($view, 'xtpl');
959             foreach($results as $name=>$value){
960                 if(is_array($value['xtpl']))
961                 {
962                     foreach($value['xtpl'] as $xName=>$xValue)
963                     {
964                         $xtpl->assign(strtoupper($xName), $xValue);
965                     }
966                 } else {
967                     $xtpl->assign(strtoupper($name), $value['xtpl']);
968                 }
969             }
970         }
971
972     }
973
974     function populateAllXTPL($xtpl, $view){
975         $this->populateXTPL($xtpl, $view);
976
977     }
978
979     function getAllFieldsView($view, $type){
980          require_once ('modules/DynamicFields/FieldCases.php');
981          $results = array();
982          foreach($this->bean->field_defs as $name=>$data){
983             if(empty($data['source']) || $data['source'] != 'custom_fields')
984             {
985                 continue;
986             }
987             $field = get_widget ( $data ['type'] );
988             $field->populateFromRow($data);
989             $field->view = $view;
990             $field->bean = $this->bean;
991             switch(strtolower($type))
992             {
993                 case 'xtpl':
994                     $results[$name] = array('xtpl'=>$field->get_xtpl());
995                     break;
996                 case 'html':
997                     $results[$name] = array('html'=> $field->get_html(), 'label'=> $field->get_html_label(), 'fieldType'=>$field->data_type, 'isCustom' =>true);
998                     break;
999             }
1000
1001         }
1002         return $results;
1003     }
1004
1005     ////////////////////////////END BACKWARDS COMPATABILITY MODE FOR PRE 5.0 MODULES\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
1006 }
1007
1008 ?>