You are here

public function OpignoModuleController::takeModule in Opigno module 3.x

Same name and namespace in other branches
  1. 8 src/Controller/OpignoModuleController.php \Drupal\opigno_module\Controller\OpignoModuleController::takeModule()

Method for Take module tab route.

1 string reference to 'OpignoModuleController::takeModule'
opigno_module.routing.yml in ./opigno_module.routing.yml
opigno_module.routing.yml

File

src/Controller/OpignoModuleController.php, line 153

Class

OpignoModuleController
Class OpignoModuleController.

Namespace

Drupal\opigno_module\Controller

Code

public function takeModule(Request $request, Group $group, OpignoModuleInterface $opigno_module) {

  /* @var $opigno_module \Drupal\opigno_module\Entity\OpignoModule */

  /* @var $query_options \Symfony\Component\HttpFoundation\ParameterBag */
  $query_options = $request->query;

  // Set current ID of group
  OpignoGroupContext::setGroupId($group
    ->id());

  // Check Module availability.
  $availability = $opigno_module
    ->checkModuleAvailability();
  if (!$availability['open']) {

    // Module is not available. Based on availability time.
    \Drupal::messenger()
      ->addWarning($availability['message']);
    return $this
      ->redirect('entity.group.canonical', [
      'group' => $group
        ->id(),
    ]);
  }

  // Check Module attempts.
  if ($this
    ->currentUser()
    ->id() != 1 && !in_array('administrator', $this
    ->currentUser()
    ->getAccount()
    ->getRoles())) {
    $allowed_attempts = $opigno_module
      ->get('takes')->value;
    if ($allowed_attempts > 0) {

      // It means, that module attempts are limited.
      // Need to check User attempts.
      $user_attempts = $opigno_module
        ->getModuleAttempts($this
        ->currentUser());
      if (count($user_attempts) >= $allowed_attempts) {

        // User has more attempts then allowed.
        // Check for not finished attempt.
        $active_attempt = $opigno_module
          ->getModuleActiveAttempt($this
          ->currentUser());
        if ($active_attempt == NULL) {

          // There is no not finished attempt.
          \Drupal::messenger()
            ->addWarning($this
            ->t('Maximum attempts for this module reached.'));
          return $this
            ->redirect('entity.group.canonical', [
            'group' => $group
              ->id(),
          ]);
        }
      }
    }
  }

  // Check if module in skills system.
  $skills_active = $opigno_module
    ->getSkillsActive();

  // Get activities from the Module.
  $activities = $opigno_module
    ->getModuleActivities();
  $target_skill = $opigno_module
    ->getTargetSkill();
  $term_storage = \Drupal::entityTypeManager()
    ->getStorage('taxonomy_term');
  $moduleHandler = \Drupal::service('module_handler');
  if ($moduleHandler
    ->moduleExists('opigno_skills_system') && $skills_active) {
    $skills_tree = array_reverse($term_storage
      ->loadTree('skills', $target_skill));
    $db_connection = \Drupal::service('database');

    // Get current user's skills.
    $uid = $this
      ->currentUser()
      ->id();
    $query = $db_connection
      ->select('opigno_skills_statistic', 'o_s_s');
    $query
      ->fields('o_s_s', [
      'tid',
      'score',
      'progress',
      'stage',
    ]);
    $query
      ->condition('o_s_s.uid', $uid);
    $user_skills = $query
      ->execute()
      ->fetchAllAssoc('tid');

    // Find skills which needs to level-up.
    $depth_of_skills_tree = $skills_tree[0]->depth;
    $current_skills = [];
    if (!isset($depth_of_skills_tree)) {
      $depth_of_skills_tree = 0;
    }

    // Set default successfully finished this skills tree for user.
    // If the system finds any skill which is not successfully finished then change this variable to FALSE.
    $successfully_finished = TRUE;
    while ($depth_of_skills_tree >= 0) {
      foreach ($skills_tree as $key => $skill) {
        $tid = $skill->tid;
        $skill_entity = $term_storage
          ->load($tid);
        $minimum_score = $skill_entity
          ->get('field_minimum_score')
          ->getValue()[0]['value'];
        $children = $term_storage
          ->loadChildren($tid);
        $skill_access = TRUE;

        // Check if the skill was successfully finished.
        if (!isset($user_skills[$skill->tid])) {
          $successfully_finished = FALSE;
        }
        else {
          if ($minimum_score > $user_skills[$skill->tid]->score || $user_skills[$skill->tid]->progress < 100) {
            $successfully_finished = FALSE;
          }
        }
        if (!empty($children)) {
          $children_ids = array_keys($children);
          foreach ($children_ids as $child_id) {
            if (!isset($user_skills[$child_id])) {
              $skill_access = FALSE;
              break;
            }
            if ($user_skills[$child_id]->progress < 100 || $user_skills[$child_id]->score < $minimum_score) {
              $skill_access = FALSE;
            }
          }
        }
        if ($skill->depth == $depth_of_skills_tree && $skill_access == TRUE && !$successfully_finished) {
          $current_skills[] = $skill->tid;
        }
      }
      $depth_of_skills_tree--;
    }

    // Check if Opigno system could load all suitable activities not included in the current module.
    $use_all_activities = $opigno_module
      ->getModuleSkillsGlobal();
    if ($use_all_activities && !empty($current_skills)) {
      $activities += $opigno_module
        ->getSuitableActivities($current_skills);
    }
  }

  // Create new attempt or resume existing one.
  $attempt = $opigno_module
    ->getModuleActiveAttempt($this
    ->currentUser());
  if ($attempt == NULL) {

    // No existing attempt, create new one.
    $attempt = UserModuleStatus::create([]);
    $attempt
      ->setModule($opigno_module);
    $attempt
      ->setFinished(0);
    $attempt
      ->save();
  }

  // Get training unfinished attempt.
  $attempt_lp = $opigno_module
    ->getTrainingActiveAttempt($this
    ->currentUser(), $group);
  if ($attempt_lp == NULL) {

    // No unfinished attempt. Create training new attempt
    // only if current step is mandatory.
    $steps = LearningPathContent::getAllStepsOnlyModules($group
      ->id());
    $step_module_info = array_filter($steps, function ($step) use ($opigno_module) {
      return $step['id'] == $opigno_module
        ->id();
    });
    if (!empty($step_module_info)) {
      $step_module_info = array_shift($step_module_info);

      // Get started time for training attempt.
      $started = $attempt
        ->get('started')
        ->getValue();
      $started = !empty($started[0]['value']) ? $started[0]['value'] : time();

      // Create training new attempt.
      $attempt_lp = LPStatus::create([
        'uid' => $this
          ->currentUser()
          ->id(),
        'gid' => $group
          ->id(),
        'status' => 'in progress',
        'started' => $started,
        'finished' => 0,
      ]);
      $attempt_lp
        ->save();
    }
  }

  // Redirect to the module results page with message 'successfully finished'.
  if (isset($successfully_finished) && $successfully_finished) {
    if ($attempt
      ->isFinished()) {
      $this
        ->messenger()
        ->addWarning($this
        ->t('You already successfully finished this skill\'s tree.'));
    }
    else {
      $sum_score = 0;
      foreach ($user_skills as $skill) {
        $sum_score += $skill->score;
      }
      $avg_score = $sum_score / count($user_skills);
      $attempt
        ->setFinished(\Drupal::time()
        ->getRequestTime());
      $attempt
        ->setScore($avg_score);
      $attempt
        ->setEvaluated(1);
      $attempt
        ->setMaxScore($avg_score);
      $attempt
        ->save();
    }
    $_SESSION['successfully_finished'] = TRUE;
    return $this
      ->redirect('opigno_module.module_result', [
      'opigno_module' => $opigno_module
        ->id(),
      'user_module_status' => $attempt
        ->id(),
    ]);
  }

  // Set context variable for the type of activity answer link.
  // It's necessary for detecting manual/direct activity answer link.
  // Here we prepare Opigno flow activity link type.
  OpignoGroupContext::setActivityLinkType('flow');
  if (count($activities) > 0) {

    // Get activity that will be answered.
    $next_activity_id = NULL;
    $last_activity_id = $attempt
      ->getLastActivityId();
    $get_next = FALSE;

    // Check if Skills system is activated for this module.
    if ($moduleHandler
      ->moduleExists('opigno_skills_system') && $skills_active) {

      // Load user's answers for current skills.
      $activities_ids = array_keys($activities);
      $query = $db_connection
        ->select('opigno_answer_field_data', 'o_a_f_d');
      $query
        ->leftJoin('opigno_module_relationship', 'o_m_r', 'o_a_f_d.activity = o_m_r.child_id');
      $query
        ->addExpression('MAX(o_a_f_d.score)', 'score');
      $query
        ->addExpression('MAX(o_a_f_d.changed)', 'changed');
      $query
        ->addExpression('MAX(o_a_f_d.skills_mode)', 'skills_mode');
      $query
        ->addField('o_a_f_d', 'activity');
      $query
        ->condition('o_a_f_d.user_id', $uid)
        ->condition('o_a_f_d.activity', $activities_ids, 'IN')
        ->condition('o_a_f_d.skills_mode', NULL, 'IS NOT NULL');
      $answers = $query
        ->groupBy('o_a_f_d.activity')
        ->orderBy('score')
        ->orderBy('changed')
        ->execute()
        ->fetchAllAssoc('activity');
      $answers_ids = array_keys($answers);
      $available_activities = count($activities_ids) > count($answers_ids) ? TRUE : FALSE;
      if (!is_null($last_activity_id)) {
        $last_skill_activity = \Drupal::entityTypeManager()
          ->getStorage('opigno_activity')
          ->load($last_activity_id);
        $last_skill_id = $last_skill_activity
          ->getSkillId();
      }
      foreach ($activities as $activity) {

        // Get initial level. This is equal to the count of levels.
        $initial_level = $term_storage
          ->load($activity->skills_list);
        if ($initial_level) {
          $initial_level = count($initial_level
            ->get('field_level_names')
            ->getValue());
        }

        // Check if the skill level of activity matches the current skill level of the user.
        if (isset($user_skills[$activity->skills_list]) && $user_skills[$activity->skills_list]->stage != $activity->skill_level || !isset($user_skills[$activity->skills_list]) && $activity->skill_level != $initial_level) {
          continue;
        }
        if (in_array($activity->skills_list, $current_skills) && !in_array($activity->id, $answers_ids)) {
          $next_activity_id = $activity->id;

          // If we don't have ID of last activity then set next activity by default.
          if (!isset($last_skill_id)) {
            break;
          }
          elseif ($last_skill_id == $activity->skills_list) {
            break;
          }
        }
      }

      // If user already answered at all questions of available skills.
      if ($next_activity_id == NULL) {
        foreach ($answers as $answer) {
          if (in_array($answer->skills_mode, $current_skills) && $answer->score != $activities[$answer->activity]->max_score) {
            $next_activity_id = $answer->activity;

            // If we don't have ID of last activity then set next activity by default.
            if (!isset($last_skill_id)) {
              break;
            }
            elseif ($last_skill_id == $answer->skills_mode) {
              break;
            }
          }
        }
      }

      // Redirect user to the next activity(answer).
      if (isset($next_activity_id)) {
        return $this
          ->redirect('opigno_module.group.answer_form', [
          'group' => $group
            ->id(),
          'opigno_activity' => $next_activity_id,
          'opigno_module' => $opigno_module
            ->id(),
        ]);
      }
      else {
        $_SESSION['successfully_finished'] = FALSE;
      }
    }

    // Get additional module settings.
    $backwards_param = $query_options
      ->get('backwards');

    // Take into account randomization options.
    $randomization = $opigno_module
      ->getRandomization();
    if ($randomization > 0) {

      // Get random activity and put it in a sequence.
      $random_activity = $opigno_module
        ->getRandomActivity($attempt);
      if ($random_activity) {
        $next_activity_id = $random_activity
          ->id();
      }
    }
    else {
      foreach ($activities as $activity_id => $activity) {

        // Check for backwards navigation submit.
        if ($opigno_module
          ->getBackwardsNavigation() && isset($prev_activity_id) && $last_activity_id == $activity_id && $backwards_param) {
          $next_activity_id = $prev_activity_id;
          break;
        }
        if (is_null($last_activity_id) || $get_next) {

          // Get the first activity.
          $next_activity_id = $activity_id;
          break;
        }
        if ($last_activity_id == $activity_id) {

          // Get the next activity after this one.
          $get_next = TRUE;
        }
        $prev_activity_id = $activity_id;
      }

      // Check if user navigate to previous module with "back" button.
      $from_first_activity = key($activities) == $last_activity_id || key($activities) == $attempt
        ->getCurrentActivityId() || $last_activity_id == NULL;
      if ($opigno_module
        ->getBackwardsNavigation() && $from_first_activity && $backwards_param) {
        return $this
          ->redirect('opigno_module.get_previous_module', [
          'opigno_module' => $opigno_module
            ->id(),
        ]);
      }
    }

    // Get group context.
    $cid = OpignoGroupContext::getCurrentGroupContentId();
    $gid = OpignoGroupContext::getCurrentGroupId();
    if ($gid) {
      $steps = LearningPathContent::getAllStepsOnlyModules($gid);
      foreach ($steps as $step) {
        if ($step['id'] == $opigno_module
          ->id() && $step['cid'] != $cid) {

          // Update content cid.
          OpignoGroupContext::setCurrentContentId($step['cid']);
        }
      }
    }
    $activities_storage = static::entityTypeManager()
      ->getStorage('opigno_activity');
    if (!is_null($next_activity_id)) {

      // Means that we have some activity to answer.
      $attempt
        ->setCurrentActivity($activities_storage
        ->load($next_activity_id));
      $attempt
        ->save();
      return $this
        ->redirect('opigno_module.group.answer_form', [
        'group' => $group
          ->id(),
        'opigno_activity' => $next_activity_id,
        'opigno_module' => $opigno_module
          ->id(),
      ]);
    }
    else {

      // If a user clicks "Back" button,
      // show the last question instead of summary page.
      $previous = $query_options
        ->get('previous');
      if ($previous) {
        return $this
          ->redirect('opigno_module.group.answer_form', [
          'group' => $group
            ->id(),
          'opigno_activity' => $last_activity_id,
          'opigno_module' => $opigno_module
            ->id(),
        ]);
      }
      else {
        $attempt
          ->finishAttempt();
        return $this
          ->redirect('opigno_module.module_result', [
          'opigno_module' => $opigno_module
            ->id(),
          'user_module_status' => $attempt
            ->id(),
        ]);
      }
    }
  }
  if ($target_skill) {
    $target_skill_entity = $term_storage
      ->load($target_skill);
    $warning = $this
      ->t('There are not enough activities in the Opigno System to complete "@target_skill" skills tree!', [
      '@target_skill' => $target_skill_entity
        ->getName(),
    ]);
    return [
      '#markup' => "<div class='lp_step_explanation'>{$warning}</div>",
      '#attached' => [
        'library' => [
          'opigno_learning_path/creation_steps',
        ],
      ],
    ];
  }
  \Drupal::messenger()
    ->addWarning($this
    ->t('Module @module_label does not contain any activity.', [
    '@module_label' => $opigno_module
      ->label(),
  ]));
  return $this
    ->redirect('entity.group.canonical', [
    'group' => $group
      ->id(),
  ]);
}