From a5ca0d75e89a43a90f58a86e3dcdc46e521ede1b Mon Sep 17 00:00:00 2001 From: Frederic G. MARAND Date: Sun, 29 Jul 2012 17:10:47 +0200 Subject: [PATCH] Issue 1514848 by skipyt, fgm: Define the property type when the field is not stored in the database. --- computed_field.module | 365 +++++++++++++++++++++++++++++++++++++------------ 1 files changed, 278 insertions(+), 87 deletions(-) diff --git a/computed_field.module b/computed_field.module index c7bff05..2f79e91 100644 --- a/computed_field.module +++ b/computed_field.module @@ -43,14 +43,120 @@ function computed_field_entity_property_callback(&$info, $entity_type, $field, $ 'float' => 'decimal', 'numeric' => 'decimal', 'varchar' => 'text', 'text' => 'text', 'longtext' => 'text', ); + if (isset($field['columns']['value']) && isset($property_types[$field['columns']['value']['type']])) { // Entity API's defaults are pretty good so set the property_type and let // them do the work for us. $field_type['property_type'] = $property_types[$field['columns']['value']['type']]; - entity_metadata_field_default_property_callback($info, $entity_type, $field, $instance, $field_type); - // The only thing is that a setter doesn't make sense, so let's disable it. - $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']]; - unset($property['setter callback']); + } + else { + $field_type['property_type'] = $property_types[$field['settings']['database']['data_type']]; + } + + entity_metadata_field_default_property_callback($info, $entity_type, $field, $instance, $field_type); + // The only thing is that a setter doesn't make sense, so let's disable it. + $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']]; + + if (!$field['settings']['store']) { + $property['getter callback'] = 'computed_field_getter_callback'; + } + unset($property['setter callback']); +} + +/** + * Computed field getter callback. + * @param $entity + * @param array $options + * @param $name + * @param $entity_type + * @param $info + * @return array|mixed|null + */ +function computed_field_getter_callback($entity, array $options, $name, $entity_type, $info) { + $field = field_info_field($name); + $langcode = isset($options['language']) ? $options['language']->language : LANGUAGE_NONE; + $langcode = entity_metadata_field_get_language($entity_type, $entity, $field, $langcode, TRUE); + $values = array(); + if (isset($entity->{$name}[$langcode])) { + foreach ($entity->{$name}[$langcode] as $delta => $data) { + $values[$delta] = $data['value']; + if ($info['type'] == 'boolean' || $info['type'] == 'list') { + // Ensure that we have a clean boolean data type. + $values[$delta] = (boolean) $values[$delta]; + } + } + } + // For an empty single-valued field, we have to return NULL. + return $field['cardinality'] == 1 ? ($values ? reset($values) : NULL) : $values; +} + +/** + * Implements hook_compute_field(). + */ +function hook_compute_field() { + return array( + 'field_name' => array( + 'compute function' => 'computed_field_compute_value', + 'display function' => 'computed_field_display_value', + ), + ); +} + +/** + * Callback function for the 'compute function' for one computed field. + * Here's a simple example which sets the computed field's value to + * the value of the sum of 2 number fields. + * @param $entity_type + * @param $entity + * @param $field + * @param $instance + * @param $langcode + * @param $items + */ +function computed_field_compute_value($entity_type, $entity, $field, $instance, $langcode, &$items) { + $items[0]['value'] = array_pop(array_pop(field_get_items($entity_type, $entity, 'field_a'))) + + array_pop(array_pop(field_get_items($entity_type, $entity, 'field_b'))); +} + +/** + * Callback function for the 'display function' for one computed field. + * Return the value to be displayed. Original value is in $entity_field_item['value']. + * @param $entity_type + * @param $entity + * @param $field + * @param $instance + * @param $langcode + * @param $items + * @param int $delta + * @return string + */ +function computed_field_display_value($entity_type, $entity, $field, $instance, $langcode, $items, $delta = 0) { + return (string) $items[$delta]['value']; +} + +/** + * Returns the computed field settings. + * + * If field name is provided it will return the settings only for that field. + * + * @param string $field_name + * + * @return array + */ +function computed_field_settings($field_name = '') { + $settings = &drupal_static(__FUNCTION__); + + if (!isset($settings)) { + $settings = module_invoke_all('computed_field'); + drupal_alter('computed_field', $settings); + // $settings may still be null, but if so, it will be array(), not NULL. + } + + if (empty($field_name)) { + return $settings; + } + else { + return isset($settings[$field_name]) ? $settings[$field_name] : NULL; } } @@ -59,49 +165,50 @@ function computed_field_entity_property_callback(&$info, $entity_type, $field, $ */ function computed_field_field_settings_form($field, $instance, $has_data) { $form = array(); - $compute_func = 'computed_field_' . $field['field_name'] . '_compute'; - $display_func = 'computed_field_' . $field['field_name'] . '_display'; + $computed_field_settings = computed_field_settings($field['field_name']); $settings = $field['settings']; $form['#element_validate'] = array('computed_field_field_settings_form_validate'); - $form['code'] = array( - '#type' => 'textarea', - '#rows' => 15, - '#title' => t('Computed Code (PHP)'), - '#description' => t('The variables available to your code include: @fields. To set the value of the field, set @entity_field. For multi-value computed fields continue with @entity_field_multi. Here\'s a simple example which sets the computed field\'s value to the value of the sum of the number fields (@field_a and @field_b) in a node entity:

@example The first pop fetches the last (or only) item from the field while the second pop fetches its [\'value\'] contents (assuming it\'s the only key that\'s set).

Alternately, this code can be supplied by your own custom function named: @compute_func(&$entity_field, $entity_type, $entity, $field, $instance, $langcode, $items)', - array('@fields' => '&$entity_field, $entity_type, $entity, $field, $instance, $langcode, and $items', - '@entity_field' => '$entity_field[0][\'value\']', - '@entity_field_multi' => '$entity_field[1][\'value\']', - '@field_a' => 'field_a', - '@field_b' => 'field_b', - '@example' => '$entity_field[0][\'value\'] = array_pop(array_pop(field_get_items($entity_type, $entity, \'field_a\'))) + array_pop(array_pop(field_get_items($entity_type, $entity, \'field_b\')));', - '@compute_func' => $compute_func)), - '#default_value' => !empty($settings['code']) ? $settings['code'] : '$entity_field[0][\'value\'] = "";', - '#access' => !function_exists($compute_func), - ); - if (function_exists($compute_func)) { + if (empty($computed_field_settings['compute function']) || !function_exists($computed_field_settings['compute function'])) { + $form['code'] = array( + '#type' => 'textarea', + '#rows' => 15, + '#title' => t('Computed Code (PHP)'), + '#description' => t('The variables available to your code include: @fields. To set the value of the field, set @entity_field. For multi-value computed fields continue with @entity_field_multi. Here\'s a simple example which sets the computed field\'s value to the value of the sum of the number fields (@field_a and @field_b) in a node entity:

@example The first pop fetches the last (or only) item from the field while the second pop fetches its [\'value\'] contents (assuming it\'s the only key that\'s set).

Alternately, this code can be supplied by your own custom function specified in hook_computed_field().

', + array('@fields' => '&$entity_field, $entity_type, $entity, $field, $instance, $langcode, and $items', + '@entity_field' => '$entity_field[0][\'value\']', + '@entity_field_multi' => '$entity_field[1][\'value\']', + '@field_a' => 'field_a', + '@field_b' => 'field_b', + '@example' => '$entity_field[0][\'value\'] = array_pop(array_pop(field_get_items($entity_type, $entity, \'field_a\'))) + array_pop(array_pop(field_get_items($entity_type, $entity, \'field_b\')));')), + '#default_value' => !empty($settings['code']) ? $settings['code'] : '$entity_field[0][\'value\'] = "";', + ); + } + else { $form['compute_func'] = array( - '#type' => 'item', - '#markup' => t('This field is COMPUTED using @compute_func().', array('@compute_func' => $compute_func)), + '#type' => 'item', + '#markup' => t('This field is COMPUTED using @compute_func().', array('@compute_func' => $computed_field_settings['compute function'])), ); } - $form['display_format'] = array( - '#type' => 'textarea', - '#title' => t('Display Code (PHP)'), - '#description' => t('This code should assign a string to the @display_output variable, which will be printed when the field is displayed. The raw computed value of the field is in @value. Note: this code has no effect if you use the "Raw computed value" display formatter.

Alternately, this code can be supplied by your own custom function named: @display_func($field, $entity_field_item, $entity_lang, $langcode). Return the value to be displayed. Original value is in $entity_field_item[\'value\'].', - array('@display_output' => '$display_output', - '@value' => '$entity_field_item[\'value\']', - '@display_func' => $display_func)), - '#default_value' => !empty($settings['display_format']) ? $settings['display_format'] : '$display_output = $entity_field_item[\'value\'];', - '#access' => !function_exists($display_func), - ); - if (function_exists($display_func)) { + + if (empty($computed_field_settings['display function']) || !function_exists($computed_field_settings['display function'])) { + $form['display_format'] = array( + '#type' => 'textarea', + '#title' => t('Display Code (PHP)'), + '#description' => t('This code should assign a string to the @display_output variable, which will be printed when the field is displayed. The raw computed value of the field is in @value. Note: this code has no effect if you use the "Raw computed value" display formatter.

Alternately, this code can be supplied by your own custom function specified in hook_computed_field().', + array('@display_output' => '$display_output', + '@value' => '$entity_field_item[\'value\']')), + '#default_value' => !empty($settings['display_format']) ? $settings['display_format'] : '$display_output = $entity_field_item[\'value\'];', + ); + } + else { $form['display_func'] = array( '#type' => 'item', - '#markup' => t('This field is DISPLAYED using @display_func().', array('@display_func' => $display_func)), + '#markup' => t('This field is DISPLAYED using @display_func().', array('@display_func' => $computed_field_settings['display function'])), ); } + $form['store'] = array( '#type' => 'checkbox', '#title' => t('Store value in the database'), @@ -120,10 +227,10 @@ function computed_field_field_settings_form($field, $instance, $has_data) { $form['database']['data_type'] = array( '#type' => 'radios', '#title' => t('Data Type'), - '#description' => t('The SQL datatype to store this field in.'), + '#description' => t('

The SQL datatype to store this field in.

This setting is used also when the field is not stored in the database to set the property type.

'), '#default_value' => !empty($settings['database']['data_type']) ? $settings['database']['data_type'] : 'varchar', '#options' => array('varchar' => 'varchar', 'text' => 'text', 'longtext' => 'longtext', 'int' => 'int', 'float' => 'float', 'numeric' => 'decimal'), - '#required' => FALSE, + '#required' => TRUE, '#disabled' => $has_data, ); $form['database']['data_length'] = array( @@ -314,47 +421,57 @@ function computed_field_field_formatter_view($entity_type, $entity, $field, $ins } return $element; } - - // Other display formatters which run through display code processing - // Check if the value is to be formatted by a display function outside the DB - $display_func = 'computed_field_' . $field['field_name'] . '_display'; - if (function_exists($display_func)) $display_in_code = TRUE; - else $display_in_code = FALSE; - - // Loop the items to display - foreach ($items as $delta => $item) { - - // For "some" backwards compatibility - $entity_field_item = $item; - - // Setup a variable with the entity language if available - if (isset($entity->language)) $entity_lang = $entity->language; - else $entity_lang = LANGUAGE_NONE; - - // If there are value "holes" in the field array let's set the value to NULL - // to avoid undefined index errors in typical PHP display code - if (!isset($entity_field_item['value'])) $entity_field_item['value'] = NULL; - - // Execute the display code - $display_output = NULL; - if ($display_in_code) { - $display_output = $display_func($field, $entity_field_item, $entity_lang, $langcode); + else if ($field['type'] == 'computed' && + in_array($display['type'], array_keys(computed_field_field_formatter_info()))) { + // Other display formatters which run through display code processing + // Check if the value is to be formatted by a display function outside the DB + $computed_field_settings = computed_field_settings($field['field_name']); + if (!empty($computed_field_settings['display function']) && function_exists($computed_field_settings['display function'])) { + $display_in_code = TRUE; + $display_func = $computed_field_settings['display function']; } else { - eval($field['settings']['display_format']); + $display_in_code = FALSE; } - - // Output the formatted display item - switch ($display['type']) { - case 'computed_field_unsanitized': - $element[$delta] = array('#markup' => $display_output); - break; - case 'computed_field_plain': - $element[$delta] = array('#markup' => check_plain($display_output)); - break; - case 'computed_field_markup': - $element[$delta] = array('#markup' => check_markup($display_output)); - break; + // Loop the items to display + foreach ($items as $delta => $item) { + // Execute the display code + $display_output = NULL; + if ($display_in_code) { + $display_output = $display_func($entity_type, $entity, $field, $instance, $langcode, $items, $delta); + } + else { + // For "some" backwards compatibility + $entity_field_item = $item; + + // Setup a variable with the entity language if available + if (isset($entity->language)) { + $entity_lang = $entity->language; + } + else { + $entity_lang = LANGUAGE_NONE; + } + + // If there are value "holes" in the field array let's set the value to NULL + // to avoid undefined index errors in typical PHP display code + if (!isset($entity_field_item['value'])) { + $entity_field_item['value'] = NULL; + } + eval($field['settings']['display_format']); + } + + // Output the formatted display item + switch ($display['type']) { + case 'computed_field_unsanitized': + $element[$delta] = array('#markup' => $display_output); + break; + case 'computed_field_plain': + $element[$delta] = array('#markup' => check_plain($display_output)); + break; + case 'computed_field_markup': + $element[$delta] = array('#markup' => check_markup($display_output)); + break; + } } } return $element; @@ -372,27 +489,101 @@ function computed_field_field_is_empty($item, $field) { } /** + * Implements hook_token_info(). + */ +function computed_field_token_info() { + $types['computed'] = array( + 'name' => t("Computed"), + 'description' => t("Tokens related to Computed fields."), + 'needs-data' => 'node', + ); + $fields = field_info_fields(); + $node = array(); + $computed = array(); + foreach ($fields as $field_name => $field) { + if ($field['module'] == "computed_field") { + $node[str_replace('_', '-', $field_name)] = array( + 'name' => t("Computed: %field_name", array('%field_name' => $field_name)), + 'description' => t("Computed field %field_name value.", array('%field_name' => $field_name)), + 'type' => 'computed', + ); + $computed['rawvalue'] = array( + 'name' => t("Raw computed value"), + 'description' => t("Computed field %field_name raw value.", array('%field_name' => $field_name)), + ); + } + } + if (!empty($computed)) { + return array( + 'types' => $types, + 'tokens' => array('node' => $node, 'computed' => $computed), + ); + } +} + +/** + * Implements hook_tokens(). + */ +function computed_field_tokens($type, $tokens, array $data = array(), array $options = array()) { + $computed_fields = array(); + $replacements = array(); + $sanitize = !empty($options['sanitize']); + $lang = isset($options['language']->language) ? $options['language']->language : LANGUAGE_NONE; + $fields = field_info_fields(); + foreach ($fields as $field_name => $field) { + if ($field['module'] == "computed_field") { + $computed_fields[] = str_replace('_', '-', $field_name); + } + } + foreach ($tokens as $name => $original) { + // For normal display output + if (in_array($name, $computed_fields)) { + $field_name = str_replace('-', '_', $name); + $entity_field_item = $data[$type]->{$field_name}[$lang][0]; + $field = $fields[$field_name]; + // Check if the value is to be formatted by a display function outside the DB + $display_func = 'computed_field_' . $field_name . '_display'; + if (function_exists($display_func)) { + $display_output = $display_func($field, $entity_field_item); + } + else { + eval($field['settings']['display_format']); + } + $replacements[$original] = $sanitize ? check_plain($display_output) : $display_output; + } + // For raw value output + elseif (in_array(str_replace(':rawvalue', '', $name), $computed_fields)) { + $field_name = str_replace('-', '_', str_replace(':rawvalue', '', $name)); + $replacements[$original] = $sanitize ? check_plain($data[$type]->{$field_name}[$lang][0]['value']) : $data[$type]->{$field_name}[$lang][0]['value']; + } + } + return $replacements; +} + +/** * Private function to compute the fields value. */ function _computed_field_compute_value($entity_type, $entity, $field, $instance, $langcode, &$items) { $settings = $field['settings']; - - // Setup a variable with the field values - $entity_field =& $items; - - // Setup a variable with the entity language if available - if (isset($entity->language)) $entity_lang = $entity->language; - else $entity_lang = LANGUAGE_NONE; + $computed_field_settings = computed_field_settings($field['field_name']); // Allow the value to be computed from code not stored in DB - $compute_func = 'computed_field_' . $field['field_name'] . '_compute'; - if (function_exists($compute_func)) { - $compute_func($entity_field, $entity_type, $entity, $field, $instance, $langcode, $items); + $compute_function = isset($computed_field_settings['compute function']) ? $computed_field_settings['compute function'] : FALSE; + if (!empty($compute_function) && function_exists($compute_function)) { + $compute_function($entity_type, $entity, $field, $instance, $langcode, $items); } else { if (isset($settings['code'])) { + // Setup a variable with the field values + $entity_field = &$items; + // Setup a variable with the entity language if available + if (isset($entity->language)) { + $entity_lang = $entity->language; + } + else { + $entity_lang = LANGUAGE_NONE; + } eval($settings['code']); } } } - -- 1.7.4.1