You are here

class AbjsTestForm in A/B Test JS 8

Same name and namespace in other branches
  1. 2.0.x src/Form/AbjsTestForm.php \Drupal\abjs\Form\AbjsTestForm

Class for build form test.

Hierarchy

Expanded class hierarchy of AbjsTestForm

1 string reference to 'AbjsTestForm'
abjs.routing.yml in ./abjs.routing.yml
abjs.routing.yml

File

src/Form/AbjsTestForm.php, line 14

Namespace

Drupal\abjs\Form
View source
class AbjsTestForm extends FormBase {

  /**
   * Provides a class for obtaining system time.
   *
   * @var \Drupal\Component\Datetime\Time
   */
  protected $time;

  /**
   * Provides database connection service.
   *
   * @var \Drupal\Core\Database\Database
   */
  protected $database;

  /**
   * Class constructor.
   */
  public function __construct(Connection $database, Time $time) {
    $this->database = $database;
    $this->time = $time;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static($container
      ->get('database'), $container
      ->get('datetime.time'));
  }

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'abjs_test';
  }

  /**
   * Building the form.
   *
   * @param array $form
   *   The form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The state of forms.
   * @param int $tid
   *   The ID of the item.
   */
  public function buildForm(array $form, FormStateInterface $form_state, $tid = NULL) {
    $form = [];
    $test_name_default = "";
    $test_active_default = 0;
    if (!empty($tid)) {

      // Retrieve the test to prefill the edit form.
      $test_result = $this->database
        ->query('SELECT name, active FROM {abjs_test} WHERE tid = :tid', [
        ':tid' => $tid,
      ]);
      $test = $test_result
        ->fetchObject();
      if (empty($test)) {
        $this
          ->messenger()
          ->addMessage($this
          ->t('The requested test does not exist.'), 'error');
        return $form;
      }
      $test_name_default = $test->name;
      $test_active_default = $test->active;
      $form['tid'] = [
        '#type' => 'value',
        '#value' => $tid,
      ];
    }

    // Because we have many fields with the same values, we have to set
    // #tree to be able to access them.
    $form['#tree'] = TRUE;
    $form['name'] = [
      '#type' => 'textfield',
      '#title' => $this
        ->t('Test Name'),
      '#default_value' => $test_name_default,
      '#size' => 30,
      '#maxlength' => 50,
      '#required' => TRUE,
    ];

    // Make select list of conditions.
    $conditions = $this->database
      ->query("SELECT cid, name FROM {abjs_condition} ORDER BY cid ASC, created DESC");
    $options_array = [
      0 => $this
        ->t('Select Condition'),
    ];
    foreach ($conditions as $condition) {
      $options_array[$condition->cid] = $condition->name . ' (c_' . $condition->cid . ')';
    }

    // Group conditions together, and allow for adding and removing conditions
    // via AJAX incide this fieldset.
    $form['conditions_fieldset'] = [
      '#type' => 'fieldset',
      '#title' => $this
        ->t('Conditions'),
      // Set up the wrapper so that AJAX will be able to replace the fieldset.
      '#prefix' => '<div id="conditions-fieldset-wrapper">',
      '#suffix' => '</div>',
      '#description' => $this
        ->t('Select Conditions for which the test will apply. All conditions must must be satisfied for the test to apply'),
    ];
    $existing_conditions_count = 0;
    if (!$form_state
      ->has('num_conditions')) {

      // On initial load, get the number of condition select fields (1 for add
      // form, query the number for edit form).
      $form_state
        ->set('num_conditions', 1);
      if (!empty($tid)) {
        $existing_conditions = $this->database
          ->query("SELECT cid FROM {abjs_test_condition} WHERE tid = :tid", [
          ':tid' => $tid,
        ])
          ->fetchAll();
        if (!empty($existing_conditions)) {
          $existing_conditions_count = count($existing_conditions);
          $form_state
            ->set('num_conditions', $existing_conditions_count);
        }
      }
    }

    // Prefill all the condition select fields that exist.
    for ($i = 0; $i < $existing_conditions_count; $i++) {
      $form['conditions_fieldset']['conditions'][$i] = [
        '#type' => 'select',
        '#title' => $this
          ->t('Select Condition'),
        '#options' => $options_array,
        '#default_value' => $existing_conditions[$i]->cid,
        '#required' => TRUE,
      ];
    }

    // Add number of sesgment fields determined by use of Ajax Add and
    // remove buttons.
    for ($i = $existing_conditions_count; $i < $form_state
      ->get('num_conditions'); $i++) {
      $form['conditions_fieldset']['conditions'][$i] = [
        '#type' => 'select',
        '#title' => $this
          ->t('Select Condition'),
        '#options' => $options_array,
        '#default_value' => 0,
        '#required' => TRUE,
      ];
    }

    // Ajax add button.
    $form['conditions_fieldset']['add_condition'] = [
      '#type' => 'submit',
      '#value' => $this
        ->t('Add'),
      '#name' => 'add-condition',
      '#submit' => [
        '::abjsAjaxAddCondition',
      ],
      '#ajax' => [
        'callback' => '::abjsAjaxConditionsCallback',
        'wrapper' => 'conditions-fieldset-wrapper',
      ],
      '#limit_validation_errors' => [],
    ];

    // Ajax Remove button.
    if ($form_state
      ->get('num_conditions') > 1) {
      $form['conditions_fieldset']['remove_condition'] = [
        '#type' => 'submit',
        '#value' => $this
          ->t('Remove'),
        '#name' => 'remove-condition',
        '#submit' => [
          '::abjsAjaxRemoveCondition',
        ],
        '#ajax' => [
          'callback' => '::abjsAjaxConditionsCallback',
          'wrapper' => 'conditions-fieldset-wrapper',
        ],
        '#limit_validation_errors' => [],
      ];
    }

    // Now do the same for experiences.
    // Make select list of experiences.
    $experiences = $this->database
      ->query("SELECT eid, name FROM {abjs_experience} ORDER BY changed DESC, created DESC");
    $options_array = [
      0 => $this
        ->t('Select Experience'),
    ];
    foreach ($experiences as $experience) {
      $options_array[$experience->eid] = $experience->name . ' (e_' . $experience->eid . ')';
    }

    // Group experiences together, and allow for adding and removing experiences
    // via AJAX incide this fieldset.
    $form['experiences_fieldset'] = [
      '#type' => 'fieldset',
      '#title' => $this
        ->t('Experiences'),
      // Set up the wrapper so that AJAX will be able to replace the fieldset.
      '#prefix' => '<div id="experiences-fieldset-wrapper">',
      '#suffix' => '</div>',
      '#description' => $this
        ->t('Select one or more Experiences for the test, and assign fractions to each Experience (e.g. 1/2, 1/3, 0, 1, 0.5, .95, etc...). You cannot use the same Experience ID twice in the same test, so you must duplicate an Experience to use it twice.'),
    ];
    $existing_experiences_count = 0;
    if (!$form_state
      ->has('num_experiences')) {

      // On initial load, get the number of experience select fields (1 for add
      // form, query the number for edit form).
      $form_state
        ->set('num_experiences', 1);
      if (!empty($tid)) {
        $existing_experiences = $this->database
          ->query("SELECT eid, fraction FROM {abjs_test_experience} WHERE tid = :tid", [
          ':tid' => $tid,
        ])
          ->fetchAll();
        if (!empty($existing_experiences)) {
          $existing_experiences_count = count($existing_experiences);
          $form_state
            ->set('num_experiences', $existing_experiences_count);
        }
      }
    }

    // Prefill all the experience select fields that exist, including fractions
    // for each experience.
    for ($i = 0; $i < $existing_experiences_count; $i++) {
      $form['experiences_fieldset']['experiences'][$i]['experience'] = [
        '#type' => 'select',
        '#title' => $this
          ->t('Experience %i', [
          '%i' => $i + 1,
        ]),
        '#options' => $options_array,
        '#default_value' => $existing_experiences[$i]->eid,
        '#required' => TRUE,
      ];
      $form['experiences_fieldset']['experiences'][$i]['fraction'] = [
        '#type' => 'textfield',
        '#title' => $this
          ->t('Experience %i Fraction', [
          '%i' => $i + 1,
        ]),
        '#default_value' => $existing_experiences[$i]->fraction,
        '#size' => 5,
        '#maxlength' => 10,
        '#required' => TRUE,
      ];
    }

    // Add number of experience fields determined by use of Ajax Add and remove
    // buttons.
    for ($i = $existing_experiences_count; $i < $form_state
      ->get('num_experiences'); $i++) {
      $form['experiences_fieldset']['experiences'][$i]['experience'] = [
        '#type' => 'select',
        '#title' => $this
          ->t('Experience %i', [
          '%i' => $i + 1,
        ]),
        '#options' => $options_array,
        '#default_value' => 0,
        '#required' => TRUE,
      ];
      $form['experiences_fieldset']['experiences'][$i]['fraction'] = [
        '#type' => 'textfield',
        '#title' => $this
          ->t('Experience %i Fraction', [
          '%i' => $i + 1,
        ]),
        '#default_value' => '',
        '#size' => 5,
        '#maxlength' => 10,
        '#required' => TRUE,
      ];
    }

    // Ajax add button.
    $form['experiences_fieldset']['add_experience'] = [
      '#type' => 'submit',
      '#value' => $this
        ->t('Add'),
      '#name' => 'add-experience',
      '#submit' => [
        '::abjsAjaxAddExperience',
      ],
      '#ajax' => [
        'callback' => '::abjsAjaxExperiencesCallback',
        'wrapper' => 'experiences-fieldset-wrapper',
      ],
      '#limit_validation_errors' => [],
    ];

    // Ajax Remove button.
    if ($form_state
      ->get('num_experiences') > 1) {
      $form['experiences_fieldset']['remove_experience'] = [
        '#type' => 'submit',
        '#value' => $this
          ->t('Remove'),
        '#name' => 'remove-experience',
        '#submit' => [
          '::abjsAjaxRemoveExperience',
        ],
        '#ajax' => [
          'callback' => '::abjsAjaxExperiencesCallback',
          'wrapper' => 'experiences-fieldset-wrapper',
        ],
        '#limit_validation_errors' => [],
      ];
    }

    // Add selector for activating/deactivating test.
    $form['active'] = [
      '#type' => 'select',
      '#title' => $this
        ->t('Status'),
      '#options' => [
        0 => $this
          ->t('Inactive'),
        1 => $this
          ->t('Active'),
      ],
      '#default_value' => $test_active_default,
    ];

    // Save test.
    $form['actions']['save'] = [
      '#type' => 'submit',
      '#value' => $this
        ->t('Save'),
      '#weight' => 5,
      '#validate' => [
        '::validateTest',
      ],
      '#submit' => [
        '::saveTest',
      ],
      '#attributes' => [
        'class' => [
          "button button-action button--primary",
        ],
      ],
    ];

    // Cancel test and return to admin tests page.
    $form['actions']['cancel'] = [
      '#type' => 'submit',
      '#value' => $this
        ->t('Cancel'),
      '#weight' => 10,
      '#submit' => [
        '::cancelTest',
      ],
      '#limit_validation_errors' => [],
    ];

    // Delete test.
    if (!empty($tid)) {
      $form['actions']['delete'] = [
        '#type' => 'submit',
        '#value' => $this
          ->t('Delete'),
        '#weight' => 15,
        '#submit' => [
          '::deleteTest',
        ],
      ];
    }
    return $form;
  }

  /**
   * Adds condition select fields to AbjsTestForm.
   */
  public function abjsAjaxAddCondition(array $form, FormStateInterface $form_state) {
    $form_state
      ->set('num_conditions', $form_state
      ->get('num_conditions') + 1);
    $form_state
      ->setRebuild();
  }

  /**
   * Assigns Ajax changes to conditions fieldset in AbjsTestForm.
   */
  public function abjsAjaxConditionsCallback(array $form, FormStateInterface $form_state) {
    return $form['conditions_fieldset'];
  }

  /**
   * Removes condition select fields to AbjsTestForm.
   */
  public function abjsAjaxRemoveCondition(array $form, FormStateInterface $form_state) {
    if ($form_state
      ->get('num_conditions') > 1) {
      $form_state
        ->set('num_conditions', $form_state
        ->get('num_conditions') - 1);
    }
    $form_state
      ->setRebuild();
  }

  /**
   * Adds experience select fields and fractions to AbjsTestForm.
   */
  public function abjsAjaxAddExperience(array $form, FormStateInterface $form_state) {
    $form_state
      ->set('num_experiences', $form_state
      ->get('num_experiences') + 1);
    $form_state
      ->setRebuild();
  }

  /**
   * Assigns Ajax changes to experiences fieldset in AbjsTestForm.
   */
  public function abjsAjaxExperiencesCallback(array $form, FormStateInterface $form_state) {
    return $form['experiences_fieldset'];
  }

  /**
   * Removes experience select fields and fractions to AbjsTestForm.
   */
  public function abjsAjaxRemoveExperience(array $form, FormStateInterface $form_state) {
    if ($form_state
      ->get('num_experiences') > 1) {
      $form_state
        ->set('num_experiences', $form_state
        ->get('num_experiences') - 1);
    }
    $form_state
      ->setRebuild();
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
  }

  /**
   * Validate the form.
   *
   * @param array $form
   *   The form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The state of forms.
   */
  public function validateTest(array &$form, FormStateInterface $form_state) {
    for ($i = 0; $i < count($form_state
      ->getValue([
      'experiences_fieldset',
      'experiences',
    ])); $i++) {
      if (!preg_match('#^[0-9./]+$#', $form_state
        ->getValue([
        'experiences_fieldset',
        'experiences',
      ])[$i]['fraction'])) {
        $form_state
          ->setErrorByName("experiences_fieldset][experiences][{$i}][fraction", $this
          ->t('Invalid character used in Experience @i Fraction. Only numbers, decimals, and slashes are allowed. Other characters, including spaces, are not allowed.', [
          '@i' => $i + 1,
        ]));
      }
    }
  }

  /**
   * Save data.
   *
   * @param array $form
   *   The form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The state of forms.
   */
  public function saveTest(array &$form, FormStateInterface $form_state) {
    $user = $this
      ->currentUser();
    if ($form_state
      ->hasValue('tid')) {

      // This is an existing test, so update instead of insert.
      $tid = $form_state
        ->getValue('tid');
      $this->database
        ->update('abjs_test')
        ->fields([
        'name' => $form_state
          ->getValue('name'),
        'active' => $form_state
          ->getValue('active'),
        'changed' => $this->time
          ->getRequestTime(),
        'changed_by' => $user
          ->id(),
      ])
        ->condition('tid', $tid, '=')
        ->execute();

      // Delete all entries in the test-condition and test-experience tables to
      // make life easy. Re-insert them later in this function.
      $this->database
        ->delete('abjs_test_condition')
        ->condition('tid', $tid)
        ->execute();
      $this->database
        ->delete('abjs_test_experience')
        ->condition('tid', $tid)
        ->execute();
    }
    else {

      // This is a new test, so insert it.
      $tid = $this->database
        ->insert('abjs_test')
        ->fields([
        'name' => $form_state
          ->getValue('name'),
        'active' => $form_state
          ->getValue('active'),
        'created' => $this->time
          ->getRequestTime(),
        'created_by' => $user
          ->id(),
        'changed' => $this->time
          ->getRequestTime(),
        'changed_by' => $user
          ->id(),
      ])
        ->execute();
    }

    // Whether new or existing test, insert conditions and experiences for this
    // test into the test-condition and test-experience tables. If this is an
    // existing test, an earlier step deleted all the existing rows for this
    // test in these tables. Using db_merge instead of db_insert in case
    // multiple of the same condition or experience were entered, in which case
    // this will collapse them to one.
    foreach ($form_state
      ->getValue([
      'conditions_fieldset',
      'conditions',
    ]) as $cid) {
      if ($cid > 0) {
        $this->database
          ->merge('abjs_test_condition')
          ->key([
          'tid' => $tid,
          'cid' => $cid,
        ])
          ->fields([
          'tid' => $tid,
          'cid' => $cid,
        ])
          ->execute();
      }
    }
    foreach ($form_state
      ->getValue([
      'experiences_fieldset',
      'experiences',
    ]) as $experience) {
      if (isset($experience['experience']) && $experience['experience'] > 0) {
        $this->database
          ->merge('abjs_test_experience')
          ->key([
          'tid' => $tid,
          'eid' => $experience['experience'],
        ])
          ->fields([
          'tid' => $tid,
          'eid' => $experience['experience'],
          'fraction' => $experience['fraction'],
        ])
          ->execute();
      }
    }
    $msg = $form_state
      ->hasValue('tid') ? $this
      ->t("Successfully updated test") : $this
      ->t("Successfully saved new test");
    $this
      ->messenger()
      ->addMessage($msg);
    $form_state
      ->setRedirect('abjs.test_admin');
  }

  /**
   * Cancel the action.
   *
   * @param array $form
   *   The form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The state of forms.
   */
  public function cancelTest(array &$form, FormStateInterface $form_state) {
    $form_state
      ->setRedirect('abjs.test_admin');
  }

  /**
   * Delete item.
   *
   * @param array $form
   *   The form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The state of forms.
   */
  public function deleteTest(array &$form, FormStateInterface $form_state) {
    $form_state
      ->setRedirect('abjs.test_delete_confirm_form', [
      'tid' => $form_state
        ->getValue('tid'),
    ]);
  }

}

Members

Namesort descending Modifiers Type Description Overrides
AbjsTestForm::$database protected property Provides database connection service.
AbjsTestForm::$time protected property Provides a class for obtaining system time.
AbjsTestForm::abjsAjaxAddCondition public function Adds condition select fields to AbjsTestForm.
AbjsTestForm::abjsAjaxAddExperience public function Adds experience select fields and fractions to AbjsTestForm.
AbjsTestForm::abjsAjaxConditionsCallback public function Assigns Ajax changes to conditions fieldset in AbjsTestForm.
AbjsTestForm::abjsAjaxExperiencesCallback public function Assigns Ajax changes to experiences fieldset in AbjsTestForm.
AbjsTestForm::abjsAjaxRemoveCondition public function Removes condition select fields to AbjsTestForm.
AbjsTestForm::abjsAjaxRemoveExperience public function Removes experience select fields and fractions to AbjsTestForm.
AbjsTestForm::buildForm public function Building the form. Overrides FormInterface::buildForm
AbjsTestForm::cancelTest public function Cancel the action.
AbjsTestForm::create public static function Instantiates a new instance of this class. Overrides FormBase::create
AbjsTestForm::deleteTest public function Delete item.
AbjsTestForm::getFormId public function Returns a unique string identifying the form. Overrides FormInterface::getFormId
AbjsTestForm::saveTest public function Save data.
AbjsTestForm::submitForm public function Form submission handler. Overrides FormInterface::submitForm
AbjsTestForm::validateTest public function Validate the form.
AbjsTestForm::__construct public function Class constructor.
DependencySerializationTrait::$_entityStorages protected property An array of entity type IDs keyed by the property name of their storages.
DependencySerializationTrait::$_serviceIds protected property An array of service IDs keyed by property name used for serialization.
DependencySerializationTrait::__sleep public function 1
DependencySerializationTrait::__wakeup public function 2
FormBase::$configFactory protected property The config factory. 1
FormBase::$requestStack protected property The request stack. 1
FormBase::$routeMatch protected property The route match.
FormBase::config protected function Retrieves a configuration object.
FormBase::configFactory protected function Gets the config factory for this form. 1
FormBase::container private function Returns the service container.
FormBase::currentUser protected function Gets the current user.
FormBase::getRequest protected function Gets the request object.
FormBase::getRouteMatch protected function Gets the route match.
FormBase::logger protected function Gets the logger for a specific channel.
FormBase::redirect protected function Returns a redirect response object for the specified route. Overrides UrlGeneratorTrait::redirect
FormBase::resetConfigFactory public function Resets the configuration factory.
FormBase::setConfigFactory public function Sets the config factory for this form.
FormBase::setRequestStack public function Sets the request stack object to use.
FormBase::validateForm public function Form validation handler. Overrides FormInterface::validateForm 62
LinkGeneratorTrait::$linkGenerator protected property The link generator. 1
LinkGeneratorTrait::getLinkGenerator Deprecated protected function Returns the link generator.
LinkGeneratorTrait::l Deprecated protected function Renders a link to a route given a route name and its parameters.
LinkGeneratorTrait::setLinkGenerator Deprecated public function Sets the link generator service.
LoggerChannelTrait::$loggerFactory protected property The logger channel factory service.
LoggerChannelTrait::getLogger protected function Gets the logger for a specific channel.
LoggerChannelTrait::setLoggerFactory public function Injects the logger channel factory.
MessengerTrait::$messenger protected property The messenger. 29
MessengerTrait::messenger public function Gets the messenger. 29
MessengerTrait::setMessenger public function Sets the messenger.
RedirectDestinationTrait::$redirectDestination protected property The redirect destination service. 1
RedirectDestinationTrait::getDestinationArray protected function Prepares a 'destination' URL query parameter for use with \Drupal\Core\Url.
RedirectDestinationTrait::getRedirectDestination protected function Returns the redirect destination service.
RedirectDestinationTrait::setRedirectDestination public function Sets the redirect destination service.
StringTranslationTrait::$stringTranslation protected property The string translation service. 1
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.
UrlGeneratorTrait::$urlGenerator protected property The url generator.
UrlGeneratorTrait::getUrlGenerator Deprecated protected function Returns the URL generator service.
UrlGeneratorTrait::setUrlGenerator Deprecated public function Sets the URL generator service.
UrlGeneratorTrait::url Deprecated protected function Generates a URL or path for a specific route based on the given parameters.