You are here

opigno_learning_path.module in Opigno Learning path 8

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

File

opigno_learning_path.module
View source
<?php

/**
 * @file
 * Contains opigno_learning_path.module.
 */
use Drupal\Component\Render\MarkupInterface;
use Drupal\views\Plugin\views\query\QueryPluginBase;
use Drupal\views\ViewExecutable;
use Drupal\entity_browser\Plugin\Field\FieldWidget\EntityReferenceBrowserWidget;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Link;
use Drupal\Core\Render\Markup;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Url;
use Drupal\Core\Render\Element;
use Drupal\Core\Template\Attribute;
use Drupal\group\Entity\Group;
use Drupal\group\Entity\GroupInterface;
use Drupal\opigno_ilt\ILTInterface;
use Drupal\opigno_learning_path\Entity\LatestActivity;
use Drupal\opigno_learning_path\Entity\LPResult;
use Drupal\opigno_learning_path\Entity\LPStatus;
use Drupal\opigno_learning_path\LearningPathAccess;
use Drupal\opigno_learning_path\LearningPathContent;
use Drupal\opigno_group_manager\Controller\OpignoGroupManagerController;
use Drupal\opigno_group_manager\Entity\OpignoGroupManagedContent;
use Drupal\opigno_group_manager\OpignoGroupContext;
use Drupal\opigno_module\Entity\OpignoActivity;
use Drupal\opigno_module\Entity\OpignoModule;
use Drupal\opigno_module\Entity\UserModuleStatus;
use Drupal\opigno_moxtra\MeetingInterface;
use Drupal\multiselect\Element\Multiselect;
use Drupal\user\Entity\User;
use Drupal\Core\Breadcrumb\Breadcrumb;

/**
 * Implements hook_theme().
 */
function opigno_learning_path_theme() {
  return [
    'opigno_learning_path_manager' => [
      'variables' => [
        'base_path' => NULL,
        'base_href' => NULL,
        'learning_path_id' => NULL,
      ],
    ],
    'opigno_learning_path_courses' => [
      'variables' => [
        'base_path' => NULL,
        'base_href' => NULL,
        'learning_path_id' => NULL,
        'group_type' => NULL,
        'view_type' => NULL,
        'next_link' => NULL,
        'user_has_info_card' => NULL,
        'parent_learning_path' => NULL,
      ],
    ],
    'opigno_learning_path_modules' => [
      'variables' => [
        'base_path' => NULL,
        'base_href' => NULL,
        'learning_path_id' => NULL,
        'module_context' => NULL,
        'next_link' => NULL,
        'user_has_info_card' => NULL,
      ],
    ],
    'opigno_learning_path_item_form' => [
      'variables' => [],
    ],
    'group__learning_path' => [
      'base hook' => 'group',
    ],
    'group__learning_path__teaser_of_group' => [
      'base hook' => 'group',
    ],
    'region__content__admin__learning_path' => [
      'base hook' => 'region',
      'step_list_top' => NULL,
      'step_list_aside' => NULL,
    ],
    'page__group' => [
      'base hook' => 'page',
      'join_group_form' => NULL,
      'delete_lp_form' => NULL,
    ],
    'opigno_learning_path_training_content' => [
      'render element' => 'content',
    ],
    'opigno_learning_path_training_content_step' => [
      'variables' => [
        'step' => NULL,
        'group' => NULL,
      ],
    ],
    'multiselect_new_class' => [
      'arguments' => [
        'element' => NULL,
      ],
      'render element' => 'element',
      'template' => 'multiselect-new-class',
    ],
    'opigno_learning_path_progress_ajax_container' => [
      'variables' => [
        'group_id' => NULL,
        'account_id' => NULL,
        'latest_cert_date' => NULL,
        'class' => NULL,
      ],
    ],
    'opigno_learning_path_progress' => [
      'variables' => [
        'label' => NULL,
        'value' => NULL,
        'class' => NULL,
        'progress_bar' => TRUE,
      ],
    ],
    'opigno_learning_path_message' => [
      'variables' => [
        'markup' => NULL,
      ],
    ],
    'opigno_learning_path_training' => [
      'render element' => 'elements',
    ],
    'opigno_learning_path_training_timeline' => [
      'variables' => [
        'steps' => NULL,
      ],
    ],
    'opigno_learning_path_training_timeline_info' => [
      'variables' => [
        'label' => NULL,
        'text' => NULL,
      ],
    ],
    'opigno_learning_path_training_summary' => [
      'variables' => [
        'progress' => NULL,
        'score' => NULL,
        'group_id' => NULL,
        'has_certificate' => NULL,
        'is_passed' => NULL,
        'state_class' => NULL,
        'registration_date' => NULL,
        'validation_message' => NULL,
        'time_spend' => NULL,
        'certificate_url' => NULL,
      ],
    ],
    'opigno_learning_path_training_details' => [
      'variables' => [
        'group_id' => NULL,
      ],
    ],
    'opigno_learning_path_training_course_content' => [
      'render element' => 'elements',
    ],
    'opigno_learning_path_training_course' => [
      'variables' => [
        'passed' => NULL,
        'score' => NULL,
        'step' => NULL,
        'completed' => NULL,
        'badges' => NULL,
        'time_spent' => NULL,
      ],
    ],
    'opigno_learning_path_training_module' => [
      'variables' => [
        'status' => NULL,
        'group_id' => NULL,
        'step' => NULL,
        'approved' => NULL,
        'completed' => NULL,
        'badges' => NULL,
        'time_spent' => NULL,
      ],
    ],
    'opigno_learning_path_training_step' => [
      'render element' => 'elements',
    ],
    'opigno_learning_path_step_block' => [
      'variables' => [
        'title' => NULL,
        'state_summary' => NULL,
        'table_summary' => NULL,
      ],
    ],
  ];
}
function opigno_learning_path_theme_suggestions_multiselect(array $variables) {
  if (isset($variables["element"]["#name"]) && $variables["element"]["#name"] == 'new_class_users') {
    $suggestions = [];
    $suggestions[] = 'multiselect_new_class';
    return $suggestions;
  }
}

/**
 * Implements hook_page_attachments().
 */
function opigno_learning_path_page_attachments(array &$page) {
  $route = \Drupal::routeMatch()
    ->getRouteName();
  if ($route == 'entity.group.edit_form') {
    $page['#attached']['library'][] = 'opigno_learning_path/delete';
  }
  if ($route == 'entity.group.canonical') {
    $page['#attached']['library'][] = 'opigno_learning_path/join';
    $page['#attached']['library'][] = 'opigno_learning_path/training_main_page';
    $query = \Drupal::request()->query
      ->all();
    if (!empty($query['force'])) {
      $page['#attached']['library'][] = 'opigno_learning_path/run_force';
    }
  }
  $page['#attached']['library'][] = 'opigno_learning_path/global';
}

/**
 * Implements hook_theme_suggestions_alter().
 */
function opigno_learning_path_theme_suggestions_alter(array &$suggestions, array $variables, $hook) {
  if (\Drupal::routeMatch()
    ->getRouteName() == 'opigno_learning_path.manager.get_item_form' && $hook == 'page') {
    $suggestions[] = 'opigno_learning_path_item_form';
  }
  if (opigno_learning_path_is_lp_route() && $hook == 'region' && in_array('region__content', $suggestions)) {
    $suggestions[] = 'region__content__admin__learning_path';
  }
}

/**
 * Implements hook_preprocess_html().
 */
function opigno_learning_path_preprocess_html(&$variables) {

  // Remove admin bar for item form (included in iframe).
  if (\Drupal::routeMatch()
    ->getRouteName() == 'opigno_learning_path.manager.get_item_form') {
    unset($variables['page_top']);
  }
  if (opigno_learning_path_is_lp_route()) {
    $variables['attributes']['class'][] = 'admin-learning-path';
  }
}

/**
 * Implements hook_preprocess_region().
 */
function opigno_learning_path_preprocess_region(&$variables) {
  if (opigno_learning_path_is_lp_route() && $variables['region'] == 'content') {
    $variables['step_list_top'] = opigno_learning_path_get_step_list_top();
    $variables['step_list_aside'] = opigno_learning_path_get_step_list_aside();
    $group = \Drupal::routeMatch()
      ->getParameter('group');
    if ($group instanceof GroupInterface && \Drupal::routeMatch()
      ->getRouteName() != 'opigno_group_manager.manager.index' && \Drupal::routeMatch()
      ->getRouteName() != 'opigno_learning_path.learning_path_courses' && \Drupal::routeMatch()
      ->getRouteName() != 'opigno_learning_path.learning_path_modules') {
      $current_step = opigno_learning_path_get_current_step();

      // Check if training is published.
      $is_published = $group
        ->hasField('field_learning_path_published') ? $group
        ->get('field_learning_path_published')
        ->getValue()[0]['value'] : TRUE;
      $finish_text = !$is_published ? t('Publish') : t('Finish');
      $next_step = $current_step < 5 ? $current_step + 1 : NULL;
      $link_text = !$next_step ? $finish_text : t('Next');
      if (!$next_step && !$group
        ->access('update')) {
        $variables['next_link'] = '';
      }
      else {
        $variables['next_link'] = Link::createFromRoute($link_text, 'opigno_learning_path.content_steps', [
          'group' => $group
            ->id(),
          'current' => $current_step ? $current_step : 0,
        ], [
          'attributes' => [
            'class' => [
              'btn',
              'btn-success',
              'color-white',
            ],
            'data-toggle' => 'trigger_click',
            'data-target' => '#group-learning-path-edit-form .form-submit',
          ],
        ])
          ->toRenderable();
      }
    }
    else {
      $variables['next_link'] = '';
    }
  }
  if (\Drupal::routeMatch()
    ->getRouteName() == 'opigno_learning_path.membership.overview' && $variables['region'] == 'content' && ($group = \Drupal::routeMatch()
    ->getParameter('group'))) {
    $bundle = $group
      ->bundle();

    // Limit the number of members in the class.
    if (!($bundle == 'opigno_class') || count($group
      ->getMembers()) < 100) {
      $link = Link::createFromRoute(t('Add members'), 'entity.group_content.add_form', [
        'group' => $group
          ->id(),
        'plugin_id' => 'group_membership',
      ], [
        'attributes' => [
          'id' => 'btn_member_add',
          'class' => [
            'btn',
            'btn-success',
          ],
        ],
      ])
        ->toRenderable();
      $variables['add_member_link'] = render($link);
    }
    else {
      $variables['add_member_link'] = [
        '#type' => 'markup',
        '#markup' => '<div class="class-limit-reached">' . t('A limit of the number of members in a class is reached.') . '</div>',
      ];
    }
  }
}

/**
 * Implements hook_preprocess_multiselect().
 */
function opigno_learning_path_preprocess_multiselect_new_class(&$variables) {
  $element = $variables['element'];
  Element::setAttributes($element, array(
    'id',
    'name',
    'size',
    'required',
  ));
  $available_options = Multiselect::getOptions('available', $element);
  $available_size = min(count($available_options), 10);
  $selected_options = Multiselect::getOptions('selected', $element);
  $selected_size = min(count($selected_options), 10);
  $total_size = $available_size + $selected_size;
  $variables['multiselect'] = array(
    'available' => array(
      'id' => $element['#attributes']['id'] . '-available',
      'label' => t('Available Options'),
      'attributes' => array(
        'id' => $element['#attributes']['id'] . '-available',
        'size' => $total_size,
      ),
      'options' => $available_options,
    ),
    'selected' => array(
      'id' => $element['#attributes']['id'],
      'label' => t('Selected Options'),
      'attributes' => $element['#attributes'],
      'options' => $selected_options,
    ),
    'labels' => array(
      'add' => t('Add'),
      'remove' => t('Remove'),
    ),
  );

  // Prepare selected attributes.
  $variables['multiselect']['selected']['attributes']['size'] = $total_size;

  // Prepare attributes for available select.
  foreach (array(
    'multiple',
    'required',
    'class',
  ) as $key) {
    $element_key = "#{$key}";
    if (isset($element[$element_key])) {
      $variables['multiselect']['available']['attributes'][$key] = $element[$element_key];
    }
  }

  // Prepare attributes.
  $multiselect =& $variables['multiselect'];
  foreach (array(
    'available',
    'selected',
  ) as $key) {
    $multiselect[$key]['attributes']['class'][] = 'multiselect-' . $key;
    $multiselect[$key]['attributes']['class'][] = 'form-multiselect';
    if (isset($multiselect[$key]['attributes']) && !$multiselect[$key]['attributes'] instanceof Attribute) {
      if ($multiselect[$key]['attributes']) {
        $multiselect[$key]['attributes'] = new Attribute($multiselect[$key]['attributes']);
      }
    }
  }
}

/**
 * Sends email notifications about training expired certification.
 *
 * Implements hook_cron().
 */
function opigno_learning_path_cron() {
  $config = \Drupal::config('opigno_learning_path.learning_path_settings');
  $current_timestamp = time();
  $near_timestamp = strtotime('+7 day', $current_timestamp);

  // Get users trainings with soon expiration - in 7 days.
  $db_connection = \Drupal::database();
  $results = $db_connection
    ->select('user_lp_status_expire', 'lps')
    ->fields('lps', [
    'uid',
    'gid',
  ])
    ->condition('expire', $current_timestamp, '>')
    ->condition('expire', $near_timestamp, '<')
    ->condition('notified', 0)
    ->execute()
    ->fetchAll();
  if ($results) {
    $token = \Drupal::token();
    $mail_service = \Drupal::service('plugin.manager.mail');
    $subject = t('The "[group:title]" training certification is expiring on [group:expiration_date]');
    $message = $config
      ->get('opigno_learning_path_notify_user_user_certificate_expired');
    $module = 'opigno_learning_path';
    $key = 'training_expiring_certification';
    foreach ($results as $item) {
      $group = Group::load($item->gid);
      $user = User::load($item->uid);
      if ($user && $group) {
        $params['subject'] = $token
          ->replace($subject, [
          'group' => $group,
          'uid' => $item->uid,
        ]);
        $params['message'] = $token
          ->replace($message, [
          'group' => $group,
          'uid' => $item->uid,
        ]);
        $to = $user
          ->getEmail();
        $langcode = $user
          ->getPreferredLangcode();
        $sent = $mail_service
          ->mail($module, $key, $to, $langcode, $params, NULL, TRUE);
        if ($sent["result"]) {
          LPStatus::setUserNotification($item->gid, $item->uid, 1);
        }
      }
    }
  }
}

/**
 * Implements hook_preprocess_page().
 */
function opigno_learning_path_preprocess_page(&$variables) {
  $route = \Drupal::routeMatch()
    ->getRouteName();
  $account = \Drupal::currentUser();
  $group = \Drupal::routeMatch()
    ->getParameter('group');

  // Hide local tasks.
  if (opigno_learning_path_is_lp_route()) {
    unset($variables['page']['content']['platon_local_tasks']);
  }

  // Check if there is required trainings.
  if ($route == 'entity.group.canonical' && $group && $group
    ->getGroupType()
    ->id() == 'learning_path' && ($required_trainings = LearningPathAccess::hasUncompletedRequiredTrainings($group, $account))) {

    // Show for user required trainings.
    $links = [];
    foreach ($required_trainings as $gid) {
      $training = Group::load($gid);
      $url = Url::fromRoute($route, [
        'group' => $training
          ->id(),
      ]);
      $link = Link::fromTextAndUrl($training
        ->label(), $url)
        ->toString();
      array_push($links, render($link));
    }
    $messenger = \Drupal::messenger();
    $messenger
      ->addWarning(Markup::create(t('You need to complete first some prerequisite trainings:') . ' ' . implode(', ', $links)));
  }

  // Get join form.
  if ($route == 'entity.group.canonical' && $group) {
    $form_builder = \Drupal::service('opigno_learning_path.join_form');
    $form = $form_builder
      ->getForm($group);
    if ($group
      ->hasField('field_learning_path_visibility')) {
      $visibility = $group->field_learning_path_visibility->value;
      if ($visibility === 'semiprivate' && !$account
        ->isAuthenticated() || !$group
        ->hasPermission('join group', $account)) {
        return;
      }
    }
    if (!$group
      ->getMember($account)) {
      $variables['join_group_form'] = [
        '#type' => 'container',
        '#attributes' => [
          'id' => 'join-group-form-overlay',
          'style' => 'display: none;',
        ],
        [
          '#type' => 'container',
          '#attributes' => [
            'id' => 'join-group-form-wrapper',
          ],
          [
            '#type' => 'container',
            '#attributes' => [
              'class' => [
                'text-right',
              ],
            ],
            [
              '#type' => 'html_tag',
              '#tag' => 'button',
              '#attributes' => [
                'class' => [
                  'close-overlay',
                ],
              ],
              '#value' => t('close'),
            ],
          ],
          [
            '#type' => 'container',
            'form' => $form,
          ],
        ],
      ];
    }
  }
  if ($route == 'entity.group.edit_form' && ($group = \Drupal::routeMatch()
    ->getParameter('group'))) {
    $form_builder = \Drupal::service('entity.form_builder');
    $form = $form_builder
      ->getForm($group, 'delete', []);

    // Change default link to current page.
    $url = Url::fromRoute('entity.group.edit_form', [
      'group' => $group
        ->id(),
    ], [
      'attributes' => [
        'class' => [
          'button',
          'close-overlay',
        ],
      ],
    ]);
    $link = Link::fromTextAndUrl(t('Cancel'), $url)
      ->toRenderable();
    $form['actions']['cancel']['#url'] = $url;
    $variables['delete_lp_form'] = [
      '#type' => 'container',
      '#attributes' => [
        'id' => 'delete-lp-form-overlay',
        'style' => 'display: none;',
      ],
      [
        '#type' => 'container',
        '#attributes' => [
          'id' => 'delete-lp-form-wrapper',
        ],
        [
          '#type' => 'container',
          '#attributes' => [
            'class' => [
              'text-right',
            ],
          ],
          [
            '#type' => 'html_tag',
            '#tag' => 'button',
            '#attributes' => [
              'class' => [
                'close-overlay',
              ],
            ],
            '#value' => t('close'),
          ],
        ],
        [
          '#type' => 'container',
          'form' => $form,
        ],
      ],
    ];
  }
}

/**
 * Implements hook_preprocess_HOOK() for block.
 */
function opigno_learning_path_preprocess_block(&$variables) {
  $route = \Drupal::routeMatch();

  // Change pages title.
  if ($variables['plugin_id'] == 'page_title_block') {
    if ($route
      ->getRouteName() == 'entity.taxonomy_vocabulary.overview_form') {
      $parameter = $route
        ->getParameter('taxonomy_vocabulary');
      if ($parameter && $parameter
        ->id() == 'learning_path_category') {
        $variables['content']['#title'] = t('Manage training categories');
      }
    }
    elseif ($route
      ->getRouteName() == 'opigno_learning_path.learning_path_settings') {
      $variables['content']['#title'] = t('Manage training settings');
    }
  }

  // Unset empty links for opigno admin menu.
  if ($variables["derivative_plugin_id"] == "opigno-admin") {
    $menu_items = $variables["content"]["#items"];
    foreach ($menu_items as $name => $item) {
      if (!$item['below']) {
        unset($variables["content"]["#items"][$name]);
      }
    }
  }
}

/**
 * Implements template_preprocess_HOOK().
 */
function opigno_learning_path_preprocess_opigno_certificate(array &$variables) {

  /** @var \Drupal\opigno_certificate\CertificateInterface $opigno_certificate */
  $opigno_certificate = $variables['elements']['#opigno_certificate'];

  /** @var \Drupal\Core\Entity\ContentEntityInterface $referencing_entity */
  $referencing_entity = $opigno_certificate->referencing_entity->entity;

  // Provide the referencing entity context.
  if ($referencing_entity) {
    $completed = opigno_learning_path_completed_on($referencing_entity
      ->id(), \Drupal::currentUser()
      ->id(), TRUE);
    if ($completed) {
      $variables['completed_on'] = \Drupal::service('date.formatter')
        ->format($completed, 'custom', 'd/M/Y');
    }

    // @todo: add a proper API function to get courses for a learning path.
    $managed_content = OpignoGroupManagedContent::loadByProperties([
      'group_id' => $referencing_entity
        ->id(),
    ]);
    $course_titles = [];
    foreach ($managed_content as $content) {

      // Need the content type object to get the LearningPathContent object.
      $content_type_id = $content
        ->getGroupContentTypeId();
      if ($content_type_id == 'ContentTypeCourse') {
        $group = Group::load($content
          ->getEntityId());
        $course_titles[] = $group
          ->label();
      }
    }
    $variables['extra'] = implode(', ', $course_titles);
  }
}

/**
 * Returns step list for page top area.
 */
function opigno_learning_path_get_step_list_top() {
  $steps = [];
  $group_type = opigno_learning_path_get_group_type();
  $current_step = opigno_learning_path_get_current_step();
  $passed_steps = opigno_learning_path_validate_group_creating_steps();
  $enough_activities = TRUE;
  if ($group_type == 'learning_path') {
    $steps = [
      1 => t('Create training'),
      2 => t('Structure the training'),
      3 => t('Add modules to courses'),
      4 => t('Add activities to modules'),
      5 => t('Enroll learners'),
    ];

    // Check if training has 'automatic skills module'.
    $group = \Drupal::routeMatch()
      ->getParameter('group');
    $moduleHandler = \Drupal::service('module_handler');
    if ($moduleHandler
      ->moduleExists('opigno_skills_system') && $current_step == 5) {
      $group_steps = OpignoGroupManagedContent::loadByGroupId($group
        ->id());
      foreach ($group_steps as $group_step) {
        $id = $group_step
          ->getEntityId();
        $type_id = $group_step
          ->getGroupContentTypeId();
        if ($type_id === 'ContentTypeModule') {
          $module = OpignoModule::load($id);
          if ($module
            ->getSkillsActive() && $module
            ->getModuleSkillsGlobal()) {
            $target_skill = $module
              ->getTargetSkill();
            $term_storage = \Drupal::entityTypeManager()
              ->getStorage('taxonomy_term');
            $skills_tree = array_reverse($term_storage
              ->loadTree('skills', $target_skill));
            $skills_ids = [];

            // Get all ids of skills from that tree.
            foreach ($skills_tree as $skill) {
              $skills_ids[] = $skill->tid;
            }

            // Get all suitable activities for that skills tree from Opigno system.
            $activities = $module
              ->getSuitableActivities($skills_ids);
            foreach ($skills_tree as $key => $skill) {
              $tid = $skill->tid;
              $term_storage = \Drupal::entityTypeManager()
                ->getStorage('taxonomy_term');
              $skill_entity = $term_storage
                ->load($tid);
              $min_count_answers = $skill_entity
                ->get('field_minimum_count_of_answers')
                ->getValue()[0]['value'];
              $skill_levels = $skill_entity
                ->get('field_level_names')
                ->getValue();
              $count_of_levels = count($skill_levels);
              $skill_availability = [];
              while ($count_of_levels > 0) {
                $skill_availability[$count_of_levels] = 0;
                $count_of_levels--;
              }
              ksort($skill_availability);

              // Get count of suitable activities for each level of skills.
              foreach ($activities as $activity) {
                if ($activity->skills_list == $skill->tid) {
                  $skill_availability[$activity->skill_level]++;
                }
              }
              foreach ($skill_availability as $level => $count_of_activities) {
                if ($count_of_activities < $min_count_answers) {
                  $enough_activities = FALSE;
                  $target_skill_entity = $term_storage
                    ->load($target_skill);
                  $not_enough_skill_name = $skill_entity
                    ->label();
                  $not_enough_skill_level = $skill_levels[$level - 1]['value'];
                  break;
                }
              }
            }
          }
        }
      }
    }
  }
  elseif ($group_type == 'opigno_course') {
    $steps = [
      1 => t('Create course'),
      2 => t('Add modules to courses'),
      3 => t('Add activities to modules'),
    ];
  }
  elseif ($group_type == 'opigno_module') {
    $steps = [
      1 => t('Create module'),
      2 => t('Add activities to module'),
      3 => t('Add bulk activities to module'),
    ];
  }
  elseif ($group_type == 'opigno_class') {
    $steps = [
      1 => t('Create class'),
      2 => t('Enroll learners'),
    ];
  }
  $html = '<ul class="d-none d-md-flex justify-content-center text-center list-unstyled step-list-top mb-5">';
  $icon = '';
  foreach ($steps as $key => $step) {
    $classes = [
      'mx-4',
      'mb-4',
      'mb-md-0',
    ];
    if ($current_step >= $key) {
      $classes[] = 'active';
    }
    if ($passed_steps) {

      // @TODO check if step is validated.
      if (in_array($key, $passed_steps) && ($key != 4 || isset($enough_activities) && $enough_activities)) {
        $classes[] = 'done';
        $icon = 'ok-fill';
      }
      else {
        $classes[] = 'error';
        $icon = 'close';
      }
    }
    $html .= '<li class="' . implode(' ', $classes) . '">';
    $html .= '<div class="number mx-auto">' . $key . '</div>';
    $html .= '<div class="title">' . $step . '</div>';
    $html .= '<i class="icon-' . $icon . '"></i>';
    $html .= '</li>';
  }
  $html .= '</ul>';

  // Add steps explanation.
  if ($group_type == 'learning_path') {
    $explanations = [
      1 => t('<p>A training is a program composed of diverse courses, modules and activities which guide students throughout their learning process.</p><p>Start here by describing the basic information of your learning program and click the button «Next» . You will be taken to step 2, to the Learning Path Manager to create content.</p><p>You can learn more in our <a href="https://opigno.atlassian.net/wiki/spaces/OUM20/overview" target="_blank">online user manual</a></p>'),
      2 => t('<p>Welcome to the Learning Path Manager. It makes possible to build up the steps composing your training, that can be courses (groups of modules), modules, live meetings or instructor-led trainings. At the following steps you will be able to manage the modules inside the courses, and the activities inside the modules.</p><p>Start by adding or creating your first content.</p>'),
      3 => t('In case you added some courses to your training, you can manage here the modules inside these courses . Note that you can also directly add modules in the training at step 2.'),
      4 => t('You can manage here the contents of the modules inside your training, add activities to them, manage the existing activities.'),
      5 => t('You can invite here users or classes (group of users) to join your training. Then click on "Publish" button to have your training ready!'),
    ];
    if (isset($enough_activities) && !$enough_activities) {
      $warning = t('There are not enough activities in the Opigno System to complete "@target_skill" skills tree!<br />
        Missed skill "@skill_name" with level "@skill_level".', [
        '@target_skill' => $target_skill_entity
          ->getName(),
        '@skill_name' => $not_enough_skill_name,
        '@skill_level' => $not_enough_skill_level,
      ]);
      $html .= "<div class='lp_step_warning lp_step_explanation'>{$warning}</div>";
    }
    $messenger = \Drupal::messenger();
    $messages = $messenger
      ->messagesByType('error');
    if (!empty($messages)) {
      foreach ($messages as $message) {
        $html .= "<div class='lp_step_warning lp_step_explanation'>{$message}</div>";
      }
      $messenger
        ->deleteByType('error');
    }
    $explanation = $explanations[$current_step];
    $html .= "<div class='lp_step_explanation'>{$explanation}</div>";
  }
  return new FormattableMarkup($html, []);
}

/**
 * Returns step list for page aside area.
 */
function opigno_learning_path_get_step_list_aside() {
  $group_type = opigno_learning_path_get_group_type();
  $steps = [];
  $route = \Drupal::routeMatch();
  if (($entity = $route
    ->getParameter('group')) !== NULL) {

    /** @var \Drupal\group\Entity\GroupInterface $entity */
    $args = [
      'group' => $entity
        ->id(),
    ];
    $home_link = [
      '#type' => 'container',
      '#attributes' => [
        'class' => [
          'mb-3',
        ],
      ],
      'link' => Link::createFromRoute(Markup::create('<i class="icon-home"></i>' . t('Home')), 'entity.group.canonical', $args, [
        'attributes' => [
          'class' => [
            'btn',
            'btn-primary',
            'btn-home',
            'w-100',
          ],
        ],
      ])
        ->toRenderable(),
    ];
    $description_link = Link::createFromRoute(t('Description'), 'entity.group.edit_form', $args)
      ->toRenderable();
    $lp_manager_link = Link::createFromRoute(t('Learning Path Manager'), 'opigno_group_manager.manager.index', $args)
      ->toRenderable();
    $modules_link = Link::createFromRoute(t('Modules'), 'opigno_learning_path.learning_path_courses', $args)
      ->toRenderable();
    $activities_link = Link::createFromRoute(t('Activities'), 'opigno_learning_path.learning_path_modules', $args)
      ->toRenderable();
    $members_link = Link::createFromRoute(t('Members'), 'opigno_learning_path.membership.overview', $args)
      ->toRenderable();
  }
  elseif (($entity = $route
    ->getParameter('opigno_module')) !== NULL) {

    /** @var \Drupal\opigno_module\Entity\OpignoModule $entity */
    $args = [
      'opigno_module' => $entity
        ->id(),
    ];
    $description_link = Link::createFromRoute(t('Description'), 'opigno_module.edit', $args)
      ->toRenderable();
    $activities_link = Link::createFromRoute(t('Activities'), 'opigno_module.modules', $args)
      ->toRenderable();
    $activities_bank_link = Link::createFromRoute(t('Activities bank'), 'opigno_module.activities_bank', $args)
      ->toRenderable();
    $home_link = [];
    $lp_manager_link = [];
    $modules_link = [];
    $members_link = [];
  }
  else {
    $description_link = [
      '#type' => 'html_tag',
      '#tag' => 'span',
      '#value' => t('Description'),
    ];
    $lp_manager_link = [
      '#type' => 'html_tag',
      '#tag' => 'span',
      '#value' => t('Learning Path Manager'),
    ];
    $modules_link = [
      '#type' => 'html_tag',
      '#tag' => 'span',
      '#value' => t('Modules'),
    ];
    $activities_link = [
      '#type' => 'html_tag',
      '#tag' => 'span',
      '#value' => t('Activities'),
    ];
    $activities_bank_link = [
      '#type' => 'html_tag',
      '#tag' => 'span',
      '#value' => t('Activities bank'),
    ];
    $members_link = [
      '#type' => 'html_tag',
      '#tag' => 'span',
      '#value' => t('Members'),
    ];
    $home_link = [];
  }
  if ($group_type === 'learning_path') {
    $steps = [
      1 => $description_link,
      2 => $lp_manager_link,
      3 => $modules_link,
      4 => $activities_link,
      5 => $members_link,
    ];
    $user = \Drupal::currentUser();

    /** @var \Drupal\group\Entity\Group $group */
    $group = $route
      ->getParameter('group');
    if (isset($group) && !$group
      ->access('update', $user)) {
      unset($steps[1]);
      unset($steps[2]);
      unset($steps[3]);
      unset($steps[4]);
    }
    if (isset($group) && !$group
      ->access('administer members', $user)) {
      unset($steps[5]);
    }
  }
  elseif ($group_type == 'opigno_course') {
    $steps = [
      1 => $description_link,
      2 => $modules_link,
      3 => $activities_link,
    ];
  }
  elseif ($group_type == 'opigno_module') {
    $steps = [
      1 => $description_link,
      2 => $activities_link,
      3 => $activities_bank_link,
    ];
  }
  elseif ($group_type == 'opigno_class') {
    $steps = [
      1 => $description_link,
      2 => $members_link,
    ];
  }
  $current_step = opigno_learning_path_get_current_step();
  if (isset($steps[$current_step])) {
    $steps[$current_step]['#wrapper_attributes']['class'][] = 'active';
  }
  return [
    '#type' => 'container',
    'home_link' => $home_link,
    [
      '#type' => 'container',
      '#attributes' => [
        'class' => [
          'step-list-aside-wrapper',
        ],
      ],
      [
        '#type' => 'html_tag',
        '#tag' => 'h2',
        '#value' => t('Manage'),
      ],
      [
        '#theme' => 'item_list',
        '#list_type' => 'ul',
        '#items' => $steps,
        '#attributes' => [
          'class' => 'list-unstyled step-list-aside',
        ],
      ],
    ],
  ];
}

/**
 * Returns routes steps.
 */
function opigno_learning_path_get_routes_steps() {
  $type = opigno_learning_path_get_group_type();
  $steps = [];
  if ($type == 'learning_path') {
    $steps = [
      'entity.group.add_form' => 1,
      'entity.group.edit_form' => 1,
      'opigno_group_manager.manager.index' => 2,
      'opigno_learning_path.learning_path_courses' => 3,
      'opigno_learning_path.learning_path_modules' => 4,
      'opigno_learning_path.membership.overview' => 5,
      'entity.group_content.add_form' => 5,
    ];
  }
  elseif ($type == 'opigno_course') {
    $steps = [
      'entity.group.add_form' => 1,
      'entity.group.edit_form' => 1,
      'opigno_learning_path.learning_path_courses' => 2,
      'opigno_learning_path.learning_path_modules' => 3,
    ];
  }
  elseif ($type == 'opigno_module') {
    $steps = [
      'opigno_module.add' => 1,
      'opigno_module.edit' => 1,
      'opigno_module.modules' => 2,
      'opigno_module.activities_bank' => 3,
    ];
  }
  elseif ($type == 'opigno_class') {
    $steps = [
      'entity.group.add_form' => 1,
      'entity.group.edit_form' => 1,
      'opigno_learning_path.membership.overview' => 2,
      'entity.group_content.add_form' => 2,
    ];
  }
  return $steps;
}

/**
 * Returns group type.
 */
function opigno_learning_path_get_group_type() {
  $type = NULL;
  $route = \Drupal::routeMatch();
  $route_name = $route
    ->getRouteName();
  $parameters = $route
    ->getParameters();
  if ($parameters
    ->has('group_type')) {
    $type = $route
      ->getParameter('group_type')
      ->id();
  }
  elseif ($parameters
    ->has('group')) {
    $type = $route
      ->getParameter('group')
      ->get('type')
      ->getString();
  }
  elseif ($parameters
    ->has('opigno_module') || $route_name === 'opigno_module.add') {
    $type = 'opigno_module';
  }
  return $type;
}

/**
 * Returns current step.
 */
function opigno_learning_path_get_current_step() {
  $steps = opigno_learning_path_get_routes_steps();
  return isset($steps[\Drupal::routeMatch()
    ->getRouteName()]) ? $steps[\Drupal::routeMatch()
    ->getRouteName()] : NULL;
}

/**
 * Returns LP route flag.
 */
function opigno_learning_path_is_lp_route() {
  $route = \Drupal::routeMatch()
    ->getRouteName();
  $is_learning_path = FALSE;
  $routes = [
    'opigno_group_manager.manager.index',
    'opigno_learning_path.learning_path_modules',
    'opigno_learning_path.learning_path_courses',
    'opigno_learning_path.membership.overview',
    'entity.group_content.add_form',
    'opigno_module.add',
    'opigno_module.edit',
    'opigno_module.modules',
    'opigno_module.activities_bank',
  ];
  if ($route == 'entity.group.add_form' && (\Drupal::routeMatch()
    ->getParameter('group_type')
    ->id() == 'opigno_course' || \Drupal::routeMatch()
    ->getParameter('group_type')
    ->id() == 'learning_path' || \Drupal::routeMatch()
    ->getParameter('group_type')
    ->id() == 'opigno_class')) {
    $is_learning_path = TRUE;
  }
  if ($route == 'entity.group.edit_form' && (\Drupal::routeMatch()
    ->getParameter('group')
    ->get('type')
    ->getString() == 'opigno_course' || \Drupal::routeMatch()
    ->getParameter('group')
    ->get('type')
    ->getString() == 'learning_path' || \Drupal::routeMatch()
    ->getParameter('group')
    ->get('type')
    ->getString() == 'opigno_class')) {
    $is_learning_path = TRUE;
  }
  if (in_array($route, $routes)) {
    $is_learning_path = TRUE;
  }
  return $is_learning_path;
}

/**
 * Implements hook_help().
 */
function opigno_learning_path_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {

    // Main module help for the opigno_learning_path module.
    case 'help.page.opigno_learning_path':
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('Add the Learning Path feature to the opigno instance') . '</p>';
      return $output;
    default:
      return '';
  }
}

/**
 * Implements hook_form_alter().
 */
function opigno_learning_path_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  $build_info = $form_state
    ->getBuildInfo();
  $route_name = \Drupal::routeMatch()
    ->getRouteName();
  if ($form_id == 'opigno_module_edit_form' || $form_id == 'opigno_module_add_form' || $form_id == 'opigno_module_form') {
    $form['created']['#access'] = FALSE;
    $form['uid']['#access'] = FALSE;
  }

  // Check if there is an entry with the key "learning_path_id".
  $is_learning_path = FALSE;
  foreach ($build_info['args'] as $info_value) {
    if (is_array($info_value) && array_key_exists('learning_path_info', $info_value)) {
      $is_learning_path = TRUE;
      break;
    }
  }

  // If the form is from the learning_path_manager and has an entity,
  // ajaxify it.
  if ($is_learning_path && method_exists($build_info['callback_object'], 'getEntity')) {

    // Get the entity.
    $entity = $build_info['callback_object']
      ->getEntity();
    $entity_type = $entity
      ->getEntityTypeId();
    $bundle = $entity
      ->bundle();
    $id = $entity
      ->id();

    // Add form class for ajaxification. In case of add form,
    // append "new" instead of the entity ID.
    if ($id) {
      $ajax_id = 'ajax-form-entity-' . $entity_type . '-' . $bundle . '-' . $id;
    }
    else {
      $ajax_id = 'ajax-form-entity-' . $entity_type . '-' . $bundle . '-new';
    }
    $form['#attributes']['class'][] = $ajax_id;

    // Ajaxification settings of the buttons.
    $ajax_settings = [
      'callback' => 'Drupal\\opigno_learning_path\\Controller\\LearningPathManagerController::ajaxFormEntityCallback',
      'wrapper' => $ajax_id,
      'effect' => 'fade',
    ];
    $form['#attached']['library'][] = 'opigno_learning_path/ajax_form';
    $form['actions']['submit']['#ajax'] = $ajax_settings;
    $form['actions']['publish']['#ajax'] = $ajax_settings;
    $form['actions']['unpublish']['#ajax'] = $ajax_settings;
    $form['actions']['preview']['#access'] = FALSE;
    unset($form['actions']['publish']['#dropbutton']);
    unset($form['actions']['unpublish']['#dropbutton']);

    // Ajaxify the buttons.
    foreach (array_keys($form['actions']) as $action) {
      if ($action != 'preview' && isset($form['actions'][$action]['#type']) && $form['actions'][$action]['#type'] === 'submit') {
        $form['actions'][$action]['#submit'][] = 'Drupal\\opigno_learning_path\\Controller\\LearningPathManagerController::ajaxFormEntityFormSubmit';
      }
    }

    // Handle case of entity edition : define the options.
    if ($id) {
      $current_path = \Drupal::service('path.current')
        ->getPath();
      $path_args = explode('/', $current_path);

      // Case of edit link.
      if ($path_args[1] == 'ajax-form-entity') {
        $view_mode = $path_args[5];
        $reload = FALSE;
      }
      else {
        $view_mode = 'default';
        $reload = 'reload_entity';
      }
    }
    else {
      $view_mode = 'default';
      $reload = TRUE;
    }

    // Add all configurations to the form to make it available everywhere.
    $form['ajax_form_entity'] = [
      '#type' => 'hidden',
      '#value' => [
        'view_mode' => $view_mode,
        'reload' => $reload,
        'content_selector' => '.' . $ajax_id,
        'form_selector' => '.' . $ajax_id,
      ],
    ];
  }

  // Set fields visibility dependencies.
  if (in_array($form_id, [
    'group_learning_path_add_form',
    'group_learning_path_edit_form',
  ])) {
    $form['field_requires_validation']['#states'] = [
      'visible' => [
        ':input[name="field_learning_path_visibility"]' => [
          'value' => 'semiprivate',
        ],
      ],
    ];
    $form['field_anonymous_visibility']['#states'] = [
      'visible' => [
        ':input[name="field_learning_path_visibility"]' => [
          'value' => 'semiprivate',
        ],
      ],
    ];
    $form['field_certificate_expire_results']['#states'] = [
      'visible' => [
        ':input[name="field_certificate_expire[value]"]' => [
          'checked' => TRUE,
        ],
      ],
    ];
    unset($form["field_certificate_expire_results"]["widget"]["#options"]["_none"]);

    // Change the submit button title.
    $form['actions']['submit']['#value'] = t('Next');

    // Add redirect to the step 2.
    $form['actions']['submit']['#submit'][] = 'opigno_learning_path_add_form_redirect';

    // Unpublish existing training.
    if ($route_name == 'entity.group.edit_form') {
      $group = \Drupal::routeMatch()
        ->getParameter('group');
      $state = $group
        ->get('field_learning_path_published')->value;
      if ($state == 0) {
        $title = t('Publish');
        $route = 'opigno_learning_path.manager.publish';
      }
      else {
        $title = t('Unpublish');
        $route = 'opigno_learning_path.manager.unpublish';
      }
      $form['actions']['submit']['unpublish'] = [
        '#type' => 'link',
        '#title' => $title,
        '#url' => Url::fromRoute($route, [
          'group' => $group
            ->id(),
        ]),
        '#attributes' => [
          'class' => [
            'button',
          ],
        ],
        '#weight' => 2,
      ];
    }

    // Remove field if opigno_commerce module is disabled.
    if (isset($form['field_lp_price'])) {
      $moduleHandler = \Drupal::service('module_handler');
      if (!$moduleHandler
        ->moduleExists('opigno_commerce')) {
        unset($form['field_lp_price']);
      }
    }

    // Add validation for field.
    if (isset($form['field_required_trainings'])) {
      $form['field_required_trainings']['#element_validate'] = [
        'opigno_learning_path_field_required_trainings_validate',
      ];
    }
  }

  // Set title for learning path step 1.
  if ($form_id === 'group_learning_path_add_form') {
    $form['#title'] = t('Create training');
  }

  // Add custom submit callback to join/leave group form.
  if (in_array($form_id, [
    'group_content_learning_path-group_membership_group-join_form',
    'group_content_learning_path-group_membership_group-leave_form',
  ])) {
    $group = \Drupal::routeMatch()
      ->getParameter('group');
    if ($form_id == 'group_content_learning_path-group_membership_group-join_form') {
      $visibility = $group->field_learning_path_visibility->value;

      // Check if we need to wait validation.
      $validation = LearningPathAccess::requiredValidation($group);
      if ($visibility === 'semiprivate' && $validation) {

        // User message.
        $form['user_message'] = [
          '#type' => 'textarea',
          '#title' => t('Request message'),
          '#description' => t('You can enter 200 symbols.'),
          '#attributes' => [
            'maxlength' => 200,
          ],
        ];
      }
    }

    // Save group object for submit function.
    $form_state
      ->setFormState([
      'group' => $group,
    ]);
    foreach (array_keys($form['actions']) as $action) {
      if ($action != 'preview' && isset($form['actions'][$action]['#type']) && $form['actions'][$action]['#type'] === 'submit') {

        // Unset path alias field.
        unset($form['path']);

        // Add custom submit to user join group form.
        $form['actions'][$action]['#submit'][] = 'opigno_learning_path_group_membership_form_submit';

        // Change submit button text.
        $form['actions'][$action]['#value'] = t('Join training');
      }
    }

    // Cancel link.
    unset($form['actions']['cancel']);
    $url = Url::fromRoute('entity.group.canonical', [
      'group' => $group
        ->id(),
    ], [
      'attributes' => [
        'class' => 'button',
      ],
    ]);
    $link = Link::fromTextAndUrl(t('Cancel'), $url)
      ->toRenderable();
    $form['actions']['submit']['#suffix'] = render($link);
  }
  if ($form_id === 'group_content_learning_path-group_membership_edit_form') {

    /** @var \Drupal\Core\Entity\ContentEntityFormInterface $form_object */
    $form_object = $form_state
      ->getFormObject();

    /** @var \Drupal\group\Entity\GroupContentInterface $membership */
    $membership = $form_object
      ->getEntity();
    if ($membership) {
      $status = LearningPathAccess::getMembershipStatus($membership
        ->id());
      $access = $membership
        ->getGroup()
        ->hasPermission('administer members', \Drupal::currentUser());
    }
    $form['user_status'] = [
      '#type' => 'radios',
      '#title' => t('Status'),
      '#options' => LearningPathAccess::getMembershipStatusesArray(),
      '#required' => TRUE,
      '#default_value' => !empty($status) ? $status : '',
      '#weight' => $form['group_roles']['#weight'] - 1,
      '#access' => !empty($access),
    ];
    unset($_SESSION['opigno_learning_path_group_membership_edit']);
    $form['#validate'][] = '_opigno_learning_path_group_membership_edit_form_validate';
  }
  if (in_array($form_id, [
    'group_content_learning_path-group_membership_add_form',
    'group_content_opigno_class-group_membership_add_form',
  ])) {
    $is_class = $form_id == 'group_content_opigno_class-group_membership_add_form';
    unset($form['entity_id']);
    unset($form['path']);
    unset($form['actions']);
    unset($form['#validate']);
    unset($form['#submit']);
    if (!$is_class) {
      unset($form['group_roles']);
    }

    // Get the current learning path group.

    /** @var \Drupal\group\Entity\Group $group */
    $group = \Drupal::routeMatch()
      ->getParameter('group');
    $form['#title'] = $is_class ? t('Members in class') : t('Members in learning path');
    $form['btn_create'] = Link::createFromRoute(t('Create new users'), 'opigno_learning_path.membership.create_member', [
      'group' => $group
        ->id(),
    ])
      ->toRenderable();
    $form['btn_create']['#attributes']['class'][] = 'use-ajax';
    $form['btn_create']['#attributes']['class'][] = 'btn';
    $form['btn_create']['#attributes']['class'][] = 'btn-success';
    $form['btn_create']['#attributes']['data-dialog-type'] = 'modal';
    $form['btn_create']['#attributes']['data-dialog-options'] = json_encode([
      'dialogClass' => 'modal-dialog-sidebar',
    ]);
    $form['training_users_autocomplete'] = [
      '#type' => 'textfield',
      '#title' => $is_class ? t('Find existing users') : t('Find existing users or groups'),
      '#autocomplete_route_name' => 'opigno_learning_path.membership.add_user_to_training_autocomplete',
      '#autocomplete_route_parameters' => [
        'group' => $group
          ->id(),
      ],
      '#placeholder' => t('Enter a user’s name or email'),
      '#attributes' => [
        'id' => 'training_users_autocomplete',
      ],
    ];
    $form['training_users'] = [
      '#type' => 'multiselect',
      '#attributes' => [
        'id' => 'training_users',
        'class' => [
          'row',
        ],
      ],
      '#options' => [],
      // Allow modifying option with AJAX.
      '#validated' => TRUE,
      // Fixes multiselect issue 2852654.
      '#process' => [
        [
          'Drupal\\multiselect\\Element\\MultiSelect',
          'processSelect',
        ],
      ],
    ];
    if ($is_class) {
      $form['group_roles_fieldset'] = [
        '#type' => 'fieldset',
      ];
      $form["group_roles"]["#group"] = 'group_roles_fieldset';
    }
    $form['send_message'] = [
      '#type' => 'checkbox',
      '#title' => t('Notify user ?'),
    ];
    $form['message'] = [
      '#type' => 'textarea',
      '#placeholder' => $is_class ? t('You were added to "@group" class', [
        '@group' => $group
          ->label(),
      ]) : t('I invite you to take part in my learning program to boost your skills'),
    ];
    $form['actions']['submit'] = [
      '#type' => 'submit',
      '#value' => t('Send invitation'),
    ];
    $form['#attached']['library'][] = 'core/drupal.dialog.ajax';
    $form['#attached']['library'][] = 'opigno_learning_path/member_add';
    $form['#attached']['library'][] = 'opigno_learning_path/create_member';
    $form['#attached']['drupalSettings']['opigno_learning_path']['gid'] = $group
      ->id();
    $form['#validate'] = [];
    $form['#submit'] = [
      'opigno_learning_path_group_membership_add_form_submit',
    ];
  }
  if (in_array($form_id, [
    'entity_browser_media_entity_browser_badge_images_form',
    'entity_browser_media_entity_browser_groups_form',
    'entity_browser_media_entity_browser_file_pdf_form',
  ])) {
    $form["#attached"]['library'][] = 'opigno_learning_path/opigno_dropzonejs_widgets';
  }
  if (in_array($form_id, [
    'opigno_moxtra_score_meeting_form',
    'opigno_ilt_create_result_form',
    'opigno_ilt_score_form',
    'opigno_moxtra_create_meeting_result_form',
  ])) {

    // Attach addition styles for entity forms in embedded html page.
    $form["#attached"]['library'][] = 'opigno_learning_path/iframe_opigno_lp_manager';
  }
}

/**
 * Additional custom validation for LP membership edit form.
 */
function _opigno_learning_path_group_membership_edit_form_validate(array &$form, FormStateInterface $form_state) {
  $values = $form_state
    ->getValues();
  $_SESSION['opigno_learning_path_group_membership_edit']['user_status'] = $values['user_status'];
}

/**
 * Custom validation for field_required_training.
 */
function opigno_learning_path_field_required_trainings_validate(&$element, FormStateInterface $form_state, $form) {
  $required_trainings = $form_state
    ->getValue('field_required_trainings')['target_id'];
  if ($required_trainings == NULL || empty($required_trainings)) {
    return;
  }

  // Get trainings id.
  $tids = [];
  foreach ($required_trainings as $item) {
    array_push($tids, $item['target_id']);
  }

  /* @var Drupal\Group\Entity\Group $group */
  $group = $form_state
    ->getFormObject()
    ->getEntity();
  if (in_array($group
    ->id(), $tids)) {
    $form_state
      ->setErrorByName('field_required_trainings', t('You can set in required trainings list current training.'));
  }
}

/**
 * Implements hook_field_widget_form_alter().
 */
function opigno_learning_path_field_widget_form_alter(&$element, FormStateInterface $form_state, $context) {
  if ($context['widget'] instanceof EntityReferenceBrowserWidget) {

    // Change field type for widget.
    $element['#type'] = 'fieldgroup';
  }
}

/**
 * Adds redirect after creating new learning path.
 */
function opigno_learning_path_add_form_redirect(array $form, FormStateInterface $form_state) {
  $group = $form_state
    ->getFormObject()
    ->getEntity();

  // If a learning path is created, redirect to the step 2.
  if (isset($group)) {
    $form_state
      ->setRedirect('opigno_group_manager.manager.index', [
      'group' => $group
        ->id(),
    ]);
  }
}

/**
 * Adds redirect after user join group.
 *
 * Clear cache if user join/leave group by himself.
 */
function opigno_learning_path_group_membership_form_submit(array $form, FormStateInterface $form_state) {
  if ($group = $form_state
    ->get('group')) {
    $form_state
      ->setRedirect('entity.group.canonical', [
      'group' => $group
        ->id(),
    ]);
  }
}

/**
 * Callback used in opigno_learning_path_form_alter().
 */
function opigno_learning_path_group_membership_add_form_submit(array $form, FormStateInterface $form_state) {
  $options = $form_state
    ->getValue('training_users');
  $send_message = $form_state
    ->getValue('send_message');
  $message = $form_state
    ->getValue('message');

  // Load added users & classes from the form_state.
  $users = [];
  $classes = [];
  foreach ($options as $option) {
    [
      $type,
      $id,
    ] = explode('_', $option);
    if ($type === 'user') {
      $users[] = $id;
    }
    elseif ($type === 'class') {
      $classes[] = $id;
    }
  }
  $users = User::loadMultiple($users);
  $classes = Group::loadMultiple($classes);
  $need_clear_cache = count($classes) > 1;

  // Load the learning path group.

  /** @var \Drupal\group\Entity\Group $group */
  $group = \Drupal::routeMatch()
    ->getParameter('group');
  $is_class = $group
    ->getGroupType()
    ->id() == 'opigno_class';

  // Assign each class to the learning path.
  foreach ($classes as $class) {
    $db_connection = \Drupal::service('database');
    $is_class_added = $db_connection
      ->select('group_content_field_data', 'g_c_f_d')
      ->fields('g_c_f_d', [
      'id',
    ])
      ->condition('gid', $group
      ->id())
      ->condition('entity_id', $class
      ->id())
      ->condition('type', 'group_content_type_27efa0097d858')
      ->execute()
      ->fetchField();
    if (!$is_class_added) {

      /** @var \Drupal\group\Entity\Group $class */
      $group
        ->addContent($class, 'subgroup:opigno_class');

      // Rebuild module and theme data to escape cache warnings when add more then one class
      if ($need_clear_cache) {
        $module_data = \Drupal::service('extension.list.module')
          ->reset()
          ->getList();
        $files = [];
        $modules = [];
        foreach ($module_data as $name => $extension) {
          if ($extension->status) {
            $files[$name] = $extension;
            $modules[$name] = $extension->weight;
          }
        }
        $modules = module_config_sort($modules);
        \Drupal::service('kernel')
          ->updateModules($modules, $files);
        $module_handler = \Drupal::moduleHandler();
        $module_handler
          ->loadAll();
        $module_handler
          ->invokeAll('rebuild');
      }

      // Add class members to the users.
      $members = $class
        ->getMembers();
      foreach ($members as $member) {

        /** @var \Drupal\group\GroupMembership $member */
        $user = $member
          ->getUser();
        $users[$user
          ->id()] = $user;
      }
    }
  }

  // Add each user to the learning path & send email.
  foreach ($users as $user) {
    if (!isset($user)) {
      continue;
    }

    // If user is already in learning path, do nothing.
    $existing = $group
      ->getMember($user);
    if ($existing !== FALSE) {
      continue;
    }

    // Add user to the learning path.

    /** @var \Drupal\user\Entity\User $user */
    $group
      ->addMember($user);
    if ($is_class) {
      $group_roles = $form_state
        ->getValue('group_roles');

      // Set new member roles.
      if ($group_roles) {
        if ($member = $group
          ->getMember($user)) {
          $group_content = $member
            ->getGroupContent();
          $group_roles_new = $group_content
            ->get('group_roles')
            ->getValue();
          foreach ($group_roles as $group_role) {
            $found = FALSE;
            $role = $group_role['target_id'];
            foreach ($group_roles_new as $index => $value) {
              if ($value['target_id'] === $role) {
                $found = TRUE;
                unset($group_roles_new[$index]);
                break;
              }
            }
            if ($found === FALSE) {
              $group_roles_new[] = [
                'target_id' => $role,
              ];
            }
          }
          try {
            $group_content
              ->set('group_roles', $group_roles_new);
            $group_content
              ->save();
          } catch (\Exception $e) {
            \Drupal::logger('opigno_learning_path')
              ->error($e
              ->getMessage());
            \Drupal::messenger()
              ->addMessage($e
              ->getMessage(), 'error');
          }
        }
      }
    }
    if ($send_message) {

      // Send email.
      $module = 'opigno_learning_path';
      $key = 'opigno_learning_path_add_membership';
      $email = $user
        ->getEmail();
      $lang = $user
        ->getPreferredLangcode();
      $params = [];
      $params['subject'] = t('Opigno notification');
      $params['message'] = $message ? $message : t('I invite you to take part in my learning program to boost your skills');
      \Drupal::service('plugin.manager.mail')
        ->mail($module, $key, $email, $lang, $params);
    }
  }
  $form_state
    ->setRedirect('opigno_learning_path.membership.overview', [
    'group' => $group
      ->id(),
  ]);
}

/**
 * Sets additional fields for group visibility.
 *
 * Implements hook_ENTITY_TYPE_presave().
 */
function opigno_learning_path_group_presave(Group $group) {
  $type = $group->type->entity
    ->id();
  if ($type == 'learning_path') {
    LearningPathAccess::setVisibilityFields($group);
  }
}

/**
 * Restricts user access to Learning Path and it's content.
 *
 * Implements hook_ENTITY_TYPE_access().
 */
function opigno_learning_path_group_access(Group $group, $operation, AccountInterface $account) {
  $opigno_types = [
    'learning_path',
    'opigno_course',
    'opigno_class',
  ];
  $group_type = $group
    ->bundle();
  if (!in_array($group_type, $opigno_types)) {
    return AccessResult::neutral();
  }
  $is_platform_cm = $account
    ->hasPermission('manage group content in any group');
  $is_platform_um = $account
    ->hasPermission('manage group members in any group');
  $group_visibility = NULL;
  if ($group
    ->hasField('field_learning_path_visibility')) {
    $group_visibility = $group
      ->get('field_learning_path_visibility')->value;
  }
  switch ($operation) {
    case 'join':

      // This is currently only used for the entity.group.join route but may be
      // applied to other cases such as join/signup links, so as well as the
      // visibility check, additional check the 'join group' permission which is
      // already on the route.
      if ($account
        ->isAnonymous() && $group_visibility == 'semiprivate') {
        return Accessresult::forbidden();
      }

      // Restrict access to any users to join to private Training.
      if ($group_visibility == 'private') {
        return Accessresult::forbidden();
      }
      if ($group
        ->hasPermission('join group', $account)) {
        return AccessResult::allowed();
      }
      break;
    case 'view':
    case 'view teaser':
    case 'take':
      if ($is_platform_cm || $is_platform_um) {

        // Allow platform-level managers to view any group.
        return AccessResult::allowed();
      }
      $group_owner_id = (int) $group
        ->getOwnerId();
      $account_id = (int) $account
        ->id();
      $is_owner = $group_owner_id === $account_id;
      if ($is_owner) {

        // Allow view own groups.
        return AccessResult::allowed();
      }
      if ($group
        ->hasField('field_learning_path_published')) {
        $is_published = $group->field_learning_path_published->value;
        if (!$is_published) {
          $training_role_allowed = FALSE;
          if ($member = $group
            ->getMember($account)) {
            $roles = $member
              ->getRoles();
            foreach ([
              'learning_path-user_manager',
              'learning_path-content_manager',
            ] as $role) {
              if (array_key_exists($role, $roles)) {
                $training_role_allowed = TRUE;
                break;
              }
            }
          }
          if (!$training_role_allowed) {

            // Deny if group unpublished and user has no admin or manager roles.
            return AccessResult::forbidden();
          }
        }
      }
      switch ($group_type) {
        case 'learning_path':
          $membership = $group
            ->getMember($account);
          $is_member = $membership !== FALSE;

          // Deny if there are unfinished required trainings.
          // Don't block training admins and group managers.
          if ($operation == 'take' && (!$group
            ->hasPermission('administer members', $account) || !$group
            ->hasPermission('edit group', $account))) {

            // List of required trainings that should to be finished by user
            // before he can start a current training.
            $uncompleted = LearningPathAccess::hasUncompletedRequiredTrainings($group, $account);
            if (!empty($uncompleted)) {
              return AccessResult::forbidden();
            }
          }
          switch ($group_visibility) {
            case 'public':

              // Restrict access to public training with price for Anonymous.
              if ($account
                ->isAnonymous() && $group
                ->hasField('field_lp_price') && $group
                ->get('field_lp_price')->value) {
                return AccessResult::forbidden();
              }
              return AccessResult::allowed();
            case 'semiprivate':
              $group_hide_for_anonymous = $group->field_anonymous_visibility->value;
              $is_anonymous = $account
                ->isAnonymous();
              if ($group_hide_for_anonymous && $is_anonymous) {

                // Deny access if group is hidden for anonymous users
                // and current user is an anonymous.
                return AccessResult::forbidden();
              }
              if ($operation == 'take' && (!$is_member || $is_anonymous)) {
                return AccessResult::forbidden();
              }

              // Check if we need to wait validation.
              $is_valid = LearningPathAccess::statusGroupValidation($group, $account);
              if ($operation == 'take' && !$is_valid) {

                // Deny access if user is a blocked or non-activated member
                // of current group, viewing not a group homepage
                // and group requires validation.
                return AccessResult::forbidden();
              }
              return AccessResult::allowed();
            case 'private':
              $is_valid = $is_member && LearningPathAccess::statusGroupValidation($group, $account);
              return $is_valid ? AccessResult::allowed() : AccessResult::forbidden();
          }
          break;
        case 'opigno_course':
        case 'opigno_class':
          $is_valid = LearningPathAccess::checkCourseClassAccess($group, $account);
          return $is_valid ? AccessResult::allowed() : AccessResult::forbidden();
      }
      break;
    case 'update':
      $is_group_cm = $group
        ->hasPermission('edit group', $account);
      switch ($group_type) {
        case 'opigno_class':

          // Allow user managers to edit class.
          return $is_platform_um || $is_group_cm ? AccessResult::allowed() : AccessResult::forbidden();
      }

      // Allow content managers to edit group.
      return $is_platform_cm || $is_group_cm ? AccessResult::allowed() : AccessResult::forbidden();
    case 'delete':
      $is_group_cm = $group
        ->hasPermission('delete group', $account);
      switch ($group_type) {
        case 'opigno_class':

          // Allow user managers to delete class.
          return $is_platform_um || $is_group_cm ? AccessResult::allowed() : AccessResult::forbidden();
      }

      // Allow content managers to delete group.
      return $is_platform_cm || $is_group_cm ? AccessResult::allowed() : AccessResult::forbidden();
    case 'administer members':
      $is_group_um = $group
        ->hasPermission('administer members', $account);

      // Allow user managers to manage members.
      return $is_platform_um || $is_group_um ? AccessResult::allowed() : AccessResult::forbidden();
    case 'view certificate':
      $completed = opigno_learning_path_completed_on($group
        ->id(), $account
        ->id(), TRUE);
      return $completed ? AccessResult::allowed() : AccessResult::forbidden();
  }
  return AccessResult::neutral();
}

/**
 * Restricts user access to Learning Path content.
 *
 * Implements hook_ENTITY_TYPE_access().
 */
function opigno_learning_path_opigno_module_access(EntityInterface $entity, $operation, AccountInterface $account) {
  if ($account
    ->hasPermission('manage group content in any group')) {

    // Allow platform-level content managers
    // to view/edit/delete learning path modules.
    return AccessResult::allowed();
  }
  if (!LearningPathAccess::getGroupContentAccess($entity, $account)) {
    return AccessResult::forbidden();
  }
  return AccessResult::neutral();
}

/**
 * Implements hook_ENTITY_TYPE_access().
 */
function opigno_learning_path_group_content_access(EntityInterface $entity, $operation, AccountInterface $account) {
  $bundle = $entity
    ->bundle();
  if (in_array($bundle, [
    'opigno_class-group_membership',
    'opigno_course-group_membership',
    'learning_path-group_membership',
  ]) && $account
    ->hasPermission('manage group members in any group')) {

    // Allow platform-level user managers
    // to view/edit/delete user group membership.
    return AccessResult::allowed();
  }
  return AccessResult::neutral();
}

/**
 * Implements hook_ENTITY_TYPE_access().
 */
function opigno_learning_path_user_access(EntityInterface $entity, $operation, AccountInterface $account) {
  if ($account
    ->hasPermission('manage group members in any group')) {

    // Allow platform-level user managers to view/edit/delete users.
    return AccessResult::allowed();
  }
  return AccessResult::neutral();
}

/**
 * Implements hook_ENTITY_TYPE_create_access().
 */
function opigno_learning_path_group_content_create_access(AccountInterface $account, array $context, $entity_bundle) {
  if (in_array($entity_bundle, [
    'opigno_class-group_membership',
    'opigno_course-group_membership',
    'learning_path-group_membership',
  ]) && $account
    ->hasPermission('manage group members in any group')) {

    // Allow platform-level user managers to create new user group membership.
    return AccessResult::allowed();
  }
  return AccessResult::neutral();
}

/**
 * Implements hook_ENTITY_TYPE_create_access().
 */
function opigno_learning_path_user_create_access(AccountInterface $account, array $context, $entity_bundle) {
  if ($account
    ->hasPermission('manage group members in any group')) {

    // Allow platform-level user managers to create new users.
    return AccessResult::allowed();
  }
  return AccessResult::neutral();
}

/**
 * Implements hook_entity_update().
 */
function opigno_learning_path_entity_update(EntityInterface $entity) {
  if ($entity
    ->bundle() == 'learning_path-group_membership') {
    LearningPathAccess::mergeUserStatus($entity);
  }
  if ($entity
    ->bundle() == 'learning_path') {
    $db_connection = \Drupal::service('database');
    try {
      $db_connection
        ->update('opigno_learning_path_achievements')
        ->fields([
        'name' => $entity
          ->label(),
      ])
        ->condition('gid', $entity
        ->id())
        ->execute();
    } catch (\Exception $e) {
      \Drupal::logger('opigno_learning_path')
        ->error($e
        ->getMessage());
      \Drupal::messenger()
        ->addMessage($e
        ->getMessage(), 'error');
    }
  }
  if ($entity
    ->getEntityTypeId() === 'user_module_status') {

    // Try to get training id from path.
    $route = \Drupal::routeMatch();

    /* @var \Drupal\group\Entity\Group $group */
    $group = $route
      ->getParameter('group');
    if (!empty($group) && $group instanceof Group && $group
      ->getGroupType() == 'learning_path') {
      $group_id = $group
        ->id();
    }
    else {

      // Try to get training id from context.
      $group_id = OpignoGroupContext::getCurrentGroupId();
    }
    if ($group_id) {

      /** @var \Drupal\opigno_module\Entity\UserModuleStatus $entity */

      /** @var \Drupal\opigno_module\Entity\OpignoModule $module */
      $module = $entity
        ->getModule();
      $module_id = $module
        ->id();
      $account = \Drupal::currentUser();

      // Update latest group activity.
      LatestActivity::insertGroupActivity($group_id, $module_id, $account
        ->id());
    }
  }
}

/**
 * Implements hook_entity_delete().
 */
function opigno_learning_path_entity_delete(EntityInterface $entity) {
  if (in_array($entity
    ->bundle(), [
    'learning_path-group_membership',
    'opigno_course-group_membership',
  ])) {
    LearningPathAccess::deleteUserStatus($entity);
    if ($entity
      ->bundle() == 'learning_path-group_membership') {
      LearningPathAccess::setLearningPathCourseMember($entity, 'delete');
    }
  }
  if ($entity
    ->bundle() == 'group_content_type_27efa0097d858') {

    // Get Class members.
    $members = FALSE;
    if ($entity && $entity
      ->getEntity()) {
      $members = $entity
        ->getEntity()
        ->getMembers();
    }

    // Get class group id.
    $gid = $entity
      ->getGroup()
      ->id();
    $classes_members = [];
    $classes_ids = LearningPathContent::getGroupMembershipIdsByType($gid, [
      'group_content_type_27efa0097d858',
    ]);
    if ($classes_ids) {
      foreach ($classes_ids as $id) {
        $class_members = LearningPathContent::getGroupMembershipIdsByType($id, [
          'opigno_class-group_membership',
        ]);
        if ($class_members) {
          $classes_members = array_merge($classes_members, $class_members);
        }
      }
      if ($classes_members) {
        $classes_members = array_unique($classes_members);
      }
    }
    if ($members && !empty($gid) && is_numeric($gid)) {
      try {
        $group = Group::load($gid);

        // Get class parent groups.
        foreach ($members as $member) {
          $user = $member
            ->getGroupContent()
            ->getEntity();
          $uid = $user
            ->id();
          $class_id = $entity
            ->getEntity()
            ->id();
          $created_group_membership = LearningPathContent::getGroupMembershipTimestamp($gid, $uid);
          $created_class_membership = LearningPathContent::getGroupMembershipTimestamp($class_id, $uid);
          if ($group && $user) {

            // If user is the member only in this class of training all classes.
            $unique_member = $classes_members ? !in_array($uid, $classes_members) : TRUE;

            // Get group and class membership timestamps.
            if ($unique_member && $created_group_membership && $created_class_membership && $created_group_membership >= $created_class_membership) {

              // Remove class parent group membership.
              $group
                ->removeMember($user);
            }
          }
        }
      } catch (\Exception $e) {
        \Drupal::logger('opigno_learning_path')
          ->error($e
          ->getMessage());
        \Drupal::messenger()
          ->addMessage($e
          ->getMessage(), 'error');
      }
    }
  }
  if ($entity
    ->bundle() == 'opigno_class-group_membership') {

    // Get class group id.
    $gid = $entity
      ->getGroup()
      ->id();
    if (!empty($gid) && is_numeric($gid)) {
      try {

        // Get class parent groups.
        $parent_groups = LearningPathContent::getClassGroups($gid);
        if ($parent_groups) {
          $user = $entity
            ->getEntity();
          foreach ($parent_groups as $parent) {
            $group = Group::load($parent->gid);
            if ($group && $user) {

              // Check if user is a member.
              if ($group
                ->getMember($user) !== FALSE) {

                // Get group and class membership timestamps.
                $created_group_membership = LearningPathContent::getGroupMembershipTimestamp($parent->gid, $user
                  ->id());
                $created_class_membership = $entity
                  ->getCreatedTime();
                if ($created_group_membership && $created_class_membership && $created_group_membership >= $created_class_membership) {

                  // Remove class parent group membership.
                  $group
                    ->removeMember($user);
                }
              }
            }
          }
        }
      } catch (\Exception $e) {
        \Drupal::logger('opigno_learning_path')
          ->error($e
          ->getMessage());
        \Drupal::messenger()
          ->addMessage($e
          ->getMessage(), 'error');
      }
    }
  }
  if ($entity
    ->bundle() == 'learning_path') {
    LPStatus::removeCertificateExpiration($entity
      ->id());
  }
}

/**
 * Returns student managers.
 */
function opigno_learning_path_get_student_managers($group) {
  $users = [];
  $owner = $group
    ->getOwner();
  $users[$owner
    ->id()] = $owner;
  $membership_loader = \Drupal::service('group.membership_loader');
  $student_managers = $membership_loader
    ->loadByGroup($group, [
    'learning_path-user_manager',
  ]);
  foreach ($student_managers as $membership) {
    $user = $membership
      ->getUser();
    if ($user) {
      $users[$user
        ->id()] = $user;
    }
  }
  return $users;
}

/**
 * Implements hook_entity_presave().
 */
function opigno_learning_path_entity_presave(EntityInterface $entity) {
  if ($entity
    ->bundle() == 'learning_path-group_membership') {

    /** @var \Drupal\group\Entity\GroupContentInterface $entity */
    LearningPathAccess::membershipPreSave($entity);
    $uid = $entity
      ->getEntity()
      ->id();
    $group = $entity
      ->getGroup();
    $gid = $group
      ->id();

    // Add notifications to user.
    if ($entity
      ->isNew()) {
      $message = t('Enrolled to a new training "@name"', [
        '@name' => $group
          ->label(),
      ]);
      $url = Url::fromRoute('entity.group.canonical', [
        'group' => $group
          ->id(),
      ])
        ->toString();
      opigno_set_message($uid, $message, $url);
      $user = User::load($uid);
      $visibility = $group->field_learning_path_visibility->value;

      // Check if we need to wait validation.
      $validation = LearningPathAccess::requiredValidation($group, $user);
      if ($visibility === 'semiprivate' && $validation) {

        // Send an email to the managers of group.
        $module = 'opigno_learning_path';
        $key = 'opigno_learning_path_membership_needs_validate';
        $site_config = \Drupal::config('system.site');
        $link = Url::fromUri('internal:/group/' . $gid . '/members')
          ->setAbsolute()
          ->toString();
        $receivers = opigno_learning_path_get_student_managers($group);
        foreach ($receivers as $uid => $receiver) {
          if ($user
            ->id() === $uid) {
            continue;
          }
          $email = $receiver
            ->getEmail();
          $lang = $receiver
            ->getPreferredLangcode();
          $params = [
            'group' => $group,
            'account' => $user,
          ];
          $params['subject'] = t('A user @username has requested to joining the group @training', [
            '@username' => $user
              ->getDisplayName(),
            '@training' => $group
              ->label(),
          ]);
          $args = [
            '@username' => $receiver
              ->getDisplayName(),
            '@request_username' => $user
              ->getDisplayName(),
            '@training' => $group
              ->label(),
            ':link' => $link,
            '@link_text' => $link,
            '@platform' => $site_config
              ->get('name'),
          ];
          $params['message'] = t('Dear @username

A user @request_username has requested to joining the group @training.
You can manage the membership at: <a href=":link">@link_text</a>

@platform', $args);
          \Drupal::service('plugin.manager.mail')
            ->mail($module, $key, $email, $lang, $params);
        }
      }
    }
  }
}

/**
 * Implements hook_mail().
 */
function opigno_learning_path_mail($key, &$message, $params) {
  if (in_array($key, [
    'opigno_learning_path_user_subscribe',
    'opigno_learning_path_add_membership',
    'opigno_learning_path_membership_needs_validate',
    'opigno_learning_path_membership_validated',
    'training_expiring_certification',
    'opigno_learning_path_managers_notify',
    'opigno_learning_path_user_notify',
  ])) {
    $message['from'] = \Drupal::config('system.site')
      ->get('mail');
    $message['subject'] = $params['subject'];
    $message['body'][] = $params['message'];
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function opigno_learning_path_form_opigno_module_delete_form_alter(&$form, FormStateInterface $form_state, $form_id) {

  // Get the module entity object.
  $build_info = $form_state
    ->getBuildInfo();
  $module = $build_info['callback_object']
    ->getEntity();

  // Group content storage.
  $group_content_storage = Drupal::entityTypeManager()
    ->getStorage('group_content');
  $query = $group_content_storage
    ->getQuery();

  // Check if module related to at least one group.
  $gid = $query
    ->condition('entity_id', $module
    ->id())
    ->condition('type', [
    'group_content_type_162f6c7e7c4fa',
    'group_content_type_411cfb95b8271',
  ], 'IN')
    ->execute();

  // If it's exist as group content hide the delete button.
  if (!empty($gid)) {
    $form['description'] = [
      '#markup' => t('This module is being used and it needs to be removed from the trainings and/or courses using it before being able to delete it.'),
    ];
    unset($form['actions']['submit']);
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function opigno_learning_path_form_group_opigno_course_delete_form_alter(&$form, FormStateInterface $form_state, $form_id) {

  // Get the course entity object.
  $build_info = $form_state
    ->getBuildInfo();
  $course = $build_info['callback_object']
    ->getEntity();

  // Group content storage.
  $group_content_storage = Drupal::entityTypeManager()
    ->getStorage('group_content');
  $query = $group_content_storage
    ->getQuery();

  // Check if course related to at least one group.
  $gid = $query
    ->condition('entity_id', $course
    ->id())
    ->condition('type', 'group_content_type_af9d804582e19')
    ->execute();

  // If it's exist as group content hide the delete button.
  if (!empty($gid)) {
    $form['description'] = [
      '#markup' => t('This course is being used and it needs to be removed from the trainings using it before being able to delete it.'),
    ];
    unset($form['actions']['submit']);
  }
}

/**
 * Implements hook_views_query_alter().
 */
function opigno_learning_path_views_query_alter(ViewExecutable $view, QueryPluginBase $query) {

  // Hide learning paths from catalog
  // for anonymous depending on the field value.
  if ($view
    ->id() == 'opigno_training_catalog' && Drupal::currentUser()
    ->isAnonymous()) {

    // Join the anonymous field table.
    $definition = [
      'table' => 'group__field_anonymous_visibility',
      'field' => 'entity_id',
      'left_table' => 'groups_field_data',
      'left_field' => 'id',
    ];
    $join = Drupal::service('plugin.manager.views.join')
      ->createInstance('standard', $definition);

    // Add relation and where condition.
    $query
      ->addRelationship('group__field_anonymous_visibility', $join, 'group__field_anonymous_visibility');
    $query->where[] = [
      'conditions' => [
        [
          'field' => 'group__field_anonymous_visibility.field_anonymous_visibility_value',
          'value' => 1,
          'operator' => '!=',
        ],
      ],
      'type' => 'AND',
    ];
  }
  if ($view
    ->id() === 'opigno_group_members') {
    $user = \Drupal::currentUser();
    if (!$user
      ->hasPermission('manage group members in any group')) {

      // If current user is not an admin and not a global user manager.
      // Get trainings where the current user is a user manager.

      /** @var \Drupal\group\GroupMembershipLoaderInterface $membership_service */
      $membership_service = \Drupal::service('group.membership_loader');
      $memberships = $membership_service
        ->loadByUser($user, [
        'learning_path-user_manager',
      ]);
      $groups_ids = array_map(function ($membership) {

        /** @var \Drupal\group\GroupMembership $membership */
        return $membership
          ->getGroup()
          ->id();
      }, $memberships);

      // Filter listed memberships by them.
      if (!empty($groups_ids)) {
        $query->where[] = [
          'conditions' => [
            [
              'field' => 'groups_field_data_group_content_field_data.id',
              'value' => $groups_ids,
              'operator' => 'IN',
            ],
          ],
          'type' => 'AND',
        ];
      }
      else {
        $query->where[] = [
          'conditions' => [
            [
              'field' => 'FALSE',
              'value' => [],
              'operator' => 'formula',
            ],
          ],
          'type' => 'AND',
        ];
      }
    }
  }
  if ($view
    ->id() === 'opigno_group_members_table') {

    // Add filter for only members of a training.

    /** @var \Drupal\group\Entity\Group $group */
    $group = \Drupal::routeMatch()
      ->getParameter('group');
    if ($group) {
      $members = $view->storage
        ->get('group_members');
      $members = !empty($members) ? $members : [
        0,
      ];
      $query->where[] = [
        'conditions' => [
          [
            'field' => 'group_content_field_data.entity_id',
            'value' => $members,
            'operator' => 'IN',
          ],
        ],
        'type' => 'AND',
      ];
    }
  }
}

/**
 * Implements hook_entity_insert().
 */
function opigno_learning_path_entity_insert(EntityInterface $entity) {
  if ($entity
    ->bundle() == 'opigno_class-group_membership') {

    // Get class group id.
    $gid = $entity
      ->getGroup()
      ->id();
    if (!empty($gid) && is_numeric($gid)) {
      try {

        // Get class parent groups.
        $parent_groups = LearningPathContent::getClassGroups($gid);
        if ($parent_groups) {
          foreach ($parent_groups as $parent) {
            $group = Group::load($parent->gid);
            $user = $entity
              ->getEntity();
            if ($group && $user) {

              // Check if user is not a member.
              if ($group
                ->getMember($user) === FALSE) {

                // Add class parent group membership.
                $group
                  ->addMember($user);
              }
            }
          }
        }
      } catch (\Exception $e) {
        \Drupal::logger('opigno_learning_path')
          ->error($e
          ->getMessage());
        \Drupal::messenger()
          ->addMessage($e
          ->getMessage(), 'error');
      }
    }
  }
  if ($entity
    ->bundle() == 'learning_path-group_membership') {

    // Add new membership of group into statistic.
    $membership_id = $entity
      ->get('entity_id')
      ->getValue()[0]['target_id'];
    $group_id = $entity
      ->get('gid')
      ->getValue()[0]['target_id'];
    opigno_learning_path_save_achievements($group_id, $membership_id);
    $entity
      ->enforceIsNew();
    LearningPathAccess::mergeUserStatus($entity);
  }
}

/**
 * Implements hook_entity_base_field_info().
 */
function opigno_learning_path_entity_base_field_info(EntityTypeInterface $entity_type) {
  $fields = [];
  if ($entity_type
    ->id() === 'user_module_status') {
    $fields['learning_path'] = BaseFieldDefinition::create('entity_reference')
      ->setLabel(t('Learning path'))
      ->setDescription(t('The learning path whose context the module was taken in.'))
      ->setSetting('target_type', 'group')
      ->setSetting('target_bundles', [
      'learning_path' => 'learning_path',
    ]);
  }
  return $fields;
}

/**
 * Implements hook_ENTITY_TYPE_presave().
 */
function opigno_learning_path_user_module_status_presave(UserModuleStatus $attempt) {
  if ($attempt
    ->isNew()) {
    $gid = OpignoGroupContext::getCurrentGroupId();
    $learning_path = isset($gid) ? Group::load($gid) : NULL;
    if ($learning_path) {
      $attempt
        ->set('learning_path', $learning_path);
    }
  }
}

/**
 * Calculates module attempt score.
 *
 * @param \Drupal\opigno_module\Entity\UserModuleStatus $attempt
 *   Attempt object.
 *
 * @return int
 *   Score in percent.
 */
function opigno_learning_path_get_attempt_score(UserModuleStatus $attempt) {
  $id = $attempt
    ->id();
  $result =& drupal_static(__FUNCTION__);
  if (!isset($result[$id])) {
    $result[$id] = $attempt
      ->getAttemptScore();
  }
  return $result[$id];
}

/**
 * Calculates module best score.
 *
 * @param int $id
 *   Module id.
 * @param int $uid
 *   User id.
 *
 * @return int
 *   Score in percent.
 */
function opigno_learning_path_get_module_best_score($id, $uid) {
  $score =& drupal_static(__FUNCTION__);
  $key = "{$id}_{$uid}";
  if (!isset($score[$key])) {
    $opigno_module = OpignoModule::load($id);
    $user = User::load($uid);
    $score[$key] = $opigno_module
      ->getBestScore($user);
  }
  return $score[$key];
}

/**
 * Builds up a training module step.
 *
 * @param int $group_id
 *   Training group ID.
 * @param int $uid
 *   User ID.
 * @param \Drupal\opigno_module\Entity\OpignoModule $module
 *   Opigno Module entity.
 * @param int $latest_cert_date
 *   Latest certification date.
 *
 * @return array
 *   Data array about step in a group for a user.
 *
 * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
 * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
 */
function opigno_learning_path_get_module_step($group_id, $uid, OpignoModule $module, $latest_cert_date = NULL) {
  $opigno_lps = Drupal::service('opigno_lps');
  return $opigno_lps
    ->getModuleStep($group_id, $uid, $module, $latest_cert_date);
}

/**
 * Builds up a training course step.
 *
 * @param int $group_id
 *   Training group ID.
 * @param int $uid
 *   User ID.
 * @param \Drupal\group\Entity\GroupInterface $course
 *   Group entity of the course.
 *
 * @return array
 *   Data array about step in a group for a user.
 *
 * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
 * @throws \Drupal\Component\Plugin\Exception\PluginException
 * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
 */
function opigno_learning_path_get_course_step($group_id, $uid, GroupInterface $course, $latest_cert_date = NULL) {
  $opigno_lps = Drupal::service('opigno_lps');
  return $opigno_lps
    ->getCourseStep($group_id, $uid, $course, $latest_cert_date);
}

/**
 * Builds up a training live meeting step.
 *
 * @param int $group_id
 *   Training group ID.
 * @param int $uid
 *   User ID.
 * @param \Drupal\opigno_moxtra\MeetingInterface $meeting
 *   Opigno Moxtra Meeting entity.
 *
 * @return array
 *   Data array about step in a group for a user.
 *
 * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
 * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
 */
function opigno_learning_path_get_meeting_step($group_id, $uid, MeetingInterface $meeting) {
  $opigno_lps = Drupal::service('opigno_lps');
  return $opigno_lps
    ->getMeetingStep($group_id, $uid, $meeting);
}

/**
 * Builds up a training ILT step.
 *
 * @param int $group_id
 *   Training group ID.
 * @param int $uid
 *   User ID.
 * @param \Drupal\opigno_ilt\ILTInterface $ilt
 *   Opigno ILT entity.
 *
 * @return array
 *   Data array about step in a group for a user.
 *
 * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
 * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
 */
function opigno_learning_path_get_ilt_step($group_id, $uid, ILTInterface $ilt) {
  $opigno_lps = Drupal::service('opigno_lps');
  return $opigno_lps
    ->getIltStep($group_id, $uid, $ilt);
}

/**
 * Builds up a list of steps in a group for a user.
 *
 * @param int $group_id
 *   Group ID.
 * @param int $uid
 *   User ID.
 * @param int $latest_cert_date
 *   Latest certification date.
 *
 * @return array
 *   Info about each step in a group for a user.
 *
 * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
 * @throws \Drupal\Component\Plugin\Exception\PluginException
 * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
 */
function opigno_learning_path_get_steps($group_id, $uid, $only_modules_and_courses = NULL, $latest_cert_date = NULL) {
  $key = "{$group_id}_{$uid}_{$only_modules_and_courses}_{$latest_cert_date}";
  $results =& drupal_static(__FUNCTION__);
  if (!isset($results[$key])) {
    $steps = [];
    $attempts_raw = [];
    $module = [];
    $user = User::load($uid);
    $entity_type_manager = \Drupal::entityTypeManager();

    /** @var \Drupal\opigno_group_manager\OpignoGroupContentTypesManager $content_type_manager */
    $content_type_manager = \Drupal::service('opigno_group_manager.content_types.manager');

    /** @var \Drupal\opigno_group_manager\Entity\OpignoGroupManagedContent $first_content */
    $managed_content = OpignoGroupManagedContent::getFirstStep($group_id);
    while ($managed_content) {
      $id = $managed_content
        ->getEntityId();
      $type_id = $managed_content
        ->getGroupContentTypeId();
      $type = $content_type_manager
        ->createInstance($type_id);

      /** @var \Drupal\opigno_group_manager\OpignoGroupContent $content */
      $content = $type
        ->getContent($id);
      if ($content === FALSE) {

        // If can't load step content, skip it. Assume user has got 100% score.
        $managed_content = $managed_content
          ->getNextStep(100);
        continue;
      }
      switch ($type_id) {
        case 'ContentTypeModule':

          /** @var \Drupal\opigno_module\Entity\OpignoModule $module */
          $module = $entity_type_manager
            ->getStorage('opigno_module')
            ->load($id);
          $step_info = opigno_learning_path_get_module_step($group_id, $uid, $module, $latest_cert_date);

          /** @var \Drupal\opigno_module\Entity\OpignoModule $module */
          if ($module = OpignoModule::load($id)) {

            /** @var \Drupal\opigno_module\Entity\UserModuleStatus[] $attempts */
            $attempts = $module
              ->getModuleAttempts($user, NULL, $latest_cert_date);
            if (!empty($attempts)) {
              $attempts_raw[$id] = $attempts;
            }
            $best_attempt = NULL;
            if (!empty($attempts)) {
              $best_attempt = opigno_learning_path_best_attempt($attempts);
            }
            if ($best_attempt) {
              $step_info['best_attempt'] = $best_attempt
                ->id();
            }
          }
          $steps[] = $step_info;
          break;
        case 'ContentTypeCourse':
          $module = Group::load($id);
          $step_info = opigno_learning_path_get_course_step($group_id, $uid, $module, $latest_cert_date);
          $course_steps = OpignoGroupManagedContent::loadByGroupId($id);
          if (!empty($course_steps)) {

            // Check if each course module has at least one activity.
            foreach ($course_steps as $course_step) {
              $id = $course_step
                ->getEntityId();
              $opigno_module = OpignoModule::load($id);
              $attempts = $opigno_module
                ->getModuleAttempts($user, NULL, $latest_cert_date);
              if (!empty($attempts)) {
                $attempts_raw[$id] = $attempts;
              }
              $best_attempt = NULL;
              if (!empty($attempts)) {
                $best_attempt = opigno_learning_path_best_attempt($attempts);
              }
              if ($best_attempt) {
                $step_info['best_attempt'] = $best_attempt
                  ->id();
                $step_info['best_attempts'][$id] = $best_attempt
                  ->id();
              }
            }
          }
          $steps[] = $step_info;
          break;
        case 'ContentTypeMeeting':

          /** @var \Drupal\opigno_moxtra\MeetingInterface $meeting */
          if ($only_modules_and_courses) {
            break;
          }
          $meeting = $entity_type_manager
            ->getStorage('opigno_moxtra_meeting')
            ->load($id);
          $step_info = opigno_learning_path_get_meeting_step($group_id, $uid, $meeting);
          $steps[] = $step_info;
          break;
        case 'ContentTypeILT':

          /** @var \Drupal\opigno_ilt\ILTInterface $ilt */
          if ($only_modules_and_courses) {
            break;
          }
          $ilt = $entity_type_manager
            ->getStorage('opigno_ilt')
            ->load($id);
          $step_info = opigno_learning_path_get_ilt_step($group_id, $uid, $ilt);
          $steps[] = $step_info;
          break;
      }

      // Get training guided navigation option.
      $guidedNavigation = TRUE;
      if ($group = Group::load($group_id)) {
        $guidedNavigation = OpignoGroupManagerController::getGuidedNavigation($group);
      }

      // To get a next step, if user is not attempted step,
      // assume user has got 100% score.
      $best_score = isset($step_info) && opigno_learning_path_is_attempted($step_info, $uid) ? $step_info['best score'] : 100;
      $managed_content = $managed_content
        ->getNextStep($best_score, $attempts_raw, $module, $guidedNavigation, $type_id);
    }
    $results[$key] = $steps;
  }
  return $results[$key];
}

/**
 * Select best attempt from list.
 *
 * @param array $attempts
 *   List of attempts.
 *
 * @return object
 *   Best Attempt.
 */
function opigno_learning_path_best_attempt(array $attempts) {
  usort($attempts, function ($a, $b) {

    /** @var \Drupal\opigno_module\Entity\UserModuleStatus $a */

    /** @var \Drupal\opigno_module\Entity\UserModuleStatus $b */
    $b_score = $b
      ->getAttemptScore();
    $a_score = $a
      ->getAttemptScore();
    return $b_score - $a_score;
  });
  return reset($attempts);
}

/**
 * Select list of modules for course.
 *
 * @param integer $course_id
 *   Couse id.
 *
 * @return array
 *   Return list of modules for course.
 */
function opigno_learning_path_get_course_modules($course_id) {
  $course_steps = OpignoGroupManagedContent::loadByGroupId($course_id);
  $modules = [];
  if (!empty($course_steps)) {

    // Check if each course module has at least one activity.
    foreach ($course_steps as $course_step) {
      $id = $course_step
        ->getEntityId();
      $modules[$id] = OpignoModule::load($id);
    }
  }
  return $modules;
}

/**
 * Builds up a full list of all the steps in a group for a user.
 *
 * @param int $group_id
 *   Group ID.
 * @param int $uid
 *   User ID.
 *
 * @return array
 *   Info about each step in a group for a user.
 *
 * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
 * @throws \Drupal\Component\Plugin\Exception\PluginException
 * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
 */
function opigno_learning_path_get_all_steps($group_id, $uid, $only_modules_and_courses = NULL, $latest_cert_date = NULL) {
  $key = "{$group_id}_{$uid}_{$only_modules_and_courses}_{$latest_cert_date}";
  $results =& drupal_static(__FUNCTION__);
  if (!isset($results[$key]) && !empty($group_id)) {
    $steps = [];
    $contents = OpignoGroupManagedContent::loadByGroupId($group_id);
    usort($contents, function ($a, $b) {

      /** @var \Drupal\opigno_group_manager\Entity\OpignoGroupManagedContent $a */

      /** @var \Drupal\opigno_group_manager\Entity\OpignoGroupManagedContent $b */
      $b_y = $b
        ->getCoordinateY();
      $a_y = $a
        ->getCoordinateY();
      return $a_y > $b_y;
    });
    $entity_type_manager = \Drupal::entityTypeManager();

    /** @var \Drupal\opigno_group_manager\OpignoGroupContentTypesManager $content_type_manager */
    $content_type_manager = \Drupal::service('opigno_group_manager.content_types.manager');
    $managed_content = reset($contents);
    while ($managed_content) {
      $id = $managed_content
        ->getEntityId();
      $type_id = $managed_content
        ->getGroupContentTypeId();
      $type = $content_type_manager
        ->createInstance($type_id);

      /** @var \Drupal\opigno_group_manager\OpignoGroupContent $content */
      $content = $type
        ->getContent($id);
      if ($content === FALSE) {

        // If can't load step content, skip it. Assume user has got 100% score.
        $managed_content = $managed_content
          ->getNextStep(100);
        continue;
      }
      switch ($type_id) {
        case 'ContentTypeModule':

          /** @var \Drupal\opigno_module\Entity\OpignoModule $module */
          $module = $entity_type_manager
            ->getStorage('opigno_module')
            ->load($id);
          $step_info = opigno_learning_path_get_module_step($group_id, $uid, $module, $latest_cert_date);

          /** @var \Drupal\opigno_module\Entity\OpignoModule $module */
          if ($module = OpignoModule::load($id)) {

            /** @var \Drupal\opigno_module\Entity\UserModuleStatus[] $attempts */
            $user = \Drupal::currentUser();
            $attempts = $module
              ->getModuleAttempts($user, NULL, $latest_cert_date);
            $best_attempt = NULL;
            if (!empty($attempts)) {
              $best_attempt = opigno_learning_path_best_attempt($attempts);
            }
            if ($best_attempt) {
              $step_info['best_attempt'] = $best_attempt
                ->id();
            }
          }
          $steps[] = $step_info;
          break;
        case 'ContentTypeCourse':
          $course = Group::load($id);
          $step_info = opigno_learning_path_get_course_step($group_id, $uid, $course, $latest_cert_date);
          $steps[] = $step_info;
          break;
        case 'ContentTypeMeeting':

          /** @var \Drupal\opigno_moxtra\MeetingInterface $meeting */
          if ($only_modules_and_courses) {
            break;
          }
          $meeting = $entity_type_manager
            ->getStorage('opigno_moxtra_meeting')
            ->load($id);
          $step_info = opigno_learning_path_get_meeting_step($group_id, $uid, $meeting);
          $steps[] = $step_info;
          break;
        case 'ContentTypeILT':

          /** @var \Drupal\opigno_ilt\ILTInterface $ilt */
          if ($only_modules_and_courses) {
            break;
          }
          $ilt = $entity_type_manager
            ->getStorage('opigno_ilt')
            ->load($id);
          $step_info = opigno_learning_path_get_ilt_step($group_id, $uid, $ilt);
          $steps[] = $step_info;
          break;
      }
      $managed_content = next($contents);
    }
    $results[$key] = $steps;
  }
  return $results[$key];
}

/**
 * Get activities in a module for a user.
 *
 * @param int $module_id
 *   Module ID.
 * @param int $uid
 *   User ID.
 * @param bool $step_state_counting
 *   TRUE if function called for calculating step state, FALSE otherwise.
 * @param int $latest_cert_date
 *   Latest certification date.
 *
 * @return array
 *   Info about each activity status for a user.
 */
function opigno_learning_path_get_module_activities($module_id, $uid, $step_state_counting = FALSE, $latest_cert_date = NULL) {
  $user = User::load($uid);

  /** @var \Drupal\opigno_module\Entity\OpignoModule $module */
  $module = OpignoModule::load($module_id);

  /** @var \Drupal\opigno_module\Entity\UserModuleStatus[] $attempts */
  $attempts = $module
    ->getModuleAttempts($user, NULL, $latest_cert_date);

  // Load activities for a module.
  $module_activities = $module
    ->getModuleActivities();
  $activities = array_map(function ($activity) use ($user, $module, $attempts, $step_state_counting) {

    // Value returned by the OpignoModule::getModuleActivities()
    // is not an OpignoActivity.

    /** @var \Drupal\opigno_module\Entity\OpignoActivity $activity */
    $activity = OpignoActivity::load($activity->id);

    /** @var \Drupal\opigno_module\Entity\OpignoAnswer[] $answers */
    $answers = array_map(function ($attempt) use ($user, $module, $activity, $step_state_counting) {

      /** @var \Drupal\opigno_module\Entity\OpignoActivity $activity */
      $answer = $activity
        ->getUserAnswer($module, $attempt, $user);
      if ($answer && $step_state_counting && $activity
        ->hasField('opigno_evaluation_method') && $activity
        ->get('opigno_evaluation_method')->value && !$answer
        ->isEvaluated()) {
        $answer = NULL;
      }
      return $answer;
    }, $attempts);

    // The OpignoActivity::getUserAnswer() may return NULL.
    $answers = array_filter($answers, function ($answer) {
      return isset($answer);
    });
    return [
      'activity_id' => $activity
        ->id(),
      'module_id' => $module
        ->id(),
      'answers' => count($answers),
    ];
  }, $module_activities);
  return $activities;
}

/**
 * Get activities in a group for a user.
 *
 * @param int $group_id
 *   Group ID.
 * @param int $uid
 *   User ID.
 * @param int $latest_cert_date
 *   Latest certification date.
 *
 * @return array
 *   Info about each activity status for a user.
 *
 * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
 * @throws \Drupal\Component\Plugin\Exception\PluginException
 * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
 */
function opigno_learning_path_get_activities($group_id, $uid, $latest_cert_date = NULL) {
  $activities = [];
  $steps = opigno_learning_path_get_steps($group_id, $uid, NULL, $latest_cert_date);
  foreach ($steps as $step) {
    if ($step['typology'] === 'Module') {
      $module_activities = opigno_learning_path_get_module_activities($step['id'], $uid, FALSE, $latest_cert_date);

      // Check if the module in skills system.
      $module = \Drupal::entityTypeManager()
        ->getStorage('opigno_module')
        ->load($step['id']);
      $moduleHandler = \Drupal::service('module_handler');
      if ($moduleHandler
        ->moduleExists('opigno_skills_system') && $module
        ->getSkillsActive() && $step['completed on'] != 0) {
        foreach ($module_activities as $key => $m_activity) {
          $module_activities[$key]['answers'] = 1;
        }
      }
      $activities = array_merge($activities, $module_activities);

      // Limit random activities.
      if ($module
        ->getRandomization() == 2) {
        $unanswered_activities = array_filter($activities, function ($activity) {
          return !$activity['answers'];
        });
        $answered_activities = array_filter($activities, function ($activity) {
          return $activity['answers'];
        });
        $activities = array_slice($answered_activities, 0, $module
          ->getRandomActivitiesCount());
        if (count($activities) < $module
          ->getRandomActivitiesCount()) {
          $diff_num = $module
            ->getRandomActivitiesCount() - count($activities);
          $difference = array_slice($unanswered_activities, 0, $diff_num);
          array_merge($activities, $difference);
        }
      }
    }
    elseif ($step['typology'] === 'Course') {
      $course_activities = opigno_learning_path_get_activities($step['id'], $uid, $latest_cert_date);
      $activities = array_merge($activities, $course_activities);
    }
    elseif (($step['typology'] === 'ILT' || $step['typology'] === 'Meeting') && $step['mandatory'] == 1) {
      if ($step['time spent'] > 0 && $step['presence'] > 0) {
        $answer = 1;
      }
      else {
        $answer = 0;
      }
      $activities[] = [
        'answers' => $answer,
      ];
    }
  }
  return $activities;
}

/**
 * Returns step progress.
 */
function opigno_learning_path_get_step_progress($step, $uid, $step_state_counting = FALSE, $latest_cert_date = NULL) {
  if (isset($step['passed']) && $step['passed']) {
    return 1;
  }
  $typology = isset($step['typology']) ? $step['typology'] : NULL;
  $moduleHandler = \Drupal::service('module_handler');
  if (in_array($typology, [
    'Meeting',
    'ILT',
  ])) {
    return isset($step['attempts']) && $step['attempts'] > 0 ? 1 : 0;
  }
  elseif ($typology === 'Module' || $typology === 'Course') {
    if ($typology === 'Module') {
      $activities = opigno_learning_path_get_module_activities($step['id'], $uid, $step_state_counting, $latest_cert_date);
      $module = OpignoModule::load($step['id']);

      // Limit random activities.
      if ($module
        ->getRandomization() == 2) {
        $unanswered_activities = array_filter($activities, function ($activity) {
          return !$activity['answers'];
        });
        $answered_activities = array_filter($activities, function ($activity) {
          return $activity['answers'];
        });
        $activities = array_slice($answered_activities, 0, $module
          ->getRandomActivitiesCount());
        if (count($activities) < $module
          ->getRandomActivitiesCount()) {
          $diff_num = $module
            ->getRandomActivitiesCount() - count($activities);
          $difference = array_slice($unanswered_activities, 0, $diff_num);
          array_merge($activities, $difference);
        }
      }
    }
    elseif ($typology === 'Course') {
      $activities = opigno_learning_path_get_activities($step['id'], $uid, $latest_cert_date);
    }
    else {
      $activities = [];
    }
    $total = count($activities);
    $attempted = count(array_filter($activities, function ($activity) {
      return $activity['answers'] > 0;
    }));

    // Add cheat for skills modules to jump to the next module if the user already has needed skills.
    if ($attempted == 0 && $typology == 'Module' && $step['current attempt score'] >= $step['required score']) {
      $module = \Drupal::entityTypeManager()
        ->getStorage('opigno_module')
        ->load($step['id']);
      if ($moduleHandler
        ->moduleExists('opigno_skills_system') && $module
        ->getSkillsActive() && isset($step['best_attempt'])) {
        $attempt = \Drupal::entityTypeManager()
          ->getStorage('user_module_status')
          ->load($step['best_attempt']);
        if (!empty($attempt) && $attempt
          ->isFinished()) {
          return 1;
        }
      }
    }
    return $total > 0 ? $attempted / $total : 0;
  }
  return 0;
}

/**
 * Returns step status.
 */
function opigno_learning_path_get_step_status($step, $uid, $step_state_counting = FALSE, $latest_cert_date = NULL) {
  $progress = opigno_learning_path_get_step_progress($step, $uid, $step_state_counting, $latest_cert_date);
  if (isset($step['passed']) && $step['passed'] == TRUE) {
    return 'passed';
  }
  elseif ($progress < 1) {
    return 'pending';
  }
  else {
    $score = $step['best score'];
    $min_score = $step['required score'];
    return $score < $min_score ? 'failed' : 'passed';
  }
}

/**
 * Calculates learning path completion date.
 *
 * @param int $group_id
 *   Group ID.
 * @param int $uid
 *   User ID.
 *
 * @return int
 *   Completion time.
 *
 * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
 * @throws \Drupal\Component\Plugin\Exception\PluginException
 * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
 */
function opigno_learning_path_completed_on($group_id, $uid, $only_mandatory = FALSE) {

  // Get all mandatory steps.
  $steps = opigno_learning_path_get_steps($group_id, $uid);
  $mandatory_steps = array_filter($steps, function ($step) {
    return $step['mandatory'];
  });
  $completed_on = 0;

  // If has mandatory steps.
  if (!empty($mandatory_steps)) {
    $completed_steps = array_filter($mandatory_steps, function ($step) {
      return $step['completed on'] > 0;
    });

    // If all mandatory steps completed.
    if (count($completed_steps) === count($mandatory_steps)) {

      // Get completion time of the last step.
      if (!$only_mandatory) {
        $completed_on = max(array_map(function ($step) {
          return $step['completed on'];
        }, $steps));
      }
      else {
        $completed_on = max(array_map(function ($step) {
          return $step['completed on'];
        }, $mandatory_steps));
      }
    }
  }
  return $completed_on;
}

/**
 * Returns attempted flag.
 *
 * @param array|\Drupal\group\Entity\GroupInterface $step
 *   Learning Path or Course Group entity,
 *   or step array, returned by opigno_learning_path_get_steps()
 * @param int $uid
 *   User ID.
 *
 * @return bool
 *   Attempted flag.
 *
 * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
 * @throws \Drupal\Component\Plugin\Exception\PluginException
 * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
 */
function opigno_learning_path_is_attempted($step, $uid) {
  if (is_array($step)) {
    if ($step['typology'] === 'Course') {
      $steps = opigno_learning_path_get_steps($step['id'], $uid);
    }
    else {
      return $step['attempts'] > 0;
    }
  }
  else {
    $steps = opigno_learning_path_get_steps($step
      ->id(), $uid);
  }

  // If at least one step has attempted.
  return !empty(array_filter($steps, function ($step) use ($uid) {
    return opigno_learning_path_is_attempted($step, $uid);
  }));
}

/**
 * Returns LP passed flag.
 *
 * @param array|\Drupal\group\Entity\GroupInterface $step
 *   Learning Path or Course Group entity,
 *   or step array, returned by opigno_learning_path_get_steps()
 * @param int $uid
 *   User ID.
 * @param bool $current_attempt
 *   Current attempt flag.
 *
 * @return bool
 *   LP passed flag.
 *
 * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
 * @throws \Drupal\Component\Plugin\Exception\PluginException
 * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
 */
function opigno_learning_path_is_passed($step, $uid, $current_attempt = FALSE) {
  if (is_array($step)) {
    if ($step['typology'] === 'Course') {
      $steps = !$current_attempt ? opigno_learning_path_get_steps($step['id'], $uid) : opigno_learning_path_get_steps_current_attempt($step['id'], $uid);
    }
    else {
      return opigno_learning_path_is_attempted($step, $uid) && $step['completed on'] > 0 && $step['best score'] >= $step['required score'];
    }
  }
  else {
    $steps = !$current_attempt ? opigno_learning_path_get_steps($step
      ->id(), $uid) : opigno_learning_path_get_steps_current_attempt($step
      ->id(), $uid);

    // Check only mandatory steps.
    $steps = array_filter($steps, function ($step) {
      return $step['mandatory'];
    });
  }

  // Not passed, if haven't steps.
  if (empty($steps)) {
    return FALSE;
  }
  $steps = array_filter($steps, function ($step) use ($uid, $current_attempt) {
    return !opigno_learning_path_is_passed($step, $uid, $current_attempt);
  });

  // If all steps has passed.
  return empty($steps);
}

/**
 * Builds a list of group steps for a user current attempt.
 */
function opigno_learning_path_get_steps_current_attempt($group_id, $uid) {
  $key = "{$group_id}_{$uid}";
  $results =& drupal_static(__FUNCTION__);
  if (!isset($results[$key])) {
    $steps = [];
    $attempts_raw = [];
    $module = [];
    $entity_type_manager = \Drupal::entityTypeManager();

    /** @var \Drupal\opigno_group_manager\OpignoGroupContentTypesManager $content_type_manager */
    $content_type_manager = \Drupal::service('opigno_group_manager.content_types.manager');

    /** @var \Drupal\opigno_group_manager\Entity\OpignoGroupManagedContent $first_content */
    $managed_content = OpignoGroupManagedContent::getFirstStep($group_id);
    while ($managed_content) {
      $id = $managed_content
        ->getEntityId();
      $type_id = $managed_content
        ->getGroupContentTypeId();
      $type = $content_type_manager
        ->createInstance($type_id);

      /** @var \Drupal\opigno_group_manager\OpignoGroupContent $content */
      $content = $type
        ->getContent($id);
      if ($content === FALSE) {

        // If can't load step content, skip it. Assume user has got 100% score.
        $managed_content = $managed_content
          ->getNextStep(100);
        continue;
      }
      switch ($type_id) {
        case 'ContentTypeModule':

          /** @var \Drupal\opigno_module\Entity\OpignoModule $module */
          $module = $entity_type_manager
            ->getStorage('opigno_module')
            ->load($id);
          $step_info = opigno_learning_path_get_module_step($group_id, $uid, $module);
          $step_info["best score"] = $step_info["current attempt score"];

          /** @var \Drupal\opigno_module\Entity\OpignoModule $module */
          if ($module = OpignoModule::load($id)) {

            /** @var \Drupal\opigno_module\Entity\UserModuleStatus[] $attempts */
            $user = \Drupal::currentUser();
            $attempts = $module
              ->getModuleAttempts($user, 'last');
            $attempts_raw[$id] = $attempts;
            if (!empty($attempts)) {
              $last_attempt = array_shift($attempts);
              $step_info['last_attempt'] = $last_attempt;
              $step_info['best_attempt'] = $last_attempt
                ->id();
            }
          }
          $steps[] = $step_info;
          break;
        case 'ContentTypeCourse':
          $course = Group::load($id);
          $step_info = opigno_learning_path_get_course_step($group_id, $uid, $course);
          $step_info["best score"] = $step_info["current attempt score"];
          $steps[] = $step_info;
          break;
        case 'ContentTypeMeeting':

          /** @var \Drupal\opigno_moxtra\MeetingInterface $meeting */
          $meeting = $entity_type_manager
            ->getStorage('opigno_moxtra_meeting')
            ->load($id);
          $step_info = opigno_learning_path_get_meeting_step($group_id, $uid, $meeting);
          $step_info["best score"] = $step_info["current attempt score"];
          $steps[] = $step_info;
          break;
        case 'ContentTypeILT':

          /** @var \Drupal\opigno_ilt\ILTInterface $ilt */
          $ilt = $entity_type_manager
            ->getStorage('opigno_ilt')
            ->load($id);
          $step_info = opigno_learning_path_get_ilt_step($group_id, $uid, $ilt);
          $step_info["best score"] = $step_info["current attempt score"];
          $steps[] = $step_info;
          break;
      }

      // Get training guided navigation option.
      $group = Group::load($group_id);
      $guidedNavigation = OpignoGroupManagerController::getGuidedNavigation($group);

      // To get a next step, if user is not attempted step,
      // assume user has got 100% score.
      $best_score = isset($step_info) && opigno_learning_path_is_attempted($step_info, $uid) ? $step_info['best score'] : 100;
      $managed_content = $managed_content
        ->getNextStep($best_score, $attempts_raw, $module, $guidedNavigation, $type_id);
    }
    $results[$key] = $steps;
  }
  return $results[$key];
}

/**
 * Calculates learning path score.
 *
 * @param int $gid
 *   Training group ID.
 * @param int $uid
 *   User ID.
 * @param bool $current_attempt
 *   Current attempt flag.
 * @param int $latest_cert_date
 *   Latest certification date.
 *
 * @return int
 *   Score.
 *
 * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
 * @throws \Drupal\Component\Plugin\Exception\PluginException
 * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
 */
function opigno_learning_path_get_score($gid, $uid, $current_attempt = FALSE, $latest_cert_date = NULL) {
  $steps = !$current_attempt ? opigno_learning_path_get_steps($gid, $uid, NULL, $latest_cert_date) : opigno_learning_path_get_steps_current_attempt($gid, $uid);
  $mandatory_steps = array_filter($steps, function ($step) {
    return $step['mandatory'];
  });
  if (!empty($mandatory_steps)) {
    $score = round(array_sum(array_map(function ($step) {
      return $step['best score'];
    }, $mandatory_steps)) / count($mandatory_steps));
  }
  else {
    $score = 0;
  }
  return $score;
}

/**
 * Calculates time spent in training.
 *
 * @param int $gid
 *   Training group ID.
 * @param int $uid
 *   User ID.
 *
 * @return int
 *   Time spent in seconds.
 *
 * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
 * @throws \Drupal\Component\Plugin\Exception\PluginException
 * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
 */
function opigno_learning_path_get_time_spent($gid, $uid) {
  $steps = opigno_learning_path_get_steps($gid, $uid);
  return array_sum(array_map(function ($step) {
    return $step['time spent'];
  }, $steps));
}

/**
 * Stores step achievements data.
 *
 * @param int $gid
 *   Training group ID.
 * @param int $uid
 *   User ID.
 * @param array $step
 *   Step info array returned by the opigno_learning_path_get_steps().
 * @param int $parent_id
 *   ID of the parent row in the opigno_learning_path_step_achievements table.
 *
 * @return int
 *   Row ID.
 *
 * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
 * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
 */
function opigno_learning_path_save_step_achievements($gid, $uid, array $step, $parent_id = 0) {
  if (empty($gid)) {
    return FALSE;
  }
  $table_name = 'opigno_learning_path_step_achievements';

  // Format timestamp to the storage format.
  $completed_timestamp = $step['completed on'];
  $completed = $completed_timestamp > 0 ? DrupalDateTime::createFromTimestamp($completed_timestamp)
    ->format(DrupalDateTime::FORMAT) : NULL;
  $group = Group::load($gid);
  $latest_cert_date = LPStatus::getTrainingStartDate($group, $uid);
  $status = opigno_learning_path_get_step_status($step, $uid, TRUE, $latest_cert_date);

  // Check the absence of the user at the live meeting or at the ILT.
  switch ($step['typology']) {
    case 'Meeting':
      $entityTypeManager = \Drupal::entityTypeManager();

      /** @var \Drupal\opigno_moxtra\MeetingResultInterface[] $meeting_results */
      $meeting_results = $entityTypeManager
        ->getStorage('opigno_moxtra_meeting_result')
        ->loadByProperties([
        'user_id' => $uid,
        'meeting' => $step['id'],
      ]);
      if (!empty($meeting_results)) {
        $presence = current($meeting_results)
          ->getStatus();
        if ($presence == 0) {
          $completed = NULL;
          $status = 'failed';
        }
      }
      break;
    case 'ILT':
      $entityTypeManager = \Drupal::entityTypeManager();

      /** @var \Drupal\opigno_ilt\ILTResultInterface[] $ilt_results */
      $ilt_results = $entityTypeManager
        ->getStorage('opigno_ilt_result')
        ->loadByProperties([
        'user_id' => $uid,
        'opigno_ilt' => $step['id'],
      ]);
      if (!empty($ilt_results)) {
        $presence = current($ilt_results)
          ->getStatus();
        if ($presence == 0) {
          $completed = NULL;
          $status = 'failed';
        }
      }
      break;
  }

  // Fix empty scores.
  if (empty($step['best score'])) {
    $step['best score'] = 0;
  }

  // Save step results in the opigno_learning_path_step_achievements.
  $keys = [
    'uid' => $uid,
    'gid' => $gid,
    'typology' => $step['typology'],
    'entity_id' => $step['id'],
    'parent_id' => $parent_id,
  ];
  $fields = [
    'uid' => $uid,
    'gid' => $gid,
    'entity_id' => $step['id'],
    'parent_id' => $parent_id,
    'name' => $step['name'],
    'typology' => $step['typology'],
    'mandatory' => $step['mandatory'],
    'status' => $status,
    'score' => $step['best score'],
    'time' => $step['time spent'],
    'completed' => $completed,
    'position' => isset($step['position']) ? $step['position'] : 0,
  ];
  $query = \Drupal::database()
    ->merge($table_name)
    ->keys($keys)
    ->fields($fields);
  if (isset($step['current attempt time']) && $step['typology'] != 'Meeting' && $step['typology'] != 'ILT') {
    $query = $query
      ->expression('time', 'time + :time', [
      ':time' => $step['current attempt time'],
    ]);
  }

  // Set score for Module.
  if ($step['typology'] == 'Module') {
    $module = OpignoModule::load($step['id']);
    $user = User::load($uid);
    $moduleHandler = \Drupal::service('module_handler');
    if (!$moduleHandler
      ->moduleExists('opigno_skills_system') || !$module
      ->getSkillsActive()) {
      $fields['score'] = $module
        ->getUserScore($user, $latest_cert_date);
    }
  }
  elseif (isset($step['current attempt score']) && $step['typology'] != 'Meeting' && $step['typology'] != 'ILT') {
    $query = $query
      ->expression('score', 'GREATEST(score, :score, :best_score)', [
      ':score' => $step['current attempt score'],
      ':best_score' => $fields['score'],
    ]);
  }
  $query
    ->execute();

  // Get saved ID.
  $query = \Drupal::database()
    ->select($table_name, 'a')
    ->fields('a', [
    'id',
  ]);
  foreach ($keys as $field => $value) {
    $query = $query
      ->condition($field, $value);
  }
  return $query
    ->execute()
    ->fetchField();
}

/**
 * Stores training achievements data.
 *
 * @param int $gid
 *   Training group ID.
 * @param int $uid
 *   User ID.
 * @param bool $with_meeting
 *   If training contains ILT or Live Meeting.
 *
 * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
 * @throws \Drupal\Component\Plugin\Exception\PluginException
 * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
 */
function opigno_learning_path_save_achievements($gid, $uid, $with_meeting = FALSE) {
  if (empty($gid)) {
    return FALSE;
  }
  $table_name = 'opigno_learning_path_achievements';
  $user = User::load($uid);
  $group = Group::load($gid);

  /** @var \Drupal\group\GroupMembership $group_membership */
  $group_membership = $group
    ->getMember($user);

  // Format timestamps to the storage format.
  $created_timestamp = $group_membership !== FALSE ? $group_membership
    ->getGroupContent()
    ->get('created')
    ->getValue()[0]['value'] : 0;
  $created = DrupalDateTime::createFromTimestamp($created_timestamp)
    ->format(DrupalDateTime::FORMAT);
  $completed_timestamp = opigno_learning_path_completed_on($gid, $uid);
  $completed = $completed_timestamp > 0 ? DrupalDateTime::createFromTimestamp($completed_timestamp)
    ->format(DrupalDateTime::FORMAT) : NULL;
  $latest_cert_date = LPStatus::getTrainingStartDate($group, $uid);
  if ($with_meeting) {

    // When a training contains a meeting.
    $is_passed = opigno_learning_path_is_passed($group, $uid);
    if (isset($completed)) {
      $status = $is_passed ? 'completed' : 'failed';
    }
    else {
      $status = 'pending';
    }
  }
  else {

    // Usual flow.
    $status = isset($completed) ? 'completed' : 'pending';
  }
  $score = opigno_learning_path_get_score($gid, $uid, FALSE, $latest_cert_date);
  $progress_service = \Drupal::service('opigno_learning_path.progress');
  $progress = $progress_service
    ->getProgressRound($gid, $uid, $latest_cert_date);
  $time = opigno_learning_path_get_time_spent($gid, $uid);
  $keys = [
    'uid' => $uid,
    'gid' => $gid,
  ];
  $fields = [
    'uid' => $uid,
    'gid' => $gid,
    'name' => $group
      ->label(),
    'status' => $status,
    'score' => $score,
    'progress' => $progress,
    'time' => $time,
    'registered' => $created,
    'completed' => $completed,
  ];
  \Drupal::database()
    ->merge($table_name)
    ->keys($keys)
    ->fields($fields)
    ->execute();
}

/**
 * Function to detect if user already started Learning path.
 *
 * @param \Drupal\group\Entity\Group $group
 *   Group.
 * @param \Drupal\Core\Session\AccountProxyInterface $user
 *   User.
 *
 * @return mixed|bool
 *   Current result attempt if it's exist or FALSE if not.
 */
function opigno_learning_path_started(Group $group, AccountProxyInterface $user) {
  return LPResult::getCurrentLPAttempt($group, $user);
}

/**
 * Detects if all steps of creating learning path are correctly filled.
 *
 * Return array with the correctly filled steps.
 */
function opigno_learning_path_validate_group_creating_steps() {
  $is_learning_path_route = opigno_learning_path_is_lp_route();
  if ($is_learning_path_route) {

    // Load the learning path group.
    $group = \Drupal::routeMatch()
      ->getParameter('group');
    if (!$group) {

      // First step - group is not created yet.
      $steps_number = [];
      return $steps_number;
    }
    $group_type = $group
      ->getGroupType()
      ->id();
    $steps = OpignoGroupManagedContent::loadByGroupId($group
      ->id());

    // Array with steps number.
    $steps_number = array_values(opigno_learning_path_get_routes_steps());
    $steps_number = array_unique($steps_number);
    if (!$steps) {

      // Means group is empty or all steps are invalid.
      $steps_number = [
        1,
      ];
    }
    else {

      // Check if there is at least one mandatory entity.
      if ($group_type == 'learning_path') {
        $has_mandatory_item = FALSE;
        foreach ($steps as $step) {
          if ($step
            ->isMandatory()) {
            $has_mandatory_item = TRUE;
          }
        }
        if (!$has_mandatory_item) {
          $steps_number = array_diff($steps_number, [
            2,
          ]);
        }
      }
      foreach ($steps as $step) {
        $id = $step
          ->getEntityId();
        $type_id = $step
          ->getGroupContentTypeId();
        if ($group_type == 'learning_path') {

          // Check if training is published.
          $is_published = $group->field_learning_path_published->value;
          if (!$is_published) {
            $steps_number = array_diff($steps_number, [
              5,
            ]);
          }

          // Check if each module has at least one activity.
          if ($type_id === 'ContentTypeModule') {
            $module = OpignoModule::load($id);
            $activities = $module
              ->getModuleActivities();
            $moduleHandler = \Drupal::service('module_handler');
            if (!$moduleHandler
              ->moduleExists('opigno_skills_system') || !$activities && !$module
              ->getSkillsActive() && !$module
              ->getModuleSkillsGlobal()) {
              $steps_number = array_diff($steps_number, [
                4,
              ]);
            }
          }
          elseif ($type_id === 'ContentTypeCourse') {
            $course_steps = OpignoGroupManagedContent::loadByGroupId($id);
            if (!$course_steps) {

              // Means course group is empty, so 3 and 4 steps are invalid.
              $steps_number = array_diff($steps_number, [
                3,
                4,
              ]);
            }
            else {

              // Check if each course module has at least one activity.
              foreach ($course_steps as $course_step) {
                $id = $course_step
                  ->getEntityId();
                $module = OpignoModule::load($id);
                $activities = $module
                  ->getModuleActivities();
                if (!$activities) {
                  $steps_number = array_diff($steps_number, [
                    4,
                  ]);
                }
              }
            }
          }
        }
        elseif ($group_type == 'opigno_course') {
          $course_steps = OpignoGroupManagedContent::loadByGroupId($group
            ->id());
          if (!$course_steps) {

            // Means course group is empty, so 2 and 3 steps are invalid.
            $steps_number = array_diff($steps_number, [
              2,
              3,
            ]);
          }
          else {
            foreach ($course_steps as $index => $course_step) {
              $id = $course_step
                ->getEntityId();
              $module = OpignoModule::load($id);
              if ($module) {
                $activities = $module
                  ->getModuleActivities();
                if (!$activities) {
                  $steps_number = array_diff($steps_number, [
                    3,
                  ]);
                }
              }
            }
          }
        }
      }
    }
    return $steps_number;
  }
  return FALSE;
}

/**
 * Function to detect if user has resumed step in LP.
 *
 * Return step content id or FALSE if there is no resumed step.
 */
function opigno_learning_path_resumed_step(array $steps) {
  $account = \Drupal::currentUser();

  // Array with all finished modules.
  $attempts_finished = [];
  foreach ($steps as $step) {
    $opigno_module = OpignoModule::load($step['id']);
    if ($opigno_module != NULL) {
      $attempts = $opigno_module
        ->getModuleAttempts($account, 'last');
      if ($attempts) {

        // Get last attempt.
        $last_attempt = end($attempts);
        if (!$last_attempt
          ->isFinished()) {

          // Means last attempt is unfinished. Return FALSE.
          break;
        }
        $attempts_finished[$opigno_module
          ->id()] = intval($last_attempt
          ->get('finished')->value);
      }
    }
  }
  if (!empty($attempts_finished)) {

    // Get last finished opigno_module id.
    $last_module_id = array_keys($attempts_finished, max($attempts_finished));
    $last_step = end($steps);
    if (reset($last_module_id) == $last_step['id']) {

      // continue.
    }
    else {
      for ($i = 0; $i < count($steps); ++$i) {
        if (intval($steps[$i]['id']) == reset($last_module_id)) {
          $next_step_cid = $steps[$i + 1]['cid'];
          return $next_step_cid;
        }
      }
    }
  }
  return FALSE;
}

/**
 * Implements hook_preprocess_HOOK().
 */
function opigno_learning_path_preprocess_status_messages(&$variables) {
  $is_lp_route = opigno_learning_path_is_lp_route();

  // Unset message after creating new training.
  if (isset($variables['message_list']['status']) && $is_lp_route) {
    $status_messages =& $variables['message_list']['status'];
    foreach ($status_messages as $delta => $message) {
      if ($message instanceof MarkupInterface) {

        // Actually message content.
        $pattern = '/(?=.*Learning Path)(?=.*has been created)/';
        if (preg_match($pattern, $message
          ->__toString())) {
          unset($status_messages[$delta]);
          break;
        }
      }
    }

    // Prevent empty block message appearing.
    if (empty($status_messages)) {
      unset($variables['message_list']['status']);
    }
  }
}

/**
 * Implements opigno_learning_path_get_modules_ids_by_group().
 *
 * Get modules ids by group.
 *
 * Return array with modules ids.
 */
function opigno_learning_path_get_modules_ids_by_group(Group $group) {

  // Get the courses and modules within those.
  $modules = [];
  $group_content = $group
    ->getContent('subgroup:opigno_course');
  foreach ($group_content as $content) {

    /* @var $content \Drupal\group\Entity\GroupContent */

    /* @var $content_entity \Drupal\group\Entity\Group */
    $course = $content
      ->getEntity();
    $course_contents = $course
      ->getContent('opigno_module_group');
    foreach ($course_contents as $course_content) {

      /* @var $module_entity \Drupal\opigno_module\Entity\OpignoModule */
      $module_entity = $course_content
        ->getEntity();
      $modules[] = $module_entity
        ->id();
    }
  }

  // Get the direct modules.
  $group_content = $group
    ->getContent('opigno_module_group');
  foreach ($group_content as $content) {

    /* @var $content \Drupal\group\Entity\GroupContent */

    /* @var $content_entity \Drupal\opigno_module\Entity\OpignoModule */
    $content_entity = $content
      ->getEntity();
    $modules[] = $content_entity
      ->id();
  }
  return $modules;
}

/**
 * Implements hook_library_info_alter().
 */
function opigno_learning_path_library_info_alter(&$libraries, $extension) {

  // Make possible to load Dropzone library from profile folder.
  if ($extension == 'dropzonejs' && \Drupal::moduleHandler()
    ->moduleExists('dropzonejs')) {
    $js = drupal_get_path("profile", "opigno_lms") . '/libraries/dropzone/dist/min/dropzone.min.js';
    $css = drupal_get_path("profile", "opigno_lms") . '/libraries/dropzone/dist/min/dropzone.min.css';
    if (file_exists($js) && file_exists($css)) {
      $libraries['dropzonejs']['js'] = [
        '/' . $js => [],
      ];
      $libraries['dropzonejs']['css']['theme'] = [
        '/' . $css => [],
      ];
    }
  }
}

/**
 * Implements hook_preprocess_breadcrumb().
 */
function opigno_learning_path_preprocess_breadcrumb(&$variables) {
  $route_name = \Drupal::routeMatch()
    ->getRouteName();
  if ($route_name == 'opigno_module.module_result') {
    $variables['#cache']['contexts'][] = 'url';
  }
}

/**
 * Implements hook_system_breadcrumb_alter().
 */
function opigno_learning_path_system_breadcrumb_alter(Breadcrumb &$breadcrumb, RouteMatchInterface $route_match, array $context) {

  // Replace link on user result page.
  if ($route_match
    ->getRouteName() == 'opigno_module.module_result') {

    // Get group.
    // First try to get group from route.
    $group = $route_match
      ->getParameter('group');
    if (empty($group)) {

      // Try to get group from context.
      $gid = OpignoGroupContext::getCurrentGroupId();
      if (!empty($gid)) {
        $group = Group::load($gid);
      }
    }
    if ($group instanceof Group) {
      $links = $breadcrumb
        ->getLinks();

      // Replace link with link to training homepage.
      $links[1] = Link::createFromRoute($group
        ->label(), 'entity.group.canonical', [
        'group' => $group
          ->id(),
      ]);
      $breadcrumb = new Breadcrumb();
      $breadcrumb
        ->setLinks($links);
    }
  }
}

/**
 * Implements hook_views_view_field().
 */
function opigno_learning_path_preprocess_views_view_field(&$variables) {
  if ($variables['view']
    ->id() == 'media_browser_images' && $variables['field']->options['id'] == 'name') {
    $variables['bundle'] = $variables['view']->field['bundle']->original_value
      ->jsonSerialize();
  }
  if ($variables['view']
    ->id() == 'group_members' && $variables['field']->options['id'] == 'group_roles' && empty($variables['view']->field['group_roles']->original_value)) {
    $variables['empty'] = TRUE;
    $variables['output'] = $variables['field']->options['empty'];
  }
}

Functions

Namesort descending Description
opigno_learning_path_add_form_redirect Adds redirect after creating new learning path.
opigno_learning_path_best_attempt Select best attempt from list.
opigno_learning_path_completed_on Calculates learning path completion date.
opigno_learning_path_cron Sends email notifications about training expired certification.
opigno_learning_path_entity_base_field_info Implements hook_entity_base_field_info().
opigno_learning_path_entity_delete Implements hook_entity_delete().
opigno_learning_path_entity_insert Implements hook_entity_insert().
opigno_learning_path_entity_presave Implements hook_entity_presave().
opigno_learning_path_entity_update Implements hook_entity_update().
opigno_learning_path_field_required_trainings_validate Custom validation for field_required_training.
opigno_learning_path_field_widget_form_alter Implements hook_field_widget_form_alter().
opigno_learning_path_form_alter Implements hook_form_alter().
opigno_learning_path_form_group_opigno_course_delete_form_alter Implements hook_form_FORM_ID_alter().
opigno_learning_path_form_opigno_module_delete_form_alter Implements hook_form_FORM_ID_alter().
opigno_learning_path_get_activities Get activities in a group for a user.
opigno_learning_path_get_all_steps Builds up a full list of all the steps in a group for a user.
opigno_learning_path_get_attempt_score Calculates module attempt score.
opigno_learning_path_get_course_modules Select list of modules for course.
opigno_learning_path_get_course_step Builds up a training course step.
opigno_learning_path_get_current_step Returns current step.
opigno_learning_path_get_group_type Returns group type.
opigno_learning_path_get_ilt_step Builds up a training ILT step.
opigno_learning_path_get_meeting_step Builds up a training live meeting step.
opigno_learning_path_get_modules_ids_by_group Implements opigno_learning_path_get_modules_ids_by_group().
opigno_learning_path_get_module_activities Get activities in a module for a user.
opigno_learning_path_get_module_best_score Calculates module best score.
opigno_learning_path_get_module_step Builds up a training module step.
opigno_learning_path_get_routes_steps Returns routes steps.
opigno_learning_path_get_score Calculates learning path score.
opigno_learning_path_get_steps Builds up a list of steps in a group for a user.
opigno_learning_path_get_steps_current_attempt Builds a list of group steps for a user current attempt.
opigno_learning_path_get_step_list_aside Returns step list for page aside area.
opigno_learning_path_get_step_list_top Returns step list for page top area.
opigno_learning_path_get_step_progress Returns step progress.
opigno_learning_path_get_step_status Returns step status.
opigno_learning_path_get_student_managers Returns student managers.
opigno_learning_path_get_time_spent Calculates time spent in training.
opigno_learning_path_group_access Restricts user access to Learning Path and it's content.
opigno_learning_path_group_content_access Implements hook_ENTITY_TYPE_access().
opigno_learning_path_group_content_create_access Implements hook_ENTITY_TYPE_create_access().
opigno_learning_path_group_membership_add_form_submit Callback used in opigno_learning_path_form_alter().
opigno_learning_path_group_membership_form_submit Adds redirect after user join group.
opigno_learning_path_group_presave Sets additional fields for group visibility.
opigno_learning_path_help Implements hook_help().
opigno_learning_path_is_attempted Returns attempted flag.
opigno_learning_path_is_lp_route Returns LP route flag.
opigno_learning_path_is_passed Returns LP passed flag.
opigno_learning_path_library_info_alter Implements hook_library_info_alter().
opigno_learning_path_mail Implements hook_mail().
opigno_learning_path_opigno_module_access Restricts user access to Learning Path content.
opigno_learning_path_page_attachments Implements hook_page_attachments().
opigno_learning_path_preprocess_block Implements hook_preprocess_HOOK() for block.
opigno_learning_path_preprocess_breadcrumb Implements hook_preprocess_breadcrumb().
opigno_learning_path_preprocess_html Implements hook_preprocess_html().
opigno_learning_path_preprocess_multiselect_new_class Implements hook_preprocess_multiselect().
opigno_learning_path_preprocess_opigno_certificate Implements template_preprocess_HOOK().
opigno_learning_path_preprocess_page Implements hook_preprocess_page().
opigno_learning_path_preprocess_region Implements hook_preprocess_region().
opigno_learning_path_preprocess_status_messages Implements hook_preprocess_HOOK().
opigno_learning_path_preprocess_views_view_field Implements hook_views_view_field().
opigno_learning_path_resumed_step Function to detect if user has resumed step in LP.
opigno_learning_path_save_achievements Stores training achievements data.
opigno_learning_path_save_step_achievements Stores step achievements data.
opigno_learning_path_started Function to detect if user already started Learning path.
opigno_learning_path_system_breadcrumb_alter Implements hook_system_breadcrumb_alter().
opigno_learning_path_theme Implements hook_theme().
opigno_learning_path_theme_suggestions_alter Implements hook_theme_suggestions_alter().
opigno_learning_path_theme_suggestions_multiselect
opigno_learning_path_user_access Implements hook_ENTITY_TYPE_access().
opigno_learning_path_user_create_access Implements hook_ENTITY_TYPE_create_access().
opigno_learning_path_user_module_status_presave Implements hook_ENTITY_TYPE_presave().
opigno_learning_path_validate_group_creating_steps Detects if all steps of creating learning path are correctly filled.
opigno_learning_path_views_query_alter Implements hook_views_query_alter().
_opigno_learning_path_group_membership_edit_form_validate Additional custom validation for LP membership edit form.