You are here

GConfigForm.php in Jammer 1.0.x

Namespace

Drupal\jammer\Form

File

src/Form/GConfigForm.php
View source
<?php

namespace Drupal\jammer\Form;

use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing;
use Drupal\user\Entity\Role;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Url;
use Drupal\Core\Link;
use Symfony\Component\HttpFoundation\RedirectResponse;
class GConfigForm extends ConfigFormBase {
  public function getFormId() {
    return 'jammer_config';
  }

  // returns TRUE if given $entry still present in $data (same form+element(s))
  private function entryMatch($entry, $data) {
    foreach ($data as $line) {
      if ($line['form'] != $entry['form']) {
        continue;

        // skip, not the same form
      }
      foreach ($line['elements'] as $el) {
        if (in_array($el, $entry['elements'])) {
          return TRUE;

          // at least one match
        }
      }
    }

    // no match
    return FALSE;
  }

  // build array for #rows of existing entries (for display in 'table')
  // TODO: this function may clearly be umproved
  private function buildTable($data, &$headers) {

    // create table headers
    $headers = [
      $this
        ->t('Form id'),
      $this
        ->t('Element id'),
      $this
        ->t('Hide/disable'),
      $this
        ->t('Exception'),
      $this
        ->t('Role(s)'),
      $this
        ->t('Invert roles'),
      $this
        ->t('Priority'),
      $this
        ->t('Comment'),
      $this
        ->t('Modify'),
      $this
        ->t('Delete'),
    ];

    // prepare array for per-group sorting
    $groups = [];
    foreach ($data as $entry) {
      if (isset($entry['group']) and !empty($entry['group'])) {
        $groups[$entry['group']] = $entry['group'];
      }
    }

    // sort ascending and insert before the "unsorted" group
    sort($groups, SORT_LOCALE_STRING);
    array_unshift($groups, $this
      ->t("Unsorted entries"));

    // prepare and store data in group-sorted array
    $content = [];
    foreach ($groups as $g) {
      $content[$g] = [];
    }
    foreach ($data as $cnt => $entry) {
      if (isset($entry['group']) and !empty($entry['group'])) {
        $target = $entry['group'];
      }
      else {
        $target = $this
          ->t("Unsorted entries");
      }
      $line = [];
      $line[] = $entry['form'];
      if (is_array($entry['elements'])) {
        $line[] = implode(" ", $entry['elements']);
      }
      else {
        $line[] = $entry['elements'];
      }
      if ($entry['remove'] == 0) {
        $line[] = $this
          ->t('Remove');
      }
      else {
        $line[] = $this
          ->t('Disable');
      }
      if ($entry['creation'] == 0) {
        $line[] = $this
          ->t('Always');
      }
      else {
        if ($entry['creation'] == 1) {
          $line[] = $this
            ->t('Exclude creation');
        }
        else {
          if ($entry['creation'] == 2) {
            $line[] = $this
              ->t('Only creation');
          }
          else {
            $line[] = $this
              ->t('Exclude when empty');
          }
        }
      }
      if (isset($entry['roles']) and is_array($entry['roles'])) {
        $line[] = implode("; ", $entry['roles']);
      }
      else {
        $line[] = "";
      }
      if ($entry['invert'] == 1) {
        $line[] = $this
          ->t("Yes");
      }
      else {
        $line[] = $this
          ->t("No");
      }
      $line[] = $entry['priority'];
      $line[] = $entry['comment'];

      // add modify/delete
      $t = Link::createFromRoute('Modify', 'jammer.jammer_config_action', [
        'action' => 'modify',
        'id' => $cnt,
      ]);
      $line[] = $t;
      $t = Link::createFromRoute('Delete', 'jammer.jammer_config_action', [
        'action' => 'delete',
        'id' => $cnt,
      ]);
      $line[] = $t;
      $content[$target][] = $line;
    }

    // flatten result (with groups as separated rows)
    $result = [];
    foreach ($content as $grp => $list) {
      $t = new FormattableMarkup("<b>• {$grp}</b>", []);
      $result[] = [
        $t,
        '',
        '',
        '',
        '',
        '',
        '',
        '',
        '',
      ];
      foreach ($list as $l) {
        $result[] = $l;
      }
    }
    return $result;
  }

  // explode list to array: used to convert comma-separated lists
  private function createArray($val) {
    $val = str_replace(";", " ", $val);
    $val = str_replace(",", " ", $val);
    $val = str_replace("  ", " ", $val);
    $val = str_replace("  ", " ", $val);
    $res = explode(" ", $val);
    $res2 = [];
    foreach ($res as $r) {
      $res2[] = trim($r);

      // proper data
    }
    return $res2;
  }

  // build data line (in module data format) from submited values
  private function buildLine($form_state) {
    $result = [];
    $result['form'] = $form_state
      ->getValue('target_form_id');
    $result['elements'] = $this
      ->createArray($form_state
      ->getValue('element_id'));
    $result['remove'] = $form_state
      ->getValue('remove_disable');
    $result['creation'] = $form_state
      ->getValue('always_creation');
    $result['roles'] = $form_state
      ->getValue('roles');

    // still an array
    $result['invert'] = $form_state
      ->getValue('invert_roles');
    $result['priority'] = $form_state
      ->getValue('priority');
    $result['group'] = $form_state
      ->getValue('group');
    $result['comment'] = $form_state
      ->getValue('comment');
    return $result;
  }

  // configuration form builder
  public function buildForm(array $form, FormStateInterface $form_state) {
    $weight = 0;
    $messenger = \Drupal::messenger();

    // get calling parameters
    $action = \Drupal::routeMatch()
      ->getParameter('action');
    $id = \Drupal::routeMatch()
      ->getParameter('id');

    // get our array of data
    $config = $this
      ->config('jammer.settings');
    $tmp = $config
      ->get('stored_values');
    $data = unserialize($tmp);
    $headers = [];

    // build table rows (for display, at end of the form)
    $rows_table = $this
      ->buildTable($data, $headers);

    // get list of nodes types and list of fields
    $node_types = \Drupal\node\Entity\NodeType::loadMultiple();
    $options = [];
    $list_elements = "<div><ul>";
    foreach ($node_types as $node_type) {
      $options[$node_type
        ->id()] = $node_type
        ->label();
      $fields_in = \Drupal::service('entity_field.manager')
        ->getFieldDefinitions('node', $node_type
        ->id());
      $list_elements .= "<li>" . $node_type
        ->label() . "</li><ul>";
      foreach ($fields_in as $field_in => $nop) {
        $list_elements .= "<li>{$field_in}</li>";
      }
      $list_elements .= "</ul>";
    }
    $list_elements .= "</ul></div>";

    // get list of roles
    $roles_list = Role::loadMultiple();
    $roles = [];
    foreach ($roles_list as $role_list) {
      $roles[$role_list
        ->id()] = $role_list
        ->label();
    }

    // if $id get the corresponding line
    if (isset($id) and isset($data[$id])) {
      $currentLine = $data[$id];
    }
    else {
      $currentLine = NULL;
    }

    // if action is "export" generates a dump of configuration
    if ($action == 'export') {
      $form['intro'] = [
        '#type' => 'textarea',
        '#rows' => 16,
        '#cols' => 64,
        '#default_value' => serialize($data),
        '#disabled' => 'disabled',
        '#weight' => $weight++,
      ];
      $form['post'] = [
        '#markup' => "<div><p>Save this dump and " . Link::createFromRoute('go back', 'jammer.jammer_config', [], [
          'attributes' => [
            'class' => 'button',
          ],
        ])
          ->toString() . " to configuration page.</p></div>",
        '#allowed_tags' => [
          'a',
          'div',
          'p',
        ],
        '#weight' => $weight++,
      ];
      $form2 = parent::buildForm($form, $form_state);
      $form2['actions']['submit']['#access'] = FALSE;
      return $form2;
    }

    // if action is "import" present a textarea to insert data and button to validate import
    if ($action == 'import') {
      $form['intro'] = [
        '#markup' => "<div><p>Copy export-data into following text area and click 'Import' button.</p><p>" . "Beware: if you import garbage, you will get garbage!</p></div>",
        '#allowed_tags' => [
          'div',
          'p',
        ],
        '#weight' => $weight++,
      ];
      $form['import'] = [
        '#type' => 'textarea',
        '#rows' => 16,
        '#cols' => 64,
        '#weight' => $weight++,
      ];
      $form['post'] = [
        '#markup' => "<div><p>Validate this import or " . Link::createFromRoute('go back', 'jammer.jammer_config', [], [
          'attributes' => [
            'class' => 'button',
          ],
        ])
          ->toString() . " if you are not sure.</p></div>",
        '#allowed_tags' => [
          'a',
          'div',
          'p',
        ],
        '#weight' => $weight++,
      ];
      $form2 = parent::buildForm($form, $form_state);
      $form2['actions']['submit']['#value'] = $this
        ->t('Import');
      return $form2;
    }

    // if action=modify prepare data for pre-fill fields
    if ($action == 'modify') {
      if (!$currentLine) {

        // call for 'modify' without valid line: redirect to base form
        $messenger
          ->addMessage($this
          ->t('Bad \'modify\' data. Ignored'), $messenger::TYPE_WARNING);

        // go back to default page
        return new redirectResponse(Url::fromRoute('jammer.jammer_config')
          ->toString());
      }
      else {

        // will be used to pre-fill fields
        $prefill = $currentLine;
      }
    }
    else {
      $prefill = NULL;
    }

    // if action=clear just show confirm button
    if ($action == 'clear') {

      // just display a custom confirm form
      $form['intro'] = [
        '#markup' => "<div>You are about to delete <b>all</b> the configuration. Please confirm or <b>" . Link::createFromRoute('go back', 'jammer.jammer_config', [], [
          'attributes' => [
            'class' => 'button',
          ],
        ])
          ->toString() . "</b>!</div>",
        '#allowed_tags' => [
          'div',
          'b',
          'a',
        ],
      ];
      $form2 = parent::buildForm($form, $form_state);
      $form2['actions']['submit']['#value'] = $this
        ->t('Confirm');
      return $form2;
    }

    // if action=delete just show confirm button
    if ($action == 'delete') {
      if (!$currentLine) {

        // call for 'delete' without valid line: redirect to base form
        $messenger
          ->addMessage($this
          ->t('Bad \'delete\' data. Ignored'), $messenger::TYPE_WARNING);

        // go back to default page
        return new redirectResponse(Url::fromRoute('jammer.jammer_config')
          ->toString());
      }

      // just display a custom confirm form (TODO: add data about concerned entry)
      $form['intro'] = [
        '#markup' => "<div>You are about to delete this entry. Please confirm or <b>" . Link::createFromRoute('go back', 'jammer.jammer_config', [], [
          'attributes' => [
            'class' => 'button',
          ],
        ])
          ->toString() . "</b>!</div>",
        '#allowed_tags' => [
          'div',
          'b',
          'a',
        ],
      ];
      $form2 = parent::buildForm($form, $form_state);
      $form2['actions']['submit']['#value'] = $this
        ->t('Confirm');
      return $form2;
    }

    // build our view/configuration form
    $form['intro'] = [
      '#markup' => "<div><b>Configuration panel for G-Jammer module.</b></div>" . "<div>You can also:<ul><li>" . Link::createFromRoute('export current configuration', 'jammer.jammer_config_action', [
        'action' => 'export',
        'id' => 'all',
      ])
        ->toString() . '</li><li>' . Link::createFromRoute('import a configuration', 'jammer.jammer_config_action', [
        'action' => 'import',
        'id' => 'all',
      ])
        ->toString() . '</li><li>' . Link::createFromRoute('clear the current configuration', 'jammer.jammer_config_action', [
        'action' => 'clear',
        'id' => 'all',
      ])
        ->toString() . '</li></ul>',
      '#allowed_tags' => [
        'div',
        'b',
        'a',
        'ul',
        'li',
      ],
      '#weight' => $weight++,
    ];
    $form['target_form_id'] = [
      '#type' => 'select',
      '#title' => $this
        ->t('Form id'),
      '#options' => $options,
      '#required' => TRUE,
      '#chosen' => TRUE,
      '#description' => $this
        ->t('The form id containing the element you want to remove'),
      '#weight' => $weight++,
    ];
    $form['element_id'] = [
      '#type' => 'textfield',
      '#title' => $this
        ->t('Element id'),
      '#required' => TRUE,
      '#size' => 512,
      '#description' => $this
        ->t('The element id of the element you want to remove. Can be a list (comma or space separated)'),
      '#weight' => $weight++,
    ];
    $form['existing_values'] = [
      '#type' => 'details',
      '#title' => $this
        ->t('Existing elements Id per node type'),
      '#open' => FALSE,
      '#weight' => $weight++,
    ];
    $form['existing_values']['text'] = [
      '#markup' => $list_elements,
      '#allowed_tags' => [
        'div',
        'ul',
        'li',
      ],
      '#weight' => $weight++,
    ];
    $form['remove_disable'] = [
      '#type' => 'radios',
      '#title' => $this
        ->t('Remove or disable element'),
      '#default_value' => 0,
      '#options' => [
        0 => $this
          ->t('Remove'),
        1 => $this
          ->t('Disable'),
      ],
      '#description' => $this
        ->t('Select if you want to remove (hide) or disable (read-only) element. Selecting disable has no effect on non-interactive elements'),
      '#weight' => $weight++,
    ];
    $form['always_creation'] = [
      '#type' => 'radios',
      '#title' => $this
        ->t('Always jam or exclude/only creation'),
      '#default_value' => 0,
      '#options' => [
        0 => $this
          ->t('Always'),
        1 => $this
          ->t('Exclude creation'),
        2 => $this
          ->t('Only creation'),
        3 => $this
          ->t('Exclude when empty'),
      ],
      '#description' => $this
        ->t('Select if jam is always performed or if a form used to create a new content should be ignored, or if it should only applies at creation. Exclude when empty makes the jam to be applied only when field is not empty. Beware that it makes sense only for text fields (or related fields)'),
      '#weight' => $weight++,
    ];
    $form['roles'] = [
      '#type' => 'select',
      '#title' => $this
        ->t('Only apply to role(s)'),
      '#options' => $roles,
      '#multiple' => TRUE,
      '#chosen' => TRUE,
      '#size' => 128,
      '#description' => $this
        ->t('Select role(s) for which element will be removed/disabled (if user has any of the roles listed here element will be hidden). By default all role(s) are selected except administrator'),
      '#weight' => $weight++,
    ];
    $form['invert_roles'] = [
      '#type' => 'checkbox',
      '#return_value' => 1,
      '#title' => $this
        ->t(' Reverse role(s)'),
      '#description' => $this
        ->t('When selected the jam will not be applied to listed role(s), and will apply to all other role(s)'),
      '#weight' => $weight++,
    ];
    $priorities = [];
    for ($i = -20; $i <= 20; $i++) {
      $priorities[$i] = $i;
    }
    $form['priority'] = [
      '#type' => 'select',
      '#title' => $this
        ->t('Priority'),
      '#options' => $priorities,
      '#default_value' => 0,
      '#description' => $this
        ->t('Rules are evaluated in top-bottom order of this value'),
      '#weight' => $weight++,
    ];
    $form['group'] = [
      '#type' => 'textfield',
      '#title' => $this
        ->t('Category/group'),
      '#size' => 32,
      '#maxlength' => 32,
      '#description' => $this
        ->t('Used to group elements together in summary list below'),
      '#weight' => $weight++,
    ];
    $form['comment'] = [
      '#type' => 'textfield',
      '#title' => $this
        ->t('Comment'),
      '#size' => 64,
      '#maxlength' => 64,
      '#description' => $this
        ->t('Associated comment'),
      '#weight' => $weight++,
    ];
    $form['validate'] = [
      '#type' => 'submit',
      '#value' => $this
        ->t('Validate'),
      '#button_type' => 'primary',
      '#weight' => $weight++,
    ];
    $form['table_list'] = [
      '#type' => 'table',
      '#caption' => $this
        ->t('Existing actions'),
      '#header' => $headers,
      '#rows' => $rows_table,
      '#weight' => $weight++,
    ];

    // if needed, set default values
    if ($prefill) {
      $form['target_form_id']['#default_value'] = $prefill['form'];
      $form['element_id']['#default_value'] = implode(" ", $prefill['elements']);
      $form['remove_disable']['#default_value'] = $prefill['remove'];
      $form['always_creation']['#default_value'] = $prefill['creation'];
      $form['roles']['#default_value'] = $prefill['roles'];
      $form['invert_roles']['#default_value'] = $prefill['invert'];
      $form['priority']['#default_value'] = $prefill['priority'];
      $form['group']['#default_value'] = $prefill['group'];
      $form['comment']['#default_value'] = $prefill['comment'];
    }

    // return the form
    $form2 = parent::buildForm($form, $form_state);

    // change submit button name (Note: can't figure how to *move' submit button
    // so I created an other one at the desired position)
    $form2['actions']['submit']['#value'] = $this
      ->t('Validate');
    return $form2;
  }

  // check data and submit change
  public function submitForm(array &$form, FormStateInterface $form_state) {
    $messenger = \Drupal::messenger();

    // get calling parameters
    $action = \Drupal::routeMatch()
      ->getParameter('action');
    $id = \Drupal::routeMatch()
      ->getParameter('id');

    // get our array of data
    $config = $this
      ->config('jammer.settings');
    $tmp = $config
      ->get('stored_values');
    $data = unserialize($tmp);

    // bad action value (TODO: improve routing to prevent this to happen)
    if (isset($action) and $action != 'delete' and $action != 'modify' and $action != 'import' and $action != 'export' and $action != 'clear') {

      // go back to main config page
      $form_state
        ->setRedirect('jammer.jammer_config');
      parent::submitForm($form, $form_state);
      $messenger
        ->deleteByType($messenger::TYPE_STATUS);
      $messenger
        ->addMessage($this
        ->t('Bad action type'), $messenger::TYPE_ERROR);
      return;
    }

    // should not occur
    if (isset($action) and $action == 'export') {

      // go back to main config page
      $form_state
        ->setRedirect('jammer.jammer_config');
      parent::submitForm($form, $form_state);
      $messenger
        ->deleteByType($messenger::TYPE_STATUS);
      $messenger
        ->addMessage($this
        ->t('Bad action type (\'export\' is not accessible to \'submit\')'), $messenger::TYPE_ERROR);
      return;
    }

    // request for clear
    if (isset($action) and $action == 'clear') {
      $data = [];
      $config
        ->set('stored_values', serialize($data));
      $config
        ->save();

      // go back to main config page
      $form_state
        ->setRedirect('jammer.jammer_config');
      parent::submitForm($form, $form_state);
      $messenger
        ->deleteByType($messenger::TYPE_STATUS);
      $messenger
        ->addMessage($this
        ->t('Configuration all cleared'), $messenger::TYPE_STATUS);
      return;
    }

    // request for import
    if (isset($action) and $action == 'import') {

      // get data to import
      $import = trim($form_state
        ->getValue('import'));

      // make some sanity checks on imported data
      $tmp = @unserialize($import);
      $ok = TRUE;
      if ($tmp === FALSE) {
        $ok = FALSE;
        $msg = $this
          ->t('corrupted data');
      }
      if (!is_array($tmp)) {
        $ok = FALSE;
        $msg = $this
          ->t('bad format');
      }

      // note: should add more sanity checks (data structure)
      if (!$ok) {

        // go back to main config page
        $form_state
          ->setRedirect('jammer.jammer_config');
        parent::submitForm($form, $form_state);
        $messenger
          ->deleteByType($messenger::TYPE_STATUS);
        $messenger
          ->addMessage($this
          ->t('Error with import-data: ') . $msg, $messenger::TYPE_ERROR);
        return;
      }

      // record it into configuration
      $config
        ->set('stored_values', $import);
      $config
        ->save();

      // go back to main config page
      $form_state
        ->setRedirect('jammer.jammer_config');
      parent::submitForm($form, $form_state);
      $messenger
        ->deleteByType($messenger::TYPE_STATUS);
      $messenger
        ->addMessage($this
        ->t('Configuration imported'), $messenger::TYPE_STATUS);
      return;
    }

    // request for deleting entry
    if (isset($action) and $action == 'delete') {

      // bad $id
      if (!isset($id) or !isset($data[$id])) {
        $messenger
          ->addMessage($this
          ->t('Bad or missing \'id\' for \'delete\' action'), $messenger::TYPE_ERROR);

        // go back to main config page
        $form_state
          ->setRedirect('jammer.jammer_config');
        parent::submitForm($form, $form_state);
        return;
      }

      // unset line and rebuild array
      unset($data[$id]);
      $data = array_values($data);

      // save new configuration
      $config
        ->set('stored_values', serialize($data));
      $config
        ->save();

      // go back to main config page
      $form_state
        ->setRedirect('jammer.jammer_config');
      parent::submitForm($form, $form_state);

      // this is to prevent the default submit message
      $messenger
        ->deleteByType($messenger::TYPE_STATUS);
      $messenger
        ->addMessage($this
        ->t('Entry deleted'), $messenger::TYPE_STATUS);
      return;
    }

    // request for modifying entry
    if (isset($action) and $action == 'modify') {

      // bad $id
      if (!isset($id) or !isset($data[$id])) {
        $messenger
          ->addMessage($this
          ->t('Bad or missing \'id\' for \'modify\' action'), $messenger::TYPE_ERROR);

        // go back to main config page
        $form_state
          ->setRedirect('jammer.jammer_config');
        parent::submitForm($form, $form_state);
        return;
      }

      // prepare new values for entry
      $entry = $this
        ->buildLine($form_state);

      // modify entry
      $data[$id] = $entry;

      // save new configuration
      $config
        ->set('stored_values', serialize($data));
      $config
        ->save();

      // go back to main config page
      $form_state
        ->setRedirect('jammer.jammer_config');
      parent::submitForm($form, $form_state);

      // this is to prevent the default submit message
      $messenger
        ->deleteByType($messenger::TYPE_STATUS);
      $messenger
        ->addMessage($this
        ->t('Entry modified'), $messenger::TYPE_STATUS);
      return;
    }

    // no action: request for creating entry
    $entry = $this
      ->buildLine($form_state);

    // check if still exists an entry with same form and field(s) and generate a warning
    $warn = $this
      ->entryMatch($entry, $data);

    // add entry
    $data[] = $entry;

    // save new configuration
    $config
      ->set('stored_values', serialize($data));
    $config
      ->save();
    parent::submitForm($form, $form_state);

    // this is to prevent the default submit message
    $messenger
      ->deleteByType($messenger::TYPE_STATUS);
    $messenger
      ->addMessage($this
      ->t('Entry added'), $messenger::TYPE_STATUS);
    if ($warn) {
      $messenger
        ->addMessage($this
        ->t('An entry still has this form and field(s). You should check for duplicated/conflicting entries'), $messenger::TYPE_WARNING);
    }
    return;
  }
  protected function getEditableConfigNames() {
    return [
      'jammer.settings',
    ];
  }

}

Classes

Namesort descending Description
GConfigForm