addfolder

This commit is contained in:
2018-04-23 21:14:46 +02:00
parent cec9a0314b
commit a7cb972085
192 changed files with 16037 additions and 24 deletions
@@ -0,0 +1,80 @@
<?php
namespace Drupal\field_group\Annotation;
use Drupal\Component\Annotation\Plugin;
/**
* Defines a FieldGroupFormatter annotation object.
*
* Formatters handle the display of fieldgroups.
*
* Additional annotation keys for formatters can be defined in
* hook_field_group_formatter_info_alter().
*
* @Annotation
*
* @see \Drupal\field_group\FieldGroupFormatterPluginManager
* @see \Drupal\field_group\FieldGroupFormatterInterface
*
* @ingroup field_formatter
*/
class FieldGroupFormatter extends Plugin {
/**
* The plugin ID.
*
* @var string
*/
public $id;
/**
* The human-readable name of the formatter type.
*
* @ingroup plugin_translatable
*
* @var \Drupal\Core\Annotation\Translation
*/
public $label;
/**
* A short description of the formatter type.
*
* @ingroup plugin_translatable
*
* @var \Drupal\Core\Annotation\Translation
*/
public $description;
/**
* The name of the fieldgroup formatter class.
*
* This is not provided manually, it will be added by the discovery mechanism.
*
* @var string
*/
public $class;
/**
* An array of contexts the formatter supports (form / view).
*
* @var array
*/
public $supported_contexts = array();
/**
* The different format types available for this formatter.
*
* @var array
*/
public $format_types = array();
/**
* An integer to determine the weight of this formatter relative to other
* formatter in the Field UI when selecting a formatter for a given group.
*
* @var int optional
*/
public $weight = NULL;
}
@@ -0,0 +1,57 @@
<?php
namespace Drupal\field_group\Element;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element\RenderElement;
/**
* Provides a render element for an accordion.
*
* @FormElement("field_group_accordion")
*/
class Accordion extends RenderElement {
/**
* {@inheritdoc}
*/
public function getInfo() {
$class = get_class($this);
return array(
'#process' => array(
array($class, 'processAccordion'),
),
'#theme_wrappers' => array('field_group_accordion'),
);
}
/**
* Process the accordion item.
*
* @param array $element
* An associative array containing the properties and children of the
* details element.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return array
* The processed element.
*/
public static function processAccordion(&$element, FormStateInterface $form_state) {
// Add the jQuery UI accordion.
$element['#attached']['library'][] = 'field_group/formatter.accordion';
// Add the effect class.
if (isset($element['#effect'])) {
if (!isset($element['#attributes']['class'])) {
$element['#attributes']['class'] = array();
}
$element['#attributes']['class'][] = 'effect-' . $element['#effect'];
}
return $element;
}
}
@@ -0,0 +1,24 @@
<?php
namespace Drupal\field_group\Element;
use Drupal\Core\Render\Element\RenderElement;
/**
* Provides a render element for an accordion item.
*
* @FormElement("field_group_accordion_item")
*/
class AccordionItem extends RenderElement {
/**
* {@inheritdoc}
*/
public function getInfo() {
return array(
'#open' => TRUE,
'#theme_wrappers' => array('field_group_accordion_item'),
);
}
}
@@ -0,0 +1,88 @@
<?php
namespace Drupal\field_group\Element;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element\RenderElement;
/**
* Provides a render element for horizontal tabs.
*
* Formats all child details and all non-child details whose #group is
* assigned this element's name as horizontal tabs.
*
* @FormElement("horizontal_tabs")
*/
class HorizontalTabs extends RenderElement {
/**
* {@inheritdoc}
*/
public function getInfo() {
$class = get_class($this);
return array(
'#default_tab' => '',
'#process' => array(
array($class, 'processHorizontalTabs'),
),
'#theme_wrappers' => array('horizontal_tabs'),
);
}
/**
* Creates a group formatted as horizontal tabs.
*
* @param array $element
* An associative array containing the properties and children of the
* details element.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param bool $on_form
* Are the tabs rendered on a form or not.
*
* @return array
* The processed element.
*/
public static function processHorizontalTabs(&$element, FormStateInterface $form_state, $on_form = TRUE) {
// Inject a new details as child, so that form_process_details() processes
// this details element like any other details.
$element['group'] = array(
'#type' => 'details',
'#theme_wrappers' => array(),
'#parents' => $element['#parents'],
);
// Add an invisible label for accessibility.
if (!isset($element['#title'])) {
$element['#title'] = t('Horizontal Tabs');
$element['#title_display'] = 'invisible';
}
// Add required JavaScript and Stylesheet.
$element['#attached']['library'][] = 'field_group/element.horizontal_tabs';
// Only add forms library on forms.
if ($on_form) {
$element['#attached']['library'][] = 'core/drupal.form';
}
// 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 ($form_state->hasValue($name . '__active_tab')){
$element['#default_tab'] = $form_state->getValue($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;
}
}
@@ -0,0 +1,65 @@
<?php
namespace Drupal\field_group\Element;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element\RenderElement;
/**
* Provides a render element for a html element.
*
* @FormElement("field_group_html_element")
*/
class HtmlElement extends RenderElement {
/**
* {@inheritdoc}
*/
public function getInfo() {
$class = get_class($this);
return array(
'#process' => array(
array($class, 'processHtmlElement'),
),
'#theme_wrappers' => array('field_group_html_element'),
);
}
/**
* Process a html element
*
* @param array $element
* An associative array containing the properties and children of the
* details element.
* @param FormStateInterface $form_state
* The current state of the form.
*
* @return array
* The processed element.
*/
public static function processHtmlElement(&$element, FormStateInterface $form_state) {
// If an effect is set, we need to load extra js.
if (!empty($element['#effect']) && $element['#effect'] !== 'none') {
$element['#attached']['library'][] = 'field_group/formatter.html_element';
// Add the required classes for the js.
$element['#attributes']['class'][] = 'fieldgroup-collapsible';
$element['#attributes']['class'][] = 'effect-' . $element['#effect'];
if (!empty($element['#speed'])) {
$element['#attributes']['class'][] = 'speed-' . $element['#speed'];
}
// Add jquery ui effects library for the blind effect.
if ($element['#effect'] == 'blind') {
$element['#attached']['library'][] = 'core/jquery.ui.effects.blind';
}
}
return $element;
}
}
@@ -0,0 +1,179 @@
<?php
namespace Drupal\field_group;
use Drupal\Core\Field\PluginSettingsBase;
/**
* Base class for 'Fieldgroup formatter' plugin implementations.
*
* @ingroup field_group_formatter
*/
abstract class FieldGroupFormatterBase extends PluginSettingsBase implements FieldGroupFormatterInterface {
/**
* The group this formatter needs to render.
* @var stdClass
*/
protected $group;
/**
* The formatter settings.
*
* @var array
*/
protected $settings;
/**
* The label display setting.
*
* @var string
*/
protected $label;
/**
* The view mode.
*
* @var string
*/
protected $viewMode;
/**
* The context mode.
*
* @var string
*/
protected $context;
/**
* Constructs a FieldGroupFormatterBase object.
*
* @param string $plugin_id
* The plugin_id for the formatter.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param $group
* The group object.
* @param array $settings
* The formatter settings.
* @param string $label
* The formatter label.
*/
public function __construct($plugin_id, $plugin_definition, $group, array $settings, $label) {
parent::__construct(array(), $plugin_id, $plugin_definition);
$this->group = $group;
$this->settings = $settings;
$this->label = $label;
$this->context = $group->context;
}
/**
* Get the current label.
* @return string
*/
public function getLabel() {
return $this->label;
}
/**
* {@inheritdoc}
*/
public function settingsForm() {
$form = array();
$form['label'] = array(
'#type' => 'textfield',
'#title' => t('Field group label'),
'#default_value' => $this->label,
'#weight' => -5,
);
$form['id'] = array(
'#title' => t('ID'),
'#type' => 'textfield',
'#default_value' => $this->getSetting('id'),
'#weight' => 10,
'#element_validate' => array('field_group_validate_id'),
);
$form['classes'] = array(
'#title' => t('Extra CSS classes'),
'#type' => 'textfield',
'#default_value' => $this->getSetting('classes'),
'#weight' => 11,
'#element_validate' => array('field_group_validate_css_class'),
);
return $form;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = array();
if ($this->getSetting('formatter')) {
$summary[] = $this->pluginDefinition['label'] . ': ' . $this->getSetting('formatter');
}
if ($this->getSetting('id')) {
$summary[] = $this->t('Id: @id', array('@id' => $this->getSetting('id')));
}
if ($this->getSetting('classes')) {
$summary[] = \Drupal::translation()->translate('Extra CSS classes: @classes', array('@classes' => $this->getSetting('classes')));
}
return $summary;
}
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return self::defaultContextSettings('view');
}
/**
* {@inheritdoc}
*/
public static function defaultContextSettings($context) {
return array(
'classes' => '',
'id' => '',
);
}
/**
* Get the classes to add to the group.
*/
protected function getClasses() {
$classes = array();
// Add a required-fields class to trigger the js.
if ($this->getSetting('required_fields')) {
$classes[] = 'required-fields';
$classes[] = 'field-group-' . str_replace('_', '-', $this->getBaseId());
}
if ($this->getSetting('classes')) {
$classes = array_merge($classes, explode(' ', trim($this->getSetting('classes'))));
}
return $classes;
}
/**
* {@inheritdoc}
*/
public function preRender(&$element, $rendering_object) {
$element['#group_name'] = $this->group->group_name;
$element['#entity_type'] = $this->group->entity_type;
$element['#bundle'] = $this->group->bundle;
}
}
@@ -0,0 +1,58 @@
<?php
namespace Drupal\field_group;
use Drupal\Component\Plugin\PluginInspectionInterface;
/**
* Interface definition for fieldgroup formatter plugins.
*
* @ingroup field_group_formatter
*/
interface FieldGroupFormatterInterface extends PluginInspectionInterface {
/**
* Allows the field group formatter to manipulate the field group array and attach the formatters rendering element.
*
* @param array $element
* The field group render array.
* @param object $rendering_object
* The object / entity beïng rendered.
*/
public function preRender(&$element, $rendering_object);
/**
* Returns a form to configure settings for the formatter.
*
* Invoked in field_group_field_ui_display_form_alter to allow
* administrators to configure the formatter. The field_group module takes care
* of handling submitted form values.
*
* @return array
* The form elements for the formatter settings.
*/
public function settingsForm();
/**
* Returns a short summary for the current formatter settings.
*
* If an empty result is returned, a UI can still be provided to display
* a settings form in case the formatter has configurable settings.
*
* @return array()
* A short summary of the formatter settings.
*/
public function settingsSummary();
/**
* Defines the default settings for this plugin.
*
* @param string $context
* The context to get the default settings for.
*
* @return array
* A list of default settings, keyed by the setting name.
*/
public static function defaultContextSettings($context);
}
@@ -0,0 +1,143 @@
<?php
namespace Drupal\field_group;
use Drupal\Component\Plugin\Factory\DefaultFactory;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\Core\Extension\ModuleHandlerInterface;
/**
* Plugin type manager for all fieldgroup formatters.
*/
class FieldGroupFormatterPluginManager extends DefaultPluginManager {
/**
* Constructs a new FieldGroupFormatterPluginManager object.
*
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
* Cache backend instance to use.
* @param \Drupal\Core\Language\LanguageManager $language_manager
* The language manager.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler to invoke the alter hook with.
*/
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
parent::__construct('Plugin/field_group/FieldGroupFormatter', $namespaces, $module_handler, 'Drupal\field_group\FieldGroupFormatterInterface', 'Drupal\field_group\Annotation\FieldGroupFormatter');
$this->alterInfo('field_group_formatter_info');
$this->setCacheBackend($cache_backend, 'field_group_formatter_info');
}
/**
* {@inheritdoc}
*/
public function createInstance($plugin_id, array $configuration = array()) {
$plugin_definition = $this->getDefinition($plugin_id);
$plugin_class = DefaultFactory::getPluginClass($plugin_id, $plugin_definition);
// If the plugin provides a factory method, pass the container to it.
if (is_subclass_of($plugin_class, 'Drupal\Core\Plugin\ContainerFactoryPluginInterface')) {
return $plugin_class::create(\Drupal::getContainer(), $configuration, $plugin_id, $plugin_definition);
}
return new $plugin_class($plugin_id, $plugin_definition, $configuration['group'], $configuration['settings'], $configuration['label']);
}
/**
* Overrides PluginManagerBase::getInstance().
*
* @param array $options
* An array with the following key/value pairs:
* - format_type: The current format type.
* - group: The current group.
* - prepare: (bool, optional) Whether default values should get merged in
* the 'configuration' array. Defaults to TRUE.
* - configuration: (array) the configuration for the formatter. The
* following key value pairs are allowed, and are all optional if
* 'prepare' is TRUE:
* - label: (string) Position of the label. The default 'field' theme
* implementation supports the values 'inline', 'above' and 'hidden'.
* Defaults to 'above'.
* - settings: (array) Settings specific to the formatter. Each setting
* defaults to the default value specified in the formatter definition.
*
* @return \Drupal\field_group\FieldGroupFormatterInterface|null
* A formatter object or NULL when plugin is not found.
*/
public function getInstance(array $options) {
$configuration = $options['configuration'];
$format_type = $options['format_type'];
$context = $options['group']->context;
// Fill in default configuration if needed.
if (!isset($options['prepare']) || $options['prepare'] == TRUE) {
$configuration = $this->prepareConfiguration($format_type, $context, $configuration);
}
$plugin_id = $format_type;
// Validate if plugin exists and it's allowed for current context.
$definition = $this->getDefinition($format_type, FALSE);
if (!isset($definition['class']) || !in_array($context, $definition['supported_contexts'])) {
return NULL;
}
$configuration += array(
'group' => $options['group'],
);
return $this->createInstance($plugin_id, $configuration);
}
/**
* Merges default values for formatter configuration.
*
* @param string $format_type
* The format type
* @param string $context
* The context to prepare configuration for.
* @param array $properties
* An array of formatter configuration.
*
* @return array
* The display properties with defaults added.
*/
public function prepareConfiguration($format_type, $context, array $configuration) {
// Fill in defaults for missing properties.
$configuration += array(
'label' => '',
'settings' => array(),
);
// Fill in default settings values for the formatter.
$configuration['settings'] += $this->getDefaultSettings($format_type, $context);
return $configuration;
}
/**
* Returns the default settings of a field_group formatter.
*
* @param string $type
* A formatter type name.
* @param string $context
* The context to get default values for.
*
* @return array
* The formatter type's default settings, as provided by the plugin
* definition, or an empty array if type or settings are undefined.
*/
public function getDefaultSettings($type, $context) {
$plugin_definition = $this->getDefinition($type, FALSE);
if (!empty($plugin_definition['class'])) {
$plugin_class = DefaultFactory::getPluginClass($type, $plugin_definition);
return $plugin_class::defaultContextSettings($context);
}
return array();
}
}
+97
View File
@@ -0,0 +1,97 @@
<?php
namespace Drupal\field_group;
use Drupal\Core\Url;
use Drupal\field_ui\FieldUI;
/**
* Static methods for fieldgroup UI.
*/
class FieldgroupUi {
/**
* Get the field ui route that should be used for given arguments.
* @param stdClass $group
* The group to get the field ui route for.
*
* @return \Drupal\Core\Url
* A URL object.
*/
public static function getFieldUiRoute($group) {
$entity_type = \Drupal::entityTypeManager()->getDefinition($group->entity_type);
if ($entity_type->get('field_ui_base_route')) {
$context_route_name = "";
$mode_route_name = "default";
$route_parameters = FieldUI::getRouteBundleParameter($entity_type, $group->bundle);
// Get correct route name based on context and mode.
if ($group->context == 'form') {
$context_route_name = 'entity_form_display';
if ($group->mode != 'default') {
$mode_route_name = 'form_mode';
$route_parameters['form_mode_name'] = $group->mode;
}
}
else {
$context_route_name = 'entity_view_display';
if ($group->mode != 'default') {
$mode_route_name = 'view_mode';
$route_parameters['view_mode_name'] = $group->mode;
}
}
return new Url("entity.{$context_route_name}.{$group->entity_type}.{$mode_route_name}", $route_parameters);
}
}
/**
* Get the field group delete route for a given group.
* @param \stdClass $group
*
* @return \Drupal\Core\Url
* A URL object.
*/
public static function getDeleteRoute($group) {
$entity_type_id = $group->entity_type;
$entity_type = \Drupal::entityTypeManager()->getDefinition($entity_type_id);
if ($entity_type->get('field_ui_base_route')) {
$mode_route_name = '';
$route_parameters = FieldUI::getRouteBundleParameter($entity_type, $group->bundle);
$route_parameters['field_group_name'] = $group->group_name;
// Get correct route name based on context and mode.
if ($group->context == 'form') {
$context_route_name = 'form_display';
if ($group->mode != 'default') {
$mode_route_name = '.form_mode';
$route_parameters['form_mode_name'] = $group->mode;
}
}
else {
$context_route_name = 'display';
if ($group->mode != 'default') {
$mode_route_name = '.view_mode';
$route_parameters['view_mode_name'] = $group->mode;
}
}
return new Url('field_ui.field_group_delete_' . $entity_type_id . '.' . $context_route_name . $mode_route_name, $route_parameters);
}
throw new \InvalidArgumentException('The given group is not a valid.');
}
}
@@ -0,0 +1,290 @@
<?php
namespace Drupal\field_group\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\field_group\FieldgroupUi;
/**
* Provides a form for adding a fieldgroup to a bundle.
*/
class FieldGroupAddForm extends FormBase {
/**
* The prefix for groups.
*
* @var string
*/
const GROUP_PREFIX = 'group_';
/**
* The name of the entity type.
*
* @var string
*/
protected $entityTypeId;
/**
* The entity bundle.
*
* @var string
*/
protected $bundle;
/**
* The context for the group.
*
* @var string
*/
protected $context;
/**
* The mode for the group.
*
* @var string
*/
protected $mode;
/**
* Current step of the form.
*
* @var string
*/
protected $currentStep;
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'field_group_add_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, $entity_type_id = NULL, $bundle = NULL, $context = NULL) {
if ($context == 'form') {
$this->mode = \Drupal::request()->get('form_mode_name');
}
else {
$this->mode = \Drupal::request()->get('view_mode_name');
}
if (empty($this->mode)) {
$this->mode = 'default';
}
if (!$form_state->get('context')) {
$form_state->set('context', $context);
}
if (!$form_state->get('entity_type_id')) {
$form_state->set('entity_type_id', $entity_type_id);
}
if (!$form_state->get('bundle')) {
$form_state->set('bundle', $bundle);
}
if (!$form_state->get('step')) {
$form_state->set('step', 'formatter');
}
$this->entityTypeId = $form_state->get('entity_type_id');
$this->bundle = $form_state->get('bundle');
$this->context = $form_state->get('context');
$this->currentStep = $form_state->get('step');
if ($this->currentStep == 'formatter') {
$this->buildFormatterSelectionForm($form, $form_state);
}
else {
$this->buildConfigurationForm($form, $form_state);
}
return $form;
}
/**
* Build the formatter selection step.
*/
function buildFormatterSelectionForm(array &$form, FormStateInterface $form_state) {
// Gather group formatters.
$formatter_options = \field_group_field_formatter_options($form_state->get('context'));
$form['add'] = array(
'#type' => 'container',
'#attributes' => array('class' => array('form--inline', 'clearfix')),
);
$form['add']['group_formatter'] = array(
'#type' => 'select',
'#title' => $this->t('Add a new group'),
'#options' => $formatter_options,
'#empty_option' => $this->t('- Select a group type -'),
'#required' => TRUE,
);
// Field label and field_name.
$form['new_group_wrapper'] = array(
'#type' => 'container',
'#states' => array(
'!visible' => array(
':input[name="group_formatter"]' => array('value' => ''),
),
),
);
$form['new_group_wrapper']['label'] = array(
'#type' => 'textfield',
'#title' => $this->t('Label'),
'#size' => 15,
'#required' => TRUE,
);
$form['new_group_wrapper']['group_name'] = array(
'#type' => 'machine_name',
'#size' => 15,
// This field should stay LTR even for RTL languages.
'#field_prefix' => '<span dir="ltr">' . self::GROUP_PREFIX,
'#field_suffix' => '</span>&lrm;',
'#description' => $this->t('A unique machine-readable name containing letters, numbers, and underscores.'),
'#maxlength' => FieldStorageConfig::NAME_MAX_LENGTH - strlen(self::GROUP_PREFIX),
'#machine_name' => array(
'source' => array('new_group_wrapper', 'label'),
'exists' => array($this, 'groupNameExists'),
),
'#required' => TRUE,
);
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Save and continue'),
'#button_type' => 'primary',
'#validate' => array(
array($this, 'validateFormatterSelection')
),
);
$form['#attached']['library'][] = 'field_ui/drupal.field_ui';
}
/**
* Build the formatter configuration form.
*/
function buildConfigurationForm(array &$form, FormStateInterface $form_state) {
$group = new \stdClass();
$group->context = $this->context;
$group->entity_type = $this->entityTypeId;
$group->bundle = $this->bundle;
$group->mode = $this->mode;
$manager = \Drupal::service('plugin.manager.field_group.formatters');
$plugin = $manager->getInstance(array(
'format_type' => $form_state->getValue('group_formatter'),
'configuration' => [
'label' => $form_state->getValue('label'),
],
'group' => $group,
));
$form['format_settings'] = array(
'#type' => 'container',
'#tree' => TRUE,
);
$form['format_settings'] += $plugin->settingsForm();
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Create group'),
'#button_type' => 'primary',
);
}
/**
* Validate the formatter selection step.
*/
public function validateFormatterSelection(array &$form, FormStateInterface $form_state) {
$group_name = self::GROUP_PREFIX . $form_state->getValue('group_name');
// Add the prefix.
$form_state->setValueForElement($form['new_group_wrapper']['group_name'], $group_name);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
if ($form_state->get('step') == 'formatter') {
$form_state->set('step', 'configuration');
$form_state->set('group_label', $form_state->getValue('label'));
$form_state->set('group_name', $form_state->getValue('group_name'));
$form_state->set('group_formatter', $form_state->getValue('group_formatter'));
$form_state->setRebuild();
}
else {
$new_group = (object) array(
'group_name' => $form_state->get('group_name'),
'entity_type' => $this->entityTypeId,
'bundle' => $this->bundle,
'mode' => $this->mode,
'context' => $this->context,
'children' =>[],
'parent_name' => '',
'weight' => 20,
'format_type' => $form_state->get('group_formatter'),
);
$new_group->format_settings = $form_state->getValue('format_settings');
$new_group->label = $new_group->format_settings['label'];
unset($new_group->format_settings['label']);
$new_group->format_settings += _field_group_get_default_formatter_settings($form_state->get('group_formatter'), $this->context);
field_group_group_save($new_group);
// Store new group information for any additional submit handlers.
$groups_added = $form_state->get('groups_added');
$groups_added['_add_new_group'] = $new_group->group_name;
drupal_set_message(t('New group %label successfully created.', array('%label' => $new_group->label)));
$form_state->setRedirectUrl(FieldgroupUi::getFieldUiRoute($new_group));
\Drupal::cache()->invalidate('field_groups');
}
}
/**
* Checks if a group machine name is taken.
*
* @param string $value
* The machine name, not prefixed.
* @param array $element
* An array containing the structure of the 'group_name' element.
* @param FormStateInterface $form_state
* The current state of the form.
*
* @return bool
* Whether or not the group machine name is taken.
*/
public function groupNameExists($value, $element, FormStateInterface $form_state) {
// Add the prefix.
$group_name = self::GROUP_PREFIX . $value;
$entity_type = $form_state->get('entity_type_id');
$bundle = $form_state->get('bundle');
$context = $form_state->get('context');
$mode = $form_state->get('mode');
return field_group_exists($group_name, $entity_type, $bundle, $context, $mode);
}
}
@@ -0,0 +1,82 @@
<?php
namespace Drupal\field_group\Form;
use Drupal\Core\Form\ConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\field_group\FieldgroupUi;
/**
* Provides a form for removing a fieldgroup from a bundle.
*/
class FieldGroupDeleteForm extends ConfirmFormBase {
/**
* The fieldgroup to delete.
*
* @var stdClass
*/
protected $fieldGroup;
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'field_group_delete_form';
}
public function buildForm(array $form, FormStateInterface $form_state, $field_group_name = NULL, $entity_type_id = NULL, $bundle = NULL, $context = NULL) {
if ($context == 'form') {
$mode = $this->getRequest()->attributes->get('form_mode_name');
}
else {
$mode = $this->getRequest()->attributes->get('view_mode_name');
}
if (empty($mode)) {
$mode = 'default';
}
$this->fieldGroup = field_group_load_field_group($field_group_name, $entity_type_id, $bundle, $context, $mode);
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$bundles = entity_get_bundles();
$bundle_label = $bundles[$this->fieldGroup->entity_type][$this->fieldGroup->bundle]['label'];
field_group_group_delete($this->fieldGroup);
drupal_set_message(t('The group %group has been deleted from the %type content type.', array('%group' => t($this->fieldGroup->label), '%type' => $bundle_label)));
// Redirect.
$form_state->setRedirectUrl($this->getCancelUrl());
}
/**
* {@inheritdoc}
*/
public function getQuestion() {
return $this->t('Are you sure you want to delete the group %group?', array('%group' => t($this->fieldGroup->label)));
}
/**
* {@inheritdoc}
*/
public function getConfirmText() {
return $this->t('Delete');
}
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
return FieldgroupUi::getFieldUiRoute($this->fieldGroup);
}
}
@@ -0,0 +1,106 @@
<?php
namespace Drupal\field_group\Plugin\Derivative;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\Core\Routing\RouteProviderInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Component\Plugin\Derivative\DeriverBase;
/**
* Provides local action definitions for all entity bundles.
*/
class FieldGroupLocalAction extends DeriverBase implements ContainerDeriverInterface {
use StringTranslationTrait;
/**
* The route provider.
*
* @var \Drupal\Core\Routing\RouteProviderInterface
*/
protected $routeProvider;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Constructs a FieldUiLocalAction object.
*
* @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
* The route provider to load routes by name.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*/
public function __construct(RouteProviderInterface $route_provider, EntityTypeManagerInterface $entity_type_manager) {
$this->routeProvider = $route_provider;
$this->entityTypeManager = $entity_type_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$container->get('router.route_provider'),
$container->get('entity_type.manager')
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
$this->derivatives = array();
foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) {
if ($entity_type->get('field_ui_base_route')) {
$default_options = [
'title' => $this->t('Add group'),
];
$this->derivatives['field_group_add_' . $entity_type_id . '_form_display'] = [
'route_name' => "field_ui.field_group_add_$entity_type_id.form_display",
'appears_on' => [
"entity.entity_form_display.{$entity_type_id}.default",
],
] + $default_options;
$this->derivatives['field_group_add_' . $entity_type_id . '_form_display_form_mode'] = [
'route_name' => "field_ui.field_group_add_$entity_type_id.form_display.form_mode",
'appears_on' => [
"entity.entity_form_display.{$entity_type_id}.form_mode",
],
] + $default_options;
$this->derivatives['field_group_add_' . $entity_type_id . '_display'] = [
'route_name' => "field_ui.field_group_add_$entity_type_id.display",
'appears_on' => [
"entity.entity_view_display.{$entity_type_id}.default",
],
] + $default_options;
$this->derivatives['field_group_add_' . $entity_type_id . '_display_view_mode'] = [
'route_name' => "field_ui.field_group_add_$entity_type_id.display.view_mode",
'appears_on' => [
"entity.entity_view_display.{$entity_type_id}.view_mode",
],
] + $default_options;
}
}
foreach ($this->derivatives as &$entry) {
$entry += $base_plugin_definition;
}
return $this->derivatives;
}
}
@@ -0,0 +1,89 @@
<?php
namespace Drupal\field_group\Plugin\field_group\FieldGroupFormatter;
use Drupal\Component\Utility\Html;
use Drupal\Core\Form\FormState;
use Drupal\field_group\FieldGroupFormatterBase;
/**
* Plugin implementation of the 'accordion' formatter.
*
* @FieldGroupFormatter(
* id = "accordion",
* label = @Translation("Accordion"),
* description = @Translation("This fieldgroup renders child groups as jQuery accordion."),
* supported_contexts = {
* "form",
* "view",
* }
* )
*/
class Accordion extends FieldGroupFormatterBase {
/**
* {@inheritdoc}
*/
public function preRender(&$element, $rendering_object) {
parent::preRender($element, $rendering_object);
$form_state = new FormState();
$element += array(
'#type' => 'field_group_accordion',
'#effect' => $this->getSetting('effect'),
);
if ($this->getSetting('id')) {
$element['#id'] = Html::getId($this->getSetting('id'));
}
$classes = $this->getClasses();
if (!empty($classes)) {
$element += array('#attributes' => array('class' => $classes));
}
\Drupal\field_group\Element\Accordion::processAccordion($element, $form_state);
}
/**
* {@inheritdoc}
*/
public function settingsForm() {
$form = parent::settingsForm();
$form['effect'] = array(
'#title' => $this->t('Effect'),
'#type' => 'select',
'#options' => array('none' => $this->t('None'), 'bounceslide' => $this->t('Bounce slide')),
'#default_value' => $this->getSetting('effect'),
'#weight' => 2,
);
return $form;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = array();
$summary[] = $this->t('Effect : @effect',
array('@effect' => $this->getSetting('effect'))
);
return $summary;
}
/**
* {@inheritdoc}
*/
public static function defaultContextSettings($context) {
return array(
'effect' => 'none',
) + parent::defaultSettings($context);
}
}
@@ -0,0 +1,119 @@
<?php
namespace Drupal\field_group\Plugin\field_group\FieldGroupFormatter;
use Drupal;
use Drupal\Component\Utility\Html;
use Drupal\field_group\FieldGroupFormatterBase;
/**
* Plugin implementation of the 'accordion_item' formatter.
*
* @FieldGroupFormatter(
* id = "accordion_item",
* label = @Translation("Accordion Item"),
* description = @Translation("This fieldgroup renders the content in a div, part of accordion group."),
* format_types = {
* "open",
* "closed",
* },
* supported_contexts = {
* "form",
* "view",
* },
* )
*/
class AccordionItem extends FieldGroupFormatterBase {
/**
* {@inheritdoc}
*/
public function preRender(&$element, $rendering_object) {
parent::preRender($element, $rendering_object);
$element += array(
'#type' => 'field_group_accordion_item',
'#open' => $this->getSetting('formatter') == 'open' ? TRUE : FALSE,
'#description' => $this->getSetting('description'),
'#title' => Drupal::translation()->translate($this->getLabel()),
);
if ($this->getSetting('id')) {
$element['#id'] = Html::getId($this->getSetting('id'));
}
$classes = $this->getClasses();
if (!empty($classes)) {
$element += array('#attributes' => array('class' => $classes));
}
if ($this->getSetting('required_fields')) {
$element['#attached']['library'][] = 'field_group/formatter.details';
}
}
/**
* {@inheritdoc}
*/
public function settingsForm() {
$form = parent::settingsForm();
$form['formatter'] = array(
'#title' => $this->t('Default state'),
'#type' => 'select',
'#options' => array_combine($this->pluginDefinition['format_types'], $this->pluginDefinition['format_types']),
'#default_value' => $this->getSetting('formatter'),
'#weight' => -4,
);
if ($this->context == 'form') {
$form['required_fields'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Mark group as required if it contains required fields.'),
'#default_value' => $this->getSetting('required_fields'),
'#weight' => 2,
);
}
return $form;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = array();
if ($this->getSetting('required_fields')) {
$summary[] = $this->t('Mark as required');
}
if ($this->getSetting('description')) {
$summary[] = $this->t('Description : @description',
array('@description' => $this->getSetting('description'))
);
}
return $summary;
}
/**
* {@inheritdoc}
*/
public static function defaultContextSettings($context) {
$defaults = array(
'formatter' => 'closed',
'description' => '',
) + parent::defaultSettings($context);
if ($context == 'form') {
$defaults['required_fields'] = 1;
}
return $defaults;
}
}
@@ -0,0 +1,118 @@
<?php
namespace Drupal\field_group\Plugin\field_group\FieldGroupFormatter;
use Drupal\Component\Utility\Html;
use Drupal\field_group\FieldGroupFormatterBase;
/**
* Details element.
*
* @FieldGroupFormatter(
* id = "details",
* label = @Translation("Details"),
* description = @Translation("Add a details element"),
* supported_contexts = {
* "form",
* "view"
* }
* )
*/
class Details extends FieldGroupFormatterBase {
/**
* {@inheritdoc}
*/
public function preRender(&$element, $rendering_object) {
parent::preRender($element, $rendering_object);
$element += array(
'#type' => 'details',
'#title' => Html::escape($this->t($this->getLabel())),
'#open' => $this->getSetting('open')
);
if ($this->getSetting('id')) {
$element['#id'] = Html::getId($this->getSetting('id'));
}
$classes = $this->getClasses();
if (!empty($classes)) {
$element += array(
'#attributes' => array('class' => $classes),
);
}
if ($this->getSetting('description')) {
$element += array(
'#description' => $this->getSetting('description'),
);
}
if ($this->getSetting('required_fields')) {
$element['#attached']['library'][] = 'field_group/formatter.details';
$element['#attached']['library'][] = 'field_group/core';
}
}
/**
* {@inheritdoc}
*/
public function settingsForm() {
$form = parent::settingsForm();
$form['open'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Display element open by default.'),
'#default_value' => $this->getSetting('open'),
);
if ($this->context == 'form') {
$form['required_fields'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Mark group as required if it contains required fields.'),
'#default_value' => $this->getSetting('required_fields'),
'#weight' => 2,
);
}
return $form;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = array();
if ($this->getSetting('open')) {
$summary[] = $this->t('Default state open');
}
else {
$summary[] = $this->t('Default state closed');
}
if ($this->getSetting('required_fields')) {
$summary[] = $this->t('Mark as required');
}
return $summary;
}
/**
* {@inheritdoc}
*/
public static function defaultContextSettings($context) {
$defaults = array(
'open' => FALSE,
'required_fields' => $context == 'form',
) + parent::defaultSettings($context);
if ($context == 'form') {
$defaults['required_fields'] = 1;
}
return $defaults;
}
}
@@ -0,0 +1,117 @@
<?php
namespace Drupal\field_group\Plugin\field_group\FieldGroupFormatter;
use Drupal\Component\Utility\Html;
use Drupal\field_group\FieldGroupFormatterBase;
/**
* Plugin implementation of the 'fieldset' formatter.
*
* @FieldGroupFormatter(
* id = "fieldset",
* label = @Translation("Fieldset"),
* description = @Translation("This fieldgroup renders the inner content in a fieldset with the title as legend."),
* supported_contexts = {
* "form",
* "view",
* }
* )
*/
class Fieldset extends FieldGroupFormatterBase {
/**
* {@inheritdoc}
*/
public function preRender(&$element, $rendering_object) {
$element += array(
'#type' => 'fieldset',
'#title' => Html::escape($this->t($this->getLabel())),
'#pre_render' => array(),
'#attributes' => array(),
);
if ($this->getSetting('description')) {
$element += array(
'#description' => $this->getSetting('description'),
);
}
if ($this->getSetting('id')) {
$element['#id'] = Html::getId($this->getSetting('id'));
}
$classes = $this->getClasses();
if (!empty($classes)) {
$element['#attributes'] += array('class' => $classes);
}
if ($this->getSetting('required_fields')) {
$element['#attached']['library'][] = 'field_group/formatter.fieldset';
$element['#attached']['library'][] = 'field_group/core';
}
}
/**
* {@inheritdoc}
*/
public function settingsForm() {
$form = parent::settingsForm();
$form['description'] = array(
'#title' => $this->t('Description'),
'#type' => 'textarea',
'#default_value' => $this->getSetting('description'),
'#weight' => -4,
);
if ($this->context == 'form') {
$form['required_fields'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Mark group as required if it contains required fields.'),
'#default_value' => $this->getSetting('required_fields'),
'#weight' => 2,
);
}
return $form;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = parent::settingsSummary();
if ($this->getSetting('required_fields')) {
$summary[] = $this->t('Mark as required');
}
if ($this->getSetting('description')) {
$summary[] = $this->t('Description : @description',
array('@description' => $this->getSetting('description'))
);
}
return $summary;
}
/**
* {@inheritdoc}
*/
public static function defaultContextSettings($context) {
$defaults = array(
'description' => '',
) + parent::defaultSettings($context);
if ($context == 'form') {
$defaults['required_fields'] = 1;
}
return $defaults;
}
}
@@ -0,0 +1,219 @@
<?php
namespace Drupal\field_group\Plugin\field_group\FieldGroupFormatter;
use Drupal\Component\Utility\Html;
use Drupal\Core\Form\FormState;
use Drupal\Core\Template\Attribute;
use Drupal\field_group\FieldGroupFormatterBase;
/**
* Plugin implementation of the 'html_element' formatter.
*
* @FieldGroupFormatter(
* id = "html_element",
* label = @Translation("HTML element"),
* description = @Translation("This fieldgroup renders the inner content in a HTML element with classes and attributes."),
* supported_contexts = {
* "form",
* "view",
* }
* )
*/
class HtmlElement extends FieldGroupFormatterBase {
/**
* {@inheritdoc}
*/
public function preRender(&$element, $rendering_object) {
parent::preRender($element, $rendering_object);
$element_attributes = new Attribute();
if ($this->getSetting('attributes')) {
// This regex split the attributes string so that we can pass that
// later to drupal_attributes().
preg_match_all('/([^\s=]+)="([^"]+)"/', $this->getSetting('attributes'), $matches);
// Put the attribute and the value together.
foreach ($matches[1] as $key => $attribute) {
$element_attributes[$attribute] = $matches[2][$key];
}
}
// Add the id to the attributes array.
if ($this->getSetting('id')) {
$element_attributes['id'] = Html::getId($this->getSetting('id'));
}
// Add the classes to the attributes array.
$classes = $this->getClasses();
if (!empty($classes)) {
if (!isset($element_attributes['class'])) {
$element_attributes['class'] = array();
}
// If user also entered class in the attributes textfield, force it to an array.
else {
$element_attributes['class'] = array($element_attributes['class']);
}
$element_attributes['class'] = array_merge($classes, $element_attributes['class']->value());
}
$element['#effect'] = $this->getSetting('effect');
$element['#speed'] = $this->getSetting('speed');
$element['#type'] = 'field_group_html_element';
$element['#wrapper_element'] = $this->getSetting('element');
$element['#attributes'] = $element_attributes;
if ($this->getSetting('show_label')) {
$element['#title_element'] = $this->getSetting('label_element');
$element['#title'] = Html::escape($this->t($this->getLabel()));
}
$form_state = new FormState();
\Drupal\field_group\Element\HtmlElement::processHtmlElement($element, $form_state);
if ($this->getSetting('required_fields')) {
$element['#attached']['library'][] = 'field_group/formatter.html_element';
$element['#attached']['library'][] = 'field_group/core';
}
}
/**
* {@inheritdoc}
*/
public function settingsForm() {
$form = parent::settingsForm();
$form['element'] = array(
'#title' => $this->t('Element'),
'#type' => 'textfield',
'#default_value' => $this->getSetting('element'),
'#description' => $this->t('E.g. div, section, aside etc.'),
'#weight' => 1,
);
$form['show_label'] = array(
'#title' => $this->t('Show label'),
'#type' => 'select',
'#options' => array(0 => $this->t('No'), 1 => $this->t('Yes')),
'#default_value' => $this->getSetting('show_label'),
'#weight' => 2,
'#attributes' => array(
'data-fieldgroup-selector' => 'show_label'
),
);
$form['label_element'] = array(
'#title' => $this->t('Label element'),
'#type' => 'textfield',
'#default_value' => $this->getSetting('label_element'),
'#weight' => 3,
'#states' => array(
'visible' => array(
':input[data-fieldgroup-selector="show_label"]' => array('value' => 1),
),
),
);
if ($this->context == 'form') {
$form['required_fields'] = array(
'#title' => $this->t('Mark group as required if it contains required fields.'),
'#type' => 'checkbox',
'#default_value' => $this->getSetting('required_fields'),
'#weight' => 4,
);
}
$form['attributes'] = array(
'#title' => $this->t('Attributes'),
'#type' => 'textfield',
'#default_value' => $this->getSetting('attributes'),
'#description' => $this->t('E.g. name="anchor"'),
'#weight' => 5,
);
$form['effect'] = array(
'#title' => $this->t('Effect'),
'#type' => 'select',
'#options' => array(
'none' => $this->t('None'),
'collapsible' => $this->t('Collapsible'),
'blind' => $this->t('Blind')
),
'#default_value' => $this->getSetting('effect'),
'#weight' => 6,
'#attributes' => array(
'data-fieldgroup-selector' => 'effect'
),
);
$form['speed'] = array(
'#title' => $this->t('Speed'),
'#type' => 'select',
'#options' => array('slow' => $this->t('Slow'), 'fast' => $this->t('Fast')),
'#default_value' => $this->getSetting('speed'),
'#weight' => 7,
'#states' => array(
'!visible' => array(
':input[data-fieldgroup-selector="effect"]' => array('value' => 'none'),
),
),
);
return $form;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = parent::settingsSummary();
$summary[] = $this->t('Element: @element',
array('@element' => $this->getSetting('element'))
);
if ($this->getSetting('show_label')) {
$summary[] = $this->t('Label element: @element',
array('@element' => $this->getSetting('label_element'))
);
}
if ($this->getSetting('attributes')) {
$summary[] = $this->t('Attributes: @attributes',
array('@attributes' => $this->getSetting('attributes'))
);
}
if ($this->getSetting('required_fields')) {
$summary[] = $this->t('Mark as required');
}
return $summary;
}
/**
* {@inheritdoc}
*/
public static function defaultContextSettings($context) {
$defaults = array(
'element' => 'div',
'show_label' => 0,
'label_element' => 'h3',
'effect' => 'none',
'speed' => 'fast',
'attributes' => '',
) + parent::defaultSettings($context);
if ($context == 'form') {
$defaults['required_fields'] = 1;
}
return $defaults;
}
}
@@ -0,0 +1,123 @@
<?php
namespace Drupal\field_group\Plugin\field_group\FieldGroupFormatter;
use Drupal\Component\Utility\Html;
use Drupal\field_group\FieldGroupFormatterBase;
/**
* Plugin implementation of the 'tab' formatter.
*
* @FieldGroupFormatter(
* id = "tab",
* label = @Translation("Tab"),
* description = @Translation("This fieldgroup renders the content as a tab."),
* format_types = {
* "open",
* "closed",
* },
* supported_contexts = {
* "form",
* "view",
* },
* )
*/
class Tab extends FieldGroupFormatterBase {
/**
* {@inheritdoc}
*/
public function preRender(&$element, $rendering_object) {
parent::preRender($element, $rendering_object);
$add = array(
'#type' => 'details',
'#title' => Html::escape($this->t($this->getLabel())),
'#description' => $this->getSetting('description'),
);
if ($this->getSetting('id')) {
$add['#id'] = Html::getId($this->getSetting('id'));
}
else {
$add['#id'] = Html::getId('edit-' . $this->group->group_name);
}
$classes = $this->getClasses();
if (!empty($classes)) {
$element += array(
'#attributes' => array('class' => $classes),
);
}
if ($this->getSetting('formatter') == 'open') {
$element['#open'] = TRUE;
}
// Front-end and back-end on configuration will lead
// to vertical tabs nested in a separate vertical group.
if (!empty($this->group->parent_name)) {
$add['#group'] = $this->group->parent_name;
$add['#parents'] = array($add['#group']);
}
if ($this->getSetting('required_fields')) {
$element['#attached']['library'][] = 'field_group/formatter.tabs';
$element['#attached']['library'][] = 'field_group/core';
}
$element += $add;
}
/**
* {@inheritdoc}
*/
public function settingsForm() {
$form = parent::settingsForm();
$form['formatter'] = array(
'#title' => $this->t('Default state'),
'#type' => 'select',
'#options' => array_combine($this->pluginDefinition['format_types'], $this->pluginDefinition['format_types']),
'#default_value' => $this->getSetting('formatter'),
'#weight' => -4,
);
$form['description'] = array(
'#title' => $this->t('Description'),
'#type' => 'textarea',
'#default_value' => $this->getSetting('description'),
'#weight' => -4,
);
if ($this->context == 'form') {
$form['required_fields'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Mark group as required if it contains required fields.'),
'#default_value' => $this->getSetting('required_fields'),
'#weight' => 2,
);
}
return $form;
}
/**
* {@inheritdoc}
*/
public static function defaultContextSettings($context) {
$defaults = array(
'formatter' => 'closed',
'description' => '',
) + parent::defaultSettings($context);
if ($context == 'form') {
$defaults['required_fields'] = 1;
}
return $defaults;
}
}
@@ -0,0 +1,139 @@
<?php
namespace Drupal\field_group\Plugin\field_group\FieldGroupFormatter;
use Drupal\Component\Utility\Html;
use Drupal\Core\Form\FormState;
use Drupal\Core\Render\Element;
use Drupal\Core\Render\Element\VerticalTabs;
use Drupal\field_group\Element\HorizontalTabs;
use Drupal\field_group\FieldGroupFormatterBase;
/**
* Plugin implementation of the 'horizontal_tabs' formatter.
*
* @FieldGroupFormatter(
* id = "tabs",
* label = @Translation("Tabs"),
* description = @Translation("This fieldgroup renders child groups in its own tabs wrapper."),
* supported_contexts = {
* "form",
* "view",
* }
* )
*/
class Tabs extends FieldGroupFormatterBase {
/**
* {@inheritdoc}
*/
public function preRender(&$element, $rendering_object) {
parent::preRender($element, $rendering_object);
$element += array(
'#prefix' => '<div class=" ' . implode(' ' , $this->getClasses()) . '">',
'#suffix' => '</div>',
'#tree' => TRUE,
'#parents' => array($this->group->group_name),
'#default_tab' => '',
);
if ($this->getSetting('id')) {
$element['#id'] = Html::getId($this->getSetting('id'));
}
// By default tabs don't have titles but you can override it in the theme.
if ($this->getLabel()) {
$element['#title'] = Html::escape($this->getLabel());
}
$form_state = new FormState();
if ($this->getSetting('direction') == 'vertical') {
$element += array(
'#type' => 'vertical_tabs',
'#theme_wrappers' => array('vertical_tabs'),
);
$complete_form = array();
$element = VerticalTabs::processVerticalTabs($element, $form_state, $complete_form);
}
else {
$element += array(
'#type' => 'horizontal_tabs',
'#theme_wrappers' => array('horizontal_tabs'),
);
$on_form = $this->context == 'form';
$element = HorizontalTabs::processHorizontalTabs($element, $form_state, $on_form);
}
// Make sure the group has 1 child. This is needed to succeed at form_pre_render_vertical_tabs().
// Skipping this would force us to move all child groups to this array, making it an un-nestable.
$element['group']['#groups'][$this->group->group_name] = array(0 => array());
$element['group']['#groups'][$this->group->group_name]['#group_exists'] = TRUE;
// Search for a tab that was marked as open. First one wins.
foreach (Element::children($element) as $tab_name) {
if (!empty($element[$tab_name]['#open'])) {
$element[$this->group->group_name . '__active_tab']['#default_value'] = $tab_name;
break;
}
}
}
/**
* {@inheritdoc}
*/
public function settingsForm() {
$form = parent::settingsForm();
$form['direction'] = array(
'#title' => $this->t('Direction'),
'#type' => 'select',
'#options' => array(
'vertical' => $this->t('Vertical'),
'horizontal' => $this->t('Horizontal'),
),
'#default_value' => $this->getSetting('direction'),
'#weight' => 1,
);
return $form;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = parent::settingsSummary();
$summary[] = $this->t('Direction: @direction',
array('@direction' => $this->getSetting('direction'))
);
return $summary;
}
/**
* {@inheritdoc}
*/
public static function defaultContextSettings($context) {
return array(
'direction' => 'vertical',
) + parent::defaultContextSettings($context);
}
/**
* {@inheritdoc}
*/
public function getClasses() {
$classes = parent::getClasses();
$classes[] = 'field-group-' . $this->group->format_type . '-wrapper';
return $classes;
}
}
@@ -0,0 +1,32 @@
<?php
namespace Drupal\field_group\Routing;
use Drupal\Core\ParamConverter\ParamConverterInterface;
/**
* Parameter converter for upcasting fieldgroup config ids to fieldgroup object.
*/
class FieldGroupConverter implements ParamConverterInterface {
/**
* {@inheritdoc}
*/
public function applies($definition, $name, \Symfony\Component\Routing\Route $route) {
return isset($definition['type']) && $definition['type'] == 'field_group';
}
/**
* {@inheritdoc}
*/
public function convert($value, $definition, $name, array $defaults) {
$identifiers = explode('.', $value);
if (count($identifiers) != 5) {
return;
}
return field_group_load_field_group($identifiers[4], $identifiers[0], $identifiers[1], $identifiers[2], $identifiers[3]);
}
}
@@ -0,0 +1,161 @@
<?php
namespace Drupal\field_group\Routing;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Routing\RouteSubscriberBase;
use Drupal\Core\Routing\RoutingEvents;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* Subscriber for Field group routes.
*/
class RouteSubscriber extends RouteSubscriberBase {
/**
* The entity type manager
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $manager;
/**
* Constructs a RouteSubscriber object.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $manager
* The entity type manager.
*/
public function __construct(EntityManagerInterface $manager) {
$this->manager = $manager;
}
/**
* {@inheritdoc}
*/
protected function alterRoutes(RouteCollection $collection) {
// Create fieldgroup routes for every entity.
foreach ($this->manager->getDefinitions() as $entity_type_id => $entity_type) {
$defaults = array();
if ($route_name = $entity_type->get('field_ui_base_route')) {
// Try to get the route from the current collection.
if (!$entity_route = $collection->get($route_name)) {
continue;
}
$path = $entity_route->getPath();
$options = $entity_route->getOptions();
// Special parameter used to easily recognize all Field UI routes.
$options['_field_ui'] = TRUE;
if (($bundle_entity_type = $entity_type->getBundleEntityType()) && $bundle_entity_type !== 'bundle') {
$options['parameters'][$entity_type->getBundleEntityType()] = array(
'type' => 'entity:' . $entity_type->getBundleEntityType(),
);
}
$options['parameters']['field_group'] = array(
'type' => 'field_group',
'entity_type' => $entity_type->getBundleEntityType(),
);
$defaults_delete = [
'entity_type_id' => $entity_type_id,
'_form' => '\Drupal\field_group\Form\FieldGroupDeleteForm',
];
$defaults_add = [
'entity_type_id' => $entity_type_id,
'_form' => '\Drupal\field_group\Form\FieldGroupAddForm',
'_title' => 'Add group',
];
// If the entity type has no bundles and it doesn't use {bundle} in its
// admin path, use the entity type.
if (strpos($path, '{bundle}') === FALSE) {
$defaults_add['bundle'] = !$entity_type->hasKey('bundle') ? $entity_type_id : '';
$defaults_delete['bundle'] = $defaults_add['bundle'];
}
// Routes to delete field groups.
$route = new Route(
"$path/form-display/{field_group_name}/delete",
['context' => 'form'] + $defaults_delete,
array('_permission' => 'administer ' . $entity_type_id . ' form display'),
$options
);
$collection->add("field_ui.field_group_delete_$entity_type_id.form_display", $route);
$route = new Route(
"$path/form-display/{form_mode_name}/{field_group_name}/delete",
['context' => 'form'] + $defaults_delete,
array('_permission' => 'administer ' . $entity_type_id . ' form display'),
$options
);
$collection->add("field_ui.field_group_delete_$entity_type_id.form_display.form_mode", $route);
$route = new Route(
"$path/display/{field_group_name}/delete",
['context' => 'view'] + $defaults_delete,
array('_permission' => 'administer ' . $entity_type_id . ' display'),
$options
);
$collection->add("field_ui.field_group_delete_$entity_type_id.display", $route);
$route = new Route(
"$path/display/{view_mode_name}/{field_group_name}/delete",
['context' => 'view'] + $defaults_delete,
array('_permission' => 'administer ' . $entity_type_id . ' display'),
$options
);
$collection->add("field_ui.field_group_delete_$entity_type_id.display.view_mode", $route);
// Routes to add field groups.
$route = new Route(
"$path/form-display/add-group",
['context' => 'form'] + $defaults_add,
array('_permission' => 'administer ' . $entity_type_id . ' form display'),
$options
);
$collection->add("field_ui.field_group_add_$entity_type_id.form_display", $route);
$route = new Route(
"$path/form-display/{form_mode_name}/add-group",
['context' => 'form'] + $defaults_add,
array('_permission' => 'administer ' . $entity_type_id . ' form display'),
$options
);
$collection->add("field_ui.field_group_add_$entity_type_id.form_display.form_mode", $route);
$route = new Route(
"$path/display/add-group",
['context' => 'view'] + $defaults_add,
array('_permission' => 'administer ' . $entity_type_id . ' display'),
$options
);
$collection->add("field_ui.field_group_add_$entity_type_id.display", $route);
$route = new Route(
"$path/display/{view_mode_name}/add-group",
['context' => 'view'] + $defaults_add,
array('_permission' => 'administer ' . $entity_type_id . ' display'),
$options
);
$collection->add("field_ui.field_group_add_$entity_type_id.display.view_mode", $route);
}
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
//$events = parent::getSubscribedEvents();
// Come after field_ui, config_translation.
$events[RoutingEvents::ALTER] = array('onAlterRoutes', -210);
return $events;
}
}
@@ -0,0 +1,329 @@
<?php
namespace Drupal\field_group\Tests;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\simpletest\WebTestBase;
/**
* Tests for displaying entities.
*
* @group field_group
*/
class EntityDisplayTest extends WebTestBase {
use FieldGroupTestTrait;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('node', 'field_test', 'field_ui', 'field_group', 'field_group_test');
function setUp() {
parent::setUp();
// Create test user.
$admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer node fields', 'administer node form display', 'administer node display', 'bypass node access'));
$this->drupalLogin($admin_user);
// Create content type, with underscores.
$type_name = strtolower($this->randomMachineName(8)) . '_test';
$type = $this->drupalCreateContentType(array('name' => $type_name, 'type' => $type_name));
$this->type = $type->id();
$display = entity_get_display('node', $type_name, 'default');
// Create a node.
$node_values = array('type' => $type_name);
// Create test fields.
$test_fields = array('field_test', 'field_test_2', 'field_no_access');
foreach ($test_fields as $field_name) {
$field_storage = FieldStorageConfig::create([
'field_name' => $field_name,
'entity_type' => 'node',
'type' => 'test_field',
]);
$field_storage->save();
$instance = FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => $type_name,
'label' => $this->randomMachineName(),
]);
$instance->save();
// Assign a test value for the field.
$node_values[$field_name][0]['value'] = mt_rand(1, 127);
// Set the field visible on the display object.
$display_options = array(
'label' => 'above',
'type' => 'field_test_default',
'settings' => array(
'test_formatter_setting' => $this->randomMachineName(),
),
);
$display->setComponent($field_name, $display_options);
}
// Save display + create node.
$display->save();
$this->node = $this->drupalCreateNode($node_values);
}
/**
* Test if an fieldgroup that only contains fields
* that has no access is not shown.
*/
function testFieldAccess() {
$data = array(
'label' => 'Wrapper',
'children' => array(
0 => 'field_no_access',
),
'format_type' => 'html_element',
'format_settings' => array(
'element' => 'div',
'id' => 'wrapper-id',
),
);
$this->createGroup('node', $this->type, 'view', 'default', $data);
$this->drupalGet('node/' . $this->node->id());
// Test if group is not shown.
$this->assertNoFieldByXPath("//div[contains(@id, 'wrapper-id')]", NULL, t('Div that contains fields with no access is not shown.'));
}
/**
* Test the html element formatter.
*/
function testHtmlElement() {
$data = array(
'weight' => '1',
'children' => array(
0 => 'field_test',
1 => 'body',
),
'label' => 'Link',
'format_type' => 'html_element',
'format_settings' => array(
'label' => 'Link',
'element' => 'div',
'id' => 'wrapper-id',
'classes' => 'test-class',
),
);
$group = $this->createGroup('node', $this->type, 'view', 'default', $data);
//$groups = field_group_info_groups('node', 'article', 'view', 'default', TRUE);
$this->drupalGet('node/' . $this->node->id());
// Test group ids and classes.
$this->assertFieldByXPath("//div[contains(@id, 'wrapper-id')]", NULL, t('Wrapper id set on wrapper div'));
$this->assertFieldByXPath("//div[contains(@class, 'test-class')]", NULL, t('Test class set on wrapper div') . 'class="' . $group->group_name . ' test-class');
// Test group label.
$this->assertNoRaw('<h3><span>' . $data['label'] . '</span></h3>', t('Label is not shown'));
// Set show label to true.
$group->format_settings['show_label'] = TRUE;
$group->format_settings['label_element'] = 'h3';
field_group_group_save($group);
$this->drupalGet('node/' . $this->node->id());
$this->assertRaw('<h3>' . $data['label'] . '</h3>', t('Label is shown'));
// Change to collapsible with blink effect.
$group->format_settings['effect'] = 'blink';
$group->format_settings['speed'] = 'fast';
field_group_group_save($group);
$this->drupalGet('node/' . $this->node->id());
$this->assertFieldByXPath("//div[contains(@class, 'speed-fast')]", NULL, t('Speed class is set'));
$this->assertFieldByXPath("//div[contains(@class, 'effect-blink')]", NULL, t('Effect class is set'));
}
/**
* Test the fieldset formatter.
*/
function testFieldset() {
$data = array(
'weight' => '1',
'children' => array(
0 => 'field_test',
1 => 'body',
),
'label' => 'Test Fieldset',
'format_type' => 'fieldset',
'format_settings' => array(
'id' => 'fieldset-id',
'classes' => 'test-class',
'description' => 'test description',
),
);
$group = $this->createGroup('node', $this->type, 'view', 'default', $data);
$this->drupalGet('node/' . $this->node->id());
// Test group ids and classes.
$this->assertFieldByXPath("//fieldset[contains(@id, 'fieldset-id')]", NULL, t('Correct id set on the fieldset'));
$this->assertFieldByXPath("//fieldset[contains(@class, 'test-class')]", NULL, t('Test class set on the fieldset'));
}
/**
* Test the tabs formatter.
*/
function testTabs() {
$data = array(
'label' => 'Tab 1',
'weight' => '1',
'children' => array(
0 => 'field_test',
),
'format_type' => 'tab',
'format_settings' => array(
'label' => 'Tab 1',
'classes' => 'test-class',
'description' => '',
'formatter' => 'open',
),
);
$first_tab = $this->createGroup('node', $this->type, 'view', 'default', $data);
$data = array(
'label' => 'Tab 2',
'weight' => '1',
'children' => array(
0 => 'field_test_2',
),
'format_type' => 'tab',
'format_settings' => array(
'label' => 'Tab 1',
'classes' => 'test-class-2',
'description' => 'description of second tab',
'formatter' => 'closed',
),
);
$second_tab = $this->createGroup('node', $this->type, 'view', 'default', $data);
$data = array(
'label' => 'Tabs',
'weight' => '1',
'children' => array(
0 => $first_tab->group_name,
1 => $second_tab->group_name,
),
'format_type' => 'tabs',
'format_settings' => array(
'direction' => 'vertical',
'label' => 'Tab 1',
'classes' => 'test-class-wrapper',
),
);
$tabs_group = $this->createGroup('node', $this->type, 'view', 'default', $data);
$this->drupalGet('node/' . $this->node->id());
// Test properties.
$this->assertFieldByXPath("//div[contains(@class, 'test-class-wrapper')]", NULL, t('Test class set on tabs wrapper'));
$this->assertFieldByXPath("//details[contains(@class, 'test-class-2')]", NULL, t('Test class set on second tab'));
$this->assertRaw('<div class="details-description">description of second tab</div>', t('Description of tab is shown'));
// Test if correctly nested.
$this->assertFieldByXPath("//div[contains(@class, 'test-class-wrapper')]//details[contains(@class, 'test-class')]", NULL, 'First tab is displayed as child of the wrapper.');
$this->assertFieldByXPath("//div[contains(@class, 'test-class-wrapper')]//details[contains(@class, 'test-class-2')]", NULL, 'Second tab is displayed as child of the wrapper.');
// Test if it's a vertical tab.
$this->assertFieldByXPath('//div[@data-vertical-tabs-panes=""]', NULL, 'Tabs are shown vertical.');
// Switch to horizontal
$tabs_group->format_settings['direction'] = 'horizontal';
field_group_group_save($tabs_group);
$this->drupalGet('node/' . $this->node->id());
// Test if it's a horizontal tab.
$this->assertFieldByXPath('//div[@data-horizontal-tabs-panes=""]', NULL, 'Tabs are shown horizontal.');
}
/**
* Test the accordion formatter.
*/
function testAccordion() {
$data = array(
'label' => 'Accordion item 1',
'weight' => '1',
'children' => array(
0 => 'field_test',
),
'format_type' => 'accordion_item',
'format_settings' => array(
'label' => 'Accordion item 1',
'classes' => 'test-class',
'formatter' => 'closed',
),
);
$first_item = $this->createGroup('node', $this->type, 'view', 'default', $data);
$first_item_id = 'node_article_full_' . $first_item->group_name;
$data = array(
'label' => 'Accordion item 2',
'weight' => '1',
'children' => array(
0 => 'field_test_2',
),
'format_type' => 'accordion_item',
'format_settings' => array(
'label' => 'Tab 2',
'classes' => 'test-class-2',
'formatter' => 'open',
),
);
$second_item = $this->createGroup('node', $this->type, 'view', 'default', $data);
$second_item_id = 'node_article_full_' . $second_item->group_name;
$data = array(
'label' => 'Accordion',
'weight' => '1',
'children' => array(
0 => $first_item->group_name,
1 => $second_item->group_name,
),
'format_type' => 'accordion',
'format_settings' => array(
'label' => 'Tab 1',
'classes' => 'test-class-wrapper',
'effect' => 'bounceslide'
),
);
$accordion = $this->createGroup('node', $this->type, 'view', 'default', $data);
$this->drupalGet('node/' . $this->node->id());
// Test properties.
$this->assertFieldByXPath("//div[contains(@class, 'test-class-wrapper')]", NULL, t('Test class set on tabs wrapper'));
$this->assertFieldByXPath("//div[contains(@class, 'effect-bounceslide')]", NULL, t('Correct effect is set on the accordion'));
$this->assertFieldByXPath("//div[contains(@class, 'test-class')]", NULL, t('Accordion item with test-class is shown'));
$this->assertFieldByXPath("//div[contains(@class, 'test-class-2')]", NULL, t('Accordion item with test-class-2 is shown'));
$this->assertFieldByXPath("//h3[contains(@class, 'field-group-accordion-active')]", NULL, t('Accordion item 2 was set active'));
// Test if correctly nested
$this->assertFieldByXPath("//div[contains(@class, 'test-class-wrapper')]//div[contains(@class, 'test-class')]", NULL, 'First item is displayed as child of the wrapper.');
$this->assertFieldByXPath("//div[contains(@class, 'test-class-wrapper')]//div[contains(@class, 'test-class-2')]", NULL, 'Second item is displayed as child of the wrapper.');
}
}
@@ -0,0 +1,46 @@
<?php
namespace Drupal\field_group\Tests;
use Drupal\Component\Utility\Unicode;
/**
* Provides common functionality for the FieldGroup test classes.
*/
trait FieldGroupTestTrait {
/**
* Create a new group.
* @param array $data
* Data for the field group.
*/
function createGroup($entity_type, $bundle, $context, $mode, array $data) {
if (!isset($data['format_settings'])) {
$data['format_settings'] = array();
}
$data['format_settings'] += _field_group_get_default_formatter_settings($data['format_type'], $context);
$group_name = 'group_' . Unicode::strtolower($this->randomMachineName());
$field_group = (object) array(
'group_name' => $group_name,
'entity_type' => $entity_type,
'bundle' => $bundle,
'mode' => $mode,
'context' => $context,
'children' => isset($data['children']) ? $data['children'] : array(),
'parent_name' => isset($data['parent']) ? $data['parent'] : '',
'weight' => isset($data['weight']) ? $data['weight'] : 0,
'label' => isset($data['label']) ? $data['label'] : $this->randomString(8),
'format_type' => $data['format_type'],
'format_settings' => $data['format_settings'],
);
field_group_group_save($field_group);
return $field_group;
}
}
@@ -0,0 +1,24 @@
<?php
namespace Drupal\field_group\Tests;
use Drupal\Core\Url;
use Drupal\simpletest\WebTestBase;
/**
* Test field_group without field_ui.
*
* @group field_group
*/
class FieldGroupWithoutFieldUiTest extends WebTestBase {
protected static $modules = ['field_group', 'block'];
public function testLocalActions() {
// Local actions of field_group should not depend on field_ui
// @see https://www.drupal.org/node/2719569
$this->placeBlock('local_actions_block', ['id' => 'local_actions_block']);
$this->drupalGet(Url::fromRoute('user.login'));
}
}
@@ -0,0 +1,148 @@
<?php
namespace Drupal\field_group\Tests;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\simpletest\WebTestBase;
/**
* Tests for managing display of entities.
*
* @group field_group
*/
class ManageDisplayTest extends WebTestBase {
use FieldGroupTestTrait;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('node', 'field_ui', 'field_group');
function setUp() {
parent::setUp();
// Create test user.
$admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer node fields', 'administer node form display', 'administer node display', 'bypass node access'));
$this->drupalLogin($admin_user);
// Create content type, with underscores.
$type_name = Unicode::strtolower($this->randomMachineName(8)) . '_test';
$type = $this->drupalCreateContentType(array('name' => $type_name, 'type' => $type_name));
$this->type = $type->id();
}
/**
* Test the creation a group on the article content type.
*/
function testCreateGroup() {
// Create random group name.
$this->group_label = $this->randomString(8);
$this->group_name_input = Unicode::strtolower($this->randomMachineName());
$this->group_name = 'group_' . $this->group_name_input;
$this->group_formatter = 'details';
// Setup new group.
$group = array(
'group_formatter' => $this->group_formatter,
'label' => $this->group_label,
);
$this->drupalPostForm('admin/structure/types/manage/' . $this->type . '/form-display/add-group', $group, t('Save and continue'));
$this->assertText('Machine-readable name field is required.');
// Setup new group.
$group = array(
'group_formatter' => $this->group_formatter,
'label' => $this->group_label,
'group_name' => $this->group_name_input,
);
// Add new group on the 'Manage form display' page.
$this->drupalPostForm('admin/structure/types/manage/' . $this->type . '/form-display/add-group', $group, t('Save and continue'));
$this->drupalPostForm(NULL, [], t('Create group'));
$this->assertRaw(t('New group %label successfully created.', array('%label' => $this->group_label)), t('Group message displayed on screen.'));
// Test if group is in the $groups array.
$this->group = field_group_load_field_group($this->group_name, 'node', $this->type, 'form', 'default');
$this->assertNotNull($this->group, t('Group was loaded'));
// Add new group on the 'Manage display' page.
$this->drupalPostForm('admin/structure/types/manage/' . $this->type . '/display/add-group', $group, t('Save and continue'));
$this->drupalPostForm(NULL, [], t('Create group'));
$this->assertRaw(t('New group %label successfully created.', array('%label' => $this->group_label)), t('Group message displayed on screen.'));
// Test if group is in the $groups array.
$loaded_group = field_group_load_field_group($this->group_name, 'node', $this->type, 'view', 'default');
$this->assertNotNull($loaded_group, t('Group was loaded'));
}
/**
* Delete a group.
*/
function testDeleteGroup() {
$data = array(
'format_type' => 'fieldset',
'label' => 'testing',
);
$group = $this->createGroup('node', $this->type, 'form', 'default', $data);
$this->drupalPostForm('admin/structure/types/manage/' . $this->type . '/form-display/' . $group->group_name . '/delete', array(), t('Delete'));
$this->assertRaw(t('The group %label has been deleted from the %type content type.', array('%label' => $group->label, '%type' => $this->type)), t('Group removal message displayed on screen.'));
$display = EntityFormDisplay::load($group->entity_type . '.' . $group->bundle . '.' . $group->mode);
$data = $display->getThirdPartySettings('field_group');
// Test that group is not in the $groups array.
\Drupal::entityTypeManager()->getStorage('entity_form_display')->resetCache();
$loaded_group = field_group_load_field_group($group->group_name, 'node', $this->type, 'form', 'default');
$this->assertNull($loaded_group, t('Group not found after deleting'));
$data = array(
'format_type' => 'fieldset',
'label' => 'testing',
);
$group = $this->createGroup('node', $this->type, 'view', 'default', $data);
$this->drupalPostForm('admin/structure/types/manage/' . $this->type . '/display/' . $group->group_name . '/delete', array(), t('Delete'));
$this->assertRaw(t('The group %label has been deleted from the %type content type.', array('%label' => $group->label, '%type' => $this->type)), t('Group removal message displayed on screen.'));
// Test that group is not in the $groups array.
\Drupal::entityTypeManager()->getStorage('entity_view_display')->resetCache();
$loaded_group = field_group_load_field_group($group->group_name, 'node', $this->type, 'view', 'default');
$this->assertNull($loaded_group, t('Group not found after deleting'));
}
/**
* Nest a field underneath a group.
*/
function testNestField() {
$data = array(
'format_type' => 'fieldset',
);
$group = $this->createGroup('node', $this->type, 'form', 'default', $data);
$edit = array(
'fields[body][parent]' => $group->group_name,
);
$this->drupalPostForm('admin/structure/types/manage/' . $this->type . '/form-display', $edit, t('Save'));
$this->assertRaw(t('Your settings have been saved.'), t('Settings saved'));
$group = field_group_load_field_group($group->group_name, 'node', $this->type, 'form', 'default');
$this->assertTrue(in_array('body', $group->children), t('Body is a child of %group', array('%group' => $group->group_name)));
}
}