quiz-6.0.0-alpha4/src/Form/QuizQuestionsForm.php

src/Form/QuizQuestionsForm.php
<?php

namespace Drupal\quiz\Form;

use Drupal\Component\Datetime\TimeInterface;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Link;
use Drupal\Core\Messenger\MessengerTrait;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Routing\RedirectDestinationInterface;
use Drupal\Core\Url;
use Drupal\quiz\Entity\Quiz;
use Drupal\quiz\Entity\QuizQuestionRelationship;
use Drupal\quiz\Util\QuizUtil;
use Symfony\Component\DependencyInjection\ContainerInterface;
use function count;
use function quiz_get_question_types;

/**
 * Form to manage questions in a quiz.
 */
class QuizQuestionsForm extends FormBase {

  use MessengerTrait;

  /**
   * QuizQuestionAnsweringForm constructor.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   The entity type manager.
   * @param \Drupal\Component\Datetime\TimeInterface $time
   *   The time service.
   * @param \Drupal\Core\Routing\RedirectDestinationInterface $destination
   *   The redirect destination helper.
   * @param \Drupal\Core\Database\Connection $connection
   *   The current active database's master connection.
   * @param \Drupal\Core\Render\RendererInterface $renderer
   *   The renderer to use.
   */
  public function __construct(
    protected EntityTypeManagerInterface $entityTypeManager,
    protected TimeInterface $time,
    protected RedirectDestinationInterface $destination,
    protected Connection $connection,
    protected RendererInterface $renderer,
  ) {
  }

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

  /**
   * Fields for creating new questions are added to the quiz_questions_form.
   *
   * @param array $form
   *   FAPI form(array).
   * @param array $types
   *   All the question types(array).
   * @param \Drupal\quiz\Entity\Quiz $quiz
   *   The quiz node.
   */
  public function quizAddFieldsCreatingQuestions(array &$form, array &$types, Quiz $quiz): void {
    // Display links to create other questions.
    $form['additional_questions'] = [
      '#type' => 'details',
      '#title' => $this->t('Create new question'),
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
    ];

    $create_question = FALSE;

    $entity_manager = $this->entityTypeManager;
    $access_handler = $entity_manager->getAccessControlHandler('quiz_question');

    foreach ($types as $type => $info) {

      $options = [
        'query' => [
          'qid' => $quiz->id(),
          'vid' => $quiz->getRevisionId(),
        ],
        'attributes' => [
          'class' => 'use-ajax',
          'data-dialog-type' => 'modal',
          'data-dialog-options' => Json::encode(['width' => 800]),
        ],
      ];

      $access = $access_handler->createAccess($type);
      if ($access) {
        $create_question = TRUE;
      }
      $url = Url::fromRoute('entity.quiz_question.add_form', ['quiz_question_type' => $type], $options);
      $form['additional_questions'][$type] = [
        '#markup' => '<div class="add-questions">' .
        Link::fromTextAndUrl($info['label'], $url)->toString() . '</div>',
        '#access' => $access,
      ];
    }
    if (!$create_question) {
      $form['additional_questions']['create'] = [
        '#type' => 'markup',
        '#markup' => $this->t('You have not enabled any question type module or no has permission been given to create any question.'),
        // @todo revisit UI text
      ];
    }
  }

  /**
   * Handles "manage questions" tab.
   *
   * Displays form which allows questions to be assigned to the given quiz.
   *
   * This function is not used if the question assignment type "categorized
   * random questions" is chosen.
   */
  public function buildForm(array $form, FormStateInterface $form_state): array {

    $types = quiz_get_question_types();
    $quiz = $form_state->getBuildInfo()['args'][0];
    $this->quizAddFieldsCreatingQuestions($form, $types, $quiz);

    $header = ['Question', 'Type', 'Max score', 'Auto max score'];
    if ($quiz->get('randomization')->getString() == 2) {
      $header[] = 'Required';
    }
    $header = array_merge($header, [
      'Revision',
      'Operations',
      'Weight',
      'Parent',
    ]);

    // Display questions in this quiz.
    $form['question_list'] = [
      '#type' => 'table',
      '#title' => $this->t('Questions in this @quiz', ['@quiz' => QuizUtil::getQuizName()]),
      '#header' => $header,
      '#empty' => $this->t('There are currently no questions in this @quiz. Assign existing questions by using the question browser below. You can also use the links above to create new questions.', ['@quiz' => QuizUtil::getQuizName()]),
      '#tabledrag' => [
        [
          'action' => 'match',
          'relationship' => 'parent',
          'group' => 'qqr-pid',
          'source' => 'qqr-id',
          'hidden' => TRUE,
          'limit' => 1,
        ],
        [
          'action' => 'order',
          'relationship' => 'sibling',
          'group' => 'table-sort-weight',
        ],
      ],
    ];

    // @todo deal with $include_random.
    $all_questions = $quiz->getQuestions();

    uasort($all_questions, [static::class, 'sortQuestions']);
    $questions = [];
    foreach ($all_questions as $qqr_id => $question) {
      if (!$question->get('qqr_pid')->getString()) {
        // This is a parent question.
        $questions[$qqr_id] = $question;
        $questions += $this->getSubQuestions($question, $all_questions);
      }
    }

    // We add the questions to the form array.
    $this->quizAddQuestionToForm($form, $questions, $quiz, $types);

    // @todo Show the number of questions in the table header.
    $always_count = isset($form['question_list']['titles']) ?
      count($form['question_list']['titles']) : 0;
    // $form['question_list']['#title'] .= ' (' . $always_count . ')';
    // Timestamp is needed to avoid multiple users editing the same quiz at the
    // same time.
    $form['timestamp'] = [
      '#type' => 'hidden',
      '#default_value' => $this->time->getRequestTime(),
    ];

    $form['actions']['#type'] = 'actions';

    $form['actions']['submit'] = [
      '#type' => 'submit',
      '#value' => $this->t('Submit'),
    ];

    // Give the user the option to create a new revision of the quiz.
    $this->quizAddRevisionCheckbox($form, $quiz);

    return $form;
  }

  /**
   * Set the sub-questions.
   *
   * @return array
   *   of QuizQuestion
   */
  public function getSubQuestions($root_question, $all_questions): array {
    $append = [];
    foreach ($all_questions as $sub_question) {
      if ($root_question->id() == $sub_question->get('qqr_pid')->getString()) {
        // Question is a leaf of this parent.
        $append[$sub_question->id()] = $sub_question;
      }
    }
    return $append;
  }

  /**
   * Entity type sorter for quiz questions.
   */
  public function sortQuestions($a, $b): int {
    $aw = $a->get('weight')->getString();
    $bw = $b->get('weight')->getString();
    if ($aw == $bw) {
      return 0;
    }
    return ($aw < $bw) ? -1 : 1;
  }

  /**
   * {@inheritdoc}
   */
  public function getFormId(): string {
    return 'quiz_questions_form';
  }

  /**
   * {@inheritdoc}
   */
  public function validateForm(array &$form, FormStateInterface $form_state): void {
    $question_list = $form_state->getValue('question_list');
    // Return if question list is empty.
    if (!$question_list) {
      return;
    }
    foreach ($question_list as $index => $row) {
      foreach ($row as $name => $value) {
        if ($name == 'max_score' && (!isset($value) || $value === '')) {
          $form_state->setError($form['question_list'][$index]['max_score'], $this->t('Max score cannot be empty.'));
        }
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state): void {
    $question_list = $form_state->getValue('question_list');
    // Return if question list is empty.
    if (!$question_list) {
      return;
    }
    foreach ($question_list as $qqr_id => $row) {
      $qqr = QuizQuestionRelationship::load($qqr_id);
      foreach ($row as $name => $value) {
        if ($name == 'qqr_pid' && empty($value)) {
          $value = NULL;
        }

        $qqr->set($name, $value);
      }
      $qqr->save();
    }

    $this->messenger()->addMessage($this->t('Questions updated successfully.'));
  }

  /**
   * Adds checkbox for creating new revision.
   *
   * Checks it by default if answers exists.
   *
   * @param array $form
   *   FAPI form(array).
   * @param \Drupal\quiz\Entity\Quiz $quiz
   *   Quiz node(object).
   */
  public function quizAddRevisionCheckbox(array &$form, Quiz $quiz): void {
    if ($quiz->hasAttempts()) {
      $results_url = Url::fromRoute('view.quiz_results.list', ['quiz' => $quiz->id()])
        ->toString();
      $quiz_url = Url::fromRoute('entity.quiz.edit_form', ['quiz' => $quiz->id()], [
        'query' => $this->destination->getAsArray(),
      ])->toString();
      $form['revision_help'] = [
        '#markup' => $this->t('This quiz has been answered. To make changes to the quiz you must either <a href="@results_url">delete all results</a> or <a href="@quiz_url">create a new revision</a>. This includes deleting any questions.', [
          '@results_url' => $results_url,
          '@quiz_url' => $quiz_url,
        ]),
      ];
      $form['actions']['submit']['#access'] = FALSE;
    }
  }

  /**
   * Adds the questions in the $questions array to the form.
   *
   * @param array $form
   *   FAPI form(array).
   * @param \Drupal\Quiz\Entity\QuizQuestionRelationship[] $questions
   *   The questions to be added to the question list(array).
   * @param \Drupal\quiz\Entity\Quiz $quiz
   *   The quiz.
   * @param array $question_types
   *   Array of all available question types.
   *
   * @todo Not bringing in revision data yet.
   */
  public function quizAddQuestionToForm(array &$form, array &$questions, Quiz &$quiz, array &$question_types): void {
    foreach ($questions as $id => $question_relationship) {
      $question_vid = $question_relationship->get('question_vid')->getString();

      /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
      $storage = $this->entityTypeManager->getStorage('quiz_question');
      /** @var \Drupal\quiz\Entity\QuizQuestion $quiz_question */
      $quiz_question = $storage->loadRevision($question_vid);

      if (!$quiz_question) {
        // Question no longer exists.
        // @see https://www.drupal.org/project/quiz/issues/2923829
        continue;
      }

      $table = &$form['question_list'];

      $view_url = Url::fromRoute('entity.quiz_question.canonical', ['quiz_question' => $quiz_question->id()], [
        'attributes' => [
          'class' => 'use-ajax',
          'data-dialog-type' => 'modal',
          'data-dialog-options' => Json::encode(['width' => 800]),
        ],
        'query' => $this->destination->getAsArray(),
      ]);

      $edit_url = Url::fromRoute('entity.quiz_question.edit_form', ['quiz_question' => $quiz_question->id()], [
        'attributes' => [
          'class' => 'use-ajax',
          'data-dialog-type' => 'modal',
          'data-dialog-options' => Json::encode(['width' => 800]),
        ],
        'query' => $this->destination->getAsArray(),
      ]);

      $remove_url = Url::fromRoute('entity.quiz_question_relationship.delete_form', ['quiz_question_relationship' => $question_relationship->id()], [
        'attributes' => [
          'class' => 'use-ajax',
          'data-dialog-type' => 'modal',
        ],
        'query' => $this->destination->getAsArray(),
      ]);

      if ($quiz_question->access('view')) {
        $question_titles = [
          '#markup' => Link::fromTextAndUrl($quiz_question->get('title')
            ->getString(), $view_url)->toString(),
        ];
      }
      else {
        $question_titles = [
          '#plain_text' => $quiz_question->get('title')
            ->getString(),
        ];
      }

      $table[$id]['#attributes']['class'][] = 'draggable';

      if ($quiz_question->bundle() != 'page') {
        $table[$id]['#attributes']['class'][] = 'tabledrag-leaf';
      }

      $table[$id]['title'] = $question_titles;

      if ($question_relationship->get('qqr_pid')->getString()) {
        $indentation = [
          '#theme' => 'indentation',
          '#size' => 1,
        ];
        $table[$id]['title']['#prefix'] = $this->renderer->render($indentation);
      }

      $table[$id]['type'] = [
        '#markup' => $quiz_question->bundle(),
      ];

      // Toggle the max score input based on the auto max score checkbox
      // Hide for ungraded questions (directions, pages, etc.)
      $table[$id]['max_score'] = [
        '#type' => $quiz_question->isGraded() ? 'textfield' : 'hidden',
        '#size' => 2,
        '#disabled' => (bool) $question_relationship->get('auto_update_max_score')->getString(),
        '#default_value' => $question_relationship->get('max_score')->getString(),
        '#states' => [
          'disabled' => [
            "#edit-question-list-$id-auto-update-max-score" => ['checked' => TRUE],
          ],
        ],
      ];

      $table[$id]['auto_update_max_score'] = [
        '#type' => $quiz_question->isGraded() ? 'checkbox' : 'hidden',
        '#default_value' => $question_relationship->get('auto_update_max_score')
          ->getString() ?
        $question_relationship->get('auto_update_max_score')->getString() : 0,
      ];

      // Add checkboxes to mark compulsory questions for randomized quizzes.
      if ($quiz->get('randomization')->getString() == 2) {
        $table[$id]['question_status'] = [
          '#type' => 'checkbox',
          '#default_value' => $question_relationship->get('question_status')
            ->getString(),
        ];
      }

      $entity_manager = $this->entityTypeManager;
      $access_handler = $entity_manager->getAccessControlHandler('quiz_question');

      // Add a checkbox to update to the latest revision of the question.
      $latest_quiz_question = $this->entityTypeManager
        ->getStorage('quiz_question')
        ->load($quiz_question->id());
      if ($question_relationship->get('question_vid')->value ==
        $latest_quiz_question->getRevisionId()) {
        $update_cell = [
          '#markup' => $this->t('<em>Up to date</em>'),
        ];
      }
      else {
        $update_cell = [
          '#type' => 'checkbox',
          '#return_value' => $latest_quiz_question->getRevisionId(),
          '#title' => $this->t('Update to latest'),
        ];
      }
      $table[$id]['question_vid'] = $update_cell;

      $table[$id]['operations'] = [
        '#type' => 'operations',
        '#links' => [
          [
            'title' => $this->t('Edit'),
            'url' => $edit_url,
          ],
        ],
      ];

      if (!$quiz->hasAttempts()) {
        $table[$id]['operations']['#links'][] = [
          'title' => $this->t('Remove'),
          'url' => $remove_url,
        ];
      }

      $table[$id]['#weight'] = (int) $question_relationship->get('weight')
        ->getString();
      $table[$id]['weight'] = [
        '#title_display' => 'invisible',
        '#title' => $this
          ->t('Weight for ID @id', [
            '@id' => $id,
          ]),
        '#type' => 'number',
        '#default_value' => (int) $question_relationship->get('weight')
          ->getString(),
        '#attributes' => [
          'class' => [
            'table-sort-weight',
          ],
        ],
      ];

      $table[$id]['parent']['qqr_id'] = [
        '#title' => $this->t('Relationship ID'),
        '#type' => 'hidden',
        '#default_value' => $question_relationship->get('qqr_id')->getString(),
        '#attributes' => [
          'class' => [
            'qqr-id',
          ],
        ],
        '#parents' => [
          'question_list',
          $id,
          'qqr_id',
        ],
      ];

      $table[$id]['parent']['qqr_pid'] = [
        '#title' => $this->t('Parent ID'),
        '#title_display' => 'invisible',
        '#type' => 'number',
        '#size' => 3,
        '#min' => 0,
        '#default_value' => $question_relationship->get('qqr_pid')->getString(),
        '#attributes' => [
          'class' => [
            'qqr-pid',
          ],
        ],
        '#parents' => [
          'question_list',
          $id,
          'qqr_pid',
        ],
      ];
    }
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc