ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
class.TestScreenGUI.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
22
26use ILIAS\Data\Factory as DataFactory;
31use ILIAS\UI\Component\Launcher\Factory as LauncherFactory;
33use ILIAS\UI\Factory as UIFactory;
34use ILIAS\UI\Renderer as UIRenderer;
35use ILIAS\HTTP\Services as HTTPServices;
38use ILIAS\Style\Content\Service as ContentStyle;
39
46{
47 public const DEFAULT_CMD = 'testScreen';
48
49 private readonly \ilTestPassesSelector $test_passes_selector;
50 private readonly int $ref_id;
51 private readonly MainSettings $main_settings;
52 private readonly \ilTestSession $test_session;
53 private readonly DataFactory $data_factory;
54 private \ilTestPasswordChecker $password_checker;
55
56 public function __construct(
57 private readonly \ilObjTest $object,
58 private readonly \ilObjUser $user,
59 private readonly UIFactory $ui_factory,
60 private readonly UIRenderer $ui_renderer,
61 private readonly \ilLanguage $lng,
62 private readonly Refinery $refinery,
63 private readonly \ilCtrlInterface $ctrl,
64 private readonly \ilGlobalTemplateInterface $tpl,
65 private readonly ContentStyle $content_style,
66 private readonly HTTPServices $http,
67 private readonly TabsManager $tabs_manager,
68 private readonly \ilAccessHandler $access,
69 private readonly \ilTestAccess $test_access,
70 private readonly \ilDBInterface $database,
71 private readonly \ilRbacSystem $rbac_system
72 ) {
73 $this->ref_id = $this->object->getRefId();
74 $this->main_settings = $this->object->getMainSettings();
75 $this->data_factory = new DataFactory();
76
77 $this->test_session = (new \ilTestSessionFactory($this->object, $this->database, $this->user))->getSession();
78
79 $this->test_passes_selector = new \ilTestPassesSelector($this->database, $this->object);
80 $this->test_passes_selector->setActiveId($this->test_session->getActiveId());
81 $this->test_passes_selector->setLastFinishedPass($this->test_session->getLastFinishedPass());
82 $this->password_checker = new \ilTestPasswordChecker($this->rbac_system, $this->user, $this->object, $this->lng);
83 }
84
85 public function executeCommand(): void
86 {
87 if ($this->access->checkAccess('read', '', $this->ref_id)) {
88 $this->{$this->ctrl->getCmd(self::DEFAULT_CMD)}();
89 return;
90 }
91
92 $this->tabs_manager->activateTab(TabsManager::TAB_ID_TEST);
93
94 if (!$this->object->getMainSettings()->getAdditionalSettings()->getHideInfoTab()) {
95 $this->ctrl->redirectByClass([\ilRepositoryGUI::class, \ilObjTestGUI::class, \ilInfoScreenGUI::class]);
96 }
97
98 $this->tpl->setOnScreenMessage('failure', sprintf(
99 $this->lng->txt('msg_no_perm_read_item'),
100 $this->object->getTitle()
101 ), true);
102 $this->ctrl->setParameterByClass(\ilRepositoryGUI::class, 'ref_id', ROOT_FOLDER_ID);
103 $this->ctrl->redirectByClass(\ilRepositoryGUI::class);
104 }
105
106 public function testScreen(): void
107 {
108 $this->tabs_manager->activateTab(TabsManager::TAB_ID_TEST);
109 $this->tpl->setPermanentLink($this->object->getType(), $this->ref_id);
110
111 $elements = [];
112
113 if ($this->areSkillLevelThresholdsMissing()) {
114 $elements = [$this->getSkillLevelThresholdsMissingInfo()];
115 }
116 $elements = $this->handleRenderMessageBox($elements);
117 $elements = $this->handleRenderIntroduction($elements);
118
119 $this->tpl->setContent(
120 $this->ui_renderer->render(
121 $this->testCanBeStarted() ? $this->handleRenderLauncher($elements) : $elements
122 )
123 );
124 }
125
126 private function handleRenderMessageBox(array $elements): array
127 {
128 $message_box_message = '';
129 $message_box_message_elements = [];
130
131 $exam_conditions_enabled = $this->main_settings->getIntroductionSettings()->getExamConditionsCheckboxEnabled();
132 $password_enabled = $this->main_settings->getAccessSettings()->getPasswordEnabled();
133 $test_behaviour_settings = $this->main_settings->getTestBehaviourSettings();
134
135 if ($exam_conditions_enabled && $password_enabled) {
136 $message_box_message_elements[] = $this->lng->txt('tst_launcher_status_message_conditions_and_password');
137 } elseif ($exam_conditions_enabled) {
138 $message_box_message_elements[] = $this->lng->txt('tst_launcher_status_message_conditions');
139 } elseif ($password_enabled) {
140 $message_box_message_elements[] = $this->lng->txt('tst_launcher_status_message_password');
141 }
142
143 if ($test_behaviour_settings->getProcessingTimeEnabled() && !$this->isUserOutOfProcessingTime()) {
144 $message_box_message_elements[] = sprintf(
145 $this->lng->txt('tst_time_limit_message'),
146 $test_behaviour_settings->getProcessingTimeAsMinutes()
147 );
148 }
149
150 $nr_of_tries = $this->object->getNrOfTries();
151
152 if ($nr_of_tries !== 0) {
153 $message_box_message_elements[] = sprintf($this->lng->txt('tst_attempt_limit_message'), $nr_of_tries);
154 }
155
156 if ($this->object->isStartingTimeEnabled() && !$this->object->startingTimeReached()) {
157 $message_box_message_elements[] = sprintf(
158 $this->lng->txt('detail_starting_time_not_reached'),
159 \ilDatePresentation::formatDate(new \ilDateTime($this->object->getStartingTime(), IL_CAL_UNIX))
160 );
161 }
162
163 if ($this->object->isEndingTimeEnabled() && !$this->object->endingTimeReached()) {
164 $message_box_message_elements[] = sprintf(
165 $this->lng->txt('tst_exam_ending_time_message'),
166 \ilDatePresentation::formatDate(new \ilDateTime($this->object->getEndingTime(), IL_CAL_UNIX))
167 );
168 }
169
170 foreach ($message_box_message_elements as $message_box_message_element) {
171 $message_box_message .= ' ' . $message_box_message_element;
172 }
173
174 if (!empty($message_box_message)) {
175 $elements[] = $this->ui_factory->messageBox()->info($message_box_message);
176 }
177
178 return $elements;
179 }
180
181 private function handleRenderIntroduction(array $elements): array
182 {
183 $introduction = $this->object->getIntroduction();
184
185 if (
186 $this->main_settings->getIntroductionSettings()->getIntroductionEnabled() &&
187 !empty($introduction)
188 ) {
189 $this->content_style->gui()->addCss($this->tpl, $this->ref_id);
190 $elements[] = $this->ui_factory->panel()->standard(
191 $this->lng->txt('tst_introduction'),
192 $this->ui_factory->legacy()->content($introduction),
193 );
194 }
195
196 return $elements;
197 }
198
199 private function handleRenderLauncher(array $elements): array
200 {
201 $elements[] = $this->getLauncher();
202 return $elements;
203 }
204
205 private function getLauncher(): Launcher
206 {
207 $launcher_factory = $this->ui_factory->launcher();
208
209 if ($this->object->isStartingTimeEnabled() && !$this->object->startingTimeReached()) {
210 return $launcher_factory
211 ->inline($this->data_factory->link('', $this->data_factory->uri($this->http->request()->getUri()->__toString())))
212 ->withButtonLabel(sprintf(
213 $this->lng->txt('detail_starting_time_not_reached'),
214 \ilDatePresentation::formatDate(new \ilDateTime($this->object->getStartingTime(), IL_CAL_UNIX))
215 ), false)
216 ;
217 }
218
219 if ($this->object->isEndingTimeEnabled() && $this->object->endingTimeReached()) {
220 return $launcher_factory
221 ->inline($this->data_factory->link('', $this->data_factory->uri($this->http->request()->getUri()->__toString())))
222 ->withButtonLabel(sprintf(
223 $this->lng->txt('detail_ending_time_reached'),
224 \ilDatePresentation::formatDate(new \ilDateTime($this->object->getEndingTime(), IL_CAL_UNIX))
225 ), false)
226 ;
227 }
228
229 if ($this->isUserOutOfProcessingTime()) {
230 return $launcher_factory
231 ->inline($this->data_factory->link('', $this->data_factory->uri($this->http->request()->getUri()->__toString())))
232 ->withButtonLabel($this->lng->txt('tst_out_of_time_message'), false)
233 ;
234 }
235
236 $participant_access = $this->test_access->isParticipantAllowed(
237 $this->object->getId(),
238 $this->user->getId()
239 );
240
241 if ($participant_access === ParticipantAccess::NOT_INVITED) {
242 return $launcher_factory
243 ->inline($this->data_factory->link('', $this->data_factory->uri($this->http->request()->getUri()->__toString())))
244 ->withButtonLabel($this->lng->txt('tst_exam_not_assigned_participant_disclaimer'), false)
245 ;
246 }
247
248 if ($participant_access !== ParticipantAccess::ALLOWED) {
249 return $launcher_factory
250 ->inline($this->data_factory->link('', $this->data_factory->uri($this->http->request()->getUri()->__toString())))
251 ->withButtonLabel($participant_access->getAccessForbiddenMessage($this->lng), false)
252 ;
253 }
254
255 if (!$this->hasAvailablePasses()) {
256 return $launcher_factory
257 ->inline($this->data_factory->link('', $this->data_factory->uri($this->http->request()->getUri()->__toString())))
258 ->withButtonLabel($this->lng->txt('tst_launcher_button_label_passes_limit_reached'), false);
259 }
260
261 if ($this->blockUserAfterHavingPassed()) {
262 return $launcher_factory
263 ->inline($this->data_factory->link('', $this->data_factory->uri($this->http->request()->getUri()->__toString())))
264 ->withButtonLabel($this->lng->txt('tst_already_passed_cannot_retake'), false)
265 ;
266 }
267
268 $next_pass_allowed_timestamp = 0;
269 if (!$this->object->isNextPassAllowed($this->test_passes_selector, $next_pass_allowed_timestamp)) {
270 return $launcher_factory
271 ->inline($this->data_factory->link('', $this->data_factory->uri($this->http->request()->getUri()->__toString())))
272 ->withButtonLabel(
273 sprintf(
274 $this->lng->txt('wait_for_next_pass_hint_msg'),
275 \ilDatePresentation::formatDate(new \ilDateTime($next_pass_allowed_timestamp, IL_CAL_UNIX)),
276 ),
277 false
278 )
279 ;
280 }
281
282 if ($this->lastPassSuspended()) {
283 return $launcher_factory->inline($this->getResumeLauncherLink());
284 }
285
286 if ($this->isModalLauncherNeeded()) {
287 return $this->buildModalLauncher();
288 }
289 return $launcher_factory->inline($this->getStartLauncherLink());
290 }
291
292 private function getResumeLauncherLink(): Link
293 {
294 $url = $this->ctrl->getLinkTarget(
295 (new \ilTestPlayerFactory($this->object))->getPlayerGUI(),
297 );
298 return $this->data_factory->link($this->lng->txt('tst_resume_test'), $this->data_factory->uri(ILIAS_HTTP_PATH . '/' . $url));
299 }
300
301 private function buildModalLauncher(): Launcher
302 {
303 $launcher = $this->ui_factory->launcher()->inline($this->getModalLauncherLink())
304 ->withInputs(
305 $this->ui_factory->input()->field()->group($this->getModalLauncherInputs()),
306 function (Result $result) {
307 $this->evaluateLauncherModalForm($result);
308 },
310 )->withModalSubmitLabel($this->lng->txt('continue'));
311
312 $request = $this->http->request();
313 $key = 'launcher_id';
314 if (array_key_exists($key, $request->getQueryParams())
315 && $request->getQueryParams()[$key] === 'exam_modal') {
316 $launcher = $launcher->withRequest($request);
317 }
318 return $launcher;
319 }
320
321 private function getModalLauncherLink(): Link
322 {
323 $uri = $this->data_factory->uri($this->http->request()->getUri()->__toString())->withParameter('launcher_id', 'exam_modal');
324 return $this->data_factory->link($this->lng->txt('tst_exam_start'), $uri);
325 }
326
327 private function getModalLauncherInputs(): array
328 {
329 if ($this->main_settings->getIntroductionSettings()->getExamConditionsCheckboxEnabled()) {
330 $modal_inputs['exam_conditions'] = $this->ui_factory->input()->field()->checkbox(
331 $this->lng->txt('tst_exam_conditions'),
332 $this->lng->txt('tst_exam_conditions_label')
333 )->withRequired(true);
334 }
335
336 if ($this->main_settings->getAccessSettings()->getPasswordEnabled()) {
337 $modal_inputs['exam_password'] = $this->ui_factory->input()->field()->password(
338 $this->lng->txt('tst_exam_password'),
339 $this->lng->txt('tst_exam_password_label')
340 )->withRevelation(true)
341 ->withRequired(true)
342 ->withAdditionalTransformation(
343 $this->refinery->custom()->transformation(
344 static function (Password $value): string {
345 return $value->toString();
346 }
347 )
348 );
349 }
350
351 if ($this->user->isAnonymous()) {
352 $access_code_input = $this->ui_factory->input()->field()->text(
353 $this->lng->txt('tst_exam_access_code'),
354 $this->lng->txt('tst_exam_access_code_label')
355 );
356
357 $access_code_from_session = $this->test_session->getAccessCodeFromSession();
358 if ($access_code_from_session) {
359 $access_code_input = $access_code_input->withValue($access_code_from_session);
360 }
361
362 $modal_inputs['exam_access_code'] = $access_code_input;
363 }
364
365 if ($this->main_settings->getParticipantFunctionalitySettings()->getUsePreviousAnswerAllowed()
366 && $this->test_passes_selector->getLastFinishedPass() >= 0) {
367 $modal_inputs['exam_use_previous_answers'] = $this->ui_factory->input()->field()->checkbox(
368 $this->lng->txt('tst_exam_use_previous_answers'),
369 $this->lng->txt('tst_exam_use_previous_answers_label')
370 );
371 }
372
373 return $modal_inputs ?? [];
374 }
375
377 {
378 $exam_conditions_enabled = $this->main_settings->getIntroductionSettings()->getExamConditionsCheckboxEnabled();
379 $password_enabled = $this->main_settings->getAccessSettings()->getPasswordEnabled();
380
381 if ($exam_conditions_enabled && $password_enabled) {
382 $modal_message_box_message = $this->lng->txt('tst_exam_modal_message_conditions_and_password');
383 } elseif ($exam_conditions_enabled) {
384 $modal_message_box_message = $this->lng->txt('tst_exam_modal_message_conditions');
385 } elseif ($password_enabled) {
386 $modal_message_box_message = $this->lng->txt('tst_exam_modal_message_password');
387 }
388
389 return isset($modal_message_box_message) ? $this->ui_factory->messageBox()->info($modal_message_box_message) : null;
390 }
391
392 private function getStartLauncherLink(): Link
393 {
394 $url = $this->ctrl->getLinkTarget(
395 (new \ilTestPlayerFactory($this->object))->getPlayerGUI(),
397 );
398 return $this->data_factory->link($this->lng->txt('tst_exam_start'), $this->data_factory->uri(ILIAS_HTTP_PATH . '/' . $url));
399 }
400
401 private function evaluateLauncherModalForm(Result $result): void
402 {
403 if ($result->isOK()) {
404 $conditions_met = true;
405 $message = '';
406 $access_settings_password = $this->main_settings->getAccessSettings()->getPassword();
407 $anonymous = $this->user->isAnonymous();
408 foreach ($result->value() as $key => $value) {
409
410 switch ($key) {
411 case 'exam_conditions':
412 $exam_conditions_value = (bool) $value;
413 if (!$exam_conditions_value) {
414 $conditions_met = false;
415 $message .= $this->lng->txt('tst_exam_conditions_not_checked_message') . '<br>';
416 }
417 break;
418 case 'exam_password':
419 $password = $value;
420 $exam_password_valid = ($password === $access_settings_password);
421 if (!$exam_password_valid) {
422 $conditions_met = false;
423 $message .= $this->lng->txt('tst_exam_password_invalid_message') . '<br>';
424 if ($this->object->getTestLogger()->isLoggingEnabled()
425 && !$this->object->getAnonymity()) {
426 $logger = $this->object->getTestLogger();
427 $logger->logParticipantInteraction(
428 $logger->getInteractionFactory()->buildParticipantInteraction(
429 $this->ref_id,
430 null,
431 $this->user->getId(),
432 $_SERVER['REMOTE_ADDR'],
433 TestParticipantInteractionTypes::WRONG_TEST_PASSWORD_PROVIDED,
434 []
435 )
436 );
437 }
438 }
439 $this->password_checker->setUserEnteredPassword($password);
440 break;
441 case 'exam_access_code':
442 if ($anonymous && !empty($value)) {
443 $this->test_session->setAccessCodeToSession($value);
444 } else {
445 $this->test_session->unsetAccessCodeInSession();
446 }
447 break;
448 case 'exam_use_previous_answers':
449 $exam_use_previous_answers_value = (string) (int) $value;
450 break;
451 }
452 }
453
454 if ($message !== '') {
455 $this->tpl->setOnScreenMessage(\ilGlobalTemplateInterface::MESSAGE_TYPE_FAILURE, $message, true);
456 }
457
458 if (empty($result->value())) {
459 $this->tpl->setOnScreenMessage(
461 $this->lng->txt('tst_exam_required_fields_not_filled_message'),
462 true
463 );
464 } elseif ($conditions_met) {
465 if (
466 !$anonymous &&
467 $this->main_settings->getParticipantFunctionalitySettings()->getUsePreviousAnswerAllowed()
468 ) {
469 $this->user->setPref('tst_use_previous_answers', $exam_use_previous_answers_value ?? '0');
470 $this->user->update();
471 }
472
473 if (isset($password) && $password === $access_settings_password) {
474 \ilSession::set('tst_password_' . $this->object->getTestId(), $password);
475 } else {
476 \ilSession::set('tst_password_' . $this->object->getTestId(), '');
477 $this->test_session->setPasswordChecked(false);
478 }
479
480 $this->ctrl->redirectByClass(
481 (new \ilTestPlayerFactory($this->object))->getPlayerGUI()::class,
483 );
484 }
485 } else {
486 $this->tpl->setOnScreenMessage(
488 $this->lng->txt('tst_exam_required_fields_not_filled_message'),
489 true
490 );
491 }
492 }
493
494 private function testCanBeStarted(): bool
495 {
496 if ($this->object->getOfflineStatus()
497 || !$this->object->isComplete($this->object->getQuestionSetConfig())) {
498 return false;
499 }
500
501 return true;
502 }
503
504 private function isUserOutOfProcessingTime(): bool
505 {
506 $test_behaviour_settings = $this->object->getMainSettings()->getTestBehaviourSettings();
507 if (!$test_behaviour_settings->getProcessingTimeEnabled()
508 || $test_behaviour_settings->getResetProcessingTime()) {
509 return false;
510 }
511
512 $active_id = $this->test_passes_selector->getActiveId();
513 $last_started_pass = $this->test_session->getLastStartedPass();
514 return $last_started_pass !== null
515 && $this->object->isMaxProcessingTimeReached(
516 $this->object->getStartingTimeOfUser($active_id, $last_started_pass),
517 $active_id
518 );
519 }
520
521 private function blockUserAfterHavingPassed(): bool
522 {
523 if ($this->main_settings->getTestBehaviourSettings()->getBlockAfterPassedEnabled()) {
524 return $this->test_passes_selector->getLastFinishedPass() >= 0
525 && $this->test_passes_selector->hasTestPassedOnce($this->test_session->getActiveId());
526 }
527
528 return false;
529 }
530
531 private function hasAvailablePasses(): bool
532 {
533 $nr_of_tries = $this->object->getNrOfTries();
534
535 return $nr_of_tries === 0 || (count($this->test_passes_selector->getExistingPasses()) <= $nr_of_tries && count($this->test_passes_selector->getClosedPasses()) < $nr_of_tries);
536 }
537
538 private function lastPassSuspended(): bool
539 {
540 return (count($this->test_passes_selector->getExistingPasses()) - count($this->test_passes_selector->getClosedPasses())) === 1;
541 }
542
543 private function isModalLauncherNeeded(): bool
544 {
545 return (
546 $this->main_settings->getIntroductionSettings()->getExamConditionsCheckboxEnabled()
547 || $this->main_settings->getAccessSettings()->getPasswordEnabled()
548 || $this->main_settings->getParticipantFunctionalitySettings()->getUsePreviousAnswerAllowed()
549 && $this->test_passes_selector->getLastFinishedPass() >= 0
550 || $this->user->isAnonymous()
551 );
552 }
553
555 {
556 $message = $this->lng->txt('tst_skl_level_thresholds_missing');
557
558 $link_target = $this->buildLinkTarget(
560 );
561
562 $link = $this->ui_factory->link()->standard(
563 $this->lng->txt('tst_skl_level_thresholds_link'),
564 $link_target
565 );
566
567 return $this->ui_factory->messageBox()->failure($message)->withLinks([$link]);
568 }
569
570 private function areSkillLevelThresholdsMissing(): bool
571 {
572 if (!$this->object->isSkillServiceEnabled()) {
573 return false;
574 }
575
576 $questionContainerId = $this->object->getId();
577
578 $assignmentList = new \ilAssQuestionSkillAssignmentList($this->database);
579 $assignmentList->setParentObjId($questionContainerId);
580 $assignmentList->loadFromDb();
581
582 foreach ($assignmentList->getUniqueAssignedSkills() as $data) {
583 foreach ($data['skill']->getLevelData() as $level) {
584 $threshold = new \ilTestSkillLevelThreshold($this->database);
585 $threshold->setTestId($this->object->getTestId());
586 $threshold->setSkillBaseId($data['skill_base_id']);
587 $threshold->setSkillTrefId($data['skill_tref_id']);
588 $threshold->setSkillLevelId($level['id']);
589
590 if (!$threshold->dbRecordExists()) {
591 return true;
592 }
593 }
594 }
595
596 return false;
597 }
598
599 private function buildLinkTarget(?string $cmd = null): string
600 {
601 $target = array_merge(['ilRepositoryGUI', 'ilObjTestGUI'], ['ilTestSkillAdministrationGUI', 'ilTestSkillLevelThresholdsGUI']);
602 return $this->ctrl->getLinkTargetByClass($target, $cmd);
603 }
604}
Builds a Color from either hex- or rgb values.
Definition: Factory.php:31
Builds data types.
Definition: Factory.php:36
A password is used as part of credentials for authentication.
Definition: Password.php:31
Class Services.
Definition: Services.php:38
__construct(private readonly \ilObjTest $object, private readonly \ilObjUser $user, private readonly UIFactory $ui_factory, private readonly UIRenderer $ui_renderer, private readonly \ilLanguage $lng, private readonly Refinery $refinery, private readonly \ilCtrlInterface $ctrl, private readonly \ilGlobalTemplateInterface $tpl, private readonly ContentStyle $content_style, private readonly HTTPServices $http, private readonly TabsManager $tabs_manager, private readonly \ilAccessHandler $access, private readonly \ilTestAccess $test_access, private readonly \ilDBInterface $database, private readonly \ilRbacSystem $rbac_system)
readonly ilTestPassesSelector $test_passes_selector
const IL_CAL_UNIX
return true
static formatDate(ilDateTime $date, bool $a_skip_day=false, bool $a_include_wd=false, bool $include_seconds=false, ?ilObjUser $user=null,)
@classDescription Date and time handling
language handling
User class.
class ilRbacSystem system function like checkAccess, addActiveRole ... Supporting system functions ar...
static set(string $a_var, $a_val)
Set a value.
const ROOT_FOLDER_ID
Definition: constants.php:32
$http
Definition: deliver.php:30
A result encapsulates a value or an error and simplifies the handling of those.
Definition: Result.php:29
isOK()
Get to know if the result is ok.
value()
Get the encapsulated value.
withButtonLabel(string $label, bool $launchable=true)
Labels the button that launches the process; if the process is not launchable for the user,...
withInputs(Group $fields, \Closure $evaluation, ?MessageBox\MessageBox $instruction=null)
If the Launcher is configured with Inputs, an Roundtrip Modal is shown with these Inputs.
An entity that renders components to a string output.
Definition: Renderer.php:31
Interface ilAccessHandler This interface combines all available interfaces which can be called via gl...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Interface ilDBInterface.
static http()
Fetches the global http state from ILIAS.
global $lng
Definition: privfeed.php:31
$_SERVER['HTTP_HOST']
Definition: raiseError.php:26
$url
Definition: shib_logout.php:68
$message
Definition: xapiexit.php:31