You are here

quiz_scale.module in Quiz 8.5

Same filename and directory in other branches
  1. 8.6 question_types/quiz_scale/quiz_scale.module

Scale question type for the Quiz module.

Allows the creation of scale questions (ex: likert scale). Mainly used for creating survey like questions within the quiz framework.

File

question_types/quiz_scale/quiz_scale.module
View source
<?php

/**
 * @file
 * Scale question type for the Quiz module.
 *
 * Allows the creation of scale questions (ex: likert scale). Mainly used for
 * creating survey like questions within the quiz framework.
 */

/**
 * Implements hook_help().
 */
function scale_help($path, $args) {
  if ($path == 'admin/help#scale') {
    return t('This module provides a scale question type for Quiz. It may be used to construct surveys.');
  }
}

/**
 * Implements hook_menu().
 */
function scale_menu() {
  $items['scale/collection/manage'] = array(
    'title' => 'Manage your preset collections',
    'access callback' => 'node_access',
    'access arguments' => array(
      'create',
      'scale',
    ),
    'type' => MENU_SUGGESTED_ITEM,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'scale_manage_collection_form',
    ),
  );
  return $items;
}

/**
 * Implements hook_theme().
 */
function scale_theme($existing, $type, $theme, $path) {
  $module_path = drupal_get_path('module', 'scale');
  return array(
    'scale_creation_form' => array(
      'render element' => 'form',
      'path' => $module_path . '/theme',
      'file' => 'scale.theme.inc',
    ),
    'scale_response_form' => array(
      'render element' => 'form',
      'path' => $module_path . '/theme',
      'file' => 'scale.theme.inc',
    ),
    'scale_answer_node_view' => array(
      'variables' => array(
        'alternatives' => NULL,
      ),
      'path' => $module_path . '/theme',
      'file' => 'scale.theme.inc',
    ),
    'scale_answering_form' => array(
      'render element' => 'form',
      'path' => $module_path . '/theme',
      'template' => 'scale-answering-form',
    ),
  );
}

/**
 * Implements hook_permission().
 */
function scale_permission() {
  return array(
    'Edit global presets' => array(
      'title' => t('Administer presets'),
    ),
  );
}

/**
 * Implements hook_quiz_question_info().
 */
function scale_quiz_question_info() {
  return array(
    'scale' => array(
      'name' => t('Scale question'),
      'description' => t('Quiz questions that allow a user to choose from a scale.'),
      'question provider' => 'ScaleQuestion',
      'response provider' => 'ScaleResponse',
      // All wrapper functions are in the quiz_question module.
      'module' => 'quiz_question',
    ),
  );
}

/**
 * Implements hook_quiz_question_config().
 */
function scale_quiz_question_config() {
  $form['manage'] = array(
    '#markup' => l(t('Manage presets'), 'scale/collection/manage'),
  );
  $form['scale_max_num_of_alts'] = array(
    '#type' => 'number',
    '#min' => 2,
    '#max' => 50,
    '#title' => t('Maximum number of alternatives allowed'),
    '#default_value' => variable_get('scale_max_num_of_alts', 10),
  );
  $form['#validate'][] = 'scale_config_validate';
  return $form;
}

/**
 * Get the answer data for a specific result.
 *
 * @param int $question_nid
 *   The Question node ID.
 * @param int $question_vid
 *   The Question revision ID.
 * @param int $result_id
 *   The result ID for this attempt.
 *
 * @return array|false
 *   Array containing the answer data.
 */
function scale_get_answer($question_nid, $question_vid, $result_id) {
  $results = db_query('SELECT answer_id, question_vid, question_nid, result_id
    FROM {quiz_scale_user_answers}
    WHERE question_nid = :qnid AND question_vid = :qvid AND result_id = :rid', array(
    ':qnid' => $question_nid,
    ':qvid' => $question_vid,
    ':rid' => $result_id,
  ))
    ->fetchAssoc();
  return $results ? $results : FALSE;
}

/**
 * Implements hook_user_cancel().
 */
function scale_user_cancel($edit, $account, $method) {
  $results = db_query('SELECT id FROM {quiz_scale_answer_collection} ac
    JOIN {quiz_scale_user} u ON(ac.id = u.answer_collection_id)
    WHERE uid = :uid
    AND ac.for_all = :for_all
    AND ac.id NOT IN
      (SELECT answer_collection_id
       FROM {quiz_scale_node_properties})
    AND ac.id NOT IN
      (SELECT answer_collection_id
       FROM {quiz_scale_user}
       WHERE NOT uid = :uid)', array(
    ':uid' => $account->uid,
    ':for_all' => 0,
  ));
  foreach ($results as $result) {
    db_delete('quiz_scale_answer')
      ->condition('answer_collection_id', $result->id)
      ->execute();
    db_delete('quiz_scale_answer_collection')
      ->condition('id', $result->id)
      ->execute();
  }
  db_delete('quiz_scale_user')
    ->condition('uid', $account->uid)
    ->execute();
}

/**
 * Form for changing and deleting the current users preset answer collections.
 *
 * Users with the Edit global presets permissions can also add new global
 * presets here.
 */
function scale_manage_collection_form($form, &$form_state) {
  $edit_globals_access = user_access('Edit global presets');

  // We create an instance of ScaleQuestion. We want to use some of its methods.
  $scale_question = new ScaleQuestion(new stdClass());
  $collections = $scale_question
    ->getPresetCollections($edit_globals_access);

  // If user is allowed to edit global answer collections he is also allowed
  // to add new global presets.
  if ($edit_globals_access) {
    $new_col = new stdClass();
    $new_col->for_all = 1;
    $new_col->name = t('New global collection(available to all users)');
    $collections['new'] = $new_col;
  }
  $form = array();
  if (count($collections) == 0) {
    $form['no_col'] = array(
      '#markup' => t("You don't have any preset collections."),
    );
    return $form;
  }

  // Populate the form.
  foreach ($collections as $col_id => $obj) {
    $form["collection{$col_id}"] = array(
      '#type' => 'fieldset',
      '#title' => $collections[$col_id]->name,
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      '#group' => 'scale_manage_collection_form',
    );
    $alternatives = isset($collections[$col_id]->alternatives) ? $collections[$col_id]->alternatives : array();
    for ($i = 0; $i < variable_get('scale_max_num_of_alts', 10); $i++) {
      $form["collection{$col_id}"]["alternative{$i}"] = array(
        '#title' => t('Alternative !i', array(
          '!i' => $i + 1,
        )),
        '#size' => 60,
        '#maxlength' => 256,
        '#type' => 'textfield',
        '#default_value' => isset($alternatives[$i]) ? $alternatives[$i] : '',
        '#required' => $i < 2 && $col_id != 'new',
      );
    }
    if ($col_id != 'new') {
      if ($edit_globals_access) {
        $form["collection{$col_id}"]['for_all'] = array(
          '#type' => 'checkbox',
          '#title' => t('Available to all users'),
          '#default_value' => $collections[$col_id]->for_all,
        );
      }
      $form["collection{$col_id}"]['to-do'] = array(
        '#type' => 'radios',
        '#title' => t('What will you do?'),
        '#default_value' => '0',
        '#options' => array(
          t('Save changes, do not change questions using this preset'),
          t('Save changes, and change your own questions who uses this preset'),
          t('Delete this preset(This will not affect existing questions)'),
        ),
      );
    }
    else {
      $form["collection{$col_id}"]["to-do"] = array(
        '#type' => 'value',
        '#value' => 3,
      );
      $form["collection{$col_id}"]["for_all"] = array(
        '#type' => 'value',
        '#value' => 1,
      );
    }
  }
  $form['process'] = array(
    '#type' => 'submit',
    '#value' => t('Submit'),
  );
  $form['#submit'][] = 'scale_collection_form_submit';
  if ($edit_globals_access) {
    $form['#validate'][] = 'scale_collection_form_validate';
  }
  $form['#tree'] = TRUE;
  return $form;
}

/**
 * Handles the scale collection form.
 */
function scale_collection_form_submit($form, &$form_state) {
  $user = \Drupal::currentUser();
  $changed = 0;
  $deleted = 0;
  foreach ($form_state['values'] as $key => $alternatives) {
    if ($col_id = _scale_get_col_id($key)) {
      $s_q = new ScaleQuestion(new stdClass());
      $s_q
        ->initUtil($col_id);

      // @todo Rename to-do to $op
      switch ($alternatives['to-do']) {

        // Save, but don't change.
        case 0:

        // Save and change existing questions.
        case 1:
          $new_col_id = $s_q
            ->saveAnswerCollection(FALSE, $alternatives, 1);
          if (isset($alternatives['for_all'])) {
            _scale_set_for_all($new_col_id, $alternatives['for_all']);
          }
          if ($new_col_id == $col_id) {
            break;
          }
          $changed++;

          // We save the changes, but don't change existing questions.
          if ($alternatives['to-do'] == 0) {

            // The old version of the collection shall not be a preset anymore.
            _scale_unpreset_collection($col_id, $user
              ->id());

            // If the old version of the collection doesn't belong to any
            // questions it is safe to delete it.
            $s_q
              ->deleteCollectionIfNotUsed($col_id);
            if (isset($alternatives['for_all'])) {
              _scale_set_for_all($new_col_id, $alternatives['for_all']);
            }
          }
          elseif ($alternatives['to-do'] == 1) {

            // Update all the users questions where the collection is used.
            $nids = db_query('SELECT nid FROM {node} WHERE uid = :uid', array(
              ':uid' => 1,
            ))
              ->fetchCol();
            db_update('quiz_scale_node_properties')
              ->fields(array(
              'answer_collection_id' => $new_col_id,
            ))
              ->condition('answer_collection_id', $nids, 'IN')
              ->execute();
            db_delete('quiz_scale_user')
              ->condition('answer_collection_id', $col_id)
              ->condition('uid', $user
              ->id())
              ->execute();
            $s_q
              ->deleteCollectionIfNotUsed($col_id);
          }
          break;

        // Delete.
        case 2:
          $got_deleted = $s_q
            ->deleteCollectionIfNotUsed($col_id);
          if (!$got_deleted) {
            _scale_unpreset_collection($col_id, $user
              ->id());
          }
          $deleted++;
          break;

        // New.
        case 3:
          if (drupal_strlen($alternatives['alternative0']) > 0) {
            $new_col_id = $s_q
              ->saveAnswerCollection(FALSE, $alternatives, 1);
            _scale_set_for_all($new_col_id, $alternatives['for_all']);
            \Drupal::messenger()
              ->addMessage(t('New preset has been added'));
          }
          break;
      }
    }
  }
  if ($changed > 0) {
    \Drupal::messenger()
      ->addMessage(t('!changed presets have been changed.', array(
      '!changed' => $changed,
    )));
  }
  if ($deleted > 0) {
    \Drupal::messenger()
      ->addMessage(t('!deleted presets have been deleted.', array(
      '!deleted' => $deleted,
    )));
  }
}

/**
 * Validates the scale collection form.
 */
function scale_collection_form_validate($form, &$form_state) {

  // If the user is trying to create a new collection.
  if (drupal_strlen($form_state['values']['collectionnew']['alternative0']) > 0) {

    // If the new collection don't have two alternatives.
    if (drupal_strlen($form_state['values']['collectionnew']['alternative1']) == 0) {

      // This can't be replaced by adding #required to the form elements. If
      // we did so we would always have to create a new collection when we
      // press submit.
      form_set_error('collectionnew][alternative1', t('New preset must have atleast two alternatives.'));
    }
  }
}

/**
 * Searches a string for the answer collection id.
 *
 * @param string $string
 *
 * @return int|false
 *   answer collection id.
 */
function _scale_get_col_id($string) {
  $res = array();
  $success = preg_match('/^collection([0-9]{1,}|new)$/', $string, $res);
  return $success > 0 ? $res[1] : FALSE;
}

/**
 * Make sure an answer collection isn't a preset for a given user.
 *
 * @param int $col_id
 *   Answer_collection_id.
 * @param int $user_id
 */
function _scale_unpreset_collection($col_id, $user_id) {
  db_delete('quiz_scale_user')
    ->condition('answer_collection_id', $col_id)
    ->condition('uid', $user_id)
    ->execute();
  if (user_access('Edit global presets')) {
    db_update('quiz_scale_answer_collection')
      ->fields(array(
      'for_all' => 0,
    ))
      ->execute();
  }
}

/**
 * Make an answer collection (un)available for all question creators.
 *
 * @param int $new_col_id
 *   Answer collection id.
 * @param int $for_all
 *   Use 0 if not for all, 1 if for all.
 */
function _scale_set_for_all($new_col_id, $for_all) {
  db_update('quiz_scale_answer_collection')
    ->fields(array(
    'for_all' => $for_all,
  ))
    ->condition('id', $new_col_id)
    ->execute();
}

Functions

Namesort descending Description
scale_collection_form_submit Handles the scale collection form.
scale_collection_form_validate Validates the scale collection form.
scale_get_answer Get the answer data for a specific result.
scale_help Implements hook_help().
scale_manage_collection_form Form for changing and deleting the current users preset answer collections.
scale_menu Implements hook_menu().
scale_permission Implements hook_permission().
scale_quiz_question_config Implements hook_quiz_question_config().
scale_quiz_question_info Implements hook_quiz_question_info().
scale_theme Implements hook_theme().
scale_user_cancel Implements hook_user_cancel().
_scale_get_col_id Searches a string for the answer collection id.
_scale_set_for_all Make an answer collection (un)available for all question creators.
_scale_unpreset_collection Make sure an answer collection isn't a preset for a given user.