diff --git a/cck.info b/cck.info new file mode 100644 index 0000000000000000000000000000000000000000..7c0b6758ca93aa6d24c49f56e6be2034f90e6506 --- /dev/null +++ b/cck.info @@ -0,0 +1,12 @@ +; $Id$ +name = CCK Field UI +description = Add custom fields to users or content types using a User Interface. +package = CCK +core = 7.x +files[] = cck.module +files[] = cck.install +files[] = includes/cck.admin.inc +files[] = includes/cck.text.inc +files[] = includes/cck.number.inc +files[] = includes/cck.list.inc +files[] = theme/theme.inc \ No newline at end of file diff --git a/cck.install b/cck.install new file mode 100644 index 0000000000000000000000000000000000000000..5e2df5707fc267e022f5cdabe41454b838ebd376 --- /dev/null +++ b/cck.install @@ -0,0 +1,98 @@ + array( + 'field_name' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'description' => 'The name of the field.', + ), + 'bundle' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => FALSE, + 'description' => 'The name of the bundle, NULL for field settings.', + ), + 'setting_type' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'description' => 'The type of setting that CCK is managing (field, instance, widget, display).', + ), + 'setting' => array( + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'description' => 'The name of the setting that CCK is managing.', + ), + 'setting_option' => array( + 'type' => 'text', + 'size' => 'medium', + 'not null' => TRUE, + 'description' => 'The custom value for this setting.', + ), + ), + ); + return $schema; +} + +/* +// TODO need to make the following changes to update existing data: +// Set up and move to content module to handle upgrade path: + +function content_update_7000() { + + - Add new columns for: + - deleted: defaults to 0 + - data: contains serialized array of complete $instance. + - Module-defined field settings are now at $field['settings'][...] + - Module-defined widget settings are now at $instance['widget']['settings'] + - Module-defined instance settings are at $instance['settings'] + - Field settings should be limited to settings that affect the schema, + all others should be instance settings. + - Display is now $instance['display'] instead of $instance['display_settings'] + - Display format now adds label to every context instead of one label + at the top level, for a more consistent structure. + - Rename display options, formatter names are now prefixed with field name. + - No more content type: $field['widget']['type_name'] + becomes $instance['bundle'] + - Required is now $instance['required'] + instead of $field['required'] + - Label is now $instance['label'] + instead of $field['widget']['label'] + - Description is now $instance['description'] + instead of $field['widget']['description'] + - Weight is now $instance['weight'] instead of $field['widget']['weight'] + - Text module textareas are now a separate field type so the + db schema won't change. + - Text module format columns are always set and don't change by field settings + so the db schema won't change. + - Text and Number with allowed values are now made into List field types. + +} +*/ \ No newline at end of file diff --git a/cck.js b/cck.js new file mode 100644 index 0000000000000000000000000000000000000000..44705b18c3c58fc4d2013897f792db93a6254abf --- /dev/null +++ b/cck.js @@ -0,0 +1,82 @@ +// $Id$ + +Drupal.behaviors.cckManageFields = { + attach: function(context) { + attachUpdateSelects(context); + } +}; + +function attachUpdateSelects(context) { + var widgetTypes = Drupal.settings.cckWidgetTypes; + var fields = Drupal.settings.cckFields; + + // Store the default text of widget selects. + $('#cck-field-overview .cck-widget-type-select', context).each(function() { + this.initialValue = this.options[0].text; + }); + + // 'Field type' select updates its 'Widget' select. + $('#cck-field-overview .cck-field-type-select', context).each(function() { + this.targetSelect = $('.cck-widget-type-select', $(this).parents('tr').eq(0)); + + $(this).change(function() { + var selectedFieldType = this.options[this.selectedIndex].value; + var options = (selectedFieldType in widgetTypes) ? widgetTypes[selectedFieldType] : [ ]; + this.targetSelect.cckPopulateOptions(options); + }); + + // Trigger change on initial pageload to get the right widget options + // when field type comes pre-selected (on failed validation). + $(this).trigger('change'); + }); + + // 'Existing field' select updates its 'Widget' select and 'Label' textfield. + $('#cck-field-overview .cck-field-select', context).each(function() { + this.targetSelect = $('.cck-widget-type-select', $(this).parents('tr').eq(0)); + this.targetTextfield = $('.cck-label-textfield', $(this).parents('tr').eq(0)); + + $(this).change(function(e, updateText) { + var updateText = (typeof(updateText) == 'undefined') ? true : updateText; + var selectedField = this.options[this.selectedIndex].value; + var selectedFieldType = (selectedField in fields) ? fields[selectedField].type : null; + var selectedFieldWidget = (selectedField in fields) ? fields[selectedField].widget : null + var options = (selectedFieldType && (selectedFieldType in widgetTypes)) ? widgetTypes[selectedFieldType] : [ ]; + this.targetSelect.cckPopulateOptions(options, selectedFieldWidget); + + if (updateText) { + $(this.targetTextfield).attr('value', (selectedField in fields) ? fields[selectedField].label : ''); + } + }); + + // Trigger change on initial pageload to get the right widget options + // and label when field type comes pre-selected (on failed validation). + $(this).trigger('change', false); + }); +} + +jQuery.fn.cckPopulateOptions = function(options, selected) { + return this.each(function() { + var disabled = false; + if (options.length == 0) { + options = [this.initialValue]; + disabled = true; + } + + // If possible, keep the same widget selected when changing field type. + // This is based on textual value, since the internal value might be + // different (optionwidgets_buttons vs. nodereference_buttons). + var previousSelectedText = this.options[this.selectedIndex].text; + + var html = ''; + jQuery.each(options, function(value, text) { + // Figure out which value should be selected. The 'selected' param + // takes precedence. + var is_selected = ((typeof(selected) !== 'undefined' && value == selected) || (typeof(selected) == 'undefined' && text == previousSelectedText)); + html += ''; + }); + + $(this) + .html(html) + .attr('disabled', disabled ? 'disabled' : ''); + }); +} \ No newline at end of file diff --git a/cck.module b/cck.module new file mode 100644 index 0000000000000000000000000000000000000000..faab4eda74f18c1a3e2c47b3be480859059745d6 --- /dev/null +++ b/cck.module @@ -0,0 +1,564 @@ + 'Fields', + 'description' => 'Manage custom fields that have been added to content types or users.', + 'page callback' => 'cck_fields_list', + 'access arguments' => array('administer content types'), + 'type' => MENU_NORMAL_ITEM, + ); + $items['admin/build/fields/list'] = array( + 'title' => 'Field list', + 'page callback' => 'cck_fields_list', + 'access arguments' => array('administer content types'), + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => 1, + ); + // TODO D7 : remove, debug code + $items['admin/build/fields/field-info'] = array( + 'title' => 'Field Info (debug)', + 'page callback' => 'cck_debug_field_info', + 'access arguments' => array('administer content types'), + 'type' => MENU_LOCAL_TASK, + 'weight' => 2, + ); + $items['admin/build/types/field-info'] = array( + 'title' => 'Field Info (debug)', + 'page callback' => 'cck_debug_field_info', + 'access arguments' => array('administer content types'), + 'type' => MENU_LOCAL_TASK, + 'weight' => 2, + ); + // Create an 'Edit' tab for the user. + $items['admin/user/settings/edit'] = array( + 'title' => 'Edit', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('user_admin_settings'), + 'access arguments' => array('administer users'), + 'type' => MENU_DEFAULT_LOCAL_TASK, + ); + + + // Make sure this doesn't fire until field_bundles is working, + // and tables are updated, needed to avoid errors on initial installation. + if (!defined('MAINTENANCE_MODE')) { + // Create tabs for all possible bundles. + foreach (field_info_fieldable_types() as $obj_type => $info) { + foreach ($info['bundles'] as $bundle_name => $bundle_label) { + $admin_path = cck_bundle_admin_path($bundle_name); + $items[$admin_path .'/fields'] = array( + 'title' => 'Manage fields', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('cck_field_overview_form', $bundle_name), + 'access arguments' => array('administer content types'), + 'type' => MENU_LOCAL_TASK, + 'weight' => 1, + ); + // A dummy function to trigger a page refresh so that + // field menus get rebuilt correctly when new fields are added. + $items[$admin_path .'/fields/refresh'] = array( + 'title' => 'Refresh menu', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('cck_field_menu_refresh', $bundle_name), + 'access arguments' => array('administer content types'), + 'type' => MENU_CALLBACK, + 'weight' => 1, + ); + $items[$admin_path .'/display'] = array( + 'title' => 'Display fields', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('cck_display_overview_form', $bundle_name), + 'access arguments' => array('administer content types'), + 'type' => MENU_LOCAL_TASK, + 'weight' => 2, + ); + + // 'Display fields' tab and context secondary tabs. + $tabs = cck_build_modes($obj_type); + foreach ($tabs as $key => $tab) { + $items[$admin_path .'/display/'. $key] = array( + 'title' => $tab['title'], + 'page arguments' => array('cck_display_overview_form', $bundle_name, $key), + 'access arguments' => array('administer content types'), + 'type' => $key == 'basic' ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK, + 'weight' => $key == 'basic' ? 0 : 1, + ); + } + + // Add tabs for any instances that are already created. + $instances = field_info_instances($bundle_name); + foreach ($instances as $instance) { + $field_name = $instance['field_name']; + $items[$admin_path .'/fields/'. $field_name] = array( + 'title' => $instance['label'], + 'page callback' => 'drupal_get_form', + 'page arguments' => array('cck_field_edit_form', $bundle_name, $field_name), + 'access arguments' => array('administer content types'), + 'type' => MENU_LOCAL_TASK, + ); + $items[$admin_path .'/fields/'. $field_name .'/edit'] = array( + 'title' => 'Configure instance settings', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('cck_field_edit_form', $bundle_name, $field_name), + 'access arguments' => array('administer content types'), + 'type' => MENU_DEFAULT_LOCAL_TASK, + ); + $items[$admin_path .'/fields/'. $field_name .'/field-settings'] = array( + 'title' => 'Configure field settings', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('cck_field_settings_form', $bundle_name, $field_name), + 'access arguments' => array('administer content types'), + 'type' => MENU_LOCAL_TASK, + ); + $items[$admin_path .'/fields/'. $field_name .'/widget-type'] = array( + 'title' => 'Change widget type', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('cck_widget_type_form', $bundle_name, $field_name), + 'access arguments' => array('administer content types'), + 'type' => MENU_LOCAL_TASK, + ); + $items[$admin_path .'/fields/'. $field_name .'/remove'] = array( + 'title' => 'Remove instance', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('cck_field_remove_form', $bundle_name, $field_name), + 'access arguments' => array('administer content types'), + 'type' => MENU_LOCAL_TASK, + ); + } + } + } + } + return $items; +} + +function cck_debug_field_info() { + dsm(_field_info()); + return ''; +} + +/** + * Implementation of hook_theme(). + */ +function cck_theme() { + $path = drupal_get_path('module', 'cck') .'/theme'; + require_once "./$path/theme.inc"; + + return array( + 'cck_field_overview_form' => array( + 'template' => 'cck-admin-field-overview-form', + 'path' => $path, + 'arguments' => array('form' => NULL), + ), + 'cck_display_overview_form' => array( + 'template' => 'cck-admin-display-overview-form', + 'path' => $path, + 'arguments' => array('form' => NULL), + ), + ); +} + +/** + * Implementation of hook_field_attach_pre_view(). + */ +function cck_field_attach_view($output, $obj_type, &$object, $teaser, $page) { + // Add identifier to the object to be used to alter extra fields in the view. + list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object); + $object->content['#fieldable'] = TRUE; + $object->content['#bundle'] = $bundle; + return $output; +} + +/** + * + */ +function cck_field_attach_insert($obj_type, &$object) { + + // Add identifier to the object to be used to alter extra fields in the view. + list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object); + + // Implement hook_devel_generate(), if needed. + if (!empty($object->devel_generate)) { + include_once('./'. drupal_get_path('module', 'cck') .'/includes/cck.devel.inc'); + cck_generate_fields($object, $bundle); + } + +} + +/** + * Implementation of hook_field_attach_form(). + */ +function cck_field_attach_form($obj_type, &$object, &$form, &$form_state) { + // Add identifier to the object to be used to alter extra fields in the form. + list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object); + $form['#fieldable'] = TRUE; + $form['#bundle'] = $bundle; +} + +/** + * Implementation of hook_form_alter(). + */ +function cck_form_alter(&$form, $form_state, $form_id) { + if (!empty($form['#fieldable']) && isset($form['#bundle'])) { + $bundle_name = $form['#bundle']; + $extra = cck_extra_field_values($bundle_name); + foreach ($extra as $key => $value) { + if (isset($form[$key])) { + $form[$key]['#weight'] = $value['weight']; + } + } + } +} + +/** + * Implementation of profile_alter(). + * + * Change extra weights here after extra fields have been added. + * Hook field_attach_post_view() is too early, other fields aren't + * all added to the form at that point. + * + * TODO Can we make the view alter more generic? This requires that + * we specifically alter each individual 'view' for all fieldable entities. + */ +function cck_profile_alter($account) { + if (!empty($account->content['#fieldable']) && isset($account->content['#bundle'])) { + $bundle_name = $account->content['#bundle']; + $extra = cck_extra_field_values($bundle_name); + foreach ($extra as $key => $value) { + if (isset($account->content[$key])) { + $account->content[$key]['#weight'] = $value['weight']; + } + } + } +} + +/** + * Implementation of node_view_alter. + * + * Change extra weights here after extra fields have been added. + * Hook field_attach_post_view() is too early, other fields aren't + * all added to the form at that point. + * + * TODO Can we make the view alter more generic? This requires that + * we specifically alter each individual 'view' for all fieldable entities. + */ +function cck_node_view_alter(&$node, $teaser = FALSE, $page = FALSE) { + if (!empty($node->content['#fieldable']) && isset($node->content['#bundle'])) { + $bundle_name = $node->content['#bundle']; + $extra = cck_extra_field_values($bundle_name); + foreach ($extra as $key => $value) { + // Some core 'fields' use a different key in node forms and in 'view' + // render arrays. + if (isset($value['view']) && isset($node->content[$value['view']])) { + $node->content[$value['view']]['#weight'] = $value['weight']; + } + elseif (isset($node->content[$key])) { + $node->content[$key]['#weight'] = $value['weight']; + } + } + } +} + +/** + * Implementation of hook_cck_extra_fields. + */ +function cck_cck_extra_fields($bundle_name) { + if ($bundle_name == 'user') { + return _cck_user_extra_fields(); + } + else { + return _cck_node_extra_fields($bundle_name); + } +} + +// TODO Add other user elements here. +// How to do this for users? The form elements are totally different +// than the view elements. +function _cck_user_extra_fields() { + $extra = array(); + $extra['account'] = array( + 'label' => 'User name and password', + 'description' => t('User module form element'), + 'weight' => -10 + ); + $extra['timezone'] = array( + 'label' => 'Timezone', + 'description' => t('User module form element.'), + 'weight' => 6 + ); + $extra['summary'] = array( + 'label' => 'History', + 'description' => t('User module view element.'), + 'weight' => 5 + ); + return $extra; +} + +function _cck_node_extra_fields($type_name) { + $type = node_get_types('type', $type_name); + $extra = array(); + + if ($type->has_title) { + $extra['title'] = array( + 'label' => $type->title_label, + 'description' => t('Node module element.'), + 'weight' => -5 + ); + } + if ($type->has_body) { + $extra['body_field'] = array( + 'label' => $type->body_label, + 'description' => t('Node module element.'), + 'weight' => 0, + 'view' => 'body' + ); + } + if (module_exists('locale') && variable_get("language_content_type_$type_name", 0)) { + $extra['language'] = array( + 'label' => t('Language'), + 'description' => t('Locale module element.'), + 'weight' => 0 + ); + } + if (module_exists('menu')) { + $extra['menu'] = array( + 'label' => t('Menu settings'), + 'description' => t('Menu module element.'), + 'weight' => -2 + ); + } + if (module_exists('taxonomy') && taxonomy_get_vocabularies($type_name)) { + $extra['taxonomy'] = array( + 'label' => t('Taxonomy'), + 'description' => t('Taxonomy module element.'), + 'weight' => -3 + ); + } + if (module_exists('book')) { + $extra['book'] = array( + 'label' => t('Book'), + 'description' => t('Book module element.'), + 'weight' => 10 + ); + } + if (module_exists('upload') && variable_get("upload_$type_name", TRUE)) { + $extra['attachments'] = array( + 'label' => t('File attachments'), + 'description' => t('Upload module element.'), + 'weight' => 30, + 'view' => 'files' + ); + } + + return $extra; +} + +/** + * Retrieve the user-defined weight for non-CCK node 'fields'. + * + * CCK's 'Manage fields' page lets users reorder node fields, including non-CCK + * items (body, taxonomy, other hook_nodeapi-added elements by contrib modules...). + * Contrib modules that want to have their 'fields' supported need to expose + * them with hook_cck_extra_fields, and use this function to retrieve the + * user-defined weight. + * + * @param $type_name + * The content type name. + * @param $pseudo_field_name + * The name of the 'field'. + * @return + * The weight for the 'field', respecting the user settings stored + * by field.module. + */ +function cck_extra_field_weight($bundle_name, $pseudo_field_name) { + $extra = cck_extra_field_values($bundle_name); + if (isset($extra[$pseudo_field_name])) { + return $extra[$pseudo_field_name]['weight']; + } +} + +function cck_extra_field_values($bundle_name) { + static $info = array(); + + if (empty($info)) { + $info = array(); + $bundles = field_info_bundles(); + foreach ($bundles as $bundle => $bundle_label) { + // Gather information about non-CCK 'fields'. + $extra = module_invoke_all('cck_extra_fields', $bundle); + drupal_alter('cck_extra_fields', $extra, $bundle); + + // Add saved weights. + foreach (variable_get('cck_extra_weights_'. $bundle, array()) as $key => $value) { + // Some stored entries might not exist anymore, for instance if uploads + // have been disabled, or vocabularies removed... + if (isset($extra[$key])) { + $extra[$key]['weight'] = $value; + } + } + $info[$bundle] = $extra; + } + } + if (array_key_exists($bundle_name, $info)) { + return $info[$bundle_name]; + } + else { + return array(); + } +} + + +function cck_build_modes($obj_type, $tab_selector = NULL) { + static $info; + + if (!isset($info[$obj_type])) { + $info[$obj_type] = module_invoke_all('cck_build_modes'); + // Collect titles, and filter out non active modes. + $active_modes = field_build_modes($obj_type); + foreach ($info[$obj_type] as $tab => $values) { + $modes = array(); + foreach ($info[$obj_type][$tab]['build modes'] as $mode) { + if (isset($active_modes[$mode])) { + $modes[$mode] = $active_modes[$mode]; + } + } + if ($modes) { + $info[$obj_type][$tab]['build modes'] = $modes; + } + else { + unset($info[$obj_type][$tab]); + } + } + } + if ($tab_selector) { + return isset($info[$obj_type][$tab_selector]) ? $info[$obj_type][$tab_selector]['build modes'] : array(); + } + else { + return $info[$obj_type]; + } +} + +/** + * Implementation of hook_cck_build_modes(), on behalf of other core modules. + * + * @return + * An array describing the build modes defined by the module, grouped by tabs. + * + * Expected format: + * array( + * // A module can add its render modes to a tab defined by another module. + * 'tab1' => array( + * 'title' => t('The human-readable title of the tab'), + * 'build modes' => array('mymodule_mode1', 'mymodule_mode2'), + * ), + * 'tab2' => array( + * // ... + * ), + * ); + */ +function cck_cck_build_modes() { + $modes = array( + 'basic' => array( + 'title' => t('Basic'), + 'build modes' => array('teaser', 'full'), + ), + 'rss' => array( + 'title' => t('RSS'), + 'build modes' => array(NODE_BUILD_RSS), + ), + 'print' => array( + 'title' => t('Print'), + 'build modes' => array(NODE_BUILD_PRINT), + ), + 'search' => array( + 'title' => t('Search'), + 'build modes' => array(NODE_BUILD_SEARCH_INDEX, NODE_BUILD_SEARCH_RESULT), + ), + ); + return $modes; +} + +/** + * Implementation of hook_node_type(). + * + * TODO Right way to do this in D7? Not needed for users since the user type is never deleted. + * Content types could be deleted. Not sure about other entities. + * [yched] : fieldable will need to take care of calling field_rename_bundle(), field_delete_bundle() + * when appropriate anyway. Those functions - we don't have them yet :) - will need to call a hook + * so that CCK UI is notified of this. + */ +function cck_node_type($op, $info) { + switch ($op) { + case 'update': + if ($extra = variable_get('cck_extra_weights_'. $info->old_type, array())) { + variable_set('cck_extra_weights_'. $info->type, $extra); + variable_del('cck_extra_weights_'. $info->old_type); + } + break; + case 'delete': + variable_del('cck_extra_weights_'. $info->type); + break; + } +} + +/** + * Helper function to create the right administration path for a bundle. + */ +function cck_bundle_admin_path($bundle_name) { + if ($bundle_name == 'user') { + return 'admin/user/settings'; + } + else { + return 'admin/build/node-type/' . str_replace('_', '-', $bundle_name); + } +} \ No newline at end of file diff --git a/includes/cck.admin.inc b/includes/cck.admin.inc new file mode 100644 index 0000000000000000000000000000000000000000..a6aa1745eb6b74b633acf673d706ae45fbb9bf2f --- /dev/null +++ b/includes/cck.admin.inc @@ -0,0 +1,1510 @@ + t('Operations'), 'colspan' => '4'),); + $rows = array(); + foreach ($names as $key => $name) { + $type = $types[$key]; + if (node_hook($type, 'form')) { + $bundle_url_str = str_replace('_', '-', $type->type); + $row = array( + check_plain($name), + check_plain($type->type), + ); + // Make the description smaller + $row[] = array('data' => filter_xss_admin($type->description), 'class' => 'description'); + // Set the edit column. + $row[] = array('data' => l(t('edit'), 'admin/build/node-type/'. $bundle_url_str)); + // Set links for managing fields. + // TODO: a hook to allow other cck modules to add more stuff? + $row[] = array('data' => l(t('manage fields'), 'admin/build/node-type/'. $bundle_url_str .'/fields')); + // Set the delete column. + if ($type->custom) { + $row[] = array('data' => l(t('delete'), 'admin/build/node-type/'. $bundle_url_str .'/delete')); + } + else { + $row[] = array('data' => ''); + } + + $rows[] = $row; + } + } + + if (empty($rows)) { + $rows[] = array(array('data' => t('No bundles available.'), 'colspan' => '7', 'class' => 'message')); + } + + return theme('table', $header, $rows); +} + +/** + * Menu callback; lists all defined fields for quick reference. + */ +function cck_fields_list() { + $instances = field_info_instances(); + $field_types = field_info_field_types(); + $bundles = field_info_bundles(); + $header = array(t('Field name'), t('Field type'), t('Used in')); + $rows = array(); + foreach ($instances as $bundle => $info) { + foreach ($info as $field_name => $instance) { + $field = field_info_field($field_name); + $rows[$field_name]['data'][0] = $field['locked'] ? t('@field_name (Locked)', array('@field_name' => $field_name)) : $field_name; + $rows[$field_name]['data'][1] = t($field_types[$field['type']]['label']); + $admin_path = cck_bundle_admin_path($bundle); + $rows[$field_name]['data'][2][] = l($bundles[$bundle], $admin_path .'/fields'); + $rows[$field_name]['class'] = $field['locked'] ? 'menu-disabled' : ''; + } + } + foreach ($rows as $field_name => $cell) { + $rows[$field_name]['data'][2] = implode(', ', $cell['data'][2]); + } + if (empty($rows)) { + $output = t('No fields have been defined for any content type yet.'); + } + else { + $output = theme('table', $header, $rows); + } + return $output; +} + +/** + * Helper function to display a message about inactive fields. + */ +function cck_inactive_message($bundle) { + // TODO : adapt to new D7 APIs + $inactive_fields = field_inactive_fields($bundle); + if (!empty($inactive_fields)) { + $field_types = _field_field_types(); + $widget_types = _field_widget_types($bundle); + drupal_set_message(t('This bundle has inactive fields. Inactive fields are not included in lists of available fields until their modules are enabled.'), 'error'); + foreach ($inactive_fields as $field_name => $field) { + drupal_set_message(t('!field (!field_name) is an inactive !field_type field that uses a !widget_type widget.', array( + '!field' => $field['label'], + '!field_name' => $field['field_name'], + '!field_type' => array_key_exists($field['type'], $field_types) ? $field_types[$field['type']]['label'] : $field['type'], + '!widget_type' => array_key_exists($field['widget']['type'], $widget_types) ? $widget_types[$field['widget']['type']]['label'] : $field['widget']['type'], + ))); + } + } +} + +/** + * Menu callback; listing of fields for a content type. + * + * Allows fields to be reordered and nested in fieldgroups using + * JS drag-n-drop. Non-field form elements can also be moved around. + */ +function cck_field_overview_form(&$form_state, $bundle) { + + cck_inactive_message($bundle); + $admin_path = cck_bundle_admin_path($bundle); + + // When displaying the form, make sure the list of fields + // is up-to-date. + if (empty($form_state['post'])) { + field_cache_clear(); + } + + // Gather bundle information. + $instances = field_info_instances($bundle); + $field_types = field_info_field_types(); + $widget_types = field_info_widget_types(); + + $extra = cck_extra_field_values($bundle); + + $groups = $group_options = array(); + if (module_exists('fieldgroup')) { + $groups = fieldgroup_groups($bundle); + $group_types = fieldgroup_types(); + $group_options = _fieldgroup_groups_label($bundle); + // Add the ability to group under the newly created row. + $group_options['_add_new_group'] = '_add_new_group'; + } + + // Store the default weights as we meet them, to be able to put the + //'add new' rows after them. + $weights = array(); + + $form = array( + '#tree' => TRUE, + '#bundle' => $bundle, + '#fields' => array_keys($instances), + '#groups' => array_keys($groups), + '#extra' => array_keys($extra), + '#field_rows' => array(), + '#group_rows' => array(), + ); + + // Fields. + foreach ($instances as $name => $instance) { + $field = field_info_field($instance['field_name']); + $admin_field_path = $admin_path .'/fields/'. $instance['field_name']; + $weight = $instance['weight']; + $form[$name] = array( + 'label' => array('#markup' => check_plain($instance['label'])), + 'field_name' => array('#markup' => $instance['field_name']), + 'type' => array( + '#markup' => l(t($field_types[$field['type']]['label']), $admin_field_path .'/field-settings', array( + 'attributes' => array('title' => t('Edit field settings.'))))), + 'widget_type' => array( + '#markup' => l(t($widget_types[$instance['widget']['type']]['label']), $admin_field_path .'/widget-type', array( + 'attributes' => array('title' => t('Change widget type.'))))), + 'configure' => array( + '#markup' => l(t('Configure'), $admin_field_path, array( + 'attributes' => array('title' => t('Edit instance settings.'))))), + 'remove' => array( + '#markup' => l(t('Remove'), $admin_field_path .'/remove', array( + 'attributes' => array('title' => t('Remove instance.'))))), + 'weight' => array('#type' => 'textfield', '#default_value' => $weight, '#size' => 3), + 'parent' => array('#type' => 'select', '#options' => $group_options, '#default_value' => ''), + 'prev_parent' => array('#type' => 'hidden', '#value' => ''), + 'hidden_name' => array('#type' => 'hidden', '#default_value' => $instance['field_name']), + '#leaf' => TRUE, + '#row_type' => 'field', + // TODO D7 : ?? + 'field' => array('#type' => 'value', '#value' => $field), + ); + + if (!empty($instance['locked'])) { + $form[$name]['configure'] = array('#value' => t('Locked')); + $form[$name]['remove'] = array(); + $form[$name]['#disabled_row'] = TRUE; + } + $form['#field_rows'][] = $name; + $weights[] = $weight; + } + + // Groups. + foreach ($groups as $name => $group) { + $weight = $group['weight']; + $form[$name] = array( + 'label' => array('#markup' => check_plain($group['label'])), + 'group_name' => array('#markup' => $group['group_name']), + 'group_type' => array('#markup' => t($group_types[$group['group_type']])), + 'configure' => array('#markup' => l(t('Configure'), $admin_path .'/groups/'. $group['group_name'])), + 'remove' => array('#markup' => l(t('Remove'), $admin_path .'/groups/'. $group['group_name'] .'/remove')), + 'weight' => array('#type' => 'textfield', '#default_value' => $weight, '#size' => 3), + 'parent' => array('#type' => 'hidden', '#default_value' => ''), + 'hidden_name' => array('#type' => 'hidden', '#default_value' => $group['group_name']), + '#root' => TRUE, + '#row_type' => 'group', + 'group' => array('#type' => 'value', '#value' => $group), + ); + // Adjust child fields rows. + foreach ($group['fields'] as $field_name => $field) { + $form[$field_name]['parent']['#default_value'] = $name; + $form[$field_name]['prev_parent']['#value'] = $name; + } + $form['#group_rows'][] = $name; + $weights[] = $weight; + } + + // Non-field elements. + foreach ($extra as $name => $label) { + $weight = $extra[$name]['weight']; + $form[$name] = array( + 'label' => array('#markup' => t($extra[$name]['label'])), + 'description' => array('#markup' => isset($extra[$name]['description']) ? $extra[$name]['description'] : ''), + 'weight' => array('#type' => 'textfield', '#default_value' => $weight, '#size' => 3), + 'parent' => array('#type' => 'hidden', '#default_value' => ''), + 'hidden_name' => array('#type' => 'hidden', '#default_value' => $name), + '#leaf' => TRUE, + '#root' => TRUE, + '#disabled_row' => TRUE, + '#row_type' => 'extra', + ); + $form['#field_rows'][] = $name; + $weights[] = $weight; + } + + // Additional row : add new field. + $weight = !empty($weights) ? max($weights) + 1 : 0; + $field_type_options = cck_field_type_options(); + $widget_type_options = cck_widget_type_options(NULL, TRUE); + if ($field_type_options && $widget_type_options) { + array_unshift($field_type_options, t('- Select a field type -')); + array_unshift($widget_type_options, t('- Select a widget -')); + $name = '_add_new_field'; + $form[$name] = array( + 'label' => array( + '#type' => 'textfield', + '#size' => 15, + '#description' => t('Label'), + ), + 'field_name' => array( + '#type' => 'textfield', + '#field_prefix' => 'field_', + '#size' => 15, + '#description' => t('Field name (a-z, 0-9, _)'), + ), + 'type' => array( + '#type' => 'select', + '#options' => $field_type_options, + '#description' => theme('advanced_help_topic', 'cck', 'fields') . t('Type of data to store.'), + ), + 'widget_type' => array( + '#type' => 'select', + '#options' => $widget_type_options, + '#description' => t('Form element to edit the data.'), + ), + 'weight' => array('#type' => 'textfield', '#default_value' => $weight, '#size' => 3), + 'parent' => array('#type' => 'select', '#options' => $group_options, '#default_value' => ''), + 'hidden_name' => array('#type' => 'hidden', '#default_value' => $name), + '#leaf' => TRUE, + '#add_new' => TRUE, + '#row_type' => 'add_new_field', + ); + $form['#field_rows'][] = $name; + } + + // Additional row : add existing field. + $existing_field_options = cck_existing_field_options($bundle); + if ($existing_field_options && $widget_type_options) { + $weight++; + array_unshift($existing_field_options, t('- Select an existing field -')); + $name = '_add_existing_field'; + $form[$name] = array( + 'label' => array( + '#type' => 'textfield', + '#size' => 15, + '#description' => t('Label'), + ), + 'field_name' => array( + '#type' => 'select', + '#options' => $existing_field_options, + '#description' => t('Field to share'), + ), + 'widget_type' => array( + '#type' => 'select', + '#options' => $widget_type_options, + '#description' => t('Form element to edit the data.'), + ), + 'weight' => array('#type' => 'textfield', '#default_value' => $weight, '#size' => 3), + 'parent' => array('#type' => 'select', '#options' => $group_options, '#default_value' => ''), + 'hidden_name' => array('#type' => 'hidden', '#default_value' => $name), + '#leaf' => TRUE, + '#add_new' => TRUE, + '#row_type' => 'add_existing_field', + ); + $form['#field_rows'][] = $name; + } + + // Additional row : add new group. + if (module_exists('fieldgroup')) { + $weight++; + $name = '_add_new_group'; + $form[$name] = array( + 'label' => array( + '#type' => 'textfield', + '#size' => 15, + '#description' => t('Label'), + ), + 'group_name' => array( + '#type' => 'textfield', + '#field_prefix' => 'group_', + '#size' => 15, + '#description' => t('Group name (a-z, 0-9, _)'), + ), + 'group_option' => array( + '#type' => 'hidden', + '#value' => '', + ), + 'group_type' => array( + '#type' => 'hidden', + '#value' => 'standard', + ), + 'weight' => array('#type' => 'textfield', '#default_value' => $weight, '#size' => 3), + 'parent' => array('#type' => 'hidden', '#default_value' => ''), + 'hidden_name' => array('#type' => 'hidden', '#default_value' => $name), + '#root' => TRUE, + '#add_new' => TRUE, + '#row_type' => 'add_new_group', + ); + $form['#group_rows'][] = $name; + } + + $form['submit'] = array('#type' => 'submit', '#value' => t('Save')); + return $form; +} + +function cck_field_overview_form_validate($form, &$form_state) { + _cck_field_overview_form_validate_add_new($form, $form_state); + _cck_field_overview_form_validate_add_existing($form, $form_state); +} + +/** + * Helper function for cck_field_overview_form_validate. + * + * Validate the 'add new field' row. + */ +function _cck_field_overview_form_validate_add_new($form, &$form_state) { + + $field = $form_state['values']['_add_new_field']; + + // Validate if any information was provided in the 'add new field' row. + if (array_filter(array($field['label'], $field['field_name'], $field['type'], $field['widget_type']))) { + // No label. + if (!$field['label']) { + form_set_error('_add_new_field][label', t('Add new field: you need to provide a label.')); + } + + // No field name. + if (!$field['field_name']) { + form_set_error('_add_new_field][field_name', t('Add new field: you need to provide a field name.')); + } + // Field name validation. + else { + $field_name = $field['field_name']; + + // Add the 'field_' prefix. + if (substr($field_name, 0, 6) != 'field_') { + $field_name = 'field_'. $field_name; + form_set_value($form['_add_new_field']['field_name'], $field_name, $form_state); + } + + // Invalid field name. + if (!preg_match('!^field_[a-z0-9_]+$!', $field_name)) { + form_set_error('_add_new_field][field_name', t('Add new field: the field name %field_name is invalid. The name must include only lowercase unaccentuated letters, numbers, and underscores.', array('%field_name' => $field_name))); + } + if (strlen($field_name) > 32) { + form_set_error('_add_new_field][field_name', t('Add new field: the field name %field_name is too long. The name is limited to 32 characters, including the \'field_\' prefix.', array('%field_name' => $field_name))); + } + // A field named 'field_instance' would cause a tablename clash with {field_field_instance} + if ($field_name == 'field_instance') { + form_set_error('_add_new_field][field_name', t("Add new field: the name 'field_instance' is a reserved name.")); + } + + // Field name already exists. + // We need to check inactive fields as well, so we can't use field_fields(). + module_load_include('inc', 'content', 'includes/content.crud'); + $fields = field_read_fields(array(), TRUE); + $used = FALSE; + foreach ($fields as $existing_field) { + $used |= ($existing_field['field_name'] == $field_name); + } + if ($used) { + form_set_error('_add_new_field][field_name', t('Add new field: the field name %field_name already exists.', array('%field_name' => $field_name))); + } + } + + // No field type. + if (!$field['type']) { + form_set_error('_add_new_field][type', t('Add new field: you need to select a field type.')); + } + + // No widget type. + if (!$field['widget_type']) { + form_set_error('_add_new_field][widget_type', t('Add new field: you need to select a widget.')); + } + // Wrong widget type. + elseif ($field['type']) { + $widget_types = cck_widget_type_options($field['type']); + if (!isset($widget_types[$field['widget_type']])) { + form_set_error('_add_new_field][widget_type', t('Add new field: invalid widget.')); + } + } + } +} + +/** + * Helper function for cck_field_overview_form_validate. + * + * Validate the 'add existing field' row. + */ +function _cck_field_overview_form_validate_add_existing($form, &$form_state) { + + // The form element might be absent if no existing fields can be added to + // this content type + if (isset($form_state['values']['_add_existing_field'])) { + $field = $form_state['values']['_add_existing_field']; + + // Validate if any information was provided in the 'add existing field' row. + if (array_filter(array($field['label'], $field['field_name'], $field['widget_type']))) { + // No label. + if (!$field['label']) { + form_set_error('_add_existing_field][label', t('Add existing field: you need to provide a label.')); + } + + // No existing field. + if (!$field['field_name']) { + form_set_error('_add_existing_field][field_name', t('Add existing field: you need to select a field.')); + } + + // No widget type. + if (!$field['widget_type']) { + form_set_error('_add_existing_field][widget_type', t('Add existing field: you need to select a widget.')); + } + // Wrong widget type. + elseif ($field['field_name'] && ($existing_field = field_info_field($field['field_name']))) { + $widget_types = cck_widget_type_options($existing_field['type']); + if (!isset($widget_types[$field['widget_type']])) { + form_set_error('_add_existing_field][widget_type', t('Add existing field: invalid widget.')); + } + } + } + } +} + +function cck_field_overview_form_submit($form, &$form_state) { + $form_values = $form_state['values']; + $bundle = $form['#bundle']; + $admin_path = cck_bundle_admin_path($bundle); + + // Update field weights. + $extra = array(); + foreach ($form_values as $key => $values) { + // Groups are handled in fieldgroup_cck_overview_form_submit(). + if (in_array($key, $form['#fields'])) { + db_query("UPDATE {field_config_instance} SET weight = %d WHERE bundle = '%s' AND field_name = '%s'", + $values['weight'], $bundle, $key); + } + elseif (in_array($key, $form['#extra'])) { + $extra[$key] = $values['weight']; + } + } + + if ($extra) { + variable_set('cck_extra_weights_'. $bundle, $extra); + } + else { + variable_del('cck_extra_weights_'. $bundle); + } + + $destinations = array(); + + // Create new field. + $field = array(); + if (!empty($form_values['_add_new_field']['field_name'])) { + $values = $form_values['_add_new_field']; + $field = array( + 'field_name' => $values['field_name'], + 'type' => $values['type'], + ); + $instance = array( + 'field_name' => $field['field_name'], + 'bundle' => $bundle, + 'label' => $values['label'], + 'weight' => $values['weight'], + 'widget' => array( + 'type' => $values['widget_type'], + ), + ); + + // Create the field and instance. + try { + field_create_field($field); + field_create_instance($instance); + + // Rebuild the menu so we can navigate to the field settings screen. + //field_clear_type_cache(TRUE); + //menu_rebuild(); + $destinations[] = $admin_path .'/fields/refresh'; + $destinations[] = $admin_path .'/fields/'. $field['field_name'] .'/field-settings'; + $destinations[] = $admin_path .'/fields/'. $field['field_name'] .'/edit'; + + // Store new field information for fieldgroup submit handler. + $form_state['fields_added']['_add_new_field'] = $field['field_name']; + } + catch (Exception $e) { + drupal_set_message(t('There was a problem creating field %label: @message.', array( + '%label' => $values['label'], '@message' => $e->getMessage()))); + } + } + + // Add existing field. + if (!empty($form_values['_add_existing_field']['field_name'])) { + $values = $form_values['_add_existing_field']; + $field = field_info_field($values['field_name']); + if (!empty($field['locked'])) { + drupal_set_message(t('The field %label cannot be added because it is locked.', array('%label' => $field['field_name']))); + } + else { + $instance = array( + 'field_name' => $field['field_name'], + 'bundle' => $bundle, + 'label' => $values['label'], + 'weight' => $values['weight'], + 'widget' => array( + 'type' => $values['widget_type'], + ), + ); + + try { + field_create_instance($instance); + $destinations[] = $admin_path .'/fields/refresh'; + $destinations[] = $admin_path .'/fields/'. $instance['field_name'] .'/edit'; + // Store new field information for fieldgroup submit handler. + $form_state['fields_added']['_add_existing_field'] = $instance['field_name']; + } + catch (Exception $e) { + drupal_set_message(t('There was a problem creating field instance %label: @message.', array( + '%label' => $field['label'], '@message' => $e->getMessage()))); + } + } + } + + if ($destinations) { + $destinations[] = urldecode(substr(drupal_get_destination(), 12)); + unset($_REQUEST['destination']); + $form_state['redirect'] = cck_get_destinations($destinations); + } + + field_cache_clear(); +} + +/** + * Menu callback; presents a listing of fields display settings for a content type. + * + * Form includes form widgets to select which fields appear for teaser, full node + * and how the field labels should be rendered. + */ +function cck_display_overview_form(&$form_state, $bundle, $contexts_selector = 'basic') { + cck_inactive_message($bundle); + $admin_path = cck_bundle_admin_path($bundle); + + // Gather type information. + $entity = field_info_bundle_entity($bundle); + $instances = field_info_instances($bundle); + $field_types = field_info_field_types(); + + $groups = $group_options = array(); + if (module_exists('fieldgroup')) { + $groups = fieldgroup_groups($type['type']); + $group_options = _fieldgroup_groups_label($type['type']); + } + + $contexts = cck_build_modes($entity, $contexts_selector); + $form = array( + '#tree' => TRUE, + '#bundle' => $bundle, + '#fields' => array_keys($instances), + '#groups' => array_keys($groups), + '#contexts' => $contexts_selector, + ); + + if (empty($instances)) { + drupal_set_message(t('There are no fields configured yet. You can add new fields on the Manage fields page.', array('@link' => url($admin_path .'/fields'))), 'warning'); + return $form; + } + + // Fields. + $label_options = array( + 'above' => t('Above'), + 'inline' => t('Inline'), + 'hidden' => t(''), + ); + foreach ($instances as $name => $instance) { + $field = field_info_field($instance['field_name']); + $field_type = $field_types[$field['type']]; + $defaults = $instance['display']; + $weight = $instance['weight']; + + $form[$name] = array( + 'human_name' => array('#markup' => check_plain($instance['label'])), + 'weight' => array('#type' => 'value', '#value' => $weight), + 'parent' => array('#type' => 'value', '#value' => ''), + ); + + // Formatters. + $options = cck_formatter_options($field['type']); + $options['hidden'] = t(''); + + foreach ($contexts as $key => $value) { + $form[$name][$key]['label'] = array( + '#type' => 'select', + '#options' => $label_options, + '#default_value' => isset($defaults[$key]['label']) ? $defaults[$key]['label'] : 'hidden', + ); + $form[$name][$key]['type'] = array( + '#type' => 'select', + '#options' => $options, + '#default_value' => isset($defaults[$key]['type']) ? $defaults[$key]['type'] : 'default', + ); + // exclude from $content + $form[$name][$key]['exclude'] = array( + '#type' => 'checkbox', + '#options' => array(0 => t('Include'), 1 => t('Exclude')), + '#default_value' => isset($defaults[$key]['exclude']) ? $defaults[$key]['exclude'] : 0, + ); + } + } + + // Groups. + $label_options = array( + 'above' => t('Above'), + 'hidden' => t(''), + ); + $options = array( + 'no_style' => t('no styling'), + 'simple' => t('simple'), + 'fieldset' => t('fieldset'), + 'fieldset_collapsible' => t('fieldset - collapsible'), + 'fieldset_collapsed' => t('fieldset - collapsed'), + 'hidden' => t(''), + ); + foreach ($groups as $name => $group) { + $defaults = $group['settings']['display']; + $weight = $group['weight']; + + $form[$name] = array( + 'human_name' => array('#markup' => check_plain($group['label'])), + 'weight' => array('#type' => 'value', '#value' => $weight), + ); + foreach ($contexts as $key => $title) { + $form[$name][$key]['label'] = array( + '#type' => 'select', + '#options' => $label_options, + '#default_value' => isset($defaults[$key]['label']) ? $defaults[$key]['label'] : 'above', + ); + $form[$name][$key]['format'] = array( + '#type' => 'select', + '#options' => $options, + '#default_value' => isset($defaults[$key]['format']) ? $defaults[$key]['format'] : 'fieldset', + ); + // exclude in $content + $form[$name][$key]['exclude'] = array( + '#type' => 'checkbox', + '#options' => array(0 => t('Include'), 1 => t('Exclude')), + '#default_value' => isset($defaults[$key]['exclude']) ? $defaults[$key]['exclude'] : 0, + ); + } + foreach ($group['fields'] as $field_name => $field) { + $form[$field_name]['parent']['#value'] = $name; + } + } + + $form['submit'] = array('#type' => 'submit', '#value' => t('Save')); + return $form; +} + +/** + * Submit handler for the display overview form. + */ +function cck_display_overview_form_submit($form, &$form_state) { + + module_load_include('inc', 'field', 'includes/field.crud'); + $form_values = $form_state['values']; + foreach ($form_values as $key => $values) { + // Groups are handled in fieldgroup_display_overview_form_submit(). + if (in_array($key, $form['#fields'])) { + $instance = field_info_instance($key, $form['#bundle']); + // We have some numeric keys here, so we can't use array_merge. + unset($values['weight'], $values['parent']); + $instance['display'] = $values + (array) $instance['display']; + field_update_instance($instance); + } + } + + drupal_set_message(t('Your settings have been saved.')); +} + +/** + * Return an array of field_type options. + */ +function cck_field_type_options() { + static $options; + + if (!isset($options)) { + $options = array(); + $field_types = field_info_field_types(); + $field_type_options = array(); + foreach ($field_types as $name => $field_type) { + // Skip field types which have no widget types. + if (cck_widget_type_options($name)) { + $options[$name] = t($field_type['label']); + } + } + asort($options); + } + return $options; +} + +/** + * Return an array of widget type options for a field type. + * + * If no field type is provided, returns a nested array of + * all widget types, keyed by field type human name. + */ +function cck_widget_type_options($field_type = NULL, $by_label = FALSE) { + static $options; + + if (!isset($options)) { + $options = array(); + foreach (field_info_widget_types() as $name => $widget_type) { + foreach ($widget_type['field types'] as $widget_field_type) { + $options[$widget_field_type][$name] = t($widget_type['label']); + } + } + } + + if ($field_type) { + return !empty($options[$field_type]) ? $options[$field_type] : array(); + } + elseif ($by_label) { + $field_types = field_info_field_types(); + $options_by_label = array(); + foreach ($options as $field_type => $widgets) { + $options_by_label[t($field_types[$field_type]['label'])] = $widgets; + } + return $options_by_label; + } + else { + return $options; + } +} + +/** + * Return an array of formatter options for a field type. + * + * If no field type is provided, returns a nested array of + * all formatters, keyed by field type. + */ +function cck_formatter_options($field_type = NULL) { + static $options; + + if (!isset($options)) { + $options = array(); + foreach (field_info_formatter_types() as $name => $formatter) { + foreach ($formatter['field types'] as $formatter_field_type) { + $options[$formatter_field_type][$name] = t($formatter['label']); + } + } + } + + if ($field_type) { + return !empty($options[$field_type]) ? $options[$field_type] : array(); + } + else { + return $options; + } +} + +/** + * Return an array of existing field to be added to a node type. + */ +function cck_existing_field_options($bundle) { + $bundles = field_info_instances(); + $options = array(); + $field_types = field_info_field_types(); + foreach ($bundles as $bundle_name => $instances) { + // No need to look in the currenht bundle. + if ($bundle_name != $bundle) { + foreach ($instances as $instance) { + $field = field_info_field($instance['field_name']); + // Don't show locked fields, or fields that are already in the current + // bundle. + if (empty($field['locked']) && !field_info_instance($field['field_name'], $bundle)) { + $text = t('@type: @field (@label)', array('@type' => t($field_types[$field['type']]['label']), '@label' => t($instance['label']), '@field' => $instance['field_name'])); + $options[$instance['field_name']] = (drupal_strlen($text) > 80) ? truncate_utf8($text, 77) . '...' : $text; + } + } + } + } + // Sort the list by type, then by field name, then by label. + asort($options); + return $options; +} + +/** + * Helper function to determine if a field has data in the database. + */ +function cck_field_has_data($field_name) { + $has_data = FALSE; + if (drupal_function_exists('field_db_tablename')) { + $table = field_db_tablename($field_name); + if (db_table_exists($table)) { + $query = db_select($table, 'f', array('fetch' => PDO::FETCH_ASSOC)); + $query->fields('f', array('entity_id')); + $query->condition('f.deleted', 0); + $results = $query->execute()->fetchAssoc(); + if (!empty($results)) { + $has_data = TRUE; + } + } + } + return $has_data; +} + +/** + * Menu callback; presents the field settings edit page. + */ +function cck_field_settings_form(&$form_state, $bundle, $field_name) { + // TODO D7 : why do we fetch from the db instead of fetching from field_info_field() / field_info_instance() ? + $instance = field_read_instance($field_name, $bundle); + $field = field_read_field($field_name); + $field_type = field_info_field_types($field['type']); + + $info_function = $field['module'] .'_field_info'; + $info = $info_function(); + $description = '

'. $info[$field['type']]['label'] .': '; + $description .= $info[$field['type']]['description'] .'

'; + $form['#prefix'] = '
'. $description .'
'; + + $description = '

'. t('These settings apply to the %field field everywhere it is used. These settings impact the way that data is stored in the database and cannot be changed once data has been created.', array( + '%field' => $instance['label'])) .'

'; + + // Create a form structure for the field values. + $form['field'] = array( + '#type' => 'fieldset', + '#title' => t('%field field settings', array('%field' => $instance['label'])), + '#description' => $description, + '#tree' => TRUE, + ); + + // See if data already exists for this field. + // If so, prevent changes to the field settings. + $has_data = cck_field_has_data($field_name); + if ($has_data) { + $form['field']['#description'] = '
'. t('There is data for this field in the database. The field settings can no longer be changed.' .'
') . $form['field']['#description']; + } + + // Build the non-configurable field values. + $form['field']['field_name'] = array('#type' => 'value', '#value' => $field_name); + $form['field']['type'] = array('#type' => 'value', '#value' => $field['type']); + $form['field']['module'] = array('#type' => 'value', '#value' => $field['module']); + $form['field']['active'] = array('#type' => 'value', '#value' => $field['active']); + + // Build the configurable field values. + $description = t('Maximum number of values users can enter for this field.'); + if (field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_DEFAULT) { + $description .= '
'. t("'Unlimited' will provide an 'Add more' button so the users can add as many values as they like."); + } + $form['field']['cardinality'] = array( + '#type' => 'select', + '#title' => t('Number of values'), + '#options' => array(FIELD_CARDINALITY_UNLIMITED => t('Unlimited')) + drupal_map_assoc(range(1, 10)), + '#default_value' => $field['cardinality'], + '#description' => $description, + '#disabled' => $has_data, + ); + + // Add settings provided by the field module. + $form['field']['settings'] = array(); + $additions = module_invoke($field_type['module'], 'field_settings_form', $field, $instance); + if (is_array($additions)) { + $form['field']['settings'] = $additions; + } + foreach ($form['field']['settings'] as $key => $setting) { + if (substr($key, 0, 1) != '#') { + $form['field']['settings'][$key]['#disabled'] = $has_data; + } + } + + $form['#bundle'] = $bundle; + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Save field settings'), + ); + + return $form; +} + +/** + * Save a field's settings after editing. + */ +function cck_field_settings_form_submit($form, &$form_state) { + + $form_values = $form_state['values']; + $field = $form_values['field']; + + // Don't allow changes to fields with data. + if (cck_field_has_data($field['field_name'])) { + return; + } + + $bundle = $form['#bundle']; + $instance = field_info_instance($field['field_name'], $bundle); + + // Update the field. + cck_field_update_field($field); + + drupal_set_message(t('Updated field %label field settings.', array('%label' => $instance['label']))); + $form_state['redirect'] = cck_next_destination($form_state, $bundle); +} + +/** + * The Field API doesn't allow field updates, + * so we create a method here to update field if no data is created yet. + */ +function cck_field_update_field($field) { + + $field_types = field_info_field_types(); + $module = $field_types[$field['type']]['module']; + + // Make sure field settings at least have their default values. + if (!is_array($field['settings'])) { + $field['settings'] = array(); + } + $field['settings'] += (array) module_invoke($module, 'field_settings', $field['type']); + + drupal_write_record('field_config', $field, array('field_name')); + + // Clear caches + field_cache_clear(TRUE); +} + +/** + * Menu callback; select a widget for the field. + */ +function cck_widget_type_form(&$form_state, $bundle, $field_name) { + $instance = field_read_instance($field_name, $bundle); + $field = field_read_field($field_name); + + $field_type = field_info_field_types($field['type']); + $widget_type = field_info_widget_types($instance['widget']['type']); + $bundles = field_info_bundles(); + $bundle_label = $bundles[$bundle]; + + $form = array(); + $form['basic'] = array( + '#type' => 'fieldset', + '#title' => t('Change widget'), + ); + $form['basic']['widget_type'] = array( + '#type' => 'select', + '#title' => t('Widget type'), + '#required' => TRUE, + '#options' => cck_widget_type_options($field['type']), + '#default_value' => $instance['widget']['type'], + '#description' => t('The type of form element you would like to present to the user when creating this field in the %type type.', array('%type' => $bundle_label)), + ); + + $form['#instance'] = $instance; + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Continue'), + ); + + $form['#validate'] = array(); + $form['#submit'] = array('cck_widget_type_form_submit'); + + return $form; +} + +/** + * Submit the change in widget type. + */ +function cck_widget_type_form_submit($form, &$form_state) { + $form_values = $form_state['values']; + $instance = $form['#instance']; + $bundle = $instance['bundle']; + + // Set the right module information + $widget_type = field_info_widget_types($form_values['widget_type']); + $widget_module = $widget_type['module']; + + if (drupal_function_exists('field_update_instance')) { + $instance['widget']['type'] = $form_values['widget_type']; + $instance['widget']['module'] = $widget_module; + try { + field_update_instance($instance); + drupal_set_message(t('Changed the widget for field %label.', array( + '%label' => $instance['label']))); + } catch (FieldException $e) { + drupal_set_message(t('There was a problem changing the widget for field %label.', array( + '%label' => $instance['label']))); + } + } + else { + drupal_set_message(t('There was a problem changing the widget for field %label.', array( + '%label' => $instance['label']))); + } + + $form_state['redirect'] = cck_next_destination($form_state, $bundle); +} + +/** + * Menu callback; present a form for removing a field from a content type. + */ +function cck_field_remove_form(&$form_state, $bundle, $field_name) { + $field = field_info_field($field_name); + $instance = field_info_instance($field_name, $bundle); + $admin_path = cck_bundle_admin_path($bundle); + + $form = array(); + $form['bundle'] = array( + '#type' => 'value', + '#value' => $bundle, + ); + $form['field_name'] = array( + '#type' => 'value', + '#value' => $field_name, + ); + + $output = confirm_form($form, + t('Are you sure you want to remove the field %field?', array('%field' => $instance['label'])), + $admin_path .'/fields', + t('If you have any content left in this field, it will be lost. This action cannot be undone.'), + t('Remove'), t('Cancel'), + 'confirm' + ); + + if ($field['locked']) { + unset($output['actions']['submit']); + $output['description']['#markup'] = t('This field is locked and cannot be removed.'); + } + + return $output; +} + +/** + * Remove a field from a content type. + */ +function cck_field_remove_form_submit($form, &$form_state) { + $form_values = $form_state['values']; + $field = field_info_field($form_values['field_name']); + $instance = field_info_instance($form_values['field_name'], $form_values['bundle']); + $bundles = field_info_bundles(); + $bundle = $form_values['bundle']; + $bundle_label = $bundles[$bundle]; + + if ($field['locked']) { + return; + } + + $error = FALSE; + if (!empty($bundle) && $field && $form_values['confirm'] && drupal_function_exists('field_delete_instance')) { + try { + field_delete_instance($form_values['field_name'], $form_values['bundle']); + } catch (FieldException $e) { + $error = TRUE; + } + } + else { + $error = TRUE; + } + if ($error) { + drupal_set_message(t('There was a problem removing the %field from %type.', array( + '%field' => $instance['label'], + '%type' => $bundle_label))); + } + else { + drupal_set_message(t('The field %field has been removed from %type.', array( + '%field' => $instance['label'], + '%type' => $bundle_label))); + } + $form_state['redirect'] = cck_next_destination($form_state, $bundle); +} + +/** + * Menu callback; presents the field instance edit page. + */ +function cck_field_edit_form(&$form_state, $bundle, $field_name) { + $output = ''; + $instance = field_read_instance($field_name, $bundle); + $field = field_read_field($field_name); + + $form = array(); + + if (!empty($field['locked'])) { + $output = array(); + $output['locked'] = array( + '#markup' => t('The field %field is locked and cannot be edited.', array('%field' => $instance['label'])), + ); + return $output; + } + + $field_type = field_info_field_types($field['type']); + $widget_type = field_info_widget_types($instance['widget']['type']); + $bundles = field_info_bundles(); + + $title = isset($instance['label']) ? $instance['label'] : $instance['field_name']; + drupal_set_title(check_plain($title)); + + // Create a form structure for the instance values. + $form['instance'] = array( + '#tree' => TRUE, + '#type' => 'fieldset', + '#title' => t('%type settings', array('%type' => $bundles[$bundle])), + '#description' => t('These settings apply only to the %field field when used in the %type type.', array( + '%field' => $instance['label'], '%type' => $bundles[$bundle])), + ); + + // Build the non-configurable instance values. + $form['instance']['field_name'] = array('#type' => 'value', '#value' => $field_name); + $form['instance']['bundle'] = array('#type' => 'value', '#value' => $bundle); + $form['instance']['weight'] = array('#type' => 'value', '#value' => !empty($instance['weight']) ? $instance['weight'] : 0); + + // Build the configurable instance values. + $form['instance']['label'] = array( + '#type' => 'textfield', + '#title' => t('Label'), + '#default_value' => !empty($instance['label']) ? $instance['label'] : $field_name, + '#required' => TRUE, + '#description' => t('The human-readable label for this field.'), + ); + $form['instance']['required'] = array( + '#type' => 'checkbox', + '#title' => t('Required'), + '#default_value' => !empty($instance['required']), + '#description' => t('Check if a value must be provided.'), + ); + + $form['instance']['description'] = array( + '#type' => 'textarea', + '#title' => t('Help text'), + '#default_value' => !empty($instance['description']) ? $instance['description'] : '', + '#rows' => 5, + '#description' => t('Instructions to present to the user below this field on the editing form.
Allowed HTML tags: @tags', array('@tags' => _field_filter_xss_display_allowed_tags())), + '#required' => FALSE, + ); + + // Build the widget component of the instance. + $form['instance']['widget'] = array( + 'type' => array('#type' => 'value', '#value' => $instance['widget']['type']), + 'module' => array('#type' => 'value', '#value' => $widget_type['module']), + 'active' => array('#type' => 'value', '#value' => !empty($field['instance']['widget']['active']) ? 1 : 0), + ); + + // Add additional field instance settings from the widget module. + $additions = module_invoke($widget_type['module'], 'field_instance_settings_form', $instance); + if (is_array($additions)) { + $form['instance']['settings'] = $additions; + } + + // Add additional widget settings from the widget module. + $additions = module_invoke($widget_type['module'], 'widget_settings_form', $instance); + if (is_array($additions)) { + $form['instance']['widget']['settings'] = $additions; + $form['instance']['widget']['active']['#value'] = 1; + } + + // Add handling for default value if not provided by field. + if (field_behaviors_widget('default value', $instance) == FIELD_BEHAVIOR_DEFAULT) { + // Store the original default value for use in programmed forms. + // Set '#default_value' instead of '#value' so programmed values + // can override whatever we set here. + $default_value = $instance['widget']['default_value']; + $default_value_php = $instance['widget']['default_value_php']; + $form['instance']['widget']['default_value'] = array( + '#type' => 'value', + '#default_value' => $instance['widget']['default_value'], + ); + $form['instance']['widget']['default_value_php'] = array( + '#type' => 'value', + '#default_value' => $instance['widget']['default_value_php'], + ); + + // We can't tell at the time we build the form if this is a programmed + // form or not, so we always end up adding the default value widget + // even if we won't use it. + cck_default_value_widget($field, $instance, $form, $form_state); + } + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Save settings'), + ); + + return $form; +} + +/** + * Build default value fieldset. + */ +function cck_default_value_widget($field, $instance, &$form, &$form_state) { + $form['instance']['widget']['default_value_widget'] = array( + '#type' => 'fieldset', + '#title' => t('Default value'), + '#collapsible' => FALSE, + '#tree' => TRUE, + '#description' => t('The default value for this field, used when creating new content.') + ); + + // Make sure the default value is not a required field. + $instance['required'] = FALSE; + $instance['label'] = t('Default value'); + $instance['description'] = ''; + $items = $instance['widget']['default_value']; + // Set up form info that the default value widget will need to find in the form. + $form['#fields'] = array( + $field['field_name'] => array( + 'field' => $field, + 'instance' => $instance, + ), + ); + drupal_function_exists('field_field_form'); + // TODO allow multiple values (still requires some more work on 'add more' JS handler) + $form['instance']['widget']['default_value_widget'] += field_field_form(NULL, NULL, $field, $instance, $items, $form, $form_state, 0); + $form['#fields'][$field['field_name']]['form_path'] = array('instance', 'widget', 'default_value_widget', $field['field_name']); + + // Advanced: PHP code. + $form['instance']['widget']['default_value_widget']['advanced_options'] = array( + '#type' => 'fieldset', + '#title' => t('PHP code'), + '#collapsible' => TRUE, + '#collapsed' => empty($instance['widget']['default_value_php']), + ); + + if (user_access('Use PHP input for field settings (dangerous - grant with care)')) { + foreach (array_keys($field['columns']) as $column) { + $columns[] = t("'@column' => value for @column", array('@column' => $column)); + } + $sample = t("return array(\n 0 => array(@columns),\n // You'll usually want to stop here. Provide more values\n // if you want your 'default value' to be multi-valued:\n 1 => array(@columns),\n 2 => ...\n);", array('@columns' => implode(', ', $columns))); + + $form['instance']['widget']['default_value_widget']['advanced_options']['default_value_php'] = array( + '#type' => 'textarea', + '#title' => t('Code'), + '#default_value' => isset($instance['widget']['default_value_php']) ? $instance['widget']['default_value_php'] : '', + '#rows' => 6, + '#tree' => TRUE, + '#description' => t('Advanced usage only: PHP code that returns a default value. Should not include <?php ?> delimiters. If this field is filled out, the value returned by this code will override any value specified above. Expected format:
!sample
To figure out the expected format, you can use the devel load tab provided by devel module on a %type content page. The option to embed PHP code in the field definition is provided for backwards compatibility and could be deprecated in the future. It is strongly recommended that you move this code to a custom function in a custom module and simply identify the custom function in the box above!', array( + '!sample' => $sample, + '@link_devel' => 'http://www.drupal.org/project/devel', + '%type' => $instance['bundle'])), + ); + } + else { + $form['instance']['widget']['default_value_widget']['advanced_options']['markup_default_value_php'] = array( + '#type' => 'item', + '#title' => t('Code'), + '#value' => !empty($instance['widget']['default_value_php']) ? ''. check_plain($instance['widget']['default_value_php']) .'' : t('<none>'), + '#description' => empty($instance['widget']['default_value_php']) ? t("You're not allowed to input PHP code.") : t('This PHP code was set by an administrator and will override any value specified above.'), + ); + } +} + +/** + * Validate a field's settings. + */ +function cck_field_edit_form_validate($form, &$form_state) { + + $form_values = $form_state['values']; + $instance = $form_values['instance']; + $field = field_info_field($instance['field_name']); + + $field_type = field_info_field_types($field['type']); + $widget_type = field_info_widget_types($instance['widget']['type']); + + // TODO D7 : deprecate hook_*_settings_form_validate in favor of regular FAPI validation ? + module_invoke($widget_type['module'], 'widget_settings_form_validate', array_merge($instance, $form_values)); + module_invoke($field_type['module'], 'field_settings_form_validate', array_merge($instance, $form_values)); + + // If field.module is handling the default value, + // validate the result using the field validation. + if (field_behaviors_widget('default value', $instance) == FIELD_BEHAVIOR_DEFAULT) { + + // If this is a programmed form, get rid of the default value widget, + // we have the default values already. + if ($form['#programmed']) { + form_set_value(array('#parents' => array('instance', 'widget', 'default_value_widget')), NULL, $form_state); + return; + } + + if (isset($form_values['instance']['widget']['default_value_php']) && + ($php = trim($form_values['instance']['widget']['default_value_php']))) { + ob_start(); + $return = eval($php); + ob_end_clean(); + if (!is_array($return)) { + $error = TRUE; + } + else { + foreach ($return as $item) { + if (!is_array($item)) { + $error = TRUE; + break; + } + } + } + if ($error) { + foreach (array_keys($field['columns']) as $column) { + $columns[] = t("'@column' => value for @column", array('@column' => $column)); + } + $sample = t("return array(\n 0 => array(@columns),\n You'll usually want to stop here. Provide more values\n if you want your 'default value' to be multi-valued:\n 1 => array(@columns),\n 2 => ...\n);", array('@columns' => implode(', ', $columns))); + + form_set_error('default_value_php', t('The default value PHP code returned an incorrect value.
Expected format:
!sample
Returned value: @value', array( + '!sample' => $sample, + '@value' => print_r($return, TRUE)))); + return; + } + else { + $default_value = $return; + $is_code = TRUE; + form_set_value(array('#parents' => array('instance', 'widget', 'default_value_php')), $php, $form_state); + form_set_value(array('#parents' => array('instance', 'widget', 'default_value')), array(), $form_state); + } + } + elseif (!empty($form_values['instance']['widget']['default_value_widget'])) { + // Fields that handle their own multiple values may use an expected + // value as the top-level key, so just pop off the top element. + $key = array_shift(array_keys($form_values['instance']['widget']['default_value_widget'])); + $default_value = $form_values['instance']['widget']['default_value_widget'][$key]; + $is_code = FALSE; + form_set_value(array('#parents' => array('instance', 'widget', 'default_value_php')), '', $form_state); + form_set_value(array('#parents' => array('instance', 'widget', 'default_value')), $default_value, $form_state); + } + if (isset($default_value)) { + $node = array(); + $node[$field['field_name']] = $default_value; + $field['required'] = FALSE; + $field_function = $field_type['module'] .'_field'; + + $errors_before = form_get_errors(); + + // Widget now does its own validation, should be no need + // to add anything for widget validation here. + if (drupal_function_exists($field_function)) { + $field_function('validate', $node, $field, $default_value, $form, NULL); + } + // The field validation routine won't set an error on the right field, + // so set it here. + $errors_after = form_get_errors(); + if (count($errors_after) > count($errors_before)) { + if (trim($form_values['default_value_php'])) { + form_set_error('default_value_php', t("The PHP code for 'default value' returned @value, which is invalid.", array( + '@value' => print_r($default_value, TRUE)))); + } + else { + form_set_error('default_value', t('The default value is invalid.')); + } + } + } + } +} + +/** + * Save instance settings after editing. + */ +function cck_field_edit_form_submit($form, &$form_state) { + $form_values = $form_state['values']; + $instance = $form_values['instance']; + + // Make sure the default value widget does not get stored. + if (isset($instance['widget']['default_value_widget'])) { + unset($instance['widget']['default_value_widget']); + } + + // TODO, move this to cck.text.inc and cck.number.inc?? + // Make sure the allowed values fieldset does not get stored. + if (isset($instance['settings']['allowed_values_fieldset'])) { + $instance['settings'] += $instance['settings']['allowed_values_fieldset']; + unset($instance['settings']['allowed_values_fieldset']); + } + + // Update the instance. + module_load_include('inc', 'field', 'includes/field.crud'); + field_update_instance($instance); + + drupal_set_message(t('Saved %label configuration.', array('%label' => $instance['label']))); + + $form_state['redirect'] = cck_next_destination($form_state, $instance['bundle']); +} + +/** + * Helper functions to handle multipage redirects. + */ +function cck_get_destinations($destinations) { + $query = array(); + $path = array_shift($destinations); + if ($destinations) { + $query['destinations'] = $destinations; + } + return array($path, $query); +} + +function cck_next_destination(&$form_state, $bundle) { + $destinations = !empty($_REQUEST['destinations']) ? $_REQUEST['destinations'] : array(); + if (!empty($destinations)) { + unset($_REQUEST['destinations']); + return cck_get_destinations($destinations); + } + else { + $admin_path = cck_bundle_admin_path($bundle); + return $admin_path .'/fields'; + } +} + +/** + * Dummy function to force a page refresh so + * menu_rebuild() will work right when creating a new field + * that creates a new menu item. + */ +function cck_field_menu_refresh(&$form_state, $bundle) { + menu_rebuild(); + $destinations = cck_next_destination($form_state, $bundle); + if (is_array($destinations)) { + $path = array_shift($destinations); + drupal_goto($path, $destinations); + } + else { + drupal_goto($destinations); + } +} + +/** + * Helper function to order fields and groups when theming (preprocessing) + * overview forms. + * + * The $form is passed by reference because we assign depths as parenting + * relationships are sorted out. + */ +function _cck_overview_order(&$form, $field_rows, $group_rows) { + // Put weight and parenting values into a $dummy render structure + // and let drupal_render figure out the corresponding row order. + $dummy = array(); + // Group rows: account for weight. + if (module_exists('fieldgroup')) { + foreach ($group_rows as $name) { + $dummy[$name] = array('#markup' => $name .' ', '#weight' => $form[$name]['weight']['#value']); + } + } + // Field rows : account for weight and parenting. + foreach ($field_rows as $name) { + $dummy[$name] = array('#markup' => $name .' ', '#type' => 'markup', '#weight' => $form[$name]['weight']['#value']); + if (module_exists('fieldgroup')) { + if ($parent = $form[$name]['parent']['#value']) { + $form[$name]['#depth'] = 1; + $dummy[$parent][$name] = $dummy[$name]; + unset($dummy[$name]); + } + } + } + return $dummy ? explode(' ', trim(drupal_render($dummy))) : array(); +} + +/** + * Helper form element validator : integer. + */ +function _element_validate_integer($element, &$form_state) { + $value = $element['#value']; + if ($value !== '' && (!is_numeric($value) || intval($value) != $value)) { + form_error($element, t('%name must be an integer.', array('%name' => $element['#title']))); + } +} + +/** + * Helper form element validator : integer > 0. + */ +function _element_validate_integer_positive($element, &$form_state) { + $value = $element['#value']; + if ($value !== '' && (!is_numeric($value) || intval($value) != $value || $value <= 0)) { + form_error($element, t('%name must be a positive integer.', array('%name' => $element['#title']))); + } +} + +/** + * Helper form element validator : number. + */ +function _element_validate_number($element, &$form_state) { + $value = $element['#value']; + if ($value != '' && !is_numeric($value)) { + form_error($element, t('%name must be a number.', array('%name' => $element['#title']))); + } +} \ No newline at end of file diff --git a/includes/cck.devel.inc b/includes/cck.devel.inc new file mode 100644 index 0000000000000000000000000000000000000000..856a7b1dc66d5cd8952b07d9cd3a3687cce9fcaa --- /dev/null +++ b/includes/cck.devel.inc @@ -0,0 +1,197 @@ +{$field['field_name']} = $object_field; + } +} + +/** + * A simple function to return multiple values for fields that use + * custom multiple value widgets but don't need any other special multiple + * values handling. This will call the field generation function + * a random number of times and compile the results into a node array. + */ +function cck_devel_multiple($function, $object, $field, $instance, $bundle) { + $object_field = array(); + if (function_exists($function)) { + switch ($field['cardinality']) { + case FIELD_CARDINALITY_UNLIMITED: + $max = rand(0, 3); //just an arbitrary number for 'unlimited' + break; + default: + $max = $field['cardinality'] - 1; + break; + } + for ($i = 0; $i <= $max; $i++) { + $object_field[$i] = $function($object, $field, $instance, $bundle); + } + } + return $object_field; +} + +if (module_exists('text')) { + function text_cck_generate($object, $field, $instance, $bundle) { + if (field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_CUSTOM) { + return cck_devel_multiple('_text_cck_generate', $object, $field, $instance, $bundle); + } + else { + return _text_cck_generate($object, $field, $instance, $bundle); + } + } + + function _text_cck_generate($object, $field, $instance, $bundle) { + $object_field = array(); + if ($instance['widget']['type'] == 'text_textarea') { + $object_field['value'] = devel_create_content($format); + } + else { + // Generate a value that respects max_length. + if (empty($field['max_length'])) { + $field['max_length'] = 12; + } + $object_field['value'] = user_password($field['max_length']); + } + $format = isset($field['text_processing']) ? rand(0, 3) : 0; + $object_field['format'] = $format; + return $object_field; + } +} + +if (module_exists('number')) { + function number_cck_generate($object, $field, $instance, $bundle) { + if (field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_CUSTOM) { + return cck_devel_multiple('_number_cck_generate', $object, $field, $instance, $bundle); + } + else { + return _number_cck_generate($object, $field, $instance, $bundle); + } + } + + function _number_cck_generate($object, $field, $instance, $bundle) { + $object_field = array(); + // Make sure the field settings are all set. + foreach (array('min', 'max', 'precision', 'scale') as $key) { + if (empty($field[$key])) { + $field[$key] = NULL; + } + } + $min = is_numeric($field['min']) ? $field['min'] : 0; + switch ($field['type']) { + case 'number_integer': + $max = is_numeric($field['max']) ? $field['max'] : 10000; + $decimal = 0; + $scale = 0; + break; + + case 'number_decimal': + $precision = is_numeric($field['precision']) ? $field['precision'] : 10; + $scale = is_numeric($field['scale']) ? $field['scale'] : 2; + $max = is_numeric($field['max']) ? $field['max'] : pow(10, ($precision - $scale)); + $decimal = rand(0, (10 * $scale)) / 100; + break; + + case 'number_float': + $precision = rand(10, 32); + $scale = rand(0, 2); + $decimal = rand(0, (10 * $scale)) / 100; + $max = is_numeric($field['max']) ? $field['max'] : pow(10, ($precision - $scale)); + break; + } + $object_field['value'] = round((rand($min, $max) + $decimal), $scale); + return $object_field; + } +} + +if (module_exists('nodereference')) { + function nodereference_cck_generate($object, $field, $instance, $bundle) { + if (field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_CUSTOM) { + return cck_devel_multiple('_nodereference_cck_generate', $object, $field, $instance, $bundle); + } + else { + return _nodereference_cck_generate($object, $field, $instance, $bundle); + } + } + + function _nodereference_cck_generate($object, $field, $instance, $bundle) { + $object_field = array(); + $allowed_values = nodereference_allowed_values($field); + unset($allowed_values[0]); + if (!empty($allowed_values)) { + // Just pick one of the specified allowed values. + $object_field['nid'] = array_rand($allowed_values); + } + return $object_field; + } +} + +if (module_exists('userreference')) { + function userreference_cck_generate($object, $field) { + if (field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_CUSTOM) { + return cck_devel_multiple('_userreference_cck_generate', $object, $field); + } + else { + return _userreference_cck_generate($object, $field); + } + } + + function _userreference_cck_generate($object, $field) { + $object_field = array(); + $allowed_values = userreference_allowed_values($field); + if (isset($allowed_values['none'])) { + unset($allowed_values['none']); + } + if (!empty($allowed_values)) { + // Just pick one of the specified allowed values. + $object_field['uid'] = array_rand($allowed_values); + } + return $object_field; + } +} \ No newline at end of file diff --git a/includes/cck.list.inc b/includes/cck.list.inc new file mode 100644 index 0000000000000000000000000000000000000000..5987b88e8f39df7084d04e9624a80134afb894da --- /dev/null +++ b/includes/cck.list.inc @@ -0,0 +1,103 @@ + $field['field_name']))->fetchField(); +} + +/** + * A custom function to return allowed values from PHP code. + */ +function cck_list_allowed_values_php($field) { + $allowed_values = array(); + $php = cck_list_get_allowed_values_php($field); + if (!empty($php)) { + ob_start(); + $result = eval($php); + if (is_array($result)) { + $allowed_values = $result; + } + ob_end_clean(); + } + return $allowed_values; +} + +/** + * Implementation of hook_field_settings_form() + * on behalf of core List module. + */ +function list_field_settings_form($field, $instance) { + + $form = array( + '#element_validate' => array('list_field_settings_form_validate'), + ); + + $defaults = list_field_settings($field['type']); + $settings = array_merge($defaults, $field['settings']); + $settings['allowed_values_php'] = cck_list_get_allowed_values_php($field); + + $form['allowed_values'] = array( + '#type' => 'textarea', + '#title' => t('Allowed values list'), + '#default_value' => $settings['allowed_values'], + '#required' => FALSE, + '#rows' => 10, + '#description' => t('The possible values this field can contain. Enter one value per line, in the format key|label. The key is the value that will be stored in the database, and must be a %type value. The label is optional, and the key will be used as the label if no label is specified.
Allowed HTML tags in labels: @tags', array('%type' => $field['type'] == 'list_text' ? 'text' : 'numeric', '@tags' => _field_filter_xss_display_allowed_tags())), + ); + $form['allowed_values_function'] = array( + '#type' => 'textfield', + '#title' => t('Allowed values function'), + '#default_value' => $settings['allowed_values_function'], + '#description' => t('The name of a function that will return the allowed values list. If this field is filled out, the array returned by this code will override the allowed values list above.'), + ); + $form['advanced_options'] = array( + '#type' => 'fieldset', + '#title' => t('PHP code for allowed values'), + '#collapsible' => TRUE, + '#collapsed' => empty($settings['allowed_values_php']), + ); + if (user_access('Use PHP input for field settings (dangerous - grant with care)')) { + $form['advanced_options']['allowed_values_php'] = array( + '#type' => 'textarea', + '#title' => t('Code'), + '#default_value' => $settings['allowed_values_php'], + '#rows' => 6, + '#description' => t("Advanced usage only: PHP code that returns a keyed array of allowed values. Should not include <?php ?> delimiters. If this field is filled out, the array returned by this code will override the allowed values list, and the allowed values function will be set to 'cck_list_allowed_values_php' to execute this custom code. The option to embed PHP code in the field definition is provided for backwards compatibility and could be deprecated in the future. It is strongly recommended that you move this code to a custom function in a custom module and simply identify the custom function in the box above!"), + ); + } + else { + $form['advanced_options']['markup_allowed_values_php'] = array( + '#type' => 'item', + '#title' => t('Code'), + '#value' => !empty($settings['allowed_values_php']) ? ''. check_plain($settings['allowed_values_php']) .'' : t('<none>'), + '#description' => empty($settings['allowed_values_php']) ? t("You're not allowed to input PHP code.") : t('This PHP code was set by an administrator and will override the allowed values list and allowed values functions shown above.'), + ); + } + return $form; +} + +/** + * Handle Allowed values PHP code. + */ +function list_field_settings_form_validate($form, &$form_state) { + $form_values = $form_state['values']; + $field = $form_values['field']; + $field_name = $field['field_name']; + $option = $form_values['field']['settings']['advanced_options']['allowed_values_php']; + db_query("DELETE FROM {cck_field_settings} WHERE setting='field' AND setting_type='allowed_values_php' AND field_name = ':field_name'", array(':field_name' => $field['field_name'])); + if (!empty($option)) { + $record = array( + 'field_name' => $field_name, + 'bundle' => NULL, + 'setting_type' => 'field', + 'setting' => 'allowed_values_php', + 'setting_option' => $option, + ); + drupal_write_record('cck_field_settings', $record, array()); + form_set_value($form['allowed_values_function'], 'cck_list_allowed_values_php', $form_state); + form_set_value($form['advanced_options'], NULL, $form_state); + } +} diff --git a/includes/cck.number.inc b/includes/cck.number.inc new file mode 100644 index 0000000000000000000000000000000000000000..37770f3f88be5b43ec62837c7fd0af53dfed717e --- /dev/null +++ b/includes/cck.number.inc @@ -0,0 +1,71 @@ + 'select', + '#options' => drupal_map_assoc(range(10, 32)), + '#title' => t('Precision'), + '#description' => t('The total number of digits to store in the database, including those to the right of the decimal.'), + '#default_value' => $field['settings']['precision'], + ); + $form['scale'] = array( + '#type' => 'select', + '#options' => drupal_map_assoc(range(0, 6)), + '#title' => t('Scale'), + '#description' => t('The number of digits to the right of the decimal.'), + '#default_value' => $field['settings']['scale'], + ); + $form['decimal'] = array( + '#type' => 'select', + '#options' => array('.' => 'decimal point', ',' => 'comma', ' ' => 'space'), + '#title' => t('Decimal marker'), + '#description' => t('The character users will input to mark the decimal point in forms.'), + '#default_value' => $field['settings']['decimal'], + ); + } + return $form; +} + +function number_field_instance_settings_form($instance) { + $widget = $instance['widget']; + $field = field_info_field($instance['field_name']); + $field_type = field_info_field_types($field['type']); + $defaults = field_info_instance_settings($field['type']); + $settings = array_merge($defaults, $instance['settings']); + + $form['min'] = array( + '#type' => 'textfield', + '#title' => t('Minimum'), + '#element_validate' => array('_element_validate_number'), + '#default_value' => $instance['settings']['min'], + '#description' => t('The minimum value that should be allowed in this field. Leave blank for no minimum.') + ); + $form['max'] = array( + '#type' => 'textfield', + '#title' => t('Maximum'), + '#element_validate' => array('_element_validate_number'), + '#default_value' => $instance['settings']['max'], + '#description' => t('The maximum value that should be allowed in this field. Leave blank for no maximum.') + ); + $form['prefix'] = array( + '#type' => 'textfield', + '#title' => t('Prefix'), + '#size' => 60, + '#default_value' => $instance['settings']['prefix'], + '#description' => t('Define a string that should be prefixed to the value, like $ or €. Leave blank for none. Separate singular and plural values with a pipe (pound|pounds).'), + ); + $form['suffix'] = array( + '#type' => 'textfield', + '#title' => t('Suffix'), + '#size' => 60, + '#default_value' => $instance['settings']['suffix'], + '#description' => t('Define a string that should suffixed to the value, like m², m/s², kb/s. Leave blank for none. Separate singular and plural values with a pipe (pound|pounds).'), + ); + return $form; +} \ No newline at end of file diff --git a/includes/cck.text.inc b/includes/cck.text.inc new file mode 100644 index 0000000000000000000000000000000000000000..f27944379be53119d1a216d448e244287fcbd288 --- /dev/null +++ b/includes/cck.text.inc @@ -0,0 +1,72 @@ + 'textfield', + '#title' => t('Maximum length'), + '#default_value' => !empty($settings['max_length']) && is_numeric($settings['max_length']) ? $settings['max_length'] : $defaults['max_length'], + '#required' => FALSE, + '#element_validate' => array('_element_validate_integer_positive'), + '#description' => t('The maximum length of the field in characters. Leave blank for an unlimited size.'), + ); + + return $form; +} + +/** + * Implementation of hook_widget_settings_form() + * on behalf of core Text module. + */ +function text_widget_settings_form($instance) { + $widget = $instance['widget']; + $defaults = field_info_widget_settings($widget['type']); + $settings = array_merge($defaults, $widget['settings']); + $field = field_info_field($instance['field_name']); + if ($widget['type'] == 'text_textfield') { + $form['size'] = array( + '#type' => 'textfield', + '#title' => t('Size of textfield'), + '#default_value' => $settings['size'], + '#element_validate' => array('_element_validate_integer_positive'), + '#required' => TRUE, + ); + } + else { + $form['rows'] = array( + '#type' => 'textfield', + '#title' => t('Rows'), + '#default_value' => $settings['rows'], + '#element_validate' => array('_element_validate_integer_positive'), + '#required' => TRUE, + ); + } + return $form; +} + +/** + * Implementation of hook_field_instance_settings_form() + * on behalf of core Text module. + */ +function text_field_instance_settings_form($instance) { + $widget = $instance['widget']; + $field = field_info_field($instance['field_name']); + $field_type = field_info_field_types($field['type']); + $defaults = field_info_instance_settings($field['type']); + $settings = array_merge($defaults, $instance['settings']); + $options = array(0 => t('Plain text'), 1 => t('Filtered text (user selects input format)')); + $form['text_processing'] = array( + '#type' => 'radios', + '#title' => t('Text processing'), + '#default_value' => !empty($settings['text_processing']) && is_numeric($settings['text_processing']) ? $settings['text_processing'] : $defaults['text_processing'], + '#options' => $options, + ); + return $form; +} \ No newline at end of file diff --git a/modules/fieldgroup/fieldgroup.css b/modules/fieldgroup/fieldgroup.css new file mode 100644 index 0000000000000000000000000000000000000000..32454a0be1b42f4f525314b40ce8b62ffafb7cf5 --- /dev/null +++ b/modules/fieldgroup/fieldgroup.css @@ -0,0 +1,6 @@ +div.fieldgroup { + margin:.5em 0 1em 0; +} +div.fieldgroup .content { + padding-left:1em; +} diff --git a/modules/fieldgroup/fieldgroup.info b/modules/fieldgroup/fieldgroup.info new file mode 100644 index 0000000000000000000000000000000000000000..901e0f0b78e90739e5c9dcfbd0fa6cf1a673e1f9 --- /dev/null +++ b/modules/fieldgroup/fieldgroup.info @@ -0,0 +1,7 @@ +; $Id$ +name = Fieldgroup +description = Create display groups for CCK fields. +dependencies[] = cck +package = CCK +core = 7.x +files[]=fieldgroup.module \ No newline at end of file diff --git a/modules/fieldgroup/fieldgroup.install b/modules/fieldgroup/fieldgroup.install new file mode 100644 index 0000000000000000000000000000000000000000..280b58c9ab8f854bb043248e3ea7e069b6effef2 --- /dev/null +++ b/modules/fieldgroup/fieldgroup.install @@ -0,0 +1,46 @@ + array( + 'group_type' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => 'standard'), + 'bundle' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''), + 'group_name' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''), + 'label' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''), + 'settings' => array('type' => 'text', 'size' => 'medium', 'not null' => TRUE), + 'weight' => array('type' => 'int', 'not null' => TRUE, 'default' => 0), + ), + 'primary key' => array('bundle', 'group_name'), + ); + + $schema['field_group_fields'] = array( + 'fields' => array( + 'bundle' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''), + 'group_name' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''), + 'field_name' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''), + ), + 'primary key' => array('bundle', 'group_name', 'field_name'), + ); + + return $schema; +} \ No newline at end of file diff --git a/modules/fieldgroup/fieldgroup.module b/modules/fieldgroup/fieldgroup.module new file mode 100644 index 0000000000000000000000000000000000000000..8eef761e94f8093aa569f07afd61a65509b70876 --- /dev/null +++ b/modules/fieldgroup/fieldgroup.module @@ -0,0 +1,768 @@ +content. + * - hook_fieldgroup_form: Alter the group portion of the node form. + * - hook_fieldgroup_types: Add additional fieldgroup group_types. + * - hook_fieldgroup_default_settings: Add additional fieldgroup default settings. + * - hook_fieldgroup_save: Do additional processing when a fieldgroup is saved. + */ +/** + * Implementation of hook_init(). + */ +function fieldgroup_init() { + drupal_add_css(drupal_get_path('module', 'fieldgroup') .'/fieldgroup.css'); +} + +/** + * Implementation of hook_menu(). + */ +function fieldgroup_menu() { + $items = array(); + + // Make sure this doesn't fire until field_info_fieldable_types() is working, + // needed to avoid errors on initial installation. + if (!defined('MAINTENANCE_MODE')) { + // Create tabs for all possible bundles. + $bundles = field_info_bundles(); + foreach ($bundles as $bundle_name => $bundle_label) { + $admin_path = cck_bundle_admin_path($bundle_name); + $items[$admin_path .'/groups/%'] = array( + 'title' => 'Edit group', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('fieldgroup_group_edit_form', $bundle_name, 5), + 'access arguments' => array('administer content types'), + 'type' => MENU_CALLBACK, + ); + $items[$admin_path .'/groups/%/remove'] = array( + 'title' => 'Edit group', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('fieldgroup_remove_group', $bundle_name, 5), + 'access arguments' => array('administer content types'), + 'type' => MENU_CALLBACK, + ); + } + } + return $items; +} + +/** + * Implementation of hook_theme(). + */ +function fieldgroup_theme() { + return array( + 'fieldgroup_simple' => array( + 'template' => 'fieldgroup', + 'arguments' => array('element' => NULL), + ), + 'fieldgroup_fieldset' => array( + 'arguments' => array('element' => NULL), + ), + 'fieldgroup_display_overview_form' => array( + 'arguments' => array('form' => NULL), + ), + ); +} + +/** + * Implementation of hook_elements(). + */ +function fieldgroup_elements() { + return array( + 'fieldgroup_simple' => array(), + 'fieldgroup_fieldset' => array('#collapsible' => FALSE, '#collapsed' => FALSE, '#value' => NULL,), + ); +} + +/** + * Implementation of hook_fieldapi(). + */ +function fieldgroup_field_fieldapi($op, $field) { + switch ($op) { + case 'delete instance': + db_query("DELETE FROM {field_group_fields} WHERE field_name = '%s'", $field['field_name']); + break; + } + cache_clear_all('fieldgroup_data', 'cache_field'); +} + +function fieldgroup_group_edit_form(&$form_state, $bundle, $group_name) { + $groups = fieldgroup_groups($bundle); + + if (!$group = $groups[$group_name]) { + drupal_not_found(); + exit; + } + + $form['label'] = array( + '#type' => 'textfield', + '#title' => t('Label'), + '#default_value' => $group['label'], + '#required' => TRUE, + ); + + // Set a default value for group type early in the form so it + // can be overridden by subsequent form elements added by other modules. + $group_type = !empty($group['group_type']) ? $group['group_type'] : 'standard'; + $form['group_type'] = array('#type' => 'hidden', '#default_value' => $group_type); + + $form['settings']['#tree'] = TRUE; + $form['settings']['form'] = array( + '#type' => 'fieldset', + '#title' => t('Form settings'), + '#description' => t('These settings apply to the group in the node editing form.'), + ); + $form['settings']['form']['style'] = array( + '#type' => 'radios', + '#title' => t('Style'), + '#default_value' => $group['settings']['form']['style'], + '#options' => array( + 'fieldset' => t('always open'), + 'fieldset_collapsible' => t('collapsible'), + 'fieldset_collapsed' => t('collapsed'), + ) + ); + $form['settings']['form']['description'] = array( + '#type' => 'textarea', + '#title' => t('Help text'), + '#default_value' => $group['settings']['form']['description'], + '#rows' => 5, + '#description' => t('Instructions to present to the user on the editing form.'), + '#required' => FALSE, + ); + $form['settings']['display'] = array( + '#type' => 'fieldset', + '#title' => t('Display settings'), + '#description' => t('These settings apply to the group on the display.'), + ); + $form['settings']['display']['description'] = array( + '#type' => 'textarea', + '#title' => t('Description'), + '#default_value' => $group['settings']['display']['description'], + '#rows' => 5, + '#description' => t('A description of the group.'), + '#required' => FALSE, + ); + + foreach (array_keys(field_build_modes(field_info_bundle_entity($bundle))) as $key) { + $form['settings']['display'][$key]['format'] = array('#type' => 'value', '#value' => isset($group['settings']['display'][$key]['format']) ? $group['settings']['display'][$key]['format'] : 'fieldset'); + $form['settings']['display'][$key]['exclude'] = array('#type' => 'value', '#value' => isset($group['settings']['display'][$key]['exclude']) ? $group['settings']['display'][$key]['exclude'] : 0); + } + + $form['settings']['display']['label'] = array('#type' => 'value', '#value' => $group['settings']['display']['label']); + $form['weight'] = array('#type' => 'hidden', '#default_value' => $group['weight']); + $form['group_name'] = array('#type' => 'hidden', '#default_value' => $group_name); + + $form['#bundle'] = $bundle; + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Save'), + '#weight' => 10, + ); + + return $form; +} + +function fieldgroup_group_edit_form_submit($form, &$form_state) { + $form_values = $form_state['values']; + $bundle = $form['#bundle']; + fieldgroup_save_group($bundle, $form_values); + $form_state['redirect'] = cck_bundle_admin_path($bundle) .'/fields'; +} + +function fieldgroup_remove_group(&$form_state, $bundle, $group_name) { + $groups = fieldgroup_groups($bundle); + $group = isset($groups[$group_name]) ? $groups[$group_name] : ''; + + if (empty($group)) { + drupal_not_found(); + exit; + } + + $form['#submit'][] = 'fieldgroup_remove_group_submit'; + $form['#bundle'] = $bundle; + $form['#group_name'] = $group_name; + + return confirm_form($form, + t('Are you sure you want to remove the group %label?', + array('%label' => t($group['label']))), + cck_bundle_admin_path($bundle) .'/fields', t('This action cannot be undone.'), + t('Remove'), t('Cancel')); +} + +function fieldgroup_remove_group_submit($form, &$form_state) { + $form_values = $form_state['values']; + $bundle = $form['#bundle']; + $group_name = $form['#group_name']; + fieldgroup_delete($bundle, $group_name); + drupal_set_message(t('The group %group_name has been removed.', array('%group_name' => $group_name))); + $form_state['redirect'] = cck_bundle_admin_path($bundle) .'/fields'; +} + +/* + * Returns all groups for a content type + */ +function fieldgroup_groups($bundle = '', $sorted = FALSE, $reset = FALSE) { + static $groups, $groups_sorted; + if (!isset($groups) || $reset) { + if ($cached = cache_get('fieldgroup_data', 'cache_field')) { + $data = $cached->data; + $groups = $data['groups']; + $groups_sorted = $data['groups_sorted']; + } + else { + $result = db_query("SELECT * FROM {field_group} ORDER BY weight, group_name"); + $groups = array(); + $groups_sorted = array(); + while ($group = db_fetch_array($result)) { + $group['settings'] = unserialize($group['settings']); + $group['fields'] = array(); + $groups[$group['bundle']][$group['group_name']] = $group; + $groups_sorted[$group['bundle']][] = &$groups[$group['bundle']][$group['group_name']]; + } + //load fields + $result = db_query("SELECT nfi.*, ng.group_name FROM {field_group} ng ". + "INNER JOIN {field_group_fields} ngf ON ngf.bundle = ng.bundle AND ngf.group_name = ng.group_name ". + "INNER JOIN {field_config_instance} nfi ON nfi.field_name = ngf.field_name AND nfi.bundle = ngf.bundle ". + "WHERE nfi.widget_active = 1 ORDER BY nfi.weight"); + while ($field = db_fetch_array($result)) { + $groups[$field['bundle']][$field['group_name']]['fields'][$field['field_name']] = $field; + } + cache_set('fieldgroup_data', array('groups' => $groups, 'groups_sorted' => $groups_sorted), 'cache_field'); + } + } + if (empty($bundle)) { + return $groups; + } + elseif (empty($groups) || empty($groups[$bundle])) { + return array(); + } + return $sorted ? $groups_sorted[$bundle] : $groups[$bundle]; +} + + +function _fieldgroup_groups_label($bundle) { + $groups = fieldgroup_groups($bundle); + + $labels[''] = '<'. t('none') .'>'; + foreach ($groups as $group_name => $group) { + $labels[$group_name] = t($group['label']); + } + return $labels; +} + +function _fieldgroup_field_get_group($bundle, $field_name) { + return db_result(db_query("SELECT group_name FROM {field_group_fields} WHERE bundle = '%s' AND field_name = '%s'", $bundle, $field_name)); +} + +/** + * Implementation of hook_form_alter() + */ +function fieldgroup_form_alter(&$form, $form_state, $form_id) { + // TODO This only works right on a node form, need to make it more general. + if (!empty($form['#fields']) && !empty($form['type'])) { + foreach (fieldgroup_groups($form['type']['#value']) as $group_name => $group) { + $form[$group_name] = array( + '#type' => 'fieldset', + '#title' => check_plain(t($group['label'])), + '#collapsed' => $group['settings']['form']['style'] == 'fieldset_collapsed', + '#collapsible' => in_array($group['settings']['form']['style'], array('fieldset_collapsed', 'fieldset_collapsible')), + '#weight' => $group['weight'], + '#description' => field_filter_xss(t($group['settings']['form']['description'])), + '#attributes' => array('class' => strtr($group['group_name'], '_', '-')), + ); + + $has_accessible_field = FALSE; + foreach ($group['fields'] as $field_name => $field) { + if (isset($form[$field_name])) { + // Track whether this group has any accessible fields within it. + if (!isset($form[$field_name]['#access']) || $form[$field_name]['#access'] !== FALSE) { + $has_accessible_field = TRUE; + } + // Move the form element. + $form[$group_name][$field_name] = $form[$field_name]; + unset($form[$field_name]); + $form['#fields'][$field_name]['form_path'] = array($group_name, $field_name); + } + } + if (!empty($group['fields']) && !element_children($form[$group_name])) { + //hide the fieldgroup, because the fields are hidden too + unset($form[$group_name]); + } + + if (!$has_accessible_field) { + // Hide the fieldgroup, because the fields are inaccessible. + $form[$group_name]['#access'] = FALSE; + } + + // Allow other modules to alter the form. + // Can't use module_invoke_all because we want + // to be able to use a reference to $form and $form_state. + foreach (module_implements('fieldgroup_form') as $module) { + $function = $module .'_fieldgroup_form'; + $function($form, $form_state, $form_id, $group); + } + + } + + } + // The group is only added here so it will appear in the export + // when using Content Copy. + elseif ($form_id == 'cck_field_edit_form' && isset($form['widget'])) { + $bundle = $form['bundle']['#value']; + $form['widget']['group'] = array( + '#type' => 'value', + '#value' => _fieldgroup_field_get_group($bundle, $form['field_name']['#value']), + ); + } + elseif ($form_id == 'cck_field_overview_form') { + $form['#validate'][] = 'fieldgroup_field_overview_form_validate'; + $form['#submit'][] = 'fieldgroup_field_overview_form_submit'; + } + elseif ($form_id == 'cck_display_overview_form' && !empty($form['#groups'])) { + $form['#submit'][] = 'fieldgroup_display_overview_form_submit'; + if (!isset($form['submit'])) { + $form['submit'] = array('#type' => 'submit', '#value' => t('Save'), '#weight' => 10); + } + } + elseif ($form_id == 'cck_field_remove_form') { + $form['#submit'][] = 'fieldgroup_field_remove_form_submit'; + } +} + +/** + * API for group name validation. + * + * Pulled into separate function to be re-usable. + */ +function fieldgroup_validate_name($group, $bundle) { + $errors = array(); + + // No label. + if (!$group['label']) { + $errors['label'][] = t('You need to provide a label.'); + } + + // No group name. + if (!$group['group_name']) { + $errors['group_name'][] = t('You need to provide a group name.'); + } + // Group name validation. + else { + $group_name = $group['group_name']; + $group['group_type'] = !empty($group['group_type']) ? $group['group_type'] : 'standard'; + + // Add the 'group_' prefix. + if (substr($group_name, 0, 6) != 'group_') { + $group_name = 'group_'. $group_name; + } + + // Invalid field name. + if (!preg_match('!^group_[a-z0-9_]+$!', $group_name)) { + $errors['group_name'][] = t('The group name %group_name is invalid. The name must include only lowercase unaccentuated letters, numbers, and underscores.', array('%group_name' => $group_name)); + } + if (strlen($group_name) > 32) { + $errors['group_name'][] = t('The group name %group_name is too long. The name is limited to 32 characters, including the \'group_\' prefix.', array('%group_name' => $group_name)); + } + + // Group name already exists. + $groups = fieldgroup_groups($bundle); + if (isset($groups[$group_name])) { + $errors['group_name'][] = t('The group name %group_name already exists.', array('%group_name' => $group_name)); + } + if (empty($errors['group_name'])) { + $group['group_name'] = $group_name; + } + } + return array('group_name' => $group['group_name'], 'errors' => $errors); +} + +function fieldgroup_field_overview_form_validate($form, &$form_state) { + $form_values = $form_state['values']; + $group = $form_values['_add_new_group']; + + if (array_filter(array($group['label'], $group['group_name']))) { + $validation = fieldgroup_validate_name($group, $form['#bundle']); + if (!empty($validation['errors'])) { + foreach ($validation['errors'] as $type => $messages) { + foreach ($messages as $message) { + if ($type == 'label') { + form_set_error('_add_new_group][label', t('Add new group:') .' '. $message); + } + else { + form_set_error('_add_new_group][group_name', t('Add new group:') .' '. $message); + } + } + } + } + $group_name = $validation['group_name']; + form_set_value($form['_add_new_group']['group_name'], $group_name, $form_state); + } + else { + // Fail validation if attempt to nest fields under a new group without the + // proper information. Not raising an error would cause the nested fields + // to get weights the user doesn't expect. + + foreach ($form_values as $key => $values) { + if ($values['parent'] == '_add_new_group') { + form_set_error('_add_new_group][label', t('Add new group: you need to provide a label.')); + form_set_error('_add_new_group][group_name', t('Add new group: you need to provide a group name.')); + break; + } + } + } +} + +function fieldgroup_field_overview_form_submit($form, &$form_state) { + $form_values = $form_state['values']; + $bundle = $form['#bundle']; + + // Create new group if needed. + if (!empty($form_values['_add_new_group']['label'])) { + $group = $form_values['_add_new_group']; + $group['settings'] = field_group_default_settings($group['group_type']); + fieldgroup_save_group($bundle, $group); + $new_group_name = $group['group_name']; + } + + // Parse incoming rows. + $add_field_rows = array('_add_new_field', '_add_existing_field'); + $field_rows = array_merge($form['#fields'], $add_field_rows); + foreach ($form_values as $key => $values) { + // If 'field' row: update field parenting. + if (in_array($key, $field_rows)) { + // If newly added fields were added to a group: + if (in_array($key, $add_field_rows)) { + // We replace the '_add_*_field' key with the actual name of + // the field that got added. + // field_field_overview_form_submit() placed those + // in $form_state['fields_added'] for us. + if (isset($form_state['fields_added'][$key])) { + $key = $form_state['fields_added'][$key]; + } + else { + // No field was actually created : skip to next row. + continue; + } + } + // If the field was added to the newly created group, replace the + // '_add_new_group' value with the actual name of the group. + $parent = ($values['parent'] == '_add_new_group' && isset($new_group_name)) ? $new_group_name : $values['parent']; + // TODO: check the parent group does exist ? + fieldgroup_update_fields(array('field_name' => $key, 'group' => $parent, 'bundle' => $bundle)); + } + + // If 'group' row: update groups weights + // (possible newly created group has already been taken care of). + elseif (in_array($key, $form['#groups'])) { + db_query("UPDATE {field_group} SET weight = %d WHERE bundle = '%s' AND group_name = '%s'", + $values['weight'], $bundle, $key); + } + } + + cache_clear_all('fieldgroup_data', 'cache_field'); +} + +function field_group_default_settings($group_type) { + $settings = array( + 'form' => array('style' => 'fieldset', 'description' => ''), + 'display' => array('description' => '', 'label' => 'above'), + ); + module_load_include('inc', 'field', 'includes/field.admin'); + foreach (array_keys(field_build_modes(field_info_bundle_entity($bundle))) as $key) { + $settings['display'][$key]['format'] = 'fieldset'; + $settings['display'][$key]['exclude'] = 0; + } + // Allow other modules to add new default settings. + $settings = array_merge($settings, module_invoke_all('fieldgroup_default_settings', $group_type)); + return $settings; +} + +function fieldgroup_display_overview_form_submit($form, &$form_state) { + $form_values = $form_state['values']; + $groups = fieldgroup_groups($form['#bundle']); + foreach ($form_values as $key => $values) { + if (in_array($key, $form['#groups'])) { + $group = $groups[$key]; + // We have some numeric keys here, so we can't use array_merge. + $group['settings']['display'] = $values + $group['settings']['display']; + fieldgroup_save_group($form['#bundle'], $group); + } + } +} + +function fieldgroup_field_remove_form_submit($form, &$form_state) { + $form_values = $form_state['values']; + // TODO: + // - when a (non last) field is removed from a group, a 'ghost row' remains in the fields overview + // - when the last field is removed, the group disappears + // seems to be fixed when emptying the cache. + db_query("DELETE FROM {field_group_fields} WHERE bundle = '%s' AND field_name = '%s'", $form_values['bundle'], $form_values['field_name']); +} + +/** + * Implementation of hook_nodeapi(). + */ +function fieldgroup_nodeapi(&$node, $op, $teaser, $page) { + // TODO This will only work on a node, need to make it general enough to work on users. + switch ($op) { + case 'view': + // Prevent against invalid 'nodes' built by broken 3rd party code. + if (isset($node->type)) { + // NODE_BUILD_NORMAL is 0, and ('whatever' == 0) is TRUE, so we need a ===. + if ($node->build_mode === NODE_BUILD_NORMAL || $node->build_mode == NODE_BUILD_PREVIEW) { + $context = $teaser ? 'teaser' : 'full'; + } + else { + $context = $node->build_mode; + } + + foreach (fieldgroup_groups($node->type) as $group_name => $group) { + // Do not include group labels when indexing content. + if ($context == NODE_BUILD_SEARCH_INDEX) { + $group['settings']['display']['label'] = 'hidden'; + } + $label = $group['settings']['display']['label'] == 'above'; + $element = array( + '#title' => $label ? check_plain(t($group['label'])) : '', + '#description' => $label ? field_filter_xss(t($group['settings']['display']['description'])) : '', + ); + $format = isset($group['settings']['display'][$context]['format']) ? $group['settings']['display'][$context]['format'] : 'fieldset'; + + switch ($format) { + case 'simple': + $element['#type'] = 'fieldgroup_simple'; + $element['#group_name'] = $group_name; + break; + case 'hidden': + $element['#access'] = FALSE; + break; + + case 'fieldset_collapsed': + $element['#collapsed'] = TRUE; + case 'fieldset_collapsible': + $element['#collapsible'] = TRUE; + case 'fieldset': + $element['#type'] = 'fieldgroup_fieldset'; + $element['#attributes'] = array('class' => 'fieldgroup '. strtr($group['group_name'], '_', '-')); + break; + } + foreach ($group['fields'] as $field_name => $field) { + if (isset($node->content[$field_name])) { + $element[$field_name] = $node->content[$field_name]; + } + } + + // Allow other modules to alter the group view. + // Can't use module_invoke_all because we want + // to be able to use a reference to $node and $element. + foreach (module_implements('fieldgroup_view') as $module) { + $function = $module .'_fieldgroup_view'; + $function($node, $element, $group, $context); + } + + foreach ($group['fields'] as $field_name => $field) { + if (isset($node->content[$field_name])) { + unset($node->content[$field_name]); + } + } + + // The wrapper lets us get the themed output for the group + // to populate the $GROUP_NAME_rendered variable for node templates, + // and hide it from the $content variable if needed. + // See fieldgroup_preprocess_node(), theme_fieldgroup_wrapper(). + $wrapper = array( + 'group' => $element, + '#weight' => $group['weight'], + '#post_render' => array('fieldgroup_wrapper_post_render'), + '#group_name' => $group_name, + '#bundle' => $node->type, + '#context' => $context, + ); + + $node->content[$group_name] = $wrapper; + } + } + break; + } +} + +/** + * Hide specified fields from the $content variable in node templates. + */ +function fieldgroup_wrapper_post_render($content, $element) { + $groups = fieldgroup_groups($element['#bundle']); + $group = $groups[$element['#group_name']]; + + // The display settings are not in quite the same place in the + // group and the field, so create the value the theme will expect. + $group['display_settings'] = $group['settings']['display']; + if (theme('field_exclude', $content, $group, $element['#context'])) { + return ''; + } + return $content; +} + +/* + * Get the group name for a field. + * If the field isn't in a group, FALSE will be returned. + * @return The name of the group, or FALSE. + */ +function fieldgroup_get_group($bundle, $field_name) { + foreach (fieldgroup_groups($bundle) as $group_name => $group) { + if (in_array($field_name, array_keys($group['fields']))) { + return $group_name; + } + } + return FALSE; +} + +/** + * Implementation of hook_node_type() + * React to change in node types + */ +function fieldgroup_node_type($op, $info) { + if ($op == 'update' && !empty($info->old_type) && $info->type != $info->old_type) { + // update the tables + db_query("UPDATE {field_group} SET bundle='%s' WHERE bundle='%s'", array($info->type, $info->old_type)); + db_query("UPDATE {field_group_fields} SET bundle='%s' WHERE bundle='%s'", array($info->type, $info->old_type)); + cache_clear_all('fieldgroup_data', 'cache_field'); + } + elseif ($op == 'delete') { + db_query("DELETE FROM {field_group} WHERE bundle = '%s'", $info->type); + db_query("DELETE FROM {field_group_fields} WHERE bundle = '%s'", $info->type); + } +} + +function fieldgroup_types() { + $types = array('standard' => t('Standard group')); + // Allow other modules to add new group_types. + $types = array_merge($types, module_invoke_all('fieldgroup_types')); + return $types; +} + +/** + * CRUD API for fieldgroup module. + * + * @todo + * Make this into more of a real API for groups. + */ +/* + * Saves the given group for this content-type + */ +function fieldgroup_save_group($bundle, $group) { + $groups = fieldgroup_groups($bundle); + + // Allow other modules to intervene when the group is saved. + foreach (module_implements('fieldgroup_save_group') as $module) { + $function = $module .'_fieldgroup_save_group'; + $function($group); + } + + if (!isset($groups[$group['group_name']])) { + // Accept group name from programmed submissions if valid. + db_query("INSERT INTO {field_group} (group_type, bundle, group_name, label, settings, weight)". + " VALUES ('%s', '%s', '%s', '%s', '%s', %d)", $group['group_type'], $bundle, $group['group_name'], $group['label'], serialize($group['settings']), $group['weight']); + cache_clear_all('fieldgroup_data', 'cache_field'); + return SAVED_NEW; + } + else { + db_query("UPDATE {field_group} SET group_type = '%s', label = '%s', settings = '%s', weight = %d ". + "WHERE bundle = '%s' AND group_name = '%s'", + $group['group_type'], $group['label'], serialize($group['settings']), $group['weight'], $bundle, $group['group_name']); + cache_clear_all('fieldgroup_data', 'cache_field'); + return SAVED_UPDATED; + } +} + +function fieldgroup_update_fields($form_values) { + $default = _fieldgroup_field_get_group($form_values['bundle'], $form_values['field_name']); + + if ($default != $form_values['group']) { + if ($form_values['group'] && !$default) { + db_query("INSERT INTO {field_group_fields} (bundle, group_name, field_name) VALUES ('%s', '%s', '%s')", $form_values['bundle'], $form_values['group'], $form_values['field_name']); + } + elseif ($form_values['group']) { + db_query("UPDATE {field_group_fields} SET group_name = '%s' WHERE bundle = '%s' AND field_name = '%s'", $form_values['group'], $form_values['bundle'], $form_values['field_name']); + } + else { + db_query("DELETE FROM {field_group_fields} WHERE bundle = '%s' AND field_name = '%s'", $form_values['bundle'], $form_values['field_name']); + } + cache_clear_all('fieldgroup_data', 'cache_field'); + } +} + +function fieldgroup_delete($bundle, $group_name) { + db_query("DELETE FROM {field_group} WHERE bundle = '%s' AND group_name = '%s'", $bundle, $group_name); + db_query("DELETE FROM {field_group_fields} WHERE bundle = '%s' AND group_name = '%s'", $bundle, $group_name); + cache_clear_all('fieldgroup_data', 'cache_field'); +} + +/** + * Format a fieldgroup using a 'fieldset'. + * + * Derived from core's theme_fieldset, with no output if the content is empty. + */ +function theme_fieldgroup_fieldset($element) { + if (empty($element['#children']) && empty($element['#value'])) { + return ''; + } + + if ($element['#collapsible']) { + drupal_add_js('misc/collapse.js'); + + if (!isset($element['#attributes']['class'])) { + $element['#attributes']['class'] = ''; + } + + $element['#attributes']['class'] .= ' collapsible'; + if ($element['#collapsed']) { + $element['#attributes']['class'] .= ' collapsed'; + } + } + return ''. ($element['#title'] ? ''. $element['#title'] .'' : '') . (isset($element['#description']) && $element['#description'] ? '
'. $element['#description'] .'
' : '') . (!empty($element['#children']) ? $element['#children'] : '') . (isset($element['#value']) ? $element['#value'] : '') ."\n"; +} + + +/** + * Process variables for fieldgroup.tpl.php. + * + * The $variables array contains the following arguments: + * - $group_name + * - $group_name_css + * - $label + * - $description + * - $content + * + * @see fieldgroup.tpl.php + */ +function fieldgroup_preprocess_fieldgroup_simple(&$vars) { + $element = $vars['element']; + + $vars['group_name'] = $element['#group_name']; + $vars['group_name_css'] = strtr($element['#group_name'], '_', '-'); + $vars['label'] = isset($element['#title']) ? $element['#title'] : '';; + $vars['description'] = isset($element['#description']) ? $element['#description'] : '';; + $vars['content'] = isset($element['#children']) ? $element['#children'] : ''; +} + +/** + * Theme preprocess function for node. + * + * Adds $GROUP_NAME_rendered variables, + * containing the themed output for the whole group. + */ +function fieldgroup_preprocess_node(&$vars) { + $node = $vars['node']; + + foreach (fieldgroup_groups($node->type) as $group_name => $group) { + // '#chilren' might not be set if the group is empty. + $vars[$group_name .'_rendered'] = isset($node->content[$group_name]['#children']) ? $node->content[$group_name]['#children'] : ''; + } +} \ No newline at end of file diff --git a/modules/fieldgroup/fieldgroup.tpl.php b/modules/fieldgroup/fieldgroup.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..9bc88e758a1ada52c113ee214a53e398cf53347b --- /dev/null +++ b/modules/fieldgroup/fieldgroup.tpl.php @@ -0,0 +1,33 @@ + + +
+ + +

+ + +
+ + + + +
+ +
+ diff --git a/modules/node_reference/node_reference.info b/modules/node_reference/node_reference.info new file mode 100644 index 0000000000000000000000000000000000000000..3e9c63303ad13f07c0ba6bcbb45c704c765454af --- /dev/null +++ b/modules/node_reference/node_reference.info @@ -0,0 +1,8 @@ +; $Id$ +name = Node Reference +description = Defines a field type for referencing one node from another. +dependencies[] = text +dependencies[] = option_widgets +files[]=node_reference.module +package = Core - fields +core = 7.x diff --git a/modules/node_reference/node_reference.module b/modules/node_reference/node_reference.module new file mode 100644 index 0000000000000000000000000000000000000000..34f6958cfd356a056d51ce059232218dc1a70d7c --- /dev/null +++ b/modules/node_reference/node_reference.module @@ -0,0 +1,812 @@ + 'node_reference autocomplete', + 'page callback' => 'node_reference_autocomplete', + 'access arguments' => array('access content'), + 'type' => MENU_CALLBACK + ); + return $items; +} + +/** + * Implementation of hook_theme(). + */ +function node_reference_theme() { + return array( + 'node_reference_select' => array( + 'arguments' => array('element' => NULL), + ), + 'node_reference_buttons' => array( + 'arguments' => array('element' => NULL), + ), + 'node_reference_autocomplete' => array( + 'arguments' => array('element' => NULL), + ), + 'field_formatter_node_reference_default' => array( + 'arguments' => array('element'), + ), + 'field_formatter_node_reference_plain' => array( + 'arguments' => array('element'), + ), + 'field_formatter_node_reference_full' => array( + 'arguments' => array('element'), + 'function' => 'theme_field_formatter_node_reference_node', + ), + 'field_formatter_node_reference_teaser' => array( + 'arguments' => array('element'), + 'function' => 'theme_field_formatter_node_reference_node', + ), + ); +} + +/** + * Implementation of hook_field_info(). + */ +function node_reference_field_info() { + return array( + 'node_reference' => array( + 'label' => t('Node reference'), + 'description' => t('This field stores the ID of a related node as an integer value.'), + 'settings' => array('referenceable_types'), + 'default_widget' => 'node_reference_autocomplete', + 'default_formatter' => 'node_reference_default', + ), + ); +} + +/** + * Implementation of hook_field_settings(). + */ +function node_reference_field_settings($op, $field) { + switch ($op) { + + case 'database columns': + $columns = array( + 'nid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => FALSE), + ); + return $columns; + + } +} + +/** + * Implementation of hook_field(). + */ +function node_reference_field($op, $node, $field, &$items, $teaser, $page) { + switch ($op) { + // When preparing a translation, load any translations of existing references. + case 'prepare translation': + $addition = array(); + $addition[$field['field_name']] = array(); + if (isset($node->translation_source->$field['field_name']) && is_array($node->translation_source->$field['field_name'])) { + foreach ($node->translation_source->$field['field_name'] as $key => $reference) { + $reference_node = node_load($reference['nid']); + // Test if the referenced node type is translatable and, if so, + // load translations if the reference is not for the current language. + // We can assume the translation module is present because it invokes 'prepare translation'. + if (translation_supported_type($reference_node->type) && !empty($reference_node->language) && $reference_node->language != $node->language && $translations = translation_node_get_translations($reference_node->tnid)) { + // If there is a translation for the current language, use it. + $addition[$field['field_name']][] = array( + 'nid' => isset($translations[$node->language]) ? $translations[$node->language]->nid : $reference['nid'], + ); + } + } + } + return $addition; + + case 'validate': + // Extract nids to check. + $ids = array(); + foreach ($items as $delta => $item) { + if (is_array($item) && !empty($item['nid']) && is_numeric($item['nid'])) { + $ids[] = $item['nid']; + } + } + $refs = _node_reference_potential_references($field, '', NULL, $ids); + foreach ($items as $delta => $item) { + if (is_array($item)) { + $error_element = isset($item['_error_element']) ? $item['_error_element'] : ''; + if (is_array($item) && isset($item['_error_element'])) unset($item['_error_element']); + if (!empty($item['nid']) && !isset($refs[$item['nid']])) { + form_set_error($error_element, t("%name: this post can't be referenced.", array('%name' => t($field['widget']['label'])))); + } + } + } + return $items; + } +} + +/** + * Implementation of hook_field_is_empty(). + */ +function node_reference_field_is_empty($item, $field) { + if (empty($item['nid'])) { + return TRUE; + } + return FALSE; +} + +/** + * Implementation of hook_field_formatter_info(). + */ +function node_reference_field_formatter_info() { + return array( + 'node_reference_default' => array( + 'label' => t('Title (link)'), + 'field types' => array('node_reference'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + 'node_reference_plain' => array( + 'label' => t('Title (no link)'), + 'field types' => array('node_reference'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + 'node_reference_full' => array( + 'label' => t('Full node'), + 'field types' => array('node_reference'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + 'node_reference_teaser' => array( + 'label' => t('Teaser'), + 'field types' => array('node_reference'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + ); +} + +/** + * Theme function for 'default' node_reference field formatter. + */ +function theme_field_formatter_node_reference_default($element) { + $output = ''; + if (!empty($element['#item']['nid']) && is_numeric($element['#item']['nid']) && ($title = _node_reference_titles($element['#item']['nid']))) { + $output = l($title, 'node/'. $element['#item']['nid']); + } + return $output; +} + +/** + * Theme function for 'plain' node_reference field formatter. + */ +function theme_field_formatter_node_reference_plain($element) { + $output = ''; + if (!empty($element['#item']['nid']) && is_numeric($element['#item']['nid']) && ($title = _node_reference_titles($element['#item']['nid']))) { + $output = check_plain($title); + } + return $output; +} + +/** + * Proxy theme function for 'full' and 'teaser' node_reference field formatters. + */ +function theme_field_formatter_node_reference_node($element) { + static $recursion_queue = array(); + $output = ''; + if (!empty($element['#item']['nid']) && is_numeric($element['#item']['nid'])) { + $node = $element['#node']; + $field = field_fields($element['#field_name'], $element['#type_name']); + // If no 'referencing node' is set, we are starting a new 'reference thread' + if (!isset($node->referencing_node)) { + $recursion_queue = array(); + } + $recursion_queue[] = $node->nid; + if (in_array($element['#item']['nid'], $recursion_queue)) { + // Prevent infinite recursion caused by reference cycles: + // if the node has already been rendered earlier in this 'thread', + // we fall back to 'default' (node title) formatter. + return theme('node_reference_formatter_default', $element); + } + if ($referenced_node = node_load($element['#item']['nid'])) { + $referenced_node->referencing_node = $node; + $referenced_node->referencing_field = $field; + _node_reference_titles($element['#item']['nid'], $referenced_node->title); + $output = node_view($referenced_node, $element['#formatter'] == 'teaser'); + } + } + return $output; +} + +/** + * Helper function for formatters. + * + * Store node titles collected in the curent request. + */ +function _node_reference_titles($nid, $known_title = NULL) { + static $titles = array(); + if (!isset($titles[$nid])) { + $title = $known_title ? $known_title : db_result(db_query("SELECT title FROM {node} WHERE nid=%d", $nid)); + $titles[$nid] = $title ? $title : ''; + } + return $titles[$nid]; +} + +/** + * Implementation of hook_field_widget_info(). + * + * We need custom handling of multiple values for the node_reference_select + * widget because we need to combine them into a options list rather + * than display multiple elements. + * + * We will use the Field module's default handling for default value. + * + * Callbacks can be omitted if default handing is used. + * They're included here just so this module can be used + * as an example for custom modules that might do things + * differently. + */ +function node_reference_field_widget_info() { + return array( + 'node_reference_select' => array( + 'label' => t('Select list'), + 'field types' => array('node_reference'), + 'settings' => array('autocomplete_match'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_CUSTOM, + 'default value' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + 'node_reference_buttons' => array( + 'label' => t('Check boxes/radio buttons'), + 'field types' => array('node_reference'), + 'settings' => array('autocomplete_match'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_CUSTOM, + 'default value' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + 'node_reference_autocomplete' => array( + 'label' => t('Autocomplete text field'), + 'field types' => array('node_reference'), + 'settings' => array('autocomplete_match'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + 'default value' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + ); +} + +/** + * Implementation of FAPI hook_elements(). + * + * Any FAPI callbacks needed for individual widgets can be declared here, + * and the element will be passed to those callbacks for processing. + * + * Drupal will automatically theme the element using a theme with + * the same name as the hook_elements key. + * + * Autocomplete_path is not used by text_widget but other widgets can use it + * (see node_reference and userreference). + */ +function node_reference_elements() { + return array( + 'node_reference_select' => array( + '#input' => TRUE, + '#columns' => array('uid'), '#delta' => 0, + '#process' => array('node_reference_select_process'), + ), + 'node_reference_buttons' => array( + '#input' => TRUE, + '#columns' => array('uid'), '#delta' => 0, + '#process' => array('node_reference_buttons_process'), + ), + 'node_reference_autocomplete' => array( + '#input' => TRUE, + '#columns' => array('name'), '#delta' => 0, + '#process' => array('node_reference_autocomplete_process'), + '#autocomplete_path' => FALSE, + ), + ); +} + +/** + * Implementation of hook_field_widget_settings(). + */ +function node_reference_field_widget_settings($op, $widget) { + switch ($op) { + case 'form': + $form = array(); + $match = isset($widget['autocomplete_match']) ? $widget['autocomplete_match'] : 'contains'; + if ($widget['type'] == 'node_reference_autocomplete') { + $form['autocomplete_match'] = array( + '#type' => 'select', + '#title' => t('Autocomplete matching'), + '#default_value' => $match, + '#options' => array( + 'starts_with' => t('Starts with'), + 'contains' => t('Contains'), + ), + '#description' => t('Select the method used to collect autocomplete suggestions. Note that Contains can cause performance issues on sites with thousands of nodes.'), + ); + } + else { + $form['autocomplete_match'] = array('#type' => 'hidden', '#value' => $match); + } + return $form; + } +} + +/** + * Implementation of hook_field_widget(). + * + * Attach a single form element to the form. It will be built out and + * validated in the callback(s) listed in hook_elements. We build it + * out in the callbacks rather than here in hook_widget so it can be + * plugged into any module that can provide it with valid + * $field information. + * + * Field module will set the weight, field name and delta values + * for each form element. + * + * If there are multiple values for this field, the field module will + * call this function as many times as needed. + * + * @param $form + * the entire form array, $form['#node'] holds node information + * @param $form_state + * the form_state, $form_state['values'][$field['field_name']] + * holds the field's form values. + * @param $instance + * the field instance array + * @param $items + * array of default values for this field + * @param $delta + * the order of this item in the array of subelements (0, 1, 2, etc) + * + * @return + * the form item for a single element for this field + */ +function node_reference_field_widget(&$form, &$form_state, $instance, $items, $delta = 0) { + switch ($instance['widget']['type']) { + case 'node_reference_select': + $element = array( + '#type' => 'node_reference_select', + '#default_value' => $items, + ); + break; + + case 'node_reference_buttons': + $element = array( + '#type' => 'node_reference_buttons', + '#default_value' => $items, + ); + break; + + case 'node_reference_autocomplete': + $element = array( + '#type' => 'node_reference_autocomplete', + '#default_value' => isset($items[$delta]) ? $items[$delta] : NULL, + '#value_callback' => 'node_reference_autocomplete_value', + ); + break; + } + return $element; +} + +/** + * Value for a node_reference autocomplete element. + * + * Substitute in the node title for the node nid. + */ +function node_reference_autocomplete_value($element, $edit = FALSE) { + $field_key = $element['#columns'][0]; + if (!empty($element['#default_value'][$field_key])) { + $nid = $element['#default_value'][$field_key]; + $value = db_result(db_query(db_rewrite_sql('SELECT n.title FROM {node} n WHERE n.nid = %d'), $nid)); + $value .= ' [nid:'. $nid .']'; + return array($field_key => $value); + } + return array($field_key => NULL); +} + +/** + * Process an individual element. + * + * Build the form element. When creating a form using FAPI #process, + * note that $element['#value'] is already set. + * + * The $field and $instance arrays are in $form['#fields'][$element['#field_name']]. + */ +function node_reference_select_process($element, $edit, $form_state, $form) { + // The node_reference_select widget doesn't need to create its own + // element, it can wrap around the optionwidgets_select element. + // This will create a new, nested instance of the field. + // Add a validation step where the value can be unwrapped. + $field_key = $element['#columns'][0]; + $element[$field_key] = array( + '#type' => 'optionwidgets_select', + '#default_value' => isset($element['#value']) ? $element['#value'] : '', + // The following values were set by the field module and need + // to be passed down to the nested element. + '#title' => $element['#title'], + '#required' => $element['#required'], + '#description' => $element['#description'], + '#field_name' => $element['#field_name'], + '#type_name' => $element['#type_name'], + '#delta' => $element['#delta'], + '#columns' => $element['#columns'], + ); + if (empty($element[$field_key]['#element_validate'])) { + $element[$field_key]['#element_validate'] = array(); + } + array_unshift($element[$field_key]['#element_validate'], 'node_reference_optionwidgets_validate'); + return $element; +} + +/** + * Process an individual element. + * + * Build the form element. When creating a form using FAPI #process, + * note that $element['#value'] is already set. + * + * The $field and $instance arrays are in $form['#fields'][$element['#field_name']]. + */ +function node_reference_buttons_process($element, $edit, $form_state, $form) { + // The node_reference_select widget doesn't need to create its own + // element, it can wrap around the optionwidgets_select element. + // This will create a new, nested instance of the field. + // Add a validation step where the value can be unwrapped. + $field_key = $element['#columns'][0]; + $element[$field_key] = array( + '#type' => 'optionwidgets_buttons', + '#default_value' => isset($element['#value']) ? $element['#value'] : '', + // The following values were set by the field module and need + // to be passed down to the nested element. + '#title' => $element['#title'], + '#required' => $element['#required'], + '#description' => $element['#description'], + '#field_name' => $element['#field_name'], + '#type_name' => $element['#type_name'], + '#delta' => $element['#delta'], + '#columns' => $element['#columns'], + ); + if (empty($element[$field_key]['#element_validate'])) { + $element[$field_key]['#element_validate'] = array(); + } + array_unshift($element[$field_key]['#element_validate'], 'node_reference_optionwidgets_validate'); + return $element; +} + +/** + * Process an individual element. + * + * Build the form element. When creating a form using FAPI #process, + * note that $element['#value'] is already set. + * + */ +function node_reference_autocomplete_process($element, $edit, $form_state, $form) { + + // The node_reference autocomplete widget doesn't need to create its own + // element, it can wrap around the text_textfield element and add an autocomplete + // path and some extra processing to it. + // Add a validation step where the value can be unwrapped. + $field_key = $element['#columns'][0]; + + $element[$field_key] = array( + '#type' => 'text_textfield', + '#default_value' => isset($element['#value']) ? $element['#value'] : '', + '#autocomplete_path' => 'node_reference/autocomplete/'. $element['#field_name'], + // The following values were set by the field module and need + // to be passed down to the nested element. + '#title' => $element['#title'], + '#required' => $element['#required'], + '#description' => $element['#description'], + '#field_name' => $element['#field_name'], + '#type_name' => $element['#type_name'], + '#delta' => $element['#delta'], + '#columns' => $element['#columns'], + ); + if (empty($element[$field_key]['#element_validate'])) { + $element[$field_key]['#element_validate'] = array(); + } + array_unshift($element[$field_key]['#element_validate'], 'node_reference_autocomplete_validate'); + + // Used so that hook_field('validate') knows where to flag an error. + $element['_error_element'] = array( + '#type' => 'value', + // Wrapping the element around a text_textfield element creates a + // nested element, so the final id will look like 'field-name-0-nid-nid'. + '#value' => implode('][', array_merge($element['#parents'], array($field_key, $field_key))), + ); + return $element; +} + +/** + * Validate a select/buttons element. + * + * Remove the wrapper layer and set the right element's value. + * We don't know exactly where this element is, so we drill down + * through the element until we get to our key. + * + * We use $form_state['values'] instead of $element['#value'] + * to be sure we have the most accurate value when other modules + * like optionwidgets are using #element_validate to alter the value. + */ +function node_reference_optionwidgets_validate($element, &$form_state) { + $field_key = $element['#columns'][0]; + + $value = $form_state['values']; + $new_parents = array(); + foreach ($element['#parents'] as $parent) { + $value = $value[$parent]; + // Use === to be sure we get right results if parent is a zero (delta) value. + if ($parent === $field_key) { + $element['#parents'] = $new_parents; + form_set_value($element, $value, $form_state); + break; + } + $new_parents[] = $parent; + } +} + +/** + * Validate an autocomplete element. + * + * Remove the wrapper layer and set the right element's value. + * This will move the nested value at 'field-name-0-nid-nid' + * back to its original location, 'field-name-0-nid'. + */ +function node_reference_autocomplete_validate($element, &$form_state) { + $field_name = $element['#field_name']; + $type_name = $element['#type_name']; + $field = field_fields($field_name, $type_name); + $field_key = $element['#columns'][0]; + $delta = $element['#delta']; + $value = $element['#value'][$field_key]; + $nid = NULL; + if (!empty($value)) { + preg_match('/^(?:\s*|(.*) )?\[\s*nid\s*:\s*(\d+)\s*\]$/', $value, $matches); + if (!empty($matches)) { + // Explicit [nid:n]. + list(, $title, $nid) = $matches; + if (!empty($title) && ($n = node_load($nid)) && $title != $n->title) { + form_error($element[$field_key], t('%name: title mismatch. Please check your selection.', array('%name' => t($field['widget']['label'])))); + } + } + else { + // No explicit nid. + $reference = _node_reference_potential_references($field, $value, 'equals', NULL, 1); + if (empty($reference)) { + form_error($element[$field_key], t('%name: found no valid post with that title.', array('%name' => t($field['widget']['label'])))); + } + else { + // TODO: + // the best thing would be to present the user with an additional form, + // allowing the user to choose between valid candidates with the same title + // ATM, we pick the first matching candidate... + $nid = key($reference); + } + } + } + form_set_value($element, $nid, $form_state); +} + +/** + * Implementation of hook_allowed_values(). + */ +function node_reference_allowed_values($field) { + $references = _node_reference_potential_references($field); + + $options = array(); + foreach ($references as $key => $value) { + // Views theming runs check_plain (htmlentities) on the values. + // We reverse that with html_entity_decode. + $options[$key] = html_entity_decode(strip_tags($value['rendered']), ENT_QUOTES); + } + return $options; +} + +/** + * Fetch an array of all candidate referenced nodes. + * + * This info is used in various places (aloowed values, autocomplete results, + * input validation...). Some of them only need the nids, others nid + titles, + * others yet nid + titles + rendered row (for display in widgets). + * The array we return contains all the potentially needed information, and lets + * consumers use the parts they actually need. + * + * @param $field + * The field description. + * @param $string + * Optional string to filter titles on (used by autocomplete). + * @param $match + * Operator to match filtered name against, can be any of: + * 'contains', 'equals', 'starts_with' + * @param $ids + * Optional node ids to lookup (the $string and $match arguments will be + * ignored). + * @param $limit + * If non-zero, limit the size of the result set. + * + * @return + * An array of valid nodes in the form: + * array( + * nid => array( + * 'title' => The node title, + * 'rendered' => The text to display in widgets (can be HTML) + * ), + * ... + * ) + */ +function _node_reference_potential_references($field, $string = '', $match = 'contains', $ids = array(), $limit = NULL) { + static $results = array(); + + // Create unique id for static cache. + $cid = $field['field_name'] .':'. $match .':'. ($string !== '' ? $string : implode('-', $ids)) .':'. $limit; + if (!isset($results[$cid])) { + $references = FALSE; + if ($references === FALSE) { + $references = _node_reference_potential_references_standard($field, $string, $match, $ids, $limit); + } + + // Store the results. + $results[$cid] = $references; + } + + return $results[$cid]; +} + +/** + * Helper function for _node_reference_potential_references(): + * referenceable nodes defined by content types. + */ +function _node_reference_potential_references_standard($field, $string = '', $match = 'contains', $ids = array(), $limit = NULL) { + $related_types = array(); + $where = array(); + $args = array(); + + if (is_array($field['referenceable_types'])) { + foreach (array_filter($field['referenceable_types']) as $related_type) { + $related_types[] = "n.type = '%s'"; + $args[] = $related_type; + } + } + + $where[] = implode(' OR ', $related_types); + + if (!count($related_types)) { + return array(); + } + + if ($string !== '') { + $match_operators = array( + 'contains' => "LIKE '%%%s%%'", + 'equals' => "= '%s'", + 'starts_with' => "LIKE '%s%%'", + ); + $where[] = 'n.title '. (isset($match_operators[$match]) ? $match_operators[$match] : $match_operators['contains']); + $args[] = $string; + } + elseif ($ids) { + $where[] = 'n.nid IN (' . db_placeholders($ids) . ')'; + $args = array_merge($args, $ids); + } + + $where_clause = $where ? 'WHERE ('. implode(') AND (', $where) .')' : ''; + $sql = db_rewrite_sql("SELECT n.nid, n.title AS node_title, n.type AS node_type FROM {node} n $where_clause ORDER BY n.title, n.type"); + $result = $limit ? db_query_range($sql, $args, 0, $limit) : db_query($sql, $args); + $references = array(); + while ($node = db_fetch_object($result)) { + $references[$node->nid] = array( + 'title' => $node->node_title, + 'rendered' => $node->node_title, + ); + } + + return $references; +} + +/** + * Menu callback; Retrieve a pipe delimited string of autocomplete suggestions for existing users + */ +function node_reference_autocomplete($field_name, $string = '') { + $fields = content_fields(); + $field = $fields[$field_name]; + $match = isset($field['widget']['autocomplete_match']) ? $field['widget']['autocomplete_match'] : 'contains'; + $matches = array(); + + $references = _node_reference_potential_references($field, $string, $match, array(), 10); + foreach ($references as $id => $row) { + // Add a class wrapper for a few required CSS overrides. + $matches[$row['title'] ." [nid:$id]"] = '
'. $row['rendered'] . '
'; + } + drupal_json($matches); +} + +/** + * Implementation of hook_node_types. + */ +function node_reference_node_type($op, $info) { + switch ($op) { + case 'update': + // Reflect type name changes to the 'referenceable types' settings. + if (!empty($info->old_type) && $info->old_type != $info->type) { + $fields = field_info_fields(); + foreach ($fields as $field_name => $field) { + if ($field['type'] == 'node_reference' && isset($field['referenceable_types'][$info->old_type])) { + $field['referenceable_types'][$info->type] = empty($field['referenceable_types'][$info->old_type]) ? 0 : $info->type; + unset($field['referenceable_types'][$info->old_type]); + content_field_instance_update($field); + } + } + } + break; + } +} + +/** + * Theme preprocess function. + * + * Allows specific node templates for nodes displayed as values of a + * node_reference field with the 'full node' / 'teaser' formatters. + */ +function node_reference_preprocess_node(&$vars) { + // The 'referencing_field' attribute of the node is added by the 'teaser' + // and 'full node' formatters. + if (!empty($vars['node']->referencing_field)) { + $node = $vars['node']; + $field = $node->referencing_field; + $vars['template_files'][] = 'node-node_reference'; + $vars['template_files'][] = 'node-node_reference-'. $field['field_name']; + $vars['template_files'][] = 'node-node_reference-'. $node->type; + $vars['template_files'][] = 'node-node_reference-'. $field['field_name'] .'-'. $node->type; + } +} + +/** + * FAPI theme for an individual elements. + * + * The textfield or select is already rendered by the + * textfield or select themes and the html output + * lives in $element['#children']. Override this theme to + * make custom changes to the output. + * + * $element['#field_name'] contains the field name + * $element['#delta] is the position of this element in the group + */ +function theme_node_reference_select($element) { + return $element['#children']; +} + +function theme_node_reference_buttons($element) { + return $element['#children']; +} + +function theme_node_reference_autocomplete($element) { + return $element['#children']; +} + +/** + * Implementation of hook_field_settings_form() on behalf of core Nodereference module. + */ +function nodereference_field_settings_form($field) { + $form = array(); + $form['referenceable_types'] = array( + '#type' => 'checkboxes', + '#title' => t('Content types that can be referenced'), + '#multiple' => TRUE, + '#default_value' => is_array($field['referenceable_types']) ? $field['referenceable_types'] : array(), + '#options' => array_map('check_plain', node_get_types('names')), + ); + return $form; +} \ No newline at end of file diff --git a/modules/user_reference/user_reference.info b/modules/user_reference/user_reference.info new file mode 100644 index 0000000000000000000000000000000000000000..35cc26aba3f08bdf0d8ad8a7f04f742073ad9709 --- /dev/null +++ b/modules/user_reference/user_reference.info @@ -0,0 +1,8 @@ +; $Id$ +name = User Reference +description = Defines a field type for referencing a user from a node. +dependencies[] = text +dependencies[] = option_widgets +package = Core - fields +core = 7.x +files[]=user_reference.module \ No newline at end of file diff --git a/modules/user_reference/user_reference.module b/modules/user_reference/user_reference.module new file mode 100644 index 0000000000000000000000000000000000000000..026e90208d44d73f865c901f7eb789ab323604f8 --- /dev/null +++ b/modules/user_reference/user_reference.module @@ -0,0 +1,761 @@ + 'user_reference autocomplete', + 'page callback' => 'user_reference_autocomplete', + 'access arguments' => array('access content'), + 'type' => MENU_CALLBACK + ); + return $items; +} + +/** + * Implementation of hook_theme(). + */ +function user_reference_theme() { + return array( + 'user_reference_select' => array( + 'arguments' => array('element' => NULL), + ), + 'user_reference_buttons' => array( + 'arguments' => array('element' => NULL), + ), + 'user_reference_autocomplete' => array( + 'arguments' => array('element' => NULL), + ), + 'field_formatter_formatter_user_reference_default' => array( + 'arguments' => array('element'), + ), + 'field_formatter_formatter_user_reference_plain' => array( + 'arguments' => array('element'), + ), + ); +} + +/** + * Implementation of hook_field_info(). + */ +function user_reference_field_info() { + return array( + 'user_reference' => array( + 'label' => t('User reference'), + 'description' => t('This field stores the ID of a related user as an integer value.'), + 'settings' => array('referenceable_roles', 'referenceable_status'), + 'default_widget' => 'user_reference_autocomplete', + 'default_formatter' => 'user_reference_default', + ), + ); +} + +/** + * Implementation of hook_field_settings(). + */ +function user_reference_field_settings($op, $field) { + switch ($op) { + case 'form': + $form = array(); + $form['referenceable_roles'] = array( + '#type' => 'checkboxes', + '#title' => t('User roles that can be referenced'), + '#default_value' => isset($field['referenceable_roles']) && is_array($field['referenceable_roles']) ? array_filter($field['referenceable_roles']) : array(), + '#options' => user_roles(1), + ); + $form['referenceable_status'] = array( + '#type' => 'checkboxes', + '#title' => t('User status that can be referenced'), + '#default_value' => isset($field['referenceable_status']) && is_array($field['referenceable_status']) ? array_filter($field['referenceable_status']) : array(1), + '#options' => array(1 => t('Active'), 0 => t('Blocked')), + ); + return $form; + + case 'database columns': + $columns = array( + 'uid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => FALSE), + ); + return $columns; + + } +} + +/** + * Implementation of hook_field(). + */ +function user_reference_field($op, $node, $field, &$items, $teaser, $page) { + switch ($op) { + case 'validate': + // Extract uids to check. + $ids = array(); + foreach ($items as $delta => $item) { + if (is_array($item) && !empty($item['uid']) && is_numeric($item['uid'])) { + $ids[] = $item['uid']; + } + } + $refs = _user_reference_potential_references($field, '', NULL, $ids); + foreach ($items as $delta => $item) { + if (is_array($item)) { + $error_element = isset($item['_error_element']) ? $item['_error_element'] : ''; + if (is_array($item) && isset($item['_error_element'])) unset($item['_error_element']); + if (!empty($item['uid']) && !isset($refs[$item['uid']])) { + form_set_error($error_element, t('%name: invalid user.', array('%name' => t($field['widget']['label'])))); + } + } + } + return $items; + } +} + +/** + * Implementation of hook_field_is_empty(). + */ +function user_reference_field_is_empty($item, $field) { + if (empty($item['uid'])) { + return TRUE; + } + return FALSE; +} + +/** + * Implementation of hook_field_formatter_info(). + */ +function user_reference_field_formatter_info() { + return array( + 'user_reference_default' => array( + 'label' => t('Default'), + 'field types' => array('user_reference'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + 'user_reference_plain' => array( + 'label' => t('Plain text'), + 'field types' => array('user_reference'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + ); +} + +/** + * Theme function for 'default' user_reference field formatter. + */ +function theme_field_formatter_formatter_user_reference_default($element) { + $output = ''; + + if (isset($element['#item']['uid']) && $account = user_load(array('uid' => $element['#item']['uid']))) { + $output = theme('username', $account); + } + return $output; +} + +/** + * Theme function for 'plain' user_reference field formatter. + */ +function theme_field_formatter_formatter_user_reference_plain($element) { + $output = ''; + if (isset($element['#item']['uid']) && $account = user_load(array('uid' => $element['#item']['uid']))) { + $output = $account->name; + } + return $output; +} + +/** + * Implementation of hook_field_widget_info(). + * + * We need custom handling of multiple values for the user_reference_select + * widget because we need to combine them into a options list rather + * than display multiple elements. + * + * We will use the field module's default handling for default value. + * + * Callbacks can be omitted if default handing is used. + * They're included here just so this module can be used + * as an example for custom modules that might do things + * differently. + */ +function user_reference_field_widget_info() { + return array( + 'user_reference_select' => array( + 'label' => t('Select list'), + 'field types' => array('user_reference'), + 'settings' => array('autocomplete_match', 'reverse_link'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_CUSTOM, + 'default value' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + 'user_reference_buttons' => array( + 'label' => t('Check boxes/radio buttons'), + 'field types' => array('user_reference'), + 'settings' => array('autocomplete_match', 'reverse_link'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_CUSTOM, + 'default value' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + 'user_reference_autocomplete' => array( + 'label' => t('Autocomplete text field'), + 'field types' => array('user_reference'), + 'settings' => array('autocomplete_match', 'reverse_link'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + 'default value' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + ); +} + +/** + * Implementation of FAPI hook_elements(). + * + * Any FAPI callbacks needed for individual widgets can be declared here, + * and the element will be passed to those callbacks for processing. + * + * Drupal will automatically theme the element using a theme with + * the same name as the hook_elements key. + * + * Autocomplete_path is not used by text_widget but other widgets can use it + * (see nodereference and user_reference). + */ +function user_reference_elements() { + return array( + 'user_reference_select' => array( + '#input' => TRUE, + '#columns' => array('uid'), '#delta' => 0, + '#process' => array('user_reference_select_process'), + ), + 'user_reference_buttons' => array( + '#input' => TRUE, + '#columns' => array('uid'), '#delta' => 0, + '#process' => array('user_reference_buttons_process'), + ), + 'user_reference_autocomplete' => array( + '#input' => TRUE, + '#columns' => array('name'), '#delta' => 0, + '#process' => array('user_reference_autocomplete_process'), + '#autocomplete_path' => FALSE, + ), + ); +} + +/** + * Implementation of hook_field_widget_settings(). + */ +function user_reference_field_widget_settings($op, $widget) { + switch ($op) { + case 'form': + $form = array(); + $match = isset($widget['autocomplete_match']) ? $widget['autocomplete_match'] : 'contains'; + if ($widget['type'] == 'user_reference_autocomplete') { + $form['autocomplete_match'] = array( + '#type' => 'select', + '#title' => t('Autocomplete matching'), + '#default_value' => $match, + '#options' => array( + 'starts_with' => t('Starts with'), + 'contains' => t('Contains'), + ), + '#description' => t('Select the method used to collect autocomplete suggestions. Note that Contains can cause performance issues on sites with thousands of users.'), + ); + } + else { + $form['autocomplete_match'] = array('#type' => 'hidden', '#value' => $match); + } + $form['reverse_link'] = array( + '#type' => 'checkbox', + '#title' => t('Reverse link'), + '#default_value' => isset($widget['reverse_link']) ? $widget['reverse_link'] : 0, + '#description' => t('If selected, a reverse link back to the referencing node will displayed on the referenced user record.'), + ); + return $form; + } +} + +/** + * Implementation of hook_field_widget(). + * + * Attach a single form element to the form. It will be built out and + * validated in the callback(s) listed in hook_elements. We build it + * out in the callbacks rather than here in hook_widget so it can be + * plugged into any module that can provide it with valid + * $field information. + * + * Field module will set the weight, field name and delta values + * for each form element. This is a change from earlier CCK versions + * where the widget managed its own multiple values. + * + * If there are multiple values for this field, the field module will + * call this function as many times as needed. + * + * @param $form + * the entire form array, $form['#node'] holds node information + * @param $form_state + * the form_state, $form_state['values'][$field['field_name']] + * holds the field's form values. + * @param $instance + * the field instance array + * @param $items + * array of default values for this field + * @param $delta + * the order of this item in the array of subelements (0, 1, 2, etc) + * + * @return + * the form item for a single element for this field + */ +function user_reference_field_widget(&$form, &$form_state, $instance, $items, $delta = 0) { + switch ($instance['widget']['type']) { + case 'user_reference_select': + $element = array( + '#type' => 'user_reference_select', + '#default_value' => $items, + ); + break; + + case 'user_reference_buttons': + $element = array( + '#type' => 'user_reference_buttons', + '#default_value' => $items, + ); + break; + + case 'user_reference_autocomplete': + $element = array( + '#type' => 'user_reference_autocomplete', + '#default_value' => isset($items[$delta]) ? $items[$delta] : NULL, + '#value_callback' => 'user_reference_autocomplete_value', + ); + break; + } + return $element; +} + +/** + * Value for a user_reference autocomplete element. + * + * Substitute in the user name for the uid. + */ +function user_reference_autocomplete_value($element, $edit = FALSE) { + $field_key = $element['#columns'][0]; + if (!empty($element['#default_value'][$field_key])) { + $value = db_result(db_query("SELECT name FROM {users} WHERE uid = '%d'", $element['#default_value'][$field_key])); + return array($field_key => $value); + } + return array($field_key => NULL); +} + +/** + * Process an individual element. + * + * Build the form element. When creating a form using FAPI #process, + * note that $element['#value'] is already set. + * + * The $field and $instance arrays are in $form['#fields'][$element['#field_name']]. + */ +function user_reference_select_process($element, $edit, $form_state, $form) { + // The user_reference_select widget doesn't need to create its own + // element, it can wrap around the optionwidgets_select element. + // Add a validation step where the value can be unwrapped. + $field_key = $element['#columns'][0]; + $element[$field_key] = array( + '#type' => 'optionwidgets_select', + '#default_value' => isset($element['#value']) ? $element['#value'] : '', + // The following values were set by the field module and need + // to be passed down to the nested element. + '#title' => $element['#title'], + '#required' => $element['#required'], + '#description' => $element['#description'], + '#field_name' => $element['#field_name'], + '#type_name' => $element['#type_name'], + '#delta' => $element['#delta'], + '#columns' => $element['#columns'], + ); + if (empty($element[$field_key]['#element_validate'])) { + $element[$field_key]['#element_validate'] = array(); + } + array_unshift($element[$field_key]['#element_validate'], 'user_reference_optionwidgets_validate'); + return $element; +} + +/** + * Process an individual element. + * + * Build the form element. When creating a form using FAPI #process, + * note that $element['#value'] is already set. + * + * The $field and $instance arrays are in $form['#fields'][$element['#field_name']]. + */ +function user_reference_buttons_process($element, $edit, $form_state, $form) { + // The user_reference_select widget doesn't need to create its own + // element, it can wrap around the optionwidgets_select element. + // Add a validation step where the value can be unwrapped. + $field_key = $element['#columns'][0]; + $element[$field_key] = array( + '#type' => 'optionwidgets_buttons', + '#default_value' => isset($element['#value']) ? $element['#value'] : '', + // The following values were set by the field module and need + // to be passed down to the nested element. + '#title' => $element['#title'], + '#required' => $element['#required'], + '#description' => $element['#description'], + '#field_name' => $element['#field_name'], + '#type_name' => $element['#type_name'], + '#delta' => $element['#delta'], + '#columns' => $element['#columns'], + ); + if (empty($element[$field_key]['#element_validate'])) { + $element[$field_key]['#element_validate'] = array(); + } + array_unshift($element[$field_key]['#element_validate'], 'user_reference_optionwidgets_validate'); + return $element; +} + +/** + * Process an individual element. + * + * Build the form element. When creating a form using FAPI #process, + * note that $element['#value'] is already set. + * + * The $field and $instance arrays are in $form['#fields'][$element['#field_name']]. + */ +function user_reference_autocomplete_process($element, $edit, $form_state, $form) { + // The user_reference autocomplete widget doesn't need to create its own + // element, it can wrap around the text_textfield element and add an autocomplete + // path and some extra processing to it. + // Add a validation step where the value can be unwrapped. + $field_key = $element['#columns'][0]; + + $element[$field_key] = array( + '#type' => 'text_textfield', + '#default_value' => isset($element['#value']) ? $element['#value'] : '', + '#autocomplete_path' => 'user_reference/autocomplete/'. $element['#field_name'], + // The following values were set by the field module and need + // to be passed down to the nested element. + '#title' => $element['#title'], + '#required' => $element['#required'], + '#description' => $element['#description'], + '#field_name' => $element['#field_name'], + '#type_name' => $element['#type_name'], + '#delta' => $element['#delta'], + '#columns' => $element['#columns'], + ); + if (empty($element[$field_key]['#element_validate'])) { + $element[$field_key]['#element_validate'] = array(); + } + array_unshift($element[$field_key]['#element_validate'], 'user_reference_autocomplete_validate'); + + // Used so that hook_field('validate') knows where to flag an error. + $element['_error_element'] = array( + '#type' => 'value', + // Wrapping the element around a text_textfield element creates a + // nested element, so the final id will look like 'field-name-0-uid-uid'. + '#value' => implode('][', array_merge($element['#parents'], array($field_key, $field_key))), + ); + return $element; +} + +/** + * Validate a select/buttons element. + * + * Remove the wrapper layer and set the right element's value. + * We don't know exactly where this element is, so we drill down + * through the element until we get to our key. + * + * We use $form_state['values'] instead of $element['#value'] + * to be sure we have the most accurate value when other modules + * like optionwidgets are using #element_validate to alter the value. + */ +function user_reference_optionwidgets_validate($element, &$form_state) { + $field_key = $element['#columns'][0]; + + $value = $form_state['values']; + $new_parents = array(); + foreach ($element['#parents'] as $parent) { + $value = $value[$parent]; + // Use === to be sure we get right results if parent is a zero (delta) value. + if ($parent === $field_key) { + $element['#parents'] = $new_parents; + form_set_value($element, $value, $form_state); + break; + } + $new_parents[] = $parent; + } +} + +/** + * Validate an autocomplete element. + * + * Remove the wrapper layer and set the right element's value. + * This will move the nested value at 'field-name-0-uid-uid' + * back to its original location, 'field-name-0-uid'. + */ +function user_reference_autocomplete_validate($element, &$form_state) { + $field_name = $element['#field_name']; + $type_name = $element['#type_name']; + $field = field_fields($field_name, $type_name); + $field_key = $element['#columns'][0]; + $value = $element['#value'][$field_key]; + $uid = NULL; + if (!empty($value)) { + $reference = _user_reference_potential_references($field, $value, 'equals', NULL, 1); + if (empty($reference)) { + form_error($element[$field_key], t('%name: found no valid user with that name.', array('%name' => t($field['widget']['label'])))); + } + else { + $uid = key($reference); + } + } + form_set_value($element, $uid, $form_state); +} + +/** + * Implementation of hook_allowed_values(). + */ +function user_reference_allowed_values($field) { + $references = _user_reference_potential_references($field); + + $options = array(); + foreach ($references as $key => $value) { + // Views theming runs check_plain (htmlentities) on the values. + // We reverse that with html_entity_decode. + $options[$key] = html_entity_decode(strip_tags($value['rendered']), ENT_QUOTES); + } + return $options; +} + +/** + * Fetch an array of all candidate referenced users. + * + * This info is used in various places (aloowed values, autocomplete results, + * input validation...). Some of them only need the uids, others nid + names, + * others yet uid + names + rendered row (for display in widgets). + * The array we return contains all the potentially needed information, and lets + * consumers use the parts they actually need. + * + * @param $field + * The field description. + * @param $string + * Optional string to filter usernames on (used by autocomplete) + * @param $match + * Operator to match filtered name against, can be any of: + * 'contains', 'equals', 'starts_with' + * @param $ids + * Optional user ids to lookup (the $string and $match arguments will be + * ignored). + * @param $limit + * If non-zero, limit the size of the result set. + * + * @return + * An array of valid users in the form: + * array( + * uid => array( + * 'title' => The user name, + * 'rendered' => The text to display in widgets (can be HTML) + * ), + * ... + * ) + */ +function _user_reference_potential_references($field, $string = '', $match = 'contains', $ids = array(), $limit = NULL) { + static $results = array(); + + // Create unique id for static cache. + $cid = $field['field_name'] .':'. $match .':'. ($string !== '' ? $string : implode('-', $ids)) .':'. $limit; + if (!isset($results[$cid])) { + $references = FALSE; + if ($references === FALSE) { + $references = _user_reference_potential_references_standard($field, $string, $match, $ids, $limit); + } + + // Store the results. + $results[$cid] = $references; + } + + return $results[$cid]; +} + +/** + * Helper function for _user_reference_potential_references(): + * referenceable users defined by user role and status + */ +function _user_reference_potential_references_standard($field, $string = '', $match = 'contains', $ids = array(), $limit = NULL) { + $where = array(); + $args = array(); + $join = array(); + + if ($string !== '') { + $match_operators = array( + 'contains' => "LIKE '%%%s%%'", + 'equals' => "= '%s'", + 'starts_with' => "LIKE '%s%%'", + ); + $where[] = 'u.name '. (isset($match_operators[$match]) ? $match_operators[$match] : $match_operators['contains']); + $args[] = $string; + } + elseif ($ids) { + $where[] = 'u.uid IN (' . db_placeholders($ids) . ')'; + $args = array_merge($args, $ids); + } + else { + $where[] = "u.uid > 0"; + } + + $roles = array(); + if (isset($field['referenceable_roles']) && is_array($field['referenceable_roles'])) { + // keep only selected checkboxes + $roles = array_filter($field['referenceable_roles']); + // filter invalid values that seems to get through sometimes ?? + $roles = array_intersect(array_keys(user_roles(1)), $roles); + } + if (!empty($roles) && !in_array(DRUPAL_AUTHENTICATED_RID, $roles)) { + $where[] = "r.rid IN (". implode($roles, ',') .")"; + $join[] = 'LEFT JOIN {users_roles} r ON u.uid = r.uid'; + } + + $status = array(); + if (isset($field['referenceable_status']) && is_array($field['referenceable_status'])) { + // keep only selected checkboxes + $status = array_filter($field['referenceable_status']); + } + if (!empty($status)) { + // Limit query if only one status should be referenced. + if (count($status) == 1) { + $where[] = "u.status = ". array_pop($status); + } + } + + $users = array(); + $where_clause = $where ? 'WHERE ('. implode(') AND (', $where) .')' : ''; + $result = db_query('SELECT u.name, u.uid FROM {users} u '. implode(' ', $join) ." $where_clause ORDER BY u.name ASC", $args); + while ($user = db_fetch_object($result)) { + $users[$user->uid] = array( + 'title' => $user->name, + 'rendered' => $user->name, + ); + } + return $users; +} + +/** + * Menu callback; Retrieve a pipe delimited string of autocomplete suggestions for existing users + */ +function user_reference_autocomplete($field_name, $string = '') { + $fields = field_info_fields(); + $field = $fields[$field_name]; + $match = isset($field['widget']['autocomplete_match']) ? $field['widget']['autocomplete_match'] : 'contains'; + $matches = array(); + + $references = _user_reference_potential_references($field, $string, $match, array(), 10); + foreach ($references as $id => $row) { + // Add a class wrapper for a few required CSS overrides. + $matches[$row['title']] = '
'. $row['rendered'] . '
'; + } + drupal_json($matches); +} + +/** + * Implementation of hook_user(). + */ +function user_reference_user($type, &$edit, &$account) { + switch ($type) { + case 'load': + // Only add links if we are on the user 'view' page. + if (arg(0) != 'user' || arg(2)) { + return; + } + // find CCK user_reference field tables + // search through them for matching user ids and load those nodes + $additions = array(); + $types = content_types(); + + // Find the table and columns to search through, if the same + // table comes up in more than one field type, we only need + // to search it once. + $search_tables = array(); + $search_links = array(); + foreach ($types as $type_name => $type) { + foreach ($type['fields'] as $field) { + // Only add tables when reverse link has been selected. + if ($field['type'] == 'user_reference' && !empty($field['widget']['reverse_link'])) { + $db_info = content_database_info($field); + $search_tables[$db_info['table']] = $db_info['columns']['uid']['column']; + $search_links[$db_info['table']] = $field['widget']['reverse_link']; + } + } + } + foreach ($search_tables as $table => $column) { + $ids = db_query(db_rewrite_sql("SELECT DISTINCT(n.nid) FROM {node} n LEFT JOIN {". $table ."} f ON n.vid = f.vid WHERE f.". $column ."=". $account->uid. " AND n.status = 1")); + while ($data = db_fetch_object($ids)) { + // TODO, do we really want a complete node_load() here? We only need the title to create a link. + $node = node_load($data->nid); + $node->reverse_link = $search_links[$table]; + $additions[$node->type][] = $node; + } + } + $account->user_reference = $additions; + return; + break; + + case 'view': + if (!empty($account->user_reference)) { + $node_types = content_types(); + $additions = array(); + $values = array(); + foreach ($account->user_reference as $node_type => $nodes) { + foreach ($nodes as $node) { + if ($node->reverse_link) { + $values[$node_type][] = l($node->title, 'node/'. $node->nid); + } + } + if (isset($values[$node_type])) { + $additions[] = array( + '#type' => 'user_profile_item', + '#title' => check_plain($node_types[$node_type]['name']), + '#value' => theme('item_list', $values[$node_type]), + ); + } + } + if ($additions) { + $account->content['user_reference'] = $additions + array( + '#type' => 'user_profile_category', + '#attributes' => array('class' => 'user-member'), + '#title' => t('Related content'), + '#weight' => 10, + ); + } + } + break; + } +} + +/** + * FAPI theme for an individual elements. + * + * The textfield or select is already rendered by the + * textfield or select themes and the html output + * lives in $element['#children']. Override this theme to + * make custom changes to the output. + * + * $element['#field_name'] contains the field name + * $element['#delta] is the position of this element in the group + */ +function theme_user_reference_select($element) { + return $element['#children']; +} + +function theme_user_reference_buttons($element) { + return $element['#children']; +} + +function theme_user_reference_autocomplete($element) { + return $element['#children']; +} \ No newline at end of file diff --git a/theme/cck-admin-display-overview-form.tpl.php b/theme/cck-admin-display-overview-form.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..9e13d54dbb7a2751f8c26a35f6a922db2f627c4c --- /dev/null +++ b/theme/cck-admin-display-overview-form.tpl.php @@ -0,0 +1,42 @@ + +
+ +
+ + + + + + $value): ?> + + + + $value): ?> + + + + + + + + + + + $title): ?> + + + + + + + +
  + +
indentation; ?>human_name; ?>{$context}->label; ?>{$context}->type; ?>{$context}->exclude; ?>
+ + diff --git a/theme/cck-admin-field-overview-form.tpl.php b/theme/cck-admin-field-overview-form.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..53d9e01ff454060507f07d3d50713fb4aa2b98ca --- /dev/null +++ b/theme/cck-admin-field-overview-form.tpl.php @@ -0,0 +1,103 @@ + +
+ +
+ + + + + + + + + + + + + + + row_type): + case 'field': ?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ indentation; ?> + label; ?> + weight . $row->parent . $row->hidden_name; ?>field_name; ?>type; ?>widget_type; ?>configure; ?>  remove; ?> + indentation; ?> + label; ?> + weight . $row->parent . $row->hidden_name; ?>group_name; ?>group_type; ?>configure; ?>  remove; ?> + indentation; ?> + label; ?> + weight . $row->parent . $row->hidden_name; ?>description; ?> + indentation; ?> +
+
+ label; ?> +
+
 
weight . $row->parent . $row->hidden_name; ?>
 
field_name; ?>
 
type; ?>
 
widget_type; ?>
+ indentation; ?> +
+
+ label; ?> +
+
 
weight . $row->parent . $row->hidden_name; ?>
 
field_name; ?>
 
widget_type; ?>
+ indentation; ?> +
+
+ label; ?> +
+
 
weight . $row->parent . $row->hidden_name; ?>
 
group_name; ?>
 
group_type; ?>
 
group_option; ?>
+ + + diff --git a/theme/cck.css b/theme/cck.css new file mode 100644 index 0000000000000000000000000000000000000000..fc5d68525d76545f7d6639296d82b91410800193 --- /dev/null +++ b/theme/cck.css @@ -0,0 +1,28 @@ +/* $Id$ */ + +/* 'Manage fields' overview */ +#cck-field-overview-form .advanced-help-link, +#cck-display-overview-form .advanced-help-link { + margin: 4px 4px 0 0; +} +#cck-field-overview-form .label-group, +#cck-display-overview-form .label-group, +#cck-copy-export-form .label-group { + font-weight: bold; +} +table#cck-field-overview .label-add-new-field, +table#cck-field-overview .label-add-existing-field, +table#cck-field-overview .label-add-new-group { + float: left; +} +table#cck-field-overview tr.cck-add-new .tabledrag-changed { + display: none; +} +table#cck-field-overview tr.cck-add-new .description { + margin-bottom: 0; +} +table#cck-field-overview .cck-new { + font-weight: bold; + padding-bottom: .5em; +} + diff --git a/theme/theme.inc b/theme/theme.inc new file mode 100644 index 0000000000000000000000000000000000000000..bf06543a0bcbba7d1c6a3ce6b00e80d160b60746 --- /dev/null +++ b/theme/theme.inc @@ -0,0 +1,164 @@ +'. t('You can add a field to a group by dragging it below and to the right of the group.'); + } + if (!module_exists('advanced_help')) { + //$vars['help'] .= '
' . t('Note: Installing the Advanced help module will let you access more and better help.', array('!adv_help' => 'http://drupal.org/project/advanced_help')); + } + + $order = _cck_overview_order($form, $form['#field_rows'], $form['#group_rows']); + $rows = array(); + + // Identify the 'new item' keys in the form, they look like + // _add_new_field, add_new_group. + $keys = array_keys($form); + $add_rows = array(); + foreach ($keys as $key) { + if (substr($key, 0, 4) == '_add') { + $add_rows[] = $key; + } + } + while ($order) { + $key = reset($order); + $element = &$form[$key]; + + // Only display the 'Add' separator if the 'add' rows are still + // at the end of the table. + if (!isset($added_separator)) { + $remaining_rows = array_diff($order, $add_rows); + if (empty($remaining_rows) && empty($element['#depth'])) { + $row = new stdClass(); + $row->row_type = 'separator'; + $row->class = 'tabledrag-leaf region'; + $rows[] = $row; + $added_separator = TRUE; + } + } + + $row = new stdClass(); + + // Add target classes for the tabledrag behavior. + $element['weight']['#attributes']['class'] = 'field-weight'; + $element['parent']['#attributes']['class'] = 'group-parent'; + $element['hidden_name']['#attributes']['class'] = 'field-name'; + // Add target classes for the update selects behavior. + switch ($element['#row_type']) { + case 'add_new_field': + $element['type']['#attributes']['class'] = 'cck-field-type-select'; + $element['widget_type']['#attributes']['class'] = 'cck-widget-type-select'; + break; + case 'add_existing_field': + $element['field_name']['#attributes']['class'] = 'cck-field-select'; + $element['widget_type']['#attributes']['class'] = 'cck-widget-type-select'; + $element['label']['#attributes']['class'] = 'cck-label-textfield'; + break; + } + foreach (element_children($element) as $child) { + $row->{$child} = drupal_render($element[$child]); + } + $row->label_class = 'label-'. strtr($element['#row_type'], '_', '-'); + $row->row_type = $element['#row_type']; + $row->indentation = theme('indentation', isset($element['#depth']) ? $element['#depth'] : 0); + $row->class = 'draggable'; + $row->class .= isset($element['#disabled_row']) ? ' menu-disabled' : ''; + $row->class .= isset($element['#add_new']) ? ' cck-add-new' : ''; + $row->class .= isset($element['#leaf']) ? ' tabledrag-leaf' : ''; + $row->class .= isset($element['#root']) ? ' tabledrag-root' : ''; + + $rows[] = $row; + array_shift($order); + } + $vars['rows'] = $rows; + $vars['submit'] = drupal_render($form); + + // Add tabledrag behavior. +// drupal_add_tabledrag('cck-field-overview', 'match', 'parent', 'group-parent', 'group-parent', 'field-name', FALSE, 1); + drupal_add_tabledrag('cck-field-overview', 'match', 'parent', 'group-parent', 'group-parent', 'field-name', TRUE, 1); +// drupal_add_tabledrag('cck-field-overview', 'order', 'sibling', 'field-weight', NULL, NULL, FALSE); + drupal_add_tabledrag('cck-field-overview', 'order', 'sibling', 'field-weight'); + + // Add settings for the update selects behavior. + // TODO D7 : adapt... + $js_fields = array(); + foreach (cck_existing_field_options($form['#bundle']) as $field_name => $fields) { + $field = field_info_field($field_name); + $instance = field_info_instance($field_name, $form['#bundle']); + $js_fields[$field_name] = array('label' => $instance['label'], 'type' => $field['type'], 'widget' => $instance['widget']['type']); + } + drupal_add_js(array('cckWidgetTypes' => cck_widget_type_options(), 'cckFields' => $js_fields), 'setting'); + drupal_add_js(drupal_get_path('module', 'cck') .'/cck.js'); +} + +/** + * Theme preprocess function for cck-admin-display-overview-form.tpl.php. + */ +function template_preprocess_cck_display_overview_form(&$vars) { + $form = &$vars['form']; + + $contexts_selector = $form['#contexts']; + $vars['basic'] = $contexts_selector == 'basic'; + + $vars['contexts'] = cck_build_modes(field_info_bundle_entity($form['#bundle']), $contexts_selector); + + switch ($form['#bundle']) { + case 'user': + $help = t("Configure how user fields and field labels should be displayed. Use the 'Exclude' checkbox to exclude an item from the !content value passed to the user template.", array('!content' => '$user_profile')); + break; + + default: + if ($contexts_selector == 'basic') { + $help = t("Configure how this content type's fields and field labels should be displayed when it's viewed in teaser and full-page mode."); + } + else { + $help = t("Configure how this content type's fields should be displayed when it's rendered in the following contexts."); + } + $help .= ' '. t("Use the 'Exclude' checkbox to exclude an item from the !content value passed to the node template.", array('!content' => '$content')); + } + + $vars['help'] = $help; + + $order = _cck_overview_order($form, $form['#fields'], $form['#groups']); + if (empty($order)) { + $vars['rows'] = array(); + $vars['submit'] = ''; + return; + } + $rows = array(); + foreach ($order as $key) { + $element = &$form[$key]; + $row = new stdClass(); + foreach (element_children($element) as $child) { + if (!array_key_exists('exclude', $element[$child])) { + $row->{$child} = drupal_render($element[$child]); + } + else { + $row->{$child}->label = drupal_render($element[$child]['label']); + $row->{$child}->type = drupal_render($element[$child]['type']); + $row->{$child}->exclude = drupal_render($element[$child]['exclude']); + } + } + $row->label_class = in_array($key, $form['#groups']) ? 'label-group' : 'label-field'; + $row->indentation = theme('indentation', isset($element['#depth']) ? $element['#depth'] : 0); + $rows[] = $row; + } + + $vars['rows'] = $rows; + $vars['submit'] = drupal_render($form); +} \ No newline at end of file