From 607e9626d5af265b18e8319b156bb0fda3445cd4 Mon Sep 17 00:00:00 2001
From: Dries Buytaert
' .t('The Field API provides no user interface on its own. Use the Content Construction Kit (CCK) contrib module to manage custom fields via a web browser.') . '
'; + return $output; + } +} + +/** + * Implementation of hook_init(). + * + * TODO D7: Determine which functions need to always be "loaded", and + * put autoloaders for them into field.autoload.inc. Also figure out + * how to make this work during installation. + */ +function field_init() { + module_load_include('inc', 'field', 'field.crud'); + module_load_include('inc', 'field', 'field.autoload'); +} + +/** + * Implementation of hook_menu(). + */ +function field_menu() { + $items = array(); + + // Callback for AHAH add more buttons. + $items['field/js_add_more'] = array( + 'page callback' => 'field_add_more_js', + 'access arguments' => array('access content'), + 'type' => MENU_CALLBACK, + ); + + return $items; +} + +/** + * Implementation of hook elements(). + */ +function field_elements() { + return array( + 'field' => array(), + ); +} + +/** + * Implementation of hook_theme(). + */ +function field_theme() { + $path = drupal_get_path('module', 'field') . '/theme'; + + return array( + 'field' => array( + 'template' => 'field', + 'arguments' => array('element' => NULL), + 'path' => $path, + ), + // TODO D7 : do we need exclude in core? See [#367215]. + // This is just adding '#post_render' => array('field_wrapper_post_render') + // at the right places in the render array generated by field_default_view(). + // Can be done in hook_field_attach_post_view if we want. + 'field_exclude' => array( + 'arguments' => array('content' => NULL, 'object' => array(), 'context' => NULL), + ), + 'field_multiple_value_form' => array( + 'arguments' => array('element' => NULL), + ), + ); +} + +/** + * Implementation of hook_modules_installed(). + */ +function field_modules_installed($modules) { + field_cache_clear(); +} + +/** + * Implementation of hook_modules_uninstalled(). + */ +function field_modules_uninstalled($modules) { + module_load_include('inc', 'field', 'field.crud'); + foreach ($modules as $module) { + // TODO D7: field_module_delete is not yet implemented + // field_module_delete($module); + } +} + +/** + * Implementation of hook_modules_enabled(). + */ +function field_modules_enabled($modules) { + foreach ($modules as $module) { + field_associate_fields($module); + } + field_cache_clear(); +} + +/** + * Implementation of hook_modules_disabled(). + */ +function field_modules_disabled($modules) { + foreach ($modules as $module) { + db_update('field_config') + ->fields(array('active' => 0)) + ->condition('module', $module) + ->execute(); + db_update('field_config_instance') + ->fields(array('widget_active' => 0)) + ->condition('widget_module', $module) + ->execute(); + field_cache_clear(TRUE); + } +} + +/** + * Allows a module to update the database for fields and columns it controls. + * + * @param string $module + * The name of the module to update on. + */ +function field_associate_fields($module) { + $module_fields = module_invoke($module, 'field_info'); + if ($module_fields) { + foreach ($module_fields as $name => $field_info) { + watchdog('field', 'Updating field type %type with module %module.', array('%type' => $name, '%module' => $module)); + db_update('field_config') + ->fields(array('module' => $module, 'active' => 1)) + ->condition('type', $name) + ->execute(); + } + } + $module_widgets = module_invoke($module, 'widget_info'); + if ($module_widgets) { + foreach ($module_widgets as $name => $widget_info) { + watchdog('field', 'Updating widget type %type with module %module.', array('%type' => $name, '%module' => $module)); + db_update('field_config_instance') + ->fields(array('widget_module' => $module, 'widget_active' => 1)) + ->condition('widget_type', $name) + ->execute(); + } + } +} + +/** + * Helper function to filter out empty values. + * + * On order to keep marker rows in the database, the function ensures + * that the right number of 'all columns NULL' values is kept. + * + * @param array $field + * @param array $items + * @return array + * returns filtered and adjusted item array + * + * TODO D7: poorly named... + */ +function field_set_empty($field, $items) { + // Filter out empty values. + $filtered = array(); + $function = $field['module'] . '_field_is_empty'; + foreach ((array) $items as $delta => $item) { + if (!$function($item, $field)) { + $filtered[] = $item; + } + } + return $filtered; +} + +/** + * Helper function to sort items in a field according to + * user drag-n-drop reordering. + */ +function _field_sort_items($field, $items) { + if (($field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED) && isset($items[0]['_weight'])) { + usort($items, '_field_sort_items_helper'); + foreach ($items as $delta => $item) { + if (is_array($items[$delta])) { + unset($items[$delta]['_weight']); + } + } + } + return $items; +} + +/** + * Sort function for items order. + * (copied form element_sort(), which acts on #weight keys) + */ +function _field_sort_items_helper($a, $b) { + $a_weight = (is_array($a) && isset($a['_weight'])) ? $a['_weight'] : 0; + $b_weight = (is_array($b) && isset($b['_weight'])) ? $b['_weight'] : 0; + if ($a_weight == $b_weight) { + return 0; + } + return ($a_weight < $b_weight) ? -1 : 1; +} + +/** + * Same as above, using ['_weight']['#value'] + */ +function _field_sort_items_value_helper($a, $b) { + $a_weight = (is_array($a) && isset($a['_weight']['#value'])) ? $a['_weight']['#value'] : 0; + $b_weight = (is_array($b) && isset($b['_weight']['#value'])) ? $b['_weight']['#value'] : 0; + if ($a_weight == $b_weight) { + return 0; + } + return ($a_weight < $b_weight) ? -1 : 1; +} + +/** + * Registry of available build modes. + * TODO : move into hook_fieldable_info() ? + */ +function field_build_modes($obj_type) { + static $info; + + if (!isset($info[$obj_type])) { + // module_invoke_all messes numeric keys. + // TODO : revisit when we move away from numeric build modes. + $info[$obj_type] = array(); + foreach (module_implements('field_build_modes') as $module) { + $info[$obj_type] += module_invoke($module, 'field_build_modes', $obj_type); + } + } + return $info[$obj_type]; +} + +/** + * Clear the cached information; called in several places when field + * information is changed. + */ +function field_cache_clear($rebuild_schema = FALSE) { + cache_clear_all('*', 'cache_field', TRUE); + + module_load_include('inc', 'field', 'field.info'); + _field_info_collate_types(TRUE); + _field_info_collate_fields(TRUE); + + // Refresh the schema to pick up new information. + // TODO : if db storage gets abstracted out, we'll need to revisit how and when + // we refresh the schema... + if ($rebuild_schema) { + $schema = drupal_get_schema(NULL, TRUE); + } +} + +/** + * Like filter_xss_admin(), but with a shorter list of allowed tags. + * + * Used for items entered by administrators, like field descriptions, + * allowed values, where some (mainly inline) mark-up may be desired + * (so check_plain() is not acceptable). + */ +function field_filter_xss($string) { + return filter_xss($string, _field_filter_xss_allowed_tags()); +} + +/** + * List of tags allowed by field_filter_xss(). + */ +function _field_filter_xss_allowed_tags() { + return array('a', 'b', 'big', 'code', 'del', 'em', 'i', 'ins', 'pre', 'q', 'small', 'span', 'strong', 'sub', 'sup', 'tt', 'ol', 'ul', 'li', 'p', 'br', 'img'); +} + +/** + * Human-readable list of allowed tags, for display in help texts. + */ +function _field_filter_xss_display_allowed_tags() { + return '<' . implode('> <', _field_filter_xss_allowed_tags()) . '>'; +} + +/** + * Format a field item for display. + * + * TODO D7 : do we still need field_format ? + * - backwards compatibility of templates - check what fallbacks we can propose... + * - used by Views integration in CCK D6 + * At least needs a little rehaul/update... + * + * Used to display a field's values outside the context of the $node, as + * when fields are displayed in Views, or to display a field in a template + * using a different formatter than the one set up on the Display Fields tab + * for the node's context. + * + * @param $field + * Either a field array or the name of the field. + * @param $item + * The field item(s) to be formatted (such as $node->field_foo[0], + * or $node->field_foo if the formatter handles multiple values itself) + * @param $formatter_name + * The name of the formatter to use. + * @param $node + * Optionally, the containing node object for context purposes and + * field-instance options. + * + * @return + * A string containing the contents of the field item(s) sanitized for display. + * It will have been passed through the necessary check_plain() or check_markup() + * functions as necessary. + */ +function field_format($obj_type, $object, $field, $item, $formatter_name = NULL, $formatter_settings = array()) { + if (!is_array($field)) { + $field = field_info_field($field); + } + + if (field_access('view', $field)) { + // Basically, we need $field, $instance, $obj_type, $object to be able to display a value... + list(, , $bundle) = field_attach_extract_ids($obj_type, $object); + $instance = field_info_instance($field['field_name'], $bundle); + + $display = array( + 'type' => $formatter_name, + 'settings' => $formatter_settings, + ); + $display = _field_get_formatter($display, $field); + if ($display['type'] && $display['type'] !== 'hidden') { + $theme = $formatter['module'] . '_formatter_' . $display['type']; + + $element = array( + '#theme' => $theme, + '#field_name' => $field['field_name'], + '#bundle' => $bundle, + '#formatter' => $display['type'], + '#settings' => $display['settings'], + '#object' => $object, + '#delta' => isset($item['#delta']) ? $item['#delta'] : NULL, + ); + + if (field_behaviors_formatter('multiple values', $display) == FIELD_BEHAVIOR_DEFAULT) { + // Single value formatter. + + // hook_field('sanitize') expects an array of items, so we build one. + $items = array($item); + $function = $field['module'] . '_field_sanitize'; + if (function_exists($function)) { + $function($obj_type, $object, $field, $instance, $items); + } + + $element['#item'] = $items[0]; + } + else { + // Multiple values formatter. + $items = $item; + $function = $field['module'] . '_field_sanitize'; + if (function_exists($function)) { + $function($obj_type, $object, $field, $instance, $items); + } + + foreach ($items as $delta => $item) { + $element[$delta] = array( + '#item' => $item, + '#weight' => $delta, + ); + } + } + + return theme($theme, $element); + } + } +} + +/** + * Render a single field, fully themed with label and multiple values. + * + * To be used by third-party code (Views, Panels...) that needs to output + * an isolated field. Do *not* use inside node templates, use the + * $FIELD_NAME_rendered variables instead. + * + * By default, the field is displayed using the settings defined for the + * 'full' or 'teaser' contexts (depending on the value of the $teaser param). + * Set $node->build_mode to a different value to use a different context. + * + * Different settings can be specified by adjusting $field['display']. + * + * @param $field + * The field definition. + * @param $object + * The object containing the field to display. Must at least contain the id key, + * revision key (if applicable), bundle key, and the field data. + * @param $teaser + * Similar to hook_nodeapi('view') + * @return + * The themed output for the field. + */ +function field_view_field($obj_type, $object, $field, $instance, $teaser = FALSE) { + $output = ''; + if (isset($object->$field['field_name'])) { + $items = $object->$field['field_name']; + + // Use 'full'/'teaser' if not specified otherwise. + $object->build_mode = isset($object->build_mode) ? $object->build_mode : NODE_BUILD_NORMAL; + + // One-field equivalent to _field_invoke('sanitize'). + $function = $field['module'] . '_field_sanitize'; + if (drupal_function_exists($function)) { + $function($obj_type, $object, $field, $instance, $items); + $object->$field['field_name'] = $items; + } + + $view = field_default_view($obj_type, $object, $field, $instance, $items, $teaser); + // TODO : what about hook_field_attach_view ? + + // field_default_view() adds a wrapper to handle variables and 'excluded' + // fields for node templates. We bypass it and render the actual field. + $output = drupal_render($view[$field['field_name']]['field']); + } + return $output; +} + +/** + * Determine whether the user has access to a given field. + * + * @param $op + * The operation to be performed. Possible values: + * - "edit" + * - "view" + * @param $field + * The field on which the operation is to be performed. + * @param $account + * (optional) The account to check, if not given use currently logged in user. + * @return + * TRUE if the operation is allowed; + * FALSE if the operation is denied. + */ +function field_access($op, $field, $account = NULL) { + global $user; + + if (is_null($account)) { + $account = $user; + } + + $field_access = module_invoke_all('field_access', $op, $field, $account); + foreach ($field_access as $value) { + if ($value === FALSE) { + return FALSE; + } + } + return TRUE; +} + +/** + * Theme preprocess function for field.tpl.php. + * + * The $variables array contains the following arguments: + * - $object + * - $field + * - $items + * - $teaser + * - $page + * + * @see field.tpl.php + */ +function template_preprocess_field(&$variables) { + $element = $variables['element']; + list(, , $bundle) = field_attach_extract_ids($element['#object_type'], $element['#object']); + $instance = field_info_instance($element['#field_name'], $bundle); + $field = field_info_field($element['#field_name']); + + $variables['object'] = $element['#object']; + $variables['field'] = $field; + $variables['instance'] = $instance; + $variables['items'] = array(); + + if ($element['#single']) { + // Single value formatter. + foreach (element_children($element['items']) as $delta) { + $variables['items'][$delta] = $element['items'][$delta]['#item']; + // Use isset() to avoid undefined index message on #children when field values are empty. + $variables['items'][$delta]['view'] = isset($element['items'][$delta]['#children']) ? $element['items'][$delta]['#children'] : ''; + } + } + else { + // Multiple values formatter. + // We display the 'all items' output as $items[0], as if it was the + // output of a single valued field. + // Raw values are still exposed for all items. + foreach (element_children($element['items']) as $delta) { + $variables['items'][$delta] = $element['items'][$delta]['#item']; + } + $variables['items'][0]['view'] = $element['items']['#children']; + } + + $variables['teaser'] = $element['#teaser']; + $variables['page'] = (bool)menu_get_object(); + + $field_empty = TRUE; + + foreach ($variables['items'] as $delta => $item) { + if (!isset($item['view']) || (empty($item['view']) && (string)$item['view'] !== '0')) { + $variables['items'][$delta]['empty'] = TRUE; + } + else { + $field_empty = FALSE; + $variables['items'][$delta]['empty'] = FALSE; + } + } + + $additions = array( + 'field_type' => $field['type'], + 'field_name' => $field['field_name'], + 'field_type_css' => strtr($field['type'], '_', '-'), + 'field_name_css' => strtr($field['field_name'], '_', '-'), + 'label' => check_plain(t($instance['label'])), + 'label_display' => $element['#label_display'], + 'field_empty' => $field_empty, + 'template_files' => array( + 'field', + 'field-' . $element['#field_name'], + 'field-' . $bundle, + 'field-' . $element['#field_name'] . '-' . $bundle, + ), + ); + $variables = array_merge($variables, $additions); +} + +/** + * @} End of "defgroup field" + */ \ No newline at end of file -- cgit v1.2.3