diff --git a/gin.api.php b/gin.api.php new file mode 100644 index 0000000000000000000000000000000000000000..fda7f5938acde16977366f599014e3d0ff470db4 --- /dev/null +++ b/gin.api.php @@ -0,0 +1,58 @@ +<?php + +/** + * @file + * Hooks for gin theme. + */ + +/** + * @addtogroup hooks + * @{ + */ + +/** + * Register routes to apply Gin’s content edit form layout. + * + * Leverage this hook to achieve a consistent user interface layout on + * administrative edit forms, similar to the node edit forms. Any module + * providing a custom entity type or form mode may wish to implement this + * hook for their form routes. Please note that not every content entity + * form route should enable the Gin edit form layout, for example the + * delete entity form does not need it. + * + * @return array + * An array of route names. + * + * @see GinContentFormHelper->isContentForm() + * @see hook_gin_content_form_routes_alter() + */ +function hook_gin_content_form_routes() { + return [ + // Layout a custom node form. + 'entity.node.my_custom_form', + + // Layout a custom entity type edit form. + 'entity.my_type.edit_form', + ]; +} + +/** + * Alter the registered routes to enable or disable Gin’s edit form layout. + * + * @param array $routes + * The list of routes. + * + * @return array + * An array of route names. + * + * @see GinContentFormHelper->isContentForm() + * @see hook_gin_content_form_routes() + */ +function hook_gin_content_form_routes_alter(array &$routes) { + // Example: disable Gin edit form layout customizations for an entity type. + $routes = array_diff($routes, ['entity.my_type.edit_form']); +} + +/** + * @} End of "addtogroup hooks". + */ diff --git a/includes/form.theme b/includes/form.theme index 85ae10f8ef8e0a757aa72951faf2ff6e07c837b2..9e5446b81593be5f391c65692434b673fa7c2083 100644 --- a/includes/form.theme +++ b/includes/form.theme @@ -6,86 +6,14 @@ */ use Drupal\Core\Form\FormStateInterface; +use Drupal\gin\GinContentFormHelper; use Drupal\gin\GinSettings; /** * Implements form_alter_HOOK() for some major form changes. */ function gin_form_alter(&$form, $form_state, $form_id) { - // Are we on an edit form? - if (_gin_is_content_form($form, $form_state, $form_id)) { - // Action buttons. - if (isset($form['actions'])) { - if (isset($form['actions']['preview'])) { - // Put Save after Preview. - $save_weight = $form['actions']['preview']['#weight'] ? $form['actions']['preview']['#weight'] + 1 : 11; - $form['actions']['submit']['#weight'] = $save_weight; - } - - // Move entity_save_and_addanother_node after preview. - if (isset($form['actions']['entity_save_and_addanother_node'])) { - // Put Save after Preview. - $save_weight = $form['actions']['entity_save_and_addanother_node']['#weight']; - $form['actions']['preview']['#weight'] = $save_weight - 1; - } - - // Create gin_actions group. - $form['gin_actions'] = [ - '#type' => 'container', - '#weight' => -1, - '#multilingual' => TRUE, - '#attributes' => [ - 'class' => [ - 'gin-sticky', - ], - ], - ]; - // Assign status to gin_actions. - $form['status']['#group'] = 'gin_actions'; - - // Create actions group. - $form['gin_actions']['actions'] = [ - '#type' => 'actions', - '#weight' => 130, - ]; - - // Move all actions over. - $form['gin_actions']['actions'] = ($form['actions']) ?? []; - // Now let's just remove delete, as we'll move that over to gin_sidebar. - unset($form['gin_actions']['actions']['delete']); - - // Create gin_sidebar group. - $form['gin_sidebar'] = [ - '#group' => 'meta', - '#type' => 'container', - '#weight' => 99, - '#multilingual' => TRUE, - '#attributes' => [ - 'class' => [ - 'gin-sidebar', - ], - ], - ]; - // Copy footer over. - $form['gin_sidebar']['footer'] = ($form['footer']) ?? []; - // Copy delete action. - $form['gin_sidebar']['actions'] = []; - $form['gin_sidebar']['actions']['#type'] = ($form['actions']['#type']) ?? []; - $form['gin_sidebar']['actions']['delete'] = ($form['actions']['delete']) ?? []; - } - - // Attach library. - $form['#attached']['library'][] = 'gin/edit_form'; - } - - // If not logged in hide changed and author node info on add forms. - $not_logged_in = \Drupal::currentUser()->isAnonymous(); - $route = \Drupal::routeMatch()->getRouteName(); - - if ($not_logged_in && $route == 'node.add') { - unset($form['meta']['changed']); - unset($form['meta']['author']); - } + \Drupal::classResolver(GinContentFormHelper::class)->formAlter($form, $form_state, $form_id); // User form (Login, Register or Forgot password). if (strpos($form_id, 'user_login') !== FALSE || strpos($form_id, 'user_register') !== FALSE || strpos($form_id, 'user_pass') !== FALSE) { diff --git a/includes/helper.theme b/includes/helper.theme index 7fc893759b9dddf35621ec0eccfedb3a1462ede4..db71510a44f93cf870892dd05f527c987f1d01b4 100644 --- a/includes/helper.theme +++ b/includes/helper.theme @@ -135,49 +135,3 @@ function _gin_validate_path_logo($path) { } return FALSE; } - -/** - * Check if were on a content edit form. - */ -function _gin_is_content_form($form = NULL, $form_state = NULL, $form_id = NULL) { - $is_content_form = FALSE; - - // Get route name. - $route_name = \Drupal::routeMatch()->getRouteName(); - - // Routes to include. - $route_names = [ - 'node.add', - 'entity.node.content_translation_add', - 'entity.node.content_translation_edit', - 'quick_node_clone.node.quick_clone', - 'entity.node.edit_form', - ]; - - if ( - in_array($route_name, $route_names, TRUE) || - ($form_state && ($form_state->getBuildInfo()['base_form_id'] ?? NULL) === 'node_form') || - ($route_name === 'entity.group_content.create_form' && strpos($form_id, 'group_node') === FALSE) - ) { - $is_content_form = TRUE; - } - - // Forms to exclude. - // If media library widget, don't use new content edit form. - // gin_preprocess_html is not triggered here, so checking - // the form id is enough. - $form_ids_to_ignore = [ - 'media_library_add_form_', - 'views_form_media_library_widget_', - 'views_exposed_form', - 'date_recur_modular_sierra_occurrences_modal', - ]; - - foreach ($form_ids_to_ignore as $form_id_to_ignore) { - if ($form_id && strpos($form_id, $form_id_to_ignore) !== FALSE) { - $is_content_form = FALSE; - } - } - - return $is_content_form; -} diff --git a/includes/html.theme b/includes/html.theme index 0efc5fb84d05a5f9835f14c1e757081b100cbc87..ce0bb844ebf1eb1ceb2312e8c9ae2fd8895b911d 100644 --- a/includes/html.theme +++ b/includes/html.theme @@ -5,6 +5,7 @@ * html.theme */ +use Drupal\gin\GinContentFormHelper; use Drupal\gin\GinSettings; /** @@ -32,7 +33,7 @@ function gin_preprocess_html(&$variables) { } // Edit form? Use the new Gin Edit form layout. - if (_gin_is_content_form()) { + if (\Drupal::classResolver(GinContentFormHelper::class)->isContentForm()) { $variables['attributes']['class'][] = 'gin--edit-form'; } diff --git a/includes/page.theme b/includes/page.theme index 5a67884d7916e0f0dacbda365657500b2d1b6a92..05b520c1e873046b8d437c737f55a71976a9f63c 100644 --- a/includes/page.theme +++ b/includes/page.theme @@ -5,6 +5,7 @@ * page.theme */ +use Drupal\gin\GinContentFormHelper; use Drupal\Core\Entity\EntityInterface; use Drupal\gin\GinSettings; @@ -46,6 +47,12 @@ function gin_theme_suggestions_page_alter(&$suggestions, $variables) { $arg = str_replace(["/", '-'], ['_', '_'], $path); $suggestions[] = 'page__' . $arg; } + + // The node page template is required to use the node content form. + if (\Drupal::classResolver(GinContentFormHelper::class)->isContentForm() + && !in_array('page__node', $suggestions)) { + $suggestions[] = 'page__node'; + } } /** diff --git a/src/GinContentFormHelper.php b/src/GinContentFormHelper.php new file mode 100644 index 0000000000000000000000000000000000000000..19dac31b6d430b0f6dca0210234a5d7becbe3cd2 --- /dev/null +++ b/src/GinContentFormHelper.php @@ -0,0 +1,256 @@ +<?php + +namespace Drupal\gin; + +use Drupal\Core\DependencyInjection\ContainerInjectionInterface; +use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Routing\RouteMatchInterface; +use Drupal\Core\Session\AccountInterface; +use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\Core\Theme\ThemeManagerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Service to handle content form overrides. + */ +class GinContentFormHelper implements ContainerInjectionInterface { + + use StringTranslationTrait; + + /** + * The current user object. + * + * @var \Drupal\Core\Session\AccountInterface + */ + protected $currentUser; + + /** + * The module handler service. + * + * @var \Drupal\Core\Extension\ModuleHandlerInterface + */ + protected $moduleHandler; + + /** + * The current route match. + * + * @var \Drupal\Core\Routing\RouteMatchInterface + */ + protected $routeMatch; + + /** + * The theme manager. + * + * @var \Drupal\Core\Theme\ThemeManagerInterface + */ + protected $themeManager; + + /** + * GinContentFormHelper constructor. + * + * @param \Drupal\Core\Session\AccountInterface $current_user + * The current user. + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler + * The module handler. + * @param \Drupal\Core\Routing\RouteMatchInterface $route_match + * The current route match. + * @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager + * The theme manager. + */ + public function __construct(AccountInterface $current_user, ModuleHandlerInterface $module_handler, RouteMatchInterface $route_match, ThemeManagerInterface $theme_manager) { + $this->currentUser = $current_user; + $this->moduleHandler = $module_handler; + $this->routeMatch = $route_match; + $this->themeManager = $theme_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('current_user'), + $container->get('module_handler'), + $container->get('current_route_match'), + $container->get('theme.manager'), + ); + } + + /** + * Add some major form overrides. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * @param string $form_id + * The form id. + * + * @see hook_form_alter() + */ + public function formAlter(array &$form, FormStateInterface $form_state, $form_id) { + // Are we on an edit form? + if (!$this->isContentForm($form, $form_state, $form_id)) { + return; + } + + // Provide a default meta form element if not already provided. + // @see NodeForm::form() + $form['advanced']['#attributes']['class'][] = 'entity-meta'; + if (!isset($form['meta'])) { + $form['meta'] = [ + '#type' => 'container', + '#group' => 'advanced', + '#weight' => -10, + '#title' => $this->t('Status'), + '#attributes' => ['class' => ['entity-meta__header']], + '#tree' => TRUE, + '#access' => TRUE, + ]; + } + + // Specify necessary node form theme and library. + // @see claro_form_node_form_alter + $form['#theme'] = ['node_edit_form']; + $form['#attached']['library'][] = 'claro/node-form'; + $form['#attached']['library'][] = 'gin/edit_form'; + + // Ensure correct settings for advanced, meta and revision form elements. + $form['advanced']['#type'] = 'container'; + $form['advanced']['#accordion'] = TRUE; + $form['meta']['#type'] = 'container'; + $form['meta']['#access'] = TRUE; + + $form['revision_information']['#type'] = 'container'; + $form['revision_information']['#group'] = 'meta'; + $form['revision_information']['#attributes']['class'][] = 'entity-meta__revision'; + + // Action buttons. + if (isset($form['actions'])) { + if (isset($form['actions']['preview'])) { + // Put Save after Preview. + $save_weight = $form['actions']['preview']['#weight'] ? $form['actions']['preview']['#weight'] + 1 : 11; + $form['actions']['submit']['#weight'] = $save_weight; + } + + // Create gin_actions group. + $form['gin_actions'] = [ + '#type' => 'container', + '#weight' => -1, + '#multilingual' => TRUE, + '#attributes' => [ + 'class' => [ + 'gin-sticky', + ], + ], + ]; + // Assign status to gin_actions. + $form['status']['#group'] = 'gin_actions'; + + // Create actions group. + $form['gin_actions']['actions'] = [ + '#type' => 'actions', + '#weight' => 130, + ]; + // Add Preview to gin_actions actions. + $form['gin_actions']['actions']['preview'] = ($form['actions']['preview']) ?? []; + // Add Submit to gin_actions actions. + $form['gin_actions']['actions']['submit'] = ($form['actions']['submit']) ?? []; + + // Create gin_sidebar group. + $form['gin_sidebar'] = [ + '#group' => 'meta', + '#type' => 'container', + '#weight' => 99, + '#multilingual' => TRUE, + '#attributes' => [ + 'class' => [ + 'gin-sidebar', + ], + ], + ]; + // Copy footer over. + $form['gin_sidebar']['footer'] = ($form['footer']) ?? []; + // Copy actions over. + $form['gin_sidebar']['actions'] = ($form['actions']) ?? []; + // Unset previous added preview & submit. + unset($form['gin_sidebar']['actions']['preview']); + unset($form['gin_sidebar']['actions']['submit']); + } + + // Attach library. + $form['#attached']['library'][] = 'gin/gin_editform'; + + // If not logged in hide changed and author node info on add forms. + $not_logged_in = $this->currentUser->isAnonymous(); + $route = $this->routeMatch->getRouteName(); + + if ($not_logged_in && $route == 'node.add') { + unset($form['meta']['changed']); + unset($form['meta']['author']); + } + + } + + /** + * Check if we´re on a content edit form. + * + * _gin_is_content_form() is replaced by + * \Drupal::classResolver(GinContentFormHelper::class)->isContentForm(). + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * @param string $form_id + * The form id. + */ + public function isContentForm(array $form = NULL, FormStateInterface $form_state = NULL, $form_id = NULL) { + $is_content_form = FALSE; + + // Get route name. + $route_name = $this->routeMatch->getRouteName(); + + // Routes to include. + $route_names = [ + 'node.add', + 'entity.node.content_translation_add', + 'quick_node_clone.node.quick_clone', + 'entity.node.edit_form', + ]; + + $additional_routes = $this->moduleHandler->invokeAll('gin_content_form_routes'); + $route_names = array_merge($additional_routes, $route_names); + $this->moduleHandler->alter('gin_content_form_routes', $route_names); + $this->themeManager->alter('gin_content_form_routes', $route_names); + + if ( + in_array($route_name, $route_names, TRUE) || + ($form_state && ($form_state->getBuildInfo()['base_form_id'] ?? NULL) === 'node_form') || + ($route_name === 'entity.group_content.create_form' && strpos($form_id, 'group_node') === FALSE) + ) { + $is_content_form = TRUE; + } + + // Forms to exclude. + // If media library widget, don't use new content edit form. + // gin_preprocess_html is not triggered here, so checking + // the form id is enough. + $form_ids_to_ignore = [ + 'media_library_add_form_', + 'views_form_media_library_widget_', + 'views_exposed_form', + 'date_recur_modular_sierra_occurrences_modal', + ]; + + foreach ($form_ids_to_ignore as $form_id_to_ignore) { + if ($form_id && strpos($form_id, $form_id_to_ignore) !== FALSE) { + $is_content_form = FALSE; + } + } + + return $is_content_form; + } + +}