diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..2ee3b828 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,14 @@ +# Change log + +## Version 1.2 (2042-01-21) +- fixed error with ascii control characters when written text is processed +- fixed juristic headline scheme +- added settings for processing written text (paragraph numbers, correction margins) +- added settings for pdf generation (heqader, footer, margins) +- improved pdf layout for written text and correction +- check existence of corrector comments for pdf upload and editor settings +- removed "Pilot" from language variables + +## Version 1.1 (2023-12-20) + +- First published version of the plugin for ILIAS 7 \ No newline at end of file diff --git a/README.md b/README.md index 679d1cbf..3283bad6 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,13 @@ -# LongEssayAssessment (Pilot Version) +# LongEssayAssessment Plugin for the LMS ILIAS open source to realize exams with writing of long texts. -This pilot version is currently **under development** to complete the functionality required by the [EDUTIEK project](https://www.edutiek.de). +The EDUTIEK project (acronym for "Einfache Durchführung textintensiver E-Klausuren") is developing a comprehensive software solution for online exams in subjects in which longer texts have to be submitted as exam solutions. These include law, history, linguistics, philosophy, sociology and many more. -An initial set of features is available in the **pre-test version** named LongEssayTask which is maintained in a different [GitHub repository](https://github.com/fneumann/LongEssayTask). +The "Long Essay Assessment" is a repository object and bundles all functions for the realisation of a text exam. Responsibilities for creating, carrying out and correcting tasks are assigned to different people via the authorisation system. Support material can be provided for editing and correction is supported by an evaluation scheme. All results can be output in PDF/A format for documentation purposes. + +The integrated "Writer" is a specialised editing page for examinees during the exam. The text editor and the task or additional material can be displayed side by side or on a full page. All editing steps are logged and are reversible. Even if the network is interrupted, you can continue writing and the editing steps will be saved afterwards. At the end of the editing time, the written text is displayed for review and its submission is finally confirmed. + +The integrated "Corrector" is a specialised editing page for the proofreaders. In the submitted text, passages are marked and provided with comments. With each comment, partial points can be awarded based on the evaluation scheme. The text and comments are clearly displayed next to each other, optionally also with the comments from the first correction in the case of a second correction. To create the overall vote, a proposal for the final grade is calculated from the sum of the partial points, which can be accepted or changed. The vote can be used to create a textual overall assessment. ## Installation @@ -22,8 +26,19 @@ An initial set of features is available in the **pre-test version** named LongEs composer install --no-dev ```` -## History +Please clear your browser cache after an update before you start the writing and ccorrection screens. + +## Branches and Versions + +The plugin is published for ILIAS in different branches: + +* **release1_ilias7** will receive bug fixes only +* **release2_ilias8** will be created by March 2024 +* **main** is the current development branch. Please do not use it for production. + +Versions 2.x will receive bug fixes as well as new features without breaking existing functionality and data. +Please consult the [CHANGELOG](CHANGELOG.md) to see the different versions. -### Version 1.1 (2023-12-20) +## Known Issues -- First published version of the plugin for ILIAS 7 \ No newline at end of file +The writing and correction of exams is tested with Firefox and Chrome, so modern Chromium based browser should work. We know about issues with older Safari browsers. Please test with you local system before writing an exam and offer a tryout service for students who should write on their own device. \ No newline at end of file diff --git a/classes/Corrector/class.CorrectorContext.php b/classes/Corrector/class.CorrectorContext.php index 93d4337b..ab72bc3d 100644 --- a/classes/Corrector/class.CorrectorContext.php +++ b/classes/Corrector/class.CorrectorContext.php @@ -22,7 +22,7 @@ use ILIAS\Plugin\LongEssayAssessment\Data\Essay\CorrectorComment; use ILIAS\Plugin\LongEssayAssessment\Data\Essay\CriterionPoints; use ILIAS\Plugin\LongEssayAssessment\Data\Task\CorrectionSettings as PluginCorrectionSettings; -use Edutiek\LongEssayAssessmentService\Data\CorrectionPage; +use Edutiek\LongEssayAssessmentService\Data\PageData; use Edutiek\LongEssayAssessmentService\Data\CorrectionMark; use Edutiek\LongEssayAssessmentService\Data\CorrectionPreferences; use ILIAS\Plugin\LongEssayAssessment\Data\Corrector\CorrectorPreferences; @@ -518,9 +518,8 @@ public function getPagesOfItem(string $item_key): array (int) $item_key, $this->task->getTaskId())) ) { foreach ($essay_repo->getEssayImagesByEssayID($repoEssay->getId()) as $repoImage) { - $pages[] = new CorrectionPage( + $pages[] = new PageData( (string) $repoImage->getId(), - $item_key, $repoImage->getPageNo(), $repoImage->getWidth(), $repoImage->getHeight(), diff --git a/classes/Corrector/class.CorrectorStartGUI.php b/classes/Corrector/class.CorrectorStartGUI.php index f0f7cef3..fc2a4328 100644 --- a/classes/Corrector/class.CorrectorStartGUI.php +++ b/classes/Corrector/class.CorrectorStartGUI.php @@ -418,7 +418,7 @@ protected function downloadWrittenPdf() $params = $this->request->getQueryParams(); $writer_id = (int) ($params['writer_id'] ?? 0); - $service = $this->localDI->getCorrectorAdminService($this->object->getId()); + $service = $this->localDI->getWriterAdminService($this->object->getId()); $repoWriter = $this->localDI->getWriterRepo()->getWriterById($writer_id); $filename = 'task' . $this->object->getId() . '_writer' . $repoWriter->getId(). '-writing.pdf'; diff --git a/classes/CorrectorAdmin/class.CorrectorAdminGUI.php b/classes/CorrectorAdmin/class.CorrectorAdminGUI.php index af1b261b..69a2105a 100644 --- a/classes/CorrectorAdmin/class.CorrectorAdminGUI.php +++ b/classes/CorrectorAdmin/class.CorrectorAdminGUI.php @@ -434,7 +434,7 @@ protected function downloadWrittenPdf() $params = $this->request->getQueryParams(); $writer_id = (int) ($params['writer_id'] ?? 0); - $service = $this->localDI->getCorrectorAdminService($this->object->getId()); + $service = $this->localDI->getWriterAdminService($this->object->getId()); $repoWriter = $this->localDI->getWriterRepo()->getWriterById($writer_id); $filename = 'task' . $this->object->getId() . '_writer' . $repoWriter->getId(). '-writing.pdf'; diff --git a/classes/CorrectorAdmin/class.CorrectorAdminService.php b/classes/CorrectorAdmin/class.CorrectorAdminService.php index c683fce7..1eaf02f2 100644 --- a/classes/CorrectorAdmin/class.CorrectorAdminService.php +++ b/classes/CorrectorAdmin/class.CorrectorAdminService.php @@ -10,7 +10,6 @@ use ILIAS\Plugin\LongEssayAssessment\Data\Task\CorrectionSettings; use ILIAS\Plugin\LongEssayAssessment\Data\Corrector\Corrector; use ILIAS\Plugin\LongEssayAssessment\Data\Corrector\CorrectorAssignment; -use ILIAS\Plugin\LongEssayAssessment\Data\Corrector\CorrectorRepository; use ILIAS\Plugin\LongEssayAssessment\Data\Essay\CorrectorSummary; use ILIAS\Plugin\LongEssayAssessment\Data\DataService; use ILIAS\Plugin\LongEssayAssessment\Data\Essay\Essay; @@ -18,9 +17,7 @@ use ILIAS\Plugin\LongEssayAssessment\Data\Object\GradeLevel; use ILIAS\Plugin\LongEssayAssessment\Data\Task\LogEntry; use ILIAS\Plugin\LongEssayAssessment\Data\Task\TaskRepository; -use ILIAS\Plugin\LongEssayAssessment\Data\Task\TaskSettings; use ILIAS\Plugin\LongEssayAssessment\Data\Writer\Writer; -use ILIAS\Plugin\LongEssayAssessment\Data\Writer\WriterRepository; use ILIAS\Data\UUID\Factory as UUID; use ilObjUser; @@ -429,6 +426,7 @@ public function createCorrectionsExport(\ilObjLongEssayAssessment $object) : str $storage->createDir($zipdir); $repoTask = $this->taskRepo->getTaskSettingsById($object->getId()); + $writerAdminService = $this->localDI->getWriterAdminService($repoTask->getTaskId()); foreach ($this->essayRepo->getEssaysByTaskId($repoTask->getTaskId()) as $repoEssay) { $repoWriter = $this->writerRepo->getWriterById($repoEssay->getWriterId()); @@ -436,7 +434,7 @@ public function createCorrectionsExport(\ilObjLongEssayAssessment $object) : str $storage->createDir($zipdir . '/' . $subdir); $filename = $subdir . '-writing.pdf'; - $storage->write($zipdir . '/' . $subdir. '/'. $filename, $this->getWritingAsPdf($object, $repoWriter)); + $storage->write($zipdir . '/' . $subdir. '/'. $filename, $writerAdminService->getWritingAsPdf($object, $repoWriter)); $filename = $subdir . '-correction.pdf'; $storage->write($zipdir . '/' . $subdir. '/'. $filename, $this->getCorrectionAsPdf($object, $repoWriter)); @@ -453,32 +451,7 @@ public function createCorrectionsExport(\ilObjLongEssayAssessment $object) : str //$delivery = new \ilFileDelivery() } - /** - * Get the writing of an essay as PDF string - */ - public function getWritingAsPdf(\ilObjLongEssayAssessment $object, Writer $repoWriter, $anonymous = false) - { - $context = new CorrectorContext(); - $context->init((string) $this->dic->user()->getId(), (string) $object->getRefId()); - $writingTask = $context->getWritingTaskByWriterId($repoWriter->getId()); - if ($anonymous) { - $writingTask = $writingTask->withWriterName($repoWriter->getPseudonym()); - } - $writtenEssay = $context->getEssayOfItem((string) $repoWriter->getId()); - - $item = new DocuItem( - (string) $repoWriter->getId(), - $writingTask, - $writtenEssay, - [], - [], - ); - - $service = new Service($context); - return $service->getWritingAsPdf($item); - } - /** * Get the correction of an essay as PDF string * if a repo corrector is given as parameter, then only this correction is included, not other correctors diff --git a/classes/Data/Essay/class.EssayRepository.php b/classes/Data/Essay/class.EssayRepository.php index 6105f2e9..f3847b4f 100644 --- a/classes/Data/Essay/class.EssayRepository.php +++ b/classes/Data/Essay/class.EssayRepository.php @@ -229,16 +229,26 @@ public function getCorrectorCommentById(int $id) : ?RecordData /** * @param int $essay_id - * @param int $corrector_id + * @param int|null $corrector_id * @return CorrectorComment[] */ - public function getCorrectorCommentsByEssayIdAndCorrectorId(int $essay_id, int $corrector_id): array + public function getCorrectorCommentsByEssayIdAndCorrectorId(int $essay_id, ?int $corrector_id = null): array { - $query = "SELECT * FROM " . CorrectorComment::tableName() . " WHERE essay_id = " . $this->db->quote($essay_id, 'integer') . - " AND corrector_id = ". $this->db->quote($corrector_id, 'integer'); + $query = "SELECT * FROM " . CorrectorComment::tableName() . " WHERE essay_id = " . $this->db->quote($essay_id, 'integer'); + if (isset($corrector_id)) { + $query .= " AND corrector_id = ". $this->db->quote($corrector_id, 'integer');; + } return $this->queryRecords($query, CorrectorComment::model()); } + public function hasCorrectorCommentsByTaskId(int $task_id) + { + $query = "SELECT c.id FROM xlas_corrector_comment m JOIN xlas_corrector c ON m.corrector_id = c.id WHERE c.task_id = " + . $this->db->quote($task_id, 'integer') . ' LIMIT 1'; + + return !empty($this->getIntegerList($query, 'id')); + } + /** * @param int $id * @return CriterionPoints|null diff --git a/classes/Data/Object/class.ObjectRepository.php b/classes/Data/Object/class.ObjectRepository.php index 5e4a32e1..1d027d47 100644 --- a/classes/Data/Object/class.ObjectRepository.php +++ b/classes/Data/Object/class.ObjectRepository.php @@ -31,12 +31,12 @@ public function __construct( } /** - * @return ObjectSettings|null + * @return ObjectSettings */ - public function getObjectSettingsById(int $a_id): ?RecordData + public function getObjectSettingsById(int $a_id): RecordData { $query = "SELECT * FROM xlas_object_settings WHERE obj_id = " . $this->db->quote($a_id, 'integer'); - return $this->getSingleRecord($query, ObjectSettings::model()); + return $this->getSingleRecord($query, ObjectSettings::model(), new ObjectSettings($a_id)); } public function ifGradeLevelExistsById(int $a_id): bool diff --git a/classes/Data/Task/class.EditorSettings.php b/classes/Data/Task/class.EditorSettings.php index 1623250d..d640da5c 100644 --- a/classes/Data/Task/class.EditorSettings.php +++ b/classes/Data/Task/class.EditorSettings.php @@ -29,7 +29,11 @@ class EditorSettings extends RecordData 'headline_scheme'=> 'text', 'formatting_options' => 'text', 'notice_boards' => 'integer', - 'copy_allowed' => 'integer' + 'copy_allowed' => 'integer', + 'add_paragraph_numbers' => 'integer', + 'add_correction_margin' => 'integer', + 'left_correction_margin' => 'integer', + 'right_correction_margin' => 'integer' ]; protected int $task_id; @@ -37,6 +41,10 @@ class EditorSettings extends RecordData protected string $formatting_options = self::FORMATTING_OPTIONS_MEDIUM; protected int $notice_boards = 0; protected int $copy_allowed = 0; + protected int $add_paragraph_numbers = 1; + protected int $add_correction_margin = 0; + protected int $left_correction_margin = 0; + protected int $right_correction_margin = 0; public function __construct(int $task_id) { @@ -47,68 +55,48 @@ public static function model() { return new self(0); } - /** - * @return string - */ public function getHeadlineScheme(): string { - return (string)$this->headline_scheme; + return $this->headline_scheme; } - /** - * @param ?string $headline_scheme - */ - public function setHeadlineScheme(?string $headline_scheme): void + public function setHeadlineScheme(?string $headline_scheme): self { $this->headline_scheme = (string)$headline_scheme; + return $this; } - /** - * @return string - */ public function getFormattingOptions(): string { - return (string)$this->formatting_options; + return $this->formatting_options; } - /** - * @param ?string $formatting_options - */ - public function setFormattingOptions(?string $formatting_options): void + public function setFormattingOptions(?string $formatting_options): self { - $this->formatting_options = (string)$formatting_options; + $this->formatting_options = (string) $formatting_options; + return $this; } - /** - * @return int - */ public function getNoticeBoards(): int { - return (int)$this->notice_boards; + return $this->notice_boards; } - /** - * @param ?int $notice_boards - */ - public function setNoticeBoards(?int $notice_boards): void + public function setNoticeBoards(?int $notice_boards): self { $this->notice_boards = $notice_boards; + return $this; } - /** - * @return bool - */ public function isCopyAllowed(): bool { - return (bool)$this->copy_allowed; + return (bool )$this->copy_allowed; } - /** - * @param ?bool $copy_allowed - */ - public function setCopyAllowed(?bool $copy_allowed): void + public function setCopyAllowed(?bool $copy_allowed): self { - $this->copy_allowed = (int)$copy_allowed; + $this->copy_allowed = (int) $copy_allowed; + return $this; } /** @@ -119,13 +107,53 @@ public function getTaskId(): int return $this->task_id; } - /** - * @param int $task_id - * @return EditorSettings - */ - public function setTaskId(int $task_id): EditorSettings + public function setTaskId(int $task_id): self { $this->task_id = $task_id; return $this; } + + public function getAddParagraphNumbers() : bool + { + return (bool) $this->add_paragraph_numbers; + } + + public function setAddParagraphNumbers(bool $add_paragraph_numbers) : self + { + $this->add_paragraph_numbers = (int) $add_paragraph_numbers; + return $this; + } + + public function getAddCorrectionMargin() : bool + { + return (bool) $this->add_correction_margin; + } + + public function setAddCorrectionMargin(bool $add_correction_margin) : self + { + $this->add_correction_margin = (int) $add_correction_margin; + return $this; + } + + public function getLeftCorrectionMargin() : int + { + return $this->left_correction_margin; + } + + public function setLeftCorrectionMargin(int $left_correction_margin) : self + { + $this->left_correction_margin = $left_correction_margin; + return $this; + } + + public function getRightCorrectionMargin() : int + { + return $this->right_correction_margin; + } + + public function setRightCorrectionMargin(int $right_correction_margin) : self + { + $this->right_correction_margin = $right_correction_margin; + return $this; + } } \ No newline at end of file diff --git a/classes/Data/Task/class.PdfSettings.php b/classes/Data/Task/class.PdfSettings.php new file mode 100644 index 00000000..2d5fb8f8 --- /dev/null +++ b/classes/Data/Task/class.PdfSettings.php @@ -0,0 +1,124 @@ + + */ +class PdfSettings extends RecordData +{ + protected const tableName = 'xlas_pdf_settings'; + protected const hasSequence = false; + protected const keyTypes = [ + 'task_id' => 'integer', + ]; + protected const otherTypes = [ + 'add_header' => 'integer', + 'add_footer' => 'integer', + 'top_margin' => 'integer', + 'bottom_margin' => 'integer', + 'left_margin' => 'integer', + 'right_margin' => 'integer' + ]; + + protected int $task_id; + protected int $add_header = 1; + protected int $add_footer = 1; + protected int $top_margin = 10; + protected int $bottom_margin = 10; + protected int $left_margin = 10; + protected int $right_margin = 10; + + public function __construct(int $task_id) + { + $this->task_id = $task_id; + } + + public static function model() { + return new self(0); + } + + /** + * @return int + */ + public function getTaskId(): int + { + return $this->task_id; + } + + public function setTaskId(int $task_id): self + { + $this->task_id = $task_id; + return $this; + } + + public function getAddHeader() : bool + { + return (bool) $this->add_header; + } + + public function setAddHeader(bool $add_header) : self + { + $this->add_header = (int) $add_header; + return $this; + } + + public function getAddFooter() : bool + { + return (bool) $this->add_footer; + } + + public function setAddFooter(bool $add_footer) : self + { + $this->add_footer = (int) $add_footer; + return $this; + } + + + public function getTopMargin() : int + { + return $this->top_margin; + } + + public function setTopMargin(int $top_margin) : self + { + $this->top_margin = $top_margin; + return $this; + } + + public function getBottomMargin() : int + { + return $this->bottom_margin; + } + + public function setBottomMargin(int $bottom_margin) : self + { + $this->bottom_margin = $bottom_margin; + return $this; + } + + public function getLeftMargin() : int + { + return $this->left_margin; + } + + public function setLeftMargin(int $left_margin) : self + { + $this->left_margin = $left_margin; + return $this; + } + + public function getRightMargin() : int + { + return $this->right_margin; + } + + public function setRightMargin(int $right_margin) : self + { + $this->right_margin = $right_margin; + return $this; + } +} \ No newline at end of file diff --git a/classes/Data/Task/class.TaskRepository.php b/classes/Data/Task/class.TaskRepository.php index 86b7d4b4..ec692757 100644 --- a/classes/Data/Task/class.TaskRepository.php +++ b/classes/Data/Task/class.TaskRepository.php @@ -32,38 +32,42 @@ public function __construct(\ilDBInterface $db, /** * Save record data of an allowed type - * @param TaskSettings|EditorSettings|CorrectionSettings|Alert|Resource|Location $record + * @param TaskSettings|EditorSettings|PdfSettings|CorrectionSettings|Alert|Resource|Location $record */ public function save(RecordData $record) { $this->replaceRecord($record); } - public function createTask(TaskSettings $a_task_settings, EditorSettings $a_editor_settings, CorrectionSettings $a_correction_settings) - { - $this->save($a_task_settings); - $this->save($a_editor_settings); - $this->save($a_correction_settings); - } - /** * @param int $a_id - * @return EditorSettings|null + * @return EditorSettings */ - public function getEditorSettingsById(int $a_id): ?RecordData + public function getEditorSettingsById(int $a_id): RecordData { $query = "SELECT * FROM xlas_editor_settings WHERE task_id = " . $this->db->quote($a_id, 'integer'); - return $this->getSingleRecord($query, EditorSettings::model()); + return $this->getSingleRecord($query, EditorSettings::model(), new EditorSettings($a_id)); } - /** + /** + * @param int $a_id + * @return PdfSettings + */ + public function getPdfSettingsById(int $a_id): RecordData + { + $query = "SELECT * FROM xlas_pdf_settings WHERE task_id = " . $this->db->quote($a_id, 'integer'); + return $this->getSingleRecord($query, PdfSettings::model(), new PdfSettings($a_id)); + } + + + /** * @param int $a_id - * @return CorrectionSettings|null + * @return CorrectionSettings */ - public function getCorrectionSettingsById(int $a_id): ?RecordData + public function getCorrectionSettingsById(int $a_id): RecordData { $query = "SELECT * FROM xlas_corr_setting WHERE task_id = " . $this->db->quote($a_id, 'integer'); - return $this->getSingleRecord($query, CorrectionSettings::model()); + return $this->getSingleRecord($query, CorrectionSettings::model(), new CorrectionSettings($a_id)); } @@ -74,12 +78,12 @@ public function ifTaskExistsById(int $a_id): bool /** * @param int $a_id - * @return TaskSettings|null + * @return TaskSettings */ - public function getTaskSettingsById(int $a_id): ?RecordData + public function getTaskSettingsById(int $a_id): RecordData { $query = "SELECT * FROM xlas_task_settings WHERE task_id = " . $this->db->quote($a_id, 'integer'); - return $this->getSingleRecord($query, TaskSettings::model()); + return $this->getSingleRecord($query, TaskSettings::model(), new TaskSettings($a_id)); } public function ifAlertExistsById(int $a_id): bool @@ -108,6 +112,8 @@ public function deleteTask(int $a_id) $this->db->manipulate("DELETE FROM xlas_task_settings" . " WHERE task_id = " . $this->db->quote($a_id, "integer")); $this->db->manipulate("DELETE FROM xlas_editor_settings" . + " WHERE task_id = " . $this->db->quote($a_id, "integer")); + $this->db->manipulate("DELETE FROM xlas_pdf_settings" . " WHERE task_id = " . $this->db->quote($a_id, "integer")); $this->db->manipulate("DELETE FROM xlas_corr_setting" . " WHERE task_id = " . $this->db->quote($a_id, "integer")); diff --git a/classes/Data/class.DataService.php b/classes/Data/class.DataService.php index bfe1f7e2..4e5dca66 100644 --- a/classes/Data/class.DataService.php +++ b/classes/Data/class.DataService.php @@ -537,11 +537,6 @@ public function formatCorrectionInclusions (CorrectorSummary $summary, Corrector . $this->plugin->txt('include_criteria_points') . ' ' . $this->plugin->txt($inclusion == CorrectorSummary::INCLUDE_INFO ? 'include_suffix_info' : 'include_suffix_relevant'); } - if (($inclusion = $summary->getIncludeWriterNotes() ?? $preferences->getIncludeWriterNotes()) > CorrectorSummary::INCLUDE_NOT) { - $text .= ($text ? ', ' : '') - . $this->plugin->txt('include_writer_notes') . ' ' - . $this->plugin->txt($inclusion == CorrectorSummary::INCLUDE_INFO ? 'include_suffix_info' : 'include_suffix_relevant'); - } if (empty($text)) { $text = $this->plugin->txt('include_nothing'); } diff --git a/classes/Task/class.EditorSettingsGUI.php b/classes/Task/class.EditorSettingsGUI.php index 93a5cd58..9fae4c60 100644 --- a/classes/Task/class.EditorSettingsGUI.php +++ b/classes/Task/class.EditorSettingsGUI.php @@ -40,14 +40,18 @@ public function executeCommand() protected function editSettings() { $task_repo = $this->localDI->getTaskRepo(); + $essay_repo = $this->localDI->getEssayRepo(); - $editorSettings = $task_repo->getEditorSettingsById($this->object->getId()); + $editorSettings = $task_repo->getEditorSettingsById($this->object->getId()); + $pdfSettings = $task_repo->getPdfSettingsById($this->object->getId()); + $hasComments = $essay_repo->hasCorrectorCommentsByTaskId($this->object->getId()); $factory = $this->uiFactory->input()->field(); $sections = []; - // Object + // Editor + $fields = []; $fields['headline_scheme'] = $factory->select($this->plugin->txt('headline_scheme'), [ @@ -87,6 +91,80 @@ protected function editSettings() $sections['editor'] = $factory->section($fields, $this->plugin->txt('editor_settings')); + // Processing + + $fields = []; + + $fields['add_paragraph_numbers'] = $factory->checkbox( + $this->plugin->txt('add_paragraph_numbers'), + $this->plugin->txt('add_paragraph_numbers_info')) + ->withDisabled($hasComments) + ->withValue($editorSettings->getAddParagraphNumbers()); + + $fields['add_correction_margin'] = $factory->optionalGroup([ + 'left_correction_margin' => $factory->numeric($this->plugin->txt('left_correction_margin')) + ->withAdditionalTransformation($this->refinery->to()->int()) + ->withRequired(true) + ->withDisabled($hasComments) + ->withValue($editorSettings->getLeftCorrectionMargin()), + 'right_correction_margin' => $factory->numeric($this->plugin->txt('right_correction_margin')) + ->withAdditionalTransformation($this->refinery->to()->int()) + ->withRequired(true) + ->withDisabled($hasComments) + ->withValue($editorSettings->getRightCorrectionMargin()), + ], + $this->plugin->txt('add_correction_margin'), + $this->plugin->txt('add_correction_margin_info'), + )->withDisabled($hasComments); + // strange but effective + if (!$editorSettings->getAddCorrectionMargin()) { + $fields['add_correction_margin'] = $fields['add_correction_margin']->withValue(null); + } + + $sections['processing'] = $factory->section($fields, + $this->plugin->txt('processing_settings'), + $this->plugin->txt('processing_settings_info') + ); + + // PDF generation + + $fields = []; + + $fields['add_header'] = $factory->checkbox($this->plugin->txt('pdf_add_header'), $this->plugin->txt('pdf_add_header_info')) + ->withValue($pdfSettings->getAddHeader()); + + $fields['add_footer'] = $factory->checkbox($this->plugin->txt('pdf_add_footer'), $this->plugin->txt('pdf_add_footer_info')) + ->withValue($pdfSettings->getAddFooter()); + + $fields['top_margin'] = $factory->numeric($this->plugin->txt('pdf_top_margin'), $this->plugin->txt('pdf_top_margin_info')) + ->withAdditionalTransformation($this->refinery->to()->int()) + ->withAdditionalTransformation($this->refinery->int()->isGreaterThan(4)) + ->withRequired(true) + ->withValue($pdfSettings->getTopMargin()); + + $fields['bottom_margin'] = $factory->numeric($this->plugin->txt('pdf_bottom_margin'), $this->plugin->txt('pdf_bottom_margin_info')) + ->withAdditionalTransformation($this->refinery->to()->int()) + ->withAdditionalTransformation($this->refinery->int()->isGreaterThan(4)) + ->withRequired(true) + ->withValue($pdfSettings->getBottomMargin()); + + $fields['left_margin'] = $factory->numeric($this->plugin->txt('pdf_left_margin'), $this->plugin->txt('pdf_left_margin_info')) + ->withAdditionalTransformation($this->refinery->to()->int()) + ->withAdditionalTransformation($this->refinery->int()->isGreaterThan(4)) + ->withRequired(true) + ->withValue($pdfSettings->getLeftMargin()); + + $fields['right_margin'] = $factory->numeric($this->plugin->txt('pdf_right_margin'), $this->plugin->txt('pdf_right_margin_info')) + ->withAdditionalTransformation($this->refinery->to()->int()) + ->withAdditionalTransformation($this->refinery->int()->isGreaterThan(4)) + ->withRequired(true) + ->withValue($pdfSettings->getRightMargin()); + + $sections['pdf'] = $factory->section($fields, + $this->plugin->txt('pdf_settings'), + $this->plugin->txt('pdf_settings_info') + ); + $form = $this->uiFactory->input()->container()->form()->standard($this->ctrl->getFormAction($this), $sections); // apply inputs @@ -100,8 +178,28 @@ protected function editSettings() $editorSettings->setHeadlineScheme($data['editor']['headline_scheme']); $editorSettings->setFormattingOptions($data['editor']['formatting_options']); $editorSettings->setNoticeBoards((int) $data['editor']['notice_boards']); - $editorSettings->setCopyAllowed($data['editor']['copy_allowed']); - $task_repo->save($editorSettings); + $editorSettings->setCopyAllowed((bool) $data['editor']['copy_allowed']); + + if (!$hasComments) { + $editorSettings->setAddParagraphNumbers((bool) $data['processing']['add_paragraph_numbers']); + if (isset($data['processing']['add_correction_margin']) && is_array($data['processing']['add_correction_margin'])) { + $editorSettings->setAddCorrectionMargin(true); + $editorSettings->setLeftCorrectionMargin((int) $data['processing']['add_correction_margin']['left_correction_margin']); + $editorSettings->setRightCorrectionMargin((int) $data['processing']['add_correction_margin']['right_correction_margin']); + } + else { + $editorSettings->setAddCorrectionMargin(false); + } + } + $task_repo->save($editorSettings); + + $pdfSettings->setAddHeader((bool) $data['pdf']['add_header']); + $pdfSettings->setAddFooter((bool) $data['pdf']['add_footer']); + $pdfSettings->setTopMargin((int) $data['pdf']['top_margin']); + $pdfSettings->setBottomMargin((int) $data['pdf']['bottom_margin']); + $pdfSettings->setLeftMargin((int) $data['pdf']['left_margin']); + $pdfSettings->setRightMargin((int) $data['pdf']['right_margin']); + $task_repo->save($pdfSettings); ilUtil::sendSuccess($this->lng->txt("settings_saved"), true); $this->ctrl->redirect($this, "editSettings"); diff --git a/classes/Writer/class.WriterContext.php b/classes/Writer/class.WriterContext.php index 6650cc28..c50e0267 100644 --- a/classes/Writer/class.WriterContext.php +++ b/classes/Writer/class.WriterContext.php @@ -3,7 +3,7 @@ namespace ILIAS\Plugin\LongEssayAssessment\Writer; use Edutiek\LongEssayAssessmentService\Data\Alert; -use Edutiek\LongEssayAssessmentService\Data\WritingSettings; +use Edutiek\LongEssayAssessmentService\Data\PageData; use Edutiek\LongEssayAssessmentService\Data\WritingStep; use Edutiek\LongEssayAssessmentService\Data\WritingTask; use Edutiek\LongEssayAssessmentService\Writer\Context; @@ -118,6 +118,32 @@ public function getWrittenEssay(): WrittenEssay ); } + + /** + * @inheritDoc + */ + public function getPagesOfWriter(): array + { + $essay_repo = $this->localDI->getEssayRepo(); + + $pages = []; + if (!empty($repoEssay = $essay_repo->getEssayByWriterIdAndTaskId($this->getRepoWriter()->getId(), $this->getRepoWriter()->getTaskId())) + ) { + foreach ($essay_repo->getEssayImagesByEssayID($repoEssay->getId()) as $repoImage) { + $pages[] = new PageData( + (string) $repoImage->getId(), + $repoImage->getPageNo(), + $repoImage->getWidth(), + $repoImage->getHeight(), + null, + null + ); + } + } + return $pages; + } + + /** * @inheritDoc */ diff --git a/classes/Writer/class.WriterStartGUI.php b/classes/Writer/class.WriterStartGUI.php index bfdbd6fa..01ff241f 100644 --- a/classes/Writer/class.WriterStartGUI.php +++ b/classes/Writer/class.WriterStartGUI.php @@ -339,17 +339,11 @@ protected function startWritingReview() protected function downloadWriterPdf() { if ($this->object->canViewWriterScreen()) { - - $context = new WriterContext(); - $context->init((string) $this->dic->user()->getId(), (string) $this->object->getRefId()); + $service = $this->localDI->getWriterAdminService($this->object->getId()); $repoWriter = $this->localDI->getWriterRepo()->getWriterByUserIdAndTaskId($this->dic->user()->getId(), $this->object->getId()); - $service = new Service($context); - -// $filename = 'task' . $this->object->getId() . '_user' . $this->dic->user()->getId(). '.html'; -// ilUtil::deliverData($service->getProcessedTextAsHtml(), $filename, 'text/html'); $filename = 'task' . $this->object->getId() . '_writer' . $repoWriter->getId(). '-writing.pdf'; - ilUtil::deliverData($service->getProcessedTextAsPdf(), $filename, 'application/pdf'); + ilUtil::deliverData($service->getWritingAsPdf($this->object, $repoWriter), $filename, 'application/pdf'); } else { $this->raisePermissionError(); @@ -357,8 +351,8 @@ protected function downloadWriterPdf() } /** - * Download a generated pdf from the processed written text - */ + * Download a generated pdf from the processed written text + */ protected function downloadCorrectedPdf() { if ($this->object->canReviewCorrectedEssay()) { diff --git a/classes/WriterAdmin/class.WriterAdminGUI.php b/classes/WriterAdmin/class.WriterAdminGUI.php index 852bdbaa..04059243 100644 --- a/classes/WriterAdmin/class.WriterAdminGUI.php +++ b/classes/WriterAdmin/class.WriterAdminGUI.php @@ -83,6 +83,7 @@ public function executeCommand() */ protected function showStartPage() { + $this->addContentCss(); $this->toolbar->setFormAction($this->ctrl->getFormAction($this)); \ilRepositorySearchGUI::fillAutoCompleteToolbar( @@ -721,13 +722,14 @@ protected function getEssaysFromWriterIds(): array protected function showEssay() { $essays = $this->getEssaysFromWriterIds(); - $value = count($essays) > 0 && ($essay = array_pop($essays)) !== null? $essay->getWrittenText() : null; + $value = count($essays) > 0 && ($essay = array_pop($essays)) !== null? $essay->getWrittenText() : ''; + $value = $this->displayContent($this->localDI->getDataService($this->object->getId())->cleanupRichText($value)); $this->ctrl->saveParameter($this, "writer_id"); $link = $this->ctrl->getFormAction($this, "showEssay", "", true); $sight_modal = $this->uiFactory->modal()->roundtrip($this->plugin->txt("submission"), - $this->uiFactory->legacy($value ? $this->localDI->getDataService($this->object->getId())->cleanupRichText($value): "") + $this->uiFactory->legacy($value) ); $reload_button = $this->uiFactory->button()->standard($this->lng->txt("refresh"), "") ->withLoadingAnimationOnClick(true) @@ -811,15 +813,18 @@ protected function changeTextToPdfMultiConfirmation() } if(empty($items)) { - ilUtil::sendFailure($this->plugin->txt("change_text_to_pdf_none_possible"), true); - $this->ctrl->redirect($this, "showStartPage"); + $change_modal = $this->uiFactory->modal()->roundtrip( + $this->plugin->txt("change_text_to_pdf"), + $this->uiFactory->legacy($this->plugin->txt("change_text_to_pdf_none_possible")), + ); + } + else { + $change_modal = $this->uiFactory->modal()->interruptive( + $this->plugin->txt("change_text_to_pdf"), + $this->plugin->txt("change_text_to_pdf_confirmation"), + $this->ctrl->getFormAction($this, "changeTextToPdf") + )->withAffectedItems($items)->withActionButtonLabel('change'); } - - $change_modal = $this->uiFactory->modal()->interruptive( - $this->plugin->txt("change_text_to_pdf"), - $this->plugin->txt("change_text_to_pdf_confirmation"), - $this->ctrl->getFormAction($this, "changeTextToPdf") - )->withAffectedItems($items)->withActionButtonLabel('change'); echo($this->renderer->renderAsync($change_modal)); exit(); @@ -860,7 +865,7 @@ public function changeTextToPdf() { $context = new WriterContext(); $context->init((string) $writer->getUserId(), (string) $this->object->getRefId()); - $service->createPdfFromText($essay, $context); + $service->createPdfFromText($this->object, $essay, $writer); $service->purgeCorrectorComments($essay); } } @@ -871,6 +876,7 @@ public function changeTextToPdf() { public function uploadPDFVersion() { + $essay_repo = $this->localDI->getEssayRepo(); $writer_repo = $this->localDI->getWriterRepo(); $task_repo = $this->localDI->getTaskRepo(); $task_id = $this->object->getId(); @@ -911,10 +917,7 @@ public function uploadPDFVersion() } $service->handlePDFVersionInput($essay, $file_id); - - $context = new WriterContext(); - $context->init((string) $writer->getUserId(), (string) $this->object->getRefId()); - $service->createEssayImages($essay, $context); + $service->createEssayImages($this->object, $essay, $writer); $service->purgeCorrectorComments($essay); $this->ctrl->redirect($this); @@ -949,19 +952,20 @@ public function uploadPDFVersion() : $this->plugin->txt("pdf_version_upload"), $form)->withCard($user_info) ]; - if($essay->getEditStarted()){ - - if($essay->getWritingAuthorized() !== null - && $essay->getWritingAuthorizedBy() === $writer->getUserId() - && $essay->getPdfVersion() === null) - { + if($essay->getEditStarted()) { + if ($essay->getPdfVersion() !== null) { + if ($service->hasCorrectorComments($essay)) { + ilUtil::sendQuestion($this->plugin->txt("pdf_version_info_already_uploaded")); + } + } elseif($essay->getWritingAuthorized() !== null && $essay->getWritingAuthorizedBy() === $writer->getUserId()) { ilUtil::sendQuestion($this->plugin->txt("pdf_version_warning_authorized_essay")); - }else if($essay->getPdfVersion() === null){ - ilUtil::sendInfo($this->plugin->txt("pdf_version_info_started_essay")); + }else { + ilUtil::sendQuestion($this->plugin->txt("pdf_version_info_started_essay")); } - - $subs[] = $this->uiFactory->panel()->sub($this->plugin->txt("writing"), - $this->uiFactory->legacy((string) $essay->getWrittenText()) + + $this->addContentCss(); + $subs[] = $this->uiFactory->panel()->sub($this->plugin->txt("pdf_version_header_writing"), + $this->uiFactory->legacy($this->displayContent($this->localDI->getDataService($task_id)->cleanupRichText($essay->getWrittenText()))) ); } diff --git a/classes/WriterAdmin/class.WriterAdminService.php b/classes/WriterAdmin/class.WriterAdminService.php index 29d170b5..766f4d7e 100644 --- a/classes/WriterAdmin/class.WriterAdminService.php +++ b/classes/WriterAdmin/class.WriterAdminService.php @@ -18,6 +18,7 @@ use ILIAS\Plugin\LongEssayAssessment\Writer\WriterContext; use ILIAS\Filesystem\Stream\Streams; use ILIAS\Plugin\LongEssayAssessment\Data\Essay\EssayImage; +use ilObjLongEssayAssessment; class WriterAdminService extends BaseService { @@ -88,6 +89,23 @@ public function getOrCreateEssayForWriter(Writer $writer) : Essay return $essay; } + /** + * Get the writing of an essay as PDF string + */ + public function getWritingAsPdf(ilObjLongEssayAssessment $object, Writer $repoWriter, bool $anonymous = false, bool $rawContent = false) : string + { + $context = new WriterContext(); + $context->init((string) $repoWriter->getUserId(), (string) $object->getRefId()); + + $writingTask = $context->getWritingTask(); + if ($anonymous) { + $writingTask = $writingTask->withWriterName($repoWriter->getPseudonym()); + } + $writtenEssay = $context->getWrittenEssay(); + + $service = new Service($context); + return $service->getWritingAsPdf($writingTask, $writtenEssay, $rawContent); + } public function createLogExport() { @@ -386,6 +404,12 @@ public function handlePDFVersionInput(Essay $essay, ?string $new_file_id){ } $this->essayRepo->save($essay); } + + public function hasCorrectorComments(Essay $essay) : bool + { + $essay_repo = LongEssayAssessmentDI::getInstance()->getEssayRepo(); + return !empty($essay_repo->getCorrectorCommentsByEssayIdAndCorrectorId($essay->getId(), null)); + } public function purgeCorrectorComments(Essay $essay) { @@ -393,29 +417,27 @@ public function purgeCorrectorComments(Essay $essay) $essay_repo->deleteCorrectorCommentByEssayId($essay->getId()); } - public function createPdfFromText(Essay $essay, WriterContext $context) + public function createPdfFromText(ilObjLongEssayAssessment $object, Essay $essay, Writer $writer) { $essay_repo = LongEssayAssessmentDI::getInstance()->getEssayRepo(); - $service = new Service($context); if (empty($essay->getPdfVersion()) && !empty($essay->getWrittenText())) { - $content = $service->getProcessedTextAsPlainPdf(); + $content = $this->getWritingAsPdf($object, $writer, true, true); $stream = Streams::ofString($content); $file_id = $this->dic->resourceStorage()->manage()->stream($stream, new PDFVersionResourceStakeholder(), $this->plugin->txt('pdf_from_text')); $essay->setPdfVersion((string) $file_id); $essay_repo->save($essay); $this->authorizeWriting($essay, $this->dic->user()->getId()); - // test is put into the PDF, so it does not need to be added to the images - $this->createEssayImages($essay, $context, false); + // text is put into the created PDF, so it does not need to be added to the images + $this->createEssayImages($object, $essay, $writer, false); } } - public function createEssayImages(Essay $essay, WriterContext $context, bool $with_text = true) + public function createEssayImages(ilObjLongEssayAssessment $object, Essay $essay, Writer $writer, bool $with_text = true) { $essay_repo = LongEssayAssessmentDI::getInstance()->getEssayRepo(); - $service = new Service($context); - + $this->removeEssayImages($essay->getId()); $pdfs = []; @@ -423,7 +445,7 @@ public function createEssayImages(Essay $essay, WriterContext $context, bool $wi if ($with_text && !empty($essay->getWrittenText())) { $fs = $this->dic->filesystem()->temp(); - $fs->put('xlas/processed_text.pdf', $service->getProcessedTextAsPlainPdf()); + $fs->put('xlas/processed_text.pdf', $this->getWritingAsPdf($object, $writer,true, true)); $pdfs[] = $fs->readStream('xlas/processed_text.pdf')->detach(); } @@ -434,6 +456,9 @@ public function createEssayImages(Essay $essay, WriterContext $context, bool $wi } if (!empty($pdfs)) { + $context = new WriterContext(); + $context->init((string) $writer->getUserId(), (string) $object->getRefId()); + $service = new Service($context); $images = $service->createPageImagesFromPdfs($pdfs); $page = 1; diff --git a/classes/class.BaseGUI.php b/classes/class.BaseGUI.php index 98c3f7a5..b41fe14b 100644 --- a/classes/class.BaseGUI.php +++ b/classes/class.BaseGUI.php @@ -5,6 +5,7 @@ use ILIAS\DI\Container; use ILIAS\Plugin\LongEssayAssessment\Data\DataService; +use ILIAS\Plugin\LongEssayAssessment\Data\Task\EditorSettings; use ILIAS\UI\Factory; use ILIAS\UI\Renderer; use Psr\Http\Message\RequestInterface; @@ -135,4 +136,41 @@ public function displayText(?string $html) : string { return '