You are here

opigno_scorm.module in Opigno SCORM 8

Same filename and directory in other branches
  1. 3.x opigno_scorm.module

Module functionality implementation.

File

opigno_scorm.module
View source
<?php

/**
 * @file
 * Module functionality implementation.
 */
use Drupal\Core\Form\FormStateInterface;
use Drupal\opigno_module\Entity\OpignoAnswer;

/**
 * Implements hook_field_widget_info_alter().
 */
function opigno_scorm_field_widget_info_alter(array &$info) {
  $info['file_generic']['field_types'][] = 'opigno_scorm_package';
}

/**
 * Implements hook_theme().
 */
function opigno_scorm_theme() {
  return [
    'opigno_scorm__player' => [
      'variables' => [
        'scorm_id' => NULL,
        'tree' => [],
        'start_sco' => NULL,
      ],
      'template' => 'opigno-scorm--player',
    ],
    'opigno_scorm__player_tree' => [
      'variables' => [
        'tree' => [],
      ],
      'template' => 'opigno-scorm--player-tree',
    ],
    'opigno_scorm__player_tree_item' => [
      'variables' => [
        'sco' => NULL,
      ],
      'template' => 'opigno-scorm--player-tree-item',
    ],
  ];
}

/**
 * Get the available CMI paths for the SCORM player.
 *
 * Invokes the hook_opigno_scorm_register_cmi_paths()
 * on all implementing modules to retrieve data
 * to pass to the SCORM player.
 */
function opigno_scorm_add_cmi_paths($scorm_version) {
  $paths = \Drupal::moduleHandler()
    ->invokeAll('opigno_scorm_register_cmi_paths', [
    $scorm_version,
  ]);
  \Drupal::moduleHandler()
    ->alter('opigno_scorm_register_cmi_paths', $paths);
  return $paths;
}

/**
 * Get the CMI data for the SCORM player.
 *
 * Invokes the hook_opigno_scorm_ui_register_cmi_data()
 * on all implementing modules to retrieve data
 * to pass to the SCORM player.
 *
 * @param object $scorm
 *   Scorm object.
 * @param array $scos
 *   Scos array.
 *
 * @return array
 *   Cmi data.
 */
function opigno_scorm_add_cmi_data($scorm, array $scos, $scorm_version) {
  $data = \Drupal::moduleHandler()
    ->invokeAll('opigno_scorm_register_cmi_data', [
    $scorm,
    $scos,
    $scorm_version,
  ]);
  \Drupal::moduleHandler()
    ->alter('opigno_scorm_register_cmi_data', $data, $scorm, $scos);
  return $data;
}

/**
 * Implements hook_opigno_scorm_register_cmi_paths().
 */
function opigno_scorm_opigno_scorm_register_cmi_paths($scorm_version) {
  switch ($scorm_version) {
    case '2004':
      $data = [
        'cmi.location' => [],
        'cmi.completion_status' => [],
        'cmi.exit' => [],
        'cmi.entry' => [],
        'cmi.learner_id' => [],
        'cmi.learner_name' => [],
        'cmi.learner_preference._children' => [],
        'cmi.learner_preference.audio_level' => [],
        'cmi.learner_preference.language' => [],
        'cmi.learner_preference.delivery_speed' => [],
        'cmi.learner_preference.audio_captioning' => [],
      ];
      break;
    case '1.2':
      $data = [
        'cmi.core.lesson_location' => [],
        'cmi.core.lesson_status' => [],
        'cmi.core.exit' => [],
        'cmi.core.entry' => [],
        'cmi.core.student_name' => [],
        'cmi.core.student_id' => [],
        'cmi.student_preference._children' => [],
        'cmi.student_preference.audio' => [],
        'cmi.student_preference.language' => [],
        'cmi.student_preference.speed' => [],
        'cmi.student_preference.text' => [],
        'cmi.core.score._children' => [],
      ];
      break;
  }
  return $data;
}

/**
 * Implements hook_opigno_scorm_register_cmi_data().
 */
function opigno_scorm_opigno_scorm_register_cmi_data($scorm, $scos, $scorm_version) {
  $account = \Drupal::currentUser();
  if (!$account
    ->id()) {
    $learner_name = "anonymous";
  }
  else {
    $learner_name = $account
      ->getAccountName();
  }
  switch ($scorm_version) {
    case '2004':
      $data = [
        'cmi.location' => opigno_scorm_scorm_cmi_get($account
          ->id(), $scorm->id, 'cmi.location', ''),
        'cmi.completion_status' => opigno_scorm_scorm_cmi_get($account
          ->id(), $scorm->id, 'cmi.completion_status', 'unknown'),
        'cmi.exit' => opigno_scorm_scorm_cmi_get($account
          ->id(), $scorm->id, 'cmi.exit', ''),
        'cmi.entry' => opigno_scorm_scorm_cmi_get($account
          ->id(), $scorm->id, 'cmi.entry', ''),
        'cmi.learner_id' => $account
          ->id(),
        'cmi.learner_name' => $learner_name,
        'cmi.learner_preference._children' => "audio_level,language,delivery_speed,audio_captioning",
        'cmi.learner_preference.audio_level' => 1,
        'cmi.learner_preference.language' => '',
        'cmi.learner_preference.delivery_speed' => 1,
        'cmi.learner_preference.audio_captioning' => 0,
      ];
      break;
    case '1.2':
      $data = [
        'cmi.core.lesson_location' => opigno_scorm_scorm_cmi_get($account
          ->id(), $scorm->id, 'cmi.core.lesson_location', ''),
        'cmi.core.exit' => opigno_scorm_scorm_cmi_get($account
          ->id(), $scorm->id, 'cmi.core.exit', ''),
        'cmi.core.student_id' => $account
          ->id(),
        'cmi.core.entry' => opigno_scorm_scorm_cmi_get($account
          ->id(), $scorm->id, 'cmi.core.entry', ''),
        'cmi.core.student_name' => $learner_name,
        'cmi.student_preference._children' => "audio,language,speed,text",
        'cmi.student_preference.audio' => [],
        'cmi.student_preference.language' => '',
        'cmi.student_preference.speed' => [],
        'cmi.student_preference.text' => [],
        'cmi.core.score._children' => opigno_scorm_scorm_cmi_get($account
          ->id(), $scorm->id, 'cmi.core.score._children', 'raw,min,max'),
      ];
      break;
  }
  return $data;
}

/**
 * Helper function to get SCORM CMI data while also providing a default value.
 *
 * @param int $uid
 *   User ID.
 * @param int $scorm_id
 *   Scorn ID.
 * @param string $cmi_key
 *   Cmi key.
 * @param mixed $default_value
 *   Default.
 *
 * @return mixed|null
 *   Scorm cmi.
 */
function opigno_scorm_scorm_cmi_get($uid, $scorm_id, $cmi_key, $default_value = NULL) {
  $value = opigno_scorm_cmi_get($uid, $scorm_id, $cmi_key);
  return isset($value) ? $value : $default_value;
}

/**
 * Get a CMI data value for the given SCORM.
 *
 * @param int $uid
 *   User ID.
 * @param int $scorm_id
 *   Scorn ID.
 * @param string $cmi_key
 *   Cmi key.
 *
 * @return mixed|null
 *   Scorm cmi.
 */
function opigno_scorm_cmi_get($uid, $scorm_id, $cmi_key) {
  $data = NULL;
  $result = \Drupal::database()
    ->select('opigno_scorm_scorm_cmi_data', 'o')
    ->fields('o', [
    'value',
    'serialized',
  ])
    ->condition('o.uid', $uid)
    ->condition('o.scorm_id', $scorm_id)
    ->condition('o.cmi_key', $cmi_key)
    ->execute()
    ->fetchObject();
  if (isset($result->value)) {
    $data = !empty($result->serialized) ? unserialize($result->value) : $result->value;
  }
  return $data;
}

/**
 * Set a CMI data value for the given SCORM.
 *
 * @param int $uid
 *   User ID.
 * @param int $scorm_id
 *   Scorn ID.
 * @param string $cmi_key
 *   Cmi key.
 * @param string $value
 *   Value.
 *
 * @return bool
 *   Scorm cmi set flag.
 *
 * @throws \Exception
 */
function opigno_scorm_scorm_cmi_set($uid, $scorm_id, $cmi_key, $value) {
  if (isset($value)) {
    $serialized = 0;
    if (is_array($value) || is_object($value)) {
      $value = serialize($value);
      $serialized = 1;
    }
    elseif (is_bool($value)) {
      $value = (int) $value;
    }
    $result = \Drupal::database()
      ->merge('opigno_scorm_scorm_cmi_data')
      ->keys([
      'uid' => $uid,
      'scorm_id' => $scorm_id,
      'cmi_key' => $cmi_key,
    ])
      ->fields([
      'uid' => $uid,
      'scorm_id' => $scorm_id,
      'cmi_key' => $cmi_key,
      'value' => $value,
      'serialized' => $serialized,
    ])
      ->execute();
    return !!$result;
  }
  else {
    return TRUE;
  }
}

/**
 * Implements hook_opigno_scorm_commit().
 */
function opigno_scorm_opigno_scorm_commit($scorm, $sco_id, $data) {
  $account = Drupal::currentUser();
  if (!empty($data->scorm_version)) {
    switch ($data->scorm_version) {
      case '2004':

        // Store the last visited SCO id.
        opigno_scorm_scorm_cmi_set($account
          ->id(), $scorm->id, 'user.sco', $sco_id);

        // Store the last position.
        if (!empty($data->cmi->location)) {
          opigno_scorm_scorm_cmi_set($account
            ->id(), $scorm->id, 'cmi.location', $data->cmi->location);
        }

        // Store suspend data value.
        if (!empty($data->cmi->suspend_data)) {
          if (!is_null($sco_id)) {
            opigno_scorm_scorm_cmi_set($account
              ->id(), $scorm->id, 'cmi.suspend_data.' . $sco_id, $data->cmi->suspend_data);
          }
          opigno_scorm_scorm_cmi_set($account
            ->id(), $scorm->id, 'cmi.suspend_data', $data->cmi->suspend_data);
        }

        // Store the completion status.
        if (!empty($data->cmi->completion_status)) {
          opigno_scorm_scorm_cmi_set($account
            ->id(), $scorm->id, 'cmi.completion_status', $data->cmi->completion_status);
        }
        break;
      case '1.2':

        // Store the last position.
        if (!empty($data->cmi->core->lesson_location)) {
          opigno_scorm_scorm_cmi_set($account
            ->id(), $scorm->id, 'cmi.core.lesson_location', $data->cmi->core->lesson_location);
        }

        // Store suspend data value.
        if (!empty($data->cmi->suspend_data)) {
          opigno_scorm_scorm_cmi_set($account
            ->id(), $scorm->id, 'cmi.suspend_data', $data->cmi->suspend_data);
        }

        // Store the completion status.
        if (!empty($data->cmi->core->lesson_status)) {
          opigno_scorm_scorm_cmi_set($account
            ->id(), $scorm->id, 'cmi.core.lesson_status', $data->cmi->core->lesson_status);
        }
        break;
    }
  }
}

/**
 * Implements hook_form_alter().
 */
function opigno_scorm_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  if ($form_id === 'opigno_answer_opigno_scorm_form') {
    $form['actions']['submit']['#submit'][] = '_opigno_scorm_form_submit';
  }
}

/**
 * Form submit callback, used in opigno_h5p_form_alter().
 *
 * Saves user answer & correct answer patterns.
 */
function _opigno_scorm_form_submit($form, FormStateInterface $form_state) {
  $storage = $form_state
    ->getStorage();
  if (!empty($storage['scorm_answer']) && !empty($_SESSION['scorm_answer_results'])) {
    if (!empty($_SESSION['scorm_answer_results']['data']->cmi->interactions)) {
      foreach ($_SESSION['scorm_answer_results']['data']->cmi->interactions as $interaction) {
        _opigno_scorm_save_interaction($interaction, $storage['scorm_answer']);
      }
    }
    unset($_SESSION['scorm_answer_results']);
  }
}

/**
 * Recursive save of xAPI data.
 *
 * @param mixed $interaction
 *   Interaction object array.
 * @param \Drupal\opigno_module\Entity\OpignoAnswer $answer
 *   OpignoAnswer object.
 */
function _opigno_scorm_save_interaction($interaction, OpignoAnswer $answer) {
  $db_connection = \Drupal::service('database');
  $activity = $answer
    ->getActivity();
  $response = '';
  if (isset($interaction->learner_response)) {
    $response = $interaction->learner_response;
  }
  elseif (isset($interaction->student_response)) {
    $response = $interaction->student_response;
  }
  if ($response != '') {
    $response = _opigno_scorm_format_string_length($response, 255);
  }
  if (!empty($interaction->correct_responses) && key_exists(0, $interaction->correct_responses)) {
    try {
      $correct_responses_pattern = $interaction->correct_responses[0]->pattern;
    } catch (\Exception $e) {
      \Drupal::logger('opigno_scorm')
        ->error($e
        ->getMessage());
      \Drupal::messenger()
        ->addMessage($e
        ->getMessage(), 'error');
    }
  }
  try {

    // Save SCORM interaction data.
    $db_connection
      ->insert('opigno_scorm_user_answer_results')
      ->fields([
      'interaction_id' => _opigno_scorm_format_string_length($interaction->id, 191),
      'question_id' => $activity
        ->id(),
      'question_vid' => $activity
        ->getLoadedRevisionId(),
      'answer_id' => $answer
        ->id(),
      'answer_vid' => $answer
        ->getLoadedRevisionId(),
      'interaction_type' => _opigno_scorm_format_string_length($interaction->type, 32),
      'description' => _opigno_scorm_format_string_length($interaction->description, 255),
      'correct_responses_pattern' => isset($correct_responses_pattern) ? $correct_responses_pattern : '',
      'response' => $response,
      'result' => _opigno_scorm_format_string_length($interaction->result, 16),
      'timestamp' => !empty($interaction->timestamp) ? strtotime($interaction->timestamp) : time(),
    ])
      ->execute();
  } catch (Exception $e) {
    \Drupal::logger('opigno_scorm')
      ->error($e
      ->getMessage());
  }
}

/**
 * Truncates string by wanted length if necessary.
 *
 * @param string $string
 *   String to format.
 * @param int $length
 *   Necessary string length.
 *
 * @return string
 *   Formatted string.
 */
function _opigno_scorm_format_string_length($string, $length) {
  if (strlen($string) > $length) {
    return substr($string, 0, $length);
  }
  return $string;
}

/**
 * Implements hook_entity_bundle_field_info_alter().
 */
function opigno_scorm_entity_bundle_field_info_alter(&$fields, \Drupal\Core\Entity\EntityTypeInterface $entity_type, $bundle) {
  if ($bundle === 'opigno_scorm') {
    if (isset($fields['opigno_scorm_package'])) {

      // Use the ID as defined in the annotation of the constraint definition
      $fields['opigno_scorm_package']
        ->addConstraint('ScormPackage', []);
    }
  }
}

/**
 * Implements hook_page_attachments().
 */
function opigno_scorm_page_attachments(array &$attachments) {
  $attachments['#attached']['library'][] = 'opigno_scorm/opigno-scorm-ios-13';
}

Functions

Namesort descending Description
opigno_scorm_add_cmi_data Get the CMI data for the SCORM player.
opigno_scorm_add_cmi_paths Get the available CMI paths for the SCORM player.
opigno_scorm_cmi_get Get a CMI data value for the given SCORM.
opigno_scorm_entity_bundle_field_info_alter Implements hook_entity_bundle_field_info_alter().
opigno_scorm_field_widget_info_alter Implements hook_field_widget_info_alter().
opigno_scorm_form_alter Implements hook_form_alter().
opigno_scorm_opigno_scorm_commit Implements hook_opigno_scorm_commit().
opigno_scorm_opigno_scorm_register_cmi_data Implements hook_opigno_scorm_register_cmi_data().
opigno_scorm_opigno_scorm_register_cmi_paths Implements hook_opigno_scorm_register_cmi_paths().
opigno_scorm_page_attachments Implements hook_page_attachments().
opigno_scorm_scorm_cmi_get Helper function to get SCORM CMI data while also providing a default value.
opigno_scorm_scorm_cmi_set Set a CMI data value for the given SCORM.
opigno_scorm_theme Implements hook_theme().
_opigno_scorm_format_string_length Truncates string by wanted length if necessary.
_opigno_scorm_form_submit Form submit callback, used in opigno_h5p_form_alter().
_opigno_scorm_save_interaction Recursive save of xAPI data.