fieldgroup.module in Content Construction Kit (CCK) 6.3
Same filename and directory in other branches
Create field groups for CCK fields.
Hooks for other modules to intervene include:
- hook_fieldgroup_view: Alter the group $element added to $node->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.
File
modules/fieldgroup/fieldgroup.moduleView source
<?php
/**
* @file
* Create field groups for CCK fields.
*
* Hooks for other modules to intervene include:
* - hook_fieldgroup_view: Alter the group $element added to $node->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_ctools_plugin_directory().
*/
function fieldgroup_ctools_plugin_directory($module, $plugin) {
if ($module == 'ctools' && $plugin == 'content_types') {
return 'panels/' . $plugin;
}
}
/**
* Implementation of hook_menu().
*/
function fieldgroup_menu() {
$items = array();
// Make sure this doesn't fire until content_types is working,
// needed to avoid errors on initial installation.
if (!defined('MAINTENANCE_MODE')) {
foreach (node_get_types() as $type) {
$type_name = $type->type;
$content_type = content_types($type_name);
$type_url_str = $content_type['url_str'];
$items['admin/content/node-type/' . $type_url_str . '/groups/%'] = array(
'title' => 'Edit group',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'fieldgroup_group_edit_form',
$type_name,
5,
),
'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',
$type_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-simple',
'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_content_fieldapi($op, $field) {
switch ($op) {
case 'delete instance':
db_query("DELETE FROM {" . fieldgroup_fields_tablename() . "} WHERE field_name = '%s' AND type_name = '%s'", $field['field_name'], $field['type_name']);
cache_clear_all('fieldgroup_data:', content_cache_tablename(), TRUE);
break;
}
}
function fieldgroup_group_edit_form(&$form_state, $type_name, $group_name) {
$content_type = content_types($type_name);
$groups = fieldgroup_groups($content_type['type']);
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 node 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(content_build_modes()) 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['parent'] = array(
'#type' => 'hidden',
'#default_value' => $group['parent'],
);
$form['#content_type'] = $content_type;
$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'];
$content_type = $form['#content_type'];
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, $type_name, $group_name) {
$content_type = content_types($type_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'];
$parent = db_fetch_array(db_query("SELECT parent FROM {" . fieldgroup_tablename() . "} WHERE group_name = '%s' AND type_name = '%s'", $group_name, $content_type['type']));
$result = db_query("UPDATE {" . fieldgroup_tablename() . "} SET parent = '%s' WHERE parent = '%s' AND type_name = '%s'", $parent['parent'], $group_name, $content_type['type']);
$result = db_query("UPDATE {" . fieldgroup_fields_tablename() . "} SET group_name = '%s' WHERE group_name = '%s' AND type_name = '%s'", $parent['parent'], $group_name, $content_type['type']);
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) {
global $language;
static $groups, $groups_sorted;
if (!isset($groups) || $reset) {
if ($cached = cache_get('fieldgroup_data:' . $language->language, content_cache_tablename())) {
$data = $cached->data;
$groups = $data['groups'];
$groups_sorted = $data['groups_sorted'];
}
else {
$result = db_query("SELECT * FROM {" . fieldgroup_tablename() . "} ORDER BY type_name, weight");
$groups = array();
$groups_sorted = array();
while ($group = db_fetch_array($result)) {
$groups[$group['type_name']] = _fieldgroup_get_tree($group['type_name']);
$groups_sorted[$group['type_name']][] =& $groups[$group['type_name']][$group['group_name']];
}
cache_set('fieldgroup_data:' . $language->language, 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];
}
/**
* create a tree of fieldgroups for nesting them
*/
function _fieldgroup_get_tree($type_name, $parent = '', $depth = -1, $max_depth = null) {
static $children, $parents, $groups;
$depth++;
// We cache trees, so it's not CPU-intensive to call get_tree() on a term
// and its children, too.
if (!isset($children[$type_name])) {
$children[$type_name] = array();
$result = db_query("SELECT * FROM {" . fieldgroup_tablename() . "} WHERE type_name='%s' ORDER BY weight", $type_name);
while ($group = db_fetch_array($result)) {
$group['settings'] = unserialize($group['settings']);
$group['fields'] = array();
// Allow external modules to translate field group strings.
$group_strings = array(
'label' => $group['label'],
'form_description' => $group['settings']['form']['description'],
'display_description' => $group['settings']['display']['description'],
);
$group['label'] = $group_strings['label'];
$group['settings']['form']['description'] = $group_strings['form_description'];
$group['settings']['display']['description'] = $group_strings['display_description'];
$children[$type_name][$group['parent']][] = $group['group_name'];
$parents[$type_name][$group['group_name']][] = $group['parent'];
$groups[$type_name][$group['group_name']] = $group;
}
//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;
// Unserialize arrays.
foreach (array(
'widget_settings',
'display_settings',
'global_settings',
'db_columns',
) as $key) {
$groups[$field['type_name']][$field['group_name']]['fields'][$field['field_name']] = !empty($groups[$field['type_name']][$field['group_name']]['fields'][$field['field_name']][$key]) ? (array) unserialize($groups[$field['type_name']][$field['group_name']]['fields'][$field['field_name']][$key]) : array();
}
// For fields inside of groups, use the weight given by fieldgroup
$groups[$field['type_name']][$field['group_name']]['fields'][$field['field_name']]['weight'] = $field['weight'];
}
}
$max_depth = is_null($max_depth) ? count($children[$type_name]) : $max_depth;
if (isset($children[$type_name][$parent])) {
foreach ($children[$type_name][$parent] as $child_group_name) {
if ($max_depth > $depth) {
$group = $groups[$type_name][$child_group_name];
$group['depth'] = $depth;
$group['parents'] = $parents[$type_name][$child_group_name];
$tree[$group['group_name']] = $group;
if (!empty($children[$type_name][$child_group_name])) {
$tree = array_merge($tree, _fieldgroup_get_tree($type_name, $child_group_name, $depth, $max_depth));
}
}
}
}
return $tree ? $tree : array();
}
/**
* go through a set of fieldgroups and construct a simple representation of their hierarchy
*/
function _fieldgroup_plain_tree($items) {
$rows = array();
$rows[''] = '<' . t('none') . '>';
foreach ($items as $item) {
$group_name = $item['group_name'];
$label = t($item['label']);
if ($group_name) {
$rows[$group_name] = str_repeat('--', $item['depth']) . ' ' . $label;
}
}
return $rows;
}
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));
}
/**
* Return an array of the parents for a field or group.
*
* @param $content_type The content type of the field/group.
* @param $name The name of a group or field.
*/
function fieldgroup_get_parents($content_type, $name) {
$is_group = substr($name, 0, 6) == 'group_';
$counter = 0;
if ($is_group) {
$parents[$counter] = $name;
}
else {
if ($result = db_result(db_query("SELECT group_name FROM {" . fieldgroup_fields_tablename() . "} WHERE type_name = '%s' AND field_name = '%s'", $content_type, $name))) {
$parents[$counter] = $result;
}
}
while ($result = db_result(db_query("SELECT parent FROM {" . fieldgroup_tablename() . "} WHERE type_name = '%s' AND group_name = '%s'", $content_type, $parents[$counter]))) {
$counter++;
$parents[$counter] = $result;
}
return $parents;
}
function _fieldgroup_add_group_to_form(&$form, &$form_state, $form_id, $group_name, $group, $groups) {
$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'],
'#depth' => $group['depth'],
'#group_parent' => $group['parent'],
'#description' => content_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])) {
$form[$field_name]['#weight'] = $field['weight'];
$form[$group_name][$field_name] = $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;
}
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;
}
else {
//cascade visibility up
$form[$group_name]['#access'] = TRUE;
}
// 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);
}
}
/**
* This is function fieldgroup_order_fields_and_groups
*
* @param array $group_rows An empty array that we will fill.
* @param array $groups All of the info we need about all of the groups for the content type we're working on.
* @param array $field_check_off This contains the fields. We will unset them as we process them.
*
*/
function fieldgroup_order_fields_groups(&$group_rows, &$groups, &$field_check_off) {
$max_depth = 0;
foreach ($group_rows as $name) {
$depth = $groups[$name]['depth'];
if ($depth > $max_depth) {
$max_depth = $depth;
}
$parent = $groups[$name]['parent'];
//run through the fields and come up with new weights for display purposes
if (isset($groups[$name]['fields'])) {
foreach ($groups[$name]['fields'] as $name2 => $elements) {
$depth2 = $groups[$name]['depth'] + 1;
$groups[$name]['fields'][$name2]['depth'] = $depth2;
if (in_array($name2, $field_check_off)) {
$index = array_search($name2, $field_check_off);
unset($field_check_off[$index]);
}
}
}
}
return $max_depth;
}
/**
* 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) {
$group_rows = array();
$field_rows = array();
//prepare data that will make this easier
$groups = fieldgroup_groups($form['type']['#value']);
if (!empty($groups)) {
foreach ($groups as $name => $more) {
$group_rows[] = $name;
}
}
$fields = $form['#field_info'];
if (!empty($fields)) {
foreach ($fields as $name => $more) {
$field_rows[] = $name;
}
}
$max_depth = fieldgroup_order_fields_groups($group_rows, $groups, $field_rows);
//cover the top level fields that aren't in fieldgroups
if (isset($field_rows)) {
foreach ($field_rows as $name) {
$form[$name]['#depth'] = 0;
}
}
//now that we have the order of things as we want them, let's create the fieldsets for the fieldgroups
foreach ($groups as $group_name => $group) {
_fieldgroup_add_group_to_form($form, $form_state, $form_id, $group_name, $group, $groups);
}
//reorder the groups from the inside-out in order to avoid a recursive function
while ($max_depth >= 0) {
foreach ($group_rows as $name) {
if ($form[$name]['#depth'] == $max_depth) {
$parent = $form[$name]['#group_parent'];
if (isset($parent) && $parent != '') {
$form[$parent][$name] = $form[$name];
if ($form[$name]['#access']) {
$form[$parent]['#access'] = TRUE;
}
unset($form[$name]);
$index = array_search($name, $group_rows);
unset($group_rows[$index]);
}
}
}
$max_depth--;
}
}
elseif ($form_id == 'content_field_edit_form' && isset($form['widget'])) {
$content_type = content_types($form['type_name']['#value']);
$form['widget']['group'] = array(
'#type' => 'value',
'#value' => _fieldgroup_field_get_group($content_type['type'], $form['field_name']['#value']),
);
}
elseif ($form_id == 'content_field_overview_form') {
$form['#validate'][] = 'fieldgroup_field_overview_form_validate';
$form['#submit'][] = 'fieldgroup_field_overview_form_submit';
}
elseif ($form_id == 'content_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 == 'content_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, $type_name) {
$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.');
}
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($type_name);
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['#type_name']);
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'];
$type_name = $form['#type_name'];
// 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($type_name, $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);
$add_group_rows = array(
$new_group_name,
);
$group_rows = array_merge($form['#groups'], $add_group_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.
// content_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,
'type_name' => $type_name,
));
}
}
foreach ($form_state['values'] as $key => $values) {
// If 'group' row: update groups weights and parent
// (possible newly created group has already been taken care of).
if (in_array($key, $group_rows)) {
$parent = $values['parent'] == '_add_new_group' && isset($new_group_name) ? $new_group_name : $values['parent'];
$weight = $values['weight'];
db_query("UPDATE {" . fieldgroup_tablename() . "} SET weight = %d, parent = '%s' WHERE type_name = '%s' AND group_name = '%s'", $weight, $parent, $type_name, $key);
}
}
cache_clear_all('fieldgroup_data:', content_cache_tablename(), TRUE);
}
function field_group_default_settings($group_type) {
$settings = array(
'form' => array(
'style' => 'fieldset',
'description' => '',
),
'display' => array(
'description' => '',
'label' => 'above',
),
);
module_load_include('inc', 'content', 'includes/content.admin');
foreach (array_keys(content_build_modes()) 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['#type_name']);
foreach ($form_values as $key => $values) {
if (in_array($key, $form['#groups'])) {
$group = $groups[$key];
// Multigroup data (namely, subgroup data) ends up in the settings array as well
// Prevent this data from ending up inside of ['settings']['display']
if (is_array($values['settings']) && array_key_exists('multigroup', $values['settings'])) {
if (is_array($group['settings']['multigroup'])) {
foreach ($values['settings']['multigroup'] as $group_key => $group_values) {
$group['settings']['multigroup'][$group_key] = $values['settings']['multigroup'][$group_key];
}
}
else {
$group['settings']['multigroup'] = $values['settings']['multigroup'];
}
unset($values['settings']['multigroup']);
}
// We have some 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_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 {" . 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 = NULL, $page = NULL) {
switch ($op) {
case 'view':
// Prevent against invalid 'nodes' built by broken 3rd party code.
if (isset($node->type)) {
//prepare data that will make this easier
$group_rows = array();
$field_rows = array();
$groups = fieldgroup_groups($node->type);
if (!empty($groups)) {
foreach ($groups as $name => $more) {
$group_rows[] = $name;
}
}
$fields = $node->content;
if (!empty($fields)) {
foreach ($fields as $name => $more) {
if (is_string($name) && strstr($name, 'field_')) {
$field_rows[] = $name;
}
}
}
$max_depth = fieldgroup_order_fields_groups($group_rows, $groups, $field_rows);
//cover the top level fields that aren't in fieldgroups
if (isset($field_rows)) {
foreach ($field_rows as $name) {
$node->content[$name]['#depth'] = 0;
}
}
// Build the node content element needed to render each fieldgroup.
foreach ($groups as $group) {
fieldgroup_build_content($group, $node, $teaser, $page);
}
//reorder the groups from the inside-out in order to avoid writing a recursive function
while ($max_depth >= 0) {
foreach ($group_rows as $name) {
if (!empty($node->content[$name]) && $node->content[$name]['#depth'] == $max_depth) {
$parent = $node->content[$name]['#group_parent'];
if (isset($parent) && $parent != '') {
$node->content[$parent]['group'][$name] = $node->content[$name];
unset($node->content[$name]);
}
}
}
$max_depth--;
}
}
break;
}
}
/**
* Build the node content element needed to render a fieldgroup.
*
* @param $group
* The field group definition.
* @param $node
* The node containing the field group to display. Can be a 'pseudo-node',
* containing at least 'type', 'nid', 'vid', and the content for fields
* required for the group.
* @param $teaser
* @param $page
* Similar to hook_nodeapi('view').
*
* @see fieldgroup_nodeapi()
* @see fieldgroup_view_group()
*/
function fieldgroup_build_content($group, &$node, $teaser, $page) {
// 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;
}
$group_name = $group['group_name'];
// 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 && !empty($group['settings']['display']['description']) ? content_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;
$element['#node'] = $node;
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];
$element[$field_name]['#weight'] = $field['weight'];
$element[$field_name]['#depth'] = $field['depth'];
}
}
// 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);
}
// Unset the original field values now that we've moved them.
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'],
'#depth' => $group['depth'],
'#post_render' => array(
'fieldgroup_wrapper_post_render',
),
'#group_name' => $group_name,
'#type_name' => $node->type,
'#context' => $context,
'#group_parent' => $group['parent'],
);
$node->content[$group_name] = $wrapper;
}
/**
* Render a single field group, fully themed with label.
*
* To be used by third-party code (Panels, ...) that needs to output an
* isolated field group. Do *not* use inside node templates, use the
* $GROUP_NAME_rendered variables instead. You can also use the 'simple'
* style format and override the template fieldgroup-simple.tpl.php.
*
* By default, the field group is displayed using the settings defined for the
* 'full node' or 'teaser' contexts (depending on the value of the $teaser param).
* Set $node->build_mode to a different value to use a different context.
*
* Different settings can be specified by adjusting $group['settings']['display'].
*
* @param $group
* The field group definition.
* @param $node
* The node containing the field group to display. Can be a 'pseudo-node',
* containing at least 'type', 'nid', 'vid', and the field data required
* for the group.
* @param $teaser
* @param $page
* Similar to hook_nodeapi('view').
* @return
* The themed output for the field group.
*
* @see content_view_field()
*/
function fieldgroup_view_group($group, &$node, $teaser = FALSE, $page = FALSE) {
$group_name = $group['group_name'];
$field_types = _content_field_types();
// Clone the node to prevent from altering the original.
$node_copy = drupal_clone($node);
// Use 'full'/'teaser' if not specified otherwise.
$node_copy->build_mode = isset($node_copy->build_mode) ? $node_copy->build_mode : NODE_BUILD_NORMAL;
// Build the content element for individual fields in the field group.
if (!isset($node_copy->content)) {
$node_copy->content = array();
}
foreach (array_keys($group['fields']) as $field_name) {
$field = content_fields($field_name, $node_copy->type);
if (isset($node_copy->{$field_name})) {
$items = $node_copy->{$field_name};
// One-field equivalent to _content_field_invoke('sanitize').
$module = $field_types[$field['type']]['module'];
$function = $module . '_field';
if (function_exists($function)) {
$function('sanitize', $node_copy, $field, $items, $teaser, $page);
$node_copy->{$field_name} = $items;
}
$field_view = content_field('view', $node_copy, $field, $items, $teaser, $page);
// content_field('view') adds a wrapper to handle variables and 'excluded'
// fields for node templates. We bypass it and get the actual field.
$node_copy->content[$field_name] = $field_view[$field_name];
}
}
// Build the content element of the field group itself.
fieldgroup_build_content($group, $node_copy, $teaser, $page);
// fieldgroup_build_content() adds a wrapper to handle variables and 'excluded'
// groups for node templates. We bypass it and render the actual field group.
$output = drupal_render($node_copy->content[$group_name]['group']);
return $output;
}
/**
* Hide specified fields from the $content variable in node templates.
*/
function fieldgroup_wrapper_post_render($content, $element) {
$groups = fieldgroup_groups($element['#type_name']);
$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('content_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($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(), TRUE);
}
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_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;
}
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);
// 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 {" . fieldgroup_tablename() . "} (parent, group_type, type_name, group_name, label, settings, weight)" . " VALUES ('%s','%s', '%s', '%s', '%s', '%s', %d)", isset($group['parent']) ? $group['parent'] : $group['parent'], $group['group_type'], $type_name, $group['group_name'], $group['label'], serialize($group['settings']), $group['weight']);
cache_clear_all('fieldgroup_data:', content_cache_tablename(), TRUE);
return SAVED_NEW;
}
else {
db_query("UPDATE {" . fieldgroup_tablename() . "} SET parent = '%s', group_type = '%s', label = '%s', settings = '%s', weight = %d " . "WHERE type_name = '%s' AND group_name = '%s'", isset($group['parent']) ? $group['parent'] : $group['parent'], $group['group_type'], $group['label'], serialize($group['settings']), $group['weight'], $type_name, $group['group_name']);
cache_clear_all('fieldgroup_data:', content_cache_tablename(), TRUE);
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(), TRUE);
}
}
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(), TRUE);
}
/**
* 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 '<fieldset' . drupal_attributes($element['#attributes']) . '>' . ($element['#title'] ? '<legend>' . $element['#title'] . '</legend>' : '') . (isset($element['#description']) && $element['#description'] ? '<div class="description">' . $element['#description'] . '</div>' : '') . (!empty($element['#children']) ? $element['#children'] : '') . (isset($element['#value']) ? $element['#value'] : '') . "</fieldset>\n";
}
/**
* Process variables for fieldgroup.tpl.php.
*
* The $variables array contains the following arguments:
* - $group_name
* - $group_name_css
* - $label
* - $description
* - $content
*
* @see fieldgroup-simple.tpl.php
*/
function template_preprocess_fieldgroup_simple(&$vars) {
$element = $vars['element'];
$vars['parent'] = isset($element['#parent']) ? $element['#parent'] : NULL;
$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'] : '';
$vars['template_files'] = array(
'fieldgroup-simple-',
'fieldgroup-simple-' . $element['#group_name'],
'fieldgroup-simple-' . $element['#node']->type,
'fieldgroup-simple-' . $element['#group_name'] . '-' . $element['#node']->type,
);
}
/**
* 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'] : '';
}
}