<?php

/**
 * @file
 * Fieldgroup module.
 *
 * For an overview of all php and javascript hooks, see field_group.api.php.
 *
 */

/**
 * Implements hook_menu().
 */
function field_group_menu() {
  $items = array();

  // Ensure the following is not executed until field_bundles is working and
  // tables are updated. Needed to avoid errors on initial installation.
  if (defined('MAINTENANCE_MODE')) {
    return $items;
  }

  // Create tabs for all possible bundles.
  foreach (entity_get_info() as $entity_type => $entity_info) {
    if ($entity_info['fieldable']) {
      foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {
        if (isset($bundle_info['admin'])) {
          // Extract path information from the bundle.
          $path = $bundle_info['admin']['path'];
          // Different bundles can appear on the same path (e.g. %node_type and
          // %comment_node_type). To allow field_group_menu_load() to extract the
          // actual bundle object from the translated menu router path
          // arguments, we need to identify the argument position of the bundle
          // name string ('bundle argument') and pass that position to the menu
          // loader. The position needs to be casted into a string; otherwise it
          // would be replaced with the bundle name string.
          if (isset($bundle_info['admin']['bundle argument'])) {
            $bundle_arg = $bundle_info['admin']['bundle argument'];
            $bundle_pos = (string) $bundle_arg;
          }
          else {
            $bundle_arg = $bundle_name;
            $bundle_pos = '0';
          }

          // This is the position of the %field_group_menu placeholder in the
          // items below.
          $group_position = count(explode('/', $path)) + 1;

          // Extract access information, providing defaults.
          $access = array_intersect_key($bundle_info['admin'], drupal_map_assoc(array('access callback', 'access arguments')));
          $access += array(
            'access callback' => 'user_access',
            'access arguments' => array('administer site configuration'),
          );

          $items["$path/groups/%field_group_menu/delete"] = array(
            'load arguments' => array($entity_type, $bundle_arg, $bundle_pos, '%map'),
            'title' => 'Delete',
            'page callback' => 'drupal_get_form',
            'page arguments' => array('field_group_delete_form', $group_position),
            'type' => MENU_CALLBACK,
            'file' => 'field_group.field_ui.inc',
          ) + $access;
        }
      }
    }
  }

  return $items;
}

/**
 * Implements hook_permission().
 */
function field_group_permission() {
  return array(
    'administer fieldgroups' => array(
      'title' => t('Administer fieldgroups'),
      'description' => t('Display the administration for fieldgroups.'),
    ),
  );
}

/**
 * Menu load
 * Load a group instance based on group and bundle name.
 *
 * @param $group_name
 *   The name of the group, as contained in the path.
 * @param $entity_type
 *   The name of the entity.
 * @param $bundle_name
 *   The name of the bundle, as contained in the path.
 * @param $bundle_pos
 *   The position of $bundle_name in $map.
 * @param $map
 *   The translated menu router path argument map.
 */
function field_group_menu_load($group_name, $entity_type, $bundle_name, $bundle_pos, $map) {

  if ($bundle_pos > 0) {
    $bundle = $map[$bundle_pos];
    $bundle_name = field_extract_bundle($entity_type, $bundle);
  }

  $args = func_get_args();
  $args_pop = array_pop($args);
  $mode = array_pop($args_pop);

  ctools_include('export');
  $objects = ctools_export_load_object('field_group', 'conditions', array(
    'group_name' => $group_name,
    'entity_type' => $entity_type,
    'bundle' => $bundle_name,
    'mode' => $mode
  ));
  $object = array_shift($objects);
  if ($object && isset($object->data)) {
    return field_group_unpack($object);
  }
  return $object;

}

/**
 * Implements hook_ctools_plugin_api().
 */
function field_group_ctools_plugin_api($owner, $api) {
  if ($owner == 'field_group' && $api == 'field_group') {
    return array('version' => 1);
  }
}

/**
 * Implements hook_theme().
 */
function field_group_theme() {
  return array(
    'horizontal_tabs' => array(
      'render element' => 'element',
    ),
  );
}

/**
 * Implements hook_field_attach_delete_bundle().
 *
 * @param String $entity_type
 * @param String $bundle
 */
function field_group_field_attach_delete_bundle($entity_type, $bundle) {

  ctools_include('export');
  $list = field_group_read_groups(array('bundle' => $bundle, 'entity_type' => $entity_type));

  // Delete the entity's entry from field_group of all entities.
  // We fetch the field groups first to assign the removal task to ctools.
  if (isset($list[$entity_type], $list[$entity_type][$bundle])) {
    foreach ($list[$entity_type][$bundle] as $group_mode => $groups) {
      foreach ($groups as $group) {
        ctools_export_crud_delete('field_group', $group);
      }
    }
  }

}

/**
 * Implements hook_field_attach_form().
 */
function field_group_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) {
  $form['#attached']['css'][] = drupal_get_path('module', 'field_group') . '/field_group.field_ui.css';
  field_group_attach_groups($form, 'form');
}

/**
 * Implements hook_form_FORM_ID_alter().
 * Using hook_form_field_ui_field_overview_form_alter.
 */
function field_group_form_field_ui_field_overview_form_alter(&$form, &$form_state) {
  form_load_include($form_state, 'inc', 'field_group', 'field_group.field_ui');
  field_group_field_ui_overview_form_alter($form, $form_state);
}

/**
 * Implements hook_form_FORM_ID_alter().
 * Using hook_form_field_ui_display_overview_form_alter.
 */
function field_group_form_field_ui_display_overview_form_alter(&$form, &$form_state) {
  form_load_include($form_state, 'inc', 'field_group', 'field_group.field_ui');
  field_group_field_ui_overview_form_alter($form, $form_state, TRUE);
}

/**
 * Implements hook_field_attach_view_alter().
 */
function field_group_field_attach_view_alter(&$element, $context) {
  // Check whether the view mode uses custom display settings or the 'default' mode.
  $actual_mode = 'default';
  if (isset($element['#entity_type']) && isset($element['#bundle'])) {
    $view_mode_settings = field_view_mode_settings($element['#entity_type'], $element['#bundle']);
    $view_mode = $context['view_mode'];
    $actual_mode = (!empty($view_mode_settings[$view_mode]['custom_settings']) ? $view_mode : 'default');
    field_group_attach_groups($element, $actual_mode);
  }
}

/**
 * Implements hook_field_group_formatter_info().
 */
function field_group_field_group_formatter_info() {

  return array(
    'form' => array(
      'fieldset' => array(
        'label' => t('Fieldset'),
        'description' => t('This fieldgroup renders the inner content in a fieldset with the titel as legend.'),
        'format_types' => array('open', 'collapsible', 'collapsed'),
        'instance_settings' => array('classes' => '', 'required_fields' => 1),
        'default_formatter' => 'collapsible',
      ),
      'tabs' => array(
        'label' => t('Vertical tabs group'),
        'description' => t('This fieldgroup renders child groups in its own vertical tabs wrapper.'),
        'instance_settings' => array('classes' => ''),
      ),
      'tab' => array(
        'label' => t('Vertical tab'),
        'description' => t('This fieldgroup renders the content in a fieldset, part of vertical tabs group.'),
        'format_types' => array('open', 'closed'),
        'instance_settings' => array('classes' => '', 'required_fields' => 1),
        'default_formatter' => 'closed',
      ),
      'htabs' => array(
        'label' => ('Horizontal tabs group'),
        'description' => t('This fieldgroup renders child groups in its own horizontal tabs wrapper.'),
        'instance_settings' => array('classes' => ''),
      ),
      'htab' => array(
        'label' => t('Horizontal tab item'),
        'format_types' => array('open', 'closed'),
        'description' => t('This fieldgroup renders the content in a fieldset, part of horizontal tabs group.'),
        'default_formatter' => 'closed',
        'instance_settings' => array('classes' => '', 'required_fields' => 1),
      ),
      'accordion' => array(
        'label' => t('Accordion group'),
        'description' => t('This fieldgroup renders child groups as jQuery accordion.'),
        'instance_settings' => array('classes' => ''),
      ),
      'accordion-item' => array(
        'label' => t('Accordion item'),
        'format_types' => array('open', 'closed'),
        'description' => t('This fieldgroup renders the content in a div, part of accordion group.'),
        'default_formatter' => 'closed',
        'instance_settings' => array('classes' => '', 'required_fields' => 1),
      ),
    ),
    'display' => array(
      'div' => array(
        'label' => t('Div'),
        'description' => t('This fieldgroup renders the inner content in a simple div with the titel as legend.'),
        'format_types' => array('open', 'collapsible', 'collapsed'),
        'instance_settings' => array('effect' => 'none', 'speed' => 'fast', 'classes' => ''),
        'default_formatter' => 'collapsible',
      ),
      'fieldset' => array(
        'label' => t('Fieldset'),
        'description' => t('This fieldgroup renders the inner content in a fieldset with the titel as legend.'),
        'format_types' => array('open', 'collapsible', 'collapsed'),
        'instance_settings' => array('classes' => ''),
        'default_formatter' => 'collapsible',
      ),
      'tabs' => array(
        'label' => t('Vertical tabs group'),
        'description' => t('This fieldgroup renders child groups in its own vertical tabs wrapper.'),
        'instance_settings' => array('classes' => ''),
      ),
      'tab' => array(
        'label' => t('Vertical tab'),
        'description' => t('This fieldgroup renders the content in a fieldset, part of vertical tabs group.'),
        'format_types' => array('open', 'closed'),
        'instance_settings' => array('classes' => ''),
        'default_formatter' => 'closed',
      ),
      'htabs' => array(
        'label' => ('Horizontal tabs group'),
        'description' => t('This fieldgroup renders child groups in its own horizontal tabs wrapper.'),
        'instance_settings' => array('classes' => ''),
      ),
      'htab' => array(
        'label' => t('Horizontal tab item'),
        'format_types' => array('open', 'closed'),
        'description' => t('This fieldgroup renders the content in a fieldset, part of horizontal tabs group.'),
        'instance_settings' => array('classes' => ''),
        'default_formatter' => 'closed',
      ),
      'accordion' => array(
        'label' => t('Accordion group'),
        'description' => t('This fieldgroup renders child groups as jQuery accordion.'),
        'instance_settings' => array('classes' => ''),
      ),
      'accordion-item' => array(
        'label' => t('Accordion item'),
        'format_types' => array('open', 'closed'),
        'description' => t('This fieldgroup renders the content in a div, part of accordion group.'),
        'instance_settings' => array('classes' => ''),
        'default_formatter' => 'closed',
      ),
    ),
  );
}

/**
 * Implements hook_field_group_format_settings().
 * If the group has no format settings, default ones will be added.
 * @params Object $group The group object.
 * @return Array $form The form element for the format settings.
 */
function field_group_field_group_format_settings($group) {
  // Add a wrapper for extra settings to use by others.
  $form = array(
    'instance_settings' => array(
      '#tree' => TRUE,
      '#weight' => 2,
    ),
  );

  $field_group_types = field_group_formatter_info();
  $mode = $group->mode == 'form' ? 'form' : 'display';
  $formatter = $field_group_types[$mode][$group->format_type];

  // Add the required formatter type selector.
  if (isset($formatter['format_types'])) {
    $form['formatter'] = array(
      '#title' => t('Fieldgroup settings'),
      '#type' => 'select',
      '#options' => drupal_map_assoc($formatter['format_types']),
      '#default_value' => isset($group->format_settings['formatter']) ? $group->format_settings['formatter'] : $formatter['default_formatter'],
      '#weight' => 1,
    );
  }
  if ($mode == 'form') {
    $form['instance_settings']['required_fields'] = array(
      '#type' => 'checkbox',
      '#title' => t('Mark group for required fields.'),
      '#default_value' => isset($group->format_settings['instance_settings']['required_fields']) ? $group->format_settings['instance_settings']['required_fields'] : (isset($formatter['instance_settings']['required_fields']) ? $formatter['instance_settings']['required_fields'] : ''),
      '#weight' => 2,
    );
  }
  $form['instance_settings']['classes'] = array(
    '#title' => t('Extra CSS classes'),
    '#type' => 'textfield',
    '#default_value' => isset($group->format_settings['instance_settings']['classes']) ? $group->format_settings['instance_settings']['classes'] : (isset($formatter['instance_settings']['classes']) ? $formatter['instance_settings']['classes'] : ''),
    '#weight' => 3,
    '#element_validate' => array('field_group_validate_css_class'),
  );
  $form['instance_settings']['description'] = array(
    '#title' => t('Description'),
    '#type' => 'textarea',
    '#default_value' => isset($group->format_settings['instance_settings']['description']) ? $group->format_settings['instance_settings']['description'] : (isset($formatter['instance_settings']['description']) ? $formatter['instance_settings']['description'] : ''),
    '#weight' => 0,
  );

  // Add optional instance_settings.
  switch ($group->format_type) {
    case 'div':
      $form['instance_settings']['effect'] = array(
        '#title' => t('Effect'),
        '#type' => 'select',
        '#options' => array('none' => t('None'), 'blind' => t('Blind')),
        '#default_value' => isset($group->format_settings['instance_settings']['effect']) ? $group->format_settings['instance_settings']['effect'] : $formatter['instance_settings']['effect'],
        '#weight' => 2,
      );
      $form['instance_settings']['speed'] = array(
        '#title' => t('Speed'),
        '#type' => 'select',
        '#options' => array('none' => t('None'), 'slow' => t('Slow'), 'fast' => t('Fast')),
        '#default_value' => isset($group->format_settings['instance_settings']['speed']) ? $group->format_settings['instance_settings']['speed'] : $formatter['instance_settings']['speed'],
        '#weight' => 3,
      );
      break;
    case 'fieldset':
      $form['instance_settings']['classes'] = array(
        '#title' => t('Extra CSS classes'),
        '#type' => 'textfield',
        '#default_value' => isset($group->format_settings['instance_settings']['classes']) ? $group->format_settings['instance_settings']['classes'] : $formatter['instance_settings']['classes'],
        '#weight' => 3,
        '#element_validate' => array('field_group_validate_css_class'),
      );
      break;
    case 'tabs':
    case 'htabs':
    case 'accordion':
      unset($form['instance_settings']['description']);
      if (isset($form['instance_settings']['required_fields'])) {
        unset($form['instance_settings']['required_fields']);
      }
      break;
    case 'tab':
    case 'htab':
    case 'accordion-item':
    default:
  }

  return $form;
}

/**
 * Implements hook_field_group_pre_render().
 *
 * @param Array $elements by address.
 * @param Object $group The Field group info.
 */
function field_group_field_group_pre_render(& $element, $group, & $form) {

  $view_mode = isset($form['#view_mode']) ? $form['#view_mode'] : 'form';
  $id = $form['#entity_type'] . '_' . $form['#bundle'] . '_' . $view_mode . '_' . $group->group_name;

  // Prepare extra classes.
  $classes = array($group->format_type, str_replace('_', '-', $group->group_name));
  if (isset($group->format_settings['formatter'])) {
    $collapsible = in_array($group->format_settings['formatter'], array('collapsible', 'collapsed'));
    // Open or closed horizontal or vertical tabs will be collapsible by default.
    if ($group->format_type == 'tab' || $group->format_type == 'htab') {
      $collapsible = TRUE;
    }
    $collapsed = in_array($group->format_settings['formatter'], array('collapsed', 'closed'));
    if ($collapsible) {
      $classes[] = 'collapsible';
      if ($collapsed) {
        $classes[] = 'collapsed';
      }
    }
  }
  else {
    $collapsible = TRUE;
    $collapsed = FALSE;
    $classes[] = 'collapsible';
  }

  // Add a required-fields class to trigger the js.
  if (isset($group->format_settings['instance_settings']) && !empty($group->format_settings['instance_settings']['required_fields'])) {
    $classes[] = 'required-fields';
  }

  $classes = implode(' ', $classes);
  if (isset($group->format_settings['instance_settings'], $group->format_settings['instance_settings']['classes'])) {
    $classes .= ' ' . check_plain($group->format_settings['instance_settings']['classes']);
  }

  $description = isset($group->format_settings['instance_settings']['description']) ? filter_xss_admin($group->format_settings['instance_settings']['description']) : '';

  // Add all field_group format types to the js settings.
  $form['#attached']['js'][] = array(
    'data' => array('field_group' => array($group->format_type => $view_mode)),
    'type' => 'setting',
  );

  switch ($group->format_type) {
    // Fieldset.
    case 'fieldset':
      $element += array(
        '#id' => $id,
        '#type' => 'fieldset',
        '#title' => check_plain(t($group->label)),
        '#weight' => $group->weight,
        '#collapsible' => $collapsible,
        '#collapsed' => $collapsed,
        '#pre_render' => array(),
        '#attributes' => array('class' => explode(' ', $classes)),
        '#description' => $description,
      );
      $element['#attached']['js'][] = 'misc/form.js';
      $element['#attached']['js'][] = 'misc/collapse.js';
      break;
    // Normal or collapsible div.
    case 'div':
      $effect = isset($group->format_settings['instance_settings']['effect']) ? $group->format_settings['instance_settings']['effect'] : 'none';
      $speed = isset($group->format_settings['instance_settings']['speed']) ? $group->format_settings['instance_settings']['speed'] : 'none';
      $add = array(
        '#type' => 'markup',
        '#weight' => $group->weight,
        '#id' => $id,
      );
      $classes .= " speed-$speed effect-$effect";
      if ($group->format_settings['formatter'] != 'open') {
        $add['#prefix'] = '<div class="field-group-format ' . $classes . '">
          <span class="field-group-format-toggler">' . check_plain(t($group->label)) . '</span>
          <div class="field-group-format-wrapper" style="display: ' . ($collapsed ? 'none' : 'block') . ';">';
        $add['#suffix'] = '</div></div>';
      }
      else {
        $add['#prefix'] = '<div class="field-group-format ' . $group->group_name . ' ' . $classes . '">';
        $add['#suffix'] = '</div>';
      }
      if (!empty($description)) {
        $add['#prefix'] .= '<div class="description">' . $description . '</div>';
      }
      $element += $add;

      if ($effect == 'blind') {
        drupal_add_library('system', 'effects.blind');
      }

      break;
    // Accordion group.
    case 'accordion':
      $element += array(
        '#id' => $id,
        '#type' => 'markup',
        '#prefix' => '<div class="field-group-' . $group->format_type . '-wrapper ' . $classes . '">',
        '#suffix' => '</div>',
        '#weight' => $group->weight,
      );
      break;
    // Accordion item.
    case 'accordion-item':
      $element += array(
        '#id' => $id,
        '#type' => 'markup',
        '#prefix' => '
        <h3 class="field-group-format-toggler ' . $group->format_type . ($collapsed ? '' : ' field-group-accordion-active') . '">' . check_plain(t($group->label)) . '</h3>
        <div class="field-group-format-wrapper ' . $classes . '">',
        '#suffix' => '</div>',
        '#weight' => $group->weight,
        //'#attributes' => array('class' => array($group->format_type)),
      );
      if (!empty($description)) {
        $element['#prefix'] .= '<div class="description">' . $description . '</div>';
      }
      drupal_add_library('system', 'ui.accordion');
      break;
    // Horizontal tabs group.
    case 'htabs':
      $element += array(
        '#id' => $id,
        '#type' => 'horizontal_tabs',
        '#weight' => $group->weight,
        '#theme_wrappers' => array('horizontal_tabs'),
        '#prefix' => '<div class="field-group-' . $group->format_type . '-wrapper ' . $classes . '">',
        '#suffix' => '</div>',
      );
      $form['#attached']['js'][] = 'misc/form.js';
      $form['#attached']['js'][] = 'misc/collapse.js';
      break;
    // Horizontal tab.
    case 'htab':
      $element += array(
        '#id' => $id,
        '#type' => 'fieldset',
        '#title' => check_plain(t($group->label)),
        '#weight' => $group->weight,
        '#collapsible' => $collapsible,
        '#collapsed' => $collapsed,
        '#attributes' => array('class' => explode(" ", $classes)),
        '#group' => $group->parent_name,
        // very important. Cannot be added on the form!
        '#parents' => array($group->parent_name),
        '#description' => $description,
      );
      break;
    // Vertical tabs group.
    // Vertical tabs for forms need a little tricky approach, to be
    // able to optionally merge field(groups) with the "additional_settings"
    case 'tabs':
      $element += array(
        '#id' => $id,
        '#type' => 'vertical_tabs',
        '#weight' => $group->weight,
        '#theme_wrappers' => array('vertical_tabs'),
        '#prefix' => '<div class="field-group-' . $group->format_type . '-wrapper ' . $classes . '">',
        '#suffix' => '</div>',
      );
      $element[$group->group_name . '__active_tab'] = array(
        '#type' => 'hidden',
        '#default_value' => '',
        '#attributes' => array('class' => array('vertical-tabs-active-tab')),
      );
      $form['#attached']['library'][] = array('system', 'drupal.form');
      $form['#attached']['library'][] = array('system', 'drupal.collapse');
      break;
    // Vertical tab.
    case 'tab':
      // Could be it never runs through htab.
      $form['#attached']['js'][] = array(
        'data' => array('field_group' => array('tabs' => $view_mode)),
        'type' => 'setting',
      );
      // Front-end and back-end on configuration will lead
      // to vertical tabs nested in a separate vertical group.
      if ($view_mode != 'form') {
        $parent_name = empty($group->parent_name) ? 'additional_settings' : $group->parent_name;
        $element += array(
          '#id' => $id,
          '#type' => 'fieldset',
          '#id' => 'edit-' . $group->group_name,
          '#title' => check_plain(t($group->label)),
          '#weight' => $group->weight,
          '#collapsible' => $collapsible,
          '#collapsed' => $collapsed,
          '#attributes' => array('class' => explode(" ", $classes)),
          '#group' => $parent_name,
          '#parents' => array($parent_name),
          '#description' => $description,
        );
      }
      // Form fieldgroups which are nested into a vertical tab group
      // are handled a little different.
      elseif (!empty($group->parent_name)) {
        $parent_name = $group->parent_name;
        $element += array(
          '#id' => $id,
          '#type' => 'fieldset',
          '#id' => 'edit-' . $group->group_name,
          '#title' => check_plain(t($group->label)),
          '#weight' => $group->weight,
          '#collapsible' => $collapsible,
          '#collapsed' => $collapsed,
          '#attributes' => array('class' => explode(" ", $classes)),
          '#group' => $parent_name,
          '#description' => $description,
        );
      }
      // For the form, we will keep it in the core additional_settings,
      // or in a custom created parallel one.
      else {
        $group->parent_name = 'additional_settings';
        $parent_name = 'additional_settings';
        // Create the fieldgroup element.
        $child_element = array(
          '#id' => $id,
          '#type' => 'fieldset',
          '#id' => 'edit-' . $group->group_name,
          '#title' => check_plain(t($group->label)),
          '#weight' => -30 + $group->weight, // hardcoded to bring our extra additional vtabs on top.
          '#collapsible' => $collapsible,
          '#collapsed' => $collapsed,
          '#attributes' => array('class' => explode(" ", $classes)),
          '#group' => 'additional_settings',
          '#parents' => array($group->parent_name),
          '#description' => $description,
        );

        // Check if the additional_settings exist for this type of form,
        // if not, add the vertical_tabs wrapper.
        if (!isset($form['additional_settings']['group']['#groups']['additional_settings'])) {
          if (!isset($form['additional_settings']['#type'])) {
            $form['additional_settings'] = array(
              '#type' => 'vertical_tabs',
              '#weight' => $group->weight,
              '#theme_wrappers' => array('vertical_tabs'),
              '#prefix' => '<div class="field-group-' . $group->format_type . '-wrapper">',
              '#suffix' => '</div>',
            );
            $form['#attached']['js'][] = 'misc/form.js';
            $form['#attached']['js'][] = 'misc/collapse.js';
          }
          $form['additional_settings'][$group->group_name] = $child_element;
          // Nest the fields inside the appropriate structure.
          foreach (element_children($element) as $fieldname) {
            $form['additional_settings'][$group->group_name][$fieldname] = $element[$fieldname];
            unset($element[$fieldname]);
          }
        }
        // Merge fieldgroups with the core additional settings.
        else {
          $form['additional_settings']['group']['#groups']['additional_settings'][$group->group_name] = $child_element;
          $form['additional_settings']['group']['#groups'][$group->group_name] = array('#group_exists' => TRUE);
          // Nest the fields inside the appropriate structure.
          foreach (element_children($element) as $fieldname) {
            $form['additional_settings']['group']['#groups']['additional_settings'][$group->group_name][$fieldname] = $element[$fieldname];
            unset($element[$fieldname]);
          }
        }

      }

    break;
  }
}

/**
 * Implements hook_field_group_build_pre_render_alter().
 * @param Array $elements by address.
 */
function field_group_field_group_build_pre_render_alter(& $element) {

  $display = isset($element['#view_mode']);
  $groups = array_keys($element['#groups']);

  // Dish the fieldgroups with no fields for non-forms.
  if ($display) {
    foreach (element_children($element) as $name) {
      if (in_array($name, $groups)) {
        if (field_group_field_group_is_empty($element[$name], $groups)) {
          unset($element[$name]);
        }
      }
    }
  }
  else {
    // Fix the problem on forms with additional settings.
    // @TODO check latest status: No problems to be solved here?
  }

  // Add the default field_group javascript and stylesheet.
  $element['#attached']['js'][] = drupal_get_path('module', 'field_group') . '/field_group.js';
  $element['#attached']['css'][] = drupal_get_path('module', 'field_group') . '/field_group.css';

}

/**
 * Helper function to recursively tell if the group contains
 * an empty element or maybe one of its children.
 *
 * @param array $element
 *   array structure of the element.
 * @param array $groups
 *   Array of group names.
 * @return Boolean Is field group empty
 */
function field_group_field_group_is_empty($element, $groups) {

  $empty = TRUE;

  // Loop throug the children from current element.
  foreach (element_children($element) as $name) {

    // Fields are found in the element.
    if (!$empty) {
      return FALSE;
    }

    // Descend into groups if the child is a group.
    if (in_array($name, $groups)) {
      $empty = field_group_field_group_is_empty($element[$name], $groups);
    }
    // Child is a field, the asked element is not empty.
    else {
      return FALSE;
    }

  }

  return $empty;

}

/**
 * Implements hook_field_group_format_summary().
 */
function field_group_field_group_format_summary($group) {

  $output = '';
  if (isset($group->format_settings['formatter'])) {
    $output .= '<strong>' . $group->format_type . '</strong> ' . $group->format_settings['formatter'] . '';
  }
  if (isset($group->format_settings['instance_settings'])) {
    $last = end($group->format_settings['instance_settings']);
    $output .= '<br />';
    foreach ($group->format_settings['instance_settings'] as $key => $value) {
      if (empty($value)) {
        continue;
      }
      $output .= '<strong>' . $key . '</strong> ';
      if (drupal_strlen($value) > 38) {
        $value = truncate_utf8($value, 50, TRUE, TRUE);
      }
      elseif (is_numeric($value)) {
        $value = $value == '1' ? t('yes') : t('no');
      }
      $output .= check_plain($value);
      $output .= $last == $value ? ' ' : '<br />';
    }
  }
  return $output;
}

/**
 * Implements hook_element_info().
 */
function field_group_element_info() {
  $types['horizontal_tabs'] = array(
    '#theme_wrappers' => array('horizontal_tabs'),
    '#default_tab' => '',
    '#process' => array('form_process_horizontal_tabs'),
  );
  return $types;
}

/**
 * Implements hook_library().
 */
function field_group_library() {

  $path = drupal_get_path('module', 'field_group');
  // Horizontal Tabs.
  $libraries['horizontal-tabs'] = array(
    'title' => 'Horizontal Tabs',
    'website' => 'http://drupal.org/node/323112',
    'version' => '1.0',
    'js' => array(
      $path . '/horizontal-tabs/horizontal-tabs.js' => array(),
    ),
    'css' => array(
      $path . '/horizontal-tabs/horizontal-tabs.css' => array(),
    ),
  );

  return $libraries;
}

/**
 * Implements hook_field_extra_fields().
 */
function field_group_field_extra_fields() {
  $extra = array();

  $extra['user']['user'] = array(
    'form' => array(
      'contact' => array(
        'label' => t('Contact'),
        'description' => t('Contact user element'),
        'weight' => -5,
      ),
      'picture' => array(
        'label' => t('Picture'),
        'description' => t('User picture'),
        'weight' => -5,
      ),
    ),
  );

  return $extra;
}

/**
 * Implements hook_field_attach_rename_bundle().
 */
function field_group_field_attach_rename_bundle($entity_type, $bundle_old, $bundle_new) {
  db_query('UPDATE {field_group} SET bundle = :bundle WHERE bundle = :old_bundle AND entity_type = :entity_type', array(
    ':bundle' => $bundle_new,
    ':old_bundle' => $bundle_old,
    ':entity_type' => $entity_type,
  ));
}

/**
 * Creates a group formatted as horizontal tabs.
 *
 * @param $element
 *   An associative array containing the properties and children of the
 *   fieldset.
 * @param $form_state
 *   The $form_state array for the form this horizontal tab widget belongs to.
 * @return
 *   The processed element.
 */
function form_process_horizontal_tabs($element, &$form_state) {
  // Inject a new fieldset as child, so that form_process_fieldset() processes
  // this fieldset like any other fieldset.
  $element['group'] = array(
    '#type' => 'fieldset',
    '#theme_wrappers' => array(),
    '#parents' => $element['#parents'],
  );

  // The JavaScript stores the currently selected tab in this hidden
  // field so that the active tab can be restored the next time the
  // form is rendered, e.g. on preview pages or when form validation
  // fails.
  $name = implode('__', $element['#parents']);
  if (isset($form_state['values'][$name . '__active_tab'])) {
    $element['#default_tab'] = $form_state['values'][$name . '__active_tab'];
  }
  $element[$name . '__active_tab'] = array(
    '#type' => 'hidden',
    '#default_value' => $element['#default_tab'],
    '#attributes' => array('class' => array('horizontal-tabs-active-tab')),
  );

  return $element;
}

/**
 * Returns HTML for an element's children fieldsets as horizontal tabs.
 *
 * @param $variables
 *   An associative array containing:
 *   - element: An associative array containing the properties and children of the
 *     fieldset. Properties used: #children.
 *
 * @ingroup themeable
 */
function theme_horizontal_tabs($variables) {
  $element = $variables['element'];
  // Add required JavaScript and Stylesheet.
  drupal_add_library('field_group', 'horizontal-tabs');

  $output = '<h2 class="element-invisible">' . t('Horizontal Tabs') . '</h2>';
  $output .= '<div class="horizontal-tabs-panes">' . $element['#children'] . '</div>';

  return $output;
}

/**
 * Get all groups.
 *
 * @param $entity_type
 *   The name of the entity.
 * @param $bundle
 *   The name of the bundle.
 * @param $view_mode
 *   The view mode.
 * @param $reset.
 *   Whether to reset the cache or not.
 */
function field_group_info_groups($entity_type = NULL, $bundle = NULL, $view_mode = NULL, $reset = FALSE) {
  static $groups = FALSE;

  if (!$groups || $reset) {
    if (!$reset && $cached = cache_get('field_groups', 'cache_field')) {
      $groups = $cached->data;
    }
    else {
      $groups = field_group_read_groups();
      cache_set('field_groups', $groups, 'cache_field');
    }
  }

  if (!isset($entity_type)) {
    return $groups;
  }
  else {
    if (isset($groups[$entity_type][$bundle][$view_mode])) {
      return $groups[$entity_type][$bundle][$view_mode];
    }
    else {
      return array();
    }
  }
}

/**
 * Read all groups.
 */
function field_group_read_groups($params = array()) {

  $groups = array();
  ctools_include('export');
  $records = ctools_export_load_object('field_group', 'conditions', $params);
  foreach ($records as $group) {
    $groups[$group->entity_type][$group->bundle][$group->mode][$group->group_name] = field_group_unpack($group);
  }
  return $groups;

}

/**
 * Utility function to recreate identifiers.
 */
function _field_group_recreate_identifiers() {

  // Migrate the field groups so they have a unique identifier.
  $result = db_select('field_group', 'fg')
    ->fields('fg')
    ->execute();
  $rows = array();
  foreach($result as $row) {
    $row->identifier = $row->group_name . '|' . $row->entity_type . '|' . $row->bundle . '|' . $row->mode;
    $row->data = unserialize($row->data);
    $rows[] = $row;
  }
  foreach ($rows as $row) {
    drupal_write_record('field_group', $row, array('id'));
  }

}

/**
 * Checks if a field_group exists in required context.
 *
 * @param String $group_name
 *   The name of the group.
 * @param String $entity_type
 *   The name of the entity.
 * @param String $bundle
 *   The bundle for the entity.
 * @param String $mode
 *   The view mode context the group will be rendered.
 */
function field_group_exists($group_name, $entity_type, $bundle, $mode) {
  $groups = field_group_read_groups();
  return !empty($groups[$entity_type][$bundle][$mode][$group_name]);
}

/**
 * Unpacks a database row in a FieldGroup object.
 * @param $group
 *   Database result object with stored group data.
 * @return $group
 *   Field group object.
 */
function field_group_unpack($group) {

  // Extract unserialized data.
  if (isset($group->data)) {
    $data = $group->data;
    unset($group->data);
    $group->label = $data['label'];
    $group->weight = $data['weight'];
    $group->children = $data['children'];
    $group->format_type = !empty($data['format_type']) ? $data['format_type'] : 'fieldset';
    if (isset($data['format_settings'])) {
      $group->format_settings = $data['format_settings'];
    }
  }

  return $group;

}

/**
 * Packs a FieldGroup object into a database row.
 * @param $group
 *   FieldGroup object.
 * @return $record
 *   Database row object, ready to be inserted/update
 */
function field_group_pack($group) {

  $record = clone $group;
  $record->data = array(
    'label' => $record->label,
    'weight' => $record->weight,
    'children' => $record->children,
    'format_type' => !empty($record->format_type) ? $record->format_type : 'fieldset',
  );
  if (isset($record->format_settings)) {
    $record->data['format_settings'] = $record->format_settings;
  }
  return $record;
}

/**
 * Delete a field group.
 * This function is called by ctools export when calls are
 * made through ctools_export_crud_delete().
 *
 * @param $group
 *   A group definition.
 */
function field_group_export_delete($group) {

  $query = db_delete('field_group');

  // If we were sent an object, get the export key from it. Otherwise
  // assume we were sent the export key.
  if (is_object($group)) {
    $query->condition('id', $group->id);
    if (!empty($group->mode)) {
      $query->condition('mode', $group->mode);
    }
  }
  else {
    $query->condition('id', $group);
  }

  $query->execute();

  cache_clear_all('field_groups', 'cache_field');
  module_invoke_all('field_group_delete_field_group', $group);

}

/**
 * field_group_save().
 *
 * Saves a group definition.
 * This function is called by ctools export when calls are made
 * through ctools_export_crud_save().
 *
 * @param $group
 *   A group definition.
 */
function field_group_save(& $group) {

  // Prepare the record.
  $object = field_group_pack($group);

  if (isset($object->export_type) && $object->export_type & EXPORT_IN_DATABASE) {
    // Existing record.
    $update = array('id');
    module_invoke_all('field_group_update_field_group', $object);
  }
  else {
    // New record.
    $update = array();
    $object->export_type = EXPORT_IN_DATABASE;
    module_invoke_all('field_group_create_field_group', $object);
  }
  return drupal_write_record('field_group', $object, $update);

}

/**
 * Function to retrieve all format possibilities for the fieldgroups.
 */
function field_group_formatter_info($display_overview = FALSE) {
  $cache = &drupal_static(__FUNCTION__, array());
  if (empty($cache)) {
    if ($cached = cache_get('field_group_formatter_info', 'cache_field')) {
      $formatters = $cached->data;
    }
    else {
      $formatters = array();
      $formatters += module_invoke_all('field_group_formatter_info');
      cache_set('field_group_formatter_info', $formatters, 'cache_field');
    }
    $cache = $formatters;
  }
  return $cache;
}

/**
 * Attach groups to the (form) build.
 * @param Array $element The part of the form.
 * @param String $view_mode The mode for the build.
 */
function field_group_attach_groups(&$element, $view_mode) {

  $entity_type = $element['#entity_type'];
  $bundle = $element['#bundle'];

  $element['#groups'] = field_group_info_groups($entity_type, $bundle, $view_mode);
  $element['#fieldgroups'] = $element['#groups'];

  // Create a lookup array.
  $group_children = array();
  foreach ($element['#groups'] as $group_name => $group) {
    foreach ($group->children as $child) {
      $group_children[$child] = $group_name;
    }
  }
  $element['#group_children'] = $group_children;

  // Add a pre render callback.
  // This is needed since process seems too early for the front view modes.
  $element['#pre_render'][] = 'field_group_build_pre_render';
}

/**
 * Process/ Pre-render callback.
 *
 * Depending on whether it is a form build or content build.
 * Form api go through more than a regular build. #process is
 * needed here, where #pre_render is ideal for the regular array.
 * @see field_group_attach_groups()
 * @see field_group_fields_nest()
 * @param $element Form element
 * @return $element Array with re-arranged fields in forms.
 */
function field_group_build_pre_render($element) {

  // Merge our #fieldgroups with #groups to avoid conflicts on fieldset types.
  $element['#groups'] = array_merge($element['#groups'], $element['#fieldgroups']);

  // Nest the fields in the corresponding field groups.
  field_group_fields_nest($element);

  // Allow others to alter the pre_rendered build.
  drupal_alter('field_group_build_pre_render', $element);

  return $element;

}

/**
 * Recursive function to nest fields in the field groups.
 *
 * This function will take out all the elements in the form and
 * place them in the correct container element, a fieldgroup.
 * The current group element in the loop is passed recursively so we can
 * stash fields and groups in it while we go deeper in the array.
 * @param Array $element
 *   The current element to analyse for grouping.
 */
function field_group_fields_nest(&$element) {

  // Create all groups and keep a flat list of references to these groups.
  $group_references = array();
  foreach ($element['#groups'] as $group_name => $group) {
    $element[$group_name] = array();
    $group_references[$group_name] = &$element[$group_name];
  }

  // Move all children to their parents. Use the flat list of references for
  // direct access as we don't know where in the root_element hierarchy the
  // parent currently is situated.
  foreach ($element['#group_children'] as $child_name => $parent_name) {
    // Block denied fields (#access) before they are put in groups,
    // or fields that were not processed (like field_permissions).
    if ((!isset($element[$child_name]['#access']) || $element[$child_name]['#access']) && (!isset($element[$child_name]['#processed']) || $element[$child_name]['#processed'] == TRUE)) {
      // If this is a group, we have to use a reference to keep the reference
      // list intact (but if it is a field we don't mind).
      $group_references[$parent_name][$child_name] = &$element[$child_name];
    }
    // The child has been copied to its parent: remove it from the root element.
    unset($element[$child_name]);
  }

  // Bring extra element wrappers to achieve a grouping of fields.
  // This will mainly be prefix and suffix altering.
  foreach ($element['#groups'] as $group_name => $group) {
    field_group_pre_render($group_references[$group_name], $group, $element);
  }

}

/**
 * Function to pre render the field group element.
 *
 * @see field_group_fields_nest()
 *
 * @param $element Array of group element that needs to be created!
 * @param $group Object with the group information.
 * @param $form The form object itself.
 */
function field_group_pre_render(& $element, $group, & $form) {
  // Only run the pre_render function if the group has elements.
  // $group->group_name
  if ($element == array()) {
    return;
  }
  // Let modules define their wrapping element.
  // Note that the group element has no properties, only elements.
  foreach (module_implements('field_group_pre_render') as $module) {
    $function = $module . '_field_group_pre_render';
    if (function_exists($function)) {
      // The intention here is to have the opportunity to alter the
      // elements, as defined in hook_field_group_formatter_info.
      // Note, implement $element by reference!
      $function($element, $group, $form);
    }
  }
}
