From 63372364459c02d0d4ec246f99b9deb9335579f6 Mon Sep 17 00:00:00 2001 From: Marvin Beym Date: Wed, 15 Nov 2023 09:17:51 +0100 Subject: [PATCH 01/10] Implement CERTIFICATE_ID placeholder and Certificate Overview Tab --- .../Overview/CertificateOverviewTable.php | 261 ++++++++++++++++++ components/ILIAS/Certificate/PRIVACY.md | 8 +- components/ILIAS/Certificate/README.md | 6 +- .../class.ilDefaultPlaceholderDescription.php | 1 + .../class.ilDefaultPlaceholderValues.php | 14 +- .../Migration/CertificateIdMigration.php | 124 +++++++++ .../Setup/class.ilCertificatSetupAgent.php | 7 +- ...class.ilCertificateDatabaseUpdateSteps.php | 14 + .../classes/User/class.ilUserCertificate.php | 8 + .../class.ilUserCertificateRepository.php | 149 +++++++++- .../classes/ValueObject/CertificateId.php | 58 ++++ .../classes/class.ilCertificateCron.php | 12 +- .../class.ilObjCertificateSettingsGUI.php | 96 ++++++- ...CertificateLearningHistoryProviderTest.php | 4 + ...ilCertificateTemplatePreviewActionTest.php | 1 + .../ilDefaultPlaceholderDescriptionTest.php | 3 +- .../tests/ilDefaultPlaceholderValuesTest.php | 12 + .../ilExercisePlaceholderDescriptionTest.php | 3 +- .../Certificate/tests/ilPdfGeneratorTest.php | 4 + .../ilTestPlaceholderDescriptionTest.php | 3 +- .../tests/ilUserCertificateRepositoryTest.php | 24 +- .../tests/ilUserCertificateTest.php | 6 +- .../ilScormPlaceholderDescriptionTest.php | 3 +- lang/ilias_de.lang | 3 + lang/ilias_en.lang | 3 + 25 files changed, 802 insertions(+), 25 deletions(-) create mode 100644 components/ILIAS/Certificate/Overview/CertificateOverviewTable.php create mode 100644 components/ILIAS/Certificate/classes/Setup/Migration/CertificateIdMigration.php create mode 100644 components/ILIAS/Certificate/classes/ValueObject/CertificateId.php diff --git a/components/ILIAS/Certificate/Overview/CertificateOverviewTable.php b/components/ILIAS/Certificate/Overview/CertificateOverviewTable.php new file mode 100644 index 000000000000..59cc1fdf1e45 --- /dev/null +++ b/components/ILIAS/Certificate/Overview/CertificateOverviewTable.php @@ -0,0 +1,261 @@ +ui_factory = $ui_factory ?: $DIC->ui()->factory(); + $this->repo = $repo ?: new ilUserCertificateRepository(); + $this->ui_service = $ui_service ?: $DIC->uiService(); + $this->lng = $lng ?: $DIC->language(); + $this->request = $request ?: $DIC->http()->request(); + $this->data_factory = $data_factory ?: new \ILIAS\Data\Factory(); + $this->ctrl = $ctrl ?: $DIC->ctrl(); + $this->ui_renderer = $ui_renderer ?: $DIC->ui()->renderer(); + $this->access = $access ?: $DIC->access(); + $this->user = $user ?: $DIC->user(); + + $this->filter = $this->buildFilter(); + $this->table = $this->buildTable(); + } + + public function getRows( + DataRowBuilder $row_builder, + array $visible_column_ids, + Range $range, + Order $order, + ?array $filter_data, + ?array $additional_parameters + ): Generator { + /** + * @var array{certificate_id: null|string, issue_date: null|DateTime, object: null|string, owner: null|string} $filter_data + */ + $filter_data = $this->ui_service->filter()->getData($this->filter); + [$order_field, $order_direction] = $order->join([], fn($ret, $key, $value) => [$key, $value]); + + if (isset($filter_data['issue_date']) && $filter_data['issue_date'] !== '') { + try { + $filter_data['issue_date'] = new DateTime($filter_data['issue_date']); + } catch (Exception $e) { + $filter_data['issue_date'] = null; + } + } else { + $filter_data['issue_date'] = null; + } + + $table_rows = $this->buildTableRows($this->repo->fetchCertificatesForOverview($this->user->getLanguage(), $filter_data, $range)); + $colum_to_order = array_column($table_rows, $order_field); + array_multisort($colum_to_order, $order_direction === 'ASC' ? SORT_ASC : SORT_DESC, $table_rows); + + foreach ($table_rows as $row) { + $row['issue_date'] = DateTimeImmutable::createFromMutable((new DateTime())->setTimestamp($row['issue_date'])); + yield $row_builder->buildDataRow((string) $row['id'], $row); + } + } + + public function getTotalRowCount(?array $filter_data, ?array $additional_parameters): ?int + { + /** + * @var array{certificate_id: null|string, issue_date: null|DateTime, object: null|string, owner: null|string} $filter_data + */ + $filter_data = $this->ui_service->filter()->getData($this->filter); + + if (isset($filter_data['issue_date']) && $filter_data['issue_date'] !== '') { + try { + $filter_data['issue_date'] = new DateTime($filter_data['issue_date']); + } catch (Exception $e) { + $filter_data['issue_date'] = null; + } + } else { + $filter_data['issue_date'] = null; + } + + return $this->repo->fetchCertificatesForOverviewCount($filter_data); + } + + + private function buildFilter(): Standard + { + return $this->ui_service->filter()->standard( + 'certificates_overview_filter', + $this->ctrl->getLinkTargetByClass( + ilObjCertificateSettingsGUI::class, + ilObjCertificateSettingsGUI::CMD_CERTIFICATES_OVERVIEW + ), + [ + 'certificate_id' => $this->ui_factory->input()->field()->text($this->lng->txt('certificate_id')), + 'issue_date' => $this->ui_factory->input()->field()->text($this->lng->txt('certificate_issue_date')), + 'object' => $this->ui_factory->input()->field()->text($this->lng->txt('obj')), + 'obj_id' => $this->ui_factory->input()->field()->text($this->lng->txt('object_id')), + 'owner' => $this->ui_factory->input()->field()->text($this->lng->txt('owner')), + ], + [true, true, true, true, true], + true, + true + ); + } + + private function buildTable(): Table + { + $uiTable = $this->ui_factory->table(); + return $uiTable->data( + $this->lng->txt('certificates'), + [ + 'certificate_id' => $uiTable->column()->text($this->lng->txt('certificate_id')), + 'issue_date' => $uiTable->column()->date( + $this->lng->txt('certificate_issue_date'), + $this->data_factory->dateFormat()->withTime24($this->data_factory->dateFormat()->standard()) + ), + 'object' => $uiTable->column()->text($this->lng->txt('obj')), + 'obj_id' => $uiTable->column()->text($this->lng->txt('object_id')), + 'owner' => $uiTable->column()->text($this->lng->txt('owner')), + ], + $this + ) + ->withId('certificateOverviewTable') + ->withRequest($this->request) + ->withActions($this->buildTableActions()); + } + + private function buildTableActions(): array + { + $uri_download = $this->data_factory->uri( + ILIAS_HTTP_PATH . '/' . $this->ctrl->getLinkTargetByClass( + ilObjCertificateSettingsGUI::class, + ilObjCertificateSettingsGUI::CMD_DOWNLOAD_CERTIFICATE + ) + ); + + /** + * @var URLBuilder $url_builder_download + * @var URLBuilderToken $action_parameter_token_download , + * @var URLBuilderToken $row_id_token_download + */ + [ + $url_builder_download, + $action_parameter_token_download, + $row_id_token_download + ] = + (new URLBuilder($uri_download))->acquireParameters( + ['cert_overview'], + 'action', + 'id' + ); + + return [ + 'download' => $this->ui_factory->table()->action()->single( + $this->lng->txt('download'), + $url_builder_download->withParameter($action_parameter_token_download, 'download'), + $row_id_token_download + ) + ]; + } + + /** + * @param ilUserCertificate[] $certificates + */ + private function buildTableRows(array $certificates): array + { + $table_rows = []; + + foreach ($certificates as $certificate) { + $refIds = ilObject::_getAllReferences($certificate->getObjId()); + $objectTitle = ilObject::_lookupTitle($certificate->getObjId()); + foreach ($refIds as $refId) { + if ($this->access->checkAccess('read', '', $refId)) { + $objectTitle = $this->ui_renderer->render( + $this->ui_factory->link()->standard($objectTitle, ilLink::_getLink($refId)) + ); + break; + } + } + + $table_rows[] = [ + 'id' => $certificate->getId(), + 'certificate_id' => $certificate->getCertificateId()->get(), + 'issue_date' => $certificate->getAcquiredTimestamp(), + 'object' => $objectTitle, + 'obj_id' => (string) $certificate->getObjId(), + 'owner' => ilObjUser::_lookupLogin($certificate->getUserId()), + ]; + } + return $table_rows; + } + + public function render(): string + { + return $this->ui_renderer->render([$this->filter, $this->table]); + } +} diff --git a/components/ILIAS/Certificate/PRIVACY.md b/components/ILIAS/Certificate/PRIVACY.md index 8cc0a26a2745..2c6fcae0c582 100755 --- a/components/ILIAS/Certificate/PRIVACY.md +++ b/components/ILIAS/Certificate/PRIVACY.md @@ -37,6 +37,7 @@ For each issued persisting user certificate, the ID of the user account is store Stored Data (if used as a placeholder in a certificate template): + - Certificate ID (globally unique id) - Username - Fullname Presentation - Firstname @@ -62,14 +63,17 @@ For each issued persisting user certificate, the ID of the user account is store "PRIVACY.md" of those object types to see presentation of certificate data in that very object type. For example the course object presents certificate download links in the "Member" tab to user accounts with "Manage Member" permission. +- Furthermore, certificates are presented to users in "Administration > Achievements > Certificates" in the **Certificates** tab + if the user has **read** permission on the certificate settings. ## Data being deleted - If a user account is deleted, all it's certificates will be completely deleted from the database. There is no trash for users. -## Data being exported +## Data being exported - Certificate owners can export PDF documents of their certificates at "Achievements > Certificates". No other exports of user related data is provided by the "Certificate Service" itself. Exports may be possible - in the object types listed above. Please refer to the respective "PRIVACY.md" files. \ No newline at end of file + in the object types listed above. Please refer to the respective "PRIVACY.md" files. +- Users with **read** permission on the certificate settings can export PDF documents of certificates at "Administration > Achievements > Certificates" in the **Certificates** tab. \ No newline at end of file diff --git a/components/ILIAS/Certificate/README.md b/components/ILIAS/Certificate/README.md index cd8b70befd1d..464e7ecbca71 100755 --- a/components/ILIAS/Certificate/README.md +++ b/components/ILIAS/Certificate/README.md @@ -259,7 +259,7 @@ Example: ```php // Refers to already used certificate template -$patternCertificateId = 10; +use ILIAS\Certificate\ValueObject\CertificateId;$patternCertificateId = 10; // Object ID of the Module/Service (e.g. Course, Test) $objId = 200; @@ -294,6 +294,9 @@ $iliasVersion = 'v5.4.0'; // Determines if the current certifcate is the newest and visible certificate $currentlyActive = true; +// Unique UUID for the certificate +$cert_id = new CertificateId('unique-uuid'); + $certificate = new ilUserCertificate( $patternCertificateId, $objId, @@ -307,6 +310,7 @@ $certificate = new ilUserCertificate( $version, $iliasVersion, $currentlyActive, + $cert_id ); $repository = new ilUserCertificateRepository($database, $logger); diff --git a/components/ILIAS/Certificate/classes/Placeholder/Description/class.ilDefaultPlaceholderDescription.php b/components/ILIAS/Certificate/classes/Placeholder/Description/class.ilDefaultPlaceholderDescription.php index bb7e3af86f09..8130c0df7527 100755 --- a/components/ILIAS/Certificate/classes/Placeholder/Description/class.ilDefaultPlaceholderDescription.php +++ b/components/ILIAS/Certificate/classes/Placeholder/Description/class.ilDefaultPlaceholderDescription.php @@ -40,6 +40,7 @@ public function __construct( $this->language = $language; $this->placeholder = [ + 'CERTIFICATE_ID' => $language->txt('certificate_ph_cert_id'), 'USER_LOGIN' => $language->txt('certificate_ph_login'), 'USER_FULLNAME' => $language->txt('certificate_ph_fullname'), 'USER_FIRSTNAME' => $language->txt('certificate_ph_firstname'), diff --git a/components/ILIAS/Certificate/classes/Placeholder/Values/class.ilDefaultPlaceholderValues.php b/components/ILIAS/Certificate/classes/Placeholder/Values/class.ilDefaultPlaceholderValues.php index 25cded4664c1..e9059b92f304 100755 --- a/components/ILIAS/Certificate/classes/Placeholder/Values/class.ilDefaultPlaceholderValues.php +++ b/components/ILIAS/Certificate/classes/Placeholder/Values/class.ilDefaultPlaceholderValues.php @@ -18,6 +18,8 @@ declare(strict_types=1); +use ILIAS\Data\UUID\Factory; + require_once 'components/ILIAS/Calendar/classes/class.ilDateTime.php'; // Required because of global contant IL_CAL_DATE /** @@ -33,8 +35,10 @@ class ilDefaultPlaceholderValues implements ilCertificatePlaceholderValues private readonly ilLanguage $language; private readonly ilCertificateUtilHelper $utilHelper; private readonly ilUserDefinedFieldsPlaceholderValues $userDefinedFieldsPlaceholderValues; + private readonly Factory $uuid_factory; private readonly ilLanguage $user_language; + public function __construct( ?ilCertificateObjectHelper $objectHelper = null, ?ilCertificateDateHelper $dateHelper = null, @@ -42,7 +46,8 @@ public function __construct( ?ilLanguage $language = null, ?ilCertificateUtilHelper $utilHelper = null, ?ilUserDefinedFieldsPlaceholderValues $userDefinedFieldsPlaceholderValues = null, - private readonly int $birthdayDateFormat = IL_CAL_DATE + ?ILIAS\Data\UUID\Factory $uuid_factory = null, + private readonly int $birthdayDateFormat = IL_CAL_DATE, ) { if (null === $objectHelper) { $objectHelper = new ilCertificateObjectHelper(); @@ -76,7 +81,13 @@ public function __construct( } $this->userDefinedFieldsPlaceholderValues = $userDefinedFieldsPlaceholderValues; + if (!$uuid_factory) { + $uuid_factory = new ILIAS\Data\UUID\Factory(); + } + $this->uuid_factory = $uuid_factory; + $this->placeholder = [ + 'CERTIFICATE_ID' => '', 'USER_LOGIN' => '', 'USER_FULLNAME' => '', 'USER_FIRSTNAME' => '', @@ -170,6 +181,7 @@ public function getPlaceholderValues(int $userId, int $objId): array public function getPlaceholderValuesForPreview(int $userId, int $objId): array { $previewPlacholderValues = [ + 'CERTIFICATE_ID' => $this->utilHelper->prepareFormOutput($this->uuid_factory->uuid4AsString()), "USER_LOGIN" => $this->utilHelper->prepareFormOutput($this->language->txt("certificate_var_user_login")), "USER_FULLNAME" => $this->utilHelper->prepareFormOutput($this->language->txt("certificate_var_user_fullname")), "USER_FIRSTNAME" => $this->utilHelper->prepareFormOutput($this->language->txt("certificate_var_user_firstname")), diff --git a/components/ILIAS/Certificate/classes/Setup/Migration/CertificateIdMigration.php b/components/ILIAS/Certificate/classes/Setup/Migration/CertificateIdMigration.php new file mode 100644 index 000000000000..4a5cec55e48d --- /dev/null +++ b/components/ILIAS/Certificate/classes/Setup/Migration/CertificateIdMigration.php @@ -0,0 +1,124 @@ +getShortName(); + } + public function getDefaultAmountOfStepsPerRun(): int + { + return self::NUMBER_OF_STEPS; + } + + public function getPreconditions(Environment $environment): array + { + return [ + new ilDatabaseUpdatedObjective() + ]; + } + + public function prepare(Environment $environment): void + { + $this->db = $environment->getResource(Environment::RESOURCE_DATABASE); + $this->prepared_statement = $this->db->prepareManip( + 'UPDATE il_cert_user_cert SET certificate_id = ?, template_values = ? WHERE id = ?', + [ + ilDBConstants::T_TEXT, + ilDBConstants::T_CLOB, + ilDBConstants::T_INTEGER + ] + ); + + $this->uuid_factory = new Factory(); + } + + /** + * @throws JsonException + * @throws ilDatabaseException + */ + public function step(Environment $environment): void + { + $result = $this->db->query( + 'SELECT id, template_values FROM il_cert_user_cert WHERE certificate_id IS NULL LIMIT ' . self::NUMBER_OF_CERTS_PER_STEP + ); + + while($row = $this->db->fetchAssoc($result)) { + $template_values = json_decode($row['template_values'], true, 512, JSON_THROW_ON_ERROR); + $certificate_id = $this->uuid_factory->uuid4AsString(); + + $template_values['CERTIFICATE_ID'] = $certificate_id; + + $this->db->execute($this->prepared_statement, [ + $certificate_id, + json_encode($template_values, JSON_THROW_ON_ERROR), + (int) $row['id'] + ]); + } + + if ($this->getRemainingAmountOfSteps() === 0) { + $this->db->modifyTableColumn( + 'il_cert_user_cert', + 'certificate_id', + [ + 'type' => 'text', + 'length' => 64, + 'notnull' => true, + ] + ); + $this->db->addUniqueConstraint('il_cert_user_cert', ['id', 'certificate_id'], 'c1'); + } + } + + public function getRemainingAmountOfSteps(): int + { + $result = $this->db->query( + 'SELECT COUNT(id) AS missing_cert_id FROM il_cert_user_cert WHERE certificate_id IS NULL' + ); + $row = $this->db->fetchAssoc($result); + + $remaining_certs = (int) $row['missing_cert_id']; + + return (int) ceil($remaining_certs / self::NUMBER_OF_CERTS_PER_STEP); + } +} diff --git a/components/ILIAS/Certificate/classes/Setup/class.ilCertificatSetupAgent.php b/components/ILIAS/Certificate/classes/Setup/class.ilCertificatSetupAgent.php index 81fc251d0d68..017b77600adf 100755 --- a/components/ILIAS/Certificate/classes/Setup/class.ilCertificatSetupAgent.php +++ b/components/ILIAS/Certificate/classes/Setup/class.ilCertificatSetupAgent.php @@ -18,8 +18,9 @@ declare(strict_types=1); -use ILIAS\Setup; +use ILIAS\Certificate\Setup\Migration\CertificateIdMigration; use ILIAS\Refinery; +use ILIAS\Setup; class ilCertificatSetupAgent implements Setup\Agent { @@ -59,6 +60,8 @@ public function getStatusObjective(Setup\Metrics\Storage $storage): Setup\Object public function getMigrations(): array { - return []; + return [ + new CertificateIdMigration() + ]; } } diff --git a/components/ILIAS/Certificate/classes/Setup/class.ilCertificateDatabaseUpdateSteps.php b/components/ILIAS/Certificate/classes/Setup/class.ilCertificateDatabaseUpdateSteps.php index 4aad39124252..55f8a6a1a4ce 100755 --- a/components/ILIAS/Certificate/classes/Setup/class.ilCertificateDatabaseUpdateSteps.php +++ b/components/ILIAS/Certificate/classes/Setup/class.ilCertificateDatabaseUpdateSteps.php @@ -77,4 +77,18 @@ public function step_5(): void $this->db->addIndex('il_cert_user_cert', ['background_image_path', 'currently_active'], 'i7'); } } + + public function step_6(): void + { + if ( + $this->db->tableExists('il_cert_user_cert') + && !$this->db->tableColumnExists('il_cert_user_cert', 'certificate_id') + ) { + $this->db->addTableColumn('il_cert_user_cert', 'certificate_id', [ + 'type' => 'text', + 'length' => 64, + 'notnull' => false, + ]); + } + } } diff --git a/components/ILIAS/Certificate/classes/User/class.ilUserCertificate.php b/components/ILIAS/Certificate/classes/User/class.ilUserCertificate.php index 5bc4cd21e49f..ba86e98b2cf5 100755 --- a/components/ILIAS/Certificate/classes/User/class.ilUserCertificate.php +++ b/components/ILIAS/Certificate/classes/User/class.ilUserCertificate.php @@ -18,6 +18,8 @@ declare(strict_types=1); +use ILIAS\Certificate\ValueObject\CertificateId; + /** * @author Niels Theen */ @@ -40,6 +42,7 @@ public function __construct( private int $version, private readonly string $iliasVersion, private readonly bool $currentlyActive, + private readonly CertificateId $certificate_id, ?string $backgroundImagePath = null, ?string $thumbnailImagePath = null, private ?int $id = null @@ -139,4 +142,9 @@ public function getThumbnailImagePath(): string { return $this->thumbnailImagePath; } + + public function getCertificateId(): CertificateId + { + return $this->certificate_id; + } } diff --git a/components/ILIAS/Certificate/classes/User/class.ilUserCertificateRepository.php b/components/ILIAS/Certificate/classes/User/class.ilUserCertificateRepository.php index 0850bc6114bc..5cf0ca5cba0b 100755 --- a/components/ILIAS/Certificate/classes/User/class.ilUserCertificateRepository.php +++ b/components/ILIAS/Certificate/classes/User/class.ilUserCertificateRepository.php @@ -18,6 +18,10 @@ declare(strict_types=1); +use ILIAS\Certificate\ValueObject\CertificateId; +use ILIAS\Data\Range; +use ILIAS\Data\UUID\Factory; + /** * @author Niels Theen */ @@ -26,11 +30,13 @@ class ilUserCertificateRepository private readonly ilDBInterface $database; private readonly ilLogger $logger; private readonly string $defaultTitle; + private ?Factory $uuid_factory; public function __construct( ?ilDBInterface $database = null, ?ilLogger $logger = null, - ?string $defaultTitle = null + ?string $defaultTitle = null, + ?Factory $uuid_factory = null, ) { if (null === $database) { global $DIC; @@ -48,7 +54,14 @@ public function __construct( global $DIC; $defaultTitle = $DIC->language()->txt('certificate_no_object_title'); } + $this->defaultTitle = $defaultTitle; + + if (!$uuid_factory) { + $uuid_factory = new ILIAS\Data\UUID\Factory(); + } + $this->uuid_factory = $uuid_factory; + } /** @@ -83,7 +96,8 @@ public function save(ilUserCertificate $userCertificate): ilUserCertificate 'ilias_version' => ['text', $userCertificate->getIliasVersion()], 'currently_active' => ['integer', (int) $userCertificate->isCurrentlyActive()], 'background_image_path' => ['text', $userCertificate->getBackgroundImagePath()], - 'thumbnail_image_path' => ['text', $userCertificate->getThumbnailImagePath()] + 'thumbnail_image_path' => ['text', $userCertificate->getThumbnailImagePath()], + 'certificate_id' => ['text', $userCertificate->getCertificateId()->get()] ]; $this->logger->debug(sprintf( @@ -120,6 +134,7 @@ public function fetchActiveCertificates(int $userId): array il_cert_user_cert.background_image_path, il_cert_user_cert.id, il_cert_user_cert.thumbnail_image_path, + il_cert_user_cert.certificate_id, COALESCE(object_data.title, object_data_del.title, ' . $this->database->quote($this->defaultTitle, 'text') . ') AS title FROM il_cert_user_cert LEFT JOIN object_data ON object_data.obj_id = il_cert_user_cert.obj_id @@ -180,6 +195,7 @@ public function fetchActiveCertificatesInIntervalForPresentation( il_cert_user_cert.background_image_path, il_cert_user_cert.id, il_cert_user_cert.thumbnail_image_path, + il_cert_user_cert.certificate_id, COALESCE(object_data.title, object_data_del.title, ' . $this->database->quote($this->defaultTitle, 'text') . ') AS title FROM il_cert_user_cert LEFT JOIN object_data ON object_data.obj_id = il_cert_user_cert.obj_id @@ -280,6 +296,7 @@ public function fetchActiveCertificateForPresentation(int $userId, int $objectId il_cert_user_cert.background_image_path, il_cert_user_cert.id, il_cert_user_cert.thumbnail_image_path, + il_cert_user_cert.certificate_id, usr_data.lastname, COALESCE(object_data.title, object_data_del.title, ' . $this->database->quote($this->defaultTitle, 'text') . ') AS title FROM il_cert_user_cert @@ -346,6 +363,7 @@ public function fetchActiveCertificatesByTypeForPresentation(int $userId, string il_cert_user_cert.background_image_path, il_cert_user_cert.id, il_cert_user_cert.thumbnail_image_path, + il_cert_user_cert.certificate_id, COALESCE(object_data.title, object_data_del.title, ' . $this->database->quote($this->defaultTitle, 'text') . ') AS title FROM il_cert_user_cert LEFT JOIN object_data ON object_data.obj_id = il_cert_user_cert.obj_id @@ -604,6 +622,7 @@ private function createUserCertificate(array $row): ilUserCertificate (int) $row['version'], $row['ilias_version'], (bool) $row['currently_active'], + new CertificateId($row['certificate_id']), (string) $row['background_image_path'], (string) $row['thumbnail_image_path'], isset($row['id']) ? (int) $row['id'] : null @@ -622,4 +641,130 @@ public function deleteUserCertificatesForObject(int $userId, int $obj_id): void $this->logger->debug(sprintf('END - Successfully deleted certificate for user("%s") in object (obj_id: %s)"', $userId, $obj_id)); } + + /** + * @return ilUserCertificate[] + * @var array{certificate_id: null|string, issue_date: null|DateTime, object: null|string, owner: null|string} $filter + */ + public function fetchCertificatesForOverview(string $user_language, array $filter, ?Range $range = null): array + { + $sql_filters = []; + foreach ($filter as $key => $value) { + if ($value === null) { + continue; + } + + $column_name = $key; + switch ($key) { + case 'issue_date': + $column_name = 'acquired_timestamp'; + break; + case 'object': + $column_name = 'object_data.title'; + break; + case 'owner': + $column_name = 'usr_data.login'; + break; + case 'obj_id': + $column_name = 'cert.obj_id'; + break; + } + + if ($key === 'issue_date') { + /** @var null|DateTime $value */ + $sql_filters[] = $this->database->equals( + $column_name, + (string) $value->getTimestamp(), + ilDBConstants::T_INTEGER + ); + } else { + $sql_filters[] = $this->database->like($column_name, ilDBConstants::T_TEXT, "%$value%"); + } + } + + if ($range) { + $this->database->setLimit($range->getLength(), $range->getStart()); + } + + $result = $this->database->query( + 'SELECT cert.*, ' . + '(CASE + WHEN (trans.title IS NOT NULL AND LENGTH(trans.title) > 0) THEN trans.title + WHEN (object_data.title IS NOT NULL AND LENGTH(object_data.title) > 0) THEN object_data.title + WHEN (object_data_del.title IS NOT NULL AND LENGTH(object_data_del.title) > 0) THEN object_data_del.title + ELSE ' . $this->database->quote($this->defaultTitle, ilDBConstants::T_TEXT) . ' + END + ) as object, ' + . 'usr_data.login AS owner FROM il_cert_user_cert AS cert ' + . 'LEFT JOIN object_data ON object_data.obj_id = cert.obj_id ' + . 'INNER JOIN usr_data ON usr_data.usr_id = cert.usr_id ' + . 'LEFT JOIN object_data_del ON object_data_del.obj_id = cert.obj_id ' + . 'LEFT JOIN object_translation trans ON trans.obj_id = object_data.obj_id AND trans.lang_code = ' . $this->database->quote($user_language, 'text') + . ($sql_filters !== [] ? " WHERE " . implode(" AND ", $sql_filters) : "") + ); + + $certificates = []; + while ($row = $this->database->fetchAssoc($result)) { + $certificates[] = $this->createUserCertificate($row); + } + return $certificates; + } + + /** + * @var array{certificate_id: null|string, issue_date: null|DateTime, object: null|string, owner: null|string} $filter + */ + public function fetchCertificatesForOverviewCount(array $filter, ?Range $range = null): int + { + $sql_filters = []; + foreach ($filter as $key => $value) { + if ($value === null) { + continue; + } + + $column_name = $key; + switch ($key) { + case 'issue_date': + $column_name = 'acquired_timestamp'; + break; + case 'object': + $column_name = 'object_data.title'; + break; + case 'owner': + $column_name = 'usr_data.login'; + break; + case 'obj_id': + $column_name = 'cert.obj_id'; + break; + } + + if ($key === 'issue_date') { + /** @var null|DateTime $value */ + $sql_filters[] = $this->database->equals( + $column_name, + (string) $value->getTimestamp(), + ilDBConstants::T_INTEGER + ); + } else { + $sql_filters[] = $this->database->like($column_name, ilDBConstants::T_TEXT, "%$value%"); + } + } + + if ($range) { + $this->database->setLimit($range->getLength(), $range->getStart()); + } + + $result = $this->database->query( + 'SELECT COUNT(id) as count FROM il_cert_user_cert AS cert ' + . 'LEFT JOIN object_data ON object_data.obj_id = cert.obj_id ' + . 'INNER JOIN usr_data ON usr_data.usr_id = cert.usr_id' + . ($sql_filters !== [] ? ' AND ' . implode(' AND ', $sql_filters) : '') + ); + + return (int) $this->database->fetchAssoc($result)['count']; + } + + public function requestNewCertificateId(): CertificateId + { + return new CertificateId($this->uuid_factory->uuid4AsString()); + } } diff --git a/components/ILIAS/Certificate/classes/ValueObject/CertificateId.php b/components/ILIAS/Certificate/classes/ValueObject/CertificateId.php new file mode 100644 index 000000000000..9948183a69ed --- /dev/null +++ b/components/ILIAS/Certificate/classes/ValueObject/CertificateId.php @@ -0,0 +1,58 @@ +certificate_id = $certificate_id; + } + + public function get(): string + { + return $this->certificate_id; + } +} diff --git a/components/ILIAS/Certificate/classes/class.ilCertificateCron.php b/components/ILIAS/Certificate/classes/class.ilCertificateCron.php index 495f022a0b24..fc9805ddf954 100755 --- a/components/ILIAS/Certificate/classes/class.ilCertificateCron.php +++ b/components/ILIAS/Certificate/classes/class.ilCertificateCron.php @@ -18,6 +18,8 @@ declare(strict_types=1); +use ILIAS\Certificate\ValueObject\CertificateId; +use ILIAS\Data\UUID\Factory; use ILIAS\DI\Container; use ILIAS\Cron\Schedule\CronJobScheduleType; @@ -39,7 +41,7 @@ public function __construct( ?ilLanguage $language = null, private ?ilCertificateObjectHelper $objectHelper = null, private ?ilSetting $settings = null, - private ?ilCronManager $cronManager = null + private ?ilCronManager $cronManager = null, ) { if (null === $dic) { global $DIC; @@ -255,9 +257,11 @@ public function processEntry(int $entryCounter, ilCertificateQueueEntry $entry, $type )); + $cert_id = $this->userRepository->requestNewCertificateId(); $certificateContent = $template->getCertificateContent(); $placeholderValues = $placeholderValueObject->getPlaceholderValues($userId, $objId); + $placeholderValues['CERTIFICATE_ID'] = $cert_id->get(); $this->logger->debug(sprintf( 'Values for placeholders: "%s"', @@ -266,7 +270,7 @@ public function processEntry(int $entryCounter, ilCertificateQueueEntry $entry, $certificateContent = $this->valueReplacement->replace( $placeholderValues, - $certificateContent + $certificateContent, ); $thumbnailImagePath = $template->getThumbnailImagePath(); @@ -283,8 +287,10 @@ public function processEntry(int $entryCounter, ilCertificateQueueEntry $entry, $template->getVersion(), ILIAS_VERSION_NUMERIC, true, + $cert_id, $template->getBackgroundImagePath(), - $thumbnailImagePath + $thumbnailImagePath, + null, ); $persistedUserCertificate = $this->userRepository->save($userCertificate); diff --git a/components/ILIAS/Certificate/classes/class.ilObjCertificateSettingsGUI.php b/components/ILIAS/Certificate/classes/class.ilObjCertificateSettingsGUI.php index eddd7c16551d..3e3c4b463d17 100755 --- a/components/ILIAS/Certificate/classes/class.ilObjCertificateSettingsGUI.php +++ b/components/ILIAS/Certificate/classes/class.ilObjCertificateSettingsGUI.php @@ -18,6 +18,8 @@ declare(strict_types=1); +use ILIAS\Certificate\Overview\CertificateOverviewTable; + /** * Certificate Settings. * @author Helmut Schottmüller @@ -28,20 +30,38 @@ */ class ilObjCertificateSettingsGUI extends ilObjectGUI { + public const TAB_CERTIFICATES = 'certificates'; + public const CMD_CERTIFICATES_OVERVIEW = 'certificatesOverview'; + public const CMD_DOWNLOAD_CERTIFICATE = 'downloadCertificate'; + protected \ILIAS\HTTP\GlobalHttpState $httpState; protected \ILIAS\FileUpload\FileUpload $upload; - - public function __construct($data, int $id = 0, bool $call_by_reference = true, bool $prepare_output = true) - { + private ilLogger $logger; + private ilUserCertificateRepository $user_certificate_repo; + private ilCertificateActiveValidator $certificate_active_validator; + + public function __construct( + $data, + int $id = 0, + bool $call_by_reference = true, + bool $prepare_output = true, + ?ilUserCertificateRepository $user_certificate_repo = null, + ?ilCertificateActiveValidator $certificate_active_validator = null, + ) { global $DIC; parent::__construct($data, $id, $call_by_reference, $prepare_output); $this->httpState = $DIC->http(); $this->upload = $DIC->upload(); + $this->logger = $DIC->logger()->root(); $this->type = 'cert'; $this->lng->loadLanguageModule('certificate'); + $this->lng->loadLanguageModule('cert'); $this->lng->loadLanguageModule('trac'); + + $this->user_certificate_repo = $user_certificate_repo ?: new ilUserCertificateRepository(); + $this->certificate_active_validator = $certificate_active_validator ?: new ilCertificateActiveValidator(); } public function executeCommand(): void @@ -82,6 +102,14 @@ public function getAdminTabs(): void ); } + if ($this->certificate_active_validator->validate() && $this->rbac_system->checkAccess('visible,read', $this->object->getRefId())) { + $this->tabs_gui->addTab( + self::TAB_CERTIFICATES, + $this->lng->txt('certificates'), + $this->ctrl->getLinkTarget($this, self::CMD_CERTIFICATES_OVERVIEW) + ); + } + if ($this->rbac_system->checkAccess('edit_permission', $this->object->getRefId())) { $this->tabs_gui->addTarget( 'perm_settings', @@ -205,6 +233,68 @@ public function settings(): void $this->tpl->setContent($form->getHTML()); } + public function certificatesOverview(): void + { + if ( + !$this->certificate_active_validator->validate() + || !$this->rbac_system->checkAccess('visible,read', $this->object->getRefId()) + ) { + $this->error->raiseError($this->lng->txt('permission_denied'), $this->error->WARNING); + } + + $this->tabs_gui->activateTab(self::TAB_CERTIFICATES); + + $table = new CertificateOverviewTable(); + + $this->tpl->setContent($table->render()); + } + + public function downloadCertificate(): void + { + if ( + !$this->certificate_active_validator->validate() + || !$this->rbac_system->checkAccess('visible,read', $this->object->getRefId()) + ) { + $this->error->raiseError($this->lng->txt('permission_denied'), $this->error->WARNING); + } + + $certificate_id = $this->httpState->wrapper()->query()->retrieve( + 'cert_overview_id', + $this->refinery->byTrying([ + $this->refinery->kindlyTo()->listOf($this->refinery->kindlyTo()->int()), + $this->refinery->always([]), + ]) + ); + + if ($certificate_id === []) { + $this->ctrl->redirect($this, self::CMD_CERTIFICATES_OVERVIEW); + } + + //Only one download at a time is possible + $certificate_id = $certificate_id[array_key_first($certificate_id)]; + + try { + $user_certificate = $this->user_certificate_repo->fetchCertificate($certificate_id); + if (!$user_certificate->isCurrentlyActive()) { + throw new Exception('Certificate is not active'); + } + } catch (Exception $ex) { + $this->logger->error('Fetching user certificate for download failed. Ex.: ' . $ex->getMessage()); + $this->tpl->setOnScreenMessage('failure', $this->lng->txt('error_creating_certificate_pdf'), true); + $this->ctrl->redirect($this, self::CMD_CERTIFICATES_OVERVIEW); + } + + $pdf_generator = new ilPdfGenerator($this->user_certificate_repo); + + $pdf_action = new ilCertificatePdfAction( + $pdf_generator, + new ilCertificateUtilHelper(), + $this->lng->txt('error_creating_certificate_pdf') + ); + + $pdf_action->downloadPdf($user_certificate->getUserId(), $user_certificate->getObjId()); + $this->ctrl->redirect($this, self::CMD_CERTIFICATES_OVERVIEW); + } public function save(): void { diff --git a/components/ILIAS/Certificate/tests/ilCertificateLearningHistoryProviderTest.php b/components/ILIAS/Certificate/tests/ilCertificateLearningHistoryProviderTest.php index 2ca30b88cf9d..efee5c4decc4 100755 --- a/components/ILIAS/Certificate/tests/ilCertificateLearningHistoryProviderTest.php +++ b/components/ILIAS/Certificate/tests/ilCertificateLearningHistoryProviderTest.php @@ -18,6 +18,8 @@ declare(strict_types=1); +use ILIAS\Certificate\ValueObject\CertificateId; + class ilCertificateLearningHistoryProviderTest extends ilCertificateBaseTestCase { public function testIsActive(): void @@ -134,6 +136,7 @@ public function testGetEntries(): void 1, 'v5.4.0', true, + new CertificateId('11111111-2222-3333-4444-555555555555'), '/some/where/background_1.jpg', '/some/where/else/thumbnail_1.jpg', 40 @@ -157,6 +160,7 @@ public function testGetEntries(): void 1, 'v5.4.0', true, + new CertificateId('11111111-2222-3333-4444-555555555555'), '/some/where/background_1.jpg', '/some/where/else/thumbnail_1.jpg', 50 diff --git a/components/ILIAS/Certificate/tests/ilCertificateTemplatePreviewActionTest.php b/components/ILIAS/Certificate/tests/ilCertificateTemplatePreviewActionTest.php index e25838c29637..1fc7d8a7c021 100755 --- a/components/ILIAS/Certificate/tests/ilCertificateTemplatePreviewActionTest.php +++ b/components/ILIAS/Certificate/tests/ilCertificateTemplatePreviewActionTest.php @@ -33,6 +33,7 @@ public function testA(): void $placeholderValuesObject->method('getPlaceholderValuesForPreview') ->willReturn([ + 'CERTIFICATE_ID' => 'randomUniqueString', 'USER_LOGIN' => 'SomeLogin', 'USER_FULLNAME' => 'SomeFullName', 'USER_FIRSTNAME' => 'SomeFirstName' diff --git a/components/ILIAS/Certificate/tests/ilDefaultPlaceholderDescriptionTest.php b/components/ILIAS/Certificate/tests/ilDefaultPlaceholderDescriptionTest.php index 3a1ac3b08313..0ac746e417d5 100755 --- a/components/ILIAS/Certificate/tests/ilDefaultPlaceholderDescriptionTest.php +++ b/components/ILIAS/Certificate/tests/ilDefaultPlaceholderDescriptionTest.php @@ -61,7 +61,7 @@ public function testPlaceholderDescription(): void ->onlyMethods(['txt', 'loadLanguageModule']) ->getMock(); - $languageMock->expects($this->exactly(16)) + $languageMock->expects($this->exactly(17)) ->method('txt') ->willReturn('Something translated'); @@ -81,6 +81,7 @@ public function testPlaceholderDescription(): void $this->assertSame( [ + 'CERTIFICATE_ID' => 'Something translated', 'USER_LOGIN' => 'Something translated', 'USER_FULLNAME' => 'Something translated', 'USER_FIRSTNAME' => 'Something translated', diff --git a/components/ILIAS/Certificate/tests/ilDefaultPlaceholderValuesTest.php b/components/ILIAS/Certificate/tests/ilDefaultPlaceholderValuesTest.php index ed74b14d3897..71f7a998845d 100755 --- a/components/ILIAS/Certificate/tests/ilDefaultPlaceholderValuesTest.php +++ b/components/ILIAS/Certificate/tests/ilDefaultPlaceholderValuesTest.php @@ -148,6 +148,10 @@ public function testGetPlaceholderValues(): void $userDefinePlaceholderMock->method('getPlaceholderValuesForPreview') ->willReturn([]); + $uuid_factory_mock = $this->getMockBuilder(ILIAS\Data\UUID\Factory::class) + ->getMock(); + $uuid_factory_mock->method('uuid4AsString')->willReturn('randomUniqueString'); + $placeHolderObject = new ilDefaultPlaceholderValues( $objectHelper, $dateHelper, @@ -155,6 +159,7 @@ public function testGetPlaceholderValues(): void $language, $utilHelper, $userDefinePlaceholderMock, + $uuid_factory_mock, 1 ); $placeHolderObject->setUserLanguage($language); @@ -163,6 +168,7 @@ public function testGetPlaceholderValues(): void $this->assertEquals( [ + 'CERTIFICATE_ID' => '', 'USER_LOGIN' => 'a_login', 'USER_FULLNAME' => 'Niels Theen', 'USER_FIRSTNAME' => 'Niels', @@ -226,6 +232,10 @@ public function testGetPlaceholderValuesForPreview(): void $userDefinePlaceholderMock->method('getPlaceholderValuesForPreview') ->willReturn([]); + $uuid_factory_mock = $this->getMockBuilder(ILIAS\Data\UUID\Factory::class) + ->getMock(); + $uuid_factory_mock->method('uuid4AsString')->willReturn('randomUniqueString'); + $placeHolderObject = new ilDefaultPlaceholderValues( $objectHelper, $dateHelper, @@ -233,6 +243,7 @@ public function testGetPlaceholderValuesForPreview(): void $language, $utilHelper, $userDefinePlaceholderMock, + $uuid_factory_mock, 1 ); @@ -243,6 +254,7 @@ public function testGetPlaceholderValuesForPreview(): void $this->assertSame( [ + 'CERTIFICATE_ID' => 'randomUniqueString', 'USER_LOGIN' => 'Something', 'USER_FULLNAME' => 'Something', 'USER_FIRSTNAME' => 'Something', diff --git a/components/ILIAS/Certificate/tests/ilExercisePlaceholderDescriptionTest.php b/components/ILIAS/Certificate/tests/ilExercisePlaceholderDescriptionTest.php index ffacca6db715..b45e20a653ce 100755 --- a/components/ILIAS/Certificate/tests/ilExercisePlaceholderDescriptionTest.php +++ b/components/ILIAS/Certificate/tests/ilExercisePlaceholderDescriptionTest.php @@ -61,7 +61,7 @@ public function testPlaceholderDescriptions(): void ->onlyMethods(['txt', 'loadLanguageModule']) ->getMock(); - $languageMock->expects($this->exactly(21)) + $languageMock->expects($this->exactly(22)) ->method('txt') ->willReturn('Something translated'); @@ -81,6 +81,7 @@ public function testPlaceholderDescriptions(): void $this->assertSame( [ + 'CERTIFICATE_ID' => 'Something translated', 'USER_LOGIN' => 'Something translated', 'USER_FULLNAME' => 'Something translated', 'USER_FIRSTNAME' => 'Something translated', diff --git a/components/ILIAS/Certificate/tests/ilPdfGeneratorTest.php b/components/ILIAS/Certificate/tests/ilPdfGeneratorTest.php index ba65a0169668..b392c0a7dc1e 100755 --- a/components/ILIAS/Certificate/tests/ilPdfGeneratorTest.php +++ b/components/ILIAS/Certificate/tests/ilPdfGeneratorTest.php @@ -18,6 +18,8 @@ declare(strict_types=1); +use ILIAS\Certificate\ValueObject\CertificateId; + /** * @author Niels Theen */ @@ -44,6 +46,7 @@ public function testGenerateSpecificCertificate(): void 3, 'v5.4.0', true, + new CertificateId('11111111-2222-3333-4444-555555555555'), '/some/where/background.jpg', '/some/where/thumbnail.jpg', 300 @@ -110,6 +113,7 @@ public function testGenerateCurrentActiveCertificate(): void 3, 'v5.4.0', true, + new CertificateId('11111111-2222-3333-4444-555555555555'), '/some/where/background.jpg', '/some/where/thumbnail.jpg', 300 diff --git a/components/ILIAS/Certificate/tests/ilTestPlaceholderDescriptionTest.php b/components/ILIAS/Certificate/tests/ilTestPlaceholderDescriptionTest.php index 5864ee43eee4..005c79195a05 100755 --- a/components/ILIAS/Certificate/tests/ilTestPlaceholderDescriptionTest.php +++ b/components/ILIAS/Certificate/tests/ilTestPlaceholderDescriptionTest.php @@ -61,7 +61,7 @@ public function testPlaceholderDescriptions(): void ->onlyMethods(['txt', 'loadLanguageModule']) ->getMock(); - $languageMock->expects($this->exactly(25)) + $languageMock->expects($this->exactly(26)) ->method('txt') ->willReturn('Something translated'); @@ -81,6 +81,7 @@ public function testPlaceholderDescriptions(): void $this->assertSame( [ + 'CERTIFICATE_ID' => 'Something translated', 'USER_LOGIN' => 'Something translated', 'USER_FULLNAME' => 'Something translated', 'USER_FIRSTNAME' => 'Something translated', diff --git a/components/ILIAS/Certificate/tests/ilUserCertificateRepositoryTest.php b/components/ILIAS/Certificate/tests/ilUserCertificateRepositoryTest.php index 0bfc693ba910..e7e44c6b2fd9 100755 --- a/components/ILIAS/Certificate/tests/ilUserCertificateRepositoryTest.php +++ b/components/ILIAS/Certificate/tests/ilUserCertificateRepositoryTest.php @@ -18,6 +18,8 @@ declare(strict_types=1); +use ILIAS\Certificate\ValueObject\CertificateId; + /** * @author Niels Theen */ @@ -48,6 +50,7 @@ public function testSaveOfUserCertificateToDatabase(): void 'currently_active' => ['integer', true], 'background_image_path' => ['text', '/some/where/background.jpg'], 'thumbnail_image_path' => ['text', '/some/where/thumbnail.svg'], + 'certificate_id' => ['text', '11111111-2222-3333-4444-555555555555'], ] ); @@ -77,8 +80,10 @@ public function testSaveOfUserCertificateToDatabase(): void 1, 'v5.4.0', true, + new CertificateId('11111111-2222-3333-4444-555555555555'), '/some/where/background.jpg', - '/some/where/thumbnail.svg' + '/some/where/thumbnail.svg', + null ); $repository->save($userCertificate); @@ -108,7 +113,8 @@ public function testFetchAllActiveCertificateForUser(): void 'currently_active' => true, 'background_image_path' => '/some/where/background.jpg', 'thumbnail_image_path' => '/some/where/thumbnail.svg', - 'title' => 'Some Title' + 'title' => 'Some Title', + 'certificate_id' => '11111111-2222-3333-4444-555555555555' ], [ 'id' => 142, @@ -126,7 +132,8 @@ public function testFetchAllActiveCertificateForUser(): void 'currently_active' => true, 'background_image_path' => '/some/where/else/background.jpg', 'thumbnail_image_path' => '/some/where/thumbnail.svg', - 'title' => 'Someother Title' + 'title' => 'Someother Title', + 'certificate_id' => '11111111-2222-3333-4444-555555555555' ] ); @@ -173,6 +180,7 @@ public function testFetchActiveCertificateForUserObjectCombination(): void 'currently_active' => true, 'background_image_path' => '/some/where/background.jpg', 'thumbnail_image_path' => '/some/where/thumbnail.svg', + 'certificate_id' => '11111111-2222-3333-4444-555555555555' ], [ 'id' => 142, @@ -190,6 +198,7 @@ public function testFetchActiveCertificateForUserObjectCombination(): void 'currently_active' => true, 'background_image_path' => '/some/where/else/background.jpg', 'thumbnail_image_path' => '/some/where/thumbnail.svg', + 'certificate_id' => '11111111-2222-3333-4444-555555555555' ] ); @@ -264,7 +273,8 @@ public function testFetchActiveCertificatesByType(): void 'background_image_path' => '/some/where/background.jpg', 'thumbnail_image_path' => '/some/where/else/thumbnail.svg', 'title' => 'SomeTitle', - 'someDescription' => 'SomeDescription' + 'someDescription' => 'SomeDescription', + 'certificate_id' => '11111111-2222-3333-4444-555555555555' ], [ 'id' => 142, @@ -283,7 +293,8 @@ public function testFetchActiveCertificatesByType(): void 'background_image_path' => '/some/where/else/background.jpg', 'thumbnail_image_path' => '/some/where/else/thumbnail.svg', 'title' => 'SomeTitle', - 'someDescription' => 'SomeDescription' + 'someDescription' => 'SomeDescription', + 'certificate_id' => '11111111-2222-3333-4444-555555555555' ] ); @@ -327,7 +338,8 @@ public function testFetchCertificate(): void 'background_image_path' => '/some/where/background.jpg', 'thumbnail_image_path' => '/some/where/else/thumbnail.svg', 'title' => 'SomeTitle', - 'someDescription' => 'SomeDescription' + 'someDescription' => 'SomeDescription', + 'certificate_id' => '11111111-2222-3333-4444-555555555555' ] ); diff --git a/components/ILIAS/Certificate/tests/ilUserCertificateTest.php b/components/ILIAS/Certificate/tests/ilUserCertificateTest.php index cd497ba508b2..003e709a4921 100755 --- a/components/ILIAS/Certificate/tests/ilUserCertificateTest.php +++ b/components/ILIAS/Certificate/tests/ilUserCertificateTest.php @@ -18,6 +18,8 @@ declare(strict_types=1); +use ILIAS\Certificate\ValueObject\CertificateId; + /** * @author Niels Theen */ @@ -38,9 +40,10 @@ public function testCreateUserCertificate(): void 1, 'v5.4.0', true, + new CertificateId('11111111-2222-3333-4444-555555555555'), '/some/where/background.jpg', '/some/where/thumbnail.svg', - 140 + 140, ); $this->assertSame(1, $userCertificate->getPatternCertificateId()); @@ -57,5 +60,6 @@ public function testCreateUserCertificate(): void $this->assertTrue($userCertificate->isCurrentlyActive()); $this->assertSame('/some/where/background.jpg', $userCertificate->getBackgroundImagePath()); $this->assertSame(140, $userCertificate->getId()); + $this->assertSame('11111111-2222-3333-4444-555555555555', $userCertificate->getCertificateId()->get()); } } diff --git a/components/ILIAS/ScormAicc/tests/ilScormPlaceholderDescriptionTest.php b/components/ILIAS/ScormAicc/tests/ilScormPlaceholderDescriptionTest.php index 432742cdc252..27269419fcde 100755 --- a/components/ILIAS/ScormAicc/tests/ilScormPlaceholderDescriptionTest.php +++ b/components/ILIAS/ScormAicc/tests/ilScormPlaceholderDescriptionTest.php @@ -91,7 +91,7 @@ public function testPlaceholderDescriptions(): void ->onlyMethods(['txt', 'loadLanguageModule']) ->getMock(); - $languageMock->expects($this->exactly(21)) + $languageMock->expects($this->exactly(22)) ->method('txt') ->willReturn('Something translated'); @@ -122,6 +122,7 @@ public function testPlaceholderDescriptions(): void $this->assertSame( [ + 'CERTIFICATE_ID' => 'Something translated', 'USER_LOGIN' => 'Something translated', 'USER_FULLNAME' => 'Something translated', 'USER_FIRSTNAME' => 'Something translated', diff --git a/lang/ilias_de.lang b/lang/ilias_de.lang index 233caf2e74cb..b10ba1a10fac 100755 --- a/lang/ilias_de.lang +++ b/lang/ilias_de.lang @@ -2752,6 +2752,8 @@ certificate#:#certificate_error_upload_bgimage#:#Beim Hochladen des Hintergrundb certificate#:#certificate_export#:#Exportieren certificate#:#certificate_failed#:#nicht bestanden certificate#:#certificate_file_basename#:#Zertifikat +certificate#:#certificate_id#:#Zertifikat ID +certificate#:#certificate_issue_date#:#Ausstellungsdatum certificate#:#certificate_learning_progress_must_be_active#:#Nur Objekte mit aktivem Lernfortschritt dürfen ausgewählt werden. Bei folgenden Objekten ist der Lernfortschritt nicht aktiv: %s certificate#:#certificate_letter#:#US Brief (11 inch x 8.5 inch) certificate#:#certificate_letter_landscape#:#US Brief Quer (8,5 inch x 11 inch) @@ -2763,6 +2765,7 @@ certificate#:#certificate_pageheight#:#Seitenhöhe certificate#:#certificate_pagewidth#:#Seitenbreite certificate#:#certificate_passed#:#bestanden certificate#:#certificate_ph_birthday#:#Geburtstag des Teilnehmers oder der Teilnehmerin +certificate#:#certificate_ph_cert_id#:#Eindeutige Zertifikats-ID certificate#:#certificate_ph_city#:#Stadt der Adresse des Teilnehmers oder der Teilnehmerin certificate#:#certificate_ph_country#:#Land der Adresse des Teilnehmers oder der Teilnehmerin certificate#:#certificate_ph_date#:#Aktuelles Datum diff --git a/lang/ilias_en.lang b/lang/ilias_en.lang index 4e916e967877..94341c8923b7 100755 --- a/lang/ilias_en.lang +++ b/lang/ilias_en.lang @@ -2754,6 +2754,8 @@ certificate#:#certificate_error_upload_bgimage#:#An error occurred during the up certificate#:#certificate_export#:#Export certificate#:#certificate_failed#:#failed certificate#:#certificate_file_basename#:#Certificate +certificate#:#certificate_id#:#Certificate ID +certificate#:#certificate_issue_date#:#Issue Date certificate#:#certificate_learning_progress_must_be_active#:#Only objects with an active Learning Progress can be selected. The Learning Progress is deactivated on the following objects: %s certificate#:#certificate_letter#:#Letter (11 inch x 8.5 inch) certificate#:#certificate_letter_landscape#:#Letter Landscape (8.5 inch x 11 inch) @@ -2765,6 +2767,7 @@ certificate#:#certificate_pageheight#:#Page Height certificate#:#certificate_pagewidth#:#Page Width certificate#:#certificate_passed#:#passed certificate#:#certificate_ph_birthday#:#Birthday of the user +certificate#:#certificate_ph_cert_id#:#Unique Certificate ID certificate#:#certificate_ph_city#:#City of the user's address certificate#:#certificate_ph_country#:#Country of the user's address certificate#:#certificate_ph_date#:#Actual date From 34821bd8a402637cf0ceb96cb716e4fca224eb2b Mon Sep 17 00:00:00 2001 From: Marvin Beym Date: Thu, 2 May 2024 10:06:04 +0200 Subject: [PATCH 02/10] Rename method --- .../ILIAS/Certificate/Overview/CertificateOverviewTable.php | 2 +- .../classes/User/class.ilUserCertificateRepository.php | 2 +- .../ILIAS/Certificate/classes/ValueObject/CertificateId.php | 2 +- .../ILIAS/Certificate/classes/class.ilCertificateCron.php | 2 +- components/ILIAS/Certificate/tests/ilUserCertificateTest.php | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/components/ILIAS/Certificate/Overview/CertificateOverviewTable.php b/components/ILIAS/Certificate/Overview/CertificateOverviewTable.php index 59cc1fdf1e45..8876d4686911 100644 --- a/components/ILIAS/Certificate/Overview/CertificateOverviewTable.php +++ b/components/ILIAS/Certificate/Overview/CertificateOverviewTable.php @@ -244,7 +244,7 @@ private function buildTableRows(array $certificates): array $table_rows[] = [ 'id' => $certificate->getId(), - 'certificate_id' => $certificate->getCertificateId()->get(), + 'certificate_id' => $certificate->getCertificateId()->asString(), 'issue_date' => $certificate->getAcquiredTimestamp(), 'object' => $objectTitle, 'obj_id' => (string) $certificate->getObjId(), diff --git a/components/ILIAS/Certificate/classes/User/class.ilUserCertificateRepository.php b/components/ILIAS/Certificate/classes/User/class.ilUserCertificateRepository.php index 5cf0ca5cba0b..93d3220de6ab 100755 --- a/components/ILIAS/Certificate/classes/User/class.ilUserCertificateRepository.php +++ b/components/ILIAS/Certificate/classes/User/class.ilUserCertificateRepository.php @@ -97,7 +97,7 @@ public function save(ilUserCertificate $userCertificate): ilUserCertificate 'currently_active' => ['integer', (int) $userCertificate->isCurrentlyActive()], 'background_image_path' => ['text', $userCertificate->getBackgroundImagePath()], 'thumbnail_image_path' => ['text', $userCertificate->getThumbnailImagePath()], - 'certificate_id' => ['text', $userCertificate->getCertificateId()->get()] + 'certificate_id' => ['text', $userCertificate->getCertificateId()->asString()] ]; $this->logger->debug(sprintf( diff --git a/components/ILIAS/Certificate/classes/ValueObject/CertificateId.php b/components/ILIAS/Certificate/classes/ValueObject/CertificateId.php index 9948183a69ed..d3a5e03a8350 100644 --- a/components/ILIAS/Certificate/classes/ValueObject/CertificateId.php +++ b/components/ILIAS/Certificate/classes/ValueObject/CertificateId.php @@ -51,7 +51,7 @@ public function __construct(string $certificate_id) $this->certificate_id = $certificate_id; } - public function get(): string + public function asString(): string { return $this->certificate_id; } diff --git a/components/ILIAS/Certificate/classes/class.ilCertificateCron.php b/components/ILIAS/Certificate/classes/class.ilCertificateCron.php index fc9805ddf954..b63627b85f21 100755 --- a/components/ILIAS/Certificate/classes/class.ilCertificateCron.php +++ b/components/ILIAS/Certificate/classes/class.ilCertificateCron.php @@ -261,7 +261,7 @@ public function processEntry(int $entryCounter, ilCertificateQueueEntry $entry, $certificateContent = $template->getCertificateContent(); $placeholderValues = $placeholderValueObject->getPlaceholderValues($userId, $objId); - $placeholderValues['CERTIFICATE_ID'] = $cert_id->get(); + $placeholderValues['CERTIFICATE_ID'] = $cert_id->asString(); $this->logger->debug(sprintf( 'Values for placeholders: "%s"', diff --git a/components/ILIAS/Certificate/tests/ilUserCertificateTest.php b/components/ILIAS/Certificate/tests/ilUserCertificateTest.php index 003e709a4921..5a29c1b15f58 100755 --- a/components/ILIAS/Certificate/tests/ilUserCertificateTest.php +++ b/components/ILIAS/Certificate/tests/ilUserCertificateTest.php @@ -60,6 +60,6 @@ public function testCreateUserCertificate(): void $this->assertTrue($userCertificate->isCurrentlyActive()); $this->assertSame('/some/where/background.jpg', $userCertificate->getBackgroundImagePath()); $this->assertSame(140, $userCertificate->getId()); - $this->assertSame('11111111-2222-3333-4444-555555555555', $userCertificate->getCertificateId()->get()); + $this->assertSame('11111111-2222-3333-4444-555555555555', $userCertificate->getCertificateId()->asString()); } } From 87bdad8ba69083b8d4c68d893e332923562367fb Mon Sep 17 00:00:00 2001 From: Marvin Beym Date: Thu, 2 May 2024 10:07:13 +0200 Subject: [PATCH 03/10] Remove visible check --- .../classes/class.ilObjCertificateSettingsGUI.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/ILIAS/Certificate/classes/class.ilObjCertificateSettingsGUI.php b/components/ILIAS/Certificate/classes/class.ilObjCertificateSettingsGUI.php index 3e3c4b463d17..f3835d68b06c 100755 --- a/components/ILIAS/Certificate/classes/class.ilObjCertificateSettingsGUI.php +++ b/components/ILIAS/Certificate/classes/class.ilObjCertificateSettingsGUI.php @@ -102,7 +102,7 @@ public function getAdminTabs(): void ); } - if ($this->certificate_active_validator->validate() && $this->rbac_system->checkAccess('visible,read', $this->object->getRefId())) { + if ($this->certificate_active_validator->validate() && $this->rbac_system->checkAccess('read', $this->object->getRefId())) { $this->tabs_gui->addTab( self::TAB_CERTIFICATES, $this->lng->txt('certificates'), @@ -237,7 +237,7 @@ public function certificatesOverview(): void { if ( !$this->certificate_active_validator->validate() - || !$this->rbac_system->checkAccess('visible,read', $this->object->getRefId()) + || !$this->rbac_system->checkAccess('read', $this->object->getRefId()) ) { $this->error->raiseError($this->lng->txt('permission_denied'), $this->error->WARNING); } @@ -253,7 +253,7 @@ public function downloadCertificate(): void { if ( !$this->certificate_active_validator->validate() - || !$this->rbac_system->checkAccess('visible,read', $this->object->getRefId()) + || !$this->rbac_system->checkAccess('read', $this->object->getRefId()) ) { $this->error->raiseError($this->lng->txt('permission_denied'), $this->error->WARNING); } From 23354afc77d0ef63e2591da52bcfdf79ae643ad7 Mon Sep 17 00:00:00 2001 From: Marvin Beym Date: Thu, 2 May 2024 10:07:52 +0200 Subject: [PATCH 04/10] Rename method 'requestNewCertificateId' to 'requestIdentity' --- .../classes/User/class.ilUserCertificateRepository.php | 2 +- .../ILIAS/Certificate/classes/class.ilCertificateCron.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/ILIAS/Certificate/classes/User/class.ilUserCertificateRepository.php b/components/ILIAS/Certificate/classes/User/class.ilUserCertificateRepository.php index 93d3220de6ab..2884d22603d0 100755 --- a/components/ILIAS/Certificate/classes/User/class.ilUserCertificateRepository.php +++ b/components/ILIAS/Certificate/classes/User/class.ilUserCertificateRepository.php @@ -763,7 +763,7 @@ public function fetchCertificatesForOverviewCount(array $filter, ?Range $range = return (int) $this->database->fetchAssoc($result)['count']; } - public function requestNewCertificateId(): CertificateId + public function requestIdentity(): CertificateId { return new CertificateId($this->uuid_factory->uuid4AsString()); } diff --git a/components/ILIAS/Certificate/classes/class.ilCertificateCron.php b/components/ILIAS/Certificate/classes/class.ilCertificateCron.php index b63627b85f21..3320aa502640 100755 --- a/components/ILIAS/Certificate/classes/class.ilCertificateCron.php +++ b/components/ILIAS/Certificate/classes/class.ilCertificateCron.php @@ -257,7 +257,7 @@ public function processEntry(int $entryCounter, ilCertificateQueueEntry $entry, $type )); - $cert_id = $this->userRepository->requestNewCertificateId(); + $cert_id = $this->userRepository->requestIdentity(); $certificateContent = $template->getCertificateContent(); $placeholderValues = $placeholderValueObject->getPlaceholderValues($userId, $objId); From f06bf303aa1db721c8799e0d50c8073b92188a48 Mon Sep 17 00:00:00 2001 From: Marvin Beym Date: Thu, 2 May 2024 10:09:17 +0200 Subject: [PATCH 05/10] Use setLimit instead of Limit in sql query --- .../classes/Setup/Migration/CertificateIdMigration.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/ILIAS/Certificate/classes/Setup/Migration/CertificateIdMigration.php b/components/ILIAS/Certificate/classes/Setup/Migration/CertificateIdMigration.php index 4a5cec55e48d..ef361bd158cc 100644 --- a/components/ILIAS/Certificate/classes/Setup/Migration/CertificateIdMigration.php +++ b/components/ILIAS/Certificate/classes/Setup/Migration/CertificateIdMigration.php @@ -79,8 +79,9 @@ public function prepare(Environment $environment): void */ public function step(Environment $environment): void { + $this->db->setLimit(self::NUMBER_OF_CERTS_PER_STEP); $result = $this->db->query( - 'SELECT id, template_values FROM il_cert_user_cert WHERE certificate_id IS NULL LIMIT ' . self::NUMBER_OF_CERTS_PER_STEP + 'SELECT id, template_values FROM il_cert_user_cert WHERE certificate_id IS NULL' ); while($row = $this->db->fetchAssoc($result)) { From f7fb1cd4c4c742ee5b1d04eea86777c6cd689d2a Mon Sep 17 00:00:00 2001 From: Marvin Beym Date: Thu, 2 May 2024 10:10:26 +0200 Subject: [PATCH 06/10] Reduce number of steps --- .../classes/Setup/Migration/CertificateIdMigration.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/ILIAS/Certificate/classes/Setup/Migration/CertificateIdMigration.php b/components/ILIAS/Certificate/classes/Setup/Migration/CertificateIdMigration.php index ef361bd158cc..0dbdcc64340e 100644 --- a/components/ILIAS/Certificate/classes/Setup/Migration/CertificateIdMigration.php +++ b/components/ILIAS/Certificate/classes/Setup/Migration/CertificateIdMigration.php @@ -35,7 +35,7 @@ class CertificateIdMigration implements Migration { - public const NUMBER_OF_STEPS = 10000; + public const NUMBER_OF_STEPS = 5; public const NUMBER_OF_CERTS_PER_STEP = 100000; private ilDBPdoInterface $db; From e4a33f6a7e46311e352b96427d61c36ab1eaef08 Mon Sep 17 00:00:00 2001 From: Marvin Beym Date: Mon, 6 May 2024 09:23:54 +0200 Subject: [PATCH 07/10] Change tab order --- .../class.ilObjCertificateSettingsGUI.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/components/ILIAS/Certificate/classes/class.ilObjCertificateSettingsGUI.php b/components/ILIAS/Certificate/classes/class.ilObjCertificateSettingsGUI.php index f3835d68b06c..a92ca1b19959 100755 --- a/components/ILIAS/Certificate/classes/class.ilObjCertificateSettingsGUI.php +++ b/components/ILIAS/Certificate/classes/class.ilObjCertificateSettingsGUI.php @@ -94,14 +94,6 @@ public function executeCommand(): void public function getAdminTabs(): void { - if ($this->rbac_system->checkAccess('visible,read', $this->object->getRefId())) { - $this->tabs_gui->addTarget( - 'settings', - $this->ctrl->getLinkTarget($this, 'settings'), - ['settings', 'view'] - ); - } - if ($this->certificate_active_validator->validate() && $this->rbac_system->checkAccess('read', $this->object->getRefId())) { $this->tabs_gui->addTab( self::TAB_CERTIFICATES, @@ -110,6 +102,14 @@ public function getAdminTabs(): void ); } + if ($this->rbac_system->checkAccess('visible,read', $this->object->getRefId())) { + $this->tabs_gui->addTarget( + 'settings', + $this->ctrl->getLinkTarget($this, 'settings'), + ['settings', 'view'] + ); + } + if ($this->rbac_system->checkAccess('edit_permission', $this->object->getRefId())) { $this->tabs_gui->addTarget( 'perm_settings', From dd3f75bc4443e741bb21125c85a01df3cd9fdddd Mon Sep 17 00:00:00 2001 From: Marvin Beym Date: Mon, 6 May 2024 10:27:48 +0200 Subject: [PATCH 08/10] Change default order --- .../ILIAS/Certificate/Overview/CertificateOverviewTable.php | 1 + 1 file changed, 1 insertion(+) diff --git a/components/ILIAS/Certificate/Overview/CertificateOverviewTable.php b/components/ILIAS/Certificate/Overview/CertificateOverviewTable.php index 8876d4686911..54402619f50c 100644 --- a/components/ILIAS/Certificate/Overview/CertificateOverviewTable.php +++ b/components/ILIAS/Certificate/Overview/CertificateOverviewTable.php @@ -184,6 +184,7 @@ private function buildTable(): Table ], $this ) + ->withOrder(new Order('issue_date', Order::DESC)) ->withId('certificateOverviewTable') ->withRequest($this->request) ->withActions($this->buildTableActions()); From 7d35fb72a22dc3dbe4e5d0538f8e2b851198b785 Mon Sep 17 00:00:00 2001 From: Marvin Beym Date: Mon, 6 May 2024 10:28:08 +0200 Subject: [PATCH 09/10] Fix return type --- .../ILIAS/Certificate/Overview/CertificateOverviewTable.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/ILIAS/Certificate/Overview/CertificateOverviewTable.php b/components/ILIAS/Certificate/Overview/CertificateOverviewTable.php index 54402619f50c..15d7f00c5f3e 100644 --- a/components/ILIAS/Certificate/Overview/CertificateOverviewTable.php +++ b/components/ILIAS/Certificate/Overview/CertificateOverviewTable.php @@ -29,11 +29,11 @@ use ilCtrlInterface; use ILIAS\Data\Order; use ILIAS\Data\Range; +use ILIAS\UI\Component\Table\Data; use ILIAS\UI\Component\Table\DataRetrieval; use ILIAS\UI\Component\Table\DataRowBuilder; use ILIAS\UI\Factory; use ILIAS\UI\Implementation\Component\Input\Container\Filter\Standard; -use ILIAS\UI\Implementation\Component\Table\Table; use ILIAS\UI\Renderer; use ILIAS\UI\URLBuilder; use ILIAS\UI\URLBuilderToken; @@ -58,7 +58,7 @@ class CertificateOverviewTable implements DataRetrieval private \ILIAS\Data\Factory $data_factory; private ilCtrl|ilCtrlInterface $ctrl; private Standard $filter; - private Table $table; + private Data $table; private Renderer $ui_renderer; private ilAccessHandler $access; private ilObjUser $user; @@ -167,7 +167,7 @@ private function buildFilter(): Standard ); } - private function buildTable(): Table + private function buildTable(): Data { $uiTable = $this->ui_factory->table(); return $uiTable->data( From 3e8f0e3f9bb3e5cc4db53bfa087fa524cae23536 Mon Sep 17 00:00:00 2001 From: Marvin Beym Date: Mon, 6 May 2024 10:49:57 +0200 Subject: [PATCH 10/10] Change certificate tabe name to 'Overview' --- .../Certificate/classes/class.ilObjCertificateSettingsGUI.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/ILIAS/Certificate/classes/class.ilObjCertificateSettingsGUI.php b/components/ILIAS/Certificate/classes/class.ilObjCertificateSettingsGUI.php index a92ca1b19959..912981bb051f 100755 --- a/components/ILIAS/Certificate/classes/class.ilObjCertificateSettingsGUI.php +++ b/components/ILIAS/Certificate/classes/class.ilObjCertificateSettingsGUI.php @@ -97,7 +97,7 @@ public function getAdminTabs(): void if ($this->certificate_active_validator->validate() && $this->rbac_system->checkAccess('read', $this->object->getRefId())) { $this->tabs_gui->addTab( self::TAB_CERTIFICATES, - $this->lng->txt('certificates'), + $this->lng->txt('overview'), $this->ctrl->getLinkTarget($this, self::CMD_CERTIFICATES_OVERVIEW) ); }