fields.inc 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. <?php
  2. /**
  3. * @file
  4. * Extend core fields with some helper functions to reduce code complexity within views and ctools plugins.
  5. */
  6. /**
  7. * Fake an instance of a field.
  8. *
  9. * @param $field_name
  10. * The unique name for this field no matter what entity/bundle it may be used on.
  11. * @param $view_mode
  12. * We're building a new view mode for this function. Defaults to ctools, but we expect developers to actually name this something meaningful.
  13. * @param $formatter
  14. * The formatter key selected from the options provided by field_ui_formatter_options().
  15. * @param $formatter_settings
  16. * An array of key value pairs. These will be used as #default_value for the form elements generated by a call to hook_field_formatter_settings_form() for this field type.
  17. * Typically we'll pass an empty array to begin with and then pass this information back to ourselves on form submit so that we can set the values for later edit sessions.
  18. */
  19. function ctools_fields_fake_field_instance($field_name, $view_mode = 'ctools', $formatter, $formatter_settings) {
  20. $field = field_read_field($field_name);
  21. $field_type = field_info_field_types($field['type']);
  22. return array(
  23. // Build a fake entity type and bundle.
  24. 'field_name' => $field_name,
  25. 'entity_type' => 'ctools',
  26. 'bundle' => 'ctools',
  27. // Use the default field settings for settings and widget.
  28. 'settings' => field_info_instance_settings($field['type']),
  29. 'widget' => array(
  30. 'type' => $field_type['default_widget'],
  31. 'settings' => array(),
  32. ),
  33. // Build a dummy display mode.
  34. 'display' => array(
  35. $view_mode => array(
  36. 'type' => $formatter,
  37. 'settings' => $formatter_settings,
  38. ),
  39. ),
  40. // Set the other fields to their default values.
  41. // @see _field_write_instance().
  42. 'required' => FALSE,
  43. 'label' => $field_name,
  44. 'description' => '',
  45. 'deleted' => 0,
  46. );
  47. }
  48. /**
  49. * Helper function for calling hook_field_formatter_settings_form() without needing to load an instance of the field.
  50. *
  51. * @param $field
  52. * A fully loaded field.
  53. * @param $formatter_type
  54. * The formatter key selected from the options provided by field_ui_formatter_options().
  55. * @param $form
  56. * The full form from the function that's calling this function.
  57. * @param $form_state
  58. * The full form_state from the function that's calling this function.
  59. * @param $view_mode
  60. * We're passing a view mode from this function to the fake instance we're creating. Defaults to ctools, but we expect developers to actually name this something meaningful.
  61. */
  62. function ctools_fields_get_field_formatter_settings_form($field, $formatter_type, &$form, $form_state, $view_mode = 'ctools') {
  63. $conf = $form_state['conf'];
  64. $formatter = field_info_formatter_types($formatter_type);
  65. if (isset($formatter['settings'])) {
  66. $conf['formatter_settings'] += $formatter['settings'];
  67. }
  68. $function = $formatter['module'] . '_field_formatter_settings_form';
  69. $instance = ctools_fields_fake_field_instance($field['field_name'], $view_mode, $formatter_type, $conf['formatter_settings']);
  70. if (function_exists($function)) {
  71. $settings_form = $function($field, $instance, $view_mode, $form, $form_state);
  72. }
  73. if (empty($settings_form)) {
  74. $settings_form = array();
  75. }
  76. // Allow other modules to alter the formatter settings form.
  77. $context = array(
  78. 'module' => $formatter['module'],
  79. 'formatter' => $formatter,
  80. 'field' => $field,
  81. 'instance' => $instance,
  82. 'view_mode' => $view_mode,
  83. 'form' => $form,
  84. 'form_state' => $form_state,
  85. );
  86. drupal_alter('field_formatter_settings_form', $settings_form, $context);
  87. $settings_form['#tree'] = TRUE;
  88. $form['ctools_field_list']['#value'][] = $field;
  89. $form += $settings_form;
  90. if (isset($field['cardinality']) && $field['cardinality'] != 1) {
  91. list($prefix, $suffix) = explode('@count', t('Skip the first @count item(s)'));
  92. $form['delta_offset'] = array(
  93. '#type' => 'textfield',
  94. '#size' => 5,
  95. '#field_prefix' => $prefix,
  96. '#field_suffix' => $suffix,
  97. '#default_value' => isset($conf['delta_offset']) ? $conf['delta_offset'] : 0,
  98. );
  99. list($prefix, $suffix) = explode('@count', t('Then display at most @count item(s)'));
  100. $form['delta_limit'] = array(
  101. '#type' => 'textfield',
  102. '#size' => 5,
  103. '#field_prefix' => $prefix,
  104. '#field_suffix' => $suffix,
  105. '#description' => t('Enter 0 to display all items.'),
  106. '#default_value' => isset($conf['delta_limit']) ? $conf['delta_limit'] : 0,
  107. );
  108. $form['delta_reversed'] = array(
  109. '#title' => t('Display in reverse order'),
  110. '#type' => 'checkbox',
  111. '#default_value' => !empty($conf['delta_reversed']),
  112. '#description' => t('(start from last values)'),
  113. );
  114. }
  115. }
  116. /**
  117. * Helper function for generating all the formatter information associated with
  118. * any fields.
  119. * Especially useful for determining the fields that will be added to form that
  120. * executes hook_field_formatter_settings_form().
  121. *
  122. * @param $fields
  123. * An array of fully loaded fields.
  124. */
  125. function ctools_fields_get_field_formatter_info($fields) {
  126. $info = array();
  127. $field_info = field_info_formatter_types();
  128. foreach ($fields as $field) {
  129. foreach ($field_info as $format_name => $formatter_info) {
  130. if (in_array($field['type'], $formatter_info['field types'])) {
  131. $info += array($format_name => $formatter_info);
  132. }
  133. }
  134. }
  135. return $info;
  136. }
  137. /**
  138. * Returns the label of a certain field.
  139. *
  140. * Cribbed from Views.
  141. */
  142. function ctools_field_label($field_name) {
  143. $label_counter = array();
  144. // Count the amount of instances per label per field.
  145. $instances = field_info_instances();
  146. foreach ($instances as $entity_type) {
  147. foreach ($entity_type as $bundle) {
  148. if (isset($bundle[$field_name])) {
  149. $label_counter[$bundle[$field_name]['label']] = isset($label_counter[$bundle[$field_name]['label']]) ? ++$label_counter[$bundle[$field_name]['label']] : 1;
  150. }
  151. }
  152. }
  153. if (empty($label_counter)) {
  154. return $field_name;
  155. }
  156. // Sort the field lables by it most used label and return the most used one.
  157. arsort($label_counter);
  158. $label_counter = array_keys($label_counter);
  159. return $label_counter[0];
  160. }
  161. /**
  162. * Replacement for core _field_invoke() to invoke on a single field.
  163. *
  164. * Core only allows invoking field hooks via a private function for all fields
  165. * on an entire entity. However, we very often need to invoke our hooks on
  166. * a single field as we take things apart and only use little bits.
  167. *
  168. * @param $field_name
  169. * Either a field instance object or the name of the field.
  170. * If the 'field' key is populated it will be used as the field
  171. * settings.
  172. * @param $op
  173. * Possible operations include:
  174. * - form
  175. * - validate
  176. * - presave
  177. * - insert
  178. * - update
  179. * - delete
  180. * - delete revision
  181. * - view
  182. * - prepare translation
  183. * @param $entity_type
  184. * The type of $entity; e.g. 'node' or 'user'.
  185. * @param $entity
  186. * The fully formed $entity_type entity.
  187. * @param $a
  188. * - The $form in the 'form' operation.
  189. * - The value of $view_mode in the 'view' operation.
  190. * - Otherwise NULL.
  191. * @param $b
  192. * - The $form_state in the 'submit' operation.
  193. * - Otherwise NULL.
  194. * @param $options
  195. * An associative array of additional options, with the following keys:
  196. * - 'field_name': The name of the field whose operation should be
  197. * invoked. By default, the operation is invoked on all the fields
  198. * in the entity's bundle. NOTE: This option is not compatible with
  199. * the 'deleted' option; the 'field_id' option should be used
  200. * instead.
  201. * - 'field_id': The id of the field whose operation should be
  202. * invoked. By default, the operation is invoked on all the fields
  203. * in the entity's' bundles.
  204. * - 'default': A boolean value, specifying which implementation of
  205. * the operation should be invoked.
  206. * - if FALSE (default), the field types implementation of the operation
  207. * will be invoked (hook_field_[op])
  208. * - If TRUE, the default field implementation of the field operation
  209. * will be invoked (field_default_[op])
  210. * Internal use only. Do not explicitely set to TRUE, but use
  211. * _field_invoke_default() instead.
  212. * - 'deleted': If TRUE, the function will operate on deleted fields
  213. * as well as non-deleted fields. If unset or FALSE, only
  214. * non-deleted fields are operated on.
  215. * - 'language': A language code or an array of language codes keyed by field
  216. * name. It will be used to narrow down to a single value the available
  217. * languages to act on.
  218. *
  219. * @see _field_invoke()
  220. */
  221. function ctools_field_invoke_field($field_name, $op, $entity_type, $entity, &$a = NULL, &$b = NULL, $options = array()) {
  222. if (is_array($field_name)) {
  223. $instance = $field_name;
  224. $field = empty($field_name['field']) ? field_info_field($instance['field_name']) : $field_name['field'];
  225. $field_name = $instance['field_name'];
  226. }
  227. else {
  228. list(, , $bundle) = entity_extract_ids($entity_type, $entity);
  229. $instance = field_info_instance($entity_type, $field_name, $bundle);
  230. }
  231. if (empty($instance)) {
  232. return;
  233. }
  234. // Merge default options.
  235. $default_options = array(
  236. 'default' => FALSE,
  237. 'deleted' => FALSE,
  238. 'language' => NULL,
  239. );
  240. $options += $default_options;
  241. $return = array();
  242. // Everything from here is unmodified code from _field_invoke() formerly
  243. // inside a foreach loop over the instances.
  244. $function = $options['default'] ? 'field_default_' . $op : $field['module'] . '_field_' . $op;
  245. if (function_exists($function)) {
  246. // Determine the list of languages to iterate on.
  247. $available_languages = field_available_languages($entity_type, $field);
  248. $languages = _field_language_suggestion($available_languages, $options['language'], $field_name);
  249. foreach ($languages as $langcode) {
  250. $items = isset($entity->{$field_name}[$langcode]) ? $entity->{$field_name}[$langcode] : array();
  251. $result = $function($entity_type, $entity, $field, $instance, $langcode, $items, $a, $b);
  252. if (isset($result)) {
  253. // For hooks with array results, we merge results together.
  254. // For hooks with scalar results, we collect results in an array.
  255. if (is_array($result)) {
  256. $return = array_merge($return, $result);
  257. }
  258. else {
  259. $return[] = $result;
  260. }
  261. }
  262. // Populate $items back in the field values, but avoid replacing missing
  263. // fields with an empty array (those are not equivalent on update).
  264. if ($items !== array() || isset($entity->{$field_name}[$langcode])) {
  265. $entity->{$field_name}[$langcode] = $items;
  266. }
  267. }
  268. }
  269. return $return;
  270. }
  271. /**
  272. * Replacement for core _field_invoke_default() to invoke on a single field.
  273. *
  274. * @see ctools_field_invoke_field()
  275. * @see _field_invoke_default()
  276. */
  277. function ctools_field_invoke_field_default($field_name, $op, $entity_type, $entity, &$a = NULL, &$b = NULL, $options = array()) {
  278. $options['default'] = TRUE;
  279. return ctools_field_invoke_field($field_name, $op, $entity_type, $entity, $a, $b, $options);
  280. }
  281. /**
  282. * Returns a list of field definitions of a specified type.
  283. *
  284. * @param string $field_type
  285. * A field type name; e.g. 'text' or 'date'.
  286. *
  287. * @return array
  288. * An array of field definitions of the specified type, keyed by field name.
  289. */
  290. function ctools_fields_get_fields_by_type($field_type) {
  291. $fields = array();
  292. foreach (field_info_fields() as $field_name => $field_info) {
  293. if ($field_info['type'] == $field_type) {
  294. $fields[$field_name] = $field_info;
  295. }
  296. }
  297. return $fields;
  298. }
  299. /**
  300. * Derive the foreign keys that a field provides.
  301. *
  302. * @param $field_name
  303. * The name of the field.
  304. *
  305. * @return
  306. * An array of foreign keys according to Schema API.
  307. */
  308. function ctools_field_foreign_keys($field_name) {
  309. $foreign_keys = &drupal_static(__FUNCTION__, array());
  310. if (!isset($foreign_keys[$field_name])) {
  311. $foreign_keys[$field_name] = array();
  312. $field = field_info_field($field_name);
  313. if (!empty($field['foreign keys'])) {
  314. $foreign_keys[$field_name] = $field['foreign keys'];
  315. }
  316. else {
  317. // Try to fetch foreign keys from schema, as not everything
  318. // stores foreign keys properly in the field info.
  319. $module = $field['module'];
  320. module_load_install($module);
  321. $schema = module_invoke($module, 'field_schema', $field);
  322. if (!empty($schema['foreign keys'])) {
  323. $foreign_keys[$field_name] = $schema['foreign keys'];
  324. }
  325. }
  326. }
  327. return $foreign_keys[$field_name];
  328. }