ILIAS  trunk Revision v13.0_alpha-62-g3b72d83f2e5
class.ilLTIToolForm.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
21use ceLTIc\LTI\Util;
22use ILIAS\Refinery\Factory as Refinery;
25use Psr\Http\Message\ServerRequestInterface;
26
36{
37 private const array PRIVACY_IDENTS = [
38 0 => "il_uuid_user_id",
39 2 => "il_uuid_login",
40 1 => "il_uuid_ext_account",
41 5 => "il_uuid_SHA256",
42 6 => "il_uuid_sha256url",
43 4 => "il_uuid_random",
44 3 => "real_email",
45 ];
46 private const array PRIVACY_NAMES = [0 => "none", 1 => "firstname", 2 => "lastname", 3 => "fullname"];
47 private const string DEPRECATION_URL = "https://www.1edtech.org/lti-security-announcement-and-deprecation-schedule";
48
49 private int $saved_id = 0;
50
51 public function __construct(
52 private readonly ilLanguage $lng,
53 private readonly Factory $ui_factory,
54 private readonly Refinery $refinery,
55 private readonly ilObjUser $user,
56 private readonly int $tool_id,
57 private readonly string $version,
58 private readonly bool $in_administration = false
59 ) {
60 }
61
62 public function isAdvantage(): bool
63 {
64 return $this->version === ilLTITool::VERSION_ADVANTAGE;
65 }
66
67 public function getForm(string $action, string $submit_label = ''): Form
68 {
69 $field = $this->ui_factory->input()->field();
70 $row = $this->read();
71 $text = fn(string $column) => (string) ($row[$column] ?? "");
72 $flag = fn(string $column) => (bool) ($row[$column] ?? false);
73
74 $general_inputs = [
75 "title" => $field->text($this->lng->txt("lti_con_prov_title"))->withRequired(true)->withValue($text("title")),
76 "description" => $field->text($this->lng->txt("lti_con_prov_description"))->withValue($text("description")),
77 ];
78 // whether a tool may still be used is decided by the administration only
79 if ($this->in_administration) {
80 $availability = $field->radio($this->lng->txt("lti_con_prov_availability"));
81 foreach (ilLTITool::getAvailabilityLabels($this->lng) as $value => $label) {
82 $availability = $availability->withOption((string) $value, $label);
83 }
84 $general_inputs["availability"] = $availability
85 ->withRequired(true)
86 ->withValue((string) ($row["availability"] ?? ilLTITool::AVAILABILITY_CREATE));
87 }
88 $general = $field->section(
89 $general_inputs,
90 $this->lng->txt($this->tool_id > 0 ? "lti_form_provider_edit" : "lti_form_provider_create")
91 );
92
93 // the byline ends with the unique ILIAS platform id used in the generated user identifiers
94 $privacy_ident = $field->radio(
95 $this->lng->txt("conf_privacy_ident"),
96 $this->lng->txt("conf_privacy_ident_info") . " " . ilCmiXapiUser::getIliasUuid() . ".ilias"
97 );
98 foreach (self::PRIVACY_IDENTS as $value => $name) {
99 $privacy_ident = $privacy_ident->withOption(
100 (string) $value,
101 $this->lng->txt("conf_privacy_ident_" . $name),
102 $this->lng->txt("conf_privacy_ident_" . $name . "_info")
103 );
104 }
105 $privacy_name = $field->radio($this->lng->txt("conf_privacy_name"), $this->lng->txt("conf_privacy_name_info"));
106 foreach (self::PRIVACY_NAMES as $value => $name) {
107 $privacy_name = $privacy_name->withOption(
108 (string) $value,
109 $this->lng->txt("conf_privacy_name_" . $name),
110 $this->lng->txt("conf_privacy_name_" . $name . "_info")
111 );
112 }
113 $privacy = $field->section([
114 "privacy_ident" => $privacy_ident->withValue((string) ($row["privacy_ident"] ?? 0)),
115 "instructor_email" => $field->checkbox(
116 $this->lng->txt("lti_con_prov_instructor_email"),
117 $this->lng->txt("lti_con_prov_instructor_email_info")
118 )->withValue($flag("instructor_send_email")),
119 "privacy_name" => $privacy_name->withValue((string) ($row["privacy_name"] ?? 0)),
120 "instructor_name" => $field->checkbox(
121 $this->lng->txt("lti_con_prov_instructor_name"),
122 $this->lng->txt("lti_con_prov_instructor_name_info")
123 )->withValue($flag("instructor_send_name")),
124 "inc_usr_pic" => $field->checkbox(
125 $this->lng->txt("lti_con_prov_inc_usr_pic"),
126 $this->lng->txt("lti_con_prov_inc_usr_pic_info")
127 )->withValue($flag("inc_usr_pic")),
128 "external_provider" => $field->checkbox(
129 $this->lng->txt("lti_con_prov_external_provider"),
130 $this->lng->txt("lti_con_prov_external_provider_info")
131 )->withValue($flag("external_provider")),
132 ], $this->lng->txt("lti_con_prov_privacy_settings"));
133
134 $outcome = $field->optionalGroup([
135 "mastery_score" => $field->numeric(
136 $this->lng->txt("lti_con_prov_mastery_score_default"),
137 $this->lng->txt("lti_con_prov_mastery_score_default_info")
138 )->withStepSize(0.01)
139 ->withRequired(true)
140 ->withAdditionalTransformation($this->refinery->int()->isGreaterThanOrEqual(0))
141 ->withAdditionalTransformation($this->refinery->int()->isLessThanOrEqual(100))
142 ->withValue(round(100 * (float) ($row["mastery_score"] ?? 0.8), 2)),
143 ], $this->lng->txt("lti_con_prov_has_outcome_service"), $this->lng->txt("lti_con_prov_has_outcome_service_info"));
144 $learning_progress = $field->section([
145 "has_outcome" => $flag("has_outcome") ? $outcome : $outcome->withValue(null),
146 ], $this->lng->txt("lti_con_prov_learning_progress_options"));
147
148 $xapi = $field->optionalGroup([
149 "xapi_launch_url" => $field->text(
150 $this->lng->txt("lti_con_prov_xapi_launch_url"),
151 $this->lng->txt("lti_con_prov_xapi_launch_url_info")
152 )->withRequired(true)->withValue($text("xapi_launch_url")),
153 "xapi_launch_key" => $field->text($this->lng->txt("lti_con_prov_xapi_launch_key"))
154 ->withRequired(true)->withValue($text("xapi_launch_key")),
155 "xapi_launch_secret" => $field->text($this->lng->txt("lti_con_prov_xapi_launch_secret"))
156 ->withRequired(true)->withValue($text("xapi_launch_secret")),
157 "xapi_activity_id" => $field->text(
158 $this->lng->txt("lti_con_prov_xapi_activity_id"),
159 $this->lng->txt("lti_con_prov_xapi_activity_id_info")
160 )->withValue($text("xapi_activity_id")),
161 ], $this->lng->txt("lti_con_prov_use_xapi"), $this->lng->txt("lti_con_prov_use_xapi_info"));
162 $launch = $field->section([
163 "use_provider_id" => $field->checkbox(
164 $this->lng->txt("lti_con_prov_use_provider_id"),
165 $this->lng->txt("lti_con_prov_use_provider_id_info")
166 )->withValue($flag("use_provider_id")),
167 "always_learner" => $field->checkbox(
168 $this->lng->txt("lti_con_prov_always_learner"),
169 $this->lng->txt("lti_con_prov_always_learner_info")
170 )->withValue($flag("always_learner")),
171 "use_xapi" => $flag("use_xapi") ? $xapi : $xapi->withValue(null),
172 "custom_params" => $field->textarea(
173 $this->lng->txt("lti_con_prov_custom_params"),
174 $this->lng->txt("lti_con_prov_custom_params_info")
175 )->withValue($text("custom_params")),
176 ], $this->lng->txt("lti_con_prov_launch_options"));
177
178 $category = $field->radio($this->lng->txt("lti_con_prov_category"), $this->lng->txt("lti_con_prov_category_info"));
179 $categories = ilLTITool::getCategoryLabels($this->lng);
180 foreach ($categories as $name => $label) {
181 $category = $category->withOption($name, $label);
182 }
183 $group = $field->section([
184 "keywords" => $field->text($this->lng->txt("lti_con_prov_keywords"), $this->lng->txt("lti_con_prov_keywords_info"))
185 ->withValue($text("keywords")),
186 "category" => $category->withRequired(true)->withValue(isset($categories[$text("category")]) ? $text("category") : "content"),
187 ], $this->lng->txt("lti_con_prov_group_options"));
188
189 $hints = $field->section([
190 "remarks" => $field->textarea($this->lng->txt("lti_con_prov_remarks"))->withValue($text("remarks")),
191 ], $this->lng->txt("lti_con_prov_hints"));
192
193 $form = $this->ui_factory->input()->container()->form()->standard($action, [
194 "general" => $general,
195 "authentication" => $this->getAuthentication($row),
196 "privacy" => $privacy,
197 "learning_progress" => $learning_progress,
198 "launch" => $launch,
199 "group" => $group,
200 "hints" => $hints,
201 ]);
202
203 return $submit_label === '' ? $form : $form->withSubmitLabel($submit_label);
204 }
205
210 public function save(string $action, ServerRequestInterface $request, bool $own_tool = false): ?Form
211 {
212 $form = $this->getForm($action, $own_tool ? $this->lng->txt("lti_add_own_provider") : "")->withRequest($request);
213 $data = $form->getData();
214 if ($data === null) {
215 return $form;
216 }
217
218 $fields = [
219 "title" => ["text", $data["general"]["title"]],
220 "description" => ["text", $data["general"]["description"]],
221 "privacy_ident" => ["integer", (int) $data["privacy"]["privacy_ident"]],
222 "instructor_send_email" => ["integer", (int) $data["privacy"]["instructor_email"]],
223 "privacy_name" => ["integer", (int) $data["privacy"]["privacy_name"]],
224 "instructor_send_name" => ["integer", (int) $data["privacy"]["instructor_name"]],
225 "inc_usr_pic" => ["integer", (int) $data["privacy"]["inc_usr_pic"]],
226 "external_provider" => ["integer", (int) $data["privacy"]["external_provider"]],
227 "has_outcome" => ["integer", (int) ($data["learning_progress"]["has_outcome"] !== null)],
228 "mastery_score" => ["float", ($data["learning_progress"]["has_outcome"]["mastery_score"] ?? 80) / 100],
229 "use_provider_id" => ["integer", (int) $data["launch"]["use_provider_id"]],
230 "always_learner" => ["integer", (int) $data["launch"]["always_learner"]],
231 "use_xapi" => ["integer", (int) ($data["launch"]["use_xapi"] !== null)],
232 "xapi_launch_url" => ["text", $data["launch"]["use_xapi"]["xapi_launch_url"] ?? ""],
233 "xapi_launch_key" => ["text", $data["launch"]["use_xapi"]["xapi_launch_key"] ?? ""],
234 "xapi_launch_secret" => ["text", $data["launch"]["use_xapi"]["xapi_launch_secret"] ?? ""],
235 "xapi_activity_id" => ["text", $data["launch"]["use_xapi"]["xapi_activity_id"] ?? ""],
236 "custom_params" => ["text", $data["launch"]["custom_params"]],
237 "keywords" => ["text", $data["group"]["keywords"]],
238 "category" => ["text", $data["group"]["category"]],
239 "remarks" => ["text", $data["hints"]["remarks"]],
240 "lti_version" => ["text", $this->version],
241 ] + ($this->isAdvantage()
242 ? $this->getAdvantageFields($data["authentication"])
243 : $this->get1p1Fields($data["authentication"]));
244
245 if ($this->in_administration) {
246 $fields["availability"] = ["integer", (int) $data["general"]["availability"]];
247 } elseif ($this->tool_id === 0) {
248 $fields["availability"] = ["integer", ilLTITool::AVAILABILITY_CREATE];
249 }
250
251 if ($this->tool_id > 0) {
252 ilLTITool::update($this->tool_id, $fields);
253 $this->saved_id = $this->tool_id;
254 return null;
255 }
256
257 $this->saved_id = ilLTITool::create(
258 $fields + ["client_id" => ["text", Util::getRandomString(15)]],
259 $this->user->getId(),
260 !$own_tool
261 );
262
263 return null;
264 }
265
269 public function getSavedId(): int
270 {
271 return $this->saved_id;
272 }
273
277 private function getAuthentication(array $row): ILIAS\UI\Component\Input\Field\Section
278 {
279 return $this->ui_factory->input()->field()->section(
280 $this->isAdvantage() ? $this->getAdvantageInputs($row) : $this->get1p1Inputs($row),
281 $this->lng->txt("lti_con_prov_authentication"),
282 $this->isAdvantage() ? $this->getAdvantageByline() : ""
283 );
284 }
285
289 public static function getDeprecationNotice(
290 ILIAS\UI\Factory $ui_factory,
292 string $version
293 ): ?ILIAS\UI\Component\MessageBox\MessageBox {
294 if ($version === ilLTITool::VERSION_ADVANTAGE) {
295 return null;
296 }
297
298 $link = $ui_factory->link()->standard($lng->txt("lti_1p1_deprecated_link"), self::DEPRECATION_URL)
299 ->withOpenInNewViewport(true);
300
301 return $ui_factory->messageBox()->confirmation($lng->txt("lti_1p1_deprecated_info"))->withLinks([$link]);
302 }
303
304 private function getAdvantageByline(): string
305 {
306 return $this->lng->txt($this->tool_id === 0 ? "lti_con_version_1.3_before_id" : "lti13_hints");
307 }
308
313 private function get1p1Inputs(array $row): array
314 {
315 $field = $this->ui_factory->input()->field();
316 $key_global = $field->optionalGroup([
317 "provider_key" => $field->text($this->lng->txt("lti_con_prov_key"))
318 ->withRequired(true)->withValue((string) ($row["provider_key"] ?? "")),
319 "provider_secret" => $field->text($this->lng->txt("lti_con_prov_secret"))
320 ->withRequired(true)->withValue((string) ($row["provider_secret"] ?? "")),
321 ], $this->lng->txt("lti_con_prov_provider_key_global"), $this->lng->txt("lti_con_prov_provider_key_global_info"));
322
323 return [
324 "provider_url" => $field->text($this->lng->txt("lti_con_prov_url"))
325 ->withRequired(true)->withValue((string) ($row["provider_url"] ?? "")),
326 "key_global" => ($row["provider_key_customizable"] ?? 1) ? $key_global->withValue(null) : $key_global,
327 ];
328 }
329
334 private function get1p1Fields(array $data): array
335 {
336 return [
337 "provider_url" => ["text", $data["provider_url"]],
338 "provider_key_customizable" => ["integer", (int) ($data["key_global"] === null)],
339 "provider_key" => ["text", $data["key_global"]["provider_key"] ?? ""],
340 "provider_secret" => ["text", $data["key_global"]["provider_secret"] ?? ""],
341 ];
342 }
343
348 private function getAdvantageInputs(array $row): array
349 {
350 $field = $this->ui_factory->input()->field();
351 $content_item = $field->optionalGroup([
352 "content_item_url" => $field->text($this->lng->txt("lti_con_content_item_url"))
353 ->withValue((string) ($row["content_item_url"] ?? "")),
354 ], $this->lng->txt("lti_con_content_item"));
355 $key_type = $field->switchableGroup([
356 ilLTITool::KEY_TYPE_RSA => $field->group([
357 "public_key" => $field->textarea(
358 $this->lng->txt("lti_con_key_type_rsa_public_key"),
359 $this->lng->txt("lti_con_key_type_rsa_public_key_info")
360 )->withRequired(true)->withValue((string) ($row["public_key"] ?? "")),
361 ], $this->lng->txt("lti_con_key_type_rsa")),
362 ilLTITool::KEY_TYPE_JWK => $field->group([
363 "public_keyset" => $field->text($this->lng->txt("lti_con_key_type_jwk_url"))
364 ->withRequired(true)->withValue((string) ($row["public_keyset"] ?? "")),
365 ], $this->lng->txt("lti_con_key_type_jwk")),
366 ], $this->lng->txt("lti_con_key_type"))->withRequired(true);
367 if (in_array($row["key_type"] ?? "", [ilLTITool::KEY_TYPE_RSA, ilLTITool::KEY_TYPE_JWK], true)) {
368 $key_type = $key_type->withValue($row["key_type"]);
369 }
370
371 $inputs = [
372 "provider_url" => $field->text($this->lng->txt("lti_con_tool_url"))
373 ->withRequired(true)->withValue((string) ($row["provider_url"] ?? "")),
374 "initiate_login" => $field->text($this->lng->txt("lti_con_initiate_login_url"))
375 ->withRequired(true)->withValue((string) ($row["initiate_login"] ?? "")),
376 "redirection_uris" => $field->textarea($this->lng->txt("lti_con_redirection_uris"))
377 ->withRequired(true)->withValue(str_replace(",", "\n", (string) ($row["redirection_uris"] ?? ""))),
378 "key_type" => $key_type,
379 "content_item" => ($row["content_item"] ?? false) ? $content_item : $content_item->withValue(null),
380 "grade_synchronization" => $field->checkbox(
381 $this->lng->txt("lti_con_grade_synchronization"),
382 $this->lng->txt("lti_con_grade_synchronization_info")
383 )->withValue((bool) ($row["grade_synchronization"] ?? false)),
384 "names_roles" => $field->checkbox(
385 $this->lng->txt("lti_con_names_roles"),
386 $this->lng->txt("lti_con_names_roles_info")
387 )->withValue((bool) ($row["names_roles"] ?? false)),
388 ];
389
390 if ($this->tool_id === 0) {
391 return $inputs;
392 }
393
394 // read only data the tool needs when it is registered without dynamic registration
395 $platform_data = [
396 "lti_13_platform_id" => ilLTIEndpoint::getBaseUrl(),
397 "lti_13_client_id" => (string) ($row["client_id"] ?? ""),
398 "lti_13_deployment_id" => (string) $this->tool_id,
399 "lti_13_keyset_url" => ilLTIAdvantageKeyPair::getJwksUrl(),
400 "lti_13_token_url" => ilLTIEndpoint::getUrl(ilLTIEndpoint::TOKEN),
401 "lti_13_authentication_url" => ilLTIEndpoint::getUrl(ilLTIEndpoint::AUTHENTICATION),
402 ];
403 foreach ($platform_data as $txt => $value) {
404 $inputs[$txt] = $field->text($this->lng->txt($txt))->withValue($value)->withDisabled(true);
405 }
406
407 return $inputs;
408 }
409
414 private function getAdvantageFields(array $data): array
415 {
416 [$key_type, $key_data] = $data["key_type"];
417 preg_match_all('/\S+/', (string) $data["redirection_uris"], $redirection_uris);
418
419 return [
420 "provider_url" => ["text", $data["provider_url"]],
421 "initiate_login" => ["text", $data["initiate_login"]],
422 "redirection_uris" => ["text", implode(",", $redirection_uris[0])],
423 "key_type" => ["text", $key_type],
424 "public_key" => ["text", $key_data["public_key"] ?? ""],
425 "public_keyset" => ["text", $key_data["public_keyset"] ?? ""],
426 "content_item" => ["integer", (int) ($data["content_item"] !== null)],
427 "content_item_url" => ["text", $data["content_item"]["content_item_url"] ?? ""],
428 "grade_synchronization" => ["integer", (int) $data["grade_synchronization"]],
429 "names_roles" => ["integer", (int) $data["names_roles"]],
430 ];
431 }
432
436 private function read(): array
437 {
438 return $this->tool_id === 0 ? [] : ilLTITool::read($this->tool_id);
439 }
440}
$version
Definition: plugin.php:24
$learning_progress
Definition: plugin.php:29
$general
SECTIONS.
Builds a Color from either hex- or rgb values.
Definition: Factory.php:31
Builds data types.
Definition: Factory.php:36
const string AUTHENTICATION
static getUrl(string $script, string $suffix='')
The URL of an entry point, with the query or path that follows it.
static getBaseUrl()
The base URL of the installation as the request reached it.
const string TOKEN
Create and edit form of an external tool ILIAS may launch, stored in lti_ext_provider.
save(string $action, ServerRequestInterface $request, bool $own_tool=false)
Stores the submitted form.
const array PRIVACY_NAMES
__construct(private readonly ilLanguage $lng, private readonly Factory $ui_factory, private readonly Refinery $refinery, private readonly ilObjUser $user, private readonly int $tool_id, private readonly string $version, private readonly bool $in_administration=false)
getSavedId()
The tool the last successful save stored.
getAuthentication(array $row)
The credentials of the tool, which are the only fields the two LTI versions do not share.
const array PRIVACY_IDENTS
static getDeprecationNotice(ILIAS\UI\Factory $ui_factory, ilLanguage $lng, string $version)
The warning an LTI 1.1 tool carries, which its form shows above itself.
get1p1Inputs(array $row)
get1p1Fields(array $data)
getAdvantageInputs(array $row)
const string DEPRECATION_URL
getForm(string $action, string $submit_label='')
getAdvantageFields(array $data)
An external tool ILIAS can launch, stored in lti_ext_provider.
static getCategoryLabels(ilLanguage $lng)
The categories of the repository a tool can be listed under.
const string VERSION_ADVANTAGE
const string KEY_TYPE_JWK
const string KEY_TYPE_RSA
How an LTI Advantage tool publishes the key its messages are signed with: a PEM public key,...
const int AVAILABILITY_CREATE
static create(array $fields, int $creator_id, bool $global=true)
static read(int $id)
static getAvailabilityLabels(ilLanguage $lng)
static update(int $id, array $fields)
language handling
User class.
This describes a standard form.
Definition: Standard.php:30
This is how the factory for UI elements looks.
Definition: Factory.php:38
$request
@ilCtrl_Calls ilObjLearningSequenceContentGUI: ilObjLearningSequenceConditionsGUI
Definition: ltiauth.php:39
withValue($value)
Get an input like this with another value displayed on the client side.
Definition: Group.php:61
Interface Observer \BackgroundTasks Contains several chained tasks and infos about them.
global $lng
Definition: privfeed.php:26
if(!file_exists('../ilias.ini.php'))
$data
Definition: storeScorm.php:45
$text
Definition: xapiexit.php:21