gatsby_endpoints-8.x-1.0-alpha1/src/Form/GatsbyEndpointForm.php
src/Form/GatsbyEndpointForm.php
<?php namespace Drupal\gatsby_endpoints\Form; use Drupal\gatsby_endpoints\Plugin\GatsbyEndpointInterface; use Drupal\Core\Entity\ContentEntityTypeInterface; use Drupal\Core\Entity\EntityForm; use Drupal\Core\Entity\EntityTypeBundleInfo; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Form\SubformState; use Drupal\Core\Plugin\PluginFormFactoryInterface; use Drupal\Core\Plugin\PluginWithFormsInterface; use Drupal\Core\Url; use Symfony\Component\DependencyInjection\ContainerInterface; /** * Provides a form for editing Gatsby Endpoint entities. */ class GatsbyEndpointForm extends EntityForm { /** * The plugin form manager. * * @var \Drupal\Core\Plugin\PluginFormFactoryInterface */ protected $pluginFormFactory; /** * The entity bundle service. * * @var \Drupal\Core\Entity\EntityTypeBundleInfo */ protected $entityTypeBundleInfo; /** * GatsbyEndpointForm constructor. * * @param \Drupal\Core\Plugin\PluginFormFactoryInterface $plugin_form_manager * The plugin form manager. * @param \Drupal\Core\Entity\EntityTypeBundleInfo $entity_type_bundle_info * The entity type bundle info service. */ public function __construct(PluginFormFactoryInterface $plugin_form_manager, EntityTypeBundleInfo $entity_type_bundle_info) { $this->pluginFormFactory = $plugin_form_manager; $this->entityTypeBundleInfo = $entity_type_bundle_info; } /** * {@inheritdoc} */ public static function create(ContainerInterface $container) { return new static( $container->get('plugin_form.factory'), $container->get('entity_type.bundle.info') ); } /** * {@inheritdoc} */ public function form(array $form, FormStateInterface $form_state) { $form = parent::form($form, $form_state); /** @var \Drupal\gatsby_endpoints\Entity\GatsbyEndpointInterface $entity */ $entity = $this->entity; $form['label'] = [ '#type' => 'textfield', '#title' => $this->t('Label'), '#maxlength' => 255, '#default_value' => $entity->label(), '#description' => $this->t("Label for the Gatsby endpoint."), '#required' => TRUE, ]; $form['id'] = [ '#type' => 'machine_name', '#default_value' => $entity->id(), '#machine_name' => [ 'exists' => '\Drupal\gatsby_endpoints\Entity\GatsbyEndpoint::load', ], '#disabled' => !$entity->isNew(), ]; // Render the Preview URLs AJAX enabled fieldset. $preview_description = $this->t("Enter any Gatsby Live Preview URLs to trigger for this endpoint."); $this->addAjaxFieldset($form, $form_state, 'preview', $preview_description); // Render the Build URLs AJAX enabled fieldset. $build_description = $this->t("Enter any Gatsby Build Hooks or Build URLs to trigger for this endpoint."); $this->addAjaxFieldset($form, $form_state, 'build', $build_description); $build_entities_description = $this->t("Select which entities should trigger builds/previews for this endpoint."); $this->addEntityAjaxFieldset($form, $form_state, 'build', $build_entities_description); $form['build_trigger'] = [ '#type' => 'select', '#options' => [ 'incremental' => $this->t("Trigger builds incrementally (requires Gatsby Cloud)"), 'cron' => $this->t("Trigger builds on cron runs"), 'manual' => $this->t("Trigger builds manually with the built in drush command"), ], '#title' => $this->t("Build Trigger"), '#default_value' => $entity->getBuildTrigger() ? $entity->getBuildTrigger() : 'incremental', '#description' => $this->t('Select how Gatsby build URLs should be triggered. Incremental builds require a <a href="@gatsby-link">Gatsby Cloud Account</a>. This setting has no effect if there are no build URLs entered above.', [ '@gatsby-link' => 'https://gatsbyjs.com', ] ), '#required' => TRUE, ]; $form['weight'] = [ '#type' => 'number', '#title' => $this->t('Weight'), '#max' => 100, '#min' => -100, '#size' => 3, '#default_value' => $entity->getWeight() ? $entity->getWeight() : 0, '#description' => $this->t("Set the weight, lighter endpoints will be rendered first."), '#required' => TRUE, ]; $form['#tree'] = TRUE; $form['settings'] = []; $subform_state = SubformState::createForSubform($form['settings'], $form, $form_state); $form['settings'] = $this->getPluginForm($entity->getPlugin()) ->buildConfigurationForm($form['settings'], $subform_state); return $form; } /** * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { // Remove empty Build and Preview URLs. $this->removeEmptyUrls($form_state, 'preview'); $this->removeEmptyUrls($form_state, 'build'); parent::submitForm($form, $form_state); /** @var \Drupal\gatsby_endpoints\Entity\GatsbyEndpointInterface $entity */ $entity = $this->entity; $sub_form_state = SubformState::createForSubform($form['settings'], $form, $form_state); // Call the plugin submit handler. $this->getPluginForm($entity->getPlugin()) ->submitConfigurationForm($form, $sub_form_state); $entity->save(); $this->messenger()->addStatus($this->t('The Gatsby endpoint configuration has been saved.')); $form_state->setRedirectUrl(Url::fromRoute('gatsby_endpoints.gatsby_endpoints_collection')); } /** * {@inheritdoc} */ protected function getPluginForm(GatsbyEndpointInterface $gatsbyEndpoint) { if ($gatsbyEndpoint instanceof PluginWithFormsInterface) { return $this->pluginFormFactory->createInstance($gatsbyEndpoint, 'configure'); } return $gatsbyEndpoint; } /** * Adds a Form Element to an AJAX Fieldset. */ public function addFormElement(array &$form, FormStateInterface $form_state, $key, $element) { $cnt = $form_state->get($key . '_' . $element . '_cnt'); $form_state->set($key . '_' . $element . '_cnt', $cnt + 1); $form_state->setRebuild(); } /** * Removes a Form Element from an AJAX Fieldset. */ public function removeFormElement(array &$form, FormStateInterface $form_state, $key, $element) { $cnt = $form_state->get($key . '_' . $element . '_cnt'); if ($cnt > 1) { $form_state->set($key . '_' . $element . '_cnt', $cnt - 1); } $form_state->setRebuild(); } /** * Adds a Build Url. */ public function addBuildUrl(array &$form, FormStateInterface $form_state) { $this->addFormElement($form, $form_state, 'build', 'url'); } /** * Removes a Build Url. */ public function removeBuildUrl(array &$form, FormStateInterface $form_state) { $this->removeFormElement($form, $form_state, 'build', 'url'); } /** * Ajax callback for Build Urls that returns the correct fieldset. */ public function buildUrlCallback(array &$form, FormStateInterface $form_state) { return $form['build_urls']; } /** * Adds a Preview Url. */ public function addPreviewUrl(array &$form, FormStateInterface $form_state) { $this->addFormElement($form, $form_state, 'preview', 'url'); } /** * Removes a Preview Url. */ public function removePreviewUrl(array &$form, FormStateInterface $form_state) { $this->removeFormElement($form, $form_state, 'preview', 'url'); } /** * Ajax callback for Preview Urls that returns the correct fieldset. */ public function previewUrlCallback(array &$form, FormStateInterface $form_state) { return $form['preview_urls']; } /** * Renders AJAX fieldsets for Build and Preview URLs. */ public function addAjaxFieldset(array &$form, FormStateInterface $form_state, $key, $description) { $label = ucfirst($key); $url_cnt = $form_state->get($key . '_url_cnt'); /** @var \Drupal\gatsby_endpoints\Entity\GatsbyEndpointInterface $entity */ $entity = $this->entity; // If there is no count yet, then this is the first time rendering. if ($url_cnt === NULL) { $urls = $entity->getUrls($key); $url_cnt = !empty($urls) && !empty($urls[$key . '_url']) ? count($urls[$key . '_url']) : 0; if ($url_cnt === 0) { $url_cnt = 1; } $form_state->set($key . '_url_cnt', $url_cnt); } $form[$key . '_urls'] = [ '#type' => 'fieldset', '#title' => $this->t("Gatsby @label URLs", ['@label' => $label]), '#prefix' => '<div id="' . $key . '-urls-fieldset-wrapper">', '#suffix' => '</div>', ]; $form[$key . '_urls']['description'] = [ '#markup' => $description, '#weight' => -100, ]; for ($i = 0; $i < $url_cnt; $i++) { $form[$key . '_urls'][$key . '_url'][$i] = [ '#type' => 'url', '#title' => $this->t('Gatsby @label URL #@cnt', [ '@label' => $label, '@cnt' => $i + 1, ]), '#maxlength' => 255, '#default_value' => !empty($urls[$key . '_url'][$i]) ? $urls[$key . '_url'][$i] : "", '#required' => FALSE, ]; } $form[$key . '_urls']['actions'] = [ '#type' => 'actions', ]; $form[$key . '_urls']['actions']['add_' . $key . '_url'] = [ '#type' => 'submit', '#value' => $this->t('Add Additional @label URL', ['@label' => $label]), '#submit' => ['::add' . $label . 'Url'], '#ajax' => [ 'callback' => '::' . $key . 'UrlCallback', 'wrapper' => $key . '-urls-fieldset-wrapper', ], ]; if ($url_cnt > 1) { $form[$key . '_urls']['actions']['remove_' . $key . '_url'] = [ '#type' => 'submit', '#value' => $this->t('Remove @label URL', ['@label' => $label]), '#submit' => ['::remove' . $label . 'Url'], '#ajax' => [ 'callback' => '::' . $key . 'UrlCallback', 'wrapper' => $key . '-urls-fieldset-wrapper', ], ]; } } /** * Removes empty URLs from preview and build fieldsets. */ public function removeEmptyUrls(FormStateInterface $form_state, $key) { $values = $form_state->getValue($key . '_urls'); $values[$key . '_url'] = array_values(array_filter($values[$key . '_url'])); $form_state->setValue($key . '_urls', $values); } /** * Adds a Build Entity Fieldset. */ public function addBuildEntityFieldset(array &$form, FormStateInterface $form_state) { $this->addFormElement($form, $form_state, 'build', 'entity'); } /** * Removes a Build Entity Fieldset Url. */ public function removeBuildEntityFieldset(array &$form, FormStateInterface $form_state) { $this->removeFormElement($form, $form_state, 'build', 'entity'); } /** * Ajax callback for Build Entity Fieldsets that returns the correct fieldset. */ public function buildEntityCallback(array &$form, FormStateInterface $form_state) { return $form['build_entity_types']; } /** * Ajax callback for build entities that returns the correct bundles. */ public function buildEntityBundleCallback(array &$form, FormStateInterface $form_state) { // Determine what element triggered this callback. $triggering_element = $form_state->getTriggeringElement(); $wrapper_elements = explode('-', $triggering_element['#ajax']['wrapper']); $element_id = intval(array_pop($wrapper_elements)); $form['build_entity_types'][$element_id]['#open'] = TRUE; return $form['build_entity_types'][$element_id]; } /** * Renders AJAX fieldsets for Entity selection. */ public function addEntityAjaxFieldset(array &$form, FormStateInterface $form_state, $key, $description) { $label = ucfirst($key); $entity_cnt = $form_state->get($key . '_entity_cnt'); /** @var \Drupal\gatsby_endpoints\Entity\GatsbyEndpointInterface $entity */ $entity = $this->entity; // If there is no count yet, then this is the first time rendering. if ($entity_cnt === NULL) { $entities = $entity->getEntityTypes($key); $entity_cnt = !empty($entities) ? count($entities) - 1 : 0; if ($entity_cnt === 0) { $entity_cnt = 1; } $form_state->set($key . '_entity_cnt', $entity_cnt); } $form[$key . '_entity_types'] = [ '#type' => 'fieldset', '#title' => $this->t("Gatsby @label Entities", ['@label' => $label]), '#prefix' => '<div id="' . $key . '-entity-fieldset-wrapper">', '#suffix' => '</div>', ]; $form[$key . '_entity_types']['description'] = [ '#markup' => $description, '#weight' => -100, ]; for ($i = 0; $i < $entity_cnt; $i++) { // Get the default entity type. $entity_type = !empty($entities[$i]['entity_type']) ? $entities[$i]['entity_type'] : ""; // Check if there was an entity type set in the form state. $form_values = $form_state->getValues(); if (!empty($form_values[$key . '_entity_types'][$i]['entity_type'])) { $entity_type = $form_values[$key . '_entity_types'][$i]['entity_type']; } $content_entity_types = $this->getContentEntityTypes(); $form[$key . '_entity_types'][$i] = [ '#type' => 'details', '#title' => $this->t("Gatsby @label Entity #@cnt @entity", [ '@label' => $label, '@cnt' => $i + 1, '@entity' => !empty($entity_type) ? "(" . $content_entity_types[$entity_type] . ")" : "", ]), '#prefix' => '<div id="' . $key . '-entity-fieldset-' . $i . '">', '#suffix' => '</div>', '#open' => empty($entity_type) ? TRUE : FALSE, ]; $entity_types = ['' => $this->t("-- Select Entity Type --")] + $content_entity_types; $form[$key . '_entity_types'][$i]['entity_type'] = [ '#type' => 'select', '#options' => $entity_types, '#title' => $this->t("Entity Type"), '#default_value' => $entity_type, ]; $form[$key . '_entity_types'][$i]['entity_type']['#ajax'] = [ 'callback' => '::' . $key . 'EntityBundleCallback', 'wrapper' => $key . '-entity-fieldset-' . $i, 'event' => 'change', 'progress' => [ 'type' => 'throbber', 'message' => $this->t('Loading bundles...'), ], ]; if ($entity_type) { $entity_bundles = !empty($entities[$i]['entity_bundles']) ? $entities[$i]['entity_bundles'] : []; $include_entities = !empty($entities[$i]['include_entities']) ? $entities[$i]['include_entities'] : []; $form[$key . '_entity_types'][$i]['entity_bundles'] = [ '#type' => 'checkboxes', '#options' => $this->getContentEntityBundles($entity_type), '#title' => $this->t("Entity Bundle(s)"), '#default_value' => $entity_bundles, ]; if ($entity_type == 'node') { $build_published = !empty($entities[$i]['build_published']) ? $entities[$i]['build_published'] : FALSE; $form[$key . '_entity_types'][$i]['build_published'] = [ '#type' => 'checkbox', '#title' => $this->t('Only trigger builds for published content'), '#description' => $this->t('Depending on your content workflow, you may only want builds to be triggered for published content. By checking this box only published content will trigger a build.'), '#default_value' => $build_published, '#weight' => 3, ]; } $included_entities_description = $this->t("Select which entities should not trigger builds/previews but should be included in builds/previews. This is commonly used for entities such as media, files, or paragraphs where you don't want these items to trigger a new build, but you do want to make sure they are sent to Gatsby if they are attached to an entity that triggers a build."); $form[$key . '_entity_types'][$i]['include_entities'] = [ '#type' => 'checkboxes', '#options' => $content_entity_types, '#title' => $this->t("Include Entities"), '#description' => $included_entities_description, '#default_value' => $include_entities, '#weight' => 4, ]; } } $form[$key . '_entity_types']['actions'] = [ '#type' => 'actions', ]; $form[$key . '_entity_types']['actions']['add_' . $key . '_entity'] = [ '#type' => 'submit', '#value' => $this->t('Add Additional @label Entity', ['@label' => $label]), '#submit' => ['::add' . $label . 'EntityFieldset'], '#ajax' => [ 'callback' => '::' . $key . 'EntityCallback', 'wrapper' => $key . '-entity-fieldset-wrapper', ], ]; if ($entity_cnt > 1) { $form[$key . '_entity_types']['actions']['remove_' . $key . '_entity'] = [ '#type' => 'submit', '#value' => $this->t('Remove @label Entity', ['@label' => $label]), '#submit' => ['::remove' . $label . 'EntityFieldset'], '#ajax' => [ 'callback' => '::' . $key . 'EntityCallback', 'wrapper' => $key . '-entity-fieldset-wrapper', ], ]; } } /** * Gets a list of all the defined content entities in the system. * * @return array * An array of content entities definitions. */ private function getContentEntityTypes() { $content_entity_types = []; $allEntityTypes = $this->entityTypeManager->getDefinitions(); foreach ($allEntityTypes as $entity_type_id => $entity_type) { // Add all content entity types but not the gatsby log entity provided // by the gatsby_fastbuilds module (if it exists). if ($entity_type instanceof ContentEntityTypeInterface && $entity_type_id !== 'gatsby_log_entity') { $content_entity_types[$entity_type_id] = $entity_type->getLabel(); } } return $content_entity_types; } /** * Gets a list of all the defined bundles for a content entity type. * * @return array * An array of bundles for a specific content entity type. */ private function getContentEntityBundles($entity_type) { $bundle_definitions = $this->entityTypeBundleInfo->getBundleInfo($entity_type); $bundles = []; foreach ($bundle_definitions as $bundle => $bundle_definition) { $bundles[$bundle] = $bundle_definition['label']; } return $bundles; } }