getDefinitions(); $entity_types = array(); foreach ($entity_info as $entity_type_id => $entity_type) { if ($route_name = $entity_type->get('field_ui_base_route')) { $entity_types[] = $entity_type_id; } } foreach ($theme_registry as $theme_hook => $info) { if (in_array($theme_hook, $entity_types) || (!empty($info['base hook']) && in_array($info['base hook'], $entity_types))) { $theme_registry[$theme_hook]['preprocess functions'][] = 'field_group_build_entity_groups'; } } // ECK does not use the eck as theme function. if (isset($theme_registry['eck_entity'])) { $theme_registry['eck_entity']['preprocess functions'][] = 'field_group_build_entity_groups'; } } /** * Implements hook_theme(). */ function field_group_theme() { return array( 'horizontal_tabs' => array( 'render element' => 'element', 'template' => 'horizontal-tabs', 'file' => 'templates/theme.inc', ), 'field_group_accordion_item' => array( 'render element' => 'element', 'template' => 'field-group-accordion-item', 'file' => 'templates/theme.inc', ), 'field_group_accordion' => array( 'render element' => 'element', 'template' => 'field-group-accordion', 'file' => 'templates/theme.inc', ), 'field_group_html_element' => array( 'render element' => 'element', 'template' => 'field-group-html-element', 'file' => 'templates/theme.inc', ), ); } /** * Implements hook_theme_suggestions_alter(). * * @param array $suggestions * @param array $variables * @param $hook */ function field_group_theme_suggestions_alter(array &$suggestions, array $variables, $hook) { switch ($hook) { case 'horizontal_tabs': case 'field_group_accordion_item': case 'field_group_accordion': case 'field_group_html_element': $element = $variables['element']; $name = $element['#group_name']; $entity_type = $element['#entity_type']; $bundle = $element['#bundle']; $wrapper = ''; if (isset($element['#wrapper_element'])) { $wrapper = $element['#wrapper_element']; $suggestions[] = $hook . '__' . $wrapper; } $suggestions[] = $hook . '__' . $entity_type; $suggestions[] = $hook . '__' . $bundle; $suggestions[] = $hook . '__' . $name; if ($wrapper) { $suggestions[] = $hook . '__' . $entity_type . '__' . $wrapper; } $suggestions[] = $hook . '__' . $entity_type . '__' . $bundle; $suggestions[] = $hook . '__' . $entity_type . '__' . $name; if ($wrapper) { $suggestions[] = $hook . '__' . $entity_type . '__' . $bundle . '__' . $wrapper; } $suggestions[] = $hook . '__' . $entity_type . '__' . $bundle . '__' . $name; break; } } /** * Implements hook_form_FORM_ID_alter(). * Using hook_form_field_ui_form_display_overview_form_alter. */ function field_group_form_entity_form_display_edit_form_alter(&$form, FormStateInterface $form_state) { $form_state->loadInclude('field_group', 'inc', 'includes/field_ui'); field_group_field_ui_display_form_alter($form, $form_state); } /** * Implements hook_form_FORM_ID_alter(). * Using hook_form_field_ui_display_overview_form_alter. */ function field_group_form_entity_view_display_edit_form_alter(&$form, FormStateInterface $form_state) { $form_state->loadInclude('field_group', 'inc', 'includes/field_ui'); field_group_field_ui_display_form_alter($form, $form_state); } /** * Implements hook_field_info_max_weight(). */ function field_group_field_info_max_weight($entity_type, $bundle, $context, $context_mode) { $groups = field_group_info_groups($entity_type, $bundle, $context, $context_mode); $weights = array(); foreach ($groups as $group) { $weights[] = $group->weight; } return $weights ? max($weights) : NULL; } /** * Implements hook_form_alter(). */ function field_group_form_alter(array &$form, FormStateInterface $form_state) { if ($form_state->getFormObject() instanceof ContentEntityFormInterface && !$form_state->getFormObject() instanceof ConfirmFormInterface) { /** * @var EntityFormDisplayInterface $form_display */ if ($form_display = $form_state->getStorage()['form_display']) { $entity = $form_state->getFormObject()->getEntity(); $context = array( 'entity_type' => $entity->getEntityTypeId(), 'bundle' => $entity->bundle(), 'entity' => $entity, 'context' => 'form', 'display_context' => 'form', 'mode' => $form_display->getMode(), ); field_group_attach_groups($form, $context); $form['#pre_render'][] = 'field_group_form_pre_render'; } } } /** * Implements hook_inline_entity_form_entity_form_alter(). */ function field_group_inline_entity_form_entity_form_alter(&$entity_form, FormStateInterface $form_state) { // Attach the fieldgroups to current entity form. $context = [ 'entity_type' => $entity_form['#entity']->getEntityTypeId(), 'bundle' => $entity_form['#entity']->bundle(), 'entity' => $entity_form['#entity'], 'display_context' => 'form', 'mode' => 'default', ]; field_group_attach_groups($entity_form, $context); $entity_form['#pre_render'][] = 'field_group_form_pre_render'; } /** * Implements hook_entity_view_alter(). */ function field_group_entity_view_alter(&$build, EntityInterface $entity, EntityDisplayInterface $display) { $context = array( 'entity_type' => $display->getTargetEntityTypeId(), 'bundle' => $entity->bundle(), 'entity' => $entity, 'display_context' => 'view', 'mode' => $display->getMode(), ); field_group_attach_groups($build, $context); // If no theme hook, we have no theme hook to preprocess. // Add a prerender. if (empty($build['#theme'])) { $ds_enabled = false; if (Drupal::moduleHandler()->moduleExists('ds')) { // Check if DS is enabled for this display. if ($display->getThirdPartySetting('ds', 'layout') && !Drupal\ds\Ds::isDisabled()) { $ds_enabled = true; } } // If DS is enabled, no pre render is needed (DS adds fieldgroup preprocessing). if (!$ds_enabled) { $build['#pre_render'][] = 'field_group_entity_view_pre_render'; } } } /** * Pre render callback for rendering groups. * @see field_group_field_attach_form * @param $element Form that is being rendered. */ function field_group_form_pre_render($element) { if (empty($element['#field_group_form_pre_render'])) { $element['#field_group_form_pre_render'] = TRUE; field_group_build_entity_groups($element, 'form'); } return $element; } /** * Pre render callback for rendering groups on entities without theme hook. * @param $element * Entity being rendered. */ function field_group_entity_view_pre_render($element) { field_group_build_entity_groups($element, 'view'); return $element; } /** * Implements hook_field_group_pre_render(). * * @param Array $element * Group beïng rendered. * @param Object $group * The Field group info. * @param $rendering_object * The entity / form beïng rendered */ function field_group_field_group_pre_render(&$element, &$group, &$rendering_object) { // Add all field_group format types to the js settings. $element['#attached']['drupalSettings']['field_group'] = array( $group->format_type => [ 'mode' => $group->mode, 'context' => $group->context, 'settings' => $group->format_settings, ], ); $element['#weight'] = $group->weight; // Call the pre render function for the format type. $manager = Drupal::service('plugin.manager.field_group.formatters'); $plugin = $manager->getInstance(array( 'format_type' => $group->format_type, 'configuration' => array('label' => $group->label, 'settings' => $group->format_settings), 'group' => $group, )); $plugin->preRender($element, $rendering_object); } /** * Implements hook_field_group_build_pre_render_alter(). * @param Array $elements by address. */ function field_group_field_group_build_pre_render_alter(& $element) { // Someone is doing a node view, in a node view. Reset content. if (isset($element['#node']->content) && count($element['#node']->content) > 0) { $element['#node']->content = array(); } $display = isset($element['#view_mode']); $groups = array_keys($element['#fieldgroups']); // Dish the fieldgroups with no fields for non-forms. if ($display) { field_group_remove_empty_display_groups($element, $groups); } else { // Fix the problem on forms with additional settings. field_group_remove_empty_form_groups('form', $element, $groups, $element['#fieldgroups'], $element['#entity_type']); } } /** * Attach groups to the (form) build. * * @param Array $element * The part of the form. * @param Array $context * The contextual information. */ function field_group_attach_groups(&$element, $context) { $entity_type = $context['entity_type']; $bundle = $context['bundle']; $mode = $context['mode']; $display_context = $context['display_context']; $element['#fieldgroups'] = field_group_info_groups($entity_type, $bundle, $display_context, $mode); // Create a lookup array. $group_children = array(); foreach ($element['#fieldgroups'] as $group_name => $group) { foreach ($group->children as $child) { $group_children[$child] = $group_name; } } $element['#group_children'] = $group_children; $element['#entity_type'] = $entity_type; } /** * Preprocess/ Pre-render callback. * * @see field_group_form_pre_render() * @see field_group_theme_registry_alter * @see field_group_fields_nest() * @param $vars preprocess vars or form element * @param $context The display context (form or view) * @return $element Array with re-arranged fields in groups. */ function field_group_build_entity_groups(&$vars, $context = 'view') { if ($context == 'form') { $element = &$vars; $nest_vars = NULL; } else { if (isset($vars['elements'])) { $element = &$vars['elements']; } elseif (isset($vars['content'])) { $element = &$vars['content']; } else { if ($context === 'eck_entity') { $element = &$vars['entity']; } else { $element = &$vars; } } $nest_vars = &$vars; } // No groups on the entity. if (empty($element['#fieldgroups'])) { return $element; } // Nest the fields in the corresponding field groups. field_group_fields_nest($element, $nest_vars); // Allow others to alter the pre_rendered build. Drupal::moduleHandler()->alter('field_group_build_pre_render', $element); // Return the element on forms. if ($context == 'form') { return $element; } // No groups on the entity. Prerender removed empty field groups. if (empty($element['#fieldgroups'])) { return $element; } // Put groups inside content if we are rendering an entity_view. foreach ($element['#fieldgroups'] as $group) { if (!empty($element[$group->group_name])) { if (isset($vars['content'])) { $vars['content'][$group->group_name] = $element[$group->group_name]; } elseif (isset($vars['user_profile'])) { $vars['user_profile'][$group->group_name] = $element[$group->group_name]; } } } } /** * 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. * @param Array $vars * Rendering vars from the entity being viewed. */ function field_group_fields_nest(&$element, &$vars = NULL) { // Create all groups and keep a flat list of references to these groups. $group_references = array(); foreach ($element['#fieldgroups'] as $group_name => $group) { // Construct own weight, as some fields (for example preprocess fields) don't have weight set. $element[$group_name] = array(); $group_references[$group_name] = &$element[$group_name]; } // Loop through all form children looking for those that are supposed to be // in groups, and insert placeholder element for the new group field in the // correct location within the form structure. $element_clone = array(); foreach (Element::children($element) as $child_name) { $element_clone[$child_name] = $element[$child_name]; // If this element is in a group, create the placeholder element. if (isset($element['#group_children'][$child_name])) { $element_clone[$element['#group_children'][$child_name]] = array(); } } $element = array_merge($element_clone, $element); // 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) { // Entity being viewed if ($vars) { // If not a group, check vars['content'] for empty field. if (!isset($element['#fieldgroups'][$child_name]) && isset($vars['content'][$child_name])) { $group_references[$parent_name][$child_name] = $vars['content'][$child_name]; unset($vars['content'][$child_name]); } elseif (!isset($element['#fieldgroups'][$child_name]) && isset($vars['user_profile'][$child_name])) { $group_references[$parent_name][$child_name] = $vars['user_profile'][$child_name]; unset($vars['user_profile'][$child_name]); } // 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). else { $group_references[$parent_name][$child_name] = &$element[$child_name]; unset($element[$child_name]); } } // Form being viewed else { // Block denied fields (#access) before they are put in groups. // Fields (not groups) that don't have children (like field_permissions) are removed // in field_group_field_group_build_pre_render_alter. if (isset($element[$child_name]) && (!isset($element[$child_name]['#access']) || $element[$child_name]['#access'])) { // 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]; $group_references[$parent_name]['#weight'] = $element['#fieldgroups'][$parent_name]->weight; } // 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['#fieldgroups'] 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 * Render array of group element that needs to be created. * @param $group * Object with the group information. * @param $rendering_object * The entity / form beïng rendered. */ function field_group_pre_render(& $element, $group, & $rendering_object) { // 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 (Drupal::moduleHandler()->getImplementations('field_group_pre_render') as $module) { // 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 = $module . '_field_group_pre_render'; $function($element, $group, $rendering_object); } // Allow others to alter the pre_render. Drupal::moduleHandler()->alter('field_group_pre_render', $element, $group, $rendering_object); } /** * 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. * @param $display * The display to update if known. * @return EntityDisplayInterface || NULL */ function field_group_group_save($group, $display = NULL) { if ($display === NULL) { if ($group->context == 'form') { $display = EntityFormDisplay::load($group->entity_type . '.' . $group->bundle . '.' . $group->mode); } elseif ($group->context == 'view') { $display = EntityViewDisplay::load($group->entity_type . '.' . $group->bundle . '.' . $group->mode); } } // If no display was found. It doesn't exist yet, create it. if (!isset($display)) { if ($group->context == 'form') { $display = EntityFormDisplay::create(array( 'targetEntityType' => $group->entity_type, 'bundle' => $group->bundle, 'mode' => $group->mode, ))->setStatus(TRUE); } elseif ($group->context == 'view') { $display = EntityViewDisplay::create(array( 'targetEntityType' => $group->entity_type, 'bundle' => $group->bundle, 'mode' => $group->mode, ))->setStatus(TRUE); } } /** * @var $display \Drupal\Core\Entity\Display\EntityDisplayInterface */ if (isset($display)) { $data = (array) $group; unset($data['group_name'], $data['entity_type'], $data['bundle'], $data['mode'], $data['form'], $data['context']); $display->setThirdPartySetting('field_group', $group->group_name, $data); $display->save(); } return $display; } /** * Delete a field group. * * @param $group * A group definition. */ function field_group_group_delete($group) { if ($group->context == 'form') { $display = EntityFormDisplay::load($group->entity_type . '.' . $group->bundle . '.' . $group->mode); } elseif ($group->context == 'view') { $display = EntityViewDisplay::load($group->entity_type . '.' . $group->bundle . '.' . $group->mode); } /** * @var $display \Drupal\Core\Entity\Display\EntityDisplayInterface */ if (isset($display)) { $display->unsetThirdPartySetting('field_group', $group->group_name); $display->save(); } Drupal::moduleHandler()->invokeAll('field_group_delete_field_group', array($group)); } /** * Get all groups. * * @param $entity_type * The name of the entity. * @param $bundle * The name of the bundle. * @param $context * The context of the view mode (form or view) * @param $mode * The view mode. */ function field_group_info_groups($entity_type, $bundle, $context, $mode) { if ($context == 'form') { $display = EntityFormDisplay::load($entity_type . '.' . $bundle . '.' . $mode); if (!$display) { return array(); } $data = $display->getThirdPartySettings('field_group'); } if ($context == 'view') { $display = EntityViewDisplay::load($entity_type . '.' . $bundle . '.' . $mode); if (!$display) { return array(); } $data = $display->getThirdPartySettings('field_group'); } $groups = array(); if (isset($data)) { foreach ($data as $group_name => $definition) { $definition += array( 'group_name' => $group_name, 'entity_type' => $entity_type, 'bundle' => $bundle, 'context' => $context, 'mode' => $mode, ); $groups[$group_name] = (object) $definition; } } return $groups; } /** * Loads a group definition. * * @param $group_name * The name of the group. * @param $entity_type * The name of the entity. * @param $bundle * The name of the bundle. * @param $context * The context of the view mode (form or view) * @param $mode * The view mode to load. */ function field_group_load_field_group($group_name, $entity_type, $bundle, $context, $mode) { $groups = field_group_info_groups($entity_type, $bundle, $context, $mode); if (isset($groups[$group_name])) { return $groups[$group_name]; } } /** * 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 $context * The context of the view mode (form or view) * @param String $mode * The view mode context the group will be rendered. */ function field_group_exists($group_name, $entity_type, $bundle, $context, $mode) { return (bool) field_group_load_field_group($group_name, $entity_type, $bundle, $context, $mode); } /** * Remove empty groups on forms. * * @param String $parent_name * The name of the element. * @param array $element * The element to check the empty state. * @param array $groups * Array of group objects. */ function field_group_remove_empty_form_groups($name, & $element, $groups, &$form_groups, $entity) { $exceptions = array('user__account', 'comment__author'); $children = Element::children($element); $hasChildren = FALSE; if (count($children)) { foreach ($children as $childname) { if (in_array($childname, $groups)) { field_group_remove_empty_form_groups($childname, $element[$childname], $groups, $form_groups, $entity); } $exception = $entity . '__' . $childname; $hasChildren = $hasChildren ? TRUE : (isset($element[$childname]['#type']) || isset($element[$childname]['#markup']) || in_array($exception, $exceptions)); } } if (!$hasChildren) { // Remove empty elements from the #fieldgroups. if (empty($element) && isset($form_groups[$name]) && !is_array($form_groups[$name])) { foreach ($form_groups as $group_name => $group) { if (isset($group->children)) { $group_children = array_flip($group->children); if (isset($group_children[$name])) { unset($form_groups[$group_name]->children[$group_children[$name]]); } } } } $element['#access'] = FALSE; } } /** * Remove empty groups on entity display. * * @param array $element * The element to check the empty state. * @param array $groups * Array of group objects. */ function field_group_remove_empty_display_groups(& $element, $groups) { $empty_child = TRUE; $empty_group = TRUE; // Loop through the visible children for current element. foreach (Element::getVisibleChildren($element) as $name) { // Descend if the child is a group. if (in_array($name, $groups)) { $empty_child = field_group_remove_empty_display_groups($element[$name], $groups); if (!$empty_child) { $empty_group = FALSE; } } // Child is a field or a renderable array and the element is not empty. elseif (!empty($element[$name])) { $clone_element = $element[$name]; // Weight parameter can make empty element seen as not empty. unset($clone_element['#weight']); if (!Element::isEmpty($clone_element)) { $empty_group = FALSE; } } } // Reset an empty group. if ($empty_group) { $element = []; } return $empty_group; }