computed_field.module 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. <?php
  2. /**
  3. * Implements field hook_field_info().
  4. */
  5. function computed_field_field_info() {
  6. return array(
  7. 'computed' => array(
  8. 'label' => t('Computed'),
  9. 'description' => t('Create field data via PHP code.'),
  10. 'settings' => array(
  11. 'code' => '$entity_field[0][\'value\'] = "";',
  12. 'display_format' => '$display_output = $entity_field_item[\'value\'];',
  13. 'store' => 1,
  14. 'database' => array(
  15. 'data_type' => 'varchar',
  16. 'data_length' => 32,
  17. 'data_size' => 'normal',
  18. 'data_precision' => 10,
  19. 'data_scale' => 2,
  20. 'data_not_NULL' => FALSE,
  21. 'data_default' => NULL,
  22. 'data_index' => FALSE,
  23. ),
  24. ),
  25. 'default_widget' => 'computed',
  26. 'default_formatter' => 'computed_field_plain',
  27. // If we followed the core convention of separate fields for each data
  28. // type we could make Entity API happy by just setting a property_type.
  29. // Instead we have to use our own callback to determine the type then
  30. // rerun theirs to setup the rest of the field properties.
  31. 'property_callbacks' => array('computed_field_entity_property_callback'),
  32. ),
  33. );
  34. }
  35. /**
  36. * Callback to setup Entity API's field properties.
  37. */
  38. function computed_field_entity_property_callback(&$info, $entity_type, $field, $instance, $field_type) {
  39. $property_types = array(
  40. 'int' => 'integer',
  41. 'float' => 'decimal', 'numeric' => 'decimal',
  42. 'varchar' => 'text', 'text' => 'text', 'longtext' => 'text',
  43. );
  44. if (isset($field['columns']['value']) && isset($property_types[$field['columns']['value']['type']])) {
  45. // Entity API's defaults are pretty good so set the property_type and let
  46. // them do the work for us.
  47. $field_type['property_type'] = $property_types[$field['columns']['value']['type']];
  48. entity_metadata_field_default_property_callback($info, $entity_type, $field, $instance, $field_type);
  49. // The only thing is that a setter doesn't make sense, so let's disable it.
  50. $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']];
  51. unset($property['setter callback']);
  52. }
  53. }
  54. /**
  55. * Implements of hook_field_settings_form().
  56. */
  57. function computed_field_field_settings_form($field, $instance, $has_data) {
  58. $form = array();
  59. $compute_func = 'computed_field_' . $field['field_name'] . '_compute';
  60. $display_func = 'computed_field_' . $field['field_name'] . '_display';
  61. $settings = $field['settings'];
  62. $form['#element_validate'] = array('computed_field_field_settings_form_validate');
  63. $form['code'] = array(
  64. '#type' => 'textarea',
  65. '#rows' => 15,
  66. '#title' => t('Computed Code (PHP)'),
  67. '#description' => t('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><code>@example</code> The first pop fetches the last (or only) item from the field while the second pop fetches its <code>[\'value\']</code> contents (assuming it\'s the only key that\'s set).<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>',
  68. array('@fields' => '&$entity_field, $entity_type, $entity, $field, $instance, $langcode, and $items',
  69. '@entity_field' => '$entity_field[0][\'value\']',
  70. '@entity_field_multi' => '$entity_field[1][\'value\']',
  71. '@field_a' => 'field_a',
  72. '@field_b' => 'field_b',
  73. '@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\')));',
  74. '@compute_func' => $compute_func)),
  75. '#default_value' => !empty($settings['code']) ? $settings['code'] : '$entity_field[0][\'value\'] = "";',
  76. '#access' => !function_exists($compute_func),
  77. );
  78. if (function_exists($compute_func)) {
  79. $form['compute_func'] = array(
  80. '#type' => 'item',
  81. '#markup' => t('<strong>This field is COMPUTED using <code>@compute_func()</code>.</strong>', array('@compute_func' => $compute_func)),
  82. );
  83. }
  84. $form['display_format'] = array(
  85. '#type' => 'textarea',
  86. '#title' => t('Display Code (PHP)'),
  87. '#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)</code>. Return the value to be displayed. Original value is in $entity_field_item[\'value\'].',
  88. array('@display_output' => '$display_output',
  89. '@value' => '$entity_field_item[\'value\']',
  90. '@display_func' => $display_func)),
  91. '#default_value' => !empty($settings['display_format']) ? $settings['display_format'] : '$display_output = $entity_field_item[\'value\'];',
  92. '#access' => !function_exists($display_func),
  93. );
  94. if (function_exists($display_func)) {
  95. $form['display_func'] = array(
  96. '#type' => 'item',
  97. '#markup' => t('<strong>This field is DISPLAYED using <code>@display_func()</code>.</strong>', array('@display_func' => $display_func)),
  98. );
  99. }
  100. $form['store'] = array(
  101. '#type' => 'checkbox',
  102. '#title' => t('Store value in the database'),
  103. '#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.'),
  104. '#default_value' => is_numeric($settings['store']) ? $settings['store'] : 1 ,
  105. '#disabled' => $has_data,
  106. );
  107. $form['database'] = array('#type' => 'fieldset', '#title' => t('Database Storage Settings'));
  108. if ($has_data) {
  109. $form['database']['warning'] = array(
  110. '#type' => 'item',
  111. '#markup' => t('<strong>**This field currently has stored data, so modifications to its DB settings are not allowed.**</strong>'),
  112. );
  113. }
  114. $form['database']['data_type'] = array(
  115. '#type' => 'radios',
  116. '#title' => t('Data Type'),
  117. '#description' => t('The SQL datatype to store this field in.'),
  118. '#default_value' => !empty($settings['database']['data_type']) ? $settings['database']['data_type'] : 'varchar',
  119. '#options' => array('varchar' => 'varchar', 'text' => 'text', 'longtext' => 'longtext', 'int' => 'int', 'float' => 'float', 'numeric' => 'decimal'),
  120. '#required' => FALSE,
  121. '#disabled' => $has_data,
  122. );
  123. $form['database']['data_length'] = array(
  124. '#type' => 'textfield',
  125. '#title' => t('Data Length (varchar/text)'),
  126. '#description' => t('<strong>Only</strong> valid for <strong>varchar</strong> or <strong>text</strong> fields. The length of the field stored in the database.'),
  127. '#default_value' => !empty($settings['database']['data_length']) ? $settings['database']['data_length'] : 32,
  128. '#required' => FALSE,
  129. '#disabled' => $has_data,
  130. );
  131. $form['database']['data_size'] = array(
  132. '#type' => 'select',
  133. '#title' => t('Data Size (int/float)'),
  134. '#description' => t('<strong>Only</strong> valid for <strong>int</strong> or <strong>float</strong> fields. The size of the field stored in the database.'),
  135. '#default_value' => !empty($settings['database']['data_size']) ? $settings['database']['data_size'] : 'normal',
  136. '#options' => array('tiny' => 'tiny', 'small' => 'small', 'medium' => 'medium', 'normal' => 'normal', 'big' => 'big'),
  137. '#required' => FALSE,
  138. '#disabled' => $has_data,
  139. );
  140. $form['database']['data_precision'] = array(
  141. '#type' => 'select',
  142. '#title' => t('Decimal Precision (decimal)'),
  143. '#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.'),
  144. '#options' => drupal_map_assoc(range(10, 32)),
  145. '#default_value' => !empty($settings['database']['data_precision']) ? $settings['database']['data_precision'] : 10,
  146. '#required' => FALSE,
  147. '#disabled' => $has_data,
  148. );
  149. $form['database']['data_scale'] = array(
  150. '#type' => 'select',
  151. '#title' => t('Decimal Scale (decimal)'),
  152. '#description' => t('<strong>Only</strong> valid for <strong>decimal</strong> fields. The number of digits to the right of the decimal. '),
  153. '#options' => drupal_map_assoc(range(0, 10)),
  154. '#default_value' => !empty($settings['database']['data_scale']) ? $settings['database']['data_scale'] : 2,
  155. '#required' => FALSE,
  156. '#disabled' => $has_data,
  157. );
  158. $form['database']['data_default'] = array(
  159. '#type' => 'textfield',
  160. '#title' => t('Default Value'),
  161. '#default_value' => $settings['database']['data_default'],
  162. '#required' => FALSE,
  163. '#disabled' => $has_data,
  164. );
  165. $form['database']['data_not_NULL'] = array(
  166. '#type' => 'checkbox',
  167. '#title' => t('Not NULL'),
  168. '#default_value' => is_numeric($settings['database']['data_not_NULL']) ? $settings['database']['data_not_NULL'] : FALSE,
  169. '#disabled' => $has_data,
  170. );
  171. $form['database']['data_index'] = array(
  172. '#type' => 'checkbox',
  173. '#title' => t('Index computed values in the database (Does not apply to text or longtext fields.)'),
  174. '#default_value' => is_numeric($settings['database']['data_index']) ? $settings['database']['data_index'] : FALSE,
  175. '#disabled' => $has_data,
  176. );
  177. return $form;
  178. }
  179. /**
  180. * Implements the #element_validate callback for computed_field_field_settings_form().
  181. */
  182. function computed_field_field_settings_form_validate($element, &$form_state) {
  183. $settings = $form_state['values']['field']['settings'];
  184. if ($settings['store']) {
  185. if (empty($settings['database']['data_type'])) {
  186. form_set_error('field][settings][data_type', t('To store this field in the database, please specify a data type.'));
  187. }
  188. if (($settings['database']['data_type'] == 'text' || $settings['database']['data_type'] == 'varchar') && empty($settings['database']['data_length'])) {
  189. form_set_error('field][settings][database][data_length', t('To store this field in the database, please specify the data length.'));
  190. }
  191. if (($settings['database']['data_type'] == 'int' || $settings['database']['data_type'] == 'float') && (!empty($settings['database']['data_default']) && !is_numeric($settings['database']['data_default']))) {
  192. form_set_error('field][settings][database][data_default', t('Your default value should be numeric given your data type.'));
  193. }
  194. }
  195. }
  196. /**
  197. * Implements field hook_field_load().
  198. */
  199. function computed_field_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) {
  200. $settings = $field['settings'];
  201. // Compute field values on load if they aren't stored in the database
  202. if (!$settings['store']) {
  203. foreach ($entities as $etid => $entity) {
  204. _computed_field_compute_value($entity_type, $entity, $field, $instances, $langcode, $items[$etid]);
  205. }
  206. }
  207. }
  208. /**
  209. * Implements field hook_field_prepare_view().
  210. */
  211. function computed_field_field_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items) {
  212. // Compute field values in case user is "previewing" an entity
  213. foreach ($entities as $etid => $entity) {
  214. if (isset($entity->op) && $entity->op == 'Preview') {
  215. _computed_field_compute_value($entity_type, $entity, $field, $instances, $langcode, $items[$etid]);
  216. }
  217. }
  218. }
  219. /**
  220. * Implements field hook_field_insert().
  221. */
  222. function computed_field_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) {
  223. _computed_field_compute_value($entity_type, $entity, $field, $instance, $langcode, $items);
  224. }
  225. /**
  226. * Implements field hook_field_update().
  227. */
  228. function computed_field_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) {
  229. _computed_field_compute_value($entity_type, $entity, $field, $instance, $langcode, $items);
  230. }
  231. /**
  232. * Implements field hook_field_widget_info().
  233. */
  234. function computed_field_field_widget_info() {
  235. return array(
  236. 'computed' => array(
  237. 'label' => t('Computed'),
  238. 'field types' => array('computed'),
  239. 'behaviors' => array(
  240. 'multiple values' => FIELD_BEHAVIOR_CUSTOM,
  241. 'default value' => FIELD_BEHAVIOR_NONE,
  242. ),
  243. ),
  244. );
  245. }
  246. /**
  247. * Implements field hook_field_widget_form().
  248. */
  249. function computed_field_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
  250. // If there are no items yet, add a null item value to avoid
  251. // preview errors when selecting a different language
  252. if (empty($items)) $items[0]['value'] = NULL;
  253. foreach ($items as $item_delta => $item) {
  254. $element[$item_delta]['value'] = array(
  255. '#type' => 'value',
  256. '#tree' => TRUE,
  257. '#default_value' => isset($item['value']) ? $item['value'] : NULL,
  258. );
  259. }
  260. return $element;
  261. }
  262. /**
  263. * Implements hook_field_formatter_info().
  264. */
  265. function computed_field_field_formatter_info() {
  266. return array(
  267. 'computed_field_unsanitized' => array(
  268. 'label' => t('Unsanitized'),
  269. 'field types' => array('computed'),
  270. ),
  271. 'computed_field_plain' => array(
  272. 'label' => t('Plain text'),
  273. 'field types' => array('computed'),
  274. ),
  275. 'computed_field_markup' => array(
  276. 'label' => t('Filtered markup'),
  277. 'field types' => array('computed'),
  278. ),
  279. 'computed_field_computed_value' => array(
  280. 'label' => t('Raw value, no display code'),
  281. 'field types' => array('computed'),
  282. ),
  283. );
  284. }
  285. /**
  286. * Implements hook_field_formatter_view().
  287. */
  288. function computed_field_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  289. $element = array();
  290. // Special case formatter that returns the raw computed values without any display code processing
  291. if ($display['type'] == "computed_field_computed_value") {
  292. foreach ($items as $delta => $item) {
  293. if (!isset($entity_field_item['value'])) $entity_field_item['value'] = NULL;
  294. $element[$delta] = array('#markup' => $item['value']);
  295. }
  296. return $element;
  297. }
  298. // Other display formatters which run through display code processing
  299. // Check if the value is to be formatted by a display function outside the DB
  300. $display_func = 'computed_field_' . $field['field_name'] . '_display';
  301. if (function_exists($display_func)) $display_in_code = TRUE;
  302. else $display_in_code = FALSE;
  303. // Loop the items to display
  304. foreach ($items as $delta => $item) {
  305. // For "some" backwards compatibility
  306. $entity_field_item = $item;
  307. // Setup a variable with the entity language if available
  308. if (isset($entity->language)) $entity_lang = $entity->language;
  309. else $entity_lang = LANGUAGE_NONE;
  310. // If there are value "holes" in the field array let's set the value to NULL
  311. // to avoid undefined index errors in typical PHP display code
  312. if (!isset($entity_field_item['value'])) $entity_field_item['value'] = NULL;
  313. // Execute the display code
  314. $display_output = NULL;
  315. if ($display_in_code) {
  316. $display_output = $display_func($field, $entity_field_item, $entity_lang, $langcode);
  317. }
  318. else {
  319. eval($field['settings']['display_format']);
  320. }
  321. // Output the formatted display item
  322. switch ($display['type']) {
  323. case 'computed_field_unsanitized':
  324. $element[$delta] = array('#markup' => $display_output);
  325. break;
  326. case 'computed_field_plain':
  327. $element[$delta] = array('#markup' => check_plain($display_output));
  328. break;
  329. case 'computed_field_markup':
  330. $element[$delta] = array('#markup' => check_markup($display_output));
  331. break;
  332. }
  333. }
  334. return $element;
  335. }
  336. /**
  337. * Implements field hook_field_is_empty().
  338. */
  339. function computed_field_field_is_empty($item, $field) {
  340. $data_type = $field['settings']['database']['data_type'];
  341. if ($data_type == 'int' || $data_type == 'float') {
  342. return !is_numeric($item['value']);
  343. }
  344. return empty($item['value']);
  345. }
  346. /**
  347. * Implements hook_token_info().
  348. */
  349. function computed_field_token_info() {
  350. $types['computed'] = array(
  351. 'name' => t("Computed"),
  352. 'description' => t("Tokens related to Computed fields."),
  353. 'needs-data' => 'node',
  354. );
  355. $fields = field_info_fields();
  356. $node = array();
  357. $computed = array();
  358. foreach ($fields as $field_name => $field) {
  359. if ($field['module'] == "computed_field") {
  360. $node[str_replace('_', '-', $field_name)] = array(
  361. 'name' => t("Computed: %field_name", array('%field_name' => $field_name)),
  362. 'description' => t("Computed field %field_name value.", array('%field_name' => $field_name)),
  363. 'type' => 'computed',
  364. );
  365. $computed['rawvalue'] = array(
  366. 'name' => t("Raw computed value"),
  367. 'description' => t("Computed field %field_name raw value.", array('%field_name' => $field_name)),
  368. );
  369. }
  370. }
  371. if (!empty($computed)) {
  372. return array(
  373. 'types' => $types,
  374. 'tokens' => array('node' => $node, 'computed' => $computed),
  375. );
  376. }
  377. }
  378. /**
  379. * Implements hook_tokens().
  380. */
  381. function computed_field_tokens($type, $tokens, array $data = array(), array $options = array()) {
  382. $computed_fields = array();
  383. $replacements = array();
  384. $sanitize = !empty($options['sanitize']);
  385. $lang = isset($options['language']->language) ? $options['language']->language : LANGUAGE_NONE;
  386. $fields = field_info_fields();
  387. foreach ($fields as $field_name => $field) {
  388. if ($field['module'] == "computed_field") {
  389. $computed_fields[] = str_replace('_', '-', $field_name);
  390. }
  391. }
  392. foreach ($tokens as $name => $original) {
  393. // For normal display output
  394. if (in_array($name, $computed_fields)) {
  395. $field_name = str_replace('-', '_', $name);
  396. $entity_field_item = $data[$type]->{$field_name}[$lang][0];
  397. $field = $fields[$field_name];
  398. // Check if the value is to be formatted by a display function outside the DB
  399. $display_func = 'computed_field_' . $field_name . '_display';
  400. if (function_exists($display_func)) {
  401. $display_output = $display_func($field, $entity_field_item);
  402. }
  403. else {
  404. eval($field['settings']['display_format']);
  405. }
  406. $replacements[$original] = $sanitize ? check_plain($display_output) : $display_output;
  407. }
  408. // For raw value output
  409. elseif (in_array(str_replace(':rawvalue', '', $name), $computed_fields)) {
  410. $field_name = str_replace('-', '_', str_replace(':rawvalue', '', $name));
  411. $replacements[$original] = $sanitize ? check_plain($data[$type]->{$field_name}[$lang][0]['value']) : $data[$type]->{$field_name}[$lang][0]['value'];
  412. }
  413. }
  414. return $replacements;
  415. }
  416. /**
  417. * Private function to compute the fields value.
  418. */
  419. function _computed_field_compute_value($entity_type, $entity, $field, $instance, $langcode, &$items) {
  420. $settings = $field['settings'];
  421. // Setup a variable with the field values
  422. $entity_field =& $items;
  423. // Setup a variable with the entity language if available
  424. if (isset($entity->language)) $entity_lang = $entity->language;
  425. else $entity_lang = LANGUAGE_NONE;
  426. // Allow the value to be computed from code not stored in DB
  427. $compute_func = 'computed_field_' . $field['field_name'] . '_compute';
  428. if (function_exists($compute_func)) {
  429. $compute_func($entity_field, $entity_type, $entity, $field, $instance, $langcode, $items);
  430. }
  431. else {
  432. if (isset($settings['code'])) {
  433. eval($settings['code']);
  434. }
  435. }
  436. }