From c34412ebff5a7ffe2f836572b4c2f39068d4f821 Mon Sep 17 00:00:00 2001 From: christianbeeznst Date: Mon, 29 Jan 2024 00:24:01 -0500 Subject: [PATCH 1/4] Internal: Migration: Refactor certificate file migration - refs BT#21007 --- .../gradebook_display_certificate.php | 2 +- public/main/gradebook/lib/GradebookUtils.php | 13 +- public/main/inc/lib/certificate.lib.php | 311 ++++-------------- public/main/my_space/myStudents.php | 15 +- .../Entity/GradebookCertificate.php | 7 +- src/CoreBundle/Framework/Container.php | 6 + .../Schema/V200/Version20240128205500.php | 87 +++++ .../GradebookCertificateRepository.php | 149 +++++++++ 8 files changed, 327 insertions(+), 263 deletions(-) create mode 100644 src/CoreBundle/Migrations/Schema/V200/Version20240128205500.php create mode 100644 src/CoreBundle/Repository/GradebookCertificateRepository.php diff --git a/public/main/gradebook/gradebook_display_certificate.php b/public/main/gradebook/gradebook_display_certificate.php index d306949f4a..361af4e780 100644 --- a/public/main/gradebook/gradebook_display_certificate.php +++ b/public/main/gradebook/gradebook_display_certificate.php @@ -204,7 +204,7 @@ if ('delete' === $action) { $check = Security::check_token('get'); if ($check) { $certificate = new Certificate($_GET['certificate_id']); - $result = $certificate->deleteCertificate(true); + $result = $certificate->deleteCertificate(); Security::clear_token(); if (true == $result) { echo Display::return_message(get_lang('Certificate removed'), 'confirmation'); diff --git a/public/main/gradebook/lib/GradebookUtils.php b/public/main/gradebook/lib/GradebookUtils.php index 2f1feda216..47b03fa7ec 100644 --- a/public/main/gradebook/lib/GradebookUtils.php +++ b/public/main/gradebook/lib/GradebookUtils.php @@ -7,6 +7,7 @@ use Chamilo\CoreBundle\Component\Utils\ActionIcon; use Chamilo\CoreBundle\Component\Utils\ToolIcon; use Chamilo\CoreBundle\Component\Utils\ObjectIcon; use Chamilo\CoreBundle\Component\Utils\StateIcon; +use Chamilo\CoreBundle\Framework\Container; /** * Class GradebookUtils. @@ -654,16 +655,10 @@ class GradebookUtils */ public static function get_certificate_by_user_id($cat_id, $user_id) { - $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CERTIFICATE); - $cat_id = (int) $cat_id; - $user_id = (int) $user_id; - - $sql = "SELECT * FROM $table - WHERE cat_id = $cat_id AND user_id = $user_id "; - - $result = Database::query($sql); + $repository = Container::getGradeBookCertificateRepository(); + $certificate = $repository->getCertificateByUserId($cat_id, $user_id, true); - return Database::fetch_array($result, 'ASSOC'); + return $certificate; } /** diff --git a/public/main/inc/lib/certificate.lib.php b/public/main/inc/lib/certificate.lib.php index b50c78b33c..80db0d256f 100644 --- a/public/main/inc/lib/certificate.lib.php +++ b/public/main/inc/lib/certificate.lib.php @@ -3,9 +3,14 @@ /* For licensing terms, see /license.txt */ use Chamilo\CoreBundle\Entity\GradebookCategory; +use Chamilo\CoreBundle\Entity\PersonalFile; +use Chamilo\CoreBundle\Entity\ResourceFile; +use Chamilo\CoreBundle\Entity\ResourceNode; use Chamilo\CoreBundle\Framework\Container; use Endroid\QrCode\ErrorCorrectionLevel; use Endroid\QrCode\QrCode; +use JetBrains\PhpStorm\NoReturn; +use Symfony\Component\HttpFoundation\File\UploadedFile; /** * Certificate Class @@ -71,8 +76,6 @@ class Certificate extends Model } if ($this->user_id) { - // Need to be called before any operation - $this->checkCertificatePath(); // To force certification generation if ($this->force_certificate_generation) { $this->generate(['certificate_path' => ''], $sendNotification); @@ -91,87 +94,33 @@ class Certificate extends Model !empty($this->certification_user_path) && isset($this->certificate_data['path_certificate']) ) { - $pathinfo = pathinfo($this->certificate_data['path_certificate']); + //$pathinfo = pathinfo($this->certificate_data['path_certificate']); $this->html_file = $this->certificate_data['path_certificate']; - $this->qr_file = $this->certification_user_path.$pathinfo['filename'].'_qr.png'; + //$this->qr_file = $this->certification_user_path.$pathinfo['filename'].'_qr.png'; } else { - $this->checkCertificatePath(); + //$this->checkCertificatePath(); if ('true' === api_get_setting('document.allow_general_certificate')) { - $container = Container::getResourceNodeRepository(); - $filesystem = $container->getFileSystem(); // General certificate - $name = hash('sha256', $this->user_id.$this->certificate_data['cat_id']); - $arrayName = str_split(substr($name, 0, 3)); - - $my_path_certificate = $arrayName[0]. - DIRECTORY_SEPARATOR. - $arrayName[1]. - DIRECTORY_SEPARATOR. - $arrayName[2]. - DIRECTORY_SEPARATOR; - $this->certification_user_path = $my_path_certificate; - $path_certificate = $my_path_certificate.$name.'.html'; - - // Getting QR filename - $file_info = pathinfo($path_certificate); + $name = hash('sha256', $this->user_id . $this->certificate_data['cat_id']); + $fileName = $name . '.html'; $content = $this->generateCustomCertificate(); + $gradebookCertificateRepo = Container::getGradeBookCertificateRepository(); + $personalFile = $gradebookCertificateRepo->generateCertificatePersonalFile($this->user_id, $fileName, $content); - $my_new_content_html = str_replace( - '((certificate_barcode))', - Display::img( - $this->certification_web_user_path.$file_info['filename'].'_qr.png', - 'QR' - ), - $content - ); - - $my_new_content_html = mb_convert_encoding( - $my_new_content_html, - 'UTF-8', - api_get_system_encoding() - ); - - $this->html_file = $path_certificate; - $filesystem->write($path_certificate, $my_new_content_html); - - if ($filesystem->fileExists($path_certificate)) { + if (null !== $personalFile) { // Updating the path self::updateUserCertificateInfo( 0, $this->user_id, - $path_certificate, + $fileName, $updateCertificateData ); - $this->certificate_data['path_certificate'] = $path_certificate; + $this->certificate_data['path_certificate'] = $fileName; } } } } - /** - * Checks if the certificate user path directory is created. - */ - public function checkCertificatePath() - { - $this->certification_user_path = null; - - // Setting certification path - $path_info = UserManager::getUserPathById($this->user_id, 'system'); - $web_path_info = UserManager::getUserPathById($this->user_id, 'web'); - - if (!empty($path_info) && isset($path_info)) { - $this->certification_user_path = $path_info.'certificate/'; - $this->certification_web_user_path = $web_path_info.'certificate/'; - $mode = api_get_permissions_for_new_directories(); - if (!is_dir($path_info)) { - mkdir($path_info, $mode, true); - } - if (!is_dir($this->certification_user_path)) { - mkdir($this->certification_user_path, $mode); - } - } - } - /** * Deletes the current certificate object. This is generally triggered by * the teacher from the gradebook tool to re-generate the certificate because @@ -181,30 +130,14 @@ class Certificate extends Model * * @return bool */ - public function deleteCertificate($force_delete = false) + public function deleteCertificate(): bool { - $delete_db = false; if (!empty($this->certificate_data)) { - if (!is_null($this->html_file) || '' != $this->html_file || strlen($this->html_file)) { - // Deleting HTML file - if (is_file($this->html_file)) { - @unlink($this->html_file); - if (false === is_file($this->html_file)) { - $delete_db = true; - } else { - $delete_db = false; - } - } - // Deleting QR code PNG image file - if (is_file($this->qr_file)) { - @unlink($this->qr_file); - } - if ($delete_db || $force_delete) { - return parent::delete($this->certificate_data['id']); - } - } else { - return parent::delete($this->certificate_data['id']); - } + $categoryId = isset($this->certificate_data['cat_id']) ? (int) $this->certificate_data['cat_id'] : 0; + $gradebookCertificateRepo = Container::getGradeBookCertificateRepository(); + $gradebookCertificateRepo->deleteCertificateAndRelatedFiles($this->certificate_data['user_id'], $categoryId); + + return true; } return false; @@ -220,34 +153,23 @@ class Certificate extends Model */ public function generate($params = [], $sendNotification = false) { - // The user directory should be set - if (empty($this->certification_user_path) && - false === $this->force_certificate_generation - ) { - return false; - } $result = false; $params['hide_print_button'] = isset($params['hide_print_button']) ? true : false; - $pathToOriginalCertificate = $params['certificate_path'] ?? ''; $categoryId = 0; - $my_category = []; + $isCertificateAvailableInCategory = false; + $category = null; if (isset($this->certificate_data['cat_id'])) { - $categoryId = $this->certificate_data['cat_id']; - - $my_category = Category::load($categoryId); + $categoryId = (int) $this->certificate_data['cat_id']; + $myCategory = Category::load($categoryId); $repo = Container::getGradeBookCategoryRepository(); - $gradebookCategory = $repo->find($categoryId); - + /** @var GradebookCategory $category */ + $category = $repo->find($categoryId); + $isCertificateAvailableInCategory = !empty($categoryId) && $myCategory[0]->is_certificate_available($this->user_id); } $container = Container::getResourceNodeRepository(); $filesystem = $container->getFileSystem(); - if (!empty($categoryId) && - $my_category[0]->is_certificate_available($this->user_id) - ) { - /** @var GradebookCategory $category */ - $category = $gradebookCategory; - + if ($isCertificateAvailableInCategory && null !== $category) { $courseInfo = api_get_course_info($category->getCourse()->getCode()); $courseId = $courseInfo['real_id']; $sessionId = $category->getSession() ? $category->getSession()->getId() : 0; @@ -260,8 +182,8 @@ class Certificate extends Model $sessionId ); - if ($filesystem->fileExists($pathToOriginalCertificate) && !empty($this->certificate_data)) { - $new_content_html = GradebookUtils::get_user_certificate_content( + if (!empty($this->certificate_data)) { + $newContentHtml = GradebookUtils::get_user_certificate_content( $this->user_id, $category->getCourse()->getId(), $category->getSession() ? $category->getSession()->getId() : 0, @@ -279,73 +201,36 @@ class Certificate extends Model return true; } else { // Creating new name - $name = hash('sha256', $this->user_id.$categoryId); - $arrayName = str_split(substr($name, 0, 3)); - - $myPathCertificate = - $arrayName[0] - .DIRECTORY_SEPARATOR - .$arrayName[1].DIRECTORY_SEPARATOR - .$arrayName[2].DIRECTORY_SEPARATOR; - - $this->certification_user_path = $myPathCertificate; - - $path_certificate = $myPathCertificate.$name.'.html'; - - // Getting QR filename - $file_info = pathinfo($pathToOriginalCertificate); - $qr_code_filename = $this->certification_user_path.$name.'_qr.png'; - - $newContent = str_replace( - '((certificate_barcode))', - Display::img( - $this->certification_web_user_path.$file_info['filename'].'_qr.png', - 'QR' - ), - $new_content_html['content'] - ); - - $newContent = api_convert_encoding( - $newContent, - 'UTF-8', - api_get_system_encoding() - ); - $filesystem->write($path_certificate, $newContent); + $name = hash('sha256', $this->user_id . $categoryId); + $fileName = $name . '.html'; + $gradebookCertificateRepo = Container::getGradeBookCertificateRepository(); + $personalFile = $gradebookCertificateRepo->generateCertificatePersonalFile($this->user_id, $fileName, $newContentHtml['content']); - if ($filesystem->fileExists($path_certificate)) { + if (null !== $personalFile) { $result = true; // Updating the path $this->updateUserCertificateInfo( $this->certificate_data['cat_id'], $this->user_id, - $path_certificate + $fileName ); - $this->certificate_data['path_certificate'] = $path_certificate; + $this->certification_user_path = $fileName; + $this->certificate_data['path_certificate'] = $fileName; if ($this->isHtmlFileGenerated()) { - if (!empty($file_info)) { - $text = $this->parseCertificateVariables( - $new_content_html['variables'] + if ($sendNotification) { + $subject = get_lang('Certificate notification'); + $message = nl2br(get_lang('((user_first_name)),')); + $score = $this->certificate_data['score_certificate']; + self::sendNotification( + $subject, + $message, + api_get_user_info($this->user_id), + $courseInfo, + [ + 'score_certificate' => $score, + ] ); -/* $this->generateQRImage( - $text, - $qr_code_filename - );*/ - - if ($sendNotification) { - $subject = get_lang('Certificate notification'); - $message = nl2br(get_lang('((user_first_name)),')); - $score = $this->certificate_data['score_certificate']; - self::sendNotification( - $subject, - $message, - api_get_user_info($this->user_id), - $courseInfo, - [ - 'score_certificate' => $score, - ] - ); - } } } } @@ -355,54 +240,20 @@ class Certificate extends Model } } } else { - $this->checkCertificatePath(); - // General certificate - $name = hash('sha256', $this->user_id.$categoryId); - $arrayName = str_split(substr($name, 0, 3)); - - $myPathCertificate = - $arrayName[0] - .DIRECTORY_SEPARATOR - .$arrayName[1].DIRECTORY_SEPARATOR - .$arrayName[2].DIRECTORY_SEPARATOR; - - $this->certification_user_path = $myPathCertificate; - - $path_certificate = $myPathCertificate.$name.'.html'; - - // Getting QR filename - $file_info = pathinfo($pathToOriginalCertificate); - $content = $this->generateCustomCertificate(); - - $my_new_content_html = str_replace( - '((certificate_barcode))', - Display::img( - $this->certification_web_user_path.$file_info['filename'].'_qr.png', - 'QR' - ), - $content - ); - - $my_new_content_html = mb_convert_encoding( - $my_new_content_html, - 'UTF-8', - api_get_system_encoding() - ); + $name = hash('sha256', $this->user_id . $categoryId); + $fileName = $name . '.html'; + $certificateContent = $this->generateCustomCertificate($fileName); - $filesystem->write($path_certificate, $my_new_content_html); + $gradebookCertificateRepo = Container::getGradeBookCertificateRepository(); + $personalFile = $gradebookCertificateRepo->generateCertificatePersonalFile($this->user_id, $fileName, $certificateContent); - if ($filesystem->fileExists($path_certificate)) { - $result = true; - // Updating the path - self::updateUserCertificateInfo( - 0, - $this->user_id, - $path_certificate - ); - $this->certificate_data['path_certificate'] = $path_certificate; + if ($personalFile !== null) { + $personalRepo = Container::getPersonalFileRepository(); + $this->certificate_data['file_content'] = $personalRepo->getResourceFileContent($personalFile); + $this->certificate_data['path_certificate'] = $fileName; } - return $result; + return true; } return false; @@ -536,7 +387,7 @@ class Certificate extends Model * * @return bool */ - public function generateQRImage($text, $path) + public function generateQRImage($text, $path): bool { throw new \Exception('generateQRImage'); if (!empty($text) && !empty($path)) { @@ -724,20 +575,10 @@ class Certificate extends Model /** * @return string */ - public function generateCustomCertificate() + public function generateCustomCertificate(string $fileName = '') { - $myCertificate = GradebookUtils::get_certificate_by_user_id( - 0, - $this->user_id - ); - if (empty($myCertificate)) { - GradebookUtils::registerUserInfoAboutCertificate( - 0, - $this->user_id, - 100, - api_get_utc_datetime() - ); - } + $certificateRepo = Container::getGradeBookCertificateRepository(); + $certificateRepo->registerUserInfoAboutCertificate(0, $this->user_id, 100, $fileName); $userInfo = api_get_user_info($this->user_id); $extraFieldValue = new ExtraFieldValue('user'); @@ -762,15 +603,6 @@ class Certificate extends Model $courseId = $course->getId(); $category = $gradeBookRepo->findOneBy(['course' => $course, 'session' => $session['session_id']]); - /*$gradebookCategories = Category::load( - null, - null, - $courseCode, - null, - false, - $session['session_id'] - );*/ - if (null !== $category) { $result = Category::userFinishedCourse( $this->user_id, @@ -794,9 +626,6 @@ class Certificate extends Model if ($result) { $courseList[$courseId]['approved'] = true; $coursesApproved[$courseId] = $course->getTitle(); - - // Find time spent in LP - //$totalTimeInLearningPaths += $timeSpent; $allCoursesApproved[] = true; } $courseList[$courseId]['time_spent'] += $timeSpent; @@ -893,12 +722,16 @@ class Certificate extends Model $page_format = 'landscape' == $params['orientation'] ? 'A4-L' : 'A4'; $pdf = new PDF($page_format, $params['orientation'], $params); - $pdf->html_to_pdf( - $this->html_file, + $pdf->content_to_pdf( + $this->certificate_data['file_content'], + null, get_lang('Certificates'), + api_get_course_id(), + 'D', + false, null, false, - false + true ); } diff --git a/public/main/my_space/myStudents.php b/public/main/my_space/myStudents.php index 15e6e68e38..c843327dd6 100644 --- a/public/main/my_space/myStudents.php +++ b/public/main/my_space/myStudents.php @@ -544,19 +544,12 @@ switch ($action) { header('Location: '.$currentUrl); exit; } + break; case 'generate_certificate': - // Delete old certificate - $myCertificate = GradebookUtils::get_certificate_by_user_id( - 0, - $studentId - ); - if ($myCertificate) { - $certificate = new Certificate($myCertificate['id'], $studentId); - $certificate->deleteCertificate(true); - } - // Create new one - $certificate = new Certificate(0, $studentId); + $gradebookCertificateRepo = Container::getGradeBookCertificateRepository(); + $gradebookCertificateRepo->deleteCertificateAndRelatedFiles($studentId, 0); + $certificate = new Certificate(83, $studentId); $certificate->generatePdfFromCustomCertificate(); exit; case 'send_legal': diff --git a/src/CoreBundle/Entity/GradebookCertificate.php b/src/CoreBundle/Entity/GradebookCertificate.php index 43f6651198..179b86f391 100644 --- a/src/CoreBundle/Entity/GradebookCertificate.php +++ b/src/CoreBundle/Entity/GradebookCertificate.php @@ -6,6 +6,7 @@ declare(strict_types=1); namespace Chamilo\CoreBundle\Entity; +use Chamilo\CoreBundle\Repository\GradebookCertificateRepository; use Chamilo\CoreBundle\Traits\UserTrait; use DateTime; use Doctrine\ORM\Mapping as ORM; @@ -13,7 +14,7 @@ use Gedmo\Mapping\Annotation as Gedmo; #[ORM\Table(name: 'gradebook_certificate')] #[ORM\Index(name: 'idx_gradebook_certificate_user_id', columns: ['user_id'])] -#[ORM\Entity] +#[ORM\Entity(repositoryClass: GradebookCertificateRepository::class)] class GradebookCertificate { use UserTrait; @@ -25,7 +26,7 @@ class GradebookCertificate #[ORM\ManyToOne(targetEntity: GradebookCategory::class)] #[ORM\JoinColumn(name: 'cat_id', referencedColumnName: 'id', onDelete: 'CASCADE')] - protected GradebookCategory $category; + protected ?GradebookCategory $category = null; #[ORM\ManyToOne(targetEntity: User::class, inversedBy: 'gradeBookCertificates')] #[ORM\JoinColumn(name: 'user_id', referencedColumnName: 'id', onDelete: 'CASCADE')] @@ -117,7 +118,7 @@ class GradebookCertificate return $this; } - public function getCategory(): GradebookCategory + public function getCategory(): ?GradebookCategory { return $this->category; } diff --git a/src/CoreBundle/Framework/Container.php b/src/CoreBundle/Framework/Container.php index dc5edfbb62..5e6efd9bd3 100644 --- a/src/CoreBundle/Framework/Container.php +++ b/src/CoreBundle/Framework/Container.php @@ -14,6 +14,7 @@ use Chamilo\CoreBundle\Repository\CourseCategoryRepository; use Chamilo\CoreBundle\Repository\ExtraFieldOptionsRepository; use Chamilo\CoreBundle\Repository\ExtraFieldRepository; use Chamilo\CoreBundle\Repository\GradeBookCategoryRepository; +use Chamilo\CoreBundle\Repository\GradebookCertificateRepository; use Chamilo\CoreBundle\Repository\LanguageRepository; use Chamilo\CoreBundle\Repository\LegalRepository; use Chamilo\CoreBundle\Repository\MessageRepository; @@ -360,6 +361,11 @@ class Container return self::$container->get(GradeBookCategoryRepository::class); } + public static function getGradeBookCertificateRepository(): GradebookCertificateRepository + { + return self::$container->get(GradebookCertificateRepository::class); + } + public static function getGroupRepository(): CGroupRepository { return self::$container->get(CGroupRepository::class); diff --git a/src/CoreBundle/Migrations/Schema/V200/Version20240128205500.php b/src/CoreBundle/Migrations/Schema/V200/Version20240128205500.php new file mode 100644 index 0000000000..2eb5d81d16 --- /dev/null +++ b/src/CoreBundle/Migrations/Schema/V200/Version20240128205500.php @@ -0,0 +1,87 @@ +getContainer(); + $em = $this->getEntityManager(); + + $kernel = $container->get('kernel'); + $rootPath = $kernel->getProjectDir(); + + $q = $em->createQuery('SELECT u FROM Chamilo\CoreBundle\Entity\User u'); + + foreach ($q->toIterable() as $userEntity) { + $id = $userEntity->getId(); + $path = 'users/' . substr((string) $id, 0, 1) . '/' . $id . '/'; + + $certificateDir = $rootPath . '/app/upload/' . $path . 'certificate/'; + + if (!is_dir($certificateDir)) { + continue; + } + + $files = glob($certificateDir . '*'); + + foreach ($files as $file) { + if (!is_file($file)) { + continue; + } + + $originalTitle = basename($file); + + // Search in gradebook_certificate for a record with a path_certificate that matches $originalTitle + $certificate = $em->getRepository(GradebookCertificate::class)->findOneBy(['pathCertificate' => '/' . $originalTitle]); + if (!$certificate) { + // If not found, continue with the next file + continue; + } + + $catId = null !== $certificate->getCategory() ? $certificate->getCategory()->getId() : 0; + $newTitle = hash('sha256', $id . $catId) . '.html'; + + $existingFile = $em->getRepository(PersonalFile::class)->findOneBy(['title' => $newTitle]); + if ($existingFile) { + error_log('MIGRATIONS :: Skipping file -- ' . $file . ' (Already exists)'); + continue; + } + + error_log('MIGRATIONS :: Processing file -- ' . $file); + + $personalFile = new PersonalFile(); + $personalFile->setTitle($newTitle); + $personalFile->setCreator($userEntity); + $personalFile->setParentResourceNode($userEntity->getResourceNode()->getId()); + $personalFile->setResourceName($newTitle); + $mimeType = mime_content_type($file); + $uploadedFile = new UploadedFile($file, $newTitle, $mimeType, null, true); + $personalFile->setUploadFile($uploadedFile); + $personalFile->addUserLink($userEntity); + + $em->persist($personalFile); + $em->flush(); + + // Update the record in gradebook_certificate with the new title + $certificate->setPathCertificate('/' . $newTitle); + $em->flush(); + + } + } + } +} diff --git a/src/CoreBundle/Repository/GradebookCertificateRepository.php b/src/CoreBundle/Repository/GradebookCertificateRepository.php new file mode 100644 index 0000000000..a13c0dec09 --- /dev/null +++ b/src/CoreBundle/Repository/GradebookCertificateRepository.php @@ -0,0 +1,149 @@ +createQueryBuilder('gc') + ->where('gc.user = :userId') + ->setParameter('userId', $userId) + ->setMaxResults(1); + + if ($catId === 0) { + $catId = null; + } + + if (null === $catId) { + $qb->andWhere('gc.category IS NULL'); + } else { + $qb->andWhere('gc.category = :catId') + ->setParameter('catId', $catId); + } + + $qb->orderBy('gc.id', 'ASC'); + + $query = $qb->getQuery(); + + if ($asArray) { + try { + return $query->getOneOrNullResult(AbstractQuery::HYDRATE_ARRAY); + } catch (\Doctrine\ORM\NonUniqueResultException $e) { + return null; + } + } else { + try { + return $query->getOneOrNullResult(); + } catch (\Doctrine\ORM\NonUniqueResultException $e) { + return null; + } + } + } + + public function registerUserInfoAboutCertificate(int $catId, int $userId, float $scoreCertificate, string $fileName = ''): void + { + $existingCertificate = $this->getCertificateByUserId($catId === 0 ? null : $catId, $userId); + + if (!$existingCertificate) { + $certificate = new GradebookCertificate(); + + $category = $catId === 0 ? null : $this->_em->getRepository(GradebookCategory::class)->find($catId); + $user = $this->_em->getRepository(User::class)->find($userId); + + if (!empty($fileName)) { + $fileName = '/'.$fileName; + } + + if ($category) { + $certificate->setCategory($category); + } + $certificate->setUser($user); + $certificate->setPathCertificate($fileName); + $certificate->setScoreCertificate($scoreCertificate); + $certificate->setCreatedAt(new DateTime()); + + $this->_em->persist($certificate); + $this->_em->flush(); + } + } + + public function generateCertificatePersonalFile(int $userId, string $fileName, string $certificateContent): ?PersonalFile + { + $em = $this->getEntityManager(); + $userEntity = $em->getRepository(User::class)->find($userId); + + $existingFile = $em->getRepository(PersonalFile::class)->findOneBy(['title' => $fileName]); + + if (!$existingFile) { + $tempFilePath = tempnam(sys_get_temp_dir(), 'cert'); + file_put_contents($tempFilePath, $certificateContent); + + $mimeType = mime_content_type($tempFilePath); + $uploadedFile = new UploadedFile($tempFilePath, $fileName, $mimeType, null, true); + + $personalFile = new PersonalFile(); + $personalFile->setTitle($fileName); + $personalFile->setCreator($userEntity); + $personalFile->setParentResourceNode($userEntity->getResourceNode()->getId()); + $personalFile->setResourceName($fileName); + $personalFile->setUploadFile($uploadedFile); + $personalFile->addUserLink($userEntity); + + $em->persist($personalFile); + $em->flush(); + + unlink($tempFilePath); + + return $personalFile; + } + + return $existingFile; + } + + public function deleteCertificateAndRelatedFiles(int $userId, int $catId): bool + { + $em = $this->getEntityManager(); + $certificate = $this->getCertificateByUserId($catId, $userId); + + if (!$certificate) { + + return false; + } + + $title = basename(ltrim($certificate->getPathCertificate(), '/')); + $personalFile = $em->getRepository(PersonalFile::class)->findOneBy(['title' => $title]); + + if (!$personalFile) { + + return false; + } + + $em->remove($personalFile); + $em->flush(); + + $em->remove($certificate); + $em->flush(); + + return true; + } +} From de99429f805697a29043633d0678d400fc62195a Mon Sep 17 00:00:00 2001 From: christianbeeznst Date: Mon, 29 Jan 2024 11:34:42 -0500 Subject: [PATCH 2/4] Internal: Migration: Fix undefined variable cat_id - refs BT#21007 --- public/main/inc/lib/certificate.lib.php | 3 ++- public/main/my_space/myStudents.php | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/public/main/inc/lib/certificate.lib.php b/public/main/inc/lib/certificate.lib.php index 80db0d256f..d928c96d21 100644 --- a/public/main/inc/lib/certificate.lib.php +++ b/public/main/inc/lib/certificate.lib.php @@ -101,7 +101,8 @@ class Certificate extends Model //$this->checkCertificatePath(); if ('true' === api_get_setting('document.allow_general_certificate')) { // General certificate - $name = hash('sha256', $this->user_id . $this->certificate_data['cat_id']); + $categoryId = isset($this->certificate_data['cat_id']) ? (int) $this->certificate_data['cat_id'] : 0; + $name = hash('sha256', $this->user_id . $categoryId); $fileName = $name . '.html'; $content = $this->generateCustomCertificate(); $gradebookCertificateRepo = Container::getGradeBookCertificateRepository(); diff --git a/public/main/my_space/myStudents.php b/public/main/my_space/myStudents.php index c843327dd6..b065c45f7d 100644 --- a/public/main/my_space/myStudents.php +++ b/public/main/my_space/myStudents.php @@ -549,7 +549,7 @@ switch ($action) { case 'generate_certificate': $gradebookCertificateRepo = Container::getGradeBookCertificateRepository(); $gradebookCertificateRepo->deleteCertificateAndRelatedFiles($studentId, 0); - $certificate = new Certificate(83, $studentId); + $certificate = new Certificate(0, $studentId); $certificate->generatePdfFromCustomCertificate(); exit; case 'send_legal': From b639fac1e5781bd1d5bc5eb57e6f841c908cfc5a Mon Sep 17 00:00:00 2001 From: christianbeeznst Date: Mon, 29 Jan 2024 12:58:40 -0500 Subject: [PATCH 3/4] Reporting: Fix count of downloaded documents - refs #4861 --- public/main/inc/lib/tracking.lib.php | 40 +++++++++---------- public/main/my_space/myStudents.php | 2 +- .../default/my_space/user_details.html.twig | 4 +- 3 files changed, 21 insertions(+), 25 deletions(-) diff --git a/public/main/inc/lib/tracking.lib.php b/public/main/inc/lib/tracking.lib.php index 826b553b70..5940c59abc 100644 --- a/public/main/inc/lib/tracking.lib.php +++ b/public/main/inc/lib/tracking.lib.php @@ -6,6 +6,7 @@ use Chamilo\CoreBundle\Entity\TrackEAttemptQualify; use Chamilo\CoreBundle\Entity\Course; use Chamilo\CoreBundle\Entity\ExtraField as EntityExtraField; use Chamilo\CoreBundle\Entity\Session as SessionEntity; +use Chamilo\CoreBundle\Entity\TrackEDownloads; use Chamilo\CoreBundle\Entity\User; use Chamilo\CoreBundle\Entity\Usergroup; use Chamilo\CoreBundle\Framework\Container; @@ -4270,32 +4271,27 @@ class Tracking return Database::num_rows($rs); } - /** - * Get count student downloaded documents. - * - * @param int Student id - * @param int $courseId - * @param int Session id (optional) - * - * @return int Count downloaded documents - */ - public static function count_student_downloaded_documents($student_id, $courseId, $session_id = 0) + public static function countStudentDownloadedDocuments(int $studentId, int $courseId, int $sessionId = 0): int { - $student_id = (int) $student_id; - $courseId = (int) $courseId; - $session_id = (int) $session_id; + $em = Database::getManager(); + $qb = $em->createQueryBuilder(); - // table definition - $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_DOWNLOADS); + $qb->select('COUNT(td.downId)') + ->from(TrackEDownloads::class, 'td') + ->leftJoin('td.resourceLink', 'rl') + ->where('td.downUserId = :studentId') + ->andWhere('rl.course = :courseId') + ->setParameter('studentId', $studentId) + ->setParameter('courseId', $courseId); - $sql = 'SELECT 1 - FROM '.$table.' - WHERE down_user_id = '.$student_id.' - AND c_id = "'.$courseId.'" - AND session_id = '.$session_id.' '; - $rs = Database::query($sql); + if ($sessionId > 0) { + $qb->andWhere('rl.session = :sessionId') + ->setParameter('sessionId', $sessionId); + } - return Database::num_rows($rs); + $query = $qb->getQuery(); + + return (int) $query->getSingleScalarResult(); } /** diff --git a/public/main/my_space/myStudents.php b/public/main/my_space/myStudents.php index 15e6e68e38..a9931f8bcd 100644 --- a/public/main/my_space/myStudents.php +++ b/public/main/my_space/myStudents.php @@ -1139,7 +1139,7 @@ if (null !== $course) { $messages = Container::getForumPostRepository()->countUserForumPosts($user, $course, $session); $links = Tracking::count_student_visited_links($studentId, $courseId, $sessionId); $chat_last_connection = Tracking::chat_last_connection($studentId, $courseId, $sessionId); - $documents = Tracking::count_student_downloaded_documents($studentId, $courseId, $sessionId); + $documents = Tracking::countStudentDownloadedDocuments($studentId, $courseId, $sessionId); $uploaded_documents = Tracking::count_student_uploaded_documents($studentId, $courseCode, $sessionId); $tpl->assign('title', $course->getTitle()); diff --git a/public/main/template/default/my_space/user_details.html.twig b/public/main/template/default/my_space/user_details.html.twig index 2cc0b4f760..52b96db19d 100644 --- a/public/main/template/default/my_space/user_details.html.twig +++ b/public/main/template/default/my_space/user_details.html.twig @@ -161,10 +161,10 @@
{{ display.card_widget('First login in platform'|trans, user_extra.first_connection, 'calendar') }} {{ display.card_widget('Latest login in platform'|trans, user_extra.last_connection, 'calendar') }} - {% if (user_extra.time_spent_course) %} + {% if (user_extra.time_spent_course is defined) %} {{ display.card_widget('Time spent in the course'|trans, user_extra.time_spent_course, 'clock-o') }} {% endif %} - {% if user_extra.legal %} + {% if user_extra.legal is defined %} {{ display.card_widget('Legal accepted'|trans, user_extra.legal.datetime, 'gavel', user_extra.legal.icon) }} {% endif %}
From dc38fbb3ba2d70ec20624caa3f238ee5a7e128d9 Mon Sep 17 00:00:00 2001 From: NicoDucou Date: Tue, 30 Jan 2024 13:50:03 +0100 Subject: [PATCH 4/4] Course Progress: Migrations: only migrate active thematic not previously deleted ones - refs BT#20864 --- src/CoreBundle/Migrations/Schema/V200/Version20201216105331.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CoreBundle/Migrations/Schema/V200/Version20201216105331.php b/src/CoreBundle/Migrations/Schema/V200/Version20201216105331.php index e887382e5e..1753b45836 100644 --- a/src/CoreBundle/Migrations/Schema/V200/Version20201216105331.php +++ b/src/CoreBundle/Migrations/Schema/V200/Version20201216105331.php @@ -58,7 +58,7 @@ final class Version20201216105331 extends AbstractMigrationChamilo $courseId = $course->getId(); // c_thematic. - $sql = "SELECT * FROM c_thematic WHERE c_id = {$courseId} + $sql = "SELECT * FROM c_thematic WHERE c_id = {$courseId} and active = 1 ORDER BY iid"; $result = $connection->executeQuery($sql); $items = $result->fetchAllAssociative();