You are here

opigno_quiz_app.module in Opigno Quiz App 7

Module file. Defines module hooks.

File

opigno_quiz_app.module
View source
<?php

/**
 * @file
 * Module file. Defines module hooks.
 */
define('OPIGNO_QUIZ_APP_PASSED', 'passed');
define('OPIGNO_QUIZ_APP_FAILED', 'failed');
define('OPIGNO_QUIZ_APP_PENDING', 'pending');

/**
 * Implements hook_menu().
 */
function opigno_quiz_app_menu() {
  return array(
    'user/%/achievements' => array(
      'title' => "My achievements",
      'page callback' => 'opigno_quiz_app_user_results',
      'page arguments' => array(
        1,
      ),
      'access callback' => 'opigno_quiz_app_access_user_achievements',
      'access arguments' => array(
        1,
      ),
      'file' => 'includes/opigno_quiz_app.pages.inc',
      'type' => MENU_LOCAL_TASK,
    ),
    'my-achievements' => array(
      'title' => "My achievements",
      'page callback' => 'opigno_quiz_app_user_results',
      'access arguments' => array(
        'access own results',
      ),
      'file' => 'includes/opigno_quiz_app.pages.inc',
      'type' => MENU_CALLBACK,
    ),
    'my-results' => array(
      'title' => "My results",
      'page callback' => 'opigno_quiz_app_current_user_results',
      'access callback' => 'user_is_logged_in',
      'file' => 'includes/opigno_quiz_app.pages.inc',
      'type' => MENU_CALLBACK,
    ),
    'node/%node/teacher-results' => array(
      'title' => "Student results",
      'description' => "Displays all student results to teachers",
      'page callback' => 'opigno_quiz_app_course_results',
      'page arguments' => array(
        1,
      ),
      'access callback' => 'opigno_quiz_app_access_node_teacher_results',
      'access arguments' => array(
        1,
        NULL,
      ),
      'file' => 'includes/opigno_quiz_app.pages.inc',
      'type' => MENU_CALLBACK,
    ),
    'node/%node/sort-quizzes' => array(
      'title' => "Sort @quiz_name_plural",
      'title arguments' => array(
        '@quiz_name_plural' => defined('QUIZ_NAME_PLURAL') ? QUIZ_NAME_PLURAL : 'quizzes',
      ),
      'description' => "Sort quizzes inside the course",
      'page callback' => 'drupal_get_form',
      'page arguments' => array(
        'opigno_quiz_app_sort_course_quizzes_form',
        1,
      ),
      'access callback' => 'opigno_quiz_app_access_node_sort_quizzes',
      'access arguments' => array(
        1,
      ),
      'file' => 'includes/opigno_quiz_app.pages.inc',
      'type' => MENU_CALLBACK,
    ),
    'admin/opigno/students/teacher-results' => array(
      'title' => "My course student results",
      'description' => "Displays all course student results to teachers",
      'page callback' => 'opigno_quiz_app_courses_results',
      'access arguments' => array(
        'access teacher results',
      ),
      'file' => 'includes/opigno_quiz_app.pages.inc',
    ),
    'node/%node/resume' => array(
      'title' => 'Resume',
      'page callback' => 'opigno_quiz_app_course_resume',
      'page arguments' => array(
        1,
      ),
      'access callback' => 'opigno_quiz_app_course_resume_access',
      'access arguments' => array(
        1,
      ),
    ),
  );
}

/**
 * Access callback for the redirection to resume a quiz
 */
function opigno_quiz_app_course_resume_access($node) {
  if (in_array($node->type, array(
    'course',
    'class',
  ))) {
    return node_access('view', $node);
  }
  return FALSE;
}

/**
 * Page callback for resuming a course.
 *
 * Redirects to the appropriate lesson.
 */
function opigno_quiz_app_course_resume($node) {
  if ($node->type == 'class') {

    // Get all the courses from the class and check for each if there is a "next lesson".
    // If no courses, return to front page.
    $courses = opigno_class_app_get_class_courses($node);
    if (empty($courses)) {
      drupal_set_message(t('This class does not have any courses.'), 'status', FALSE);
      drupal_goto('node/' . $node->nid);
    }
  }
  else {
    if ($node->type == 'course') {
      $courses = array(
        $node->nid,
      );
    }
    else {
      drupal_goto('<front>');
    }
  }

  // Loop for each course. If one returns a /take, go to this one.
  // Else, keep going in this loop.
  foreach ($courses as $course_nid) {
    $path = opigno_quiz_app_course_resume_get_path($course_nid);
    if ($path != '<front>' && $path != 'node/' . $course_nid && drupal_valid_path($path)) {
      drupal_goto($path);
    }
  }

  // If no courses returned a /take, go to the first lesson of the first course.
  $first_lesson = opigno_quiz_app_get_first_lesson_from_group($node);
  if ($first_lesson != null && drupal_valid_path('node/' . $first_lesson . '/take')) {
    drupal_goto('node/' . $first_lesson . '/take');
  }

  // If no lesson in this course, says that there is no lessons in this group.
  drupal_set_message(t('This @type does not have any lessons.', array(
    '@type' => $node->type,
  )), 'status', FALSE);
  drupal_goto('node/' . $node->nid);
}
function opigno_quiz_app_course_resume_get_path($course_nid) {
  global $user;
  $last_viewed_lesson_in_course = opigno_quiz_app_course_get_last_viewed($course_nid, $user->uid);

  // If the user has not viewed any quiz yet, go to the first one.
  if (!$last_viewed_lesson_in_course) {
    $lessons = opigno_quiz_app_course_lessons($course_nid);

    // If there is at least one lesson redirect to the first lesson.
    if (isset($lessons[$course_nid])) {
      $first_lesson = key($lessons[$course_nid]);
      return 'node/' . $first_lesson . '/take';
    }
    else {

      // If there is no lesson, redirect to front page.
      return '<front>';
    }
  }
  else {
    $last_viewed_lesson_in_course_result = $last_viewed_lesson_in_course['result_id'];
    $last_viewed_lesson_in_course = $last_viewed_lesson_in_course['quiz_nid'];

    // If the user has not finished the latest lesson he was in redirect him to that lesson.
    $lesson_to_go = 0;
    if (!opigno_quiz_app_user_finished_quiz_result($last_viewed_lesson_in_course, $last_viewed_lesson_in_course_result, $user->uid)) {
      $lesson_to_go = $last_viewed_lesson_in_course;
    }
    else {

      // If the user has finished the latest lesson he was in, try to go to the next one.
      $lessons = opigno_quiz_app_course_lessons($course_nid);
      if (isset($lessons[$course_nid])) {
        $current_position = array_search($last_viewed_lesson_in_course, array_keys($lessons[$course_nid]));

        // If the lesson he just finished was the last one, go to the first one.
        if ($current_position == count($lessons[$course_nid]) - 1) {

          // If it's the last course of the class, go back to the first lesson.
          //  Else, return <front> so other function can check the next course of the class.
          //          if ($is_last_course) {
          //            $first_lesson = key($lessons[$course_nid]);
          //            return 'node/' . $first_lesson . '/take';
          //          }
          //          else {
          return 'node/' . $course_nid;

          //          }
        }
        else {

          // Go to the next lesson.
          $keys = array_keys($lessons[$course_nid]);
          $next_lesson = $keys[array_search($last_viewed_lesson_in_course, $keys) + 1];
          $lesson_to_go = $next_lesson;
        }
      }
    }
    if ($lesson_to_go) {

      // Make sure the lesson still exists otherwise user will never go to the course.
      $lesson_node = node_load($lesson_to_go);
      if ($lesson_node) {
        return 'node/' . $lesson_to_go . '/take';
      }
      else {
        $lessons = opigno_quiz_app_course_lessons($course_nid);

        // If there is at least one lesson redirect to the first lesson.
        if (isset($lessons[$course_nid])) {
          $first_lesson = key($lessons[$course_nid]);
          return 'node/' . $first_lesson . '/take';
        }
        else {

          // If there is no lesson, redirect to front page.
          return '<front>';
        }
      }
    }
  }
}

/**
 * Gets the latest lesson the user has been in.
 *
 * @param int $course_nid
 *   Course nid.
 * @param int $uid
 *   User uid.
 */
function opigno_quiz_app_course_get_last_viewed($course_nid, $uid) {
  $result = db_select('opigno_quiz_app_course_latest_viewed', 'lv');
  $result
    ->leftJoin('og_membership', 'ogm', 'ogm.etid = lv.quiz_nid');
  $result
    ->leftJoin('node', 'n', 'n.nid = lv.quiz_nid');
  $result = $result
    ->fields('lv', array(
    'quiz_nid',
    'result_id',
  ))
    ->fields('ogm', array(
    'gid',
  ))
    ->fields('n', array(
    'nid',
  ))
    ->condition('lv.course_nid', $course_nid)
    ->condition('lv.uid', $uid)
    ->condition('ogm.group_type', 'node')
    ->condition('ogm.gid', $course_nid)
    ->condition('ogm.state', 1)
    ->condition('ogm.field_name', 'og_group_ref')
    ->condition('n.status', 1)
    ->execute()
    ->fetchAssoc();
  return empty($result) ? 0 : $result;
}

/**
 * Helper function to check if user has finished a quiz.
 *
 * @param  int $quiz_nid
 * @param  int $uid
 *
 * @return int
 */
function opigno_quiz_app_user_finished_quiz_result($quiz_nid, $result_id, $uid) {
  $result = db_select('quiz_node_results', 'r')
    ->fields('r', array(
    'result_id',
  ))
    ->condition('r.uid', $uid)
    ->condition('r.time_end', 0, '>')
    ->condition('r.nid', $quiz_nid, '=')
    ->condition('r.result_id', $result_id, '=')
    ->execute()
    ->fetchField();
  return empty($result) ? 0 : $result;
}

/**
 * Sets in the db the latest lesson the user has been in
 *
 * @param int $course_nid
 *   Course nid.
 * @param int $quiz_nid
 *   Quiz nid.
 * @param int $uid
 *   User uid.
 */
function opigno_quiz_app_course_last_viewed($course_nid, $quiz_nid, $result_id, $uid) {
  db_merge('opigno_quiz_app_course_latest_viewed')
    ->key(array(
    'course_nid' => $course_nid,
    'uid' => $uid,
  ))
    ->fields(array(
    'course_nid' => $course_nid,
    'quiz_nid' => $quiz_nid,
    'uid' => $uid,
    'result_id' => $result_id,
  ))
    ->execute();
}

/**
 * Implements hook_user_delete().
 */
function opigno_quiz_app_user_delete($account) {

  // Delete user rows from custom table.
  db_delete('opigno_quiz_app_course_latest_viewed')
    ->condition('uid', $account->uid)
    ->execute();
}

/**
 * Implements hook_menu_alter().
 */
function opigno_quiz_app_menu_alter(&$items) {

  // Intercept Quiz taking rendering for fullscreen logic.
  $items['node/%node/take']['page callback'] = 'opigno_quiz_app_quiz_take';
  $items['node/%node/take']['file'] = 'includes/opigno_quiz_app.pages.inc';
  $items['node/%node/take']['file path'] = drupal_get_path('module', 'opigno_quiz_app');
}

/**
 * Implements hook_permission().
 */
function opigno_quiz_app_permission() {
  return array(
    'access all results' => array(
      'title' => t("Access all user results"),
    ),
    'access own results' => array(
      'title' => t("Access own results"),
    ),
    'access teacher results' => array(
      'title' => t('Access teacher results'),
      'description' => t('Allows course teachers to access all their student results.'),
    ),
  );
}

/**
 * Custom user access to the users achievements page
 */
function opigno_quiz_app_access_user_achievements($uid) {
  global $user;
  if ($user->uid) {
    if (user_access('access all results')) {
      return TRUE;
    }
    elseif ($user->uid == $uid && user_access('access own results')) {
      return TRUE;
    }
  }
  return FALSE;
}

/**
 * Implements hook_og_permission().
 */
function opigno_quiz_app_og_permission() {
  return array(
    'skip display of results' => array(
      'title' => t("Skip displaying results on teacher results page"),
      'description' => t("Some roles might be allowed to take quizzes, but these should not be shown on the teacher results page."),
    ),
    'sort quizzes' => array(
      'title' => t("Sort quizzes inside this course"),
    ),
  );
}

/**
 * Implements hook_node_insert().
 */
function opigno_quiz_app_node_insert($node) {
  if ($node->type == 'quiz' && !empty($node->nid) && !empty($node->og_group_ref)) {
    foreach ($node->og_group_ref as $lang => $items) {
      foreach ($items as $item) {

        // Set a default weight of 0.
        opigno_quiz_app_set_course_quiz_weight($item['target_id'], $node->nid);
      }
    }
  }
}

/**
 * Implements hook_node_update().
 */
function opigno_quiz_app_node_update($node) {
  if ($node->type == 'quiz' && !empty($node->nid) && !empty($node->og_group_ref)) {
    $original_node = $node->original;

    // adds the weight for new courses
    foreach ($node->og_group_ref as $lang => $items) {
      foreach ($items as $item) {

        // Set a default weight of 0.
        if (!opigno_quiz_app_is_in_entity_reference($item['target_id'], $original_node->og_group_ref)) {
          opigno_quiz_app_set_course_quiz_weight($item['target_id'], $node->nid);
        }
      }
    }

    // removes the weight from courses that were removed
    foreach ($original_node->og_group_ref as $lang => $items) {
      foreach ($items as $item) {

        // Set a default weight of 0.
        if (!opigno_quiz_app_is_in_entity_reference($item['target_id'], $node->og_group_ref)) {
          opigno_quiz_app_delete_course_quiz_weight($item['target_id'], $node->nid);
        }
      }
    }
  }
}

/**
 * Compares entity references fields
 */
function opigno_quiz_app_is_in_entity_reference($target_id, $field) {
  foreach ($field as $lang => $items) {
    foreach ($items as $item) {
      if ($target_id == $item['target_id']) {
        return TRUE;
      }
    }
  }
  return FALSE;
}

/**
 * Implements hook_og_context_negotiation_info
 */
function opigno_quiz_app_og_context_negotiation_info() {
  $providers = array();
  $paths = array();
  foreach (opigno_quiz_app_get_quiz_question_types() as $type) {
    $type = str_replace('_', '-', $type);
    $paths[] = "node/add/{$type}";
  }
  $providers['opigno_quiz_app_question_add'] = array(
    'name' => t('Opigno Quiz App: add question'),
    'description' => t("Determine context by checking node/add/[question type]."),
    'callback' => 'opigno_quiz_app_og_context_handler',
    'menu path' => $paths,
  );
  return $providers;
}

/**
 * OG Context provider.
 *
 * When creating a Quiz question, check the parent Quiz. If it's part
 * of a group, return the group context.
 *
 * @return array
 */
function opigno_quiz_app_og_context_handler() {
  if (opigno_quiz_app_is_node_add_question()) {
    $args = explode('/', current_path());
    $node = (object) array(
      'type' => str_replace('-', '_', array_pop($args)),
    );
    og_quiz_node_prepare($node);
    if (isset($node->og_group_ref)) {
      $items = array_shift($node->og_group_ref);
      $gid = $items[0]['target_id'];
      return array(
        'node' => array(
          $gid,
        ),
      );
    }
  }
}

/**
 * Implements hook_opigno_breadcrumbs().
 */
function opigno_quiz_app_opigno_breadcrumbs($gid) {
  $breadcrumbs = array();
  $node = menu_get_object();

  // Must we handle this page request for the breadcrumb ?
  if (isset($node->type) && $node->type == 'quiz' || current_path() == 'node/add/quiz' || preg_match('/^node\\/[0-9]+\\/sort-quizzes$/', current_path()) || opigno_quiz_app_is_node_add_question()) {

    // Add the Opigno Quizzes view link.
    $breadcrumbs[] = l(opigno_quiz_app_get_quizzes_view_title(), "node/{$gid}/quizzes");

    // Is this a sub page of the quiz (like node/%/take) ? Add the Quiz itself.
    if (isset($node->type) && $node->type == 'quiz' && preg_match('/^node\\/[0-9]+\\/.+/', current_path())) {
      $breadcrumbs[] = l($node->title, "node/{$node->nid}");
    }

    // Is this a question add form ? And the parent Quiz NID is given in the URL ?
    // Add the parent Quiz title and the manage question tab.
    if (opigno_quiz_app_is_node_add_question() && !empty($_GET['quiz_nid'])) {
      $quiz = node_load($_GET['quiz_nid']);
      $breadcrumbs[] = l($quiz->title, "node/{$_GET['quiz_nid']}");
      $breadcrumbs[] = l(t("Manage questions"), "node/{$_GET['quiz_nid']}/questions");
    }
  }
  if (!empty($breadcrumbs)) {
    return $breadcrumbs;
  }
}

/**
 * Implements hook_menu_local_tasks_alter().
 */
function opigno_quiz_app_menu_local_tasks_alter(&$data, $router_item, $root_path) {
  if ($root_path == 'node/%') {
    $node = node_load(arg(1));
    if (isset($node->type) && in_array($node->type, array(
      "course",
      "class",
    )) && og_user_access('node', $node->nid, 'access quiz')) {

      //        $quiz = opigno_quiz_get_continue_group($node);
      //
      //        // If $quiz is empty, it means that the user has finished the course. So put the first lesson again.
      //        if (empty($quiz)) {
      //          $quiz = opigno_quiz_app_get_first_lesson_from_group($node);
      //        }
      //        if (!empty($quiz)) {
      $item = menu_get_item('node/' . $node->nid . '/resume');
      $item['title'] = t("Continue");
      $item['options']['attributes']['class'][] = $item['localized_options']['attributes']['class'][] = 'opigno-quiz-app-course-start';
      $data['actions']['output'][] = array(
        '#theme' => 'menu_local_action',
        '#link' => $item,
      );

      //        }
    }
  }
  if ($root_path == 'node/%/quizzes') {
    $gid = arg(1);
    if (og_user_access('node', $gid, 'create quiz content')) {
      $item = menu_get_item('node/add/quiz');
      $item['title'] = t("Add a new @quiz_name", array(
        '@quiz_name' => QUIZ_NAME,
      ));
      $item['options']['query']['og_group_ref'] = $item['localized_options']['query']['og_group_ref'] = $gid;
      $item['options']['attributes']['class'][] = $item['localized_options']['attributes']['class'][] = 'opigno-quiz-app-add-quiz';
      $data['actions']['output'][] = array(
        '#theme' => 'menu_local_action',
        '#link' => $item,
      );
    }
    $node = node_load($gid);
    if (opigno_quiz_app_access_node_sort_quizzes($node, NULL)) {
      $item = menu_get_item("node/{$gid}/sort-quizzes");
      $destination = request_path();
      $item['options']['query']['destination'] = $item['localized_options']['query']['destination'] = $destination;
      $item['options']['attributes']['class'][] = $item['localized_options']['attributes']['class'][] = 'opigno-quiz-app-sort-quizzes';
      $data['actions']['output'][] = array(
        '#theme' => 'menu_local_action',
        '#link' => $item,
      );
    }
  }
}

/**
 * Implements hook_theme().
 */
function opigno_quiz_app_theme() {
  return array(
    'opigno_quiz_app_user_results' => array(
      'variables' => array(
        'user' => NULL,
        'results' => NULL,
      ),
    ),
    'opigno_quiz_app_user_course_results' => array(
      'variables' => array(
        'user' => NULL,
        'course_node' => NULL,
        'course_results' => NULL,
      ),
    ),
    'opigno_quiz_app_course_results' => array(
      'variables' => array(
        'course' => NULL,
        'results' => NULL,
      ),
    ),
    'opigno_quiz_app_course_user_results' => array(
      'variables' => array(
        'course' => NULL,
        'user' => NULL,
        'results' => NULL,
      ),
    ),
    'opigno_quiz_app_sort_course_quizzes_form' => array(
      'render element' => 'form',
    ),
  );
}

/**
 * Implements hook_views_api().
 */
function opigno_quiz_app_views_api() {
  return array(
    'api' => '3.0',
  );
}

/**
 * Implements hook_views_data().
 */
function opigno_quiz_app_views_data() {
  $data['opigno_quiz_app_quiz_sort']['table']['group'] = t("Opigno Quiz App");
  $data['opigno_quiz_app_quiz_sort']['table']['join'] = array(
    'node' => array(
      'left_field' => 'nid',
      'field' => 'quiz_nid',
    ),
  );
  $data['opigno_quiz_app_quiz_sort']['gid'] = array(
    'title' => t("The Quiz group"),
    'help' => t("The gid of the quiz weight table"),
    'filter' => array(
      'handler' => 'views_handler_filter_numeric',
    ),
    'relationship' => array(
      'base' => 'node',
      'base field' => 'nid',
      'field' => 'gid',
      'handler' => 'views_handler_relationship',
      'label' => t("Group"),
    ),
    'argument' => array(
      'handler' => 'views_handler_argument_numeric',
    ),
  );
  $data['opigno_quiz_app_quiz_sort']['quiz_nid'] = array(
    'title' => t("The Quiz weight (as in order) inside a group"),
    'relationship' => array(
      'base' => 'node',
      'base field' => 'nid',
      'label' => t("Quiz"),
    ),
  );
  $data['opigno_quiz_app_quiz_sort']['weight'] = array(
    'title' => t("Quiz weight (as in order)"),
    'help' => t("The weight of the quiz inside a specific group"),
    'field' => array(
      'handler' => 'opigno_quiz_app_field_course_quiz_weight',
      'click sortable' => TRUE,
    ),
    'filter' => array(
      'handler' => 'opigno_quiz_app_field_course_quiz_weight',
    ),
    'sort' => array(
      'handler' => 'opigno_quiz_app_sort_course_quiz_weight',
    ),
    'argument' => array(
      'handler' => 'views_handler_argument_numeric',
    ),
  );
  $data['opigno_quiz_app_quiz_sort']['course_class_progress'] = array(
    'title' => t("Progression"),
    'help' => t('Show the course or class progression for the logged user'),
    'field' => array(
      'handler' => 'opigno_quiz_app_field_course_class_progress',
    ),
  );
  return $data;
}

/**
 * Implements hook_modules_enabled().
 */
function opigno_quiz_app_modules_enabled($modules) {
  if (in_array('opigno_calendar_app', $modules)) {
    module_load_install('opigno_quiz_app');
    opigno_quiz_app_enable_calendar_integration();
  }
}

/**
 * Implements hook_quiz_finished().
 */
function opigno_quiz_app_quiz_finished($quiz, $score, $rid, $taker = NULL) {
  if (module_exists('rules')) {
    global $user;
    $taker = isset($taker) ? $taker : clone $user;
    $author = user_load($quiz->uid);
    $quiz = node_load($quiz->nid);
    rules_invoke_event('opigno_quiz_app_rules_quiz_taken', $taker, $author, $quiz);
    if ($score['passing'] == true && $score['percentage_score'] >= $quiz->pass_rate) {
      rules_invoke_event('opigno_quiz_app_rules_quiz_passed', $taker, $author, $quiz);
      drupal_set_message(t("You successfully completed this lesson."));
    }
    else {
      rules_invoke_event('opigno_quiz_app_rules_quiz_failed', $taker, $author, $quiz);
    }
  }

  // Make sure we're NOT in fullscreen anymore.
  setcookie('opigno_quiz_app_fs', 'false');
}

/**
 * Implements hook_quiz_scored().
 */
function opigno_quiz_app_quiz_scored($quiz, $score, $rid) {
  $taker = user_load(db_select('quiz_node_results', 'r')
    ->fields('r', array(
    'uid',
  ))
    ->condition('result_id', $rid)
    ->execute()
    ->fetchField());
  opigno_quiz_app_quiz_finished($quiz, $score, $rid, $taker);
}

/**
 * Implements hook_opigno_tool().
 */
function opigno_quiz_app_opigno_tool($node = NULL) {
  return array(
    'quiz' => array(
      'name' => t("Quiz"),
      'path' => isset($node) ? "node/{$node->nid}/quizzes" : '',
      'description' => t("Quizzes allow teachers to assess students and provide scenario-like information."),
      'actions' => array(
        'add_quiz' => array(
          'title' => t("Add a new Quiz"),
          'href' => 'node/add/quiz',
          'access_arguments' => array(
            'node',
            isset($node) ? $node->nid : 0,
            'create quiz content',
          ),
          'access_callback' => 'og_user_access',
          'query' => array(
            'og_group_ref' => isset($node) ? $node->nid : '',
          ),
        ),
      ),
    ),
  );
}

/**
 * Access callback: check if user has access to student results for the specified course.
 *
 * @param  stdClass $node
 *         The group node.
 * @param  stdClass $quiz = NULL
 *         (optional) The quiz to check permissions for.
 * @param  stdClass $account = NULL
 *
 * @return bool
 */
function opigno_quiz_app_access_node_teacher_results($node, $quiz = NULL, $account = NULL) {
  if (!isset($account)) {
    global $user;
    $account = clone $user;
  }
  $access = user_access('view any quiz results', $account) || og_user_access('node', $node->nid, 'view any quiz results', $account);
  if (!$access && isset($quiz)) {
    $access = (user_access('view results for own quiz', $account) || og_user_access('node', $node->nid, 'view results for own quiz', $account)) && $quiz->uid == $account->uid;
  }
  return $access;
}

/**
 * Access callback: check if user has access to sort quizzes inside the course.
 *
 * @param  stdClass $node
 * @param  stdClass $account = NULL
 *
 * @return bool
 */
function opigno_quiz_app_access_node_sort_quizzes($node, $account = NULL) {
  if (!isset($account)) {
    global $user;
    $account = clone $user;
  }
  return og_user_access('node', $node->nid, 'sort quizzes', $account);
}

/**
 * Access callback: check if user has access to take quizzes inside the course.
 *
 * @param  stdClass $node
 * @param  stdClass $account = NULL
 *
 * @return bool
 */
function opigno_quiz_app_take_access($node, $account = NULL) {
  if (!isset($account)) {
    global $user;
    $account = clone $user;
  }
  return og_user_access('node', $node->nid, 'access quiz', $account);
}

/**
 * Helper function to get the Opigno Quizzes View title.
 *
 * As the title might change depending on individual configuration,
 * fetch it here and cache it for better performance.
 *
 * @return string
 */
function opigno_quiz_app_get_quizzes_view_title() {
  $cache = cache_get('opigno_quiz_app:view_title:opigno_quizzes');
  if ($cache) {
    return $cache->data;
  }
  else {
    $view = views_get_view('opigno_quizzes');
    if (!empty($view->display['default']->display_options['title'])) {
      $quiz_view_title = $view->display['default']->display_options['title'];
    }
    else {
      $quiz_view_title = t("Quizzes");
    }
    cache_set('opigno_quiz_app:view_title:opigno_quizzes', $quiz_view_title, 'cache', CACHE_TEMPORARY);
    return $quiz_view_title;
  }
}

/**
 * Helper function to fetch all course quizzes.
 *
 * @param  stdClass $node
 *
 * @return array
 */
function opigno_quiz_app_get_course_quizzes($node) {
  $quizzes =& drupal_static(__FUNCTION__);
  if (!isset($quizzes[$node->nid])) {
    $quizzes[$node->nid] = array();
    $query = new EntityFieldQuery();
    $query
      ->entityCondition('entity_type', 'node')
      ->entityCondition('bundle', 'quiz')
      ->fieldCondition('og_group_ref', 'target_id', $node->nid, '=')
      ->addMetaData('account', user_load(1));
    $result = $query
      ->execute();
    $temp = array();
    if (!empty($result['node'])) {
      foreach (array_keys($result['node']) as $quiz_nid) {
        $temp[$quiz_nid] = opigno_quiz_app_get_course_quiz_weight($node->nid, $quiz_nid);
      }
    }

    // Sort by weight.
    asort($temp);
    $quizzes[$node->nid] = array_keys($temp);
  }
  return $quizzes[$node->nid];
}

/**
 * Helper function to fetch the weight of a quiz inside a course.
 *
 * @param  int $gid
 * @param  int $nid
 *
 * @return int
 */
function opigno_quiz_app_get_course_quiz_weight($gid, $nid) {
  $weight = db_select('opigno_quiz_app_quiz_sort', 'w')
    ->fields('w', array(
    'weight',
  ))
    ->condition('w.gid', $gid)
    ->condition('w.quiz_nid', $nid)
    ->execute()
    ->fetchField();
  return empty($weight) ? 0 : $weight;
}

/**
 * Helper function to insert the weight of a quiz inside a course.
 *
 * @param  int $gid
 * @param  int $nid
 * @param  int $weight
 */
function opigno_quiz_app_set_course_quiz_weight($gid, $nid, $weight = 0) {
  db_merge('opigno_quiz_app_quiz_sort')
    ->key(array(
    'gid' => $gid,
    'quiz_nid' => $nid,
  ))
    ->fields(array(
    'gid' => $gid,
    'quiz_nid' => $nid,
    'weight' => $weight,
  ))
    ->execute();
}

/**
 * Helper function to delete the weight of a quiz inside a course.
 *
 * @param  int $gid
 * @param  int $nid
 */
function opigno_quiz_app_delete_course_quiz_weight($gid, $nid) {
  db_delete('opigno_quiz_app_quiz_sort')
    ->condition('gid', $gid)
    ->condition('quiz_nid', $nid)
    ->execute();
}

/**
 * Helper function to fetch all the required quizzes for the passed course node.
 *
 * @param  stdClass $node
 *
 * @return array
 */
function opigno_quiz_app_get_all_required_quizzes($node) {
  $quizzes =& drupal_static(__FUNCTION__);
  if (!isset($quizzes[$node->nid])) {
    $quizzes[$node->nid] = array();
    if (isset($node->course_required_quiz_ref[LANGUAGE_NONE])) {
      foreach ($node->course_required_quiz_ref[LANGUAGE_NONE] as $item) {
        $nody = node_load($item['target_id']);
        if ($nody->type == "quiz") {
          $nody = node_load($item['target_id']);
          $quizzes[$node->nid][$item['target_id']] = node_load($item['target_id']);
        }
      }
    }
  }
  return $quizzes[$node->nid];
}

/**
 * Helper function to check if the user passed all required quizzes inside the course.
 *
 * @param  int $nid
 * @param  int $uid
 *
 * @return bool
 */
function opigno_quiz_app_user_passed($nid, $uid) {
  $cache =& drupal_static(__FUNCTION__);
  if (!isset($cache["{$nid}:{$uid}"])) {

    // Always false for none-courses.
    $cache["{$nid}:{$uid}"] = FALSE;
    $node = node_load($nid);
    if ($node->type == OPIGNO_COURSE_BUNDLE) {

      // Default to true (if no quizzes).
      $cache["{$nid}:{$uid}"] = TRUE;
      $quizzes = opigno_quiz_app_get_all_required_quizzes($node);
      $all_scores = quiz_get_score_data(array_keys($quizzes), $uid);
      foreach ($all_scores as $score) {
        if ($score->percent_pass > $score->percent_score) {
          $cache["{$nid}:{$uid}"] = FALSE;
          break;
        }
      }
      if (module_exists('opigno_in_house_training_app')) {
        $iht = opigno_in_house_get_all_required_iht($node);
        foreach ($iht as $index => $rest) {
          $value = opigno_in_house_training_score_form_get_default_value($index, $uid);
          if ($value['status'] != 1) {
            $cache["{$nid}:{$uid}"] = FALSE;
            break;
          }
        }
      }
      if (module_exists('opigno_webex_app')) {
        $webx = opigno_webex_app_get_all_required_quizzes($node);
        foreach ($webx as $index => $rest) {
          $value = opigno_webex_attendance_form_get_default_value($index, $uid);
          if ($value['status'] != 1) {
            $cache["{$nid}:{$uid}"] = FALSE;
            break;
          }
        }
      }
      if (module_exists('opigno_live_meetings')) {
        $meetings = opigno_live_meetings_get_all_required_meetings($node);
        foreach ($meetings as $index => $rest) {
          $value = opigno_live_meetings_score_get_db_values($index, $uid);
          if ($value['status'] != 1) {
            $cache["{$nid}:{$uid}"] = FALSE;
            break;
          }
        }
      }
    }
  }

  // Invoke all the hook if the user passed the course and if the user is not already in the table of passed users
  if ($cache["{$nid}:{$uid}"]) {
    if (module_exists('opigno_statistics_app')) {
      $results = opigno_statistics_app_query_status_from_course_and_user($nid, $uid);
      if (empty($results)) {

        // In these invoke, the user will be saved in the table of passed users (statistics app)
        module_invoke_all('opigno_course_passed', $nid, $uid);
      }
    }
    else {
      module_invoke_all('opigno_course_passed', $nid, $uid);
    }
  }
  return $cache["{$nid}:{$uid}"];
}

/**
 * Helper function to get all results for a given course and user.
 *
 * @param  int $uid
 * @param  int $nid
 * @param  bool $filter_access = FALSE
 *
 * @return array|false
 */
function opigno_quiz_app_get_course_data_result($uid, $nid, $filter_access = FALSE) {
  $cache =& drupal_static(__FUNCTION__);
  $filter_access = (int) $filter_access;
  if (!isset($cache["{$nid}:{$uid}:{$filter_access}"])) {
    $node = node_load($nid);
    $quizzes = opigno_quiz_app_get_all_required_quizzes($node);
    $final_score = 0;
    $failed = OPIGNO_QUIZ_APP_PASSED;
    $total_time = 0;
    $count = 0;
    if ($filter_access) {
      $temp = array();
      foreach ($quizzes as $quiz_nid => $quiz) {
        if (opigno_quiz_app_access_node_teacher_results($node, $quiz)) {
          $temp[$quiz_nid] = $quiz;
        }
      }
      $quizzes = $temp;
    }
    if (empty($quizzes)) {
      $cache["{$nid}:{$uid}:{$filter_access}"] = FALSE;
      return FALSE;
    }
    $nids = array_keys($quizzes);

    // Fetch all scores to calculate the total time spent.
    $all_scores = opigno_quiz_app_get_score_data($nids, $uid);
    $quiz_total_time = array();
    foreach ($all_scores as $quiz_nid => $results) {
      foreach ($results as $rid => $score) {
        if ($score->time_end != 0) {
          if (!isset($quiz_total_time[$quiz_nid])) {
            $quiz_total_time[$quiz_nid] = 0;
          }
          $total_time += $score->time_end - $score->time_start;
          $quiz_total_time[$quiz_nid] += $score->time_end - $score->time_start;
        }
      }
    }

    // Get only best scores for final table.
    $all_scores = quiz_get_score_data($nids, $uid);
    foreach ($all_scores as $score) {
      if (isset($quizzes[$score->nid])) {
        $info['quizzes'][$quizzes[$score->nid]->title]['passed'] = isset($score->percent_score) ? $score->percent_score >= $score->percent_pass ? OPIGNO_QUIZ_APP_PASSED : OPIGNO_QUIZ_APP_FAILED : OPIGNO_QUIZ_APP_PENDING;
        $info['quizzes'][$quizzes[$score->nid]->title]['score'] = isset($score->percent_score) ? $score->percent_score : NULL;
        $info['quizzes'][$quizzes[$score->nid]->title]['total_time'] = isset($quiz_total_time[$score->nid]) ? $quiz_total_time[$score->nid] : NULL;
        if (isset($score->percent_score) && isset($quizzes[$score->nid]->quiz_weight[LANGUAGE_NONE][0]['value'])) {
          $final_score += $quizzes[$score->nid]->quiz_weight[LANGUAGE_NONE][0]['value'] * $score->percent_score;
          $count += $quizzes[$score->nid]->quiz_weight[LANGUAGE_NONE][0]['value'];
        }
        elseif (isset($score->percent_score)) {
          $final_score += $score->percent_score;
          $count++;
        }
        else {
          $failed = OPIGNO_QUIZ_APP_PENDING;
        }
        if ($failed !== OPIGNO_QUIZ_APP_PENDING && $failed !== OPIGNO_QUIZ_APP_FAILED && $score->percent_score < $score->percent_pass) {
          $failed = OPIGNO_QUIZ_APP_FAILED;
        }
      }
    }

    ////////////////resultschange//////////////
    if (module_exists('opigno_in_house_training_app')) {
      $iht = opigno_in_house_get_all_required_iht($node);
      foreach ($iht as $index => $rest) {
        $value = opigno_in_house_training_score_form_get_default_value($index, $uid);
        $info['iht'][$rest->title]['passed'] = OPIGNO_QUIZ_APP_PASSED;
        $info['iht'][$rest->title]['score'] = t("Attended");
        $info['iht'][$rest->title]['total_time'] = opigno_calendar_app_get_node_duration($rest);
        if ($value['status'] != 1) {
          $failed = OPIGNO_QUIZ_APP_FAILED;
          $info['iht'][$rest->title]['passed'] = OPIGNO_QUIZ_APP_FAILED;
          $info['iht'][$rest->title]['score'] = t("Absent");
          $info['iht'][$rest->title]['total_time'] = 0;
        }
        $total_time += $info['iht'][$rest->title]['total_time'];
      }
    }
    if (module_exists('opigno_live_meetings')) {
      $meetings = opigno_live_meetings_get_all_required_meetings($node);
      foreach ($meetings as $index => $rest) {
        $value = opigno_live_meetings_score_get_db_values($index, $uid);
        $info['live_meeting'][$rest->title]['passed'] = OPIGNO_QUIZ_APP_PASSED;
        $info['live_meeting'][$rest->title]['score'] = t("Attended");
        $info['live_meeting'][$rest->title]['total_time'] = opigno_calendar_app_get_node_duration($rest);
        if ($value['status'] != 1) {
          $failed = OPIGNO_QUIZ_APP_FAILED;
          $info['live_meeting'][$rest->title]['passed'] = OPIGNO_QUIZ_APP_FAILED;
          $info['live_meeting'][$rest->title]['score'] = t("Absent");
          $info['live_meeting'][$rest->title]['total_time'] = 0;
        }
        $total_time += $info['live_meeting'][$rest->title]['total_time'];
      }
    }
    if (module_exists('opigno_webex_app')) {
      $webx = opigno_webex_app_get_all_required_quizzes($node);
      foreach ($webx as $index => $rest) {
        $value = opigno_webex_attendance_form_get_default_value($index, $uid);
        $info['webx'][$rest->title]['passed'] = OPIGNO_QUIZ_APP_PASSED;
        $info['webx'][$rest->title]['score'] = t("Attended");
        $info['webx'][$rest->title]['total_time'] = opigno_calendar_app_get_node_duration($rest);
        if ($value['status'] != 1) {
          $failed = OPIGNO_QUIZ_APP_FAILED;
          $info['webx'][$rest->title]['passed'] = OPIGNO_QUIZ_APP_FAILED;
          $info['webx'][$rest->title]['score'] = t("Absent");
          $info['webx'][$rest->title]['total_time'] = 0;
        }
        $total_time += $info['webx'][$rest->title]['total_time'];
      }
    }

    ///////////////////////////////////////////////

    // Failsafe. There were no visible results for allowed quizzes.
    if (empty($info['quizzes'])) {
      $cache["{$nid}:{$uid}:{$filter_access}"] = FALSE;
      return FALSE;
    }
    $info['passed'] = $failed;
    $info['total_score'] = $count ? round($final_score / $count) : 0;
    $info['total_time'] = $total_time;
    $cache["{$nid}:{$uid}:{$filter_access}"] = $info;
  }
  return $cache["{$nid}:{$uid}:{$filter_access}"];
}

/**
 * DEPRECATED.
 * A typo in the function name omitted the 'app' word.
 * This is now an alias to opigno_quiz_app_get_score_data(), which should be used instead.
 * @see opigno_quiz_app_get_score_data().
 */
function opigno_quiz_get_score_data($nids, $uid) {
  drupal_set_message('Called deprecated opigno_quiz_get_score_data(). Should be replaced with opigno_quiz_app_get_score_data()', 'warning');
  if (function_exists('dpm')) {
    dpm(debug_backtrace(), 'Callstack to deprecated opigno_quiz_get_score_data()');
  }
  return opigno_quiz_app_get_score_data($nids, $uid);
}

/**
 * Helper function to fetch score data for quizzes. The difference with the core quiz function is that
 * this function gets all results (not just the best one) and also returns the start and end time.
 *
 * @param  array $nids
 * @param  int $uid
 *
 * @return array
 */
function opigno_quiz_app_get_score_data($nids, $uid) {
  $scores = array();
  $query = db_select('quiz_node_results', 'r');
  $query
    ->leftJoin('quiz_node_properties', 'p', 'r.vid = p.vid');
  $query
    ->addField('r', 'score', 'percent_score');
  $query
    ->addField('p', 'pass_rate', 'percent_pass');
  $result = $query
    ->fields('r', array(
    'result_id',
    'nid',
    'time_start',
    'time_end',
  ))
    ->condition('r.uid', $uid)
    ->condition('r.time_end', 0, '>')
    ->condition('r.nid', $nids, 'IN')
    ->execute();
  while ($score = $result
    ->fetchObject()) {
    $scores[$score->nid][$score->result_id] = $score;
  }
  return $scores;
}

/**
 * Helper function to get the certificate download link from the Opigno Certificate App.
 *
 * @param  int $nid
 * @param  int $uid
 *
 * @return string
 */
function opigno_quiz_app_get_certificate($nid, $uid) {
  $download_certificate = '';
  if (function_exists('opigno_certificate_app_get_certificate_path')) {
    $certificate_path = opigno_certificate_app_get_certificate_path($nid, $uid);
    if ($certificate_path) {
      $download_certificate = '<a href="' . url($certificate_path) . '" class="opigno-quiz-app-download-certificate opigno-certificate-app-download-certificate-link"><div class="d-inline-block">' . t("certificate") . '</div></a>';
    }
    else {
      $download_certificate = '<span class="opigno-quiz-app-download-certificate"><div class="d-inline-block">' . t("certificate") . '</div></span>';
    }
  }
  return $download_certificate;
}

/**
 * Theme callback: display user results.
 */
function theme_opigno_quiz_app_user_results($vars) {
  $html = '';
  foreach ($vars['results'] as $course_info) {
    $html .= theme('opigno_quiz_app_user_course_results', array(
      'user' => $vars['user'],
      'course_node' => $course_info['node'],
      'course_results' => $course_info,
    ));
  }
  return $html;
}

/**
 * Theme callback: display user results for a specific course.
 */
function theme_opigno_quiz_app_user_course_results($vars) {
  switch ($vars['course_results']['passed']) {
    case OPIGNO_QUIZ_APP_PASSED:
      $img_name = 'passed.jpg';
      $status_text = t('Passed');
      break;
    case OPIGNO_QUIZ_APP_FAILED:
      $img_name = 'failed.jpg';
      $status_text = t('Failed');
      break;
    case OPIGNO_QUIZ_APP_PENDING:
    default:
      $img_name = 'pending.jpg';
      $status_text = t('Pending');
      break;
  }
  $download_certificate = opigno_quiz_app_get_certificate($vars['course_node']->nid, $vars['user']->uid);
  $header = array(
    0 => array(
      'class' => array(
        'opigno-quiz-app-course-status',
      ),
      'colspan' => 4,
    ),
  );
  if (path_is_admin(current_path())) {
    $detailspath = "/node/{$vars['course_node']->nid}/quiz-results/{$vars['user']->uid}";
    $header[0]['data'] = t("@title (!status - <a href='!url'>see details</a>)", array(
      '@title' => $vars['course_node']->title,
      '!status' => $status_text,
      '!url' => url($detailspath),
    ));
  }
  else {
    if (!empty($vars['course_node']->nid)) {
      $detailspath = "/node/{$vars['course_node']->nid}/my-quiz-results";
      $header[0]['data'] = t("@title (!status - <a href='!url'>see details</a>)", array(
        '@title' => $vars['course_node']->title,
        '!status' => $status_text,
        '!url' => url($detailspath),
      ));
    }
    else {
      $header[0]['data'] = t("No required lessons for this course");
    }
  }
  $result = '<div class="d-inline-block v-align-top mr-4"><img src="' . url(drupal_get_path('module', 'opigno_quiz_app') . '/img/' . $img_name) . '" alt="" /></div>';
  $result .= '<div class="d-inline-block v-align-top"><div><strong>' . $status_text . '</strong></div>';

  // $result .= '<div>' . t('Pass rate:') . ' ' . '%</div>';
  $result .= '<div>' . t('Score:') . ' ' . (isset($vars['course_results']['total_score']) ? $vars['course_results']['total_score'] : '-') . '%</div></div>';
  $rows = array();
  $rows[] = array(
    'data' => array(
      $result,
      array(
        'data' => '<div class="bar" style="width :' . (isset($vars['course_results']['total_score']) ? $vars['course_results']['total_score'] : 0) . '%"></div><div>' . (isset($vars['course_results']['total_score']) ? $vars['course_results']['total_score'] : '-') . '%</div>',
        'class' => 'percent-bar text-center',
      ),
      array(
        'data' => isset($vars['course_results']['total_time']) ? gmdate('H:i:s', $vars['course_results']['total_time']) : '-',
        'class' => 'global-time text-center',
      ),
      array(
        'data' => $download_certificate,
        'class' => 'text-center',
      ),
    ),
    'class' => array(
      'header-row',
    ),
  );
  if (!empty($vars['course_results']['quizzes'])) {
    foreach ($vars['course_results']['quizzes'] as $quiz_title => $score) {
      $rows[] = array(
        'data' => array(
          check_plain($quiz_title),
          array(
            'data' => isset($score['score']) ? $score['score'] . '%' : '-',
            'class' => 'text-right',
          ),
          array(
            'data' => isset($score['total_time']) ? gmdate('H:i:s', $score['total_time']) : '-',
            'class' => 'text-right',
          ),
          array(
            'data' => NULL,
            'class' => 'text-right',
          ),
        ),
      );
    }
  }

  //////////////////////////////////////////
  if (!empty($vars['course_results']['iht'])) {
    foreach ($vars['course_results']['iht'] as $quiz_title => $score) {
      $rows[] = array(
        'data' => array(
          check_plain($quiz_title),
          array(
            'data' => isset($score['score']) ? $score['score'] . '%' : '-',
            'class' => 'text-right',
          ),
          array(
            'data' => isset($score['total_time']) ? gmdate('H:i:s', $score['total_time']) : '-',
            'class' => 'text-right',
          ),
          array(
            'data' => NULL,
            'class' => 'text-right',
          ),
        ),
      );
    }
  }
  if (!empty($vars['course_results']['live_meeting'])) {
    foreach ($vars['course_results']['live_meeting'] as $quiz_title => $score) {
      $rows[] = array(
        'data' => array(
          check_plain($quiz_title),
          array(
            'data' => isset($score['score']) ? $score['score'] . '%' : '-',
            'class' => 'text-right',
          ),
          array(
            'data' => isset($score['total_time']) ? gmdate('H:i:s', $score['total_time']) : '-',
            'class' => 'text-right',
          ),
          array(
            'data' => NULL,
            'class' => 'text-right',
          ),
        ),
      );
    }
  }
  if (!empty($vars['course_results']['webx'])) {
    foreach ($vars['course_results']['webx'] as $quiz_title => $score) {
      $rows[] = array(
        'data' => array(
          check_plain($quiz_title),
          array(
            'data' => isset($score['score']) ? $score['score'] . '%' : '-',
            'class' => 'text-right',
          ),
          array(
            'data' => isset($score['total_time']) ? gmdate('H:i:s', $score['total_time']) : '-',
            'class' => 'text-right',
          ),
          array(
            'data' => NULL,
            'class' => 'text-right',
          ),
        ),
      );
    }
  }

  //////////////////////////////////////////*/
  return theme('table', array(
    'header' => $header,
    'rows' => $rows,
    'attributes' => array(
      'class' => array(
        'opigno-quiz-app-results-table',
        'opigno-quiz-app-results-collapsible-table',
        'opigno-quiz-app-user-course-results-table',
      ),
    ),
  ));
}

/**
 * Theme callback: display course results.
 */
function theme_opigno_quiz_app_course_results($vars) {
  $html = '';
  foreach ($vars['results'] as $results) {
    $html .= theme('opigno_quiz_app_course_user_results', array(
      'user' => $results['user'],
      'course' => $vars['course'],
      'results' => $results,
    ));
  }
  return $html;
}

/**
 * Theme callback: display course results for a specific user.
 */
function theme_opigno_quiz_app_course_user_results($vars) {
  $download_certificate = opigno_quiz_app_get_certificate($vars['course']->nid, $vars['user']->uid);
  $header = array(
    array(
      'data' => t("@name (!status)", array(
        '@name' => $vars['user']->name,
        '!status' => $vars['results']['passed'] == OPIGNO_QUIZ_APP_PASSED ? t("Passed") : ($vars['results']['passed'] == OPIGNO_QUIZ_APP_FAILED ? t("Failed") : t("Pending")),
      )) . $download_certificate,
      'class' => array(
        'opigno-quiz-app-course-status',
        'opigno-quiz-app-course-status-' . ($vars['results']['passed'] == OPIGNO_QUIZ_APP_PASSED ? 'passed' : ($vars['results']['passed'] == OPIGNO_QUIZ_APP_FAILED ? 'failed' : 'pending')),
      ),
    ),
    array(
      'data' => isset($vars['results']['total_score']) ? $vars['results']['total_score'] : '-',
      'class' => array(
        'opigno-quiz-app-course-total-score',
      ),
    ),
    array(
      'data' => isset($vars['results']['total_time']) ? gmdate('H:i:s', $vars['results']['total_time']) : '-',
      'class' => array(
        'opigno-quiz-app-course-total-time',
      ),
    ),
  );
  $rows = array();
  if (!empty($vars['results']['quizzes'])) {
    foreach ($vars['results']['quizzes'] as $quiz_title => $score) {
      $rows[] = array(
        'class' => array(
          'opigno-quiz-app-quiz-result',
          'opigno-quiz-app-quiz-result-' . ($score['passed'] == OPIGNO_QUIZ_APP_PASSED ? 'passed' : ($score['passed'] == OPIGNO_QUIZ_APP_FAILED ? 'failed' : 'pending')),
        ),
        'data' => array(
          check_plain($quiz_title),
          isset($score['score']) ? $score['score'] : '-',
          isset($score['total_time']) ? gmdate('H:i:s', $score['total_time']) : '-',
        ),
      );
    }
  }

  //////////////////////////////////////////
  if (!empty($vars['results']['iht'])) {
    foreach ($vars['results']['iht'] as $quiz_title => $score) {
      $rows[] = array(
        'class' => array(
          'opigno-quiz-app-quiz-result',
          'opigno-quiz-app-quiz-result-' . ($score['passed'] == OPIGNO_QUIZ_APP_PASSED ? 'passed' : ($score['passed'] == OPIGNO_QUIZ_APP_FAILED ? 'failed' : 'pending')),
        ),
        'data' => array(
          check_plain($quiz_title),
          isset($score['score']) ? $score['score'] : '-',
          isset($score['total_time']) ? gmdate('H:i:s', $score['total_time']) : '-',
        ),
      );
    }
  }
  if (!empty($vars['results']['live_meeting'])) {
    foreach ($vars['results']['live_meeting'] as $quiz_title => $score) {
      $rows[] = array(
        'class' => array(
          'opigno-quiz-app-quiz-result',
          'opigno-quiz-app-quiz-result-' . ($score['passed'] == OPIGNO_QUIZ_APP_PASSED ? 'passed' : ($score['passed'] == OPIGNO_QUIZ_APP_FAILED ? 'failed' : 'pending')),
        ),
        'data' => array(
          check_plain($quiz_title),
          isset($score['score']) ? $score['score'] : '-',
          isset($score['total_time']) ? gmdate('H:i:s', $score['total_time']) : '-',
        ),
      );
    }
  }
  if (!empty($vars['results']['webx'])) {
    foreach ($vars['results']['webx'] as $quiz_title => $score) {
      $rows[] = array(
        'class' => array(
          'opigno-quiz-app-quiz-result',
          'opigno-quiz-app-quiz-result-' . ($score['passed'] == OPIGNO_QUIZ_APP_PASSED ? 'passed' : ($score['passed'] == OPIGNO_QUIZ_APP_FAILED ? 'failed' : 'pending')),
        ),
        'data' => array(
          check_plain($quiz_title),
          isset($score['score']) ? $score['score'] : '-',
          isset($score['total_time']) ? gmdate('H:i:s', $score['total_time']) : '-',
        ),
      );
    }
  }

  //////////////////////////////////////////
  return theme('table', array(
    'header' => $header,
    'rows' => $rows,
    'attributes' => array(
      'class' => array(
        'opigno-quiz-app-results-table',
        'opigno-quiz-app-results-collapsible-table',
        'opigno-quiz-app-course-user-results-table',
      ),
    ),
  ));
}

/**
 * Theme callback: render the order form.
 */
function theme_opigno_quiz_app_sort_course_quizzes_form($vars) {
  $form = $vars['form'];
  drupal_add_tabledrag('opigno-quiz-app-sort-course-quizzes', 'order', 'sibling', 'opigno-quiz-app-sort-course-quizzes-weight');
  $header = array(
    function_exists('locale') ? locale(QUIZ_NAME) : QUIZ_NAME,
    t("Weight"),
  );
  $rows = array();
  foreach ($form['table'] as $key => $item) {
    if (preg_match('/quiz_[0-9]+/', $key)) {
      $data = array();
      $data[] = drupal_render($item['title']) . drupal_render($item['nid']);
      $data[] = drupal_render($item['weight']);
      $rows[] = array(
        'data' => $data,
        'class' => array(
          'draggable',
        ),
      );
    }
  }
  $form['table'] = array(
    '#markup' => theme('table', array(
      'header' => $header,
      'rows' => $rows,
      'attributes' => array(
        'id' => 'opigno-quiz-app-sort-course-quizzes',
      ),
    )),
    '#weight' => 1,
  );
  return drupal_render_children($form);
}

/**
 * Enable fullscreen mode for the passed Quiz node.
 *
 * @param object $node
 */
function _opigno_quiz_app_enable_fullscreen($node) {
  $included =& drupal_static(__FUNCTION__);
  if (empty($included)) {
    drupal_add_library('system', 'jquery.cookie');
    $path = drupal_get_path('module', 'opigno_quiz_app');
    drupal_add_css("{$path}/css/opigno_quiz_app.css");
    drupal_add_js("{$path}/js/opigno_quiz_app.fullscreen.js");
    $included = TRUE;
  }
  drupal_add_js(array(
    'opignoQuizApp' => array(
      'fullScreen' => array(
        array(
          'nid' => $node->nid,
        ),
      ),
    ),
  ), 'setting');
}

/**
 * Helper function to determine if we're adding a Question node.
 *
 * @return bool
 */
function opigno_quiz_app_is_node_add_question() {
  static $result;
  if (!isset($result)) {
    $question_types = opigno_quiz_app_get_quiz_question_types();
    $result = preg_match('/^node\\/add\\/(' . str_replace('_', '-', implode('|', $question_types)) . ')$/', current_path());
  }
  return $result;
}

/**
 * Helper function to get the available quiz question types.
 *
 * Statically cached for better performance.
 *
 * @return array
 */
function opigno_quiz_app_get_quiz_question_types() {
  static $types;
  if (!isset($types)) {
    $types = array_keys(_quiz_question_get_implementations());
  }
  return $types;
}

/**
 * Quiz node form alter
 *
 * Shows/Hides calendar date field
 */
function opigno_quiz_app_form_alter(&$form, &$form_state, $form_id) {
  if ($form_id == "quiz_node_form" && isset($form['type']['#value']) && $form['type']['#value'] == "quiz") {
    $form['field_add_to_calendar']['#parent'] = 'quiz_availability';
    $form['opigno_calendar_date']['#parent'] = 'quiz_availability';
    $form['opigno_calendar_date']['#states'] = array(
      'invisible' => array(
        '#edit-field-add-to-calendar-und' => array(
          'checked' => FALSE,
        ),
      ),
    );
    $form['quiz_availability']['field_add_to_calendar'] = $form['field_add_to_calendar'];
    $form['quiz_availability']['opigno_calendar_date'] = $form['opigno_calendar_date'];
    $form['#submit'][] = 'opigno_quiz_app_unset_opigno_calendar_date';
    unset($form['field_add_to_calendar']);
    unset($form['opigno_calendar_date']);
    $form['#validate'][] = "opigno_quiz_app_check_questions_for_type";
  }
  elseif ($form_id == 'quiz_question_answering_form') {

    /// Hides the finish quiz for users not registered

    //$form['question']['#weight']=-1;

    // Add touch support on mobile.
    if (isset($form['question_nid'])) {
      $q_node = node_load($form['question_nid']['#value']);
      if ($q_node->type === 'quiz_drag_drop') {
        $form['#attached']['js'][] = drupal_get_path('module', 'opigno_quiz_app') . '/js/jquery.ui.touch-punch.min.js';
      }
    }
    $form['is_doubtful']['#weight'] = 49;
    $form['navigation']['#weight'] = 50;
    if (!user_is_logged_in()) {

      /// Is it the last question?
      if (isset($form['#attributes']['data-confirm-message'])) {
        unset($form['navigation']['submit']);
        unset($form['navigation']['op']);
      }
    }

    /////////////////////////////////////////////////////
    $path = drupal_get_path('module', 'opigno_quiz_app');
    drupal_add_js("{$path}/js/opigno_quiz_app.js");
  }
}
function opigno_quiz_app_check_questions_for_type($form, &$form_state) {
  if (isset($form_state['values']['quiz_type'][LANGUAGE_NONE][0]['value']) && isset($form_state['values']['nid']) && isset($form_state['values']['vid'])) {
    switch ($form_state['values']['quiz_type'][LANGUAGE_NONE][0]['value']) {
      case 'quiz':
        $quiz_questions = quiz_get_questions($form_state['values']['nid'], $form_state['values']['vid'], TRUE, TRUE, TRUE, TRUE);
        foreach ($quiz_questions as $index => $value) {
          if ($value->type == "quiz_directions") {
            form_set_error('quiz_type', t('You cannot set this lesson as quiz type, remove all slides questions first.'));
          }
        }
        break;
      case "theory":
        $quiz_questions = quiz_get_questions($form_state['values']['nid'], $form_state['values']['vid'], TRUE, TRUE, TRUE, TRUE);
        foreach ($quiz_questions as $index => $value) {
          if ($value->type != "quiz_directions") {
            form_set_error('quiz_type', t('You cannot set this lesson as theory type, remove all non slides questions first.'));
          }
        }
        break;
      case "mix":
        break;
    }
  }
}

/**
 * Quiz node submit function
 *
 * Removes calendar field in case of not beeing used
 */
function opigno_quiz_app_unset_opigno_calendar_date($form, &$form_state) {
  if ($form_state['values']['field_add_to_calendar'][LANGUAGE_NONE][0]['value'] == 0) {
    $form_state['values']['opigno_calendar_date'][LANGUAGE_NONE][0]['value'] = NULL;
    $form_state['values']['opigno_calendar_date'][LANGUAGE_NONE][0]['value2'] = NULL;
    $form_state['values']['opigno_calendar_date'][LANGUAGE_NONE][0]['timezone'] = NULL;
    $form_state['values']['opigno_calendar_date'][LANGUAGE_NONE][0]['offset'] = NULL;
    $form_state['values']['opigno_calendar_date'][LANGUAGE_NONE][0]['offset2'] = NULL;
    $form_state['values']['opigno_calendar_date'][LANGUAGE_NONE][0]['rrule'] = NULL;
  }
}

/////////////////////////////////////////// Group interface ////////////////////////////////////////////////////

/**
 * Implements hook_preprocess_page().
 */
function opigno_quiz_app_preprocess_page(&$vars) {
  $group = og_context('node');
  if (!empty($group['gid'])) {
    $class_context = NULL;
    $node = node_load($group['gid']);

    // Try to get class context
    if ($node->type == "course") {
      global $user;
      if (module_exists("opigno_class_app")) {
        $classes = opigno_class_app_classes_of_course_that_user_is_part_of($node->nid, $user->uid);
        if ($classes) {
          $class = key($classes);
          $node = node_load($class);
        }
        else {

          //Course with no class context
          $vars['group_state']['course'][$node->nid]['quiz'] = opigno_quiz_app_course_lessons_progress_and_time($group['gid']);
          return;
        }
      }
      else {
        $vars['group_state']['course'][$node->nid]['quiz'] = opigno_quiz_app_course_lessons_progress_and_time($group['gid']);
      }
    }
    if ($node->type == "class") {
      if (isset($node->opigno_class_courses[LANGUAGE_NONE])) {
        foreach ($node->opigno_class_courses[LANGUAGE_NONE] as $cindex => $course) {
          $vars['group_state']['course'][$course['target_id']]['quiz'] = opigno_quiz_app_course_lessons_progress_and_time($course['target_id']);
          if (module_exists("opigno_sort_groups")) {
            $vars['group_state']['course'][$course['target_id']]['weight'] = opigno_sort_groups_get_groups_weight($node->nid, $course['target_id']);
          }
        }
      }
    }
  }
  if (isset($vars['node']) && $vars['node']->type == "quiz") {
    if (user_is_logged_in()) {
      $lesson_id = opigno_quiz_app_is_quiz_path(current_path());
      if ($lesson_id) {
        global $user;
        $lesson = node_load($lesson_id);
        $courses_field = field_get_items('node', $lesson, 'og_group_ref');
        if ($courses_field) {
          foreach ($courses_field as $index => $target_id) {

            // When we are at the results page, the $_SESSION results is no loger set so we have to check for that.
            if (isset($_SESSION['quiz_' . $lesson_id]['result_id'])) {
              opigno_quiz_app_course_last_viewed($target_id['target_id'], $lesson_id, $_SESSION['quiz_' . $lesson_id]['result_id'], $user->uid);
            }
          }
        }
      }
    }
  }
}

/**
 * Helper function to check if we are in a quiz path.
 */
function opigno_quiz_app_is_quiz_path($path) {
  $menu_item = menu_get_item($path);
  if (isset($menu_item['path']) && $menu_item['path'] === 'node/%/take') {
    return $menu_item['original_map'][1];
  }
  return FALSE;
}
function opigno_quiz_app_course_lessons($course_nid) {
  global $user;
  $lessons = array();
  $query = db_select('node', 'n')
    ->fields('n', array(
    'nid',
    'title',
    'vid',
  ))
    ->condition('n.status', 1, '=')
    ->condition('n.type', 'quiz', '=');
  $query
    ->join('og_membership', 'og_m', 'og_m.etid = n.nid');

  //JOIN node with users
  $query
    ->fields('og_m', array(
    'gid',
  ))
    ->condition('og_m.gid', $course_nid, '=')
    ->condition('og_m.field_name', 'og_group_ref', '=')
    ->condition('og_m.state', 1, '=')
    ->condition('og_m.entity_type', 'node', '=');
  $result = $query
    ->execute();
  if (opigno_quiz_app_course_has_been_sorted($result, $course_nid)) {
    $query
      ->join('opigno_quiz_app_quiz_sort', 'oqs', 'oqs.quiz_nid = n.nid');

    //JOIN node with users
    $query
      ->fields('oqs', array(
      'weight',
    ))
      ->condition('oqs.gid', $course_nid, '=')
      ->orderBy('oqs.weight', 'ASC');
  }
  $result = $query
    ->execute();
  while ($record = $result
    ->fetchAssoc()) {
    if ($router_item = menu_get_item('node/' . $record['nid'])) {
      if ($router_item['access']) {
        $lessons[$course_nid][$record['nid']]['vid'] = $record['vid'];
      }
    }
  }
  return $lessons;
}
function opigno_quiz_app_course_lessons_progress_and_time($course_nid, $account = NULL) {
  if ($account == NULL) {
    global $user;
    $account = $user;
    $uid = $account->uid;
  }
  $lessons = opigno_quiz_app_course_lessons($course_nid);
  $lessons_ = array();
  foreach ($lessons as $course_nid => $quizs) {
    foreach ($quizs as $quiz_id => $quiz) {
      $score = quiz_get_score_data(array(
        $quiz_id,
      ), $uid);
      $lessons_[$course_nid][$quiz['vid']] = $score[$quiz['vid']];
      $total_time = 0;
      $all_scores = opigno_quiz_app_get_score_data(array(
        $quiz_id,
      ), $uid);
      foreach ($all_scores as $quiz_nid => $results) {
        foreach ($results as $rid => $score) {
          if ($score->time_end != 0) {
            if (!isset($quiz_total_time[$quiz_nid])) {
              $quiz_total_time[$quiz_nid] = 0;
            }
            $total_time += $score->time_end - $score->time_start;
            $quiz_total_time[$quiz_nid] += $score->time_end - $score->time_start;
          }
        }
      }
      $lessons_[$course_nid][$quiz['vid']]->total_time = $total_time;
    }
  }
  $displayinfo = array();
  $displayinfo['courses'] = $lessons_;
  if (!empty($lessons_)) {
    return theme_opigno_quiz_app_course_lessons($displayinfo);
  }
}

/**
 * Theme callback: display course quizes and results.
 */
function theme_opigno_quiz_app_course_lessons($vars) {
  $rows = array();
  if (!empty($vars['courses'])) {
    foreach ($vars['courses'] as $course_id => $lessons) {
      foreach ($lessons as $lesson_id => $lesson) {
        $selected = "";
        if (strpos(current_path(), 'node/' . $lesson->nid) !== false) {
          $selected = "selected";
        }
        $rows[] = array(
          'class' => array(),
          'data' => array(
            l($lesson->title, 'node/' . $lesson->nid . '/take', array(
              'attributes' => array(
                'class' => array(
                  $selected,
                ),
              ),
            )),
            isset($lesson->percent_score) ? '<div class="opigno-quiz-app-group-status-quiz-result-' . ($lesson->percent_score >= $lesson->percent_pass ? "passed" : "failed") . '">' . $lesson->percent_score . '<div>' : '-',
            isset($lesson->total_time) && $lesson->total_time > 0 ? gmdate('H:i:s', $lesson->total_time) : '-',
          ),
        );
      }
    }
  }
  $header = array(
    array(
      'data' => t('Lessons'),
      'class' => array(
        'opigno-quiz-app-course-name',
      ),
    ),
    array(
      'data' => t('Score'),
      'class' => array(
        'opigno-quiz-app-course-total-score',
      ),
    ),
    array(
      'data' => t('Total Time'),
      'class' => array(
        'opigno-quiz-app-course-total-time',
      ),
    ),
  );
  return theme('table', array(
    'header' => $header,
    'rows' => $rows,
    'attributes' => array(
      'class' => array(),
    ),
  ));
}
function opigno_quiz_app_course_has_been_sorted($result, $course_nid) {
  while ($record = $result
    ->fetchAssoc()) {
    $query = db_select('opigno_quiz_app_quiz_sort', 'oqs')
      ->fields('oqs')
      ->condition('oqs.gid', $course_nid, '=')
      ->condition('oqs.quiz_nid', $record['nid'], '=');
    $result = $query
      ->execute();
    if (!($record = $result
      ->fetchAssoc())) {
      return false;
    }
  }
  return true;
}

//////// Overwriting quiz display ///////////////////////
function opigno_quiz_app_theme_registry_alter(&$theme_registry) {
  if (isset($theme_registry['quiz_no_feedback'])) {
    $theme_registry['quiz_no_feedback']['function'] = 'theme_opigno_quiz_no_feedback';
  }
  if (isset($theme_registry['quiz_take_summary'])) {
    $theme_registry['quiz_take_summary']['function'] = 'theme_opigno_quiz_take_summary';
  }
}
function theme_opigno_quiz_no_feedback() {
  return t('Thank you for taking the lesson!');
}

/*
function theme_opigno_quiz_take_summary_($variables) {
  $quiz = $variables['quiz'];
  $questions = $variables['questions'];
  $score = $variables['score'];
  $summary = $variables['summary'];
  $rid = $variables['rid'];
  // Set the title here so themers can adjust.
  drupal_set_title($quiz->title);

  // Display overall result.
  $output = '';
  if (!empty($score['possible_score'])) {
    if (!$score['is_evaluated']) {
      if (user_access('score taken quiz answer')) {
        $msg = t('Parts of this @quiz have not been evaluated yet. The score below is not final. <a class="self-score" href="!result_url">Click here</a> to give scores on your own.', array(
          '@quiz' => QUIZ_NAME,
          '!result_url' => url('node/' . $quiz->nid . '/results/' . $rid)
        ));
      }
      else {
        $msg = t('Parts of this @quiz have not been evaluated yet. The score below is not final.', array('@quiz' => QUIZ_NAME));
      }
      drupal_set_message($msg, 'warning');
    }
    $output .= '<div id="quiz_score_possible">' . t('You got %num_correct of %question_count possible points.', array(
        '%num_correct' => $score['numeric_score'],
        '%question_count' => $score['possible_score']
      )) . '</div>' . "\n";
    $output .= '<div id="quiz_score_percent">' . t('Your score: %score %', array('%score' => $score['percentage_score'])) . '</div>' . "\n";
  }
  if (isset($summary['passfail'])) {
    $output .= '<div id="quiz_summary">' . $summary['passfail'] . '</div>' . "\n";
  }
  if (isset($summary['result'])) {
    $output .= '<div id="quiz_summary">' . $summary['result'] . '</div>' . "\n";
  }
  // Get the feedback for all questions. These are included here to provide maximum flexibility for themers
  if ($quiz->display_feedback) {
    $form = drupal_get_form('quiz_report_form', $questions);
    $output .= drupal_render($form);
  }
  $group = og_context('node');
  $next = FALSE;
  $nextlink = "";
  if (!empty($group['gid'])) {
    $lessons = opigno_quiz_app_course_lessons($group['gid']);
    if (isset($lessons[$group['gid']])) {
      foreach ($lessons[$group['gid']] as $quiz_id => $quiz_) {
        if ($next == TRUE) {
          $nextlink = l(t("To next lesson"), "node/" . $quiz_id . "/take", array(
              'attributes' => array(
                'class' => array(
                  'opigno-quiz-action',
                  'action-element',
                )
              )
            ));
          break;
        }
        if ($quiz_id == $quiz->nid) {
          $next = TRUE;
        }
      }
    }
  }
  if (($nextlink == "") && (!empty($group['gid']))) {
    $nextlink = l(t("Back to course"), "node/" . $group['gid'], array(
        'attributes' => array(
          'class' => array(
            'opigno-quiz-action',
            'action-element',
          )
        )
      ));
  }
  return $output . '<div id="nextlink">' . $nextlink . '</div>';
}*/

////////////////////////////////////////////////////////////////////////////////
function theme_opigno_quiz_take_summary($variables) {
  $quiz = $variables['quiz'];
  $questions = $variables['questions'];
  $score = $variables['score'];
  $summary = $variables['summary'];
  $rid = $variables['rid'];

  // Set the title here so themers can adjust.
  drupal_set_title($quiz->title);

  // Display overall result.
  $output = '';
  if (!empty($score['possible_score'])) {
    if (!$score['is_evaluated']) {
      if (user_access('score taken quiz answer')) {
        $msg = t('Parts of this @quiz have not been evaluated yet. The score below is not final. <a class="self-score" href="!result_url">Click here</a> to give scores on your own.', array(
          '@quiz' => QUIZ_NAME,
          '!result_url' => url('node/' . $quiz->nid . '/results/' . $rid),
        ));
      }
      else {
        $msg = t('Parts of this @quiz have not been evaluated yet. The score below is not final.', array(
          '@quiz' => QUIZ_NAME,
        ));
      }
      drupal_set_message($msg, 'warning');
    }
    $output .= '<div id="quiz_score_possible">' . t('You got %num_correct of %question_count possible points.', array(
      '%num_correct' => $score['numeric_score'],
      '%question_count' => $score['possible_score'],
    )) . '</div>' . "\n";
    $output .= '<div id="quiz_score_percent">' . t('Your score: %score %', array(
      '%score' => $score['percentage_score'],
    )) . '</div>' . "\n";
  }
  if (isset($summary['passfail'])) {
    $output .= '<div id="quiz_summary">' . $summary['passfail'] . '</div>' . "\n";
  }
  if (isset($summary['result'])) {
    $output .= '<div id="quiz_summary">' . $summary['result'] . '</div>' . "\n";
  }

  // Get the feedback for all questions. These are included here to provide maximum flexibility for themers
  if ($quiz->display_feedback) {
    $form = drupal_get_form('quiz_report_form', $questions);
    $output .= drupal_render($form);
  }
  $group = og_context('node');
  $nextlink = "";
  if (!empty($group['gid'])) {
    global $user;

    // Save the course NID for later.
    $course_nid = $group['gid'];

    // If the course is in a class that the user is part of, get the class
    //   context. Else, get the course context.
    $classes = opigno_class_app_classes_of_course_that_user_is_part_of($group['gid'], $user->uid);
    if (!empty($classes)) {
      $class = key($classes);
      $group = node_load($class);
    }
    else {
      $group = node_load($group['gid']);
    }
    $continue = opigno_quiz_app_get_next_lesson_from_course($course_nid, $quiz->nid);
    if (isset($continue)) {
      $nextlink = l(t("To next lesson"), "node/" . $continue . "/take", array(
        'attributes' => array(
          'class' => array(
            'opigno-quiz-action',
            'action-element',
          ),
        ),
      ));
    }
    else {
      $nextlink = l(t("Back to @type", array(
        '@type' => $group->type,
      )), "node/" . $group->nid, array(
        'attributes' => array(
          'class' => array(
            'opigno-quiz-action',
            'action-element',
          ),
        ),
      ));
    }
  }
  return $output . '<div id="nextlink">' . $nextlink . '</div>';
}

////////////////////////////////////////////////////////////////////////////////
function opigno_quiz_app_views_query_alter(&$view, &$query) {
  if ($view->name == "opigno_quiz_student_course_results" && $view->current_display == "page_1" || $view->name == "opigno_quiz_student_views" && $view->current_display == "page_1") {
    if (isset($query->table_queue['quiz_node_results']['join']->left_table)) {
      $query->table_queue['quiz_node_results']['join']->left_field = "nid";
      $query->table_queue['quiz_node_results']['join']->field = "nid";
      $query->table_queue['quiz_node_results']['join']->definition['left_field'] = "nid";
      $query->table_queue['quiz_node_results']['join']->definition['field'] = "nid";
    }
  }
}

/**
 * Implements hook_init().
 */
function opigno_quiz_app_init() {
  if (user_is_logged_in()) {
    $page_node = menu_get_object();
    if (isset($page_node->type) && $page_node->type == "quiz") {
      if (isset($page_node->og_group_ref['und'][0]['target_id'])) {
        global $user;
        foreach ($page_node->og_group_ref[LANGUAGE_NONE] as $group) {
          if (og_is_member("node", $group['target_id'], 'user', $user, $states = array(
            OG_STATE_ACTIVE,
          ))) {
            opigno_db_insert_group_activity($group['target_id'], $user->uid);
            if (module_exists("opigno_class_app")) {
              $classes = opigno_class_app_classes_of_course_that_user_is_part_of($group['target_id'], $user->uid);
              foreach ($classes as $class) {
                opigno_db_insert_group_activity($class, $user->uid);
              }
            }
          }
        }
      }
    }
  }
}

/*
 * Helper function to get the quiz to do next for a group (class or course).
 *
 * @return int|null The next quiz ID or NULL if group is finished
 */
function opigno_quiz_get_continue_group($node) {
  global $user;
  if ($node->type == "class") {
    $courses = opigno_class_app_get_class_courses($node);
    foreach ($courses as $course) {
      $course = node_load($course);
      $quiz_to_continue = opigno_quiz_get_continue_course($course, $user->uid);
      if (!empty($quiz_to_continue)) {
        return $quiz_to_continue;
      }
    }
  }
  elseif ($node->type == "course") {
    return opigno_quiz_get_continue_course($node, $user->uid);
  }
  return null;
}

/*
 * Helper function to get the quiz to do next for a course
 *
 * @return int|null The next quiz ID or NULL if course is finished
 */
function opigno_quiz_get_continue_course($node, $uid) {
  $lessons = opigno_quiz_app_course_lessons($node->nid);
  if (!empty($lessons[$node->nid])) {
    $lessons_ids = array_keys($lessons[$node->nid]);
    foreach ($lessons_ids as $lesson_id) {
      $results = quiz_get_score_data(array(
        $lesson_id,
      ), $uid, FALSE);
      foreach ($results as $score) {
        if ($score->percent_pass > $score->percent_score) {
          return $lesson_id;
        }
      }
    }
  }
  return NULL;
}

/**
 * Helper function that returns the first lesson's ID of a group (course or class)
 *
 * @return int|null The first quiz of the course or NULL if no quiz in this group
 */
function opigno_quiz_app_get_first_lesson_from_group($node) {

  // If it's a class, get the first course NID
  if (!empty($node->type) && $node->type == 'class') {
    $courses_nids = opigno_class_app_get_class_courses($node);

    // If no course, return NULL.
    if (empty($courses_nids)) {
      return NULL;
    }
    $nid = $courses_nids[0];
  }
  else {
    $nid = $node->nid;
  }

  // Get all the lessons from the course. If no lesson, returns NULL.
  $lessons = opigno_quiz_app_course_lessons($nid);
  if (empty($lessons[$nid])) {
    return null;
  }

  // The lessons IDs are the keys of this array. Get the first key.
  $lessons_ids = array_keys($lessons[$nid]);
  return empty($lessons_ids[0]) ? null : $lessons_ids[0];
}

/**
 * Returns the next lesson NID for a course.
 *
 * @return int|null The next lesson or NULL if no more lesson
 */
function opigno_quiz_app_get_next_lesson_from_course($course_nid, $current_lesson_nid) {

  // From the current course, get the next lesson.
  // Get all the lessons from the course. If no lesson, return NULL.
  $lessons = opigno_quiz_app_course_lessons($course_nid);
  if (empty($lessons[$course_nid])) {
    return NULL;
  }

  // Loop through all lessons. If we arrive to the current lesson, do a
  //   loop more and return the next lesson NID.
  //   If there is no more loop, it means there is no more lesson. Return NULL.
  $return_next_lesson = false;
  foreach ($lessons[$course_nid] as $lesson_nid => $lesson_vid) {
    if ($return_next_lesson) {
      return $lesson_nid;
    }
    if ($lesson_nid == $current_lesson_nid) {
      $return_next_lesson = true;
    }
  }

  // If no more lesson in this course, return NULL
  return NULL;
}

/**
 * Helper function to get the course or class progression in percent
 */
function opigno_quiz_app_get_course_class_progression($group_nid, $uid = null) {
  $node = node_load($group_nid);
  switch ($node->type) {
    case 'class':
      return opigno_quiz_app_get_class_progression($group_nid, $uid);
    case 'course':
      return opigno_quiz_app_get_course_progression($group_nid, $uid);
    default:
      return 0;
  }
}

/**
 * Helper function to get the course progression in percent
 */
function opigno_quiz_app_get_course_progression($course_nid, $uid = null) {
  if ($uid === null) {
    global $user;
    $uid = $user->uid;
  }

  // Get all the lessons from this course and get all the best scores for this course
  $course = node_load($course_nid);
  $quizzes_nids = opigno_quiz_app_get_course_quizzes($course);
  $scores = quiz_get_score_data($quizzes_nids, $uid);

  // For each lessons, check if the user has tried it.
  $passed_lessons = 0;
  $total_lessons = count($quizzes_nids);
  foreach ($scores as $score) {

    // If tried and success, give 1 (total + 1)
    if ($score->percent_score >= $score->percent_pass) {
      $passed_lessons++;
    }
  }

  // Do percentage (nb success / total)
  if ($total_lessons == 0) {
    return 100;
  }
  else {
    return round($passed_lessons * 100 / $total_lessons);
  }
}

/**
 * Helper function to get the class progression in percent
 */
function opigno_quiz_app_get_class_progression($class_nid, $uid = null) {
  if ($uid === null) {
    global $user;
    $uid = $user->uid;
  }
  $class = node_load($class_nid);

  // If no course, the class is completed
  if (empty($class->opigno_class_courses['und'])) {
    return 100;
  }

  // Get the courses that are linked to this class.
  // For each course, get the progression
  $courses_total_progression = 0;
  $nb_courses = 0;
  foreach ($class->opigno_class_courses['und'] as $course_value_raw) {
    $course_nid = $course_value_raw['target_id'];
    $courses_total_progression += opigno_quiz_app_get_course_progression($course_nid, $uid);
    $nb_courses++;
  }

  // If no courses, return 100% completed
  // Else, do the average.
  if ($nb_courses == 0) {
    return 100;
  }
  else {
    return round($courses_total_progression / $nb_courses);
  }
}

/**
 * Helper function to get the average score for a course or a class.
 */
function opigno_quiz_app_get_course_class_score_average($node_nid, $uid = null) {
  if ($uid === null) {
    global $user;
    $uid = $user->uid;
  }

  // If class given, get all the courses
  $node = node_load($node_nid);
  if ($node->type == 'class') {

    // If no course, no score.
    if (empty($node->opigno_class_courses[LANGUAGE_NONE])) {
      return 0;
    }
    $courses_nids = array_map(function ($entry) {
      return $entry['target_id'];
    }, $node->opigno_class_courses[LANGUAGE_NONE]);
  }
  else {
    if ($node->type == 'course') {
      $courses_nids = array(
        $node_nid,
      );
    }
    else {
      return 0;

      // Should never run through here...
    }
  }

  // Then get all the lessons from theses courses.
  $query = new EntityFieldQuery();
  $query
    ->entityCondition('entity_type', 'node');
  $query
    ->propertyCondition('type', 'quiz');
  $query
    ->fieldCondition('og_group_ref', 'target_id', $courses_nids);
  $lessons_nids = $query
    ->execute();

  // If no lessons, no score.
  if (empty($lessons_nids)) {
    return 0;
  }
  $lessons_nids = array_keys($lessons_nids['node']);

  // Get all the scores for each lessons and do the totals
  $scores = quiz_get_score_data($lessons_nids, $uid);
  $nb_quizzes_done = 0;
  $total_score = 0;
  foreach ($scores as $score) {

    // If the quiz has not be taken, don't count it.
    if ($score->percent_score === null) {
      continue;
    }
    $total_score += $score->percent_score;
    $nb_quizzes_done++;
  }

  // Finally, do the average, but careful with division by 0.
  return $nb_quizzes_done == 0 ? 0 : round($total_score / $nb_quizzes_done);
}

/**
 * Helper function to check if a user passed the course or class given in parameter.
 */
function opigno_quiz_app_course_class_passed($nid, $uid = null) {
  if ($uid === null) {
    global $user;
    $uid = $user->uid;
  }

  // If node is class, get all the courses.
  // If node is course, add it in the courses to test.
  // If node is none, return false;
  $node = node_load($nid);
  switch ($node->type) {
    case 'class':

      // If no course, return true (passed).
      if (empty($node->opigno_class_courses[LANGUAGE_NONE])) {
        return true;
      }
      $courses_nids = array_map(function ($entry) {
        return $entry['target_id'];
      }, $node->opigno_class_courses[LANGUAGE_NONE]);
      break;
    case 'course':
      $courses_nids = array(
        $nid,
      );
      break;
    default:
      return false;
  }

  // For each courses, check if it's passed. If one is not, return false.
  foreach ($courses_nids as $course_nid) {
    if (!opigno_quiz_app_user_passed($course_nid, $uid)) {
      return false;
    }
  }
  return true;
}

/**
 * Helper function to know if a course is started or not by a user.
 */
function opigno_quiz_app_course_is_started($node_nid, $uid = null) {
  if ($uid === null) {
    global $user;
    $uid = $user->uid;
  }
  $last_viewed_lesson_in_course = opigno_quiz_app_course_get_last_viewed($node_nid, $uid);

  // If the user has not viewed any quiz yet, say "not started"
  if (!$last_viewed_lesson_in_course) {
    return false;
  }
  $last_viewed_lesson_in_course_result = $last_viewed_lesson_in_course['result_id'];
  $last_viewed_lesson_in_course = $last_viewed_lesson_in_course['quiz_nid'];

  // If the user has not finished the latest lesson, say "course started"
  if (!opigno_quiz_app_user_finished_quiz_result($last_viewed_lesson_in_course, $last_viewed_lesson_in_course_result, $uid)) {
    return true;
  }

  // If the user has finished the latest lesson he was in, try to find the next one.
  $lessons = opigno_quiz_app_course_lessons($node_nid);
  if (isset($lessons[$node_nid])) {
    $current_position = array_search($last_viewed_lesson_in_course, array_keys($lessons[$node_nid]));

    // If the lesson he just finished was the last one, return "not started (finished)"
    if ($current_position == count($lessons[$node_nid]) - 1) {
      return false;
    }
    else {
      return true;
    }
  }
  return false;
}

Functions

Namesort descending Description
opigno_quiz_app_access_node_sort_quizzes Access callback: check if user has access to sort quizzes inside the course.
opigno_quiz_app_access_node_teacher_results Access callback: check if user has access to student results for the specified course.
opigno_quiz_app_access_user_achievements Custom user access to the users achievements page
opigno_quiz_app_check_questions_for_type
opigno_quiz_app_course_class_passed Helper function to check if a user passed the course or class given in parameter.
opigno_quiz_app_course_get_last_viewed Gets the latest lesson the user has been in.
opigno_quiz_app_course_has_been_sorted
opigno_quiz_app_course_is_started Helper function to know if a course is started or not by a user.
opigno_quiz_app_course_last_viewed Sets in the db the latest lesson the user has been in
opigno_quiz_app_course_lessons
opigno_quiz_app_course_lessons_progress_and_time
opigno_quiz_app_course_resume Page callback for resuming a course.
opigno_quiz_app_course_resume_access Access callback for the redirection to resume a quiz
opigno_quiz_app_course_resume_get_path
opigno_quiz_app_delete_course_quiz_weight Helper function to delete the weight of a quiz inside a course.
opigno_quiz_app_form_alter Quiz node form alter
opigno_quiz_app_get_all_required_quizzes Helper function to fetch all the required quizzes for the passed course node.
opigno_quiz_app_get_certificate Helper function to get the certificate download link from the Opigno Certificate App.
opigno_quiz_app_get_class_progression Helper function to get the class progression in percent
opigno_quiz_app_get_course_class_progression Helper function to get the course or class progression in percent
opigno_quiz_app_get_course_class_score_average Helper function to get the average score for a course or a class.
opigno_quiz_app_get_course_data_result Helper function to get all results for a given course and user.
opigno_quiz_app_get_course_progression Helper function to get the course progression in percent
opigno_quiz_app_get_course_quizzes Helper function to fetch all course quizzes.
opigno_quiz_app_get_course_quiz_weight Helper function to fetch the weight of a quiz inside a course.
opigno_quiz_app_get_first_lesson_from_group Helper function that returns the first lesson's ID of a group (course or class)
opigno_quiz_app_get_next_lesson_from_course Returns the next lesson NID for a course.
opigno_quiz_app_get_quizzes_view_title Helper function to get the Opigno Quizzes View title.
opigno_quiz_app_get_quiz_question_types Helper function to get the available quiz question types.
opigno_quiz_app_get_score_data Helper function to fetch score data for quizzes. The difference with the core quiz function is that this function gets all results (not just the best one) and also returns the start and end time.
opigno_quiz_app_init Implements hook_init().
opigno_quiz_app_is_in_entity_reference Compares entity references fields
opigno_quiz_app_is_node_add_question Helper function to determine if we're adding a Question node.
opigno_quiz_app_is_quiz_path Helper function to check if we are in a quiz path.
opigno_quiz_app_menu Implements hook_menu().
opigno_quiz_app_menu_alter Implements hook_menu_alter().
opigno_quiz_app_menu_local_tasks_alter Implements hook_menu_local_tasks_alter().
opigno_quiz_app_modules_enabled Implements hook_modules_enabled().
opigno_quiz_app_node_insert Implements hook_node_insert().
opigno_quiz_app_node_update Implements hook_node_update().
opigno_quiz_app_og_context_handler OG Context provider.
opigno_quiz_app_og_context_negotiation_info Implements hook_og_context_negotiation_info
opigno_quiz_app_og_permission Implements hook_og_permission().
opigno_quiz_app_opigno_breadcrumbs Implements hook_opigno_breadcrumbs().
opigno_quiz_app_opigno_tool Implements hook_opigno_tool().
opigno_quiz_app_permission Implements hook_permission().
opigno_quiz_app_preprocess_page Implements hook_preprocess_page().
opigno_quiz_app_quiz_finished Implements hook_quiz_finished().
opigno_quiz_app_quiz_scored Implements hook_quiz_scored().
opigno_quiz_app_set_course_quiz_weight Helper function to insert the weight of a quiz inside a course.
opigno_quiz_app_take_access Access callback: check if user has access to take quizzes inside the course.
opigno_quiz_app_theme Implements hook_theme().
opigno_quiz_app_theme_registry_alter
opigno_quiz_app_unset_opigno_calendar_date Quiz node submit function
opigno_quiz_app_user_delete Implements hook_user_delete().
opigno_quiz_app_user_finished_quiz_result Helper function to check if user has finished a quiz.
opigno_quiz_app_user_passed Helper function to check if the user passed all required quizzes inside the course.
opigno_quiz_app_views_api Implements hook_views_api().
opigno_quiz_app_views_data Implements hook_views_data().
opigno_quiz_app_views_query_alter
opigno_quiz_get_continue_course
opigno_quiz_get_continue_group
opigno_quiz_get_score_data DEPRECATED. A typo in the function name omitted the 'app' word. This is now an alias to opigno_quiz_app_get_score_data(), which should be used instead.
theme_opigno_quiz_app_course_lessons Theme callback: display course quizes and results.
theme_opigno_quiz_app_course_results Theme callback: display course results.
theme_opigno_quiz_app_course_user_results Theme callback: display course results for a specific user.
theme_opigno_quiz_app_sort_course_quizzes_form Theme callback: render the order form.
theme_opigno_quiz_app_user_course_results Theme callback: display user results for a specific course.
theme_opigno_quiz_app_user_results Theme callback: display user results.
theme_opigno_quiz_no_feedback
theme_opigno_quiz_take_summary
_opigno_quiz_app_enable_fullscreen Enable fullscreen mode for the passed Quiz node.

Constants

Namesort descending Description
OPIGNO_QUIZ_APP_FAILED
OPIGNO_QUIZ_APP_PASSED @file Module file. Defines module hooks.
OPIGNO_QUIZ_APP_PENDING