Project

General

Profile

Paste
Download (18.5 KB) Statistics
| Branch: | Revision:

root / drupal7 / sites / all / modules / computed_field / computed_field.module @ ebcc4118

1
<?php
2

    
3
/**
4
 * @file
5
 * Functionality for the computed field.
6
 */
7

    
8
/**
9
 * Implements hook_field_info().
10
 */
11
function computed_field_field_info() {
12
  return array(
13
    'computed' => array(
14
      'label' => t('Computed'),
15
      'description' => t('Create field data via PHP code.'),
16
      'settings' => array(
17
        'code' => '$entity_field[0][\'value\'] = "";',
18
        'display_format' => '$display_output = $entity_field_item[\'value\'];',
19
        'store' => 1,
20
        'recalculate' => FALSE,
21
        'database' => array(
22
          'data_type' => 'varchar',
23
          'data_length' => 32,
24
          'data_size' => 'normal',
25
          'data_precision' => 10,
26
          'data_scale' => 2,
27
          'data_not_NULL' => FALSE,
28
          'data_default' => NULL,
29
          'data_index' => FALSE,
30
        ),
31
      ),
32
      'default_widget' => 'computed',
33
      'default_formatter' => 'computed_field_plain',
34
      // If we followed the core convention of separate fields for each data
35
      // type we could make Entity API happy by just setting a property_type.
36
      // Instead we have to use our own callback to determine the type then
37
      // rerun theirs to setup the rest of the field properties.
38
      'property_callbacks' => array('computed_field_entity_property_callback'),
39
    ),
40
  );
41
}
42

    
43
/**
44
 * Callback to setup Entity API's field properties.
45
 */
46
function computed_field_entity_property_callback(&$info, $entity_type, $field, $instance, $field_type) {
47
  $property_types = array(
48
    'int' => 'integer',
49
    'float' => 'decimal',
50
    'numeric' => 'decimal',
51
    'varchar' => 'text',
52
    'text' => 'text',
53
    'longtext' => 'text',
54
  );
55
  if (isset($field['columns']['value']) && isset($property_types[$field['columns']['value']['type']])) {
56
    // Entity API's defaults are pretty good so set the property_type and let them do the work for us.
57
    $field_type['property_type'] = $property_types[$field['columns']['value']['type']];
58
    entity_metadata_field_default_property_callback($info, $entity_type, $field, $instance, $field_type);
59
    // The only thing is that a setter doesn't make sense, so let's disable it.
60
    $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']];
61
    unset($property['setter callback']);
62
  }
63
}
64

    
65
/**
66
 * Implements hook_field_settings_form().
67
 */
68
function computed_field_field_settings_form($field, $instance, $has_data) {
69
  $form = array();
70
  $compute_func = 'computed_field_' . $field['field_name'] . '_compute';
71
  $display_func = 'computed_field_' . $field['field_name'] . '_display';
72
  $settings = $field['settings'];
73

    
74
  $form['#element_validate'] = array('computed_field_field_settings_form_validate');
75

    
76
  $form['code'] = array(
77
    '#type' => 'textarea',
78
    '#rows' => 15,
79
    '#title' => t('Computed Code (PHP)'),
80
    '#description' => t('<p>The variables available to your code include: <code>@fields</code>. To set the value of the field, set <code>@entity_field</code>.  For multi-value computed fields continue with <code>@entity_field_multi</code>.  Here\'s a simple example which sets the computed field\'s value to the value of the sum of the number fields (<code>@field_a</code> and <code>@field_b</code>) in a node entity:</p> !example <p>Alternately, this code can be supplied by your own custom function named: <code>@compute_func(&$entity_field, $entity_type, $entity, $field, $instance, $langcode, $items)</code>.</p>', array(
81
      '@fields' => '&$entity_field, $entity_type, $entity, $field, $instance, $langcode, and $items',
82
      '@entity_field' => '$entity_field[0][\'value\']',
83
      '@entity_field_multi' => '$entity_field[1][\'value\']',
84
      '@field_a' => 'field_a',
85
      '@field_b' => 'field_b',
86
      '!example' => '<p><code>$field_a = field_get_items($entity_type, $entity, "field_a");<br />
87
        $field_b = field_get_items($entity_type, $entity, "field_b");<br />
88
        $entity_field[0]["value"] = $field_a[0]["value"] + $field_b[0]["value"];</code></p>
89
      ',
90
      '@compute_func' => $compute_func,
91
    )),
92
    '#default_value' => !empty($settings['code']) ? $settings['code'] : '$entity_field[0][\'value\'] = "";',
93
    '#access' => !function_exists($compute_func),
94
  );
95
  if (function_exists($compute_func)) {
96
    $form['compute_func'] = array(
97
    '#type' => 'item',
98
    '#markup' => t('<strong>This field is COMPUTED using <code>@compute_func()</code>.</strong>', array('@compute_func' => $compute_func)),
99
    );
100
  }
101
  $form['display_format'] = array(
102
    '#type' => 'textarea',
103
    '#title' => t('Display Code (PHP)'),
104
    '#description' => t('This code should assign a string to the <code>@display_output</code> variable, which will be printed when the field is displayed. The raw computed value of the field is in <code>@value</code>.  <strong>Note:</strong> this code has no effect if you use the "Raw computed value" display formatter.<p> Alternately, this code can be supplied by your own custom function named: <code>@display_func($field, $entity_field_item, $entity_lang, $langcode, $entity)</code>.  Return the value to be displayed.  Original value is in $entity_field_item[\'value\'].', array(
105
      '@display_output' => '$display_output',
106
      '@value' => '$entity_field_item[\'value\']',
107
      '@display_func' => $display_func,
108
    )),
109
    '#default_value' => !empty($settings['display_format']) ? $settings['display_format'] : '$display_output = $entity_field_item[\'value\'];',
110
    '#access' => !function_exists($display_func),
111
  );
112
  if (function_exists($display_func)) {
113
    $form['display_func'] = array(
114
      '#type' => 'item',
115
      '#markup' => t('<strong>This field is DISPLAYED using <code>@display_func()</code>.</strong>', array('@display_func' => $display_func)),
116
    );
117
  }
118
  $form['recalculate'] = array(
119
    '#type' => 'checkbox',
120
    '#title' => t('Recalculate the field value every time.'),
121
    '#description' => t('By default, Drupal will cache the value of this field even if it is not stored in the database (and even if Page Caching is disabled). This option will cause computed_field to recalculate the value every time this field is displayed. For example, a time-based calculated value may change more often than field cache is cleared. (Note that Drupal page caching will still cache the field value.)'),
122
    '#default_value' => is_numeric($settings['recalculate']) ? $settings['recalculate'] : FALSE,
123
  );
124
  $form['store'] = array(
125
    '#type' => 'checkbox',
126
    '#title' => t('Store value in the database'),
127
    '#description' => t('The value will be stored in the database with the settings below.  As a result, it will only be recalculated when the entity is updated.  This option is required when accessing the field through Views.'),
128
    '#default_value' => is_numeric($settings['store']) ? $settings['store'] : 1 ,
129
    '#disabled' => $has_data,
130
  );
131
  $form['database'] = array('#type' => 'fieldset', '#title' => t('Database Storage Settings'));
132

    
133
  if ($has_data) {
134
    $form['database']['warning'] = array(
135
      '#type' => 'item',
136
      '#markup' => t('<strong>**This field currently has stored data, so modifications to its DB settings are not allowed.**</strong>'),
137
    );
138
  }
139
  $form['database']['data_type'] = array(
140
    '#type' => 'radios',
141
    '#title' => t('Data Type'),
142
    '#description' => t('The SQL datatype to store this field in.'),
143
    '#default_value' => !empty($settings['database']['data_type']) ? $settings['database']['data_type'] : 'varchar',
144
    '#options' => array(
145
      'varchar' => 'varchar',
146
      'text' => 'text',
147
      'longtext' => 'longtext',
148
      'int' => 'int',
149
      'float' => 'float',
150
      'numeric' => 'decimal',
151
    ),
152
    '#required' => FALSE,
153
    '#disabled' => $has_data,
154
  );
155
  $form['database']['data_length'] = array(
156
    '#type' => 'textfield',
157
    '#title' => t('Data Length (varchar/text)'),
158
    '#description' => t('<strong>Only</strong> valid for <strong>varchar</strong> or <strong>text</strong> fields. The length of the field stored in the database.'),
159
    '#default_value' => !empty($settings['database']['data_length']) ? $settings['database']['data_length'] : 32,
160
    '#required' => FALSE,
161
    '#disabled' => $has_data,
162
  );
163
  $form['database']['data_size'] = array(
164
    '#type' => 'select',
165
    '#title' => t('Data Size (int/float)'),
166
    '#description' => t('<strong>Only</strong> valid for <strong>int</strong> or <strong>float</strong> fields. The size of the field stored in the database.'),
167
    '#default_value' => !empty($settings['database']['data_size']) ? $settings['database']['data_size'] : 'normal',
168
    '#options' => array(
169
      'tiny' => 'tiny',
170
      'small' => 'small',
171
      'medium' => 'medium',
172
      'normal' => 'normal',
173
      'big' => 'big',
174
    ),
175
    '#required' => FALSE,
176
    '#disabled' => $has_data,
177
  );
178
  $form['database']['data_precision'] = array(
179
    '#type' => 'select',
180
    '#title' => t('Decimal Precision (decimal)'),
181
    '#description' => t('<strong>Only</strong> valid for <strong>decimal</strong> fields. The total number of digits to store in the database, including those to the right of the decimal.'),
182
    '#options' => drupal_map_assoc(range(10, 32)),
183
    '#default_value' => !empty($settings['database']['data_precision']) ? $settings['database']['data_precision'] : 10,
184
    '#required' => FALSE,
185
    '#disabled' => $has_data,
186
  );
187
  $form['database']['data_scale'] = array(
188
    '#type' => 'select',
189
    '#title' => t('Decimal Scale (decimal)'),
190
    '#description' => t('<strong>Only</strong> valid for <strong>decimal</strong> fields. The number of digits to the right of the decimal. '),
191
    '#options' => drupal_map_assoc(range(0, 10)),
192
    '#default_value' => !empty($settings['database']['data_scale']) ? $settings['database']['data_scale'] : 2,
193
    '#required' => FALSE,
194
    '#disabled' => $has_data,
195
  );
196
  $form['database']['data_default'] = array(
197
    '#type' => 'textfield',
198
    '#title' => t('Default Value'),
199
    '#default_value' => $settings['database']['data_default'],
200
    '#required' => FALSE,
201
    '#disabled' => $has_data,
202
  );
203
  $form['database']['data_not_NULL'] = array(
204
    '#type' => 'checkbox',
205
    '#title' => t('Not NULL'),
206
    '#default_value' => is_numeric($settings['database']['data_not_NULL']) ? $settings['database']['data_not_NULL'] : FALSE,
207
    '#disabled' => $has_data,
208
  );
209
  $form['database']['data_index'] = array(
210
    '#type' => 'checkbox',
211
    '#title' => t('Index computed values in the database (Does not apply to text or longtext fields.)'),
212
    '#default_value' => is_numeric($settings['database']['data_index']) ? $settings['database']['data_index'] : FALSE,
213
    '#disabled' => $has_data,
214
  );
215
  return $form;
216
}
217

    
218
/**
219
 * #element_validate callback for computed_field_field_settings_form().
220
 */
221
function computed_field_field_settings_form_validate($element, &$form_state) {
222
  $settings = $form_state['values']['field']['settings'];
223
  if ($settings['store']) {
224
    if (empty($settings['database']['data_type'])) {
225
      form_set_error('field][settings][data_type', t('To store this field in the database, please specify a data type.'));
226
    }
227
    if (($settings['database']['data_type'] == 'text' || $settings['database']['data_type'] == 'varchar') && empty($settings['database']['data_length'])) {
228
      form_set_error('field][settings][database][data_length', t('To store this field in the database, please specify the data length.'));
229
    }
230
    if (($settings['database']['data_type'] == 'int' || $settings['database']['data_type'] == 'float') && (!empty($settings['database']['data_default']) && !is_numeric($settings['database']['data_default']))) {
231
      form_set_error('field][settings][database][data_default', t('Your default value should be numeric given your data type.'));
232
    }
233
  }
234
}
235

    
236
/**
237
 * Implements hook_field_load().
238
 */
239
function computed_field_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) {
240
  $settings = $field['settings'];
241
  // Compute field values on load if they aren't stored in the database.
242
  if (!$settings['store']) {
243
    foreach ($entities as $etid => $entity) {
244
      _computed_field_compute_value($entity_type, $entity, $field, $instances, $langcode, $items[$etid]);
245
    }
246
  }
247
}
248

    
249
/**
250
 * Implements hook_field_prepare_view().
251
 */
252
function computed_field_field_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items) {
253
  $settings = $field['settings'];
254
  // Compute field values in case user is "previewing" an entity.
255
  foreach ($entities as $etid => $entity) {
256
    if ((isset($entity->op) && $entity->op == 'Preview') || $settings['recalculate']) {
257
      _computed_field_compute_value($entity_type, $entity, $field, $instances, $langcode, $items[$etid]);
258
    }
259
  }
260
}
261

    
262
/**
263
 * Implements hook_field_insert().
264
 */
265
function computed_field_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) {
266
  _computed_field_compute_value($entity_type, $entity, $field, $instance, $langcode, $items);
267
}
268

    
269
/**
270
 * Implements hook_field_update().
271
 */
272
function computed_field_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) {
273
  _computed_field_compute_value($entity_type, $entity, $field, $instance, $langcode, $items);
274
}
275

    
276
/**
277
 * Implements hook_field_widget_info().
278
 */
279
function computed_field_field_widget_info() {
280
  return array(
281
    'computed' => array(
282
      'label' => t('Computed'),
283
      'field types' => array('computed'),
284
      'behaviors' => array(
285
        'multiple values' => FIELD_BEHAVIOR_CUSTOM,
286
        'default value' => FIELD_BEHAVIOR_NONE,
287
      ),
288
    ),
289
  );
290
}
291

    
292
/**
293
 * Implements hook_field_widget_form().
294
 */
295
function computed_field_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
296

    
297
  // If there are no items yet, add a null item value to avoid
298
  // preview errors when selecting a different language.
299
  if (empty($items)) {
300
    $items[0]['value'] = NULL;
301
  }
302

    
303
  foreach ($items as $item_delta => $item) {
304
    $element[$item_delta]['value'] = array(
305
      '#type' => 'value',
306
      '#tree' => TRUE,
307
      '#default_value' => isset($item['value']) ? $item['value'] : NULL,
308
    );
309
  }
310
  return $element;
311
}
312

    
313
/**
314
 * Implements hook_field_formatter_info().
315
 */
316
function computed_field_field_formatter_info() {
317
  return array(
318
    'computed_field_unsanitized' => array(
319
      'label' => t('Unsanitized'),
320
      'field types' => array('computed'),
321
    ),
322
    'computed_field_plain' => array(
323
      'label' => t('Plain text'),
324
      'field types' => array('computed'),
325
    ),
326
    'computed_field_markup' => array(
327
      'label' => t('Filtered markup'),
328
      'field types' => array('computed'),
329
    ),
330
    'computed_field_computed_value' => array(
331
      'label' => t('Raw value, no display code'),
332
      'field types' => array('computed'),
333
    ),
334
  );
335
}
336

    
337
/**
338
 * Implements hook_field_formatter_view().
339
 */
340
function computed_field_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
341
  $element = array();
342
  $data_to_display = FALSE;
343

    
344
  // Special case formatter that returns the raw computed values without any display code processing.
345
  if ($display['type'] == "computed_field_computed_value") {
346
    foreach ($items as $delta => $item) {
347
      if (!isset($entity_field_item['value'])) {
348
        $entity_field_item['value'] = NULL;
349
      }
350
      $element[$delta] = array('#markup' => $item['value']);
351
    }
352
    return $element;
353
  }
354

    
355
  // Other display formatters which run through display code processing.
356
  // Check if the value is to be formatted by a display function outside the DB.
357
  $display_func = 'computed_field_' . $field['field_name'] . '_display';
358
  $display_in_code = function_exists($display_func) ? TRUE : FALSE;
359

    
360
  // Loop the items to display.
361
  foreach ($items as $delta => $item) {
362

    
363
    // For "some" backwards compatibility.
364
    $entity_field_item = $item;
365

    
366
    // Setup a variable with the entity language if available.
367
    if (isset($entity->language)) {
368
      $entity_lang = $entity->language;
369
    }
370
    else {
371
      $entity_lang = LANGUAGE_NONE;
372
    }
373

    
374
    // If there are value "holes" in the field array let's set the value to NULL.
375
    // to avoid undefined index errors in typical PHP display code.
376
    if (!isset($entity_field_item['value'])) {
377
      $entity_field_item['value'] = NULL;
378
    }
379

    
380
    // Execute the display code.
381
    $display_output = NULL;
382
    if ($display_in_code) {
383
      $display_output = $display_func($field, $entity_field_item, $entity_lang, $langcode, $entity);
384
    }
385
    else {
386
      eval($field['settings']['display_format']);
387
    }
388

    
389
    // Track if any of our items produce non-empty output.
390
    if (!empty($display_output) || is_numeric($display_output)) {
391
      $data_to_display = TRUE;
392
    }
393

    
394
    // Output the formatted display item.
395
    switch ($display['type']) {
396
      case 'computed_field_unsanitized':
397
          $element[$delta] = array('#markup' => $display_output);
398
        break;
399

    
400
      case 'computed_field_plain':
401
          $element[$delta] = array('#markup' => check_plain($display_output));
402
        break;
403

    
404
      case 'computed_field_markup':
405
          $element[$delta] = array('#markup' => check_markup($display_output));
406
        break;
407
    }
408
  }
409
  // If all items are empty then we should not return anything. This helps
410
  // ensure that empty fields are not displayed at all. This check does not
411
  // apply to fields stored in the DB as those are instead checked on save.
412
  if (isset($field['settings']['store']) && !$field['settings']['store'] && !$data_to_display) {
413
    return;
414
  }
415
  return $element;
416
}
417

    
418
/**
419
 * Implements hook_field_is_empty().
420
 */
421
function computed_field_field_is_empty($item, $field) {
422
  unset($empty);
423

    
424
  // This will depend on the class of data type.
425
  switch ($field['settings']['database']['data_type']) {
426

    
427
    case 'int':
428
    case 'float':
429
    case 'numeric':
430
      // For numbers, the field is empty if the value isn't numeric.
431
      $empty = !is_numeric($item['value']);
432
      break;
433

    
434
    case 'varchar':
435
    case 'text':
436
    case 'longtext':
437
      // For strings, the field is empty if it doesn't match the empty string.
438
      $empty = ($item['value'] === "");
439
      break;
440
  }
441
  return $empty;
442
}
443

    
444
/**
445
 * Private function to compute the fields value.
446
 */
447
function _computed_field_compute_value($entity_type, $entity, $field, $instance, $langcode, &$items) {
448
  $settings = $field['settings'];
449

    
450
  // Setup a variable with the field values.
451
  $entity_field =& $items;
452

    
453
  // Setup a variable with the entity language if available.
454
  if (isset($entity->language)) {
455
    $entity_lang = $entity->language;
456
  }
457
  else {
458
    $entity_lang = LANGUAGE_NONE;
459
  }
460

    
461
  // Allow the value to be computed from code not stored in DB.
462
  $compute_func = 'computed_field_' . $field['field_name'] . '_compute';
463
  if (function_exists($compute_func)) {
464
    $compute_func($entity_field, $entity_type, $entity, $field, $instance, $langcode, $items);
465
  }
466
  else {
467
    if (isset($settings['code'])) {
468
      eval($settings['code']);
469
    }
470
  }
471
}