diff --git a/css/localgov_forms_header_block.css b/css/localgov_forms_header_block.css new file mode 100644 index 0000000..d1085df --- /dev/null +++ b/css/localgov_forms_header_block.css @@ -0,0 +1,27 @@ +.header__form_title h1 { + font-size: 1.5rem; + font-weight: bold; + margin-bottom: 0.5rem; +} + +.header__user_description { + font-size: 1.375rem !important; + font-weight: 400 !important; + line-height: 1.5rem; + margin-bottom: 1em; +} + +.header__wizard_page_title { + font-size: 2.125rem; + font-weight: bold; + margin-bottom: 1.5rem; +} + +.header__form_title_hr { + width: 100%; + margin-top: 0; + margin-bottom: 1.5rem; + border: 0; + border-top: 1px solid #e0e0e0; + border-bottom: 1px solid #e0e0e0; +} diff --git a/localgov_forms.libraries.yml b/localgov_forms.libraries.yml index d8f347c..d11c469 100644 --- a/localgov_forms.libraries.yml +++ b/localgov_forms.libraries.yml @@ -32,3 +32,9 @@ localgov_forms.address_change: - core/drupal - core/once - core/jquery + +localgov_forms.form_header_block: + version: VERSION + css: + theme: + css/localgov_forms_header_block.css: {} diff --git a/localgov_forms.module b/localgov_forms.module index 64d3ccf..46075f5 100644 --- a/localgov_forms.module +++ b/localgov_forms.module @@ -6,6 +6,7 @@ */ use Drupal\Core\Render\Element; +use Drupal\Core\Form\FormStateInterface; /** * Implements hook_theme(). @@ -20,6 +21,16 @@ function localgov_forms_theme() { 'localgov_forms_uk_address' => [ 'render element' => 'element', ], + // Form header block. + 'localgov_forms_form_header_block' => [ + 'variables' => [ + 'formTitle' => '', + 'wizardPageTitle' => '', + 'currentPage' => '', + 'userDescription' => '', + ], + 'render element' => 'block', + ], ]; } @@ -55,3 +66,98 @@ function template_preprocess_localgov_forms_uk_address(array &$variables) { function localgov_forms_preprocess_webform(array &$variables) { $variables['#attached']['library'][] = 'localgov_forms/localgov_forms.form_errors'; } + +/** + * Implements hook_form_FORM_ID_alter(). + */ +function localgov_forms_webform_submission_form_alter(array &$form, FormStateInterface $form_state, $form_id) { + // Check if this is a webform submission form. + if (strpos($form_id, 'webform_submission_') === 0) { + // Get the webform. + $webform = $form_state->getFormObject()->getWebform(); + // Check if the form header block configuration is enabled. + if ($webform->getThirdPartySetting('localgov_forms', 'display_header_block', FALSE)) { + // Assuming you have an instance of FormHeaderBlock. + $form_header_block = \Drupal::service('plugin.manager.block')->createInstance('localgov_forms_form_header_block', []); + // Build the block. + $block_build = $form_header_block->build($form_state); + + // Check if the block has content to render. + if (!empty($block_build)) { + // Add the block as a prefix to the form. + $form['#prefix'] = \Drupal::service('renderer')->render($block_build); + } + } + } +} + +/** + * Implements hook_form_alter(). + */ +function localgov_forms_form_alter(array &$form, FormStateInterface $form_state, $form_id) { + // Check if this is a webform edit form. + if (strpos($form_id, 'webform_settings_form') === 0) { + // Get the webform. + $webform = $form_state->getFormObject()->getEntity(); + + $form['form_settings']['form_header_block_settings'] = [ + '#type' => 'details', + '#title' => t('Form header settings'), + '#open' => TRUE, + '#weight' => -9999, + ]; + // Add the "User Description" field. + $form['form_settings']['form_header_block_settings']['user_description'] = [ + '#type' => 'textarea', + '#title' => t('User Description'), + '#default_value' => $webform->getThirdPartySetting('localgov_forms', 'user_description', ''), + '#description' => t('A usasge description for this form that is shown to users.'), + // Adjust weight as needed. + '#weight' => 10, + ]; + + $form['form_settings']['form_header_block_settings']['display_header_block'] = [ + '#type' => 'checkbox', + '#title' => t('Display Form Header Block'), + '#default_value' => $webform->getThirdPartySetting('localgov_forms', 'display_header_block', TRUE), + '#description' => t('Enable the form header block to be displayed on this webform.'), + // Adjust weight as needed. + '#weight' => 1, + ]; + + // Add a submit handler to save the setting. + $form['actions']['submit']['#submit'][] = 'localgov_forms_webform_settings_form_submit'; + } + // Check if this is a webform submission form. + elseif (strpos($form_id, 'webform_submission_') === 0) { + // Get the webform. + $webform = $form_state->getFormObject()->getWebform(); + // Check if the form header block is enabled. + if ($webform->getThirdPartySetting('localgov_forms', 'display_header_block', FALSE)) { + // Store the form state in the form. + $form['#form_state'] = $form_state; + } + } +} + +/** + * Submit handler for webform edit form. + */ +function localgov_forms_webform_settings_form_submit(array &$form, FormStateInterface $form_state) { + // Get the webform. + $webform = $form_state->getFormObject()->getEntity(); + + // Get the checkbox value. + $enable_form_header_block = $form_state->getValue('display_header_block'); + + // Save the setting to the webform. + $webform->setThirdPartySetting('localgov_forms', 'display_header_block', $enable_form_header_block); + $webform->save(); + + // Get the user description value. + $user_description = $form_state->getValue('user_description'); + + // Save the user description setting to the webform. + $webform->setThirdPartySetting('localgov_forms', 'user_description', $user_description); + $webform->save(); +} diff --git a/src/Event/FormHeaderDisplayEvent.php b/src/Event/FormHeaderDisplayEvent.php new file mode 100644 index 0000000..99f0ec6 --- /dev/null +++ b/src/Event/FormHeaderDisplayEvent.php @@ -0,0 +1,208 @@ +entity = $entity; + } + + /** + * Entity getter. + * + * @return \Drupal\Core\Entity\EntityInterface|null + * The entity. + */ + public function getEntity() { + return $this->entity; + } + + /** + * Form Title getter. + * + * @return array|string|null + * The form title + */ + public function getFormTitle() { + return $this->formTitle; + } + + /** + * Form Title setter. + * + * @param array|string|null $formTitle + * The form title. + */ + public function setFormTitle($formTitle) { + $this->formTitle = $formTitle; + } + + /** + * Wizard Page Title getter. + * + * @return array|string|null + * The title. + */ + public function getWizardPageTitle() { + return $this->wizardPageTitle; + } + + /** + * Wizard Page Title setter. + * + * @param array|string|null $wizard_page_title + * The title. + */ + public function setWizardPageTitle($wizard_page_title) { + $this->wizardPageTitle = $wizard_page_title; + } + + /** + * Current page getter. + * + * @return array|string|null + * The current page title. + */ + public function getCurrentPage() { + return $this->currentPage; + } + + /** + * Current page setter. + * + * @param array|string|null $current_page + * The current page title. + */ + public function setCurrentPage($current_page) { + $this->currentPage = $current_page; + } + + /** + * Form User Description getter. + * + * @return array|string|null + * The User description title + */ + public function getFormUserDescription() { + return $this->userDescription; + } + + /** + * Form Summary setter. + * + * @param array|string|null $form_summary + * The form summary. + */ + public function setFormSummary($form_summary) { + $this->userDescription = $form_summary; + } + + /** + * Visibility getter. + * + * @return bool|null + * The title. + */ + public function getVisibility() { + return $this->visibility; + } + + /** + * Visibility setter. + * + * @param bool $visibility + * The visibility. + */ + public function setVisibility($visibility) { + $this->visibility = $visibility; + } + + /** + * Cache tags getter. + * + * @return array|null + * Cache tags array if set. + */ + public function getCacheTags() { + return $this->cacheTags; + } + + /** + * Cache tags setter. + * + * @param array $cacheTags + * The cache tags. + */ + public function setCacheTags(array $cacheTags) { + $this->cacheTags = $cacheTags; + } + +} diff --git a/src/EventSubscriber/FormHeaderSubscriber.php b/src/EventSubscriber/FormHeaderSubscriber.php new file mode 100644 index 0000000..2e2dc7e --- /dev/null +++ b/src/EventSubscriber/FormHeaderSubscriber.php @@ -0,0 +1,37 @@ + ['setFormHeader', 0], + ]; + } + + /** + * Hide page header block. + */ + public function setFormHeader(FormHeaderDisplayEvent $event) { + if ($event->getEntity() instanceof WebformInterface && + ($event->getEntity()->bundle() == 'localgov_forms_overview' || + $event->getEntity()->bundle() == 'localgov_forms_page') + ) { + $event->setVisibility(FALSE); + } + } + +} diff --git a/src/Plugin/Block/FormHeaderBlock.php b/src/Plugin/Block/FormHeaderBlock.php new file mode 100644 index 0000000..9010a0c --- /dev/null +++ b/src/Plugin/Block/FormHeaderBlock.php @@ -0,0 +1,459 @@ +currentRouteMatch = $current_route_match; + $this->eventDispatcher = $event_dispatcher; + $this->requestStack = $request_stack; + $this->titleResolver = $title_resolver; + + // Find the entity, if any, associated with the current route. + $route = $this->currentRouteMatch->getRouteObject(); + if ($route !== NULL) { + $parameters = $route->getOption('parameters'); + if ($parameters !== NULL) { + foreach ($parameters as $name => $options) { + if (!isset($options['type'])) { + continue; + } + + if (strpos($options['type'], 'entity:') === 0) { + $entity = $this->currentRouteMatch->getParameter($name); + } + elseif ($options['type'] === 'node_preview') { + $preview = $this->currentRouteMatch->getParentRouteMatch()->getParameter($name); + if (isset($preview->preview_view_mode) && $preview->preview_view_mode === 'full') { + $entity = $preview; + } + } + + if (isset($entity) && $entity instanceof EntityInterface) { + $this->entity = $entity; + break; + } + } + } + } + + // Dispatch event to allow modules to alter block content. + $event = new FormHeaderDisplayEvent($this->entity); + $this->eventDispatcher->dispatch($event, FormHeaderDisplayEvent::EVENT_NAME); + + // Set the Form title, current page, form summary, + // visibility and cache tags. + $this->formTitle = $event->getFormTitle() ?? $this->getFormTitle(); + $this->currentPage = $event->getCurrentPage() ?? $this->getCurrentPage(); + $this->wizardPageTitle = $event->getWizardPageTitle() ?? $this->getWizardPageTitle(); + $this->userDescription = $event->getFormUserDescription() ?? $this->getFormUserDescription(); + $this->visible = $event->getVisibility(); + + $entityCacheTags = $this->entity === NULL ? [] : $this->entity->getCacheTags(); + $this->cacheTags = $event->getCacheTags() ?? $entityCacheTags; + } + + /** + * Creates an instance of the FormHeaderBlock plugin. + * + * @param \Symfony\Component\DependencyInjection\ContainerInterface $container + * The service container. + * @param array $configuration + * A configuration array containing information about the plugin instance. + * @param string $plugin_id + * The plugin ID for the plugin instance. + * @param mixed $plugin_definition + * The plugin implementation definition. + * + * @return static + * Returns a new instance of the FormHeaderBlock plugin. + * + * @throws \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException + * Thrown if a required service is not found in the container. + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('current_route_match'), + $container->get('event_dispatcher'), + $container->get('request_stack'), + $container->get('title_resolver') + ); + } + + /** + * Summary of build. + * + * @param mixed $form_state + * The form state object. + * + * @return array|array{#attached: array, + * #cache: array{max-age: int, + * #currentPage: \Drupal\Core\Routing\RouteMatchInterface|null, + * #formTitle: string, + * #theme: string, #userDescription: string, + * #wizardPageTitle: string + * }} + */ + public function build(?FormStateInterface $form_state = NULL) { + $build = []; + if ($form_state) { + $this->currentPage = $form_state->get('current_page'); + $currentPage = $this->currentPage; + $wizard_pages = $this->entity->getPages(); + $page_keys = array_keys($wizard_pages); + + // Add the form title to the beginning of the array. + // so that page indexes start from 1. + array_unshift($page_keys, $this->formTitle); + + $page_title = $wizard_pages[$currentPage]["#title"]; + $this->wizardPageTitle = $page_title; + + $build = [ + '#theme' => 'localgov_forms_form_header_block', + '#formTitle' => $this->formTitle, + '#wizardPageTitle' => $this->wizardPageTitle, + '#currentPage' => $this->currentPage, + '#userDescription' => $this->userDescription, + '#cache' => [ + 'max-age' => 0, + ], + '#attached' => [ + 'library' => [ + 'localgov_forms/localgov_forms.form_header_block', + ], + ], + ]; + } + return $build; + } + + /** + * Retrieves the title of the current form. + * + * This method uses the current request and route to resolve the title + * of the form. If a route is available, the title is resolved using + * the title resolver service. If no route is found, it returns NULL. + * + * @return string|null + * The resolved title of the form, or NULL if no route is available. + */ + protected function getFormTitle() { + $request = $this->requestStack->getCurrentRequest(); + $route = $this->currentRouteMatch->getRouteObject(); + if ($route) { + return $this->titleResolver->getTitle($request, $route); + } + return NULL; + } + + /** + * Retrieves the current page index of a webform with wizard pages. + * + * This method checks if the current entity is a webform with wizard pages. + * If so, it retrieves the list of wizard pages and determines the current + * page index based on the request query parameter 'page'. If the 'page' + * parameter is not set, it defaults to the first page in the wizard. + * + * @return string|int|null + * The current page index if available, or NULL if the entity is not a + * webform with wizard pages or if no page index is determined. + */ + protected function getCurrentPage() { + if ($this->entity instanceof WebformInterface && $this->entity->hasWizardPages()) { + + $wizard_pages = $this->entity->getPages(); + $page_keys = array_keys($wizard_pages); + + // Get the current page index from the request. + $request = $this->requestStack->getCurrentRequest(); + $current_page_index = $request->query->get('page'); + + if ($current_page_index !== NULL) { + return $current_page_index; + } + else { + $currentPage = reset($page_keys); + } + + return $currentPage; + } + return NULL; + } + + /** + * Retrieves the title of the current wizard page in a webform. + * + * This method checks if the associated entity is a webform with wizard pages. + * If so, it determines the title of the current page based on the wizard's + * page structure. The form title is prepended to the page keys to ensure + * page indexes start from 1. + * + * @return string|null + * The title of the current wizard page, or NULL if the entity is not a + * webform with wizard pages or if the current page title cannot be + * determined. + */ + protected function getWizardPageTitle() { + if ($this->entity instanceof WebformInterface && $this->entity->hasWizardPages()) { + $currentPage = $this->currentPage; + $wizard_pages = $this->entity->getPages(); + $page_keys = array_keys($wizard_pages); + + // Add the form title to the beginning of the array. + // so that page indexes start from 1. + array_unshift($page_keys, $this->formTitle); + + $page_title = isset($page_keys[$currentPage]) ? $wizard_pages[$page_keys[$currentPage]]["#title"] : NULL; + + return $page_title; + } + return NULL; + } + + /** + * Retrieves the user description for the form. + * + * This method checks if the current entity is a webform with wizard pages. + * If so, it retrieves the user description from the third-party settings + * specific to the "localgov_forms" module. If the conditions are not met, + * it returns NULL. + * + * @return string|null + * The user description from the third-party settings, or NULL if the + * entity is not a webform with wizard pages or the description is not set. + */ + protected function getFormUserDescription() { + if ($this->entity instanceof WebformInterface && $this->entity->hasWizardPages()) { + // Return $this->entity->getDescription(); + return $this->entity->getThirdPartySetting('localgov_forms', 'user_description'); + } + return NULL; + } + + /** + * Determines access to the block based on its visibility. + * + * @param \Drupal\Core\Session\AccountInterface $account + * The user account object for which access is being checked. + * + * @return \Drupal\Core\Access\AccessResult + * Returns AccessResult::allowed() if the block is visible, + * otherwise returns AccessResult::neutral(). + */ + protected function blockAccess(AccountInterface $account) { + if ($this->visible) { + return AccessResult::allowed(); + } + else { + return AccessResult::neutral(); + } + } + + /** + * Provides the default configuration for the block. + * + * @return array + * An associative array containing the default configuration values: + * - label_display: (bool) Whether the label should be displayed. + * Defaults to FALSE. + */ + public function defaultConfiguration() { + return ['label_display' => FALSE]; + } + + /** + * Retrieves the cache tags associated with this block. + * + * This method checks if additional cache tags are defined for the block + * and merges them with the parent cache tags. If no additional cache tags + * are defined, it simply returns the parent cache tags. + * + * @return array + * An array of cache tags. + */ + public function getCacheTags() { + if (!empty($this->cacheTags)) { + return Cache::mergeTags(parent::getCacheTags(), $this->cacheTags); + } + return parent::getCacheTags(); + } + + /** + * {@inheritdoc} + * + * Overrides the cache contexts for the block. + * Adds the 'route' cache context to ensure the block is cached + * per route, in addition to the default cache contexts provided + * by the parent implementation. + * + * @return array + * An array of cache contexts. + */ + public function getCacheContexts() { + return Cache::mergeContexts(parent::getCacheContexts(), ['route']); + } + +} diff --git a/templates/localgov-forms-form-header-block.html.twig b/templates/localgov-forms-form-header-block.html.twig new file mode 100644 index 0000000..704b1f2 --- /dev/null +++ b/templates/localgov-forms-form-header-block.html.twig @@ -0,0 +1,37 @@ +{# +/** +* @file +* Default theme implementation of localgov_forms_form_header_block. +* +* @see template_preprocess_block() +* +* @ingroup themeable +*/ +#} +{% set classes = [ + 'clear-both', + 'headers', +] %} +{% if formTitle %} +