field_ui.inc 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  1. <?php
  2. /**
  3. * @file
  4. * Field_group.field_ui.inc is a file that contains most functions
  5. * needed on the Fields UI Manage forms (display and fields).
  6. */
  7. use Drupal\Component\Utility\Html;
  8. use Drupal\Core\Form\FormStateInterface;
  9. use Drupal\Core\Render\Element;
  10. use Drupal\field_ui\Form\EntityDisplayFormBase;
  11. /**
  12. * Helper function to get the form parameters to use while
  13. * building the fields and display overview form.
  14. *
  15. * @param $form
  16. *
  17. * @param \Drupal\Core\Entity\EntityDisplayBase $display
  18. *
  19. * @return \stdClass
  20. */
  21. function field_group_field_ui_form_params($form, \Drupal\Core\Entity\EntityDisplayBase $display) {
  22. $params = new stdClass();
  23. $params->entity_type = $display->getTargetEntityTypeId();
  24. $params->bundle = $display->getTargetBundle();
  25. $params->region_callback = 'field_group_display_overview_row_region';
  26. $params->mode = $display->getMode();
  27. $params->context = field_group_get_context_from_display($display);
  28. $params->groups = array();
  29. $params->groups = field_group_info_groups($params->entity_type, $params->bundle, $params->context, $params->mode);
  30. // Gather parenting data.
  31. $params->parents = array();
  32. foreach ($params->groups as $name => $group) {
  33. foreach ($group->children as $child) {
  34. $params->parents[$child] = $name;
  35. }
  36. }
  37. return $params;
  38. }
  39. /**
  40. * Helper function to get context from entity display.
  41. *
  42. * @param \Drupal\Core\Entity\EntityDisplayBase $display
  43. *
  44. * @return string
  45. */
  46. function field_group_get_context_from_display(\Drupal\Core\Entity\EntityDisplayBase $display) {
  47. if ($display instanceof \Drupal\Core\Entity\Display\EntityFormDisplayInterface) {
  48. return 'form';
  49. }
  50. elseif ($display instanceof \Drupal\Core\Entity\Display\EntityViewDisplayInterface) {
  51. return 'view';
  52. }
  53. throw new LogicException('Unknown display object.');
  54. }
  55. /**
  56. * Function to alter the display overview screens.
  57. */
  58. function field_group_field_ui_display_form_alter(&$form, FormStateInterface $form_state) {
  59. // Only start altering the form if we need to.
  60. if (empty($form['#fields']) && empty($form['#extra'])) {
  61. return;
  62. }
  63. $callback_object = $form_state->getBuildInfo()['callback_object'];
  64. if (!$callback_object instanceof EntityDisplayFormBase) {
  65. throw new InvalidArgumentException('Unkown callback object.');
  66. }
  67. $display = $callback_object->getEntity();
  68. $params = field_group_field_ui_form_params($form, $display);
  69. $form['#fieldgroups'] = array_keys($params->groups);
  70. $form['#context'] = $display;
  71. $table = &$form['fields'];
  72. $form_state_values = $form_state->getValues();
  73. $field_group_form_state = $form_state->get('field_group');
  74. if ($field_group_form_state == NULL) {
  75. $field_group_form_state = $params->groups;
  76. }
  77. $table['#parent_options'] = [];
  78. // Extend available parenting options.
  79. foreach ($field_group_form_state as $name => $group) {
  80. $table['#parent_options'][$name] = $group->label;
  81. }
  82. // Update existing rows accordingly to the parents.
  83. foreach (Element::children($table) as $name) {
  84. $table[$name]['parent_wrapper']['parent']['#options'] = $table['#parent_options'];
  85. // Inherit the value of the parent when default value is empty.
  86. if (empty($table[$name]['parent_wrapper']['parent']['#default_value'])) {
  87. $table[$name]['parent_wrapper']['parent']['#default_value'] = isset($params->parents[$name]) ? $params->parents[$name] : '';
  88. }
  89. }
  90. $formatter_options = field_group_field_formatter_options($params->context);
  91. $refresh_rows = isset($form_state_values['refresh_rows']) ? $form_state_values['refresh_rows'] : (isset($form_state->getUserInput()['refresh_rows']) ? $form_state->getUserInput()['refresh_rows'] : NULL);
  92. // Create the group rows and check actions.
  93. foreach ($form['#fieldgroups'] as $name) {
  94. $group = &$field_group_form_state[$name];
  95. // Check the currently selected formatter, and merge persisted values for
  96. // formatter settings for the group.
  97. // This needs to be done first, so all fields are updated before creating form elements.
  98. if (isset($refresh_rows) && $refresh_rows == $name) {
  99. $settings = isset($form_state_values['fields'][$name]) ? $form_state_values['fields'][$name] : (isset($form_state->getUserInput()['fields'][$name]) ? $form_state->getUserInput()['fields'][$name] : NULL);
  100. if (array_key_exists('settings_edit', $settings)) {
  101. $group = $field_group_form_state[$name];
  102. }
  103. field_group_formatter_row_update($group, $settings);
  104. }
  105. // Save the group when the configuration is submitted.
  106. if (!empty($form_state_values[$name . '_plugin_settings_update'])) {
  107. field_group_formatter_settings_update($group, $form_state_values['fields'][$name]);
  108. }
  109. // After all updates are finished, let the form_state know.
  110. $field_group_form_state[$name] = $group;
  111. $settings = field_group_format_settings_form($group, $form, $form_state);
  112. $id = strtr($name, '_', '-');
  113. $js_rows_data[$id] = array('type' => 'group', 'name' => $name);
  114. // A group cannot be selected as its own parent.
  115. $parent_options = $table['#parent_options'];
  116. unset($parent_options[$name]);
  117. $table[$name] = array(
  118. '#attributes' => array('class' => array('draggable', 'field-group'), 'id' => $id),
  119. '#row_type' => 'group',
  120. '#region_callback' => $params->region_callback,
  121. '#js_settings' => array('rowHandler' => 'group'),
  122. 'human_name' => array(
  123. '#markup' => Html::escape(t($group->label)),
  124. '#prefix' => '<span class="group-label">',
  125. '#suffix' => '</span>',
  126. ),
  127. 'weight' => array(
  128. '#type' => 'textfield',
  129. '#default_value' => $group->weight,
  130. '#size' => 3,
  131. '#attributes' => array('class' => array('field-weight')),
  132. ),
  133. 'parent_wrapper' => array(
  134. 'parent' => array(
  135. '#type' => 'select',
  136. '#options' => $parent_options,
  137. '#empty_value' => '',
  138. '#default_value' => isset($params->parents[$name]) ? $params->parents[$name] : '',
  139. '#attributes' => array('class' => array('field-parent')),
  140. '#parents' => array('fields', $name, 'parent'),
  141. ),
  142. 'hidden_name' => array(
  143. '#type' => 'hidden',
  144. '#default_value' => $name,
  145. '#attributes' => array('class' => array('field-name')),
  146. ),
  147. ),
  148. );
  149. // For view settings. Add a spacer cell. We can't use colspan because of the javascript .
  150. if ($params->context == 'view') {
  151. $table[$name] += array(
  152. 'spacer' => array(
  153. '#markup' => '&nbsp;'
  154. )
  155. );
  156. }
  157. $table[$name] += array(
  158. 'format' => array(
  159. 'type' => array(
  160. '#type' => 'select',
  161. '#options' => $formatter_options,
  162. '#default_value' => $group->format_type,
  163. '#attributes' => array('class' => array('field-group-type')),
  164. ),
  165. ),
  166. );
  167. $base_button = array(
  168. '#submit' => array(
  169. array($form_state->getBuildInfo()['callback_object'], 'multistepSubmit')
  170. ),
  171. '#ajax' => array(
  172. 'callback' => array($form_state->getBuildInfo()['callback_object'], 'multistepAjax'),
  173. 'wrapper' => 'field-display-overview-wrapper',
  174. 'effect' => 'fade',
  175. ),
  176. '#field_name' => $name,
  177. );
  178. if ($form_state->get('plugin_settings_edit') == $name) {
  179. $table[$name]['format']['#cell_attributes'] = array('colspan' => 2);
  180. $table[$name]['format']['format_settings'] = array(
  181. '#type' => 'container',
  182. '#attributes' => array('class' => array('field-plugin-settings-edit-form')),
  183. '#parents' => array('fields', $name, 'settings_edit_form'),
  184. '#weight' => -5,
  185. 'label' => array(
  186. '#markup' => t('Field group format:') . ' <span class="formatter-name">' . $group->format_type . '</span>',
  187. ),
  188. // Create a settings form where hooks can pick in.
  189. 'settings' => $settings,
  190. 'actions' => array(
  191. '#type' => 'actions',
  192. 'save_settings' => $base_button + array(
  193. '#type' => 'submit',
  194. '#name' => $name . '_plugin_settings_update',
  195. '#value' => t('Update'),
  196. '#op' => 'update',
  197. ),
  198. 'cancel_settings' => $base_button + array(
  199. '#type' => 'submit',
  200. '#name' => $name . '_plugin_settings_cancel',
  201. '#value' => t('Cancel'),
  202. '#op' => 'cancel',
  203. // Do not check errors for the 'Cancel' button.
  204. '#limit_validation_errors' => array(),
  205. ),
  206. ),
  207. );
  208. $table[$name]['#attributes']['class'][] = 'field-formatter-settings-editing';
  209. $table[$name]['format']['type']['#attributes']['class'] = array('visually-hidden');
  210. }
  211. else {
  212. // After saving, the settings are updated here aswell. First we create
  213. // the element for the table cell.
  214. $table[$name]['settings_summary'] = array('#markup' => '');
  215. if (!empty($group->format_settings)) {
  216. $table[$name]['settings_summary'] = field_group_format_settings_summary($name, $group);
  217. }
  218. // Add the configure button.
  219. $table[$name]['settings_edit'] = $base_button + array(
  220. '#type' => 'image_button',
  221. '#name' => $name . '_group_settings_edit',
  222. '#src' => 'core/misc/icons/787878/cog.svg',
  223. '#attributes' => array('class' => array('field-plugin-settings-edit'), 'alt' => t('Edit')),
  224. '#op' => 'edit',
  225. // Do not check errors for the 'Edit' button, but make sure we get
  226. // the value of the 'plugin type' select.
  227. '#limit_validation_errors' => array(array('fields', $name, 'type')),
  228. '#prefix' => '<div class="field-plugin-settings-edit-wrapper">',
  229. '#suffix' => '</div>',
  230. );
  231. $delete_route = \Drupal\field_group\FieldgroupUi::getDeleteRoute($group);
  232. $table[$name]['settings_edit']['#suffix'] .= Drupal::l(t('delete'), $delete_route);
  233. }
  234. $form_state->set('field_group', $field_group_form_state);
  235. }
  236. // Additional row: add new group.
  237. $parent_options = $table['#parent_options'];
  238. $form['#attached']['library'][] = 'field_group/field_ui';
  239. array_unshift($form['actions']['submit']['#submit'], 'field_group_field_overview_submit');
  240. // Create the settings for fieldgroup as vertical tabs (merged with DS).
  241. field_group_field_ui_create_vertical_tabs($form, $form_state, $params);
  242. // Show a warning if the user has not set up required containers
  243. if ($form['#fieldgroups']) {
  244. $parent_requirements = array(
  245. 'accordion-item' => array(
  246. 'parent' => 'accordion',
  247. 'message' => 'Each Accordion item element needs to have a parent Accordion group element.',
  248. ),
  249. );
  250. // On display overview tabs need to be checked.
  251. if (field_group_get_context_from_display($display) == 'view') {
  252. $parent_requirements['tab'] = array(
  253. 'parent' => 'tabs',
  254. 'message' => 'Each tab element needs to have a parent tabs group element.',
  255. );
  256. }
  257. foreach ($form['#fieldgroups'] as $group_name) {
  258. $group_check = field_group_load_field_group($group_name, $params->entity_type, $params->bundle, $params->context, $params->mode);
  259. if (isset($parent_requirements[$group_check->format_type])) {
  260. if (!$group_check->parent_name || field_group_load_field_group($group_check->parent_name, $params->entity_type, $params->bundle, $params->context, $params->mode)->format_type != $parent_requirements[$group_check->format_type]['parent']) {
  261. drupal_set_message(t($parent_requirements[$group_check->format_type]['message']), 'warning', FALSE);
  262. }
  263. }
  264. }
  265. }
  266. }
  267. /**
  268. * Create vertical tabs.
  269. */
  270. function field_group_field_ui_create_vertical_tabs(&$form, &$form_state, $params) {
  271. $form_state->set('field_group_params', $params);
  272. $existing_group_config = \Drupal::configFactory()->listAll('field_group.' . $params->entity_type . '.' . $params->bundle);
  273. $displays = array();
  274. foreach ($existing_group_config as $config) {
  275. $group = \Drupal::config($config)->get();
  276. if ($group['context'] == $params->context && $group['mode'] == $params->mode) {
  277. continue;
  278. }
  279. $displays[$group['context'] . '.' . $group['mode']] = $group['context'] . ':' . $group['mode'];
  280. }
  281. // No displays to clone.
  282. if (empty($displays)) {
  283. return;
  284. }
  285. // Add additional settings vertical tab.
  286. if (!isset($form['additional_settings'])) {
  287. $form['additional_settings'] = array(
  288. '#type' => 'vertical_tabs',
  289. '#theme_wrappers' => array('vertical_tabs'),
  290. '#prefix' => '<div>',
  291. '#suffix' => '</div>',
  292. '#tree' => TRUE,
  293. );
  294. }
  295. // Add extra guidelines for webmaster.
  296. $form['field_group'] = array(
  297. '#type' => 'details',
  298. '#group' => 'additional_settings',
  299. '#title' => t('Fieldgroups'),
  300. '#description' => t('<p class="fieldgroup-help">Fields can be dragged into groups with unlimited nesting. Each fieldgroup format comes with a configuration form, specific for that format type.<br />Note that some formats come in pair. These types have a html wrapper to nest its fieldgroup children. E.g. Place accordion items into the accordion, vertical tabs in vertical tab group and horizontal tabs in the horizontal tab group. There is one exception to this rule, you can use a vertical tab without a wrapper when the additional settings tabs are available. E.g. node forms.</p>'),
  301. '#collapsible' => TRUE,
  302. '#open' => TRUE,
  303. );
  304. $form['field_group']['fieldgroup_clone'] = array(
  305. '#title' => t('Select source display'),
  306. '#description' => t('Clone fieldgroups from selected display to the current display'),
  307. '#type' => 'select',
  308. '#options' => $displays,
  309. '#default_value' => 'none'
  310. );
  311. $form['field_group']['fieldgroup_submit'] = array(
  312. '#type' => 'submit',
  313. '#value' => t('Clone'),
  314. '#validate' => array('field_group_field_ui_clone_field_groups_validate'),
  315. '#submit' => array('field_group_field_ui_clone_field_groups')
  316. );
  317. }
  318. /**
  319. * Returns the region to which a row in the 'Manage display' screen belongs.
  320. * @param Array $row A field or field_group row
  321. * @return String the current region.
  322. */
  323. function field_group_display_overview_row_region($row) {
  324. switch ($row['#row_type']) {
  325. case 'group':
  326. return ($row['format']['type']['#value'] == 'hidden' ? 'hidden' : 'content');
  327. }
  328. }
  329. /**
  330. * Submit handler for the overview screens.
  331. * @param Array $form The complete form.
  332. * @param FormStateInterface $form_state The state of the form.
  333. */
  334. function field_group_field_overview_submit($form, FormStateInterface $form_state) {
  335. $form_values = $form_state->getValue('fields');
  336. $fields_added = $form_state->getValue('fields_added');
  337. /**
  338. * @var \Drupal\Core\Entity\EntityDisplayBase $display
  339. */
  340. $display = $form['#context'];
  341. $entity_type = $display->get('targetEntityType');
  342. $bundle = $display->get('bundle');
  343. $mode = $display->get('mode');
  344. $context = field_group_get_context_from_display($display);
  345. // Collect children.
  346. $children = array_fill_keys($form['#fieldgroups'], array());
  347. foreach ($form_values as $name => $value) {
  348. if (!empty($value['parent'])) {
  349. $children[$value['parent']][$name] = $name;
  350. }
  351. }
  352. // Update existing groups.
  353. $groups = field_group_info_groups($entity_type, $bundle, $context, $mode, TRUE);
  354. $field_group_form_state = $form_state->get('field_group');
  355. if (!empty($field_group_form_state)) {
  356. foreach ($form['#fieldgroups'] as $group_name) {
  357. // Only save updated groups.
  358. if (!isset($field_group_form_state[$group_name])) {
  359. continue;
  360. }
  361. $group = $groups[$group_name];
  362. $group->label = $field_group_form_state[$group_name]->label;
  363. $group->children = array_keys($children[$group_name]);
  364. $group->parent_name = $form_values[$group_name]['parent'];
  365. $group->weight = $form_values[$group_name]['weight'];
  366. $old_format_type = $group->format_type;
  367. $group->format_type = isset($form_values[$group_name]['format']['type']) ? $form_values[$group_name]['format']['type'] : 'visible';
  368. if (isset($field_group_form_state[$group_name]->format_settings)) {
  369. $group->format_settings = $field_group_form_state[$group_name]->format_settings;
  370. }
  371. // If the format type is changed, make sure we have all required format settings.
  372. if ($group->format_type != $old_format_type) {
  373. $default_formatter_settings = _field_group_get_default_formatter_settings($group->format_type, $context);
  374. $group->format_settings += $default_formatter_settings;
  375. }
  376. /** @var EntityFormInterface $entity_form */
  377. $entity_form = $form_state->getFormObject();
  378. /** @var EntityDisplayInterface $display */
  379. $display = $entity_form->getEntity();
  380. field_group_group_save($group, $display);
  381. }
  382. }
  383. \Drupal::cache()->invalidate('field_groups');
  384. }
  385. /**
  386. * Creates a form for field_group formatters.
  387. * @param Object $group The FieldGroup object.
  388. */
  389. function field_group_format_settings_form(&$group, $form, $form_state) {
  390. $manager = \Drupal::service('plugin.manager.field_group.formatters');
  391. $plugin = $manager->getInstance(array(
  392. 'format_type' => $group->format_type,
  393. 'configuration' => array('label' => $group->label, 'settings' => $group->format_settings),
  394. 'group' => $group,
  395. ));
  396. return $plugin->settingsForm($form, $form_state);
  397. }
  398. /**
  399. * Update the row so that the group variables are updated.
  400. * The rendering of the elements needs the updated defaults.
  401. * @param Object $group
  402. * @param array $settings
  403. */
  404. function field_group_formatter_row_update(& $group, $settings) {
  405. // if the row has changed formatter type, update the group object
  406. if (!empty($settings['format']['type']) && $settings['format']['type'] != $group->format_type) {
  407. $group->format_type = $settings['format']['type'];
  408. field_group_formatter_settings_update($group, $settings);
  409. }
  410. }
  411. /**
  412. * Update handler for field_group configuration settings.
  413. * @param Object $group The group object
  414. * @param Array $settings Configuration settings
  415. */
  416. function field_group_formatter_settings_update(& $group, $settings) {
  417. // for format changes we load the defaults.
  418. if (empty($settings['settings_edit_form']['settings'])) {
  419. $group->format_settings = _field_group_get_default_formatter_settings($group->format_type, $group->context);
  420. }
  421. else {
  422. $group->format_type = $settings['format']['type'];
  423. $group->label = $settings['settings_edit_form']['settings']['label'];
  424. $group->format_settings = $settings['settings_edit_form']['settings'];
  425. }
  426. }
  427. /**
  428. * Creates a summary for the field format configuration summary.
  429. * @param String $group_name The name of the group
  430. * @param Object $group The group object
  431. * @return Array ready to be rendered.
  432. */
  433. function field_group_format_settings_summary($group_name, $group) {
  434. $manager = \Drupal::service('plugin.manager.field_group.formatters');
  435. $plugin = $manager->getInstance(array(
  436. 'format_type' => $group->format_type,
  437. 'configuration' => array('label' => $group->label, 'settings' => $group->format_settings),
  438. 'group' => $group,
  439. ));
  440. $summary = $plugin->settingsSummary();
  441. return array(
  442. '#markup' => '<div class="field-plugin-summary">' . implode('<br />', $summary) . '</div>',
  443. '#cell_attributes' => array('class' => array('field-plugin-summary-cell')),
  444. );
  445. }
  446. /**
  447. * Validate handler to validate saving existing fieldgroups from one view mode or form to another.
  448. */
  449. function field_group_field_ui_clone_field_groups_validate($form, FormStateInterface $form_state) {
  450. $form_state_values = $form_state->getValues();
  451. $field_group_params = $form_state->get('field_group_params');
  452. list($context, $mode) = explode('.', $form_state_values['fieldgroup_clone']);
  453. $source_groups = field_group_info_groups($field_group_params->entity_type, $field_group_params->bundle, $context, $mode);
  454. // Check for types are not known in current mode.
  455. if ($field_group_params->context != 'form') {
  456. $non_existing_types = [];
  457. }
  458. else {
  459. $non_existing_types = ['html_element'];
  460. }
  461. foreach ($source_groups as $key => $group) {
  462. if (in_array($group->format_type, $non_existing_types)) {
  463. unset($source_groups[$key]);
  464. drupal_set_message(t('Skipping @group because this type does not exist in current mode', array('@group' => $group->label)), 'warning');
  465. }
  466. }
  467. if (empty($source_groups)) {
  468. // Report error found with selection.
  469. $form_state->setErrorByName('additional_settings][fieldgroup_clone', t('No field groups were found in selected view mode.'));
  470. return;
  471. }
  472. $form_state->set('#source_groups', $source_groups);
  473. }
  474. /**
  475. * Submit handler to save existing fieldgroups from one view mode or form to another.
  476. */
  477. function field_group_field_ui_clone_field_groups($form, FormStateInterface $form_state) {
  478. $fields = array_keys($form_state->getValue('fields'));
  479. $source_groups = $form_state->get('#source_groups');
  480. if ($source_groups) {
  481. $field_group_params = $form_state->get('field_group_params');
  482. foreach ($source_groups as $source_group) {
  483. if (in_array($source_group->group_name, $fields)) {
  484. drupal_set_message(t('Fieldgroup @group is not cloned since a group already exists with the same name.', array('@group' => $source_group->group_name)), 'warning');
  485. continue;
  486. }
  487. $source_group->context = $field_group_params->context;
  488. $source_group->mode = $field_group_params->mode;
  489. $source_group->children = array();
  490. field_group_group_save($source_group);
  491. drupal_set_message(t('Fieldgroup @group cloned successfully.', array('@group' => $source_group->group_name)));
  492. }
  493. }
  494. }