View source
<?php
namespace Drupal\opigno_learning_path\Controller;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\OpenModalDialogCommand;
use Drupal\Core\Ajax\RedirectCommand;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Database\Connection;
use Drupal\Core\Url;
use Drupal\group\Entity\Group;
use Drupal\opigno_group_manager\Controller\OpignoGroupManagerController;
use Drupal\opigno_group_manager\Entity\OpignoGroupManagedContent;
use Drupal\opigno_group_manager\OpignoGroupContentTypesManager;
use Drupal\opigno_group_manager\OpignoGroupContext;
use Drupal\opigno_learning_path\Entity\LPResult;
use Drupal\opigno_learning_path\LearningPathAccess;
use Drupal\opigno_learning_path\LearningPathValidator;
use Drupal\opigno_learning_path\LearningPathContent;
use Drupal\opigno_module\Entity\OpignoActivity;
use Drupal\opigno_module\Entity\OpignoModule;
use Symfony\Component\DependencyInjection\ContainerInterface;
class LearningPathStepsController extends ControllerBase {
protected $content_type_manager;
protected $database;
protected $source_type = 'group';
protected $group;
public function __construct(OpignoGroupContentTypesManager $content_types_manager, Connection $database) {
$this->content_type_manager = $content_types_manager;
$this->database = $database;
}
public static function create(ContainerInterface $container) {
return new static($container
->get('opigno_group_manager.content_types.manager'), $container
->get('database'));
}
protected function redirectToHome($message = '', $is_ajax = FALSE) {
if (!empty($this->group) && !empty($this->source_type) && $this->source_type == 'catalog') {
if ($is_ajax) {
$url = Url::fromRoute('entity.group.canonical', [
'group' => $this->group
->id(),
'force' => 1,
]);
return (new AjaxResponse())
->addCommand(new RedirectCommand($url
->toString()));
}
else {
return $this
->redirect('entity.group.canonical', [
'group' => $this->group
->id(),
'force' => 1,
]);
}
}
return FALSE;
}
protected function failedStep($type, $modal = FALSE, $message = '') {
switch ($type) {
case 'no_first':
$message = $this
->t('No first step assigned.');
break;
case 'no_url':
$message = $this
->t('No URL for the first step.');
break;
case 'no_score':
$message = $this
->t('No score provided');
break;
case 'passed':
$message = $this
->t('You passed!');
break;
case 'failed':
$message = $this
->t('You failed!');
break;
}
if ($type !== 'none' && ($redirect = $this
->redirectToHome($message, $modal))) {
return $redirect;
}
$content = [
'#type' => 'html_tag',
'#value' => $message,
'#tag' => 'p',
];
if ($modal) {
return (new AjaxResponse())
->addCommand(new OpenModalDialogCommand('', $content));
}
else {
return $content;
}
}
public function start(Group $group, $type = 'group') {
$current_attempt = opigno_learning_path_started($group, \Drupal::currentUser());
$this->source_type = $type;
$this->group = $group;
if (!$current_attempt) {
$result = LPResult::createWithValues($group
->id(), \Drupal::currentUser()
->id(), FALSE, 0);
$result
->save();
}
$user = $this
->currentUser();
$uid = $user
->id();
$gid = $group
->id();
$is_owner = $uid === $group
->getOwnerId();
$is_ajax = \Drupal::request()
->isXmlHttpRequest();
$steps = LearningPathContent::getAllStepsOnlyModules($gid, $uid);
foreach ($steps as $step) {
if (in_array($step['typology'], [
'Meeting',
'ILT',
])) {
$is_mandatory = $step['mandatory'] === 1;
if ($step['typology'] === 'Meeting') {
$entity = $this
->entityTypeManager()
->getStorage('opigno_moxtra_meeting')
->load($step['id']);
if (!$entity
->isMember($uid)) {
$is_mandatory = FALSE;
}
}
elseif ($step['typology'] === 'ILT') {
$entity = $this
->entityTypeManager()
->getStorage('opigno_ilt')
->load($step['id']);
if (!$entity
->isMember($uid)) {
$is_mandatory = FALSE;
}
}
if ($is_mandatory) {
$name = $step['name'];
$required = $step['required score'];
if ($required >= 0 || $step['typology'] == 'Meeting' && !$is_owner) {
if ($step['best score'] < $required || OpignoGroupManagerController::mustBeVisitedMeeting($step, $group)) {
$course_entity = OpignoGroupManagedContent::load($step['cid']);
$course_content_type = $this->content_type_manager
->createInstance($course_entity
->getGroupContentTypeId());
$current_step_url = $course_content_type
->getStartContentUrl($course_entity
->getEntityId(), $gid);
return $this
->failedStep('none', $is_ajax, $this
->requiredStepMessage($name, $required, $current_step_url
->toString()));
}
}
else {
if ($step['attempts'] === 0) {
return $this
->failedStep('none', $is_ajax, $this
->requiredStepMessage($name));
}
}
}
}
else {
break;
}
}
$steps = array_filter($steps, function ($step) {
return !in_array($step['typology'], [
'Meeting',
'ILT',
]);
});
$is_completed = (int) $this->database
->select('opigno_learning_path_achievements', 'a')
->fields('a')
->condition('uid', $user
->id())
->condition('gid', $group
->id())
->condition('status', 'completed')
->countQuery()
->execute()
->fetchField() > 0;
if ($is_completed) {
$results = $this->database
->select('opigno_learning_path_step_achievements', 'a')
->fields('a', [
'id',
'typology',
'entity_id',
'parent_id',
'position',
])
->condition('uid', $user
->id())
->condition('gid', $group
->id())
->execute()
->fetchAllAssoc('id');
if (!empty($results)) {
$is_valid = TRUE;
$steps_mandatory = array_filter($steps, function ($step) {
return $step['mandatory'];
});
foreach ($steps_mandatory as $step) {
$filtered = array_filter($results, function ($result) use ($results, $step) {
if (isset($step['parent'])) {
$step_parent = $step['parent'];
$result_parent = isset($results[$result->parent_id]) ? $results[$result->parent_id] : NULL;
if (!isset($result_parent) || $result_parent->typology !== $step_parent['typology'] || (int) $result_parent->entity_id !== (int) $step_parent['id'] || (int) $result_parent->position !== (int) $step_parent['position']) {
return FALSE;
}
}
return $result->typology === $step['typology'] && (int) $result->entity_id === (int) $step['id'] && (int) $result->position === (int) $step['position'];
});
if (count($filtered) !== 1) {
$is_valid = FALSE;
break;
}
}
if (!$is_valid) {
$form = $this
->formBuilder()
->getForm('Drupal\\opigno_learning_path\\Form\\DeleteAchievementsForm', $group);
if ($is_ajax) {
$redirect = $this
->redirectToHome('', TRUE);
if ($redirect) {
return $redirect;
}
else {
return (new AjaxResponse())
->addCommand(new OpenModalDialogCommand('', $form));
}
}
else {
return $form;
}
}
}
}
$freeNavigation = !OpignoGroupManagerController::getGuidedNavigation($group);
if ($freeNavigation) {
$content = OpignoGroupManagedContent::getFirstStep($group
->id());
if ($content
->getGroupContentTypeId() != 'ContentTypeCourse') {
$content_type = $this->content_type_manager
->createInstance($content
->getGroupContentTypeId());
$step_url = $content_type
->getStartContentUrl($content
->getEntityId(), $group
->id());
if ($is_ajax) {
return (new AjaxResponse())
->addCommand(new RedirectCommand($step_url
->toString()));
}
else {
return $this
->redirect($step_url
->getRouteName(), $step_url
->getRouteParameters(), $step_url
->getOptions());
}
}
}
$step_resumed_cid = opigno_learning_path_resumed_step($steps);
if ($step_resumed_cid) {
$content = OpignoGroupManagedContent::load($step_resumed_cid);
$content_type = $this->content_type_manager
->createInstance($content
->getGroupContentTypeId());
$step_url = $content_type
->getStartContentUrl($content
->getEntityId(), $group
->id());
OpignoGroupContext::setCurrentContentId($step_resumed_cid);
OpignoGroupContext::setGroupId($group
->id());
if ($is_ajax) {
return (new AjaxResponse())
->addCommand(new RedirectCommand($step_url
->toString()));
}
else {
return $this
->redirect($step_url
->getRouteName(), $step_url
->getRouteParameters(), $step_url
->getOptions());
}
}
$first_step = reset($steps);
if ($first_step === FALSE) {
return $this
->failedStep('no_first', $is_ajax);
}
$first_step = OpignoGroupManagedContent::load($first_step['cid']);
$content_type = $this->content_type_manager
->createInstance($first_step
->getGroupContentTypeId());
$step_url = $content_type
->getStartContentUrl($first_step
->getEntityId(), $group
->id());
if (empty($step_url)) {
return $this
->failedStep('no_url', $is_ajax);
}
OpignoGroupContext::setCurrentContentId($first_step
->id());
OpignoGroupContext::setGroupId($group
->id());
if ($is_ajax) {
return (new AjaxResponse())
->addCommand(new RedirectCommand($step_url
->toString()));
}
else {
return $this
->redirect($step_url
->getRouteName(), $step_url
->getRouteParameters(), $step_url
->getOptions());
}
}
public function getNextStep(Group $group, OpignoGroupManagedContent $parent_content, $content_update = TRUE) {
$content_type = $this->content_type_manager
->createInstance($parent_content
->getGroupContentTypeId());
$user_score = $content_type
->getUserScore(\Drupal::currentUser()
->id(), $parent_content
->getEntityId());
if ($user_score === FALSE && $parent_content
->isMandatory()) {
return $this
->failedStep('no_score');
}
$user = $this
->currentUser();
$uid = $user
->id();
$gid = $group
->id();
$is_owner = $uid === $group
->getOwnerId();
$cid = $parent_content
->id();
$freeNavigation = !OpignoGroupManagerController::getGuidedNavigation($group);
if ($freeNavigation) {
$steps = LearningPathContent::getAllStepsOnlyModules($gid, $uid, TRUE);
}
else {
$steps = LearningPathContent::getAllStepsOnlyModules($gid, $uid);
}
$count = count($steps);
$current_step = NULL;
$current_step_index = 0;
for ($i = 0; $i < $count - 1; ++$i) {
if ($steps[$i]['cid'] === $cid || !$freeNavigation && $steps[$i]['required score'] > $steps[$i]['best score']) {
$current_step_index = $i;
$current_step = $steps[$i];
break;
}
}
if (!$freeNavigation && isset($current_step) && $current_step['mandatory'] === 1) {
$name = $current_step['name'];
$required = $current_step['required score'];
if ($required >= 0 || $current_step['typology'] == 'Meeting') {
if ($current_step['typology'] == 'Module') {
$module = \Drupal::entityTypeManager()
->getStorage('opigno_module')
->load($current_step['id']);
$moduleHandler = \Drupal::service('module_handler');
if ($moduleHandler
->moduleExists('opigno_skills_system') && $module
->getSkillsActive()) {
$current_step['current attempt score'] = $current_step['best score'];
}
}
if ($current_step['mandatory'] && !$current_step['attempts'] || $current_step['mandatory'] && $current_step['attempts'] && empty($current_step['completed on']) || $current_step['best score'] < $required || OpignoGroupManagerController::mustBeVisitedMeeting($current_step, $group) && !$is_owner) {
$course_entity = OpignoGroupManagedContent::load($current_step['cid']);
$course_content_type = $this->content_type_manager
->createInstance($course_entity
->getGroupContentTypeId());
$current_step_url = $course_content_type
->getStartContentUrl($course_entity
->getEntityId(), $gid);
$message = $this
->requiredStepMessage($name, $required, $current_step_url
->toString());
$message = $this
->failedStep('none', FALSE, $message);
if ($current_step['typology'] == 'Module') {
$module = OpignoModule::load($current_step['id']);
if (!empty($module)) {
$activities = $module
->getModuleActivities();
$activities = array_map(function ($activity) {
return OpignoActivity::load($activity->id);
}, $activities);
$attempts = $module
->getModuleAttempts($user);
if (!empty($attempts)) {
$attempt = $this
->getTargetAttempt($attempts, $module);
}
else {
$attempt = NULL;
}
if ($activities) {
foreach ($activities as $activity) {
$answer = isset($attempt) ? $activity
->getUserAnswer($module, $attempt, $user) : NULL;
if ($answer && $activity
->hasField('opigno_evaluation_method') && $activity
->get('opigno_evaluation_method')->value && !$answer
->isEvaluated()) {
$training_url = Url::fromRoute('entity.group.canonical', [
'group' => $group
->id(),
]);
$message = $this
->t('One or several activities in module %step require a manual grading. You will be allowed to continue the training as soon as these activities have been graded and if you reach the minimum score %required.<br /><a href=":link">Back to training homepage.</a>', [
'%step' => $name,
'%required' => $required,
':link' => $training_url
->toString(),
]);
$message = $this
->failedStep('none', FALSE, $message);
break;
}
}
}
}
}
if ($content_update) {
$this
->setGroupAndContext($group
->id(), $current_step['cid']);
}
return $message;
}
}
}
if (isset($current_step['is last child']) && $current_step['is last child'] && isset($current_step['parent'])) {
$course = $current_step['parent'];
if ($course['mandatory'] === 1) {
$name = $course['name'];
$required = $course['required score'];
if ($required >= 0) {
if ($course['best score'] < $required) {
$module_content = OpignoGroupManagedContent::getFirstStep($course['id']);
$module_content_type = $this->content_type_manager
->createInstance($module_content
->getGroupContentTypeId());
$module_url = $module_content_type
->getStartContentUrl($module_content
->getEntityId(), $gid);
if ($content_update) {
$this
->setGroupAndContext($group
->id(), $module_content
->id());
}
return $this
->failedStep('none', FALSE, $this
->requiredStepMessage($name, $required, $module_url
->toString()));
}
}
else {
if ($course['attempts'] === 0) {
$module_content = OpignoGroupManagedContent::getFirstStep($course['id']);
if ($content_update) {
$this
->setGroupAndContext($group
->id(), $module_content
->id());
}
return $this
->failedStep('none', FALSE, $this
->requiredStepMessage($name));
}
}
}
}
$skip_types = [];
for ($next_step_index = $current_step_index + 1; $next_step_index < $count && in_array($steps[$next_step_index]['typology'], $skip_types); ++$next_step_index) {
$next_step = $steps[$next_step_index];
$is_mandatory = $next_step['mandatory'] === 1;
if ($next_step['typology'] === 'Meeting') {
$entity = $this
->entityTypeManager()
->getStorage('opigno_moxtra_meeting')
->load($next_step['id']);
if (!$entity
->isMember($uid)) {
$is_mandatory = FALSE;
}
}
elseif ($next_step['typology'] === 'ILT') {
$entity = $this
->entityTypeManager()
->getStorage('opigno_ilt')
->load($next_step['id']);
if (!$entity
->isMember($uid)) {
$is_mandatory = FALSE;
}
}
if ($is_mandatory) {
$name = $next_step['name'];
$required = $next_step['required score'];
if ($required > 0) {
if ($next_step['best score'] < $required) {
return $this
->failedStep('none', FALSE, $this
->requiredStepMessage($name, $required));
}
}
else {
if ($next_step['attempts'] === 0) {
return $this
->failedStep('none', FALSE, $this
->requiredStepMessage($name));
}
}
}
}
return $steps[$next_step_index] ?? NULL;
}
public function setGroupAndContext($group_id, $content_id) {
OpignoGroupContext::setGroupId($group_id);
OpignoGroupContext::setCurrentContentId($content_id);
}
public function nextStep(Group $group, OpignoGroupManagedContent $parent_content, $content_update = TRUE) {
$next_step = $this
->getNextStep($group, $parent_content, $content_update);
if ($next_step === NULL) {
$this
->messenger()
->addWarning($this
->t('You reached the last content of that training.'));
return $this
->redirect('entity.group.canonical', [
'group' => $group
->id(),
]);
}
if (!isset($next_step['cid'])) {
return $next_step;
}
$next_step = OpignoGroupManagedContent::load($next_step['cid']);
if ($content_update) {
$this
->setGroupAndContext($group
->id(), $next_step
->id());
}
$next_step_content_type = $this->content_type_manager
->createInstance($next_step
->getGroupContentTypeId());
$next_step_url = $next_step_content_type
->getStartContentUrl($next_step
->getEntityId(), $group
->id());
return $this
->redirect($next_step_url
->getRouteName(), $next_step_url
->getRouteParameters(), $next_step_url
->getOptions());
}
public function finish(Group $group) {
$current_uid = \Drupal::currentUser()
->id();
$user_passed = LearningPathValidator::userHasPassed($current_uid, $group);
if ($user_passed) {
$current_result_attempt = LPResult::getCurrentLPAttempt($group, \Drupal::currentUser());
if ($current_result_attempt) {
$current_result_attempt
->setHasPassed($user_passed);
$current_result_attempt
->setFinished(\Drupal::time()
->getRequestTime());
$current_result_attempt
->save();
}
else {
$result = LPResult::createWithValues($group
->id(), $current_uid, $user_passed, \Drupal::time()
->getRequestTime());
$result
->save();
}
return $this
->failedStep('passed');
}
else {
return $this
->failedStep('failed');
}
}
public function contentSteps(Group $group, $current) {
LearningPathValidator::stepsValidate($group);
$type = opigno_learning_path_get_group_type();
$all_steps = opigno_learning_path_get_routes_steps();
$unique_steps = array_unique($all_steps);
$next_step = $current < count($unique_steps) ? $current + 1 : NULL;
if (!$next_step) {
if ($type == 'learning_path') {
return $this
->redirect('opigno_learning_path.manager.publish', [
'group' => $group
->id(),
]);
}
else {
return $this
->redirect('entity.group.canonical', [
'group' => $group
->id(),
]);
}
}
else {
if ($type == 'learning_path') {
$group_courses = $group
->getContent('subgroup:opigno_course');
if ($current == 2 && empty($group_courses)) {
$next_step++;
}
}
$route = array_search($next_step, opigno_learning_path_get_routes_steps());
return $this
->redirect($route, [
'group' => $group
->id(),
]);
}
}
public function listSteps(Group $group) {
$date_formatter = \Drupal::service('date.formatter');
$group_id = $group
->id();
$uid = \Drupal::currentUser()
->id();
$steps = opigno_learning_path_get_steps($group_id, $uid);
$rows = array_map(function ($step) use ($date_formatter) {
return [
$step['name'],
$step['typology'],
$date_formatter
->formatInterval($step['time spent']),
$step['best score'],
];
}, $steps);
return [
'#type' => 'table',
'#header' => [
$this
->t('Name'),
$this
->t('Typology'),
$this
->t('Total time spent'),
$this
->t('Best score achieved'),
],
'#rows' => $rows,
];
}
public function nextStepAccess(Group $group, OpignoGroupManagedContent $parent_content) {
$content_type = $this->content_type_manager
->createInstance($parent_content
->getGroupContentTypeId());
$user_score = $content_type
->getUserScore(\Drupal::currentUser()
->id(), $parent_content
->getEntityId());
if ($user_score === FALSE && $parent_content
->isMandatory()) {
return AccessResult::forbidden();
}
return AccessResult::allowed();
}
public function startAccess(Group $group) {
if ($group
->bundle() !== 'learning_path') {
return AccessResult::neutral();
}
$user = $this
->currentUser();
$group_visibility = $group
->get('field_learning_path_visibility')
->getValue()[0]['value'];
if ($user
->isAnonymous() && $group_visibility != 'public') {
return AccessResult::forbidden();
}
$access = LearningPathAccess::statusGroupValidation($group, $user);
if ($access === FALSE) {
return AccessResult::forbidden();
}
return AccessResult::allowed();
}
protected function getTargetAttempt(array $attempts, OpignoModule $module) {
if ($module
->getKeepResultsOption() == 'newest') {
$attempt = end($attempts);
}
else {
$attempt = opigno_learning_path_best_attempt($attempts);
}
return $attempt;
}
protected function requiredStepMessage($name, $required = NULL, $link = '') {
if (empty($required)) {
return $this
->t('A required step: %step should be done first.', [
'%step' => $name,
]);
}
else {
$text = 'You should first get a minimum score of %required to the step %step before going further.';
if (!empty($link)) {
return $this
->t("{$text} <a href=':link'>Try again.</a>", [
'%step' => $name,
'%required' => $required,
':link' => $link,
]);
}
else {
return $this
->t("{$text}", [
'%step' => $name,
'%required' => $required,
]);
}
}
}
}