Skip to content
Closed
2 changes: 1 addition & 1 deletion assets/vue/components/Breadcrumb.vue
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,7 @@ function buildManualBreadcrumbIfNeeded() {
return true
}

const whitelist = ["admin"]
const whitelist = ["admin", "user"]
const overrides = {
admin: "AdminIndex",
gdpr: null,
Expand Down
1 change: 1 addition & 0 deletions public/main/admin/usergroups.php
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ function render_session_context_footer(): void
.' <a href="add_users_to_usergroup.php?id=\'+options.rowId+\''.$ctx.'">'.Display::getMdiIcon(ObjectIcon::USER, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Subscribe users to class')).'</a>'
.' <a href="add_courses_to_usergroup.php?id=\'+options.rowId+\''.$ctx.'">'.Display::getMdiIcon(ObjectIcon::COURSE, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Subscribe class to courses')).'</a>'
.' <a href="add_sessions_to_usergroup.php?id=\'+options.rowId+\''.$ctx.'">'.Display::getMdiIcon(ObjectIcon::SESSION, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Subscribe class to sessions')).'</a>'
.' <a href="/admin/usergroup/\'+options.rowId+\'/overview">'.Display::getMdiIcon(ActionIcon::VIEW_LIST, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Overview students and courses subscribed to the class')).'</a>'
.' <a href="?action=edit&id=\'+options.rowId+\''.$ctx.'">'.Display::getMdiIcon(ActionIcon::EDIT, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Edit')).'</a>'
.' <a onclick="javascript:if(!confirm('."\'".addslashes(api_htmlentities(get_lang("Please confirm your choice"), ENT_QUOTES))."\'".')) return false;" href="?action=delete&id=\'+options.rowId+\''.$ctx.'">'.Display::getMdiIcon(ActionIcon::DELETE, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Delete')).'</a>\';
}';
Expand Down
32 changes: 25 additions & 7 deletions public/main/inc/ajax/model.ajax.php
Original file line number Diff line number Diff line change
Expand Up @@ -2663,13 +2663,24 @@ function getWhereClause($col, $oper, $val)
$course_id,
api_get_session_id()
)) {
$url = 'class.php?action=remove_class_from_course&id='.$group['id'].'&'.api_get_cidreq(
).'&id_session='.api_get_session_id();
$icon = Display::getMdiIcon(ActionIcon::DELETE, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Remove'));
$actions = [
[
'icon' => Display::getMdiIcon(ActionIcon::VIEW_LIST, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Overview students subscribed to the class')),
'url' => api_get_path(WEB_PATH).'user/usergroup_overview?usergroup='.$group['id'].'&course='.$course_id,
],
[
'icon' => Display::getMdiIcon(ActionIcon::DELETE, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Remove')),
'url' => 'class.php?action=remove_class_from_course&id='.$group['id'].'&'.api_get_cidreq().'&id_session='.api_get_session_id(),
'onclick' => "if (!confirm('".get_lang('Are you sure you want to remove the class')."')) return false;"
],
];
} else {
$url = 'class.php?action=add_class_to_course&id='.$group['id'].'&'.api_get_cidreq(
).'&type=not_registered';
$icon = Display::getMdiIcon(ActionIcon::ADD, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Add'));
$actions = [
[
'icon' => Display::getMdiIcon(ActionIcon::ADD, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Add')),
'url' => 'class.php?action=add_class_to_course&id='.$group['id'].'&'.api_get_cidreq().'&type=not_registered',
]
];
}

switch ($group['group_type']) {
Expand All @@ -2692,7 +2703,14 @@ function getWhereClause($col, $oper, $val)
$urlUserGroup.'&id='.$group['id']
).'&nbsp;';
}
$group['actions'] .= Display::url($icon, $url);

for ($i = 0; $i < count($actions); $i++) {
$group['actions'] .= Display::url(
$actions[$i]['icon'],
$actions[$i]['url'] ?? null,
['onclick' => $actions[$i]['onclick'] ?? '']
);
}
}
$new_result[] = $group;
}
Expand Down
1 change: 1 addition & 0 deletions public/main/inc/lib/database.constants.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@
define('TABLE_GROUP_USER', 'group_rel_user');
define('TABLE_GROUP_TUTOR', 'group_rel_tutor');
define('TABLE_GROUP_CATEGORY', 'group_category');
define('TABLE_GROUP_CLASS', 'group_rel_usergroup');

// Course dropbox tables
define('TABLE_DROPBOX_CATEGORY', 'dropbox_category');
Expand Down
94 changes: 89 additions & 5 deletions public/main/inc/lib/usergroup.lib.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
/* For licensing terms, see /license.txt */

use Chamilo\CoreBundle\Entity\ResourceFile;
use Chamilo\CoreBundle\Entity\User;
use Chamilo\CoreBundle\Entity\Usergroup;
use Chamilo\CoreBundle\Enums\ActionIcon;
use Chamilo\CoreBundle\Enums\ObjectIcon;
use Chamilo\CoreBundle\Enums\ToolIcon;
use Chamilo\CoreBundle\Framework\Container;
use Chamilo\CoreBundle\Service\StandardizationService;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Polyfill\Intl\Normalizer\Normalizer;

/**
* Class UserGroup.
Expand Down Expand Up @@ -1245,11 +1248,6 @@ public function unsubscribe_courses_from_usergroup($usergroup_id, $delete_items,
]
);
}
if (0 != $sessionId && 0 != $groupId) {
$this->subscribe_sessions_to_usergroup($groupId, [0]);
} else {
$s = $sessionId;
}
}
}
}
Expand Down Expand Up @@ -3204,6 +3202,92 @@ public function getGroupsByLpCategory($categoryId, $courseId, $sessionId)
return Database::store_result($result, 'ASSOC');
}

public function getUsersInAndOutOfCourse($usergroupId, $courseId): array
{
$usergroupModel = new UserGroupModel();

$usersInUsergroup = $usergroupModel->get_users_by_usergroup($usergroupId);

$data = [];
$data['error'] = null;
$data['warning'] = null;
$data['usersSubscribedToCourse'] = [];
$data['usersNotSubscribedToCourse'] = [];

if (sizeof($usersInUsergroup) > 0) {

$courseManager = new CourseManager();
$usersInCourse = $courseManager->get_user_list_from_course_code($courseId);
$em = Container::getEntityManager();

$usersSubscribedToCourse = [];
$usersNotSubscribedToCourse = [];
foreach ($usersInUsergroup as $userId) {
$user = $em->getRepository(User::class)->find($userId);
if (array_key_exists($userId, $usersInCourse)) {
$usersSubscribedToCourse[] = $user;
} else {
$usersNotSubscribedToCourse[] = $user;
}
}

$data['usersSubscribedToCourse'] = StandardizationService::sortByNameByCountryAndStandardizeName($usersSubscribedToCourse, true);
$data['usersNotSubscribedToCourse'] = StandardizationService::sortByNameByCountryAndStandardizeName($usersNotSubscribedToCourse, true);;
} else {
$data['warning'] = get_lang('No user is subscribed to this class');
}

return $data;
}

public function getUsersAndCoursesSubscribedToAUserGroup($usergroupId):array
{
$data = [];
$data['error'] = null;
$data['warning'] = null;
$data['usersSubscribedToUsergroup'] = [];
$data['coursesSubscribedToUsergroup'] = [];

$usergroupLib = new UserGroupModel();
$em = Container::getEntityManager();

$usersSubscribedToUsergroupIds = $usergroupLib->get_users_by_usergroup($usergroupId);
if (count($usersSubscribedToUsergroupIds) > 0) {
$usersSubscribedToUsergroup = [];
foreach ($usersSubscribedToUsergroupIds as $userId) {
$usersSubscribedToUsergroup[] = $em->getRepository(User::class)->find($userId);
}


$data['usersSubscribedToUsergroup'] = StandardizationService::sortByNameByCountryAndStandardizeName($usersSubscribedToUsergroup, true);

}

$courseManager = new CourseManager();
$coursesSubscribedToUsergroupIds = $usergroupLib->get_courses_by_usergroup($usergroupId);
if (count($coursesSubscribedToUsergroupIds) > 0) {
$coursesSubscribedToUsergroup = [];
foreach ($coursesSubscribedToUsergroupIds as $courseId) {
$coursesSubscribedToUsergroup[] = [
'code' => $courseManager->get_course_code_from_course_id($courseId),
'name' => $courseManager->getCourseNameFromCode($courseManager->get_course_code_from_course_id($courseId)),
];
}
$data['coursesSubscribedToUsergroup'] = StandardizationService::sort($coursesSubscribedToUsergroup);
}

if (count($usersSubscribedToUsergroupIds) + count($coursesSubscribedToUsergroupIds) == 0) {
$data['warning'] = get_lang('No user and no course are subscribed to this class');
} else if (count($usersSubscribedToUsergroupIds) == 0) {
$data['warning'] = get_lang('No user are subscribed to this class');
} else if (count($coursesSubscribedToUsergroupIds) == 0) {
$data['warning'] = get_lang('No course are subscribed to this class');
}

return $data;
}


public static function getRoleName($relation)
{
switch ((int) $relation) {
Expand Down
31 changes: 31 additions & 0 deletions src/CoreBundle/Controller/Admin/UsergroupController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace Chamilo\CoreBundle\Controller\Admin;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use UserGroupModel;

#[Route('/admin')]
class UsergroupController extends AbstractController
{
#[Route('/usergroup/{id}/overview', name: 'class_overview')]
public function overview(int $id): Response
{
$usergroupLib = new UserGroupModel();
$usergroup = $usergroupLib->get($id);

$data = $usergroupLib->getUsersAndCoursesSubscribedToAUserGroup($id);

return $this->render('@ChamiloCore/Usergroup/overview.html.twig', [
'usergroupName' => $usergroup['title'],
'usersSubscribedToUsergroup' => $data['usersSubscribedToUsergroup'],
'coursesSubscribedToUsergroup' => $data['coursesSubscribedToUsergroup'],
'warning' => $data['warning'],
'error' => $data['error'],
]);
}
}
40 changes: 40 additions & 0 deletions src/CoreBundle/Controller/UserController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,56 @@

use Chamilo\CoreBundle\Repository\Node\IllustrationRepository;
use Chamilo\CoreBundle\Repository\Node\UserRepository;
use CourseManager;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Core\User\UserInterface;
use UserGroupModel;

/**
* @author Julio Montoya <gugli100@gmail.com>
*/
#[Route('/user')]
class UserController extends AbstractController
{
// #[Route(path: '/overview', name: 'overview_class', methods: ['GET'])]
#[Route(path: '/usergroup_overview', name: 'overview_class', methods: ['GET'])]
public function overview(Request $request): Response
{
$usergroupId = $request->query->get('usergroup');
$courseId = $request->query->get('course');

$usergroupLib = new UserGroupModel();
$usergroup = $usergroupLib->get($usergroupId);

$courseLib = new CourseManager();
$courseName = $courseLib->getCourseNameFromCode($courseLib->get_course_code_from_course_id($courseId));

$data = $usergroupLib->getUsersInAndOutOfCourse($usergroupId, $courseId);

$breadcrumb = json_encode([
['name' => get_lang('My courses'), 'url' => '/courses'],
['name' => $courseName, 'url' => '/course/'.$courseId.'/home'],
['name' => get_lang('Users'), 'url' => '/main/user/user.php?cid='.$courseId],
['name' => get_lang('Classes'), 'url' => '/main/user/class.php?cid='.$courseId],
['name' => $usergroup['title'], 'url' => '#'],
['name' => get_lang('Overview'), 'url' => ''],
]);

return $this->render('@ChamiloCore/User/usergroup_overview.html.twig', [
'legacy_breadcrumb' => $breadcrumb,
'courseId' => $courseId,
'courseName' => $courseName,
'usergroupName' => $usergroup['title'],
'usersSubscribedToCourse' => $data['usersSubscribedToCourse'],
'usersNotSubscribedToCourse' => $data['usersNotSubscribedToCourse'],
'error' => $data['error'],
'warning' => $data['warning'],
]);
}

/**
* Public profile.
*/
Expand All @@ -38,4 +77,5 @@ public function profile(string $username, UserRepository $userRepository, Illust
'illustration_url' => $url,
]);
}

}
1 change: 1 addition & 0 deletions src/CoreBundle/Enums/ActionIcon.php
Original file line number Diff line number Diff line change
Expand Up @@ -184,4 +184,5 @@ enum ActionIcon: string
case HEALTH_CHECK = 'clipboard-pulse-outline';
case FIX = 'auto-fix';
case AWARD = 'medal';
case VIEW_LIST = 'view-list';
}
83 changes: 83 additions & 0 deletions src/CoreBundle/Resources/views/User/usergroup_overview.html.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
{% extends "@ChamiloCore/Layout/base-layout.html.twig" %}

{% block chamilo_wrap %}

{%- include '@ChamiloCore/Layout/vue_setup.html.twig' %}

<section id="sectionMainContent" class="section-content">
{%- block content %}

<div>
<a href="/main/user/class.php?cid={{ courseId }}"
title="{{ 'Go back to course classes'|trans }}">
<i class="ch-tool-icon mdi mdi-arrow-left-bold-box" style="font-size: 32px"></i>
</a>
<h2>{{ 'Overview class'|trans }} : {{ usergroupName }}</h2>
</div>

<hr>

{% if error is not null %}
<div class="alert alert-danger">
<p>{{ error }}</p>
</div>
{% endif %}

{% if warning is not null %}
<div class="alert alert-warning">
<p>{{ warning }}</p>
</div>
{% endif %}

{% if (usersSubscribedToCourse|length + usersNotSubscribedToCourse|length) > 0 %}
<div style="display: flex; margin: 20px 0;">
<input style="height: 32px; width: 20vw" id="search-input" type="text"
placeholder="{{ 'Search'|trans }}">
<span class="ch-tool-icon mdi mdi-text-search" style="font-size: 32px; color:grey"></span>
<a href="javascript:void(0)" onclick="$('#search-input').val('');filterData('');">
<span class="ch-tool-icon mdi mdi-close-box-outline" style="font-size: 32px">
</span>
</a>
</div>
{% endif %}

{% if usersSubscribedToCourse|length > 0 %}
<div>
<b>{{ 'Users subscribed to the course'|trans }}</b>
<ul style='list-style-type: disc; padding-left: 1.5vw;margin: 20px 0;'>
{% for user in usersSubscribedToCourse %}
<li class="user-display">{{ user.identity }} ( {{ user.email }} )</li>
{% endfor %}
</ul>
</div>
{% endif %}

{% if usersNotSubscribedToCourse|length > 0 %}
<div>
<b>{{ 'Users not subscribed to the course'|trans }}</b>
<ul style='list-style-type: disc; padding-left: 1.5vw; margin: 20px'>
{% for user in usersNotSubscribedToCourse %}
<li class="user-display">{{ user.identity }} ( {{ user.email }} )</li>
{% endfor %}
</ul>
</div>
{% endif %}

{% endblock %}
</section>

<script>
function filterData(e)
{
const searchTerm = e.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "")
const users = document.querySelectorAll(".user-display")

users.forEach(user => {
const text = user.textContent.split("(")[0].trim().toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "")
user.style.display = text.includes(searchTerm) ? "" : "none"
})
}
document.getElementById("search-input").addEventListener("input", function(e) { filterData(e.target.value); } );
</script>

{% endblock %}
Loading
Loading