From eb766f529e06c975a0cc63717b680676b37276f1 Mon Sep 17 00:00:00 2001 From: christian Date: Tue, 7 Nov 2023 22:30:46 -0500 Subject: [PATCH 1/4] Reporting: Fix generate certificate - refs BT#21007 --- public/main/inc/lib/certificate.lib.php | 2 +- public/main/inc/lib/tracking.lib.php | 4 ++-- public/main/my_space/myStudents.php | 9 ++++----- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/public/main/inc/lib/certificate.lib.php b/public/main/inc/lib/certificate.lib.php index 1fde23b59c..db6f569856 100644 --- a/public/main/inc/lib/certificate.lib.php +++ b/public/main/inc/lib/certificate.lib.php @@ -803,7 +803,7 @@ class Certificate extends Model $tplContent->assign( 'certificate_generated_date_no_time', api_get_local_time( - $myCertificate['created_at'], + $myCertificate['created_at'] ?? null, null, null, false, diff --git a/public/main/inc/lib/tracking.lib.php b/public/main/inc/lib/tracking.lib.php index 8c471e40ab..b98f7ddf26 100644 --- a/public/main/inc/lib/tracking.lib.php +++ b/public/main/inc/lib/tracking.lib.php @@ -3265,13 +3265,13 @@ class Tracking // Compose a filter based on optional learning paths list given $condition_lp = ''; if (count($lp_ids) > 0) { - $condition_lp = " iid IN(".implode(',', $lp_ids).") "; + $condition_lp = " AND iid IN(".implode(',', $lp_ids).") "; } // Check the real number of LPs corresponding to the filter in the // database (and if no list was given, get them all) $sql = "SELECT DISTINCT(iid) FROM $lpTable - WHERE $condition_lp"; + WHERE 1=1 $condition_lp"; $result = Database::query($sql); $session_condition = api_get_session_condition($sessionId); diff --git a/public/main/my_space/myStudents.php b/public/main/my_space/myStudents.php index c342669e37..ccb092164c 100644 --- a/public/main/my_space/myStudents.php +++ b/public/main/my_space/myStudents.php @@ -550,12 +550,11 @@ switch ($action) { if ($myCertificate) { $certificate = new Certificate($myCertificate['id'], $studentId); $certificate->deleteCertificate(true); - // Create new one - $certificate = new Certificate(0, $studentId); - $certificate->generatePdfFromCustomCertificate(); - exit; } - break; + // Create new one + $certificate = new Certificate(0, $studentId); + $certificate->generatePdfFromCustomCertificate(); + exit; case 'send_legal': $isBoss = UserManager::userIsBossOfStudent(api_get_user_id(), $studentId); if ($isBoss || api_is_platform_admin()) { From 52372fbf12922b567847e46e22b9a5bfc83c2b95 Mon Sep 17 00:00:00 2001 From: christian Date: Wed, 8 Nov 2023 14:14:56 -0500 Subject: [PATCH 2/4] Reporting: Improve generate certificate - refs BT#21007 --- .../gradebook/custom_certificate.html.twig | 16 ++++++++-------- translations/messages.fr.po | 3 +++ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/public/main/template/default/gradebook/custom_certificate.html.twig b/public/main/template/default/gradebook/custom_certificate.html.twig index 319a3bdc5c..756154b705 100644 --- a/public/main/template/default/gradebook/custom_certificate.html.twig +++ b/public/main/template/default/gradebook/custom_certificate.html.twig @@ -7,11 +7,11 @@ - + diff --git a/translations/messages.fr.po b/translations/messages.fr.po index a138d739de..c82e173591 100644 --- a/translations/messages.fr.po +++ b/translations/messages.fr.po @@ -24160,3 +24160,6 @@ msgstr "À venir" msgid "Past session tab" msgstr "Terminées" + +msgid "User has participate dans de platforme %s the contrat date %s certificate date %s time %s" +msgstr "a participé à une formation en ligne sur la plateforme %s de l’Office franco-allemand pour la Jeunesse.
Du %s au %s.

Cette formation avait pour objectif la préparation linguistique et interculturelle à une mobilité dans un cadre de formation professionnelle." From 0367472c439d6dc7265593a924c70c85209ba0b0 Mon Sep 17 00:00:00 2001 From: Angel Fernando Quiroz Campos Date: Thu, 9 Nov 2023 17:24:41 -0500 Subject: [PATCH 3/4] Webservice: Reporting: Check user roles to return results - refs BT#21114 --- ...CategorizedExerciseResultStateProvider.php | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/CoreBundle/State/CategorizedExerciseResultStateProvider.php b/src/CoreBundle/State/CategorizedExerciseResultStateProvider.php index bd2cd1f514..63a7d8cb35 100644 --- a/src/CoreBundle/State/CategorizedExerciseResultStateProvider.php +++ b/src/CoreBundle/State/CategorizedExerciseResultStateProvider.php @@ -11,6 +11,7 @@ use ApiPlatform\State\ProviderInterface; use Chamilo\CoreBundle\ApiResource\CategorizedExerciseResult; use Chamilo\CoreBundle\Entity\TrackEExercise; use Chamilo\CoreBundle\Security\Authorization\Voter\TrackEExerciseVoter; +use Symfony\Component\Security\Core\Exception\AccessDeniedException; use function count; use Doctrine\ORM\EntityManagerInterface; use Event; @@ -55,7 +56,7 @@ class CategorizedExerciseResultStateProvider implements ProviderInterface ob_start(); - $categoryList = self::displayQuestionListByAttempt( + $categoryList = $this->displayQuestionListByAttempt( $objExercise, $trackExercise ); @@ -70,7 +71,7 @@ class CategorizedExerciseResultStateProvider implements ProviderInterface /** * @throws Exception */ - private static function displayQuestionListByAttempt( + private function displayQuestionListByAttempt( Exercise $objExercise, TrackEExercise $exerciseTracking ): array { @@ -317,8 +318,12 @@ class CategorizedExerciseResultStateProvider implements ProviderInterface } } + if ($this->isAllowedToSeeResults()) { + $show_results = true; + } + if (!$show_results && !$show_only_score && RESULT_DISABLE_RADAR !== $objExercise->results_disabled) { - throw new Exception(get_lang('Not allowed')); + throw new AccessDeniedException(); } // Adding total @@ -332,6 +337,10 @@ class CategorizedExerciseResultStateProvider implements ProviderInterface private static function getStatsTableByAttempt(Exercise $exercise, array $category_list = []): array { + if (empty($category_list)) { + return []; + } + $hide = (int) $exercise->getPageConfigurationAttribute('hide_category_table'); if (1 === $hide) { @@ -375,7 +384,7 @@ class CategorizedExerciseResultStateProvider implements ProviderInterface ]; } - if ($category_list['none']['score'] > 0) { + if (isset($category_list['none']) && $category_list['none']['score'] > 0) { $absolute = ExerciseLib::show_score( $category_list['none']['score'], $category_list['none']['total'], @@ -417,4 +426,15 @@ class CategorizedExerciseResultStateProvider implements ProviderInterface return $stats; } + + private function isAllowedToSeeResults(): bool + { + $isStudentBoss = $this->security->isGranted('ROLE_STUDENT_BOSS'); + $isHRM = $this->security->isGranted('ROLE_RRHH'); + $isSessionAdmin = $this->security->isGranted('ROLE_SESSION_MANAGER'); + $isCourseTutor = $this->security->isGranted('ROLE_CURRENT_COURSE_SESSION_TEACHER'); + $isAllowedToEdit = api_is_allowed_to_edit(null, true); + + return $isAllowedToEdit || $isCourseTutor || $isSessionAdmin || $isHRM || $isStudentBoss; + } } From 2d112da1fe01c7b5cdbfc581a4e4f8c827015be6 Mon Sep 17 00:00:00 2001 From: Angel Fernando Quiroz Campos Date: Thu, 9 Nov 2023 17:31:58 -0500 Subject: [PATCH 4/4] Minor: Format code --- src/CoreBundle/Command/UpdateVueTranslations.php | 6 +++--- .../Api/CreateSocialPostAttachmentAction.php | 8 +++++--- .../Api/SocialPostAttachmentsController.php | 2 -- src/CoreBundle/Entity/SocialPost.php | 4 ++-- .../Migrations/Schema/V200/Version20231022124700.php | 2 +- .../Migrations/Schema/V200/Version20231026221100.php | 4 ++-- .../Migrations/Schema/V200/Version20231026231100.php | 12 ++++++++---- .../Migrations/Schema/V200/Version20231031204300.php | 8 ++++---- .../State/CategorizedExerciseResultStateProvider.php | 2 +- src/CourseBundle/Entity/CLp.php | 2 -- 10 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/CoreBundle/Command/UpdateVueTranslations.php b/src/CoreBundle/Command/UpdateVueTranslations.php index f01b3b081e..8c2ed8b6be 100644 --- a/src/CoreBundle/Command/UpdateVueTranslations.php +++ b/src/CoreBundle/Command/UpdateVueTranslations.php @@ -84,7 +84,7 @@ class UpdateVueTranslations extends Command } /** - * Replace specifiers in a string to allow rendering them by i18n + * Replace specifiers in a string to allow rendering them by i18n. * * * $txt = "Bonjour %s. Je m’appelle %s"; @@ -99,12 +99,12 @@ class UpdateVueTranslations extends Command $type = $matches[1]; return match ($type) { - "s", "d", "f" => "{".$count++."}", + 's', 'd', 'f' => '{'.$count++.'}', default => $matches[0], }; }; - $pattern = "/%([sdf])/"; + $pattern = '/%([sdf])/'; return preg_replace_callback($pattern, $replace, $text); } diff --git a/src/CoreBundle/Controller/Api/CreateSocialPostAttachmentAction.php b/src/CoreBundle/Controller/Api/CreateSocialPostAttachmentAction.php index 66dce03e33..b83b2dd22d 100644 --- a/src/CoreBundle/Controller/Api/CreateSocialPostAttachmentAction.php +++ b/src/CoreBundle/Controller/Api/CreateSocialPostAttachmentAction.php @@ -10,6 +10,8 @@ use Chamilo\CoreBundle\Entity\SocialPost; use Chamilo\CoreBundle\Entity\SocialPostAttachment; use Chamilo\CoreBundle\Entity\User; use Chamilo\CoreBundle\Repository\Node\SocialPostAttachmentRepository; +use DateTime; +use DateTimeZone; use Doctrine\ORM\EntityManager; use Exception; use Symfony\Component\HttpFoundation\File\UploadedFile; @@ -28,12 +30,12 @@ class CreateSocialPostAttachmentAction extends BaseResourceFileAction $socialPostId = $request->request->get('messageId'); if (!$uploadedFile instanceof UploadedFile) { - throw new \Exception('No file uploaded'); + throw new Exception('No file uploaded'); } $socialPost = $em->getRepository(SocialPost::class)->find($socialPostId); if (!$socialPost) { - throw new \Exception('No social post found'); + throw new Exception('No social post found'); } /** @var User $currentUser */ @@ -44,7 +46,7 @@ class CreateSocialPostAttachmentAction extends BaseResourceFileAction $attachment->setFilename($uploadedFile->getClientOriginalName()); $attachment->setSize($uploadedFile->getSize()); $attachment->setInsertUserId($currentUser->getId()); - $attachment->setInsertDateTime(new \DateTime('now', new \DateTimeZone('UTC'))); + $attachment->setInsertDateTime(new DateTime('now', new DateTimeZone('UTC'))); $attachment->setParent($currentUser); $attachment->addUserLink($currentUser); diff --git a/src/CoreBundle/Controller/Api/SocialPostAttachmentsController.php b/src/CoreBundle/Controller/Api/SocialPostAttachmentsController.php index d3f9fc7257..ceaab2e607 100644 --- a/src/CoreBundle/Controller/Api/SocialPostAttachmentsController.php +++ b/src/CoreBundle/Controller/Api/SocialPostAttachmentsController.php @@ -8,13 +8,11 @@ use Chamilo\CoreBundle\Entity\SocialPost; use Chamilo\CoreBundle\Entity\SocialPostAttachment; use Chamilo\CoreBundle\Repository\Node\SocialPostAttachmentRepository; use Doctrine\ORM\EntityManager; -use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\Security\Core\Security; class SocialPostAttachmentsController extends BaseResourceFileAction { - public function __invoke(SocialPost $socialPost, EntityManager $em, Security $security, SocialPostAttachmentRepository $attachmentRepo): JsonResponse { $attachments = $em->getRepository(SocialPostAttachment::class)->findBy(['socialPost' => $socialPost->getId()]); diff --git a/src/CoreBundle/Entity/SocialPost.php b/src/CoreBundle/Entity/SocialPost.php index d9161c2a05..d7c435853c 100644 --- a/src/CoreBundle/Entity/SocialPost.php +++ b/src/CoreBundle/Entity/SocialPost.php @@ -147,8 +147,8 @@ class SocialPost #[ORM\OneToMany( targetEntity: SocialPostAttachment::class, - mappedBy: "socialPost", - cascade: ["persist", "remove"], + mappedBy: 'socialPost', + cascade: ['persist', 'remove'], orphanRemoval: true )] private Collection $attachments; diff --git a/src/CoreBundle/Migrations/Schema/V200/Version20231022124700.php b/src/CoreBundle/Migrations/Schema/V200/Version20231022124700.php index e757a7b9b4..43951a4c3e 100644 --- a/src/CoreBundle/Migrations/Schema/V200/Version20231022124700.php +++ b/src/CoreBundle/Migrations/Schema/V200/Version20231022124700.php @@ -453,7 +453,7 @@ final class Version20231022124700 extends AbstractMigrationChamilo // Reconstructing the URL with the new courseId and adjusted parameters. return $matches[1].'?cid='.$courseId.'&sid='.$matches[5].'&gid='.$matches[7].$remainingParams; - // Return the new URL. + // Return the new URL. }, $htmlContent ); diff --git a/src/CoreBundle/Migrations/Schema/V200/Version20231026221100.php b/src/CoreBundle/Migrations/Schema/V200/Version20231026221100.php index 8534a6a025..19ffb20803 100644 --- a/src/CoreBundle/Migrations/Schema/V200/Version20231026221100.php +++ b/src/CoreBundle/Migrations/Schema/V200/Version20231026221100.php @@ -20,8 +20,8 @@ class Version20231026221100 extends AbstractMigrationChamilo { if (false === $schema->hasTable('social_post_attachments')) { $this->addSql("CREATE TABLE social_post_attachments (id INT AUTO_INCREMENT NOT NULL, social_post_id BIGINT DEFAULT NULL, resource_node_id BIGINT DEFAULT NULL, path VARCHAR(255) NOT NULL, filename LONGTEXT NOT NULL, size INT NOT NULL, sys_insert_user_id INT NOT NULL, sys_insert_datetime DATETIME NOT NULL COMMENT '(DC2Type:datetime)', sys_lastedit_user_id INT DEFAULT NULL, sys_lastedit_datetime DATETIME DEFAULT NULL COMMENT '(DC2Type:datetime)', INDEX IDX_DF2A8F34C4F2D6B1 (social_post_id), UNIQUE INDEX UNIQ_DF2A8F341BAD783F (resource_node_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB ROW_FORMAT = DYNAMIC;"); - $this->addSql("ALTER TABLE social_post_attachments ADD CONSTRAINT FK_DF2A8F34C4F2D6B1 FOREIGN KEY (social_post_id) REFERENCES social_post (id) ON DELETE CASCADE;"); - $this->addSql("ALTER TABLE social_post_attachments ADD CONSTRAINT FK_DF2A8F341BAD783F FOREIGN KEY (resource_node_id) REFERENCES resource_node (id) ON DELETE CASCADE;"); + $this->addSql('ALTER TABLE social_post_attachments ADD CONSTRAINT FK_DF2A8F34C4F2D6B1 FOREIGN KEY (social_post_id) REFERENCES social_post (id) ON DELETE CASCADE;'); + $this->addSql('ALTER TABLE social_post_attachments ADD CONSTRAINT FK_DF2A8F341BAD783F FOREIGN KEY (resource_node_id) REFERENCES resource_node (id) ON DELETE CASCADE;'); } } } diff --git a/src/CoreBundle/Migrations/Schema/V200/Version20231026231100.php b/src/CoreBundle/Migrations/Schema/V200/Version20231026231100.php index f8f0a7424b..b1f818812e 100644 --- a/src/CoreBundle/Migrations/Schema/V200/Version20231026231100.php +++ b/src/CoreBundle/Migrations/Schema/V200/Version20231026231100.php @@ -11,6 +11,8 @@ use Chamilo\CoreBundle\Entity\SocialPostAttachment; use Chamilo\CoreBundle\Migrations\AbstractMigrationChamilo; use Chamilo\CoreBundle\Repository\Node\SocialPostAttachmentRepository; use Chamilo\CoreBundle\Repository\Node\UserRepository; +use DateTime; +use DateTimeZone; use DirectoryIterator; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Schema\Schema; @@ -39,12 +41,14 @@ final class Version20231026231100 extends AbstractMigrationChamilo $sub = $em->createQueryBuilder(); $sub->select('sp.id') - ->from('Chamilo\CoreBundle\Entity\SocialPost', 'sp'); + ->from('Chamilo\CoreBundle\Entity\SocialPost', 'sp') + ; $qb = $em->createQueryBuilder(); $qb->select('ma') ->from('Chamilo\CoreBundle\Entity\MessageAttachment', 'ma') - ->where($qb->expr()->in('ma.message', $sub->getDQL())); + ->where($qb->expr()->in('ma.message', $sub->getDQL())) + ; $query = $qb->getQuery(); $messageAttachments = $query->getResult(); @@ -73,7 +77,7 @@ final class Version20231026231100 extends AbstractMigrationChamilo $attachment->setFilename($uploadFile->getClientOriginalName()); $attachment->setSize($uploadFile->getSize()); $attachment->setInsertUserId($sender->getId()); - $attachment->setInsertDateTime(new \DateTime('now', new \DateTimeZone('UTC'))); + $attachment->setInsertDateTime(new DateTime('now', new DateTimeZone('UTC'))); $attachment->setParent($sender); $attachment->addUserLink($sender); $attachment->setCreator($sender); @@ -102,7 +106,7 @@ final class Version20231026231100 extends AbstractMigrationChamilo if ($fileInfo->isDir()) { $result = $this->findFileRecursively($filePath, $targetFile); - if ($result !== null) { + if (null !== $result) { return $result; } } else { diff --git a/src/CoreBundle/Migrations/Schema/V200/Version20231031204300.php b/src/CoreBundle/Migrations/Schema/V200/Version20231031204300.php index b8c1aef90f..b4fe16659a 100644 --- a/src/CoreBundle/Migrations/Schema/V200/Version20231031204300.php +++ b/src/CoreBundle/Migrations/Schema/V200/Version20231031204300.php @@ -13,13 +13,13 @@ final class Version20231031204300 extends AbstractMigrationChamilo { public function getDescription(): string { - return "Allow message sender to be null"; + return 'Allow message sender to be null'; } public function up(Schema $schema): void { - $this->addSql("ALTER TABLE message DROP FOREIGN KEY FK_B6BD307FF6C43E79"); - $this->addSql("ALTER TABLE message CHANGE user_sender_id user_sender_id INT DEFAULT NULL"); - $this->addSql("ALTER TABLE message ADD CONSTRAINT FK_B6BD307FF6C43E79 FOREIGN KEY (user_sender_id) REFERENCES user (id) ON DELETE SET NULL"); + $this->addSql('ALTER TABLE message DROP FOREIGN KEY FK_B6BD307FF6C43E79'); + $this->addSql('ALTER TABLE message CHANGE user_sender_id user_sender_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE message ADD CONSTRAINT FK_B6BD307FF6C43E79 FOREIGN KEY (user_sender_id) REFERENCES user (id) ON DELETE SET NULL'); } } diff --git a/src/CoreBundle/State/CategorizedExerciseResultStateProvider.php b/src/CoreBundle/State/CategorizedExerciseResultStateProvider.php index 63a7d8cb35..777e9864a4 100644 --- a/src/CoreBundle/State/CategorizedExerciseResultStateProvider.php +++ b/src/CoreBundle/State/CategorizedExerciseResultStateProvider.php @@ -11,7 +11,6 @@ use ApiPlatform\State\ProviderInterface; use Chamilo\CoreBundle\ApiResource\CategorizedExerciseResult; use Chamilo\CoreBundle\Entity\TrackEExercise; use Chamilo\CoreBundle\Security\Authorization\Voter\TrackEExerciseVoter; -use Symfony\Component\Security\Core\Exception\AccessDeniedException; use function count; use Doctrine\ORM\EntityManagerInterface; use Event; @@ -22,6 +21,7 @@ use Question; use QuestionOptionsEvaluationPlugin; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; +use Symfony\Component\Security\Core\Exception\AccessDeniedException; use TestCategory; class CategorizedExerciseResultStateProvider implements ProviderInterface diff --git a/src/CourseBundle/Entity/CLp.php b/src/CourseBundle/Entity/CLp.php index fb08c6fcea..2f5bb470e6 100644 --- a/src/CourseBundle/Entity/CLp.php +++ b/src/CourseBundle/Entity/CLp.php @@ -199,8 +199,6 @@ class CLp extends AbstractResource implements ResourceInterface, ResourceShowCou /** * Get lpType. - * - * @return int */ public function getLpType(): int {
- +
@@ -20,7 +20,7 @@ {{ 'CertificateHeader' | get_lang }} - +
@@ -30,13 +30,13 @@
- +

{{ complete_name }}

- {{ 'UserHasParticipateDansDePlatformeXTheContratDateXCertificateDateXTimeX' | get_lang | format(_s.site_name, certificate_generated_date_no_time, terms_validation_date_no_time, time_in_platform_in_hours)}} + {{ 'User has participate dans de platforme %s the contrat date %s certificate date %s time %s' | trans | format(_s.site_name, certificate_generated_date_no_time, terms_validation_date_no_time, time_in_platform_in_hours)}}


{{ 'ThisTrainingHasXHours' | get_lang | format(time_in_platform_in_hours)}}


@@ -63,7 +63,7 @@
- +
@@ -73,11 +73,11 @@
- + - +
{{ 'CertificateFooter' | get_lang }}