]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - modules/DynamicFields/DynamicField.php
Release 6.1.5
[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 is a customer relationship management program developed by
7  * SugarCRM, Inc. Copyright (C) 2004-2011 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          * @return unknown
99          */
100         function buildCache($module = false) {
101                 //We can't build the cache while installing as the required database tables may not exist.
102                 if (!empty($GLOBALS['installing']) && $GLOBALS['installing'] == true|| !$GLOBALS['db'])
103                         return false;
104                 if($module == '../data')return false;
105
106                 static $results = array ( ) ;
107
108                 $where = '';
109                 if (! empty ( $module )) {
110                         $where .= " custom_module='$module' AND ";
111                         unset( $results[ $module ] ) ; // clear out any old results for the module as $results is declared static
112                 }
113                 else
114                 {
115                     $results = array ( ) ; // clear out results - if we remove a module we don't want to have its old vardefs hanging around
116                 }
117
118                 $GLOBALS['log']->debug('rebuilding cache for ' . $module);
119                 $query = "SELECT * FROM fields_meta_data WHERE $where deleted = 0";
120
121                 $result = $GLOBALS['db']->query ( $query );
122                 require_once ('modules/DynamicFields/FieldCases.php');
123
124                 // retrieve the field definition from the fields_meta_data table
125                 // using 'encode'=false to fetchByAssoc to prevent any pre-formatting of the base metadata
126                 // for immediate use in HTML. This metadata will be further massaged by get_field_def() and so should not be pre-formatted
127                 while ( $row = $GLOBALS['db']->fetchByAssoc ( $result , -1 ,false ) ) {
128                         $field = get_widget ( $row ['type'] );
129
130                         foreach ( $row as $key => $value ) {
131                                 $field->$key = $value;
132                         }
133                         $field->default = $field->default_value;
134                         $vardef = $field->get_field_def ();
135                         $vardef ['id'] = $row ['id'];
136                         $vardef ['custom_module'] = $row ['custom_module'];
137                         if (empty ( $vardef ['source'] ))
138                                 $vardef ['source'] = 'custom_fields';
139                         if (empty ( $results [$row ['custom_module']] ))
140                                 $results [$row ['custom_module']] = array ( );
141                         $results [$row ['custom_module']] [$row ['name']] = $vardef;
142                 }
143                 if (empty ( $module )) {
144                         foreach ( $results as $module => $result ) {
145                                 $this->saveToVardef ( $module, $result );
146                         }
147                 } else {
148                         if (! empty ( $results [$module] )) {
149                                 $this->saveToVardef ( $module, $results [$module] );
150                         }else{
151                                 $this->saveToVardef ( $module, false );
152                         }
153                 }
154                 return true;
155
156         }
157
158         /**
159          * Returns the widget for a custom field from the fields_meta_data table.
160          */
161          function getFieldWidget($module, $fieldName) {
162                 if (empty($module) || empty($fieldName)){
163                         sugar_die("Unable to load widget for '$module' : '$fieldName'");
164                 }
165                 $query = "SELECT * FROM fields_meta_data WHERE custom_module='$module' AND name='$fieldName' AND deleted = 0";
166                 $result = $GLOBALS['db']->query ( $query );
167                 require_once ('modules/DynamicFields/FieldCases.php');
168                 if ( $row = $GLOBALS['db']->fetchByAssoc ( $result ) ) {
169                         $field = get_widget ( $row ['type'] );
170                         $field->populateFromRow($row);
171                         return $field;
172                 }
173          }
174
175
176         /**
177          * Updates the cached vardefs with the custom field information stored in result
178          *
179          * @param string $module
180          * @param array $result
181          */
182         function saveToVardef($module,$result) {
183
184                 global $beanList;
185                 if (! empty ( $beanList [$module] )) {
186                         $object = $beanList [$module];
187
188                         if ($object == 'aCase') {
189                                 $object = 'Case';
190                         }
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                         $GLOBALS ['dictionary'] [$object] ['custom_fields'] = false;
199                         if (! empty ( $GLOBALS ['dictionary'] [$object] )) {
200                                 if (! empty ( $result )) {
201                                         // First loop to add
202
203                                 foreach ( $result as $field ) {
204                                         foreach($field as $k=>$v){
205                                                 //allows values for custom fields to be defined outside of the scope of studio
206                                                 if(!isset($GLOBALS ['dictionary'] [$object] ['fields'] [$field ['name']][$k])){
207                                                         $GLOBALS ['dictionary'] [$object] ['fields'] [$field ['name']][$k] = $v;
208                                                 }
209                                         }
210                                 }
211
212                                         // Second loop to remove
213                                         foreach ( $GLOBALS ['dictionary'] [$object] ['fields'] as $name => $fieldDef ) {
214
215                                                 if (isset ( $fieldDef ['custom_module'] )) {
216                                                         if (! isset ( $result [$name] )) {
217                                                                 unset ( $GLOBALS ['dictionary'] [$object] ['fields'] [$name] );
218                                                         } else {
219                                                                 $GLOBALS ['dictionary'] [$object] ['custom_fields'] = true;
220                                                         }
221                                                 }
222
223                                         } //if
224                                 }
225                         }
226                         $manager = new VardefManager ( );
227                         $manager->saveCache ( $this->module, $object );
228                 }
229         }
230
231         /**
232          * returns either false or an array containing the select and join parameter for a query using custom fields
233          * @param $expandedList boolean If true, return a list of all fields with source=custom_fields in the select instead of the standard _cstm.*
234          *     This is required for any downstream construction of a SQL statement where we need to manipulate the select list,
235          *     for example, listviews with custom relate fields where the value comes from join rather than from the custom table
236          *
237          * @return array select=>select columns, join=>prebuilt join statement
238          */
239   function getJOIN( $expandedList = false , $includeRelates = false, &$where = false){
240                 if(!$this->bean->hasCustomFields()){
241             return false;
242         }
243
244         if (empty($expandedList) )
245         {
246                 $select = ",{$this->bean->table_name}_cstm.*" ;
247         }
248         else
249         {
250                 $select = '';
251                 $isList = is_array($expandedList);
252                         foreach($this->bean->field_defs as $name=>$field)
253                         {
254                                 if (!empty($field['source']) && $field['source'] == 'custom_fields' && (!$isList || !empty($expandedList[$name]))){
255                                         // assumption: that the column name in _cstm is the same as the field name. Currently true.
256                                         // 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)
257                                         if ( $field['type'] != 'html' && $name != 'parent_name')
258                                                 $select .= ",{$this->bean->table_name}_cstm.{$name}" ;
259                                 }
260                         }
261         }
262         $join = " LEFT JOIN " .$this->bean->table_name. "_cstm ON " .$this->bean->table_name. ".id = ". $this->bean->table_name. "_cstm.id_c ";
263
264         if ($includeRelates) {
265                 $jtAlias = "relJoin";
266                 $jtCount = 1;
267                 foreach($this->bean->field_defs as $name=>$field)
268                         {
269                                 if ($field['type'] == 'relate' && isset($field['custom_module'])) {
270                                         $relateJoinInfo = $this->getRelateJoin($field, $jtAlias.$jtCount);
271                                         $select .= $relateJoinInfo['select'];
272                                         $join .= $relateJoinInfo['from'];
273                                         //bug 27654 martin
274                                         if ($where)
275                                         {
276                                                 $pattern = '/'.$field['name'].'\slike/i';
277                                                 $replacement = $relateJoinInfo['name_field'].' like';
278                                                 $where = preg_replace($pattern,$replacement,$where);
279                                         }
280                                         $jtCount++;
281                                 }
282                         }
283         }
284
285         return array('select'=>$select, 'join'=>$join);
286
287     }
288
289    function getRelateJoin($field_def, $joinTableAlias) {
290                 if (empty($field_def['type']) || $field_def['type'] != "relate") {
291                         return false;
292                 }
293                 global $beanFiles, $beanList, $module;
294                 $rel_module = $field_def['module'];
295         if(empty($beanFiles[$beanList[$rel_module]])) {
296                 return false;
297         }
298
299         require_once($beanFiles[$beanList[$rel_module]]);
300             $rel_mod = new $beanList[$rel_module]();
301             $rel_table = $rel_mod->table_name;
302             if (isset($rel_mod->field_defs['name']))
303             {
304                         $name_field_def = $rel_mod->field_defs['name'];
305                 if(isset($name_field_def['db_concat_fields']))
306                 {
307                         $name_field = db_concat($joinTableAlias, $name_field_def['db_concat_fields']);
308                 }
309                 //If the name field is non-db, we need to find another field to display
310                 else if(!empty($rel_mod->field_defs['name']['source']) && $rel_mod->field_defs['name']['source'] == "non-db" && !empty($field_def['rname']))
311                 {
312                         $name_field = "$joinTableAlias." . $field_def['rname'];
313                 }
314                 else
315                 {
316                         $name_field = "$joinTableAlias.name";
317                 }
318         }
319         $tableName = isset($field_def['custom_module']) ? "{$this->bean->table_name}_cstm" : $this->bean->table_name ;
320         $relID = $field_def['id_name'];
321         $ret_array['rel_table'] = $rel_table;
322         $ret_array['name_field'] = $name_field;
323         $ret_array['select'] = ", $name_field {$field_def['name']} ";
324         $ret_array['from'] = " LEFT JOIN $rel_table $joinTableAlias ON $tableName.$relID = $joinTableAlias.id"
325                                             . " AND $joinTableAlias.deleted=0 ";
326                 return $ret_array;
327    }
328
329    /**
330     * Fills in all the custom fields of type relate relationships for an object
331     *
332     */
333    function fill_relationships(){
334         global $beanList, $beanFiles;
335         if(!empty($this->bean->relDepth)) {
336                         if($this->bean->relDepth > 1)return;
337                 }else{
338                         $this->bean->relDepth = 0;
339         }
340         foreach($this->bean->field_defs as $field){
341                         if(empty($field['source']) || $field['source'] != 'custom_fields')continue;
342             if($field['type'] == 'relate'){
343                 $related_module =$field['ext2'];
344                 $name = $field['name'];
345                 if (empty($this->bean->$name)) { //Don't load the relationship twice
346                         $id_name = $field['id_name'];
347                         if(isset($beanList[ $related_module])){
348                             $class = $beanList[$related_module];
349
350                             if(file_exists($beanFiles[$class]) && isset($this->bean->$name)){
351                                 require_once($beanFiles[$class]);
352                                 $mod = new $class();
353                                 $mod->relDepth = $this->bean->relDepth + 1;
354                                 $mod->retrieve($this->bean->$id_name);
355                                 $this->bean->$name = $mod->name;
356                             }
357                         }
358                 }
359             }
360         }
361     }
362
363     /**
364      * Process the save action for sugar bean custom fields
365      *
366      * @param boolean $isUpdate
367      */
368      function save($isUpdate){
369
370         if($this->bean->hasCustomFields() && isset($this->bean->id)){
371
372             if($isUpdate){
373                 $query = "UPDATE ". $this->bean->table_name. "_cstm SET ";
374             }
375             $queryInsert = "INSERT INTO ". $this->bean->table_name. "_cstm (id_c";
376             $values = "('".$this->bean->id."'";
377             $first = true;
378             foreach($this->bean->field_defs as $name=>$field){
379
380                 if(empty($field['source']) || $field['source'] != 'custom_fields')continue;
381                 if($field['type'] == 'html' || $field['type'] == 'parent')continue;
382                 if(isset($this->bean->$name)){
383                     $quote = "'";
384
385                     if(in_array($field['type'], array('int', 'float', 'double', 'uint', 'ulong', 'long', 'short', 'tinyint', 'currency', 'decimal'))) {
386                         $quote = '';
387                         if(!isset($this->bean->$name) || !is_numeric($this->bean->$name) ){
388                             if($field['required']){
389                                 $this->bean->$name = 0;
390                             }else{
391                                 $this->bean->$name = 'NULL';
392                             }
393                         }
394                     }
395                     if ( $field['type'] == 'bool' ) {
396                         if ( $this->bean->$name === FALSE )
397                             $this->bean->$name = '0';
398                         elseif ( $this->bean->$name === TRUE )
399                             $this->bean->$name = '1';
400                     }
401                     $val = $this->bean->$name;
402                                         if(($field['type'] == 'date' || $field['type'] == 'datetimecombo') && (empty($this->bean->$name )|| $this->bean->$name == '1900-01-01')){
403                         $quote = '';
404                         $val = 'NULL';
405                         $this->bean->$name = ''; // do not set it to string 'NULL'
406                     }
407                     if($isUpdate){
408                         if($first){
409                             $query .= " $name=$quote".$GLOBALS['db']->quote($val)."$quote";
410
411                         }else{
412                             $query .= " ,$name=$quote".$GLOBALS['db']->quote($val)."$quote";
413                         }
414                     }
415                     $first = false;
416                     $queryInsert .= " ,$name";
417                     $values .= " ,$quote". $GLOBALS['db']->quote($val). "$quote";
418                 }
419             }
420             if($isUpdate){
421                 $query.= " WHERE id_c='" . $this->bean->id ."'";
422
423             }
424
425             $queryInsert .= " ) VALUES $values )";
426             if(!$first){
427                 if(!$isUpdate){
428                     $GLOBALS['db']->query($queryInsert);
429                 }else{
430                     $checkquery = "SELECT id_c FROM {$this->bean->table_name}_cstm WHERE id_c = '{$this->bean->id}'";
431                     if ( $GLOBALS['db']->getOne($checkquery) )
432                         $result = $GLOBALS['db']->query($query);
433                     else
434                         $GLOBALS['db']->query($queryInsert);
435                 }
436             }
437
438         }
439
440
441
442     }
443     /**
444      * Deletes the field from fields_meta_data and drops the database column then it rebuilds the cache
445      * Use the widgets get_db_modify_alter_table() method to get the table sql - some widgets do not need any custom table modifications
446      * @param STRING $name - field name
447      */
448     function deleteField($widget){
449         require_once('modules/DynamicFields/templates/Fields/TemplateField.php');
450         global $beanList;
451                 if (!($widget instanceof TemplateField)) {
452                         $field_name = $widget;
453                         $widget = new TemplateField();
454                         $widget->name = $field_name;
455                 }
456                 $object_name = $beanList[$this->module];
457
458                 if ($object_name == 'aCase') {
459             $object_name = 'Case';
460         }
461
462                 $GLOBALS['db']->query("DELETE FROM fields_meta_data WHERE id='" . $this->module . $widget->name . "'");
463         $sql = $widget->get_db_delete_alter_table( $this->bean->table_name . "_cstm" ) ;
464         if (! empty( $sql ) )
465             $GLOBALS['db']->query( $sql );
466         
467         $this->removeVardefExtension($widget);
468         VardefManager::clearVardef();
469         VardefManager::refreshVardefs($this->module, $object_name);
470
471     }
472
473     /*
474      * Method required by the TemplateRelatedTextField->save() method
475      * Taken from MBModule's implementation
476      */
477     function fieldExists($name = '', $type = ''){
478         // must get the vardefs from the GLOBAL array as $bean->field_defs does not contain the values from the cache at this point
479         // TODO: fix this - saveToVardefs() updates GLOBAL['dictionary'] correctly, obtaining its information directly from the fields_meta_data table via buildCache()...
480         $vardefs = $GLOBALS['dictionary'][$this->bean->object_name]['fields'];
481         if(!empty($vardefs)){
482             if(empty($type) && empty($name))
483                 return false;
484             else if(empty($type))
485                 return !empty($vardefs[$name]);
486             else if(empty($name)){
487                 foreach($vardefs as $def){
488                     if($def['type'] == $type)
489                         return true;
490                 }
491                 return false;
492             }else
493                 return (!empty($vardefs[$name]) && ($vardefs[$name]['type'] == $type));
494         }else{
495             return false;
496         }
497     }
498
499
500     /**
501      * Adds a custom field using a field object
502      *
503      * @param Field Object $field
504      * @return boolean
505      */
506         function addFieldObject(&$field){
507                 $GLOBALS['log']->debug('adding field');
508                 $object_name = $this->module;
509         $db_name = $field->name;
510
511         $fmd = new FieldsMetaData();
512         $id =  $fmd->retrieve($object_name.$db_name,true, false);
513         $is_update = false;
514         $label = $field->label;
515         if(!empty($id)){
516             $is_update = true;
517         }else{
518             $db_name = $this->getDBName($field->name);
519             $field->name = $db_name;
520         }
521         $this->createCustomTable();
522         $fmd->id = $object_name.$db_name;
523         $fmd->custom_module= $object_name;
524         $fmd->name = $db_name;
525         $fmd->vname = $label;
526         $fmd->type = $field->type;
527         $fmd->help = $field->help;
528         if (!empty($field->len))
529             $fmd->len = $field->len; // tyoung bug 15407 - was being set to $field->size so changes weren't being saved
530         $fmd->required = ($field->required ? 1 : 0);
531         $fmd->default_value = $field->default;
532         $fmd->ext1 = $field->ext1;
533         $fmd->ext2 = $field->ext2;
534         $fmd->ext3 = $field->ext3;
535         $fmd->ext4 = (isset($field->ext4) ? $field->ext4 : '');
536         $fmd->comments = $field->comment;
537         $fmd->massupdate = $field->massupdate;
538         $fmd->importable = ( isset ( $field->importable ) ) ? $field->importable : null ;
539         $fmd->duplicate_merge = $field->duplicate_merge;
540         $fmd->audited =$field->audited;
541         $fmd->reportable = ($field->reportable ? 1 : 0);
542         if(!$is_update){
543             $fmd->new_with_id=true;
544         }
545         $fmd->save();
546         $this->buildCache($this->module);
547         if($field){
548             if(!$is_update){
549                 $query = $field->get_db_add_alter_table($this->bean->table_name . '_cstm');
550             }else{
551                 $query = $field->get_db_modify_alter_table($this->bean->table_name . '_cstm');
552             }
553             if(!empty($query)){
554                 $GLOBALS['db']->query($query);
555             }
556             $this->saveExtendedAttributes($field, array_keys($fmd->field_defs));
557         }
558         
559         return true;
560     }
561     
562     function saveExtendedAttributes($field, $column_fields)
563     {
564                 require_once ('modules/ModuleBuilder/parsers/StandardField.php') ;
565                 require_once ('modules/DynamicFields/FieldCases.php') ;
566                 global $beanList;
567                 
568                 $to_save = array();
569                 $base_field = get_widget ( $field->type) ;
570         foreach ($field->vardef_map as $property => $fmd_col){
571                         
572                 if (!isset($field->$property) || in_array($fmd_col, $column_fields) || in_array($property, $column_fields)
573                         || $this->isDefaultValue($property, $field->$property, $base_field)
574                         || $property == "action" || $property == "label_value" || $property == "label"
575                 || (substr($property, 0,3) == 'ext' && strlen($property) == 4))
576                 {
577                         continue;
578                 }
579                 $to_save[$property] = 
580                 is_string($field->$property) ? htmlspecialchars_decode($field->$property, ENT_QUOTES) : $field->$property;
581         }
582         $bean_name = $beanList[$this->module];
583         
584         $this->writeVardefExtension($bean_name, $field, $to_save);
585     }
586     
587         protected function isDefaultValue($property, $value, $baseField)
588     {
589         switch ($property) {
590                 case "importable": 
591                         return ( $value === 'true' || $value === '1' || $value === true || $value === 1 ); break;
592                 case "required":
593                 case "audited":
594                 case "massupdate":
595                         return ( $value === 'false' || $value === '0' || $value === false || $value === 0); break;
596                 case "default_value":
597                 case "default":
598                 case "help":
599                 case "comments":
600                         return ($value == "");
601                 case "duplicate_merge":
602                         return ( $value === 'false' || $value === '0' || $value === false || $value === 0 || $value === "disabled"); break;
603         }
604         
605         if (isset($baseField->$property))
606         {
607                 return $baseField->$property == $value;
608         }
609         
610         return false;
611     }
612     
613     protected function writeVardefExtension($bean_name, $field, $def_override)
614     {
615         //Hack for the broken cases module
616         $vBean = $bean_name == "aCase" ? "Case" : $bean_name;
617         $file_loc = "$this->base_path/sugarfield_{$field->name}.php";
618         
619                 $out =  "<?php\n // created: " . date('Y-m-d H:i:s') . "\n";
620         foreach ($def_override as $property => $val) 
621         {
622                 $out .= override_value_to_string_recursive(array($vBean, "fields", $field->name, $property), "dictionary", $val) . "\n";
623         }
624         
625         $out .= "\n ?>";
626         
627         if (!file_exists($this->base_path))
628             mkdir_recursive($this->base_path);
629             
630         if( $fh = @sugar_fopen( $file_loc, 'w' ) )
631             {
632                 fputs( $fh, $out);
633                 fclose( $fh );
634                 return true ;
635             }
636             else
637             {
638                 return false ;
639             }
640     }
641     
642     protected function removeVardefExtension($field)
643     {
644         $file_loc = "$this->base_path/sugarfield_{$field->name}.php";
645         
646         if (is_file($file_loc))
647         {
648                 unlink($file_loc);
649         }
650     }
651     
652
653     /**
654      * DEPRECIATED: Use addFieldObject instead.
655      * Adds a Custom Field using parameters
656      *
657      * @param unknown_type $name
658      * @param unknown_type $label
659      * @param unknown_type $type
660      * @param unknown_type $max_size
661      * @param unknown_type $required_option
662      * @param unknown_type $default_value
663      * @param unknown_type $ext1
664      * @param unknown_type $ext2
665      * @param unknown_type $ext3
666      * @param unknown_type $audited
667      * @param unknown_type $mass_update
668      * @param unknown_type $ext4
669      * @param unknown_type $help
670      * @param unknown_type $duplicate_merge
671      * @param unknown_type $comment
672      * @return boolean
673      */
674     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=''){
675                 require_once('modules/DynamicFields/templates/Fields/TemplateField.php');
676         $field = new TemplateField();
677         $field->label = $label;
678         if(empty($field->label)){
679             $field->label = $name;
680         }
681         $field->name = $name;
682                 $field->type = $type;
683                 $field->len = $max_size;
684                 $field->required = (!empty($required_option) && $required_option != 'optional');
685                 $field->default = $default_value;
686                 $field->ext1 = $ext1;
687                 $field->ext2 = $ext2;
688                 $field->ext3 = $ext3;
689                 $field->ext4 = $ext4;
690                 $field->help = $help;
691                 $field->comments = $comment;
692                 $field->massupdate = $mass_update;
693                 $field->duplicate_merge = $duplicate_merge;
694                 $field->audited = $audited;
695                 $field->reportable = 1;
696         return $this->addFieldObject($field);
697     }
698
699     /**
700      * Creates the custom table with an id of id_c
701      *
702      */
703     function createCustomTable($execute = true){
704         $out = "";
705         if (!$GLOBALS['db']->tableExists($this->bean->table_name."_cstm")) {
706                 $GLOBALS['log']->debug('creating custom table for '. $this->bean->table_name);
707             $query = "/* Missing Table: {$this->bean->table_name}_cstm */\n"
708                    . "CREATE TABLE ".$this->bean->table_name.'_cstm ( ';
709             $query .='id_c ' . $this->bean->dbManager->helper->getColumnType('id') .' NOT NULL';
710             $query .=', PRIMARY KEY ( id_c ) )';
711             if($GLOBALS['db']->dbType == 'mysql'){
712                 $query .= ' CHARACTER SET utf8 COLLATE utf8_general_ci';
713             }
714             $out = $query . "\n";
715             if ($execute)
716                 $GLOBALS['db']->query($query);
717             $out .= $this->add_existing_custom_fields($execute);
718         }
719         
720         return $out;
721     }
722
723     /**
724      * Updates the db schema and adds any custom fields we have used if the custom table was dropped
725      *
726      */
727     function add_existing_custom_fields($execute = true){
728         $out = "";
729         if($this->bean->hasCustomFields()){
730                 foreach($this->bean->field_defs as $name=>$data){
731                         if(empty($data['source']) || $data['source'] != 'custom_fields')
732                     continue;
733                     $out .= $this->add_existing_custom_field($data, $execute);
734                 }
735         }
736         return $out;
737     }
738
739     function add_existing_custom_field($data, $execute = true)
740     {
741
742         $field = get_widget ( $data ['type'] );
743         $field->populateFromRow($data);
744         $query = "/*MISSING IN DATABASE - {$data['name']} -  ROW*/\n"
745                 . $field->get_db_add_alter_table($this->bean->table_name . '_cstm');
746         $out = $query . "\n";
747         if ($execute)
748             $GLOBALS['db']->query($query);
749
750         return $out;
751     }
752
753     public function repairCustomFields($execute = true)
754     {
755         $out = $this->createCustomTable($execute);
756         //If the table didn't exist, createCustomTable will have returned all the SQL to create and populate it
757         if (!empty($out))
758             return "/*Checking Custom Fields for module : {$this->module} */\n$out";
759         //Otherwise make sure all the custom fields defined in the vardefs exist in the custom table.
760         //We aren't checking for data types, just that the column exists.
761         $db = $GLOBALS['db'];
762         $tablename = $this->bean->table_name."_cstm";
763         $compareFieldDefs = $db->getHelper()->get_columns($tablename);
764         foreach($this->bean->field_defs as $name=>$data){
765             if(empty($data['source']) || $data['source'] != 'custom_fields')
766                 continue;
767             if(!empty($compareFieldDefs[$name])) {
768                 continue;
769         }
770             $out .= $this->add_existing_custom_field($data, $execute);
771         }
772         if (!empty($out))
773             $out = "/*Checking Custom Fields for module : {$this->module} */\n$out";
774
775         return $out;
776     }
777
778     /**
779      * Adds a label to the module's mod_strings for the current language
780      * Note that the system label name
781      *
782      * @param string $displayLabel The label value to be displayed
783      * @return string The core of the system label name - returns currency_id5 for example, when the full label would then be LBL_CURRENCY_ID5
784      * TODO: Only the core is returned for historical reasons - switch to return the real system label
785      */
786     function addLabel ( $displayLabel )
787     {
788         $mod_strings = return_module_language($GLOBALS[ 'current_language' ], $this->module);
789         $limit = 10;
790         $count = 0;
791         $field_key = $this->getDBName($displayLabel, false);
792         $systemLabel = $field_key;
793         if(!$this->use_existing_labels){ // use_existing_labels defaults to false in this module; as of today, only set to true by ModuleInstaller.php
794             while( isset( $mod_strings [ $systemLabel ] ) && $count <= $limit )
795             {
796                 $systemLabel = $field_key. "_$count";
797                 $count++;
798             }
799         }
800         $selMod = (!empty($_REQUEST['view_module'])) ? $_REQUEST['view_module'] : $this->module;
801         require_once 'modules/ModuleBuilder/parsers/parser.label.php' ;
802         $parser = new ParserLabel ( $selMod , isset ( $_REQUEST [ 'view_package' ] ) ? $_REQUEST [ 'view_package' ] : null ) ;
803         $parser->handleSave ( array('label_'. $systemLabel => $displayLabel ) , $GLOBALS [ 'current_language' ] ) ;
804
805         return $systemLabel;
806     }
807
808     /**
809      * Returns a Database Safe Name
810      *
811      * @param STRING $name
812      * @param BOOLEAN $_C do we append _c to the name
813      * @return STRING
814      */
815     function getDBName($name, $_C= true){
816         static $cached_results = array();
817         if(!empty($cached_results[$name]))
818         {
819             return $cached_results[$name];
820         }
821         $exclusions = array('parent_type', 'parent_id', 'currency_id', 'parent_name');
822         // Remove any non-db friendly characters
823         $return_value = preg_replace("/[^\w]+/","_",$name);
824         if($_C == true && !in_array($return_value, $exclusions) && substr($return_value, -2) != '_c'){
825             $return_value .= '_c';
826         }
827         $cached_results[$name] = $return_value;
828         return $return_value;
829     }
830
831     function setWhereClauses(&$where_clauses){
832         if (isset($this->avail_fields)) {
833                 foreach($this->avail_fields as $name=>$value){
834                     if(!empty($_REQUEST[$name])){
835                         $where_clauses[] = $this->bean->table_name . "_cstm.$name LIKE '". $GLOBALS['db']->quote($_REQUEST[$name]). "%'";
836                     }
837                 }
838         }
839
840     }
841
842         /////////////////////////BACKWARDS COMPATABILITY MODE FOR PRE 5.0 MODULES\\\\\\\\\\\\\\\\\\\\\\\\\\\
843         ////////////////////////////END BACKWARDS COMPATABILITY MODE FOR PRE 5.0 MODULES\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
844
845     /**
846      *
847      * DEPRECATED
848      loads fields into the bean
849      This used to be called during the retrieve process now it is done through a join
850      Restored from pre-r30895 to maintain support for custom code that may have called retrieve() directly
851      */
852
853     function retrieve()
854     {
855         if(!isset($this->bean)){
856             $GLOBALS['log']->fatal("DynamicField retrieve, bean not instantiated: ".var_export(debug_print_backtrace(), true));
857             return false;
858         }
859
860         if(!$this->bean->hasCustomFields()){
861             return false;
862         }
863
864         $query = "SELECT * FROM ".$this->bean->table_name."_cstm WHERE id_c='".$this->bean->id."'";
865         $result = $GLOBALS['db']->query($query);
866         $row = $GLOBALS['db']->fetchByAssoc($result);
867
868         if($row)
869         {
870             foreach($row as $name=>$value)
871             {
872                 // originally in pre-r30895 we checked if this field was in avail_fields i.e., in fields_meta_data and not deleted
873                 // with the removal of avail_fields post-r30895 we have simplified this - we now retrieve every custom field even if previously deleted
874                 // 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)
875                 $this->bean->$name = $value;
876             }
877         }
878
879     }
880
881    function populateXTPL(&$xtpl, $view){
882                 if($this->bean->hasCustomFields()){
883                 $results = $this->getAllFieldsView($view, 'xtpl');
884                 foreach($results as $name=>$value){
885                                 if(is_array($value['xtpl'])){
886                         foreach($value['xtpl'] as $xName=>$xValue){
887                             $xtpl->assign(strtoupper($xName), $xValue);
888
889                         }
890                     }else{
891                         $xtpl->assign(strtoupper($name), $value['xtpl']);
892                     }
893                 }
894                 }
895
896     }
897
898     function populateAllXTPL(&$xtpl, $view){
899         $this->populateXTPL($xtpl, $view);
900
901         }
902
903     function getAllFieldsView($view, $type){
904                 $results = array();
905          foreach($this->bean->field_defs as $name=>$data){
906                 if(empty($data['source']) || $data['source'] != 'custom_fields')continue;
907                 require_once ('modules/DynamicFields/FieldCases.php');
908                 $field = get_widget ( $data ['type'] );
909                 $field->populateFromRow($data);
910             $field->view = $view;
911             $field->bean =& $this->bean;
912             switch(strtolower($type)){
913                 case 'xtpl':
914                     $results[$name] = array('xtpl'=>$field->get_xtpl());
915                     break;
916                 case 'html':
917                     $results[$name] = array('html'=> $field->get_html(), 'label'=> $field->get_html_label(), 'fieldType'=>$field->data_type, 'isCustom' =>true);
918                     break;
919
920             }
921
922         }
923         return $results;
924     }
925
926         ////////////////////////////END BACKWARDS COMPATABILITY MODE FOR PRE 5.0 MODULES\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
927 }
928
929 ?>