From 3d3eaee568091a6fbed6e635d7d386481c06a135 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Noack?= <noack@data-quest.de> Date: Thu, 28 Dec 2023 11:12:49 +0000 Subject: [PATCH] Resolve #3344 "Testergebnisse aus ILIAS in das Stud.IP Gradebook importieren" Closes #3344 Merge request studip/studip!2271 --- .../course/gradebook/lecturers.php | 164 ++++++++++++++++++ app/controllers/course/gradebook/students.php | 23 +++ .../course/gradebook/template_helpers.php | 19 ++ .../lecturers/custom_definitions.php | 21 ++- .../lecturers/edit_custom_definitions.php | 7 - .../lecturers/edit_ilias_definition.php | 26 +++ .../lecturers/edit_ilias_definitions.php | 75 ++++++++ .../course/gradebook/lecturers/index.php | 14 +- .../lecturers/new_ilias_definition.php | 32 ++++ app/views/course/gradebook/students/index.php | 21 ++- .../5.5.15_step3344_ilias_results.php | 32 ++++ lib/cronjobs/import_ilias_testresults.php | 51 ++++++ lib/ilias_interface/ConnectedIlias.class.php | 15 ++ .../IliasObjectConnections.class.php | 37 +++- lib/ilias_interface/IliasSoap.class.php | 37 ++++ lib/modules/GradebookModule.class.php | 6 + 16 files changed, 559 insertions(+), 21 deletions(-) create mode 100644 app/views/course/gradebook/lecturers/edit_ilias_definition.php create mode 100644 app/views/course/gradebook/lecturers/edit_ilias_definitions.php create mode 100644 app/views/course/gradebook/lecturers/new_ilias_definition.php create mode 100644 db/migrations/5.5.15_step3344_ilias_results.php create mode 100644 lib/cronjobs/import_ilias_testresults.php diff --git a/app/controllers/course/gradebook/lecturers.php b/app/controllers/course/gradebook/lecturers.php index 2e98856e7fc..2cba5f3bfc0 100644 --- a/app/controllers/course/gradebook/lecturers.php +++ b/app/controllers/course/gradebook/lecturers.php @@ -43,6 +43,7 @@ class Course_Gradebook_LecturersController extends AuthenticatedController $this->groupedInstances = $this->groupedInstances($course); $this->sumOfWeights = $this->getSumOfWeights($gradingDefinitions); $this->totalSums = $this->sumOfWeights ? $this->getTotalSums($gradingDefinitions) : 0; + $this->totalPassed = $this->getTotalPassed($gradingDefinitions); } /** @@ -71,6 +72,7 @@ class Course_Gradebook_LecturersController extends AuthenticatedController $categoryName = Definition::CUSTOM_DEFINITIONS_CATEGORY === $category ? _('Manuell eingetragen') : $category; foreach ($this->groupedDefinitions[$category] as $definition) { $headerLine[] = $categoryName.': '.$definition->name; + $headerLine[] = _('bestanden') . '(' . $categoryName.': '.$definition->name . ')'; } } $studentLines = []; @@ -81,6 +83,9 @@ class Course_Gradebook_LecturersController extends AuthenticatedController $studentLine[] = isset($this->groupedInstances[$user->user_id][$definition->id]) ? $this->groupedInstances[$user->user_id][$definition->id]->rawgrade : 0; + $studentLine[] = isset($this->groupedInstances[$user->user_id][$definition->id]) + ? $this->groupedInstances[$user->user_id][$definition->id]->passed + : 0; } } $studentLines[] = $studentLine; @@ -166,6 +171,8 @@ class Course_Gradebook_LecturersController extends AuthenticatedController )->pluck('id'); $grades = \Request::getArray('grades'); + $passed = \Request::getArray('passed'); + $feedback = \Request::getArray('feedback'); foreach ($grades as $studentId => $studentGrades) { if (!in_array($studentId, $studentIds)) { continue; @@ -177,6 +184,8 @@ class Course_Gradebook_LecturersController extends AuthenticatedController $instance = new Instance([$definitionId, $studentId]); $instance->rawgrade = ((int) $strGrade) / 100.0; + $instance->passed = $passed[$studentId][$definitionId] ?? 0; + $instance->feedback = $feedback[$studentId][$definitionId] ?? ''; $instance->store(); } } @@ -195,6 +204,9 @@ class Course_Gradebook_LecturersController extends AuthenticatedController $gradingDefinitions = Definition::findByCourse($course); $this->groupedDefinitions = $this->getGroupedDefinitions($gradingDefinitions); $this->customDefinitions = $this->groupedDefinitions[Definition::CUSTOM_DEFINITIONS_CATEGORY] ?? []; + if (!count($this->customDefinitions )) { + PageLayout::postInfo(_('Es sind keine manuellen Leistungen definiert.')); + } } /** @@ -294,6 +306,131 @@ class Course_Gradebook_LecturersController extends AuthenticatedController $this->redirect('course/gradebook/lecturers/edit_custom_definitions'); } + public function edit_ilias_definitions_action() + { + if (Navigation::hasItem('/course/gradebook/edit_ilias_definitions')) { + Navigation::activateItem('/course/gradebook/edit_ilias_definitions'); + } + + $course = \Context::get(); + $gradingDefinitions = Definition::findByCourse($course); + $this->groupedDefinitions = $this->getGroupedDefinitions($gradingDefinitions); + $this->customDefinitions = $this->groupedDefinitions['ILIAS'] ?? []; + $this->setupIliasSidebar(count($this->customDefinitions)); + if (!count($this->customDefinitions )) { + PageLayout::postInfo(_('Es sind keine ILIAS-Tests als Leistungen definiert.')); + } + } + + public function new_ilias_definition_action() + { + $this->ilias_modules = []; + $course = Course::findCurrent(); + $already_defined = new SimpleCollection(Definition::findBySQL("course_id = ? AND category='ILIAS'", [$course->id])); + foreach (Config::get()->ILIAS_INTERFACE_SETTINGS as $ilias_index => $ilias_config) { + if ($ilias_config['is_active']) { + $ilias = new ConnectedIlias($ilias_index); + $this->ilias_modules[$ilias_index] = array_filter( + DBManager::get()->fetchFirst( + "SELECT module_id FROM object_contentmodules WHERE object_id=? AND system_type=? AND module_type='tst'", [$course->id, $ilias_index], + function ($module_id) use ($ilias, $already_defined) { + $item = $ilias->index . '-' . $module_id; + if (!$already_defined->findOneBy('item', $item)) { + return $ilias->getModule($module_id); + } + return null; + } + ) + ); + } + } + } + + public function delete_ilias_definition_action($definitionId) + { + CSRFProtection::verifyUnsafeRequest(); + if (!$definition = Definition::findOneBySQL( + 'id = ? AND course_id = ?', + [$definitionId, \Context::getId()] + ) + ) { + \PageLayout::postError(_('Die Leistung konnte nicht gelöscht werden.')); + } else { + if (Definition::deleteBySQL('id = ?', [$definition->id])) { + \PageLayout::postSuccess(_('Die Leistung wurde gelöscht.')); + } else { + \PageLayout::postError(_('Die Leistung konnte nicht gelöscht werden.')); + } + } + + $this->redirect('course/gradebook/lecturers/edit_ilias_definitions'); + } + + public function create_ilias_definition_action() + { + CSRFProtection::verifyUnsafeRequest(); + $ilias_module = Request::get('ilias_module'); + $module_import = Request::int('result') + Request::int('passed'); + if (!$module_import) { + $module_import = 3; + } + if ($ilias_module) { + [$index, $module_id] = explode('-', $ilias_module); + $ilias = new ConnectedIlias($index); + $module = $ilias->getModule($module_id); + if ($module) { + $definition = Definition::create( + [ + 'course_id' => \Context::getId(), + 'item' => $ilias_module . '-' . $module_import, + 'name' => $module->getTitle(), + 'tool' => 'ILIAS', + 'category' => 'ILIAS', + 'position' => 0, + 'weight' => 0.0, + ] + ); + + if (!$definition) { + \PageLayout::postError(_('Die Leistung konnte nicht definiert werden.')); + } else { + \PageLayout::postSuccess(_('Die Leistung wurde erfolgreich definiert.')); + } + } + } + $this->redirect('course/gradebook/lecturers/edit_ilias_definitions'); + } + + public function edit_ilias_definition_action($definition_id) + { + $this->definition = Definition::find($definition_id); + if ($this->definition && Request::submitted('test_name')) { + CSRFProtection::verifyUnsafeRequest(); + $module_import = Request::int('result') + Request::int('passed'); + [$index, $module_id] = explode('-', $this->definition->item ); + $this->definition->name = Request::get('test_name'); + $this->definition->item = $index . '-' . $module_id . '-' . $module_import; + if ($this->definition->store()) { + \PageLayout::postSuccess(_('Die Leistung wurde erfolgreich aktualisiert.')); + } + + $this->redirect('course/gradebook/lecturers/edit_ilias_definitions'); + } + } + + public function import_ilias_results_action() + { + $num = IliasObjectConnections::importIliasResultsForCourse(Course::findCurrent()); + PageLayout::postInfo(sprintf( + ngettext( + '%s Resultat wurde importiert.', + '%s Resultate wurden importiert.', + $num), + $num) + ); + $this->redirect('course/gradebook/lecturers/edit_ilias_definitions'); + } + public function getInstanceForUser(Definition $definition, \CourseMember $user) { if (!isset($this->groupedInstances[$user->user_id])) { @@ -342,4 +479,31 @@ class Course_Gradebook_LecturersController extends AuthenticatedController return $totalSums; } + + private function getTotalPassed($gradingDefinitions) + { + $gradingDefinitions = \SimpleCollection::createFromArray($gradingDefinitions); + $totalPassed = []; + foreach ($this->students as $student) { + if (!isset($totalPassed[$student->user_id])) { + $totalPassed[$student->user_id] = 0; + } + + if (!isset($this->groupedInstances[$student->user_id])) { + continue; + } + + foreach ($this->groupedInstances[$student->user_id] as $definitionId => $instance) { + if ($gradingDefinitions->findOneBy('id', $definitionId)) { + $totalPassed[$student->user_id] += $instance->passed; + } + } + } + $count = $gradingDefinitions->count(); + $totalPassed = array_map( + function($p) use ($count) { + return $p == $count ? $p : 0; + }, $totalPassed); + return $totalPassed; + } } diff --git a/app/controllers/course/gradebook/students.php b/app/controllers/course/gradebook/students.php index 81a1f7fc9e0..b00c22d3860 100644 --- a/app/controllers/course/gradebook/students.php +++ b/app/controllers/course/gradebook/students.php @@ -45,6 +45,8 @@ class Course_Gradebook_StudentsController extends AuthenticatedController $this->sumOfWeights = $this->getSumOfWeights($this->gradingDefinitions); $this->subtotals = $this->getSubtotalGrades(); $this->total = $this->getTotalGrade(); + $this->subpassed = $this->getSubpassed(); + $this->passed = array_sum($this->subpassed); } /** @@ -75,6 +77,7 @@ class Course_Gradebook_StudentsController extends AuthenticatedController $definition->name, $definition->tool, $instance ? $instance->rawgrade : 0, + $instance ? $instance->passed : 0, $instance ? $instance->feedback : null, ]; } @@ -85,6 +88,7 @@ class Course_Gradebook_StudentsController extends AuthenticatedController _('Leistung'), _('Werkzeug'), _('Fortschritt'), + _('Bestanden'), _('Feedback'), ]; $data = array_merge([$headerLine], $lines); @@ -139,4 +143,23 @@ class Course_Gradebook_StudentsController extends AuthenticatedController return $sumOfWeights ? $sumOfWeightedGrades / $sumOfWeights : 0; } + + private function getSubpassed() + { + $subpassed = []; + + foreach ($this->groupedDefinitions as $category => $definitions) { + $passed = 0; + + foreach ($definitions as $definition) { + if (isset($this->groupedInstances[$definition->id])) { + $instance = $this->groupedInstances[$definition->id]; + $passed += $instance->passed; + } + } + $subpassed[$category] = $passed == count($definitions) ? $passed : 0; + } + + return $subpassed; + } } diff --git a/app/controllers/course/gradebook/template_helpers.php b/app/controllers/course/gradebook/template_helpers.php index b77822e9635..3cbd8b42a63 100644 --- a/app/controllers/course/gradebook/template_helpers.php +++ b/app/controllers/course/gradebook/template_helpers.php @@ -87,4 +87,23 @@ trait GradebookTemplateHelpers \PageLayout::setTitle(Context::getHeaderLine().' - Gradebook'); \PageLayout::setHelpKeyword("Basis.Gradebook"); } + + protected function setupIliasSidebar($num_definitions = 0) + { + $ilias = new \LinksWidget(); + $ilias->setTitle(_('ILIAS')); + $ilias->addLink( + _('Test als Leistung hinzufügen'), + $this->url_for('course/gradebook/lecturers/new_ilias_definition'), + Icon::create('learnmodule+add') + )->asDialog(); + if ($num_definitions) { + $ilias->addLink( + _('Ergebnisse aus ILIAS importieren'), + $this->url_for('course/gradebook/lecturers/import_ilias_results'), + Icon::create('refresh') + ); + } + \Sidebar::Get()->addWidget($ilias); + } } diff --git a/app/views/course/gradebook/lecturers/custom_definitions.php b/app/views/course/gradebook/lecturers/custom_definitions.php index 864aa0d3c7f..6b906981456 100644 --- a/app/views/course/gradebook/lecturers/custom_definitions.php +++ b/app/views/course/gradebook/lecturers/custom_definitions.php @@ -1,14 +1,14 @@ <form class="default" action="<?= $controller->link_for('course/gradebook/lecturers/store_grades') ?>" method="POST"> <?= CSRFProtection::tokenTag()?> <div style="overflow-x:auto;"> - <table class="default gradebook-lecturer-custom-definitions"> + <table class="default gradebook-lecturer-custom-definitions sortable-table" data-sortlist="[[0, 0]]"> <caption> <?= _('Noten manuell erfassen') ?> </caption> <thead> - <tr class="tablesorter-ignoreRow"> - <th><?= _('Name') ?></th> + <tr class="sortable"> + <th data-sort="text"><?= _('Name') ?></th> <? if (count($customDefinitions)) { ?> <? foreach ($customDefinitions as $definition) { ?> <th> @@ -35,13 +35,26 @@ <td class="gradebook-grade-input"> <? $instance = $controller->getInstanceForUser($definition, $student) ?> <? $rawgrade = $instance ? $instance->rawgrade : 0 ?> + <? $passed = $instance ? $instance->passed : 0 ?> + <? $feedback = $instance ? $instance->feedback : '' ?> <label class="undecorated"> <input type="number" name="grades[<?= htmlReady($student->user_id) ?>][<?= htmlReady($definition->id) ?>]" value="<?= $controller->formatAsPercent($rawgrade) ?>" min="0"> % </label> - + <label> + <?=_('Bestanden')?> + <input type="checkbox" + name="passed[<?= htmlReady($student->user_id) ?>][<?= htmlReady($definition->id) ?>]" + value="1" + <?= $passed ? 'checked' : ''?>> + </label> + <label> + <input type="text" + name="feedback[<?= htmlReady($student->user_id) ?>][<?= htmlReady($definition->id) ?>]" + value="<?=htmlReady($feedback)?>" placeholder="<?=_('Feedback')?>"> + </label> </td> <? } ?> <? } elseif ($index === 0) { ?> diff --git a/app/views/course/gradebook/lecturers/edit_custom_definitions.php b/app/views/course/gradebook/lecturers/edit_custom_definitions.php index dbb3454344f..19012045ada 100644 --- a/app/views/course/gradebook/lecturers/edit_custom_definitions.php +++ b/app/views/course/gradebook/lecturers/edit_custom_definitions.php @@ -42,13 +42,6 @@ </tr> <? } ?> </tbody> - <? } else { ?> - <tbody> - <tr> - <td colspan="2"> - <?= \MessageBox::info(_('Es sind keine manuellen Leistungen definiert.')) ?> - </td> - </tbody> <? } ?> diff --git a/app/views/course/gradebook/lecturers/edit_ilias_definition.php b/app/views/course/gradebook/lecturers/edit_ilias_definition.php new file mode 100644 index 00000000000..36389071fc6 --- /dev/null +++ b/app/views/course/gradebook/lecturers/edit_ilias_definition.php @@ -0,0 +1,26 @@ +<?php +/** @var StudipController $controller */ +/** @var \Grading\Definition $definition */ +?> +<form class="default" action="<?=$controller->link_for('course/gradebook/lecturers/edit_ilias_definition/' . $definition->id) ?>" method="POST"> + <?= CSRFProtection::tokenTag()?> + <fieldset> + <label> + <?= _('Name der Leistung') ?> + <input type="text" value="<?=htmlReady($definition->name)?>" name="test_name"> + </label> + <label> + <?=_('Prozentwert übertragen')?> + <input type="checkbox" value="1" <?=substr($definition->item, -1) & 1 ? 'checked' : ''?> name="result"> + </label> + <label> + <?=_('Bestanden/nicht bestanden übertragen')?> + <input type="checkbox" value="2" <?=substr($definition->item, -1) & 2 ? 'checked' : ''?> name="passed"> + </label> + </fieldset> + + <footer data-dialog-button> + <?= \Studip\Button::createAccept(_('Speichern')) ?> + <?= \Studip\LinkButton::createCancel(_('Abbrechen'), $controller->url_for('course/gradebook/lecturers/edit_ilias_definitions')) ?> + </footer> +</form> diff --git a/app/views/course/gradebook/lecturers/edit_ilias_definitions.php b/app/views/course/gradebook/lecturers/edit_ilias_definitions.php new file mode 100644 index 00000000000..1fd7a17cf29 --- /dev/null +++ b/app/views/course/gradebook/lecturers/edit_ilias_definitions.php @@ -0,0 +1,75 @@ +<?php +/** @var Grading\Definition[] $customDefinitions */ +/** @var StudipController $controller */ +?> +<table class="default"> + <caption> + <?= _('ILIAS Leistungen definieren') ?> + </caption> + + <thead> + <tr class="tablesorter-ignoreRow"> + <th><?= _('Name') ?></th> + <th><?= _('ID') ?></th> + <th><?= _('Prozentwert') ?></th> + <th><?= _('Bestanden') ?></th> + <th class="actions"><?= _('Aktionen') ?></th> + </tr> + </thead> + + <? if (count($customDefinitions)) { ?> + <tbody> + <? foreach ($customDefinitions as $definition) { ?> + <tr> + <td> + <?= htmlReady($definition->name) ?> + </td> + <td> + <?= htmlReady($definition->item) ?> + </td> + <td> + <?= substr($definition->item, -1) & 1 ? 'x' : '' ?> + </td> + <td> + <?= substr($definition->item, -1) & 2 ? 'x' : '' ?> + <td class="actions"> + <?= + \ActionMenu::get() + ->addLink( + $controller->url_for( + 'course/gradebook/lecturers/edit_ilias_definition', + $definition->id + ), + _('Ändern'), + Icon::create('edit'), + ['data-dialog' => 'size=fit'] + ) + ->addLink( + $controller->url_for( + 'course/gradebook/lecturers/delete_ilias_definition', + $definition->id + ), + _('Löschen'), + Icon::create('trash'), + ['onclick' => "return STUDIP.Dialog.confirmAsPost('" . _('Wollen Sie die Leistungsdefinition wirklich löschen?') . "', this.href);"] + ) ?> + </td> + </tr> + <? } ?> + </tbody> + <? } ?> + + + <tfoot class="gradebook-lecturer-custom-definitions-actions"> + <tr> + <td colspan="5"> + <?= \Studip\LinkButton::createAdd( + count($customDefinitions) ? _('Weiteren Test als Leistung definieren') : _('Test als Leistung definieren'), + $controller->url_for('course/gradebook/lecturers/new_ilias_definition'), + ['data-dialog' => 'size=fit'] + ) ?> + </td> + </tr> + </tfoot> +</table> +<?php diff --git a/app/views/course/gradebook/lecturers/index.php b/app/views/course/gradebook/lecturers/index.php index 62ebfccd387..7e5523c0daf 100644 --- a/app/views/course/gradebook/lecturers/index.php +++ b/app/views/course/gradebook/lecturers/index.php @@ -45,17 +45,21 @@ <?= $studentName ?> </a> </td> - <? $totalSum = isset($totalSums[$student->user_id]) ? $totalSums[$student->user_id] : 0 ?> - <td data-sort-value="<?= $totalSum?>"> - <?= $controller->formatAsPercent($totalSum) ?> % + <? $totalSum = $totalSums[$student->user_id] ?? 0 ?> + <? $totalPassed = $totalPassed[$student->user_id] ?? 0 ?> + <td data-sort-value="<?= $totalSum + $totalPassed?>"> + <?= $totalSum > 0 ? $controller->formatAsPercent($totalSum) . ' %' : ''?> + <?= $totalPassed ? _('bestanden') : ''?> </td> <? foreach ($categories as $category) { ?> <? foreach ($groupedDefinitions[$category] as $definition) { ?> <? $instance = $controller->getInstanceForUser($definition, $student) ?> <? $rawgrade = $instance ? $instance->rawgrade : 0 ?> - <td data-sort-value="<? $rawgrade ?>"> - <?= $controller->formatAsPercent($rawgrade) ?> % + <? $passed = $instance ? $instance->passed : 0 ?> + <td data-sort-value="<?= $rawgrade + $passed ?>"> + <?= $rawgrade > 0 ? $controller->formatAsPercent($rawgrade) . ' %' : ''?> + <?= $passed ? _('bestanden') : ''?> </td> <? } ?> <? } ?> diff --git a/app/views/course/gradebook/lecturers/new_ilias_definition.php b/app/views/course/gradebook/lecturers/new_ilias_definition.php new file mode 100644 index 00000000000..7a65c79fae0 --- /dev/null +++ b/app/views/course/gradebook/lecturers/new_ilias_definition.php @@ -0,0 +1,32 @@ +<?php +/** @var StudipController $controller */ +/** @var array $ilias_modules */ +?> +<form class="default" action="<?=$controller->link_for('course/gradebook/lecturers/create_ilias_definition') ?>" method="POST"> + <?= CSRFProtection::tokenTag()?> + <fieldset> + <label> + <?= _('Bitte wählen Sie einen Test aus') ?> + <select name="ilias_module"> + <? foreach ($ilias_modules as $key => $modules) : ?> + <? foreach ($modules as $module) : ?> + <option value="<?=$key . '-' . $module->getId()?>"><?=htmlReady($module->getTitle())?></option> + <? endforeach;?> + <? endforeach;?> + </select> + </label> + <label> + <?=_('Prozentwert übertragen')?> + <input type="checkbox" value="1" checked name="result"> + </label> + <label> + <?=_('Bestanden/nicht bestanden übertragen')?> + <input type="checkbox" value="2" checked name="passed"> + </label> + </fieldset> + + <footer data-dialog-button> + <?= \Studip\Button::createAccept(_('Speichern')) ?> + <?= \Studip\LinkButton::createCancel(_('Abbrechen'), $controller->url_for('course/gradebook/lecturers/edit_ilias_definitions')) ?> + </footer> +</form> diff --git a/app/views/course/gradebook/students/index.php b/app/views/course/gradebook/students/index.php index fb39eed1c6c..572067aceb8 100644 --- a/app/views/course/gradebook/students/index.php +++ b/app/views/course/gradebook/students/index.php @@ -10,16 +10,25 @@ <article class="gradebook-student"> <header> <h1><?= _("Gesamt") ?></h1> - <?= $this->render_partial("course/gradebook/_progress", ['value' => $controller->formatAsPercent($total)])?> + <? if ($total > 0) : ?> + <?= $this->render_partial("course/gradebook/_progress", ['value' => $controller->formatAsPercent($total)])?> + <? endif ?> + <? if ($passed) : ?> + <div><?= _('bestanden')?></div> + <? endif ?> </header> <? foreach ($categories as $category) { ?> <section class="gradebook-student-category"> <header> <h2><?= $controller->formatCategory($category) ?></h2> - <?= $this->render_partial("course/gradebook/_progress", ['value' => $controller->formatAsPercent($subtotals[$category])])?> </header> - + <? if ($subtotals[$category] > 0) : ?> + <?= $this->render_partial("course/gradebook/_progress", ['value' => $controller->formatAsPercent($subtotals[$category])])?> + <? endif ?> + <? if ($subpassed[$category]) : ?> + <div><?= _('bestanden')?></div> + <? endif ?> <table class="default"> <colgroup> <col width="200px" /> @@ -43,11 +52,15 @@ $instance = $groupedInstances[$definition->id] ?? null; $grade = $controller->formatAsPercent($instance ? $instance->rawgrade : 0); $feedback = $instance ? $instance->feedback : ''; + $passed = $instance ? $instance->passed : 0; ?> <tr> <td> <span class="gradebook-definition-name"><?= htmlReady($definition->name) ?></span> - <?= $this->render_partial("course/gradebook/_progress", ['value' => (int) $grade])?> + <? if ($grade > 0) : ?> + <?= $this->render_partial("course/gradebook/_progress", ['value' => (int) $grade])?> + <? endif ?> + <div><?= $passed ? _('bestanden') : '' ?></div> </td> <td> <?= htmlReady($definition->tool) ?> diff --git a/db/migrations/5.5.15_step3344_ilias_results.php b/db/migrations/5.5.15_step3344_ilias_results.php new file mode 100644 index 00000000000..20bb33ad71f --- /dev/null +++ b/db/migrations/5.5.15_step3344_ilias_results.php @@ -0,0 +1,32 @@ +<?php +require_once 'lib/cronjobs/import_ilias_testresults.php'; +final class Step3344IliasResults extends Migration +{ + + use DatabaseMigrationTrait; + + public function description() + { + return 'adds column passed to table `grading_instances`, add cronjob'; + } + + protected function up() + { + if (!$this->columnExists('grading_instances', 'passed')) { + DBManager::get()->exec("ALTER TABLE `grading_instances` ADD + `passed` TINYINT NOT NULL DEFAULT 0 AFTER `feedback`"); + + } + ImportIliasTestresults::register()->schedulePeriodic(45, 1); + } + + protected function down() + { + if ($this->columnExists('grading_instances', 'passed')) { + DBManager::get()->exec("ALTER TABLE `grading_instances` DROP + `passed`"); + } + ImportIliasTestresults::unregister(); + } + +} diff --git a/lib/cronjobs/import_ilias_testresults.php b/lib/cronjobs/import_ilias_testresults.php new file mode 100644 index 00000000000..c8c12250fc5 --- /dev/null +++ b/lib/cronjobs/import_ilias_testresults.php @@ -0,0 +1,51 @@ +<?php +/** + * ImportIliasTestresults + * + * @author André Noack <noack@data-quest.de>, Suchi & Berg GmbH <info@data-quest.de> + */ + +class ImportIliasTestresults extends CronJob +{ + public static function getName() + { + return _('Testergebnisse aus ILIAS importieren'); + } + + public static function getDescription() + { + return _('Importiert Testergebnisse in das Gradebook'); + } + + public static function getParameters() + { + return [ + 'verbose' => [ + 'type' => 'boolean', + 'default' => false, + 'status' => 'optional', + 'description' => _('Sollen Ausgaben erzeugt werden (sind später im Log des Cronjobs sichtbar)'), + ] + ]; + } + + public function execute($last_result, $parameters = []) + { + $verbose = $parameters['verbose']; + $db = DBManager::get(); + if (Config::get()->ILIAS_INTERFACE_ENABLE) { + $courses = $db->fetchFirst("SELECT DISTINCT course_id FROM grading_definitions WHERE tool='ILIAS'"); + foreach ($courses as $course_id) { + $course = Course::find($course_id); + if ($course && $course->isToolActive('IliasInterfaceModule')) { + $num = IliasObjectConnections::importIliasResultsForCourse($course); + if ($verbose) { + echo 'Veranstaltung: ' . $course->name . ' '. $course->id . ': ' . $num . ' Ergebnisse übertragen.' . "\n"; + } + } + } + } else { + echo 'ILIAS_INTERFACE is not enabled'; + } + } +} diff --git a/lib/ilias_interface/ConnectedIlias.class.php b/lib/ilias_interface/ConnectedIlias.class.php index dff41c892f3..6b827839a68 100644 --- a/lib/ilias_interface/ConnectedIlias.class.php +++ b/lib/ilias_interface/ConnectedIlias.class.php @@ -1346,4 +1346,19 @@ class ConnectedIlias public function deleteConnectedModules($object_id){ return IliasObjectConnections::DeleteAllConnections($object_id, $this->index); } + + /** + * @param string $ilias_user_id + * @return IliasUser|void + */ + public function getConnectedUser(string $ilias_user_id) + { + $user_id = DBManager::get()->fetchColumn( + "SELECT studip_user_id FROM auth_extern WHERE external_user_id = ? AND external_user_system_type = ?", + [$ilias_user_id, $this->index] + ); + if ($user_id) { + return new IliasUser($this->index, $this->ilias_config['version'], $user_id); + } + } } diff --git a/lib/ilias_interface/IliasObjectConnections.class.php b/lib/ilias_interface/IliasObjectConnections.class.php index 87468e99615..0c6a8a9721f 100644 --- a/lib/ilias_interface/IliasObjectConnections.class.php +++ b/lib/ilias_interface/IliasObjectConnections.class.php @@ -135,7 +135,7 @@ class IliasObjectConnections return false; } } - + /** * get module-id * @@ -273,4 +273,39 @@ class IliasObjectConnections $statement->execute([$object_id, $cms_type]); return $statement->rowCount(); } + + /** + * @param Course $course + * @return int + */ + public static function importIliasResultsForCourse(Course $course): int + { + $connected_ilias = []; + $students = new SimpleCollection($course->getMembersWithStatus('autor')); + $num = 0; + foreach (Grading\Definition::findBySQL("course_id = ? AND tool='ILIAS'", [$course->id]) as $definition) { + [$index, $module_id, $import_type] = explode('-', $definition->item); + if (!isset($connected_ilias[$index])) { + $connected_ilias[$index] = new ConnectedIlias($index); + } + $test_result = $connected_ilias[$index]->soap_client->getTestResults($module_id); + foreach ($test_result as $result) { + $ilias_user = $connected_ilias[$index]->getConnectedUser($result['user_id']); + if ($ilias_user) { + $member = $students->findOneBy('user_id', $ilias_user->getStudipId()); + if ($member) { + $grade = Grading\Instance::import([ + 'definition_id' => $definition->id, + 'user_id' => $member->user_id, + 'rawgrade' => $import_type & 1 && $result['maximum_points'] ? $result['received_points'] / $result['maximum_points'] : 0, + 'passed' => $import_type & 2 ? $result['passed'] : 0 + ] + ); + $num += $grade->store(); + } + } + } + } + return $num; + } } diff --git a/lib/ilias_interface/IliasSoap.class.php b/lib/ilias_interface/IliasSoap.class.php index d5647f4d73e..37d7c621aa1 100644 --- a/lib/ilias_interface/IliasSoap.class.php +++ b/lib/ilias_interface/IliasSoap.class.php @@ -1672,4 +1672,41 @@ class IliasSoap extends StudipSoapClient } return false; } + + /** + * @param string $ref_id + * @param bool $sum_only + * @return array|false + * @throws Exception + */ + public function getTestResults($ref_id, $sum_only = true) + { + $param = [ + 'sid' => $this->getSID(), + 'ref_id' => $ref_id, + 'sum_only' => $sum_only + ]; + $result = $this->call('getTestResults', $param); + if ($result !== false) { + $columns = []; + $data = []; + $xml = simplexml_load_string($result); + foreach ($xml->colspecs->colspec as $colspec) { + $columns[] = (string)$colspec['name']; + } + foreach ($xml->rows->row as $row) { + $data_row = []; + $i = 0; + foreach ($row->column as $column) { + $data_row[$columns[$i++]] = (string)$column; + } + if (isset($data_row['user_id'])) { + + } + $data[] = $data_row; + } + return $data; + } + return false; + } } diff --git a/lib/modules/GradebookModule.class.php b/lib/modules/GradebookModule.class.php index 6c000a74971..0423622a99a 100644 --- a/lib/modules/GradebookModule.class.php +++ b/lib/modules/GradebookModule.class.php @@ -112,6 +112,12 @@ class GradebookModule extends CorePlugin implements SystemPlugin, StudipModule 'custom_definitions', new Navigation(_('Noten manuell erfassen'), 'dispatch.php/course/gradebook/lecturers/custom_definitions') ); + if (Config::get()->ILIAS_INTERFACE_ENABLE && $cid && Course::findCurrent()->isToolActive('IliasInterfaceModule')) { + $navigation->addSubNavigation( + 'edit_ilias_definitions', + new Navigation(_('ILIAS-Test als Leistung definieren'), 'dispatch.php/course/gradebook/lecturers/edit_ilias_definitions') + ); + } } /** -- GitLab