Merge branch 'master' of github.com:chamilo/chamilo-lms

pull/5733/head
Yannick Warnier 1 year ago
commit 8b746cc4c6
  1. 32
      .env.dist
  2. 13
      assets/css/app.scss
  3. 2
      config/packages/prod/api_platform.yaml
  4. 10
      public/main/auth/inscription.php
  5. 2
      public/main/forum/editpost.php
  6. 2
      public/main/forum/editthread.php
  7. 2
      public/main/forum/newthread.php
  8. 8
      public/main/forum/viewforum.php
  9. 76
      public/main/inc/lib/course.lib.php
  10. 20
      public/main/my_space/myStudents.php
  11. 15
      src/CoreBundle/Controller/CourseController.php
  12. 2
      src/CoreBundle/DataFixtures/PermissionFixtures.php
  13. 15
      src/CoreBundle/Entity/Listener/SessionListener.php
  14. 15
      src/CoreBundle/Entity/Session.php
  15. 12
      src/CoreBundle/Resources/views/Course/about.html.twig
  16. 46
      src/CoreBundle/Resources/views/Permission/index.html.twig

@ -16,9 +16,9 @@ DATABASE_PASSWORD='{{DATABASE_PASSWORD}}'
###< doctrine/doctrine-bundle ###
###> symfony/framework-bundle ###
APP_ENV='{{APP_ENV}}'
APP_ENV='dev'
APP_SECRET='{{APP_SECRET}}'
APP_DEBUG='{{APP_DEBUG}}'
APP_DEBUG='1'
#TRUSTED_PROXIES=127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
#TRUSTED_HOSTS='^(localhost|example\.com)$'
###< symfony/framework-bundle ###
@ -29,28 +29,31 @@ APP_INSTALLED='{{APP_INSTALLED}}'
## Encrypt method bcrypt/sha1/md5
APP_ENCRYPT_METHOD='{{APP_ENCRYPT_METHOD}}'
APP_LOCALE='{{APP_LOCALE}}'
APP_LOCALE='en_US'
APP_CUSTOM_VUE_TEMPLATE='{{APP_CUSTOM_VUE_TEMPLATE}}'
APP_CUSTOM_VUE_TEMPLATE='0'
GOOGLE_MAPS_API_KEY='{{GOOGLE_MAPS_API_KEY}}'
GOOGLE_MAPS_API_KEY=''
# Enable the entryponint /api for API docs
APP_ENABLE_API_ENTRYPOINT=false
#APP_API_PLATFORM_URL='http://localhost/api/' #deprecated
###< chamilo ###
###> symfony/mailer ###
MAILER_DSN='{{MAILER_DSN}}'
MAILER_DSN=sendmail://default
###< symfony/mailer ###
###> nelmio/cors-bundle ###
CORS_ALLOW_ORIGIN='{{CORS_ALLOW_ORIGIN}}'
CORS_ALLOW_ORIGIN=^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$
###< nelmio/cors-bundle ###
###> lexik/jwt-authentication-bundle ###
JWT_SECRET_KEY='{{JWT_SECRET_KEY}}'
JWT_PUBLIC_KEY='{{JWT_PUBLIC_KEY}}'
JWT_PASSPHRASE='{{JWT_PASSPHRASE}}'
JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem
JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem
JWT_PASSPHRASE=your_secret_passphrase
###< lexik/jwt-authentication-bundle ###
###> symfony/messenger ###
@ -59,12 +62,3 @@ JWT_PASSPHRASE='{{JWT_PASSPHRASE}}'
# MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f/messages
# MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages
###< symfony/messenger ###
###> additional settings ###
DB_MANAGER_ENABLED='{{DB_MANAGER_ENABLED}}'
SECURITY_KEY='{{SECURITY_KEY}}'
SOFTWARE_NAME='{{SOFTWARE_NAME}}'
SOFTWARE_URL='{{SOFTWARE_URL}}'
DENY_DELETE_USERS='{{DENY_DELETE_USERS}}'
HOSTING_TOTAL_SIZE_LIMIT='{{HOSTING_TOTAL_SIZE_LIMIT}}'
###< additional settings ###

@ -698,6 +698,7 @@ form .field {
.permissions-table {
width: 100%;
border-collapse: collapse;
table-layout: auto;
th, td {
border: 1px solid #ccc;
padding: 8px;
@ -713,8 +714,12 @@ form .field {
background-color: #e9e9e9;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
padding: 5px;
font-size: 0.9em;
white-space: normal;
word-wrap: break-word;
min-width: 100px;
vertical-align: top;
}
th {
padding-top: 12px;
@ -741,6 +746,10 @@ form .field {
.save-button:hover {
background-color: #45a049;
}
input[type="checkbox"] {
transform: scale(1);
margin: 0 auto;
}
}
.files-info-page {

@ -1,3 +1,3 @@
api_platform:
enable_docs: true
enable_entrypoint: false
enable_entrypoint: '%env(bool:APP_ENABLE_API_ENTRYPOINT)%'

@ -174,6 +174,16 @@ $course_code_redirect = isset($_REQUEST['c']) && !empty($_REQUEST['c']) ? $_REQU
$exercise_redirect = isset($_REQUEST['e']) && !empty($_REQUEST['e']) ? $_REQUEST['e'] : null;
if (!empty($course_code_redirect)) {
if (!api_is_anonymous()) {
$course_info = api_get_course_info($course_code_redirect);
$subscribed = CourseManager::autoSubscribeToCourse($course_code_redirect);
if ($subscribed) {
header('Location: ' . api_get_path(WEB_PATH) . 'course/'.$course_info['real_id'].'/home?sid=0');
} else {
header('Location: ' . api_get_path(WEB_PATH) . 'course/'.$course_info['real_id'].'/about');
}
exit;
}
Session::write('course_redirect', $course_code_redirect);
Session::write('exercise_redirect', $exercise_redirect);
}

@ -124,7 +124,7 @@ $courseEntity = api_get_course_entity();
$sessionEntity = api_get_session_entity();
$forumIsVisible = $forum->isVisible($courseEntity);
$categoryIsVisible = $category->isVisible($courseEntity);
$categoryIsVisible = $category->isVisible($courseEntity) && !api_get_session_id();
if (empty($post)) {
api_not_allowed(true);

@ -95,7 +95,7 @@ $sessionEntity = api_get_session_entity();
//$forumIsVisible = $forum->isVisible($courseEntity, $sessionEntity);
$category = $forum->getForumCategory();
$categoryIsVisible = $category->isVisible($courseEntity);
$categoryIsVisible = $category->isVisible($courseEntity) && !api_get_session_id();
if (api_is_in_gradebook()) {
$interbreadcrumb[] = [

@ -125,7 +125,7 @@ if (!api_is_allowed_to_create_course() && //is a student
(
($current_forum_category && false == $current_forum_category->isVisible($courseEntity)) ||
false == $current_forum_category->isVisible($courseEntity)
)
) && !api_get_session_id()
) {
api_not_allowed(true);
}

@ -110,9 +110,10 @@ if (!empty($groupId)) {
(
($category && false == $category->isVisible($courseEntity)) ||
!$category->isVisible($courseEntity)
)
) &&
!$sessionId
) {
api_not_allowed(true);
// api_not_allowed(true);
}
} else {
// Course
@ -120,7 +121,8 @@ if (!empty($groupId)) {
(
($category && false == $category->isVisible($courseEntity)) ||
!$category->isVisible($courseEntity)
)
) &&
!$sessionId
) {
api_not_allowed(true);
}

@ -684,6 +684,82 @@ class CourseManager
return self::subscribeUser($userId, $course->getId(), $status, 0);
}
/**
* Checks if the current user can subscribe to a given course.
*/
public static function canUserSubscribeToCourse(string $courseCode): bool
{
if (api_is_anonymous()) {
return false;
}
$course = Container::getCourseRepository()->findOneBy(['code' => $courseCode]);
if (null === $course) {
return false;
}
$visibility = (int) $course->getVisibility();
if (in_array($visibility, [
Course::CLOSED,
Course::HIDDEN,
])) {
return false;
}
if (Course::REGISTERED === $visibility && false === $course->getSubscribe()) {
return false;
}
$userId = api_get_user_id();
$sql = "SELECT * FROM ".Database::get_main_table(TABLE_MAIN_COURSE_USER)."
WHERE
user_id = $userId AND
relation_type <> ".COURSE_RELATION_TYPE_RRHH." AND
c_id = ".$course->getId();
if (Database::num_rows(Database::query($sql)) > 0) {
return false;
}
if (SUBSCRIBE_NOT_ALLOWED === (int) $course->getSubscribe()) {
return false;
}
$extraFieldValue = new ExtraFieldValue('course');
$value = $extraFieldValue->get_values_by_handler_and_field_variable(
$course->getId(),
'max_subscribed_students'
);
if (!empty($value) && isset($value['value']) && '' !== $value['value']) {
$maxStudents = (int) $value['value'];
$count = CourseManager::get_user_list_from_course_code(
$courseCode,
0,
null,
null,
STUDENT,
true,
false
);
if ($count >= $maxStudents) {
return false;
}
}
if ('true' === api_get_setting('session.catalog_course_subscription_in_user_s_session')) {
$user = api_get_user_entity($userId);
$sessions = $user->getCurrentlyAccessibleSessions();
if (empty($sessions) && $user->getSessionsAsStudent()) {
return false;
}
}
return true;
}
/**
* Subscribe a user to a course. No checks are performed here to see if
* course subscription is allowed.

@ -508,6 +508,9 @@ switch ($action) {
$em->persist($message);
$em->flush();
$senderName = UserManager::formatUserFullName($currentUser);
$emailAdmin = api_get_setting('admin.administrator_email');
// Send also message to all student bosses
$bossList = UserManager::getStudentBossList($studentId);
@ -517,20 +520,29 @@ switch ($action) {
foreach ($bossList as $boss) {
$studentFullName = UserManager::formatUserFullName($student);
$content = sprintf(
$contentBoss = sprintf(
get_lang('Hi,<br/><br/>User %s sent a follow up message about student %s.<br/><br/>The message can be seen here %s'),
UserManager::formatUserFullName($currentUser),
$studentFullName,
$link
);
$message = (new Message())
$messageBoss = (new Message())
->setTitle(sprintf(get_lang('Follow up message about student %s'), $studentFullName))
->setContent($content)
->setContent($contentBoss)
->setSender(api_get_user_entity())
->addReceiverTo(api_get_user_entity($boss['boss_id']))
->setMsgType(Message::MESSAGE_TYPE_INBOX)
;
$em->persist($message);
$em->persist($messageBoss);
api_mail_html(
UserManager::formatUserFullName(api_get_user_entity($boss['boss_id'])),
api_get_user_entity($boss['boss_id'])->getEmail(),
sprintf(get_lang('Follow up message about student %s'), $studentFullName),
$contentBoss,
$senderName,
$emailAdmin
);
}
$em->flush();

@ -466,19 +466,7 @@ class CourseController extends ToolBaseController
$subscriptionUser = CourseManager::is_user_subscribed_in_course($user->getId(), $course->getCode());
}
/*$allowSubscribe = false;
if ($course->getSubscribe() || api_is_platform_admin()) {
$allowSubscribe = true;
}
$plugin = \BuyCoursesPlugin::create();
$checker = $plugin->isEnabled();
$courseIsPremium = null;
if ($checker) {
$courseIsPremium = $plugin->getItemByProduct(
$courseId,
\BuyCoursesPlugin::PRODUCT_TYPE_COURSE
);
}*/
$allowSubscribe = CourseManager::canUserSubscribeToCourse($course->getCode());
$image = Container::getIllustrationRepository()->getIllustrationUrl($course, 'course_picture_medium');
@ -499,6 +487,7 @@ class CourseController extends ToolBaseController
'is_premium' => '',
'token' => '',
'base_url' => $request->getSchemeAndHttpHost(),
'allow_subscribe' => $allowSubscribe,
];
$metaInfo = '<meta property="og:url" content="'.$urlCourse.'" />';

@ -146,6 +146,7 @@ class PermissionFixtures extends Fixture implements FixtureGroupInterface
['title' => 'Edit user', 'slug' => 'user:edit', 'description' => 'Edit users'],
['title' => 'Edit user Role', 'slug' => 'user:editrole', 'description' => 'Edit user roles'],
['title' => 'Login as user', 'slug' => 'user:loginas', 'description' => 'Login as another user'],
['title' => 'Edit Course Settings', 'slug' => 'course:editsettings', 'description' => 'Edit settings of a course'],
];
}
@ -256,6 +257,7 @@ class PermissionFixtures extends Fixture implements FixtureGroupInterface
'user:edit' => ['ADM', 'SUA', 'GLO'],
'user:editrole' => ['ADM', 'SUA', 'GLO'],
'user:loginas' => ['SUA', 'GLO'],
'course:editsettings' => ['TEA', 'ADM', 'SUA', 'GLO'],
];
}
}

@ -7,9 +7,11 @@ declare(strict_types=1);
namespace Chamilo\CoreBundle\Entity\Listener;
use Chamilo\CoreBundle\Entity\Session;
use Chamilo\CoreBundle\Repository\AssetRepository;
use Chamilo\CoreBundle\Traits\AccessUrlListenerTrait;
use Doctrine\ORM\Event\PrePersistEventArgs;
use Doctrine\ORM\Event\PreUpdateEventArgs;
use Doctrine\Persistence\Event\LifecycleEventArgs;
use Exception;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpFoundation\RequestStack;
@ -23,7 +25,8 @@ class SessionListener
public function __construct(
protected RequestStack $request,
protected Security $security
protected Security $security,
protected AssetRepository $assetRepository
) {}
/**
@ -46,6 +49,16 @@ class SessionListener
// $this->checkLimit($repo, $url);
}
/**
* This code is executed when a session is loaded from the database.
*/
public function postLoad(Session $session, LifecycleEventArgs $args): void
{
if ($session->getImage()) {
$session->setImageUrl($this->assetRepository->getAssetUrl($session->getImage()));
}
}
/**
* This code is executed when a session is updated.
*/

@ -368,6 +368,9 @@ class Session implements ResourceWithAccessUrlInterface, Stringable
#[ORM\JoinColumn(name: 'image_id', referencedColumnName: 'id', onDelete: 'SET NULL')]
protected ?Asset $image = null;
#[Groups(['user_subscriptions:sessions', 'session:read', 'session:item:read', 'session:basic'])]
private ?string $imageUrl = null;
#[Groups(['user_subscriptions:sessions', 'session:read', 'session:item:read'])]
private int $accessVisibility = 0;
@ -1185,6 +1188,18 @@ class Session implements ResourceWithAccessUrlInterface, Stringable
return null !== $this->image;
}
public function setImageUrl(?string $imageUrl): self
{
$this->imageUrl = $imageUrl;
return $this;
}
public function getImageUrl(): ?string
{
return $this->imageUrl;
}
/**
* Check if $user is course coach in any course.
*/

@ -79,7 +79,7 @@
<div class="bg-white shadow rounded p-4">
<h3 class="sub-title text-xl font-bold mb-3">{{ "Course Information"|trans }}</h3>
<div class="course-information">
<!-- Añade aquí la información del curso -->
{{ description | raw }}
</div>
</div>
</div>
@ -91,14 +91,18 @@
<div class="session-subscribe mt-2">
{% if not is_granted('IS_AUTHENTICATED') %}
{% if 'allow_registration'|api_get_setting != 'false' %}
<a href="{{ base_url ~ '/main/auth/inscription.php' ~ redirect_to_session }}" class="btn btn--success w-full py-2 mt-2 text-white bg-green-600 rounded hover:bg-green-700">
<a href="{{ base_url ~ '/main/auth/inscription.php?c=' ~ course.code }}" class="btn btn--success w-full py-2 mt-2 text-white bg-green-600 rounded hover:bg-green-700">
<i class="fa fa-pencil" aria-hidden="true"></i> {{ 'Sign Up'|trans }}
</a>
{% endif %}
{% elseif subscription %}
<a href="{{ url('home') }}courses/{{ course.code }}/index.php?sid=0" class="btn btn--success w-full py-2 mt-2 text-white bg-green-600 rounded hover:bg-green-700">{{ 'Course Homepage'|trans }}</a>
<a href="{{ base_url }}/course/{{ course.id }}/home?sid=0" class="btn btn--success w-full py-2 mt-2 text-white bg-green-600 rounded hover:bg-green-700">{{ 'Course Homepage'|trans }}</a>
{% elseif allow_subscribe %}
<a href="{{ base_url }}/main/auth/inscription.php?c={{ course.code }}" class="btn btn--success w-full py-2 mt-2 text-white bg-green-600 rounded hover:bg-green-700">{{ 'Subscribe'|trans }}</a>
{% else %}
<a href="{{ url('home') }}courses/{{ course.code }}/index.php?action=subscribe&sec_token={{ token }}" class="btn btn--success w-full py-2 mt-2 text-white bg-green-600 rounded hover:bg-green-700">{{ 'Subscribe'|trans }}</a>
<button class="btn btn--success w-full py-2 mt-2 text-white bg-gray-400 rounded" title="{{ 'Subscription is not allowed for this course'|trans }}" disabled>
{{ 'Subscription Not Available'|trans }}
</button>
{% endif %}
</div>
{% else %}

@ -4,32 +4,34 @@
<h1>{{ 'Permissions Management'|trans }}</h1>
<form method="post">
<button type="submit" class="save-button btn btn--primary hover:bg-blue-700 text-white font-bold py-2 px-4 rounded cursor-pointer mt-4 mb-4">{{ 'Save permissions'|trans }}</button>
<table class="permissions-table">
<thead>
<tr>
<th>{{ 'Permission '|trans }} <br> ({{ 'slug'|trans }})</th>
{% for role in roles %}
<th>
{{ role|trans }}<br>
<input type="checkbox" class="select-all" data-role="{{ role }}" style="margin-top: 5px;">
</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for permission in permissions %}
<div style="overflow-x: auto;">
<table class="permissions-table">
<thead>
<tr>
<td>{{ permission.title|trans }} <br> ({{ permission.slug }})</td>
<th>{{ 'Permission '|trans }} <br> ({{ 'slug'|trans }})</th>
{% for role in roles %}
<td>
<input type="checkbox" name="permissions[{{ permission.slug }}][{{ role }}]"
{% if forms[permission.slug].vars.value[role] %}checked="checked"{% endif %}>
</td>
<th>
{{ role|trans }}<br>
<input type="checkbox" class="select-all" data-role="{{ role }}" style="margin-top: 5px;">
</th>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</thead>
<tbody>
{% for permission in permissions %}
<tr>
<td>{{ permission.title|trans }} <br> ({{ permission.slug }})</td>
{% for role in roles %}
<td>
<input type="checkbox" name="permissions[{{ permission.slug }}][{{ role }}]"
{% if forms[permission.slug].vars.value[role] %}checked="checked"{% endif %}>
</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
<button type="submit" class="save-button btn btn--primary hover:bg-blue-700 text-white font-bold py-2 px-4 rounded cursor-pointer mt-4">{{ 'Save permissions'|trans }}</button>
</form>
<script>

Loading…
Cancel
Save