You are here

class QuizQuestionsForm in Quiz 8.6

Same name and namespace in other branches
  1. 8.5 src/Form/QuizQuestionsForm.php \Drupal\quiz\Form\QuizQuestionsForm
  2. 6.x src/Form/QuizQuestionsForm.php \Drupal\quiz\Form\QuizQuestionsForm

Form to manage questions in a quiz.

Hierarchy

Expanded class hierarchy of QuizQuestionsForm

File

src/Form/QuizQuestionsForm.php, line 23

Namespace

Drupal\quiz\Form
View source
class QuizQuestionsForm extends FormBase {

  /**
   * Fields for creating new questions are added to the quiz_questions_form.
   *
   * @param $form
   *   FAPI form(array).
   * @param $types
   *   All the question types(array).
   * @param $quiz
   *   The quiz node.
   */
  function _quiz_add_fields_for_creating_questions(&$form, &$types, Quiz $quiz) {

    // Display links to create other questions.
    $form['additional_questions'] = array(
      '#type' => 'fieldset',
      '#title' => t('Create new question'),
    );
    $create_question = FALSE;
    $entity_manager = Drupal::entityTypeManager();
    $access_handler = $entity_manager
      ->getAccessControlHandler('quiz_question');
    foreach ($types as $type => $info) {
      $options = array(
        '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] = array(
        '#markup' => '<div class="add-questions">' . Link::fromTextAndUrl($info['label'], $url)
          ->toString() . '</div>',
        '#access' => $access,
      );
    }
    if (!$create_question) {
      $form['additional_questions']['create'] = array(
        '#type' => 'markup',
        '#markup' => t('You have not enabled any question type module or no has permission been given to create any question.'),
      );
    }
  }

  /**
   * 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.
   *
   * @param $form_state
   *   The form state variable
   * @param $quiz
   *   The quiz node.
   *
   * @return
   *   HTML output to create page.
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    $types = quiz_get_question_types();
    $quiz = $form_state
      ->getBuildInfo()['args'][0];
    $this
      ->_quiz_add_fields_for_creating_questions($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' => t('Questions in this @quiz', array(
        '@quiz' => _quiz_get_quiz_name(),
      )),
      '#type' => 'table',
      '#header' => $header,
      '#empty' => 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.', array(
        '@quiz' => _quiz_get_quiz_name(),
      )),
      '#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, [
      'self',
      '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
      ->_quiz_add_questions_to_form($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'] = array(
      '#type' => 'hidden',
      '#default_value' => \Drupal::time()
        ->getRequestTime(),
    );
    $form['actions']['#type'] = 'actions';
    $form['actions']['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Submit'),
    );

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

  /**
   *
   * @return array
   *   of QuizQuestion
   */
  function getSubQuestions($root_question, $all_questions) {
    $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.
   */
  function sortQuestions($a, $b) {
    $aw = $a
      ->get('weight')
      ->getString();
    $bw = $b
      ->get('weight')
      ->getString();
    if ($aw == $bw) {
      return 0;
    }
    return $aw < $bw ? -1 : 1;
  }
  public function getFormId() : string {
    return 'quiz_questions_form';
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    $question_list = $form_state
      ->getValue('question_list');
    foreach ($question_list as $qqr_id => $row) {
      $qqr = \Drupal\quiz\Entity\QuizQuestionRelationship::load($qqr_id);
      foreach ($row as $name => $value) {
        if ($name == 'qqr_pid' && empty($value)) {
          $value = NULL;
        }
        $qqr
          ->set($name, $value);
      }
      $qqr
        ->save();
    }
    \Drupal::messenger()
      ->addMessage(t('Questions updated successfully.'));
    return;

    // @todo below logic in D8, maybe we get rid of "remove questions"
    //
    // Load the quiz node.
    $quiz = $form_state['build_info']['args'][0];

    // Update the refresh latest quizzes table so that we know what the users
    // latest quizzes are.
    if (\Drupal::config('quiz.settings')
      ->get('auto_revisioning', 1)) {
      $is_new_revision = $quiz
        ->hasAttempts();
    }
    else {
      $is_new_revision = !empty($form_state['values']['new_revision']);
    }
    $num_random = isset($form_state['values']['num_random_questions']) ? $form_state['values']['num_random_questions'] : 0;
    $quiz->max_score_for_random = isset($form_state['values']['max_score_for_random']) ? $form_state['values']['max_score_for_random'] : 1;

    // Store what questions belong to the quiz.
    $questions = _quiz_update_items($quiz, $weight_map, $max_scores, $auto_update_max_scores, $is_new_revision, $refreshes, $stayers, $qnr_ids_map, $qqr_pids_map, $compulsories);

    // If using random questions and no term ID is specified, make sure we have
    // enough.
    $assigned_random = 0;
    foreach ($questions as $question) {
      if ($question->question_status == QUIZ_QUESTION_RANDOM) {
        ++$assigned_random;
      }
    }

    // Adjust number of random questions downward to match number of selected
    // questions.
    if ($num_random > $assigned_random) {
      $num_random = $assigned_random;
      \Drupal::messenger()
        ->addWarning(t('The number of random questions for this @quiz have been lowered to %anum to match the number of questions you assigned.', array(
        '@quiz' => _quiz_get_quiz_name(),
        '%anum' => $assigned_random,
      )));
    }
    if ($quiz->type == 'quiz') {

      // Update the quiz node properties.
      db_update('quiz_node_properties')
        ->fields(array(
        'number_of_random_questions' => $num_random ? $num_random : 0,
        'max_score_for_random' => $quiz->max_score_for_random,
      ))
        ->condition('vid', $quiz->vid)
        ->condition('nid', $quiz->nid)
        ->execute();

      // Get sum of max_score.
      $query = db_select('quiz_node_relationship', 'qnr');
      $query
        ->addExpression('SUM(max_score)', 'sum');
      $query
        ->condition('parent_vid', $quiz->vid);
      $query
        ->condition('question_status', QUIZ_QUESTION_ALWAYS);
      $score = $query
        ->execute()
        ->fetchAssoc();
      db_update('quiz_node_properties')
        ->expression('max_score', 'max_score_for_random * number_of_random_questions + :sum', array(
        ':sum' => (int) $score['sum'],
      ))
        ->condition('vid', $quiz->vid)
        ->execute();
    }
  }

  /**
   * Adds checkbox for creating new revision. Checks it by default if answers
   * exists.
   *
   * @param $form
   *   FAPI form(array).
   *
   * @param $quiz
   *   Quiz node(object).
   */
  function _quiz_add_revision_checkbox(&$form, $quiz) {
    $config = $this
      ->config('quiz.settings');
    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' => \Drupal::destination()
          ->getAsArray(),
      ])
        ->toString();
      $form['revision_help'] = [
        '#markup' => 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>.', [
          '@results_url' => $results_url,
          '@quiz_url' => $quiz_url,
        ]),
      ];
      $form['actions']['submit']['#access'] = FALSE;
    }
  }

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

      /* @var $quiz Quiz */
      $quiz_question = Drupal::entityTypeManager()
        ->getStorage('quiz_question')
        ->loadRevision($question_vid);
      $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' => \Drupal::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' => \Drupal::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' => \Drupal::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'] = render($indentation);
      }
      $table[$id]['type'] = array(
        '#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'] = array(
        '#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' => array(
          'disabled' => array(
            "#edit-question-list-{$id}-auto-update-max-score" => array(
              'checked' => TRUE,
            ),
          ),
        ),
      );
      $table[$id]['auto_update_max_score'] = array(
        '#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'] = array(
          '#type' => 'checkbox',
          '#default_value' => $question_relationship
            ->get('question_status')
            ->getString(),
        );
      }
      $entity_manager = Drupal::entityTypeManager();
      $access_handler = $entity_manager
        ->getAccessControlHandler('quiz_question');

      // Add a checkbox to update to the latest revision of the question.
      $latest_quiz_question = Drupal::entityTypeManager()
        ->getStorage('quiz_question')
        ->load($quiz_question
        ->id());
      if ($question_relationship
        ->get('question_vid')->value == $latest_quiz_question
        ->getRevisionId()) {
        $update_cell = array(
          '#markup' => t('<em>Up to date</em>'),
        );
      }
      else {
        $revisions_url = Url::fromRoute('entity.quiz_question.edit_form', [
          'quiz_question' => $quiz_question
            ->id(),
        ]);
        $update_cell = array(
          '#type' => 'checkbox',
          '#return_value' => $latest_quiz_question
            ->getRevisionId(),
          '#title' => t('Update to latest'),
        );
      }
      $table[$id]['question_vid'] = $update_cell;
      $update_question = $access_handler
        ->access($quiz_question, 'update');
      $table[$id]['operations'] = array(
        '#type' => 'operations',
        '#links' => [
          [
            'title' => t('Edit'),
            'url' => $edit_url,
          ],
          [
            'title' => t('Remove'),
            'url' => $remove_url,
          ],
        ],
      );
      $table[$id]['#weight'] = (int) $question_relationship
        ->get('weight')
        ->getString();
      $table[$id]['weight'] = array(
        '#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'] = array(
        '#title' => 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'] = array(
        '#title' => 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',
        ],
      );
    }
  }

}

Members

Namesort descending Modifiers Type Description Overrides
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::create public static function Instantiates a new instance of this class. Overrides ContainerInjectionInterface::create 87
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.
QuizQuestionsForm::buildForm public function Handles "manage questions" tab. Overrides FormInterface::buildForm
QuizQuestionsForm::getFormId public function Returns a unique string identifying the form. Overrides FormInterface::getFormId
QuizQuestionsForm::getSubQuestions function
QuizQuestionsForm::sortQuestions function Entity type sorter for quiz questions.
QuizQuestionsForm::submitForm public function Form submission handler. Overrides FormInterface::submitForm
QuizQuestionsForm::_quiz_add_fields_for_creating_questions function Fields for creating new questions are added to the quiz_questions_form.
QuizQuestionsForm::_quiz_add_questions_to_form function Adds the questions in the $questions array to the form.
QuizQuestionsForm::_quiz_add_revision_checkbox function Adds checkbox for creating new revision. Checks it by default if answers exists.
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.