type; $content_type = content_types($type_name); // Make sure this doesn't fire until content_types is working, // needed to avoid errors on initial installation. if (!empty($content_type)) { $type_url_str = $content_type['url_str']; $items['admin/content/node-type/'. $type_url_str .'/add_group'] = array( 'title' => 'Add group', 'page callback' => 'drupal_get_form', 'page arguments' => array('fieldgroup_edit_group_form', $content_type, '', 'add'), 'access arguments' => array('administer content types'), 'type' => MENU_LOCAL_TASK, 'weight' => 3, ); $items['admin/content/node-type/'. $type_url_str .'/groups/%/edit'] = array( 'title' => 'Edit group', 'page callback' => 'drupal_get_form', 'page arguments' => array('fieldgroup_edit_group_form', $content_type, 5, 'edit'), 'access arguments' => array('administer content types'), 'type' => MENU_CALLBACK, ); $items['admin/content/node-type/'. $type_url_str .'/groups/%/remove'] = array( 'title' => 'Edit group', 'page callback' => 'drupal_get_form', 'page arguments' => array('fieldgroup_remove_group', $content_type, 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_display_overview_form' => array( 'arguments' => array('form' => NULL), ), ); } /** * Implementation of hook_elements(). */ function fieldgroup_elements() { return array( 'fieldgroup_simple' => array(), ); } /** * Implementation of hook_fieldapi(). */ function fieldgroup_content_fieldapi($op, $field) { switch ($op) { case 'delete': db_query("DELETE FROM {". fieldgroup_fields_tablename() ." WHERE field_name = '%s'", $field['field_name']); break; } cache_clear_all('fieldgroup_data', content_cache_tablename()); } function fieldgroup_edit_group_form(&$form_state, $content_type, $group_name, $action) { $groups = fieldgroup_groups($content_type['type']); if ($action == 'add') { //adding a new one $group = array(); $form['submit'] = array( '#type' => 'submit', '#value' => t('Add'), '#weight' => 10, ); } elseif ($group = $groups[$group_name]) { $form['submit'] = array( '#type' => 'submit', '#value' => t('Save'), '#weight' => 10, ); } else { drupal_not_found(); exit; } $form['label'] = array( '#type' => 'textfield', '#title' => t('Label'), '#default_value' => isset($group['label']) ? $group['label'] : '', '#required' => TRUE, ); $form['settings']['#tree'] = TRUE; $form['settings']['form'] = array( '#type' => 'fieldset', '#title' => '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' => isset($group['settings']['form']['style']) ? $group['settings']['form']['style'] : 'fieldset', '#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' => isset($group['settings']['form']['description']) ? $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' => 'Display settings', '#description' => t('These settings apply to the group on node display.'), ); $form['settings']['display']['description'] = array( '#type' => 'textarea', '#title' => t('Description'), '#default_value' => isset($group['settings']['display']['description']) ? $group['settings']['display']['description'] : '', '#rows' => 5, '#description' => t('A description of the group.'), '#required' => FALSE, ); module_load_include('inc', 'content', 'content_admin'); foreach (array_merge(array_keys(_content_admin_display_contexts(CONTENT_CONTEXTS_SIMPLE)), array('label')) as $key) { $form['settings']['display'][$key] = array('#type' => 'value', '#value' => isset($group['settings']['display'][$key]) ? $group['settings']['display'][$key] : 'fieldset'); } $form['weight'] = array('#type' => 'hidden', '#default_value' => isset($group['weight']) ? $group['weight'] : 0); $form['group_name'] = array('#type' => 'hidden', '#default_value' => $group_name); $form['#content_type'] = $content_type; $form['#group_action'] = $action; $form['#submit'][] = 'fieldgroup_edit_group_submit'; return $form; } /** * Group name validation for programmatic group addition. * * @todo * Come back here and do the same thing for groups as we've done for fields, * present a machine-readable name field as well as a label instead of * trying to create a machine-readable name from the label. */ function fieldgroup_edit_group_validate($form, &$form_state) { $form_values = $form_state['values']; $content_type = $form['#content_type']; $action = $form['#group_action']; if (!empty($form_values['group_name']) && $action == 'add') { $groups = fieldgroup_groups($content_type['type']); $group = $groups[$form_values['group_name']]; if (isset($group[$form_values['group_name']])) { form_set_error('group_name', t('The group name %name already exists.', array( '%group_name' => $form_values['group_name']))); } if (!preg_match('!^[a-z0-9_]+$!', $form_values['group_name'])) { form_set_error('group_name', t('The group name %name is invalid.', array( '%group_name' => $form_values['group_name']))); } } } function fieldgroup_edit_group_submit($form, &$form_state) { $form_values = $form_state['values']; $content_type = $form['#content_type']; // $groups = fieldgroup_groups($content_type['type']); // $group = $groups[$form_values['group_name']]; // TODO : when a group is added, it is displayed as a fieldset *with no label* // whereas the 'display field' overview states 'Label : above' // (the description is not displayed either) // fixed when the 'display field' form is submitted. fieldgroup_save_group($content_type['type'], $form_values); $form_state['redirect'] = 'admin/content/node-type/'. $content_type['url_str'] .'/fields'; } function fieldgroup_remove_group(&$form_state, $content_type, $group_name) { $groups = fieldgroup_groups($content_type['type']); $group = isset($groups[$group_name]) ? $groups[$group_name] : ''; if (empty($group)) { drupal_not_found(); exit; } $form['#submit'][] = 'fieldgroup_remove_group_submit'; $form['#content_type'] = $content_type; $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']))), 'admin/content/node-type/'. $content_type['url_str'] .'/fields', t('This action cannot be undone.'), t('Remove'), t('Cancel')); } function fieldgroup_remove_group_submit($form, &$form_state) { $form_values = $form_state['values']; $content_type = $form['#content_type']; $group_name = $form['#group_name']; fieldgroup_delete($content_type['type'], $group_name); drupal_set_message(t('The group %group_name has been removed.', array('%group_name' => $group_name))); $form_state['redirect'] = 'admin/content/node-type/'. $content_type['url_str'] .'/fields'; } /* * Returns all groups for a content type */ function fieldgroup_groups($content_type = '', $sorted = FALSE, $reset = FALSE) { static $groups, $groups_sorted; if (!isset($groups) || $reset) { if ($cached = cache_get('fieldgroup_data', content_cache_tablename())) { $data = $cached->data; $groups = $data['groups']; $groups_sorted = $data['groups_sorted']; } else { $result = db_query("SELECT * FROM {". fieldgroup_tablename() ."} 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['type_name']][$group['group_name']] = $group; $groups_sorted[$group['type_name']][] = &$groups[$group['type_name']][$group['group_name']]; } //load fields $result = db_query("SELECT nfi.*, ng.group_name FROM {". fieldgroup_tablename() ."} ng ". "INNER JOIN {". fieldgroup_fields_tablename() ."} ngf ON ngf.type_name = ng.type_name AND ngf.group_name = ng.group_name ". "INNER JOIN {". content_instance_tablename() ."} nfi ON nfi.field_name = ngf.field_name AND nfi.type_name = ngf.type_name ". "WHERE nfi.widget_active = 1 ORDER BY nfi.weight"); while ($field = db_fetch_array($result)) { $groups[$field['type_name']][$field['group_name']]['fields'][$field['field_name']] = $field; } cache_set('fieldgroup_data', array('groups' => $groups, 'groups_sorted' => $groups_sorted), content_cache_tablename()); } } if (empty($content_type)) { return $groups; } elseif (empty($groups) || empty($groups[$content_type])) { return array(); } return $sorted ? $groups_sorted[$content_type] : $groups[$content_type]; } function _fieldgroup_groups_label($content_type) { $groups = fieldgroup_groups($content_type); $labels[''] = '<'. t('none') .'>'; foreach ($groups as $group_name => $group) { $labels[$group_name] = t($group['label']); } return $labels; } function _fieldgroup_field_get_group($content_type, $field_name) { return db_result(db_query("SELECT group_name FROM {". fieldgroup_fields_tablename() ."} WHERE type_name = '%s' AND field_name = '%s'", $content_type, $field_name)); } /* * Implementation of hook_form_alter() */ function fieldgroup_form_alter(&$form, $form_state, $form_id) { if (isset($form['type']) && isset($form['#node']) && $form['type']['#value'] .'_node_form' == $form_id) { foreach (fieldgroup_groups($form['type']['#value']) as $group_name => $group) { $form[$group_name] = array( '#type' => 'fieldset', '#title' => 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' => 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])) { $form[$group_name][$field_name] = $form[$field_name]; // Track whether this group has any accessible fields within it. if ($form[$field_name]['#access'] !== FALSE) { $has_accessible_field = TRUE; } unset($form[$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; } } } elseif ($form_id == '_content_admin_field') { $content_type = content_types($form['type_name']['#value']); $form['widget']['group'] = array( '#type' => 'select', '#title' => t('Display in group'), '#options' => _fieldgroup_groups_label($content_type['type']), '#default_value' => _fieldgroup_field_get_group($content_type['type'], $form['field_name']['#value']), '#description' => t('Select a group, in which the field will be displayed on the editing form.'), '#weight' => 5, ); $form['widget']['weight']['#weight'] = 5; $form['widget']['description']['#weight'] = 7; $form['#submit'][] = 'fieldgroup_content_admin_form_submit'; } elseif ($form_id == 'content_admin_field_overview_form' && !empty($form['#groups'])) { $form['#submit'][] = 'fieldgroup_content_overview_form_submit'; } elseif ($form_id == 'content_admin_display_overview_form' && !empty($form['#groups'])) { $form['#submit'][] = 'fieldgroup_display_overview_form_submit'; if (!isset($form['submit'])) { $form['submit'] = array('#type' => 'submit', '#value' => t('Submit'), '#weight' => 10); } } elseif ($form_id == '_content_admin_field_remove') { $form['#submit'][] = 'fieldgroup_content_admin_field_remove_submit'; } } function fieldgroup_content_admin_form_submit($form, &$form_state) { $form_values = $form_state['values']; fieldgroup_update_fields($form_values); } function fieldgroup_content_overview_form_submit($form, &$form_state) { $form_values = $form_state['values']; $type_name = $form['#type_name']; foreach ($form_values as $key => $values) { // Update field parenting. if (in_array($key, $form['#fields'])) { // TODO : check the parent group does exist ? fieldgroup_update_fields(array('field_name' => $key, 'group' => $values['parent'], 'type_name' => $type_name)); } // Update groups weights. elseif (in_array($key, $form['#groups'])) { db_query("UPDATE {". fieldgroup_tablename() ."} SET weight = %d WHERE type_name = '%s' AND group_name = '%s'", $values['weight'], $type_name, $key); } } cache_clear_all('fieldgroup_data', content_cache_tablename()); } function fieldgroup_display_overview_form_submit($form, &$form_state) { $form_values = $form_state['values']; $groups = fieldgroup_groups($form['#type_name']); foreach ($form_values as $key => $values) { if (in_array($key, $form['#groups'])) { $group = $groups[$key]; // We have numeric keys here, so we can't use array_merge. $group['settings']['display'] = $values + $group['settings']['display']; fieldgroup_save_group($form['#type_name'], $group); } } } function fieldgroup_content_admin_field_remove_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 {". fieldgroup_fields_tablename() ."} WHERE type_name = '%s' AND field_name = '%s'", $form_values['type_name'], $form_values['field_name']); } /** * Implementation of hook_nodeapi(). */ function fieldgroup_nodeapi(&$node, $op, $teaser, $page) { switch ($op) { case 'view': $context = $teaser ? 'teaser' : 'full'; foreach (fieldgroup_groups($node->type) as $group_name => $group) { $label = $group['settings']['display']['label'] == 'above'; $element = array( '#title' => $label ? t($group['label']) : '', '#description' => $label ? t($group['settings']['display']['description']) : '', '#weight' => $group['weight'], ); switch ($group['settings']['display'][$context]['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'] = 'fieldset'; $element['#attributes'] = array('class' => 'fieldgroup '. strtr($group['group_name'], '_', '-')); break; } $node->content[$group_name] = $element; foreach ($group['fields'] as $field_name => $field) { if (isset($node->content[$field_name])) { $node->content[$group_name][$field_name] = $node->content[$field_name]; unset($node->content[$field_name]); } } } break; } } /** * 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 template_preprocess_fieldgroup_simple(&$variables) { $element = $variables['element']; $variables['group_name'] = $element['#group_name']; $variables['group_name_css'] = strtr($element['#group_name'], '_', '-'); $variables['label'] = isset($element['#title']) ? $element['#title'] : '';; $variables['description'] = isset($element['#description']) ? $element['#description'] : '';; $variables['content'] = isset($element['#children']) ? $element['#children'] : ''; } /* * 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($content_type, $field_name) { foreach (fieldgroup_groups($content_type) 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 {". fieldgroup_tablename() ."} SET type_name='%s' WHERE type_name='%s'", array($info->type, $info->old_type)); db_query("UPDATE {". fieldgroup_fields_tablename() ."} SET type_name='%s' WHERE type_name='%s'", array($info->type, $info->old_type)); cache_clear_all('fieldgroup_data', content_cache_tablename()); } elseif ($op == 'delete') { db_query("DELETE FROM {". fieldgroup_tablename() ."} WHERE type_name = '%s'", $info->type); db_query("DELETE FROM {". fieldgroup_fields_tablename() ."} WHERE type_name = '%s'", $info->type); } } function fieldgroup_tablename($version = NULL) { if (is_null($version)) { $version = variable_get('fieldgroup_schema_version', 0); } return $version < 6000 ? 'node_group' : 'content_group'; } function fieldgroup_fields_tablename($version = NULL) { if (is_null($version)) { $version = variable_get('fieldgroup_schema_version', 0); } return $version < 6000 ? 'node_group_fields' : 'content_group_fields'; } /** * 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($type_name, $group) { $groups = fieldgroup_groups($type_name); if (!isset($groups[$group['group_name']])) { // Accept group name from programmed submissions if valid. if (!empty($group['group_name'])) { $group_name = $group['group_name']; } else { // Otherwise, find a valid, computer-friendly name. $group_name = trim($group['label']); $group_name = drupal_strtolower($group_name); $group_name = str_replace(array(' ', '-'), '_', $group_name); $group_name = preg_replace('/[^a-z0-9_]/', '', $group_name); $group_name = 'group_'. $group_name; $group_name = substr($group_name, 0, 30); if (isset($groups[$group_name])) { $group_name_base = $group_name; $counter = 0; while (isset($groups[$group_name])) { $group_name = $group_name_base .'_'. $counter++; } } } db_query("INSERT INTO {". fieldgroup_tablename() ."} (type_name, group_name, label, settings, weight) VALUES ('%s', '%s', '%s', '%s', %d)", $type_name, $group_name, $group['label'], serialize($group['settings']), $group['weight']); cache_clear_all('fieldgroup_data', content_cache_tablename()); return SAVED_NEW; } else { db_query("UPDATE {". fieldgroup_tablename() ."} SET label = '%s', settings = '%s', weight = %d ". "WHERE type_name = '%s' AND group_name = '%s'", $group['label'], serialize($group['settings']), $group['weight'], $type_name, $group['group_name']); cache_clear_all('fieldgroup_data', content_cache_tablename()); return SAVED_UPDATED; } } function fieldgroup_update_fields($form_values) { $default = _fieldgroup_field_get_group($form_values['type_name'], $form_values['field_name']); if ($default != $form_values['group']) { if ($form_values['group'] && !$default) { db_query("INSERT INTO {". fieldgroup_fields_tablename() ."} (type_name, group_name, field_name) VALUES ('%s', '%s', '%s')", $form_values['type_name'], $form_values['group'], $form_values['field_name']); } elseif ($form_values['group']) { db_query("UPDATE {". fieldgroup_fields_tablename() ."} SET group_name = '%s' WHERE type_name = '%s' AND field_name = '%s'", $form_values['group'], $form_values['type_name'], $form_values['field_name']); } else { db_query("DELETE FROM {". fieldgroup_fields_tablename() ."} WHERE type_name = '%s' AND field_name = '%s'", $form_values['type_name'], $form_values['field_name']); } cache_clear_all('fieldgroup_data', content_cache_tablename()); } } function fieldgroup_delete($content_type, $group_name) { db_query("DELETE FROM {". fieldgroup_tablename() ."} WHERE type_name = '%s' AND group_name = '%s'", $content_type, $group_name); db_query("DELETE FROM {". fieldgroup_fields_tablename() ."} WHERE type_name = '%s' AND group_name = '%s'", $content_type, $group_name); cache_clear_all('fieldgroup_data', content_cache_tablename()); }