diff --git a/components/ILIAS/LTI/src/Firebase/BeforeValidException.php b/components/ILIAS/LTI/src/Firebase/BeforeValidException.php deleted file mode 100755 index 595164bf35db..000000000000 --- a/components/ILIAS/LTI/src/Firebase/BeforeValidException.php +++ /dev/null @@ -1,18 +0,0 @@ -payload = $payload; - } - - public function getPayload(): object - { - return $this->payload; - } -} diff --git a/components/ILIAS/LTI/src/Firebase/CHANGELOG.md b/components/ILIAS/LTI/src/Firebase/CHANGELOG.md deleted file mode 100755 index 4279dfd2331d..000000000000 --- a/components/ILIAS/LTI/src/Firebase/CHANGELOG.md +++ /dev/null @@ -1,163 +0,0 @@ -# Changelog - -## [6.9.0](https://github.com/firebase/php-jwt/compare/v6.8.1...v6.9.0) (2023-10-04) - - -### Features - -* add payload to jwt exception ([#521](https://github.com/firebase/php-jwt/issues/521)) ([175edf9](https://github.com/firebase/php-jwt/commit/175edf958bb61922ec135b2333acf5622f2238a2)) - -## [6.8.1](https://github.com/firebase/php-jwt/compare/v6.8.0...v6.8.1) (2023-07-14) - - -### Bug Fixes - -* accept float claims but round down to ignore them ([#492](https://github.com/firebase/php-jwt/issues/492)) ([3936842](https://github.com/firebase/php-jwt/commit/39368423beeaacb3002afa7dcb75baebf204fe7e)) -* different BeforeValidException messages for nbf and iat ([#526](https://github.com/firebase/php-jwt/issues/526)) ([0a53cf2](https://github.com/firebase/php-jwt/commit/0a53cf2986e45c2bcbf1a269f313ebf56a154ee4)) - -## [6.8.0](https://github.com/firebase/php-jwt/compare/v6.7.0...v6.8.0) (2023-06-14) - - -### Features - -* add support for P-384 curve ([#515](https://github.com/firebase/php-jwt/issues/515)) ([5de4323](https://github.com/firebase/php-jwt/commit/5de4323f4baf4d70bca8663bd87682a69c656c3d)) - - -### Bug Fixes - -* handle invalid http responses ([#508](https://github.com/firebase/php-jwt/issues/508)) ([91c39c7](https://github.com/firebase/php-jwt/commit/91c39c72b22fc3e1191e574089552c1f2041c718)) - -## [6.7.0](https://github.com/firebase/php-jwt/compare/v6.6.0...v6.7.0) (2023-06-14) - - -### Features - -* add ed25519 support to JWK (public keys) ([#452](https://github.com/firebase/php-jwt/issues/452)) ([e53979a](https://github.com/firebase/php-jwt/commit/e53979abae927de916a75b9d239cfda8ce32be2a)) - -## [6.6.0](https://github.com/firebase/php-jwt/compare/v6.5.0...v6.6.0) (2023-06-13) - - -### Features - -* allow get headers when decoding token ([#442](https://github.com/firebase/php-jwt/issues/442)) ([fb85f47](https://github.com/firebase/php-jwt/commit/fb85f47cfaeffdd94faf8defdf07164abcdad6c3)) - - -### Bug Fixes - -* only check iat if nbf is not used ([#493](https://github.com/firebase/php-jwt/issues/493)) ([398ccd2](https://github.com/firebase/php-jwt/commit/398ccd25ea12fa84b9e4f1085d5ff448c21ec797)) - -## [6.5.0](https://github.com/firebase/php-jwt/compare/v6.4.0...v6.5.0) (2023-05-12) - - -### Bug Fixes - -* allow KID of '0' ([#505](https://github.com/firebase/php-jwt/issues/505)) ([9dc46a9](https://github.com/firebase/php-jwt/commit/9dc46a9c3e5801294249cfd2554c5363c9f9326a)) - - -### Miscellaneous Chores - -* drop support for PHP 7.3 ([#495](https://github.com/firebase/php-jwt/issues/495)) - -## [6.4.0](https://github.com/firebase/php-jwt/compare/v6.3.2...v6.4.0) (2023-02-08) - - -### Features - -* add support for W3C ES256K ([#462](https://github.com/firebase/php-jwt/issues/462)) ([213924f](https://github.com/firebase/php-jwt/commit/213924f51936291fbbca99158b11bd4ae56c2c95)) -* improve caching by only decoding jwks when necessary ([#486](https://github.com/firebase/php-jwt/issues/486)) ([78d3ed1](https://github.com/firebase/php-jwt/commit/78d3ed1073553f7d0bbffa6c2010009a0d483d5c)) - -## [6.3.2](https://github.com/firebase/php-jwt/compare/v6.3.1...v6.3.2) (2022-11-01) - - -### Bug Fixes - -* check kid before using as array index ([bad1b04](https://github.com/firebase/php-jwt/commit/bad1b040d0c736bbf86814c6b5ae614f517cf7bd)) - -## [6.3.1](https://github.com/firebase/php-jwt/compare/v6.3.0...v6.3.1) (2022-11-01) - - -### Bug Fixes - -* casing of GET for PSR compat ([#451](https://github.com/firebase/php-jwt/issues/451)) ([60b52b7](https://github.com/firebase/php-jwt/commit/60b52b71978790eafcf3b95cfbd83db0439e8d22)) -* string interpolation format for php 8.2 ([#446](https://github.com/firebase/php-jwt/issues/446)) ([2e07d8a](https://github.com/firebase/php-jwt/commit/2e07d8a1524d12b69b110ad649f17461d068b8f2)) - -## 6.3.0 / 2022-07-15 - - - Added ES256 support to JWK parsing ([#399](https://github.com/firebase/php-jwt/pull/399)) - - Fixed potential caching error in `CachedKeySet` by caching jwks as strings ([#435](https://github.com/firebase/php-jwt/pull/435)) - -## 6.2.0 / 2022-05-14 - - - Added `CachedKeySet` ([#397](https://github.com/firebase/php-jwt/pull/397)) - - Added `$defaultAlg` parameter to `JWT::parseKey` and `JWT::parseKeySet` ([#426](https://github.com/firebase/php-jwt/pull/426)). - -## 6.1.0 / 2022-03-23 - - - Drop support for PHP 5.3, 5.4, 5.5, 5.6, and 7.0 - - Add parameter typing and return types where possible - -## 6.0.0 / 2022-01-24 - - - **Backwards-Compatibility Breaking Changes**: See the [Release Notes](https://github.com/firebase/php-jwt/releases/tag/v6.0.0) for more information. - - New Key object to prevent key/algorithm type confusion (#365) - - Add JWK support (#273) - - Add ES256 support (#256) - - Add ES384 support (#324) - - Add Ed25519 support (#343) - -## 5.0.0 / 2017-06-26 -- Support RS384 and RS512. - See [#117](https://github.com/firebase/php-jwt/pull/117). Thanks [@joostfaassen](https://github.com/joostfaassen)! -- Add an example for RS256 openssl. - See [#125](https://github.com/firebase/php-jwt/pull/125). Thanks [@akeeman](https://github.com/akeeman)! -- Detect invalid Base64 encoding in signature. - See [#162](https://github.com/firebase/php-jwt/pull/162). Thanks [@psignoret](https://github.com/psignoret)! -- Update `JWT::verify` to handle OpenSSL errors. - See [#159](https://github.com/firebase/php-jwt/pull/159). Thanks [@bshaffer](https://github.com/bshaffer)! -- Add `array` type hinting to `decode` method - See [#101](https://github.com/firebase/php-jwt/pull/101). Thanks [@hywak](https://github.com/hywak)! -- Add all JSON error types. - See [#110](https://github.com/firebase/php-jwt/pull/110). Thanks [@gbalduzzi](https://github.com/gbalduzzi)! -- Bugfix 'kid' not in given key list. - See [#129](https://github.com/firebase/php-jwt/pull/129). Thanks [@stampycode](https://github.com/stampycode)! -- Miscellaneous cleanup, documentation and test fixes. - See [#107](https://github.com/firebase/php-jwt/pull/107), [#115](https://github.com/firebase/php-jwt/pull/115), - [#160](https://github.com/firebase/php-jwt/pull/160), [#161](https://github.com/firebase/php-jwt/pull/161), and - [#165](https://github.com/firebase/php-jwt/pull/165). Thanks [@akeeman](https://github.com/akeeman), - [@chinedufn](https://github.com/chinedufn), and [@bshaffer](https://github.com/bshaffer)! - -## 4.0.0 / 2016-07-17 -- Add support for late static binding. See [#88](https://github.com/firebase/php-jwt/pull/88) for details. Thanks to [@chappy84](https://github.com/chappy84)! -- Use static `$timestamp` instead of `time()` to improve unit testing. See [#93](https://github.com/firebase/php-jwt/pull/93) for details. Thanks to [@josephmcdermott](https://github.com/josephmcdermott)! -- Fixes to exceptions classes. See [#81](https://github.com/firebase/php-jwt/pull/81) for details. Thanks to [@Maks3w](https://github.com/Maks3w)! -- Fixes to PHPDoc. See [#76](https://github.com/firebase/php-jwt/pull/76) for details. Thanks to [@akeeman](https://github.com/akeeman)! - -## 3.0.0 / 2015-07-22 -- Minimum PHP version updated from `5.2.0` to `5.3.0`. -- Add `\Firebase\JWT` namespace. See -[#59](https://github.com/firebase/php-jwt/pull/59) for details. Thanks to -[@Dashron](https://github.com/Dashron)! -- Require a non-empty key to decode and verify a JWT. See -[#60](https://github.com/firebase/php-jwt/pull/60) for details. Thanks to -[@sjones608](https://github.com/sjones608)! -- Cleaner documentation blocks in the code. See -[#62](https://github.com/firebase/php-jwt/pull/62) for details. Thanks to -[@johanderuijter](https://github.com/johanderuijter)! - -## 2.2.0 / 2015-06-22 -- Add support for adding custom, optional JWT headers to `JWT::encode()`. See -[#53](https://github.com/firebase/php-jwt/pull/53/files) for details. Thanks to -[@mcocaro](https://github.com/mcocaro)! - -## 2.1.0 / 2015-05-20 -- Add support for adding a leeway to `JWT:decode()` that accounts for clock skew -between signing and verifying entities. Thanks to [@lcabral](https://github.com/lcabral)! -- Add support for passing an object implementing the `ArrayAccess` interface for -`$keys` argument in `JWT::decode()`. Thanks to [@aztech-dev](https://github.com/aztech-dev)! - -## 2.0.0 / 2015-04-01 -- **Note**: It is strongly recommended that you update to > v2.0.0 to address - known security vulnerabilities in prior versions when both symmetric and - asymmetric keys are used together. -- Update signature for `JWT::decode(...)` to require an array of supported - algorithms to use when verifying token signatures. diff --git a/components/ILIAS/LTI/src/Firebase/CachedKeySet.php b/components/ILIAS/LTI/src/Firebase/CachedKeySet.php deleted file mode 100755 index ebdf757f10ff..000000000000 --- a/components/ILIAS/LTI/src/Firebase/CachedKeySet.php +++ /dev/null @@ -1,269 +0,0 @@ - - */ -class CachedKeySet implements ArrayAccess -{ - /** - * @var string - */ - private string $jwksUri; - /** - * @var ClientInterface - */ - private ClientInterface $httpClient; - /** - * @var RequestFactoryInterface - */ - private RequestFactoryInterface $httpFactory; - /** - * @var CacheItemPoolInterface - */ - private CacheItemPoolInterface $cache; - /** - * @var ?int - */ - private ?int $expiresAfter; - /** - * @var ?CacheItemInterface - */ - private ?CacheItemInterface $cacheItem; - /** - * @var array> - */ - private array $keySet; - /** - * @var string - */ - private string $cacheKey; - /** - * @var string - */ - private string $cacheKeyPrefix = 'jwks'; - /** - * @var int - */ - private int $maxKeyLength = 64; - /** - * @var bool - */ - private bool $rateLimit; - /** - * @var string - */ - private string $rateLimitCacheKey; - /** - * @var int - */ - private int $maxCallsPerMinute = 10; - /** - * @var string|null - */ - private ?string $defaultAlg; - - public function __construct( - string $jwksUri, - ClientInterface $httpClient, - RequestFactoryInterface $httpFactory, - CacheItemPoolInterface $cache, - int $expiresAfter = null, - bool $rateLimit = false, - string $defaultAlg = null - ) { - $this->jwksUri = $jwksUri; - $this->httpClient = $httpClient; - $this->httpFactory = $httpFactory; - $this->cache = $cache; - $this->expiresAfter = $expiresAfter; - $this->rateLimit = $rateLimit; - $this->defaultAlg = $defaultAlg; - $this->setCacheKeys(); - } - - /** - * @param string $keyId - * @return Key - */ - public function offsetGet($keyId): Key - { - if (!$this->keyIdExists($keyId)) { - throw new OutOfBoundsException('Key ID not found'); - } - return JWK::parseKey($this->keySet[$keyId], $this->defaultAlg); - } - - /** - * @param string $keyId - * @return bool - */ - public function offsetExists($keyId): bool - { - return $this->keyIdExists($keyId); - } - - /** - * @param string $offset - * @param Key $value - */ - public function offsetSet($offset, $value): void - { - throw new LogicException('Method not implemented'); - } - - /** - * @param string $offset - */ - public function offsetUnset($offset): void - { - throw new LogicException('Method not implemented'); - } - - /** - * @return array - */ - private function formatJwksForCache(string $jwks): array - { - $jwks = json_decode($jwks, true); - - if (!isset($jwks['keys'])) { - throw new UnexpectedValueException('"keys" member must exist in the JWK Set'); - } - - if (empty($jwks['keys'])) { - throw new InvalidArgumentException('JWK Set did not contain any keys'); - } - - $keys = []; - foreach ($jwks['keys'] as $k => $v) { - $kid = isset($v['kid']) ? $v['kid'] : $k; - $keys[(string) $kid] = $v; - } - - return $keys; - } - - private function keyIdExists(string $keyId): bool - { - if (null === $this->keySet) { - $item = $this->getCacheItem(); - // Try to load keys from cache - if ($item->isHit()) { - // item found! retrieve it - $this->keySet = $item->get(); - // If the cached item is a string, the JWKS response was cached (previous behavior). - // Parse this into expected format array instead. - if (\is_string($this->keySet)) { - $this->keySet = $this->formatJwksForCache($this->keySet); - } - } - } - - if (!isset($this->keySet[$keyId])) { - if ($this->rateLimitExceeded()) { - return false; - } - $request = $this->httpFactory->createRequest('GET', $this->jwksUri); - $jwksResponse = $this->httpClient->sendRequest($request); - if ($jwksResponse->getStatusCode() !== 200) { - throw new UnexpectedValueException( - sprintf( - 'HTTP Error: %d %s for URI "%s"', - $jwksResponse->getStatusCode(), - $jwksResponse->getReasonPhrase(), - $this->jwksUri, - ), - $jwksResponse->getStatusCode() - ); - } - $this->keySet = $this->formatJwksForCache((string) $jwksResponse->getBody()); - - if (!isset($this->keySet[$keyId])) { - return false; - } - - $item = $this->getCacheItem(); - $item->set($this->keySet); - if ($this->expiresAfter) { - $item->expiresAfter($this->expiresAfter); - } - $this->cache->save($item); - } - - return true; - } - - private function rateLimitExceeded(): bool - { - if (!$this->rateLimit) { - return false; - } - - $cacheItem = $this->cache->getItem($this->rateLimitCacheKey); - if (!$cacheItem->isHit()) { - $cacheItem->expiresAfter(1); // # of calls are cached each minute - } - - $callsPerMinute = (int) $cacheItem->get(); - if (++$callsPerMinute > $this->maxCallsPerMinute) { - return true; - } - $cacheItem->set($callsPerMinute); - $this->cache->save($cacheItem); - return false; - } - - private function getCacheItem(): CacheItemInterface - { - if (\is_null($this->cacheItem)) { - $this->cacheItem = $this->cache->getItem($this->cacheKey); - } - - return $this->cacheItem; - } - - private function setCacheKeys(): void - { - if (empty($this->jwksUri)) { - throw new RuntimeException('JWKS URI is empty'); - } - - // ensure we do not have illegal characters - $key = preg_replace('|[^a-zA-Z0-9_\.!]|', '', $this->jwksUri); - - // add prefix - $key = $this->cacheKeyPrefix . $key; - - // Hash keys if they exceed $maxKeyLength of 64 - if (\strlen($key) > $this->maxKeyLength) { - $key = substr(hash('sha256', $key), 0, $this->maxKeyLength); - } - - $this->cacheKey = $key; - - if ($this->rateLimit) { - // add prefix - $rateLimitKey = $this->cacheKeyPrefix . 'ratelimit' . $key; - - // Hash keys if they exceed $maxKeyLength of 64 - if (\strlen($rateLimitKey) > $this->maxKeyLength) { - $rateLimitKey = substr(hash('sha256', $rateLimitKey), 0, $this->maxKeyLength); - } - - $this->rateLimitCacheKey = $rateLimitKey; - } - } -} diff --git a/components/ILIAS/LTI/src/Firebase/ExpiredException.php b/components/ILIAS/LTI/src/Firebase/ExpiredException.php deleted file mode 100755 index 12fef0944868..000000000000 --- a/components/ILIAS/LTI/src/Firebase/ExpiredException.php +++ /dev/null @@ -1,18 +0,0 @@ -payload = $payload; - } - - public function getPayload(): object - { - return $this->payload; - } -} diff --git a/components/ILIAS/LTI/src/Firebase/JWK.php b/components/ILIAS/LTI/src/Firebase/JWK.php deleted file mode 100755 index 63fb2484b320..000000000000 --- a/components/ILIAS/LTI/src/Firebase/JWK.php +++ /dev/null @@ -1,349 +0,0 @@ - - * @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD - * @link https://github.com/firebase/php-jwt - */ -class JWK -{ - private const OID = '1.2.840.10045.2.1'; - private const ASN1_OBJECT_IDENTIFIER = 0x06; - private const ASN1_SEQUENCE = 0x10; // also defined in JWT - private const ASN1_BIT_STRING = 0x03; - private const EC_CURVES = [ - 'P-256' => '1.2.840.10045.3.1.7', // Len: 64 - 'secp256k1' => '1.3.132.0.10', // Len: 64 - 'P-384' => '1.3.132.0.34', // Len: 96 - // 'P-521' => '1.3.132.0.35', // Len: 132 (not supported) - ]; - - // For keys with "kty" equal to "OKP" (Octet Key Pair), the "crv" parameter must contain the key subtype. - // This library supports the following subtypes: - private const OKP_SUBTYPES = [ - 'Ed25519' => true, // RFC 8037 - ]; - - /** - * Parse a set of JWK keys - * - * @param array $jwks The JSON Web Key Set as an associative array - * @param string $defaultAlg The algorithm for the Key object if "alg" is not set in the - * JSON Web Key Set - * - * @return array An associative array of key IDs (kid) to Key objects - * - * @throws InvalidArgumentException Provided JWK Set is empty - * @throws UnexpectedValueException Provided JWK Set was invalid - * @throws DomainException OpenSSL failure - * - * @uses parseKey - */ - public static function parseKeySet(array $jwks, string $defaultAlg = null): array - { - $keys = []; - - if (!isset($jwks['keys'])) { - throw new UnexpectedValueException('"keys" member must exist in the JWK Set'); - } - - if (empty($jwks['keys'])) { - throw new InvalidArgumentException('JWK Set did not contain any keys'); - } - - foreach ($jwks['keys'] as $k => $v) { - $kid = isset($v['kid']) ? $v['kid'] : $k; - if ($key = self::parseKey($v, $defaultAlg)) { - $keys[(string) $kid] = $key; - } - } - - if (0 === \count($keys)) { - throw new UnexpectedValueException('No supported algorithms found in JWK Set'); - } - - return $keys; - } - - /** - * Parse a JWK key - * - * @param array $jwk An individual JWK - * @param string $defaultAlg The algorithm for the Key object if "alg" is not set in the - * JSON Web Key Set - * - * @return Key The key object for the JWK - * - * @throws InvalidArgumentException Provided JWK is empty - * @throws UnexpectedValueException Provided JWK was invalid - * @throws DomainException OpenSSL failure - * - * @uses createPemFromModulusAndExponent - */ - public static function parseKey(array $jwk, string $defaultAlg = null): ?Key - { - if (empty($jwk)) { - throw new InvalidArgumentException('JWK must not be empty'); - } - - if (!isset($jwk['kty'])) { - throw new UnexpectedValueException('JWK must contain a "kty" parameter'); - } - - if (!isset($jwk['alg'])) { - if (\is_null($defaultAlg)) { - // The "alg" parameter is optional in a KTY, but an algorithm is required - // for parsing in this library. Use the $defaultAlg parameter when parsing the - // key set in order to prevent this error. - // @see https://datatracker.ietf.org/doc/html/rfc7517#section-4.4 - throw new UnexpectedValueException('JWK must contain an "alg" parameter'); - } - $jwk['alg'] = $defaultAlg; - } - - switch ($jwk['kty']) { - case 'RSA': - if (!empty($jwk['d'])) { - throw new UnexpectedValueException('RSA private keys are not supported'); - } - if (!isset($jwk['n']) || !isset($jwk['e'])) { - throw new UnexpectedValueException('RSA keys must contain values for both "n" and "e"'); - } - - $pem = self::createPemFromModulusAndExponent($jwk['n'], $jwk['e']); - $publicKey = \openssl_pkey_get_public($pem); - if (false === $publicKey) { - throw new DomainException( - 'OpenSSL error: ' . \openssl_error_string() - ); - } - return new Key($publicKey, $jwk['alg']); - case 'EC': - if (isset($jwk['d'])) { - // The key is actually a private key - throw new UnexpectedValueException('Key data must be for a public key'); - } - - if (empty($jwk['crv'])) { - throw new UnexpectedValueException('crv not set'); - } - - if (!isset(self::EC_CURVES[$jwk['crv']])) { - throw new DomainException('Unrecognised or unsupported EC curve'); - } - - if (empty($jwk['x']) || empty($jwk['y'])) { - throw new UnexpectedValueException('x and y not set'); - } - - $publicKey = self::createPemFromCrvAndXYCoordinates($jwk['crv'], $jwk['x'], $jwk['y']); - return new Key($publicKey, $jwk['alg']); - case 'OKP': - if (isset($jwk['d'])) { - // The key is actually a private key - throw new UnexpectedValueException('Key data must be for a public key'); - } - - if (!isset($jwk['crv'])) { - throw new UnexpectedValueException('crv not set'); - } - - if (empty(self::OKP_SUBTYPES[$jwk['crv']])) { - throw new DomainException('Unrecognised or unsupported OKP key subtype'); - } - - if (empty($jwk['x'])) { - throw new UnexpectedValueException('x not set'); - } - - // This library works internally with EdDSA keys (Ed25519) encoded in standard base64. - $publicKey = JWT::convertBase64urlToBase64($jwk['x']); - return new Key($publicKey, $jwk['alg']); - default: - break; - } - - return null; - } - - /** - * Converts the EC JWK values to pem format. - * - * @param string $crv The EC curve (only P-256 & P-384 is supported) - * @param string $x The EC x-coordinate - * @param string $y The EC y-coordinate - * - * @return string - */ - private static function createPemFromCrvAndXYCoordinates(string $crv, string $x, string $y): string - { - $pem = - self::encodeDER( - self::ASN1_SEQUENCE, - self::encodeDER( - self::ASN1_SEQUENCE, - self::encodeDER( - self::ASN1_OBJECT_IDENTIFIER, - self::encodeOID(self::OID) - ) - . self::encodeDER( - self::ASN1_OBJECT_IDENTIFIER, - self::encodeOID(self::EC_CURVES[$crv]) - ) - ) . - self::encodeDER( - self::ASN1_BIT_STRING, - \chr(0x00) . \chr(0x04) - . JWT::urlsafeB64Decode($x) - . JWT::urlsafeB64Decode($y) - ) - ); - - return sprintf( - "-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----\n", - wordwrap(base64_encode($pem), 64, "\n", true) - ); - } - - /** - * Create a public key represented in PEM format from RSA modulus and exponent information - * - * @param string $n The RSA modulus encoded in Base64 - * @param string $e The RSA exponent encoded in Base64 - * - * @return string The RSA public key represented in PEM format - * - * @uses encodeLength - */ - private static function createPemFromModulusAndExponent( - string $n, - string $e - ): string { - $mod = JWT::urlsafeB64Decode($n); - $exp = JWT::urlsafeB64Decode($e); - - $modulus = \pack('Ca*a*', 2, self::encodeLength(\strlen($mod)), $mod); - $publicExponent = \pack('Ca*a*', 2, self::encodeLength(\strlen($exp)), $exp); - - $rsaPublicKey = \pack( - 'Ca*a*a*', - 48, - self::encodeLength(\strlen($modulus) + \strlen($publicExponent)), - $modulus, - $publicExponent - ); - - // sequence(oid(1.2.840.113549.1.1.1), null)) = rsaEncryption. - $rsaOID = \pack('H*', '300d06092a864886f70d0101010500'); // hex version of MA0GCSqGSIb3DQEBAQUA - $rsaPublicKey = \chr(0) . $rsaPublicKey; - $rsaPublicKey = \chr(3) . self::encodeLength(\strlen($rsaPublicKey)) . $rsaPublicKey; - - $rsaPublicKey = \pack( - 'Ca*a*', - 48, - self::encodeLength(\strlen($rsaOID . $rsaPublicKey)), - $rsaOID . $rsaPublicKey - ); - - return "-----BEGIN PUBLIC KEY-----\r\n" . - \chunk_split(\base64_encode($rsaPublicKey), 64) . - '-----END PUBLIC KEY-----'; - } - - /** - * DER-encode the length - * - * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See - * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information. - * - * @param int $length - * @return string - */ - private static function encodeLength(int $length): string - { - if ($length <= 0x7F) { - return \chr($length); - } - - $temp = \ltrim(\pack('N', $length), \chr(0)); - - return \pack('Ca*', 0x80 | \strlen($temp), $temp); - } - - /** - * Encodes a value into a DER object. - * Also defined in Firebase\JWT\JWT - * - * @param int $type DER tag - * @param string $value the value to encode - * @return string the encoded object - */ - private static function encodeDER(int $type, string $value): string - { - $tag_header = 0; - if ($type === self::ASN1_SEQUENCE) { - $tag_header |= 0x20; - } - - // Type - $der = \chr($tag_header | $type); - - // Length - $der .= \chr(\strlen($value)); - - return $der . $value; - } - - /** - * Encodes a string into a DER-encoded OID. - * - * @param string $oid the OID string - * @return string the binary DER-encoded OID - */ - private static function encodeOID(string $oid): string - { - $octets = explode('.', $oid); - - // Get the first octet - $first = (int) array_shift($octets); - $second = (int) array_shift($octets); - $oid = \chr($first * 40 + $second); - - // Iterate over subsequent octets - foreach ($octets as $octet) { - if ($octet == 0) { - $oid .= \chr(0x00); - continue; - } - $bin = ''; - - while ($octet) { - $bin .= \chr(0x80 | ($octet & 0x7f)); - $octet >>= 7; - } - $bin[0] = $bin[0] & \chr(0x7f); - - // Convert to big endian if necessary - if (pack('V', 65534) == pack('L', 65534)) { - $oid .= strrev($bin); - } else { - $oid .= $bin; - } - } - - return $oid; - } -} diff --git a/components/ILIAS/LTI/src/Firebase/JWT.php b/components/ILIAS/LTI/src/Firebase/JWT.php deleted file mode 100755 index f7296bc07e2c..000000000000 --- a/components/ILIAS/LTI/src/Firebase/JWT.php +++ /dev/null @@ -1,668 +0,0 @@ - - * @author Anant Narayanan - * @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD - * @link https://github.com/firebase/php-jwt - */ -class JWT -{ - private const ASN1_INTEGER = 0x02; - private const ASN1_SEQUENCE = 0x10; - private const ASN1_BIT_STRING = 0x03; - - /** - * When checking nbf, iat or expiration times, - * we want to provide some extra leeway time to - * account for clock skew. - * - * @var int - */ - public static int $leeway = 0; - - /** - * Allow the current timestamp to be specified. - * Useful for fixing a value within unit testing. - * Will default to PHP time() value if null. - * - * @var ?int - */ - public static ?int $timestamp = null; - - /** - * @var array - */ - public static array $supported_algs = [ - 'ES384' => ['openssl', 'SHA384'], - 'ES256' => ['openssl', 'SHA256'], - 'ES256K' => ['openssl', 'SHA256'], - 'HS256' => ['hash_hmac', 'SHA256'], - 'HS384' => ['hash_hmac', 'SHA384'], - 'HS512' => ['hash_hmac', 'SHA512'], - 'RS256' => ['openssl', 'SHA256'], - 'RS384' => ['openssl', 'SHA384'], - 'RS512' => ['openssl', 'SHA512'], - 'EdDSA' => ['sodium_crypto', 'EdDSA'], - ]; - - /** - * Decodes a JWT string into a PHP object. - * - * @param string $jwt The JWT - * @param Key|ArrayAccess|array $keyOrKeyArray The Key or associative array of key IDs - * (kid) to Key objects. - * If the algorithm used is asymmetric, this is - * the public key. - * Each Key object contains an algorithm and - * matching key. - * Supported algorithms are 'ES384','ES256', - * 'HS256', 'HS384', 'HS512', 'RS256', 'RS384' - * and 'RS512'. - * @param stdClass $headers Optional. Populates stdClass with headers. - * - * @return stdClass The JWT's payload as a PHP object - * - * @throws InvalidArgumentException Provided key/key-array was empty or malformed - * @throws DomainException Provided JWT is malformed - * @throws UnexpectedValueException Provided JWT was invalid - * @throws SignatureInvalidException Provided JWT was invalid because the signature verification failed - * @throws BeforeValidException Provided JWT is trying to be used before it's eligible as defined by 'nbf' - * @throws BeforeValidException Provided JWT is trying to be used before it's been created as defined by 'iat' - * @throws ExpiredException Provided JWT has since expired, as defined by the 'exp' claim - * - * @uses jsonDecode - * @uses urlsafeB64Decode - */ - public static function decode( - string $jwt, - $keyOrKeyArray, - stdClass &$headers = null - ): stdClass { - // Validate JWT - $timestamp = \is_null(static::$timestamp) ? \time() : static::$timestamp; - - if (empty($keyOrKeyArray)) { - throw new InvalidArgumentException('Key may not be empty'); - } - $tks = \explode('.', $jwt); - if (\count($tks) !== 3) { - throw new UnexpectedValueException('Wrong number of segments'); - } - list($headb64, $bodyb64, $cryptob64) = $tks; - $headerRaw = static::urlsafeB64Decode($headb64); - if (null === ($header = static::jsonDecode($headerRaw))) { - throw new UnexpectedValueException('Invalid header encoding'); - } - if ($headers !== null) { - $headers = $header; - } - $payloadRaw = static::urlsafeB64Decode($bodyb64); - if (null === ($payload = static::jsonDecode($payloadRaw))) { - throw new UnexpectedValueException('Invalid claims encoding'); - } - if (\is_array($payload)) { - // prevent PHP Fatal Error in edge-cases when payload is empty array - $payload = (object) $payload; - } - if (!$payload instanceof stdClass) { - throw new UnexpectedValueException('Payload must be a JSON object'); - } - $sig = static::urlsafeB64Decode($cryptob64); - if (empty($header->alg)) { - throw new UnexpectedValueException('Empty algorithm'); - } - if (empty(static::$supported_algs[$header->alg])) { - throw new UnexpectedValueException('Algorithm not supported'); - } - - $key = self::getKey($keyOrKeyArray, property_exists($header, 'kid') ? $header->kid : null); - - // Check the algorithm - if (!self::constantTimeEquals($key->getAlgorithm(), $header->alg)) { - // See issue #351 - throw new UnexpectedValueException('Incorrect key for this algorithm'); - } - if (\in_array($header->alg, ['ES256', 'ES256K', 'ES384'], true)) { - // OpenSSL expects an ASN.1 DER sequence for ES256/ES256K/ES384 signatures - $sig = self::signatureToDER($sig); - } - if (!self::verify("{$headb64}.{$bodyb64}", $sig, $key->getKeyMaterial(), $header->alg)) { - throw new SignatureInvalidException('Signature verification failed'); - } - - // Check the nbf if it is defined. This is the time that the - // token can actually be used. If it's not yet that time, abort. - if (isset($payload->nbf) && floor($payload->nbf) > ($timestamp + static::$leeway)) { - $ex = new BeforeValidException( - 'Cannot handle token with nbf prior to ' . \date(DateTime::ISO8601, (int) $payload->nbf) - ); - $ex->setPayload($payload); - throw $ex; - } - - // Check that this token has been created before 'now'. This prevents - // using tokens that have been created for later use (and haven't - // correctly used the nbf claim). - if (!isset($payload->nbf) && isset($payload->iat) && floor($payload->iat) > ($timestamp + static::$leeway)) { - $ex = new BeforeValidException( - 'Cannot handle token with iat prior to ' . \date(DateTime::ISO8601, (int) $payload->iat) - ); - $ex->setPayload($payload); - throw $ex; - } - - // Check if this token has expired. - if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) { - $ex = new ExpiredException('Expired token'); - $ex->setPayload($payload); - throw $ex; - } - - return $payload; - } - - /** - * Converts and signs a PHP array into a JWT string. - * - * @param array $payload PHP array - * @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key. - * @param string $alg Supported algorithms are 'ES384','ES256', 'ES256K', 'HS256', - * 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512' - * @param string $keyId - * @param array $head An array with header elements to attach - * - * @return string A signed JWT - * - * @uses jsonEncode - * @uses urlsafeB64Encode - */ - public static function encode( - array $payload, - $key, - string $alg, - string $keyId = null, - array $head = null - ): string { - $header = ['typ' => 'JWT', 'alg' => $alg]; - if ($keyId !== null) { - $header['kid'] = $keyId; - } - if (isset($head) && \is_array($head)) { - $header = \array_merge($head, $header); - } - $segments = []; - $segments[] = static::urlsafeB64Encode((string) static::jsonEncode($header)); - $segments[] = static::urlsafeB64Encode((string) static::jsonEncode($payload)); - $signing_input = \implode('.', $segments); - - $signature = static::sign($signing_input, $key, $alg); - $segments[] = static::urlsafeB64Encode($signature); - - return \implode('.', $segments); - } - - /** - * Sign a string with a given key and algorithm. - * - * @param string $msg The message to sign - * @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key. - * @param string $alg Supported algorithms are 'EdDSA', 'ES384', 'ES256', 'ES256K', 'HS256', - * 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512' - * - * @return string An encrypted message - * - * @throws DomainException Unsupported algorithm or bad key was specified - */ - public static function sign( - string $msg, - $key, - string $alg - ): string { - if (empty(static::$supported_algs[$alg])) { - throw new DomainException('Algorithm not supported'); - } - list($function, $algorithm) = static::$supported_algs[$alg]; - switch ($function) { - case 'hash_hmac': - if (!\is_string($key)) { - throw new InvalidArgumentException('key must be a string when using hmac'); - } - return \hash_hmac($algorithm, $msg, $key, true); - case 'openssl': - $signature = ''; - $success = \openssl_sign($msg, $signature, $key, $algorithm); // @phpstan-ignore-line - if (!$success) { - throw new DomainException('OpenSSL unable to sign data'); - } - if ($alg === 'ES256' || $alg === 'ES256K') { - $signature = self::signatureFromDER($signature, 256); - } elseif ($alg === 'ES384') { - $signature = self::signatureFromDER($signature, 384); - } - return $signature; - case 'sodium_crypto': - if (!\function_exists('sodium_crypto_sign_detached')) { - throw new DomainException('libsodium is not available'); - } - if (!\is_string($key)) { - throw new InvalidArgumentException('key must be a string when using EdDSA'); - } - try { - // The last non-empty line is used as the key. - $lines = array_filter(explode("\n", $key)); - $key = base64_decode((string) end($lines)); - if (\strlen($key) === 0) { - throw new DomainException('Key cannot be empty string'); - } - return sodium_crypto_sign_detached($msg, $key); - } catch (Exception $e) { - throw new DomainException($e->getMessage(), 0, $e); - } - } - - throw new DomainException('Algorithm not supported'); - } - - /** - * Verify a signature with the message, key and method. Not all methods - * are symmetric, so we must have a separate verify and sign method. - * - * @param string $msg The original message (header and body) - * @param string $signature The original signature - * @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $keyMaterial For Ed*, ES*, HS*, a string key works. for RS*, must be an instance of OpenSSLAsymmetricKey - * @param string $alg The algorithm - * - * @return bool - * - * @throws DomainException Invalid Algorithm, bad key, or OpenSSL failure - */ - private static function verify( - string $msg, - string $signature, - $keyMaterial, - string $alg - ): bool { - if (empty(static::$supported_algs[$alg])) { - throw new DomainException('Algorithm not supported'); - } - - list($function, $algorithm) = static::$supported_algs[$alg]; - switch ($function) { - case 'openssl': - $success = \openssl_verify($msg, $signature, $keyMaterial, $algorithm); // @phpstan-ignore-line - if ($success === 1) { - return true; - } - if ($success === 0) { - return false; - } - // returns 1 on success, 0 on failure, -1 on error. - throw new DomainException( - 'OpenSSL error: ' . \openssl_error_string() - ); - case 'sodium_crypto': - if (!\function_exists('sodium_crypto_sign_verify_detached')) { - throw new DomainException('libsodium is not available'); - } - if (!\is_string($keyMaterial)) { - throw new InvalidArgumentException('key must be a string when using EdDSA'); - } - try { - // The last non-empty line is used as the key. - $lines = array_filter(explode("\n", $keyMaterial)); - $key = base64_decode((string) end($lines)); - if (\strlen($key) === 0) { - throw new DomainException('Key cannot be empty string'); - } - if (\strlen($signature) === 0) { - throw new DomainException('Signature cannot be empty string'); - } - return sodium_crypto_sign_verify_detached($signature, $msg, $key); - } catch (Exception $e) { - throw new DomainException($e->getMessage(), 0, $e); - } - case 'hash_hmac': - default: - if (!\is_string($keyMaterial)) { - throw new InvalidArgumentException('key must be a string when using hmac'); - } - $hash = \hash_hmac($algorithm, $msg, $keyMaterial, true); - return self::constantTimeEquals($hash, $signature); - } - } - - /** - * Decode a JSON string into a PHP object. - * - * @param string $input JSON string - * - * @return mixed The decoded JSON string - * - * @throws DomainException Provided string was invalid JSON - */ - public static function jsonDecode(string $input) - { - $obj = \json_decode($input, false, 512, JSON_BIGINT_AS_STRING); - - if ($errno = \json_last_error()) { - self::handleJsonError($errno); - } elseif ($obj === null && $input !== 'null') { - throw new DomainException('Null result with non-null input'); - } - return $obj; - } - - /** - * Encode a PHP array into a JSON string. - * - * @param array $input A PHP array - * - * @return string JSON representation of the PHP array - * - * @throws DomainException Provided object could not be encoded to valid JSON - */ - public static function jsonEncode(array $input): string - { - if (PHP_VERSION_ID >= 50400) { - $json = \json_encode($input, \JSON_UNESCAPED_SLASHES); - } else { - // PHP 5.3 only - $json = \json_encode($input); - } - if ($errno = \json_last_error()) { - self::handleJsonError($errno); - } elseif ($json === 'null') { - throw new DomainException('Null result with non-null input'); - } - if ($json === false) { - throw new DomainException('Provided object could not be encoded to valid JSON'); - } - return $json; - } - - /** - * Decode a string with URL-safe Base64. - * - * @param string $input A Base64 encoded string - * - * @return string A decoded string - * - * @throws InvalidArgumentException invalid base64 characters - */ - public static function urlsafeB64Decode(string $input): string - { - return \base64_decode(self::convertBase64UrlToBase64($input)); - } - - /** - * Convert a string in the base64url (URL-safe Base64) encoding to standard base64. - * - * @param string $input A Base64 encoded string with URL-safe characters (-_ and no padding) - * - * @return string A Base64 encoded string with standard characters (+/) and padding (=), when - * needed. - * - * @see https://www.rfc-editor.org/rfc/rfc4648 - */ - public static function convertBase64UrlToBase64(string $input): string - { - $remainder = \strlen($input) % 4; - if ($remainder) { - $padlen = 4 - $remainder; - $input .= \str_repeat('=', $padlen); - } - return \strtr($input, '-_', '+/'); - } - - /** - * Encode a string with URL-safe Base64. - * - * @param string $input The string you want encoded - * - * @return string The base64 encode of what you passed in - */ - public static function urlsafeB64Encode(string $input): string - { - return \str_replace('=', '', \strtr(\base64_encode($input), '+/', '-_')); - } - - - /** - * Determine if an algorithm has been provided for each Key - * - * @param Key|ArrayAccess|array $keyOrKeyArray - * @param string|null $kid - * - * @throws UnexpectedValueException - * - * @return Key - */ - private static function getKey( - $keyOrKeyArray, - ?string $kid - ): Key { - if ($keyOrKeyArray instanceof Key) { - return $keyOrKeyArray; - } - - if (empty($kid) && $kid !== '0') { - throw new UnexpectedValueException('"kid" empty, unable to lookup correct key'); - } - - if ($keyOrKeyArray instanceof CachedKeySet) { - // Skip "isset" check, as this will automatically refresh if not set - return $keyOrKeyArray[$kid]; - } - - if (!isset($keyOrKeyArray[$kid])) { - throw new UnexpectedValueException('"kid" invalid, unable to lookup correct key'); - } - - return $keyOrKeyArray[$kid]; - } - - /** - * @param string $left The string of known length to compare against - * @param string $right The user-supplied string - * @return bool - */ - public static function constantTimeEquals(string $left, string $right): bool - { - if (\function_exists('hash_equals')) { - return \hash_equals($left, $right); - } - $len = \min(self::safeStrlen($left), self::safeStrlen($right)); - - $status = 0; - for ($i = 0; $i < $len; $i++) { - $status |= (\ord($left[$i]) ^ \ord($right[$i])); - } - $status |= (self::safeStrlen($left) ^ self::safeStrlen($right)); - - return ($status === 0); - } - - /** - * Helper method to create a JSON error. - * - * @param int $errno An error number from json_last_error() - * - * @throws DomainException - * - * @return void - */ - private static function handleJsonError(int $errno): void - { - $messages = [ - JSON_ERROR_DEPTH => 'Maximum stack depth exceeded', - JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON', - JSON_ERROR_CTRL_CHAR => 'Unexpected control character found', - JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON', - JSON_ERROR_UTF8 => 'Malformed UTF-8 characters' //PHP >= 5.3.3 - ]; - throw new DomainException( - isset($messages[$errno]) - ? $messages[$errno] - : 'Unknown JSON error: ' . $errno - ); - } - - /** - * Get the number of bytes in cryptographic strings. - * - * @param string $str - * - * @return int - */ - private static function safeStrlen(string $str): int - { - if (\function_exists('mb_strlen')) { - return \mb_strlen($str, '8bit'); - } - return \strlen($str); - } - - /** - * Convert an ECDSA signature to an ASN.1 DER sequence - * - * @param string $sig The ECDSA signature to convert - * @return string The encoded DER object - */ - private static function signatureToDER(string $sig): string - { - // Separate the signature into r-value and s-value - $length = max(1, (int) (\strlen($sig) / 2)); - list($r, $s) = \str_split($sig, $length); - - // Trim leading zeros - $r = \ltrim($r, "\x00"); - $s = \ltrim($s, "\x00"); - - // Convert r-value and s-value from unsigned big-endian integers to - // signed two's complement - if (\ord($r[0]) > 0x7f) { - $r = "\x00" . $r; - } - if (\ord($s[0]) > 0x7f) { - $s = "\x00" . $s; - } - - return self::encodeDER( - self::ASN1_SEQUENCE, - self::encodeDER(self::ASN1_INTEGER, $r) . - self::encodeDER(self::ASN1_INTEGER, $s) - ); - } - - /** - * Encodes a value into a DER object. - * - * @param int $type DER tag - * @param string $value the value to encode - * - * @return string the encoded object - */ - private static function encodeDER(int $type, string $value): string - { - $tag_header = 0; - if ($type === self::ASN1_SEQUENCE) { - $tag_header |= 0x20; - } - - // Type - $der = \chr($tag_header | $type); - - // Length - $der .= \chr(\strlen($value)); - - return $der . $value; - } - - /** - * Encodes signature from a DER object. - * - * @param string $der binary signature in DER format - * @param int $keySize the number of bits in the key - * - * @return string the signature - */ - private static function signatureFromDER(string $der, int $keySize): string - { - // OpenSSL returns the ECDSA signatures as a binary ASN.1 DER SEQUENCE - list($offset, $_) = self::readDER($der); - list($offset, $r) = self::readDER($der, $offset); - list($offset, $s) = self::readDER($der, $offset); - - // Convert r-value and s-value from signed two's compliment to unsigned - // big-endian integers - $r = \ltrim($r, "\x00"); - $s = \ltrim($s, "\x00"); - - // Pad out r and s so that they are $keySize bits long - $r = \str_pad($r, $keySize / 8, "\x00", STR_PAD_LEFT); - $s = \str_pad($s, $keySize / 8, "\x00", STR_PAD_LEFT); - - return $r . $s; - } - - /** - * Reads binary DER-encoded data and decodes into a single object - * - * @param string $der the binary data in DER format - * @param int $offset the offset of the data stream containing the object - * to decode - * - * @return array{int, string|null} the new offset and the decoded object - */ - private static function readDER(string $der, int $offset = 0): array - { - $pos = $offset; - $size = \strlen($der); - $constructed = (\ord($der[$pos]) >> 5) & 0x01; - $type = \ord($der[$pos++]) & 0x1f; - - // Length - $len = \ord($der[$pos++]); - if ($len & 0x80) { - $n = $len & 0x1f; - $len = 0; - while ($n-- && $pos < $size) { - $len = ($len << 8) | \ord($der[$pos++]); - } - } - - // Value - if ($type === self::ASN1_BIT_STRING) { - $pos++; // Skip the first contents octet (padding indicator) - $data = \substr($der, $pos, $len - 1); - $pos += $len - 1; - } elseif (!$constructed) { - $data = \substr($der, $pos, $len); - $pos += $len; - } else { - $data = null; - } - - return [$pos, $data]; - } -} diff --git a/components/ILIAS/LTI/src/Firebase/JWTExceptionWithPayloadInterface.php b/components/ILIAS/LTI/src/Firebase/JWTExceptionWithPayloadInterface.php deleted file mode 100755 index 7b5e94ba0515..000000000000 --- a/components/ILIAS/LTI/src/Firebase/JWTExceptionWithPayloadInterface.php +++ /dev/null @@ -1,21 +0,0 @@ -keyMaterial = $keyMaterial; - $this->algorithm = $algorithm; - } - - /** - * Return the algorithm valid for this key - * - * @return string - */ - public function getAlgorithm(): string - { - return $this->algorithm; - } - - /** - * @return string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate - */ - public function getKeyMaterial() - { - return $this->keyMaterial; - } -} diff --git a/components/ILIAS/LTI/src/Firebase/LICENSE b/components/ILIAS/LTI/src/Firebase/LICENSE deleted file mode 100755 index 11c0146651c5..000000000000 --- a/components/ILIAS/LTI/src/Firebase/LICENSE +++ /dev/null @@ -1,30 +0,0 @@ -Copyright (c) 2011, Neuman Vong - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - - * Neither the name of the copyright holder nor the names of other - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/components/ILIAS/LTI/src/Firebase/README.md b/components/ILIAS/LTI/src/Firebase/README.md deleted file mode 100755 index 701de23a828f..000000000000 --- a/components/ILIAS/LTI/src/Firebase/README.md +++ /dev/null @@ -1,424 +0,0 @@ -![Build Status](https://github.com/firebase/php-jwt/actions/workflows/tests.yml/badge.svg) -[![Latest Stable Version](https://poser.pugx.org/firebase/php-jwt/v/stable)](https://packagist.org/packages/firebase/php-jwt) -[![Total Downloads](https://poser.pugx.org/firebase/php-jwt/downloads)](https://packagist.org/packages/firebase/php-jwt) -[![License](https://poser.pugx.org/firebase/php-jwt/license)](https://packagist.org/packages/firebase/php-jwt) - -PHP-JWT -======= -A simple library to encode and decode JSON Web Tokens (JWT) in PHP, conforming to [RFC 7519](https://tools.ietf.org/html/rfc7519). - -Installation ------------- - -Use composer to manage your dependencies and download PHP-JWT: - -```bash -composer require firebase/php-jwt -``` - -Optionally, install the `paragonie/sodium_compat` package from composer if your -php is < 7.2 or does not have libsodium installed: - -```bash -composer require paragonie/sodium_compat -``` - -Example -------- -```php -use Firebase\JWT\JWT; -use Firebase\JWT\Key; - -$key = 'example_key'; -$payload = [ - 'iss' => 'http://example.org', - 'aud' => 'http://example.com', - 'iat' => 1356999524, - 'nbf' => 1357000000 -]; - -/** - * IMPORTANT: - * You must specify supported algorithms for your application. See - * https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40 - * for a list of spec-compliant algorithms. - */ -$jwt = JWT::encode($payload, $key, 'HS256'); -$decoded = JWT::decode($jwt, new Key($key, 'HS256')); -print_r($decoded); - -// Pass a stdClass in as the third parameter to get the decoded header values -$decoded = JWT::decode($jwt, new Key($key, 'HS256'), $headers = new stdClass()); -print_r($headers); - -/* - NOTE: This will now be an object instead of an associative array. To get - an associative array, you will need to cast it as such: -*/ - -$decoded_array = (array) $decoded; - -/** - * You can add a leeway to account for when there is a clock skew times between - * the signing and verifying servers. It is recommended that this leeway should - * not be bigger than a few minutes. - * - * Source: http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html#nbfDef - */ -JWT::$leeway = 60; // $leeway in seconds -$decoded = JWT::decode($jwt, new Key($key, 'HS256')); -``` -Example encode/decode headers -------- -Decoding the JWT headers without verifying the JWT first is NOT recommended, and is not supported by -this library. This is because without verifying the JWT, the header values could have been tampered with. -Any value pulled from an unverified header should be treated as if it could be any string sent in from an -attacker. If this is something you still want to do in your application for whatever reason, it's possible to -decode the header values manually simply by calling `json_decode` and `base64_decode` on the JWT -header part: -```php -use Firebase\JWT\JWT; - -$key = 'example_key'; -$payload = [ - 'iss' => 'http://example.org', - 'aud' => 'http://example.com', - 'iat' => 1356999524, - 'nbf' => 1357000000 -]; - -$headers = [ - 'x-forwarded-for' => 'www.google.com' -]; - -// Encode headers in the JWT string -$jwt = JWT::encode($payload, $key, 'HS256', null, $headers); - -// Decode headers from the JWT string WITHOUT validation -// **IMPORTANT**: This operation is vulnerable to attacks, as the JWT has not yet been verified. -// These headers could be any value sent by an attacker. -list($headersB64, $payloadB64, $sig) = explode('.', $jwt); -$decoded = json_decode(base64_decode($headersB64), true); - -print_r($decoded); -``` -Example with RS256 (openssl) ----------------------------- -```php -use Firebase\JWT\JWT; -use Firebase\JWT\Key; - -$privateKey = << 'example.org', - 'aud' => 'example.com', - 'iat' => 1356999524, - 'nbf' => 1357000000 -]; - -$jwt = JWT::encode($payload, $privateKey, 'RS256'); -echo "Encode:\n" . print_r($jwt, true) . "\n"; - -$decoded = JWT::decode($jwt, new Key($publicKey, 'RS256')); - -/* - NOTE: This will now be an object instead of an associative array. To get - an associative array, you will need to cast it as such: -*/ - -$decoded_array = (array) $decoded; -echo "Decode:\n" . print_r($decoded_array, true) . "\n"; -``` - -Example with a passphrase -------------------------- - -```php -use Firebase\JWT\JWT; -use Firebase\JWT\Key; - -// Your passphrase -$passphrase = '[YOUR_PASSPHRASE]'; - -// Your private key file with passphrase -// Can be generated with "ssh-keygen -t rsa -m pem" -$privateKeyFile = '/path/to/key-with-passphrase.pem'; - -// Create a private key of type "resource" -$privateKey = openssl_pkey_get_private( - file_get_contents($privateKeyFile), - $passphrase -); - -$payload = [ - 'iss' => 'example.org', - 'aud' => 'example.com', - 'iat' => 1356999524, - 'nbf' => 1357000000 -]; - -$jwt = JWT::encode($payload, $privateKey, 'RS256'); -echo "Encode:\n" . print_r($jwt, true) . "\n"; - -// Get public key from the private key, or pull from from a file. -$publicKey = openssl_pkey_get_details($privateKey)['key']; - -$decoded = JWT::decode($jwt, new Key($publicKey, 'RS256')); -echo "Decode:\n" . print_r((array) $decoded, true) . "\n"; -``` - -Example with EdDSA (libsodium and Ed25519 signature) ----------------------------- -```php -use Firebase\JWT\JWT; -use Firebase\JWT\Key; - -// Public and private keys are expected to be Base64 encoded. The last -// non-empty line is used so that keys can be generated with -// sodium_crypto_sign_keypair(). The secret keys generated by other tools may -// need to be adjusted to match the input expected by libsodium. - -$keyPair = sodium_crypto_sign_keypair(); - -$privateKey = base64_encode(sodium_crypto_sign_secretkey($keyPair)); - -$publicKey = base64_encode(sodium_crypto_sign_publickey($keyPair)); - -$payload = [ - 'iss' => 'example.org', - 'aud' => 'example.com', - 'iat' => 1356999524, - 'nbf' => 1357000000 -]; - -$jwt = JWT::encode($payload, $privateKey, 'EdDSA'); -echo "Encode:\n" . print_r($jwt, true) . "\n"; - -$decoded = JWT::decode($jwt, new Key($publicKey, 'EdDSA')); -echo "Decode:\n" . print_r((array) $decoded, true) . "\n"; -```` - -Example with multiple keys --------------------------- -```php -use Firebase\JWT\JWT; -use Firebase\JWT\Key; - -// Example RSA keys from previous example -// $privateKey1 = '...'; -// $publicKey1 = '...'; - -// Example EdDSA keys from previous example -// $privateKey2 = '...'; -// $publicKey2 = '...'; - -$payload = [ - 'iss' => 'example.org', - 'aud' => 'example.com', - 'iat' => 1356999524, - 'nbf' => 1357000000 -]; - -$jwt1 = JWT::encode($payload, $privateKey1, 'RS256', 'kid1'); -$jwt2 = JWT::encode($payload, $privateKey2, 'EdDSA', 'kid2'); -echo "Encode 1:\n" . print_r($jwt1, true) . "\n"; -echo "Encode 2:\n" . print_r($jwt2, true) . "\n"; - -$keys = [ - 'kid1' => new Key($publicKey1, 'RS256'), - 'kid2' => new Key($publicKey2, 'EdDSA'), -]; - -$decoded1 = JWT::decode($jwt1, $keys); -$decoded2 = JWT::decode($jwt2, $keys); - -echo "Decode 1:\n" . print_r((array) $decoded1, true) . "\n"; -echo "Decode 2:\n" . print_r((array) $decoded2, true) . "\n"; -``` - -Using JWKs ----------- - -```php -use Firebase\JWT\JWK; -use Firebase\JWT\JWT; - -// Set of keys. The "keys" key is required. For example, the JSON response to -// this endpoint: https://www.gstatic.com/iap/verify/public_key-jwk -$jwks = ['keys' => []]; - -// JWK::parseKeySet($jwks) returns an associative array of **kid** to Firebase\JWT\Key -// objects. Pass this as the second parameter to JWT::decode. -JWT::decode($payload, JWK::parseKeySet($jwks)); -``` - -Using Cached Key Sets ---------------------- - -The `CachedKeySet` class can be used to fetch and cache JWKS (JSON Web Key Sets) from a public URI. -This has the following advantages: - -1. The results are cached for performance. -2. If an unrecognized key is requested, the cache is refreshed, to accomodate for key rotation. -3. If rate limiting is enabled, the JWKS URI will not make more than 10 requests a second. - -```php -use Firebase\JWT\CachedKeySet; -use Firebase\JWT\JWT; - -// The URI for the JWKS you wish to cache the results from -$jwksUri = 'https://www.gstatic.com/iap/verify/public_key-jwk'; - -// Create an HTTP client (can be any PSR-7 compatible HTTP client) -$httpClient = new GuzzleHttp\Client(); - -// Create an HTTP request factory (can be any PSR-17 compatible HTTP request factory) -$httpFactory = new GuzzleHttp\Psr\HttpFactory(); - -// Create a cache item pool (can be any PSR-6 compatible cache item pool) -$cacheItemPool = Phpfastcache\CacheManager::getInstance('files'); - -$keySet = new CachedKeySet( - $jwksUri, - $httpClient, - $httpFactory, - $cacheItemPool, - null, // $expiresAfter int seconds to set the JWKS to expire - true // $rateLimit true to enable rate limit of 10 RPS on lookup of invalid keys -); - -$jwt = 'eyJhbGci...'; // Some JWT signed by a key from the $jwkUri above -$decoded = JWT::decode($jwt, $keySet); -``` - -Miscellaneous -------------- - -#### Exception Handling - -When a call to `JWT::decode` is invalid, it will throw one of the following exceptions: - -```php -use Firebase\JWT\JWT; -use Firebase\JWT\SignatureInvalidException; -use Firebase\JWT\BeforeValidException; -use Firebase\JWT\ExpiredException; -use DomainException; -use InvalidArgumentException; -use UnexpectedValueException; - -try { - $decoded = JWT::decode($payload, $keys); -} catch (InvalidArgumentException $e) { - // provided key/key-array is empty or malformed. -} catch (DomainException $e) { - // provided algorithm is unsupported OR - // provided key is invalid OR - // unknown error thrown in openSSL or libsodium OR - // libsodium is required but not available. -} catch (SignatureInvalidException $e) { - // provided JWT signature verification failed. -} catch (BeforeValidException $e) { - // provided JWT is trying to be used before "nbf" claim OR - // provided JWT is trying to be used before "iat" claim. -} catch (ExpiredException $e) { - // provided JWT is trying to be used after "exp" claim. -} catch (UnexpectedValueException $e) { - // provided JWT is malformed OR - // provided JWT is missing an algorithm / using an unsupported algorithm OR - // provided JWT algorithm does not match provided key OR - // provided key ID in key/key-array is empty or invalid. -} -``` - -All exceptions in the `Firebase\JWT` namespace extend `UnexpectedValueException`, and can be simplified -like this: - -```php -use Firebase\JWT\JWT; -use UnexpectedValueException; -try { - $decoded = JWT::decode($payload, $keys); -} catch (LogicException $e) { - // errors having to do with environmental setup or malformed JWT Keys -} catch (UnexpectedValueException $e) { - // errors having to do with JWT signature and claims -} -``` - -#### Casting to array - -The return value of `JWT::decode` is the generic PHP object `stdClass`. If you'd like to handle with arrays -instead, you can do the following: - -```php -// return type is stdClass -$decoded = JWT::decode($payload, $keys); - -// cast to array -$decoded = json_decode(json_encode($decoded), true); -``` - -Tests ------ -Run the tests using phpunit: - -```bash -$ pear install PHPUnit -$ phpunit --configuration phpunit.xml.dist -PHPUnit 3.7.10 by Sebastian Bergmann. -..... -Time: 0 seconds, Memory: 2.50Mb -OK (5 tests, 5 assertions) -``` - -New Lines in private keys ------ - -If your private key contains `\n` characters, be sure to wrap it in double quotes `""` -and not single quotes `''` in order to properly interpret the escaped characters. - -License -------- -[3-Clause BSD](http://opensource.org/licenses/BSD-3-Clause). diff --git a/components/ILIAS/LTI/src/Firebase/SignatureInvalidException.php b/components/ILIAS/LTI/src/Firebase/SignatureInvalidException.php deleted file mode 100755 index d35dee9f185f..000000000000 --- a/components/ILIAS/LTI/src/Firebase/SignatureInvalidException.php +++ /dev/null @@ -1,7 +0,0 @@ - - * @copyright SPV Software Products - * @version 3.0.0 - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class AccessToken -{ - /** - * Access token string. - * - * @var string|null $token - */ - public ?string $token = null; - - /** - * Timestamp at which the token string expires. - * - * @var int|null $expires //UK: changed datetime to int - */ - public ?int $expires = null; - - /** - * Scope(s) for which the access token is valid. - * - * @var array $scopes - */ - public ?array $scopes = array(); - - /** - * Platform for this context. - * - * @var Platform|null $platform - */ - private ?Platform $platform = null; - - /** - * Timestamp for when the object was created. - * - * @var int|null $created - */ - public ?int $created = null; - - /** - * Timestamp for when the object was last updated. - * - * @var int|null $updated - */ - public ?int $updated = null; - - /** - * Class constructor. - * @param Platform $platform Platform - * @param array|null $scopes Scopes for which the access token is valid - * @param string|null $token Access token string - * @param int|null $expires Time in seconds after which the token string will expire //UK: changed datetime to int - */ - public function __construct(Platform $platform, array $scopes = null, string $token = null, int $expires = null) - { - $this->platform = $platform; - $this->scopes = $scopes; - if (!empty($token)) { - $this->token = $token; - } - if (!empty($expires)) { - $this->expires = time() + $expires; - } - $this->created = null; - $this->updated = null; - if (empty($scopes)) { - $this->load(); - } - } - - /** - * Get platform. - * - * @return Platform Platform object for this resource link. - */ - public function getPlatform(): ?Platform - { - return $this->platform; - } - - /** - * Load a nonce value from the database. - * - * @return bool True if the nonce value was successfully loaded - */ - public function load(): bool - { - return $this->platform->getDataConnector()->loadAccessToken($this); - } - - /** - * Save a nonce value in the database. - * - * @return bool True if the nonce value was successfully saved - */ - public function save(): bool - { - sort($this->scopes); - return $this->platform->getDataConnector()->saveAccessToken($this); - } - - /** - * Check if a valid access token exists for a specific scope (or any scope if none specified). - * @param string $scope Access scope - * @return bool True if there is an unexpired access token for specified scope - */ - public function hasScope(string $scope = ''): bool - { - if (substr($scope, -9) === '.readonly') { - $scope2 = substr($scope, 0, -9); - } else { - $scope2 = $scope; - } - return !empty($this->token) && (empty($this->expires) || ($this->expires > time())) && - (empty($scope) || empty($this->scopes) || (in_array($scope, $this->scopes) || in_array($scope2, $this->scopes))); - } - - /** - * Obtain a valid access token for a scope. - * @param string $scope Access scope - * @param bool $scopeOnly If true, a token is requested just for the specified scope - * @return AccessToken New access token - */ - public function get(string $scope = '', bool $scopeOnly = false): AccessToken - { - $url = $this->platform->accessTokenUrl; - if (!empty($url) && !empty(Tool::$defaultTool) && !empty(Tool::$defaultTool->rsaKey)) { - if ($scopeOnly) { - $scopesRequested = array($scope); - } else { - $scopesRequested = Tool::$defaultTool->requiredScopes; - if (substr($scope, -9) === '.readonly') { - $scope2 = substr($scope, 0, -9); - } else { - $scope2 = $scope; - } - if (!empty($scope) && !in_array($scope, $scopesRequested) && !in_array($scope2, $scopesRequested)) { - $scopesRequested[] = $scope; - } - } - if (!empty($scopesRequested)) { - $retry = false; - do { - $method = 'POST'; - $type = 'application/x-www-form-urlencoded'; - $body = array( - 'grant_type' => 'client_credentials', - 'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', - 'scope' => implode(' ', $scopesRequested) - ); - if (!empty(Tool::$defaultTool)) { - Tool::$defaultTool->platform = $this->platform; - $body = Tool::$defaultTool->signServiceRequest($url, $method, $type, $body); - } else { - $body = $this->platform->signServiceRequest($url, $method, $type, $body); - } - $http = new HttpMessage($url, $method, $body); - if ($http->send() && !empty($http->response)) { - $http->responseJson = json_decode($http->response); - if (!is_null($http->responseJson) && !empty($http->responseJson->access_token) && !empty($http->responseJson->expires_in)) { - if (isset($http->responseJson->scope)) { - $scopesAccepted = explode(' ', $http->responseJson->scope); - } else { - $scopesAccepted = $scopesRequested; - } - $this->scopes = $scopesAccepted; - $this->token = $http->responseJson->access_token; - $this->expires = time() + $http->responseJson->expires_in; - if (!$scopeOnly) { - $this->save(); - } - } - $retry = false; - } elseif ($retry) { - $retry = false; - } elseif (!empty($scope) && (count($scopesRequested) > 1)) { // Just ask for the single scope requested - $retry = true; - $scopesRequested = array($scope); - } - } while ($retry); - } - } else { - $this->scopes = null; - $this->token = null; - $this->expires = null; - $this->created = null; - $this->updated = null; - } - - return $this; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/ApiHook/ApiContext.php b/components/ILIAS/LTI/src/ToolProvider/ApiHook/ApiContext.php deleted file mode 100755 index 20cc187c4e39..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/ApiHook/ApiContext.php +++ /dev/null @@ -1,98 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class ApiContext -{ - /** - * Context object. - * - * @var \ILIAS\LTI\ToolProvider\Context|null $context //UK: changed from \ceLTIc\LTI\Context - */ - protected ?\ILIAS\LTI\ToolProvider\Context $context = null; - - /** - * Class constructor. - * @param \ILIAS\LTI\ToolProvider\Context $context //UK: changed from \ceLTIc\LTI\Context - */ - public function __construct(\ILIAS\LTI\ToolProvider\Context $context) - { - $this->context = $context; - } - - /** - * Check if the API hook has been configured. - * - * @return bool True if the API hook has been configured - */ - public function isConfigured(): bool - { - return true; - } - - /** - * Get course group sets and groups. - * - * @return bool True if the request was successful - */ - public function getGroups(): bool - { - return false; - } - - /** - * Get Memberships. - * @param bool $withGroups True is group information is to be requested as well - * @return mixed The array of UserResult objects if successful, otherwise false - */ - public function getMemberships(bool $withGroups) - { - return false; - } - - /** - * Get Tool Settings. - * @param int $mode Mode for request (optional, default is current level only) - * @param bool $simple True if all the simple media type is to be used (optional, default is true) - * @return mixed The array of settings if successful, otherwise false - */ - public function getToolSettings(int $mode = Service\ToolSettings::MODE_CURRENT_LEVEL, bool $simple = true) - { - return false; - } - - /** - * Perform a Tool Settings service request. - * @param array $settings An associative array of settings (optional, default is none) - * @return bool True if action was successful, otherwise false - */ - public function setToolSettings(array $settings = array()): bool - { - return false; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/ApiHook/ApiHook.php b/components/ILIAS/LTI/src/ToolProvider/ApiHook/ApiHook.php deleted file mode 100755 index 60c97764f7fc..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/ApiHook/ApiHook.php +++ /dev/null @@ -1,127 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -trait ApiHook -{ - /** - * User Id hook name. - */ - public static string $USER_ID_HOOK = "UserId"; - - /** - * Context Id hook name. - */ - public static string $CONTEXT_ID_HOOK = "ContextId"; - - /** - * Course Groups service hook name. - */ - public static string $GROUPS_SERVICE_HOOK = "Groups"; - - /** - * Memberships service hook name. - */ - public static string $MEMBERSHIPS_SERVICE_HOOK = "Memberships"; - - /** - * Outcomes service hook name. - */ - public static string $OUTCOMES_SERVICE_HOOK = "Outcomes"; - - /** - * Tool Settings service hook name. - */ - public static string $TOOL_SETTINGS_SERVICE_HOOK = "ToolSettings"; - - /** - * Access Token service hook name. - */ - public static string $ACCESS_TOKEN_SERVICE_HOOK = "AccessToken"; - - /** - * API hook class names. - */ - private static array $API_HOOKS = array(); - - /** - * Register the availability of an API hook. - * @param string $hookName Name of hook - * @param string $familyCode Family code for current platform - * @param string $className Name of implementing class - * @return void - */ - public static function registerApiHook(string $hookName, string $familyCode, string $className) - { - $objectClass = get_class(); - self::$API_HOOKS["{$objectClass}-{$hookName}-{$familyCode}"] = $className; - } - - /** - * Get the class name for an API hook. - * @param string $hookName Name of hook - * @param string $familyCode Family code for current platform - */ - private static function getApiHook(string $hookName, string $familyCode) - { - $class = self::class; - return self::$API_HOOKS["{$class}-{$hookName}-{$familyCode}"]; - } - - /** - * Check if an API hook is registered. - * @param string $hookName Name of hook - * @param string $familyCode Family code for current platform - * @return bool True if the API hook is registered - */ - private static function hasApiHook(string $hookName, string $familyCode): bool - { - $class = self::class; - return isset(self::$API_HOOKS["{$class}-{$hookName}-{$familyCode}"]); - } - - /** - * Check if an API hook is registered and configured. - * @param string $hookName Name of hook - * @param string $familyCode -// * @param Platform|Context|ResourceLink $sourceObject Source object for which hook is to be used - * //UK: added: |\ILIAS\LTI\ToolProvider\Tool - * @param \ILIAS\LTI\ToolProvider\Platform|\ILIAS\LTI\ToolProvider\Context|\ILIAS\LTI\ToolProvider\ResourceLink|\ILIAS\LTI\ToolProvider\Tool $sourceObject Source object for which hook is to be used - * @return bool True if the API hook is registered and configured - */ - private static function hasConfiguredApiHook(string $hookName, string $familyCode, $sourceObject): bool - { - $ok = false; - $class = self::class; - if (isset(self::$API_HOOKS["{$class}-{$hookName}-{$familyCode}"])) { - $className = self::$API_HOOKS["{$class}-{$hookName}-{$familyCode}"]; - $hook = new $className($sourceObject); - $ok = $hook->isConfigured(); - } - - return $ok; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/ApiHook/ApiPlatform.php b/components/ILIAS/LTI/src/ToolProvider/ApiHook/ApiPlatform.php deleted file mode 100755 index d1871fbb7d73..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/ApiHook/ApiPlatform.php +++ /dev/null @@ -1,73 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class ApiPlatform -{ - /** - * Platform object. - * - * @var \ILIAS\LTI\ToolProvider\Platform|null $platform //UK: changed from \ceLTIc\LTI\Platform - */ - protected ?\ILIAS\LTI\ToolProvider\Platform $platform = null; - - /** - * Class constructor. - * @param \ILIAS\LTI\ToolProvider\Platform $platform //UK: changed from \ceLTIc\LTI\Platform - */ - public function __construct(\ILIAS\LTI\ToolProvider\Platform $platform) - { - $this->platform = $platform; - } - - /** - * Check if the API hook has been configured. - */ - public function isConfigured(): bool - { - return true; - } - - /** - * Get Tool Settings. - * @param bool $simple True if all the simple media type is to be used (optional, default is true) - * @return mixed The array of settings if successful, otherwise false - */ - public function getToolSettings(bool $simple = true) - { - return false; - } - - /** - * Perform a Tool Settings service request. - * @param array $settings An associative array of settings (optional, default is none) - * @return bool True if action was successful, otherwise false - */ - public function setToolSettings(array $settings = array()): bool - { - return false; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/ApiHook/ApiResourceLink.php b/components/ILIAS/LTI/src/ToolProvider/ApiHook/ApiResourceLink.php deleted file mode 100755 index 2b95b3069990..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/ApiHook/ApiResourceLink.php +++ /dev/null @@ -1,98 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class ApiResourceLink -{ - /** - * Resource link object. - * - * @var \ILIAS\LTI\ToolProvider\ResourceLink|null $resourceLink //changed from: \ceLTIc\LTI\ResourceLink - */ - protected ?\ILIAS\LTI\ToolProvider\ResourceLink $resourceLink = null; - - /** - * Class constructor. - * @param \ILIAS\LTI\ToolProvider\ResourceLink $resourceLink //changed from: \ceLTIc\LTI\ResourceLink - */ - public function __construct(\ILIAS\LTI\ToolProvider\ResourceLink $resourceLink) - { - $this->resourceLink = $resourceLink; - } - - /** - * Check if the API hook has been configured. - */ - public function isConfigured(): bool - { - return true; - } - - /** - * Perform an Outcomes service request. - * @param int $action The action type constant - * @param \ILIAS\LTI\ToolProvider\Outcome $ltiOutcome Outcome object - * @param \ILIAS\LTI\ToolProvider\UserResult $userresult UserResult object - * @return string|bool Outcome value read or true if the request was successfully processed - */ - public function doOutcomesService(int $action, \ILIAS\LTI\ToolProvider\Outcome $ltiOutcome, \ILIAS\LTI\ToolProvider\UserResult $userresult) - { - return false; - } - - /** - * Get memberships. - * @param bool $withGroups True is group information is to be requested as well - * @return mixed Array of UserResult objects or False if the request was not successful - */ - public function getMemberships(bool $withGroups) - { - return false; - } - - /** - * Get Tool Settings. - * @param int $mode Mode for request (optional, default is current level only) - * @param bool $simple True if all the simple media type is to be used (optional, default is true) - * @return mixed The array of settings if successful, otherwise false - */ - public function getToolSettings(int $mode = Service\ToolSettings::MODE_CURRENT_LEVEL, bool $simple = true) - { - return false; - } - - /** - * Perform a Tool Settings service request. - * @param array $settings An associative array of settings (optional, default is none) - * @return bool True if action was successful, otherwise false - */ - public function setToolSettings(array $settings = array()): bool - { - return false; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/ApiHook/ApiTool.php b/components/ILIAS/LTI/src/ToolProvider/ApiHook/ApiTool.php deleted file mode 100755 index 2ed6945bea38..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/ApiHook/ApiTool.php +++ /dev/null @@ -1,73 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class ApiTool -{ - /** - * Tool object. - * - * @var \ILIAS\LTI\ToolProvider\Tool|null $tool //UK: changed from \ceLTIc\LTI\Tool - */ - protected ?\ILIAS\LTI\ToolProvider\Tool $tool = null; - - /** - * Class constructor. - * @param \ILIAS\LTI\ToolProvider\Tool|null $tool //UK: changed from \ceLTIc\LTI\Tool - */ - public function __construct(?\ILIAS\LTI\ToolProvider\Tool $tool) - { - $this->tool = $tool; - } - - /** - * Check if the API hook has been configured. - */ - public function isConfigured(): bool - { - return true; - } - - /** - * Get the User ID. - * - * @return string User ID value, or empty string if not available. - */ - public function getUserId(): string - { - return ''; - } - - /** - * Get the Context ID. - * - * @return string Context ID value, or empty string if not available. - */ - public function getContextId(): string - { - return ''; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/AssessmentControlAction.php b/components/ILIAS/LTI/src/ToolProvider/AssessmentControlAction.php deleted file mode 100755 index 9c1e1f750418..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/AssessmentControlAction.php +++ /dev/null @@ -1,139 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class AssessmentControlAction -{ - /** - * Pause action. - */ - public const ACTION_PAUSE = 'pause'; - - /** - * Pause action. - */ - public const ACTION_RESUME = 'resume'; - - /** - * Pause action. - */ - public const ACTION_TERMINATE = 'terminate'; - - /** - * Pause action. - */ - public const ACTION_UPDATE = 'update'; - - /** - * Pause action. - */ - public const ACTION_FLAG = 'flag'; - - /** - * Extra time. - * - * @var int|null $extraTime - */ - public ?int $extraTime = null; - - /** - * Reason code. - * - * @var string|null $code - */ - public ?string $code = null; - - /** - * Reason message. - * - * @var string|null $message - */ - public ?string $message = null; - - /** - * Action. - * - * @var string|null $action - */ - private ?string $action = null; - - /** - * Incident date value. - * - * @var int|null $date //UK: changed DateTime to int - */ - private ?int $date = null; - - /** - * Severity. - * - * @var float|null $severity - */ - private ?float $severity = null; - - /** - * Class constructor. - * @param string $action Action - * @param int $date Date/time of incident //UK: changed DateTime to int - * @param float $severity Severity of incident - */ - public function __construct(string $action, int $date, float $severity) - { - $this->action = $action; - $this->date = $date; - $this->severity = $severity; - } - - /** - * Get the action. - * - * @return string Action value - */ - public function getAction(): ?string - { - return $this->action; - } - - /** - * Get the incident date. - * - * @return int Incident date value //UK: changed DateTime to int - */ - public function getDate(): ?int - { - return $this->date; - } - - /** - * Get the severity. - * - * @return float Severity value - */ - public function getSeverity(): ?float - { - return $this->severity; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/Content/FileItem.php b/components/ILIAS/LTI/src/ToolProvider/Content/FileItem.php deleted file mode 100755 index 0662002ca2b9..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/Content/FileItem.php +++ /dev/null @@ -1,122 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class FileItem extends Item -{ - /** - * Copy advice for content-item. - * - * @var bool|null $copyAdvice - */ - private ?bool $copyAdvice = null; - - /** - * Expiry date/time for content-item. - * - * @var int|null $expiresAt - */ - private ?int $expiresAt = null; - - /** - * Class constructor. - * - * @param Placement[]|Placement $placementAdvices Array of Placement objects (or single placement object) for item (optional) - * @param string|null $id URL of content-item (optional) - */ - public function __construct($placementAdvices = null, ?string $id = null) - { - parent::__construct(Item::TYPE_FILE, $placementAdvices, $id); - } - - /** - * Set copy advice for the content-item. - * @param bool|null $copyAdvice Copy advice value - */ - public function setCopyAdvice(?bool $copyAdvice) - { - $this->copyAdvice = $copyAdvice; - } - - /** - * Set expiry date/time for the content-item. - * @param int|null $expiresAt Expiry date/time - */ - public function setExpiresAt(?int $expiresAt) - { - $this->expiresAt = $expiresAt; - } - - /** - * Wrap the content item to form an item complying with the application/vnd.ims.lti.v1.contentitems+json media type. - * - * @return object - */ - public function toJsonldObject(): object - { - $item = parent::toJsonldObject(); - if (!is_null($this->copyAdvice)) { - $item->copyAdvice = $this->copyAdvice; - } - if (!empty($this->expiresAt)) { - $item->expiresAt = gmdate('Y-m-d\TH:i:s\Z', $this->expiresAt); - } - - return $item; - } - - /** - * Wrap the content items to form a complete value for the https://purl.imsglobal.org/spec/lti-dl/claim/content_items claim. - * - * @return object - */ - public function toJsonObject(): object - { - $item = parent::toJsonObject(); - if (!empty($this->expiresAt)) { - $item->expiresAt = gmdate('Y-m-d\TH:i:s\Z', $this->expiresAt); - } - - return $item; - } - - /** - * Extract content-item details from its JSON representation. - * @param object $item A JSON object representing a file content-item - */ - protected function fromJsonObject(object $item) - { - parent::fromJsonObject($item); - foreach (get_object_vars($item) as $name => $value) { - switch ($name) { - case 'copyAdvice': - case 'expiresAt': - $this->{$name} = $item->{$name}; - break; - } - } - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/Content/Image.php b/components/ILIAS/LTI/src/ToolProvider/Content/Image.php deleted file mode 100755 index b8f6d3398c7a..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/Content/Image.php +++ /dev/null @@ -1,137 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class Image -{ - /** - * URL of image. - * - * @var string $url - */ - private ?string $url = null; - - /** - * Width of image. - * - * @var int|null $width - */ - private ?int $width = null; - - /** - * Height of image. - * - * @var int|null $height - */ - private ?int $height = null; - - /** - * Class constructor. - * @param string $url URL of image - * @param int|null $width Width of image in pixels (optional) - * @param int|null $height Height of image in pixels (optional) - */ - public function __construct(string $url, int $width = null, int $height = null) - { - $this->url = $url; - $this->height = $height; - $this->width = $width; - } - - /** - * Generate the JSON-LD object representation of the image. - * - * @return object - */ - public function toJsonldObject(): object - { - $image = new \stdClass(); - $image->{'@id'} = $this->url; - if (!is_null($this->width)) { - $image->width = $this->width; - } - if (!is_null($this->height)) { - $image->height = $this->height; - } - - return $image; - } - - /** - * Generate the JSON object representation of the image. - * - * @return string - */ - public function toJsonObject(): string - { - $image = new \stdClass(); - $image->url = $this->url; - if (!is_null($this->width)) { - $image->width = $this->width; - } - if (!is_null($this->height)) { - $image->height = $this->height; - } - - return $image; - } - - /** - * Generate an Image object from its JSON or JSON-LD representation. - * @param object $item A JSON or JSON-LD object representing a content-item - * @return Image|null The Image object - */ - public static function fromJsonObject(object $item): ?Image - { - $obj = null; - $width = null; - $height = null; - if (is_object($item)) { - $url = null; - foreach (get_object_vars($item) as $name => $value) { - switch ($name) { - case '@id': - case 'url': - $url = $item->{$name}; - break; - case 'width': - $width = $item->width; - break; - case 'height': - $height = $item->height; - break; - } - } - } else { - $url = $item; - } - if ($url) { - $obj = new Image($url, $height, $width); - } - - return $obj; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/Content/Item.php b/components/ILIAS/LTI/src/ToolProvider/Content/Item.php deleted file mode 100755 index 06b3b957939b..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/Content/Item.php +++ /dev/null @@ -1,541 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ - -class Item -{ - /** - * Type for link content-item. - */ - public const TYPE_LINK = 'link'; - - /** - * Type for LTI link content-item. - */ - public const TYPE_LTI_LINK = 'ltiResourceLink'; - - /** - * Type for LTI assignment content-item. - */ - public const TYPE_LTI_ASSIGNMENT = 'ltiAssignment'; - - /** - * Type for file content-item. - */ - public const TYPE_FILE = 'file'; - - /** - * Type for HTML content-item. - */ - public const TYPE_HTML = 'html'; - - /** - * Type for image content-item. - */ - public const TYPE_IMAGE = 'image'; - - /** - * Media type for LTI launch links. - */ - public const LTI_LINK_MEDIA_TYPE = 'application/vnd.ims.lti.v1.ltilink'; - - /** - * Media type for LTI assignment links. - */ - public const LTI_ASSIGNMENT_MEDIA_TYPE = 'application/vnd.ims.lti.v1.ltiassignment'; - - /** - * Type of content-item. - * - * @var string|null $type - */ - private ?string $type = null; - - /** - * ID of content-item. - * - * @var string|null $id - */ -// private ?string $id = null; //UK: changed to public - public ?string $id = null; - - /** - * Array of placement objects for content-item. - * - * @var array $placements - */ - private array $placements = array(); - - /** - * URL of content-item. - * - * @var string|null $url - */ -// private ?string $url = null; //UK: changed to public - public ?string $url = null; - - /** - * Media type of content-item. - * - * @var string|null $mediaType - */ - private ?string $mediaType = null; - - /** - * Title of content-item. - * - * @var string|null $title - */ - private ?string $title = null; - - /** - * Description of content-item. - * - * @var string|null $text - */ - private ?string $text = null; - - /** - * HTML to be embedded. - * - * @var string|null $html - */ - private ?string $html = null; - - /** - * Icon image object for content-item. - * - * @var Image|null $icon - */ - private ?Image $icon = null; - - /** - * Thumbnail image object for content-item. - * - * @var Image|null $thumbnail - */ - private ?Image $thumbnail = null; - - /** - * Hide the item from learners by default? - * - * @var bool $hideOnCreate - */ - private ?bool $hideOnCreate = null; - - /** - * Class constructor. - * @param string $type Class type of content-item - * @param Placement[]|Placement $placementAdvices Array of Placement objects (or single placement object) for item (optional) - * @param string|null $id URL of content-item (optional) - */ - public function __construct(string $type, $placementAdvices = null, string $id = null) - { - $this->type = $type; - if (!empty($placementAdvices)) { - if (!is_array($placementAdvices)) { - $placementAdvices = array($placementAdvices); - } - foreach ($placementAdvices as $placementAdvice) { - $this->placements[$placementAdvice->documentTarget] = $placementAdvice; - } - } - $this->id = $id; - } - - /** - * Set a URL value for the content-item. - * @param string $url URL value - */ - public function setUrl(string $url) - { - $this->url = $url; - } - - /** - * Set a media type value for the content-item. - * @param string $mediaType Media type value - */ - public function setMediaType(string $mediaType) - { - $this->mediaType = $mediaType; - } - - /** - * Set a title value for the content-item. - * @param string $title Title value - */ - public function setTitle(string $title) - { - $this->title = $title; - } - - /** - * Set a link text value for the content-item. - * @param string $text Link text value - */ - public function setText(string $text) - { - $this->text = $text; - } - - /** - * Set an HTML embed value for the content-item. - * @param string $html HTML text value - */ - public function setHtml(string $html) - { - $this->html = $html; - } - - /** - * Add a placement for the content-item. - * @param Placement $placementAdvice Placement advice object - */ - public function addPlacementAdvice(Placement $placementAdvice) - { - if (!empty($placementAdvice)) { - $this->placements[$placementAdvice->documentTarget] = $placementAdvice; - } - } - - /** - * Set an icon image for the content-item. - * @param Image $icon Icon image object - */ - public function setIcon(Image $icon) - { - $this->icon = $icon; - } - - /** - * Set a thumbnail image for the content-item. - * @param Image $thumbnail Thumbnail image object - */ - public function setThumbnail(Image $thumbnail) - { - $this->thumbnail = $thumbnail; - } - - /** - * Set whether the content-item should be hidden from learners by default. - * @param bool|null $hideOnCreate True if the item should be hidden from learners - */ - public function setHideOnCreate(?bool $hideOnCreate) - { - $this->hideOnCreate = $hideOnCreate; - } - - /** - * Wrap the content items to form a complete application/vnd.ims.lti.v1.contentitems+json media type instance. - * @param mixed $items An array of content items or a single item - * @param string $ltiVersion LTI version in use - * @return string - */ - public static function toJson($items, string $ltiVersion = Util::LTI_VERSION1): string - { - if (!is_array($items)) { - $items = array($items); - } - if ($ltiVersion !== Util::LTI_VERSION1P3) { - $obj = new \stdClass(); - $obj->{'@context'} = 'http://purl.imsglobal.org/ctx/lti/v1/ContentItem'; - $obj->{'@graph'} = array(); - foreach ($items as $item) { - $obj->{'@graph'}[] = $item->toJsonldObject(); - } - } else { - $obj = array(); - foreach ($items as $item) { - $obj[] = $item->toJsonObject(); - } - } - - return json_encode($obj); - } - - /** - * Generate an array of Item objects from their JSON representation. - * @param object $items A JSON object representing Content-Items - * @return array Array of Item objects - */ - public static function fromJson(object $items): array - { - $isJsonLd = isset($items->{'@graph'}); - if ($isJsonLd) { - $items = $items->{'@graph'}; - } - if (!is_array($items)) { - $items = array($items); - } - $objs = array(); - foreach ($items as $item) { - $obj = self::fromJsonItem($item); - if (!empty($obj)) { - $objs[] = $obj; - } - } - - return $objs; - } - - /** - * Wrap the content item to form an item complying with the application/vnd.ims.lti.v1.contentitems+json media type. - * - * @return object - */ - protected function toJsonldObject(): object - { - $item = new \stdClass(); - if (!empty($this->id)) { - $item->{'@id'} = $this->id; - } - if (!empty($this->type)) { - if (($this->type === self::TYPE_LTI_LINK) || ($this->type === self::TYPE_LTI_ASSIGNMENT)) { - $item->{'@type'} = 'LtiLinkItem'; - } elseif ($this->type === self::TYPE_FILE) { - $item->{'@type'} = 'FileItem'; - } else { - $item->{'@type'} = 'ContentItem'; - } - } else { - $item->{'@type'} = 'ContentItem'; - } - if (!empty($this->title)) { - $item->title = $this->title; - } - if (!empty($this->text)) { - $item->text = $this->text; - } elseif (!empty($this->html)) { - $item->text = $this->html; - } - if (!empty($this->url)) { - $item->url = $this->url; - } - if (!empty($this->mediaType)) { - $item->mediaType = $this->mediaType; - } - if (!empty($this->placements)) { - $placementAdvice = new \stdClass(); - $placementAdvices = array(); - foreach ($this->placements as $placement) { - $obj = $placement->toJsonldObject(); - if (!empty($obj)) { - if (!empty($placement->documentTarget)) { - $placementAdvices[] = $placement->documentTarget; - } - $placementAdvice = (object) array_merge((array) $placementAdvice, (array) $obj); - } - } - if (!empty($placementAdvice)) { - $item->placementAdvice = $placementAdvice; - if (!empty($placementAdvices)) { - $item->placementAdvice->presentationDocumentTarget = implode(',', $placementAdvices); - } - } - } - if (!empty($this->icon)) { - $item->icon = $this->icon->toJsonldObject(); - } - if (!empty($this->thumbnail)) { - $item->thumbnail = $this->thumbnail->toJsonldObject(); - } - if (!is_null($this->hideOnCreate)) { - $item->hideOnCreate = $this->hideOnCreate; - } - - return $item; - } - - /** - * Wrap the content items to form a complete value for the https://purl.imsglobal.org/spec/lti-dl/claim/content_items claim. - * - * @return object - */ - protected function toJsonObject(): object - { - $item = new \stdClass(); - switch ($this->type) { - case 'LtiLinkItem': - $item->type = self::TYPE_LTI_LINK; - break; - case 'FileItem': - $item->type = self::TYPE_FILE; - break; - case 'ContentItem': - if (empty($this->url)) { - $item->type = self::TYPE_HTML; - } elseif (!empty($this->mediaType) && (strpos($this->mediaType, 'image') === 0)) { - $item->type = self::TYPE_IMAGE; - } else { - $item->type = self::TYPE_LINK; - } - break; - default: - $item->type = $this->type; - break; - } - if (!empty($this->title)) { - $item->title = $this->title; - } - if (!empty($this->text)) { - $item->text = Util::stripHtml($this->text); - } - if (!empty($this->html)) { - $item->html = $this->html; - } - if (!empty($this->url)) { - $item->url = $this->url; - } - foreach ($this->placements as $type => $placement) { - switch ($type) { - case Placement::TYPE_EMBED: - case Placement::TYPE_IFRAME: - case Placement::TYPE_WINDOW: - case Placement::TYPE_FRAME: - $obj = $placement->toJsonObject(); - break; - default: - $obj = null; - break; - } - if (!empty($obj)) { - $item->{$type} = $obj; - } - } - if (!empty($this->icon)) { - $item->icon = $this->icon->toJsonObject(); - } - if (!empty($this->thumbnail)) { - $item->thumbnail = $this->thumbnail->toJsonObject(); - } - if (!is_null($this->hideOnCreate)) { - $item->hideOnCreate = $this->hideOnCreate; - } - - return $item; - } - - /** - * Generate an Item object from its JSON or JSON-LD representation. - * @param object $item A JSON or JSON-LD object representing a content-item - * @return Item|LtiLinkItem|FileItem The content-item object - */ - public static function fromJsonItem(object $item) - { - $obj = null; - $placement = null; - if (isset($item->{'@type'})) { - if (isset($item->presentationDocumentTarget)) { - $placement = Placement::fromJsonObject($item, $item->presentationDocumentTarget); - } - switch ($item->{'@type'}) { - case 'ContentItem': - $obj = new Item('ContentItem', $placement); - break; - case 'LtiLinkItem': - $obj = new LtiLinkItem($placement); - break; - case 'FileItem': - $obj = new FileItem($placement); - break; - } - } elseif (isset($item->type)) { - $placements = array(); - $placement = Placement::fromJsonObject($item, 'embed'); - if (!empty($placement)) { - $placements[] = $placement; - } - $placement = Placement::fromJsonObject($item, 'iframe'); - if (!empty($placement)) { - $placements[] = $placement; - } - $placement = Placement::fromJsonObject($item, 'window'); - if (!empty($placement)) { - $placements[] = $placement; - } - switch ($item->type) { - case self::TYPE_LINK: - case self::TYPE_HTML: - case self::TYPE_IMAGE: - $obj = new Item($item->type, $placements); - break; - case self::TYPE_LTI_LINK: - $obj = new LtiLinkItem($placements); - break; - case self::TYPE_LTI_ASSIGNMENT: - $obj = new LtiAssignmentItem($placements); - break; - case self::TYPE_FILE: - $obj = new FileItem($placements); - break; - } - } - if (!empty($obj)) { - $obj->fromJsonObject($item); - } - - return $obj; - } - - /** - * Extract content-item details from its JSON representation. - * @param object $item A JSON object representing a content-item - */ - protected function fromJsonObject(object $item) - { - if (isset($item->{'@id'})) { - $this->id = $item->{'@id'}; - } - foreach (get_object_vars($item) as $name => $value) { - switch ($name) { - case 'title': - case 'text': - case 'html': - case 'url': - case 'mediaType': - case 'hideOnCreate': - $this->{$name} = $item->{$name}; - break; - case 'placementAdvice': - $this->addPlacementAdvice(Placement::fromJsonObject($item)); - break; - case 'embed': - case 'window': - case 'iframe': - $this->addPlacementAdvice(Placement::fromJsonObject($item, $name)); - break; - case 'icon': - case 'thumbnail': - $this->{$name} = Image::fromJsonObject($item->{$name}); - break; - } - } - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/Content/LineItem.php b/components/ILIAS/LTI/src/ToolProvider/Content/LineItem.php deleted file mode 100755 index 1b65c8f0133a..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/Content/LineItem.php +++ /dev/null @@ -1,174 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class LineItem -{ - /** - * Label of line-item. - * - * @var string|null $label - */ - private ?string $label = null; - - /** - * Maximum score of line-item. - * - * @var int|null $scoreMaximum - */ - private ?int $scoreMaximum = null; - - /** - * Resource ID associated with line-item. - * - * @var string|null $resourceId - */ - private ?string $resourceId = null; - - /** - * Tag of line-item. - * - * @var string|null $tag - */ - private ?string $tag = null; - - /** - * Class constructor. - * @param string $label Label - * @param int $scoreMaximum Maximum score - * @param string|null $resourceId Resource ID (optional) - * @param string|null $tag Tag (optional) - */ - public function __construct(string $label, int $scoreMaximum, string $resourceId = null, string $tag = null) - { - $this->label = $label; - $this->scoreMaximum = $scoreMaximum; - $this->resourceId = $resourceId; - $this->tag = $tag; - } - - /** - * Generate the JSON-LD object representation of the line-item. - * - * @return object - */ - public function toJsonldObject(): object - { - $lineItem = new \stdClass(); - - $lineItem->{'@type'} = 'LineItem'; - $lineItem->label = $this->label; - $lineItem->reportingMethod = 'http://purl.imsglobal.org/ctx/lis/v2p1/Result#normalScore'; - if (!empty($this->resourceId)) { - $lineItem->assignedActivity = new \stdClass(); - $lineItem->assignedActivity->activityId = $this->resourceId; - } - $lineItem->scoreConstraints = new \stdClass(); - $lineItem->scoreConstraints->{'@type'} = 'NumericLimits'; - $lineItem->scoreConstraints->normalMaximum = $this->scoreMaximum; - - return $lineItem; - } - - /** - * Generate the JSON object representation of the line-item. - * - * @return object - */ - public function toJsonObject(): object - { - $lineItem = new \stdClass(); - - $lineItem->label = $this->label; - $lineItem->scoreMaximum = $this->scoreMaximum; - if (!empty($this->resourceId)) { - $lineItem->resourceId = $this->resourceId; - } - if (!empty($this->tag)) { - $lineItem->tag = $this->tag; - } - - return $lineItem; - } - - /** - * Generate a LineItem object from its JSON or JSON-LD representation. - * @param object $item A JSON or JSON-LD object representing a content-item - * @return LineItem|null The LineItem object - */ - public static function fromJsonObject(object $item): ?LineItem - { - $obj = null; - $label = null; - $reportingMethod = null; - $scoreMaximum = null; - $activityId = null; - $tag = null; - $available = null; - $submission = null; - foreach (get_object_vars($item) as $name => $value) { - switch ($name) { - case 'label': - $label = $item->label; - break; - case 'reportingMethod': - $reportingMethod = $item->reportingMethod; - break; - case 'scoreConstraints': - $scoreConstraints = $item->scoreConstraints; - break; - case 'scoreMaximum': - $scoreMaximum = $item->scoreMaximum; - break; - case 'assignedActivity': - if (isset($item->assignedActivity->activityId)) { - $activityId = $item->assignedActivity->activityId; - } - break; - case 'resourceId': - $activityId = $item->resourceId; - break; - case 'tag': - $tag = $item->tag; - break; - } - } - if (is_null($scoreMaximum) && $label && $reportingMethod && $scoreConstraints) { - foreach (get_object_vars($scoreConstraints) as $name => $value) { - $method = str_replace('Maximum', 'Score', $name); - if (substr($reportingMethod, -strlen($method)) === $method) { - $scoreMaximum = $value; - break; - } - } - } - if (!is_null($scoreMaximum)) { - $obj = new LineItem($label, $scoreMaximum, $activityId, $tag); - } - - return $obj; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/Content/LtiAssignmentItem.php b/components/ILIAS/LTI/src/ToolProvider/Content/LtiAssignmentItem.php deleted file mode 100755 index cf62c93df87b..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/Content/LtiAssignmentItem.php +++ /dev/null @@ -1,41 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class LtiAssignmentItem extends LtiLinkItem -{ - /** - * Class constructor. - * - * @param Placement[]|Placement $placementAdvices Array of Placement objects (or single placement object) for item (optional) - * @param string $id URL of content-item (optional) - */ - public function __construct($placementAdvices = null, ?string $id = null) - { - Item::__construct(Item::TYPE_LTI_ASSIGNMENT, $placementAdvices, $id); - $this->setMediaType(Item::LTI_ASSIGNMENT_MEDIA_TYPE); - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/Content/LtiLinkItem.php b/components/ILIAS/LTI/src/ToolProvider/Content/LtiLinkItem.php deleted file mode 100755 index f99fd4511dc3..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/Content/LtiLinkItem.php +++ /dev/null @@ -1,214 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class LtiLinkItem extends Item -{ - /** - * Custom parameters for content-item. - * - * @var array $custom - */ - private array $custom = array(); - - /** - * Line-item object for content-item. - * - * @var LineItem|null $lineItem - */ - private ?LineItem $lineItem = null; - - /** - * Time period for availability. - * - * @var string|null $available - */ - private ?string $available = null; - - /** - * Time period for submission. - * - * @var string|null $submission - */ - private ?string $submission = null; - - /** - * Do not allow the item to be updated? - * - * @var bool $noUpdate - */ - private ?bool $noUpdate = null; - - /** - * Class constructor. - * - * @param Placement[]|Placement $placementAdvices Array of Placement objects (or single placement object) for item (optional) - * @param string $id URL of content-item (optional) - */ - public function __construct($placementAdvices = null, $id = null) - { - parent::__construct(Item::TYPE_LTI_LINK, $placementAdvices, $id); - $this->setMediaType(Item::LTI_LINK_MEDIA_TYPE); - } - - /** - * Add a custom parameter for the content-item. - * @param string $name Name of parameter - * @param string|null $value Value of parameter - */ - public function addCustom(string $name, ?string $value = null) - { - if (!empty($name)) { - if (!empty($value)) { - $this->custom[$name] = $value; - } else { - reset($this->custom[$name]); - } - } - } - - /** - * Set a line-item for the content-item. - * @param LineItem $lineItem Line-item - */ - public function setLineItem(LineItem $lineItem) - { - $this->lineItem = $lineItem; - } - - /** - * Set an availability time period for the content-item. - * @param string|TimePeriod $available Time period //UK: check changed from TimePeriod to string - */ - public function setAvailable($available) - { - $this->available = $available; - } - - /** - * Set a submission time period for the content-item. - * @param string|TimePeriod $submission Time period //UK: check changed from TimePeriod to string - */ - public function setSubmission($submission) - { - $this->submission = $submission; - } - - /** - * Set whether the content-item should not be allowed to be updated. - * @param bool|null $noUpdate True if the item should not be updatable - */ - public function setNoUpdate(?bool $noUpdate) - { - $this->noUpdate = $noUpdate; - } - - /** - * Wrap the content item to form an item complying with the application/vnd.ims.lti.v1.contentitems+json media type. - * - * @return object - */ - public function toJsonldObject(): object - { - $item = parent::toJsonldObject(); - if (!empty($this->lineItem)) { - $item->lineItem = $this->lineItem->toJsonldObject(); - } - if (!is_null($this->noUpdate)) { - $item->noUpdate = $this->noUpdate; - } - //ToDo: Check -// if (!is_null($this->available)) { -// $item->available = $this->available->toJsonldObject(); -// } -// if (!is_null($this->submission)) { -// $item->submission = $this->submission->toJsonldObject(); -// } - if (!empty($this->custom)) { - $item->custom = $this->custom; - } - - return $item; - } - - /** - * Wrap the content items to form a complete value for the https://purl.imsglobal.org/spec/lti-dl/claim/content_items claim. - * - * @return object - */ - public function toJsonObject(): object - { - $item = parent::toJsonObject(); - if (!empty($this->lineItem)) { - $item->lineItem = $this->lineItem->toJsonObject(); - } - if (!is_null($this->noUpdate)) { - $item->noUpdate = $this->noUpdate; - } - //ToDo: Check -// if (!is_null($this->available)) { -// $item->available = $this->available->toJsonObject(); -// } -// if (!is_null($this->submission)) { -// $item->submission = $this->submission->toJsonObject(); -// } - if (!empty($this->custom)) { - $item->custom = $this->custom; - } - - return $item; - } - - /** - * Extract content-item details from its JSON representation. - * @param object $item A JSON object representing an LTI link content-item - */ - protected function fromJsonObject(object $item) - { - parent::fromJsonObject($item); - foreach (get_object_vars($item) as $name => $value) { - switch ($name) { - case 'custom': - foreach ($item->custom as $paramName => $paramValue) { - $this->addCustom($paramName, $paramValue); - } - break; - case 'lineItem': - $this->setLineItem(LineItem::fromJsonObject($item->lineItem)); - break; - case 'available': - $this->setAvailable(TimePeriod::fromJsonObject($item->available)); - break; - case 'submission': - $this->setSubmission(TimePeriod::fromJsonObject($item->submission)); - break; - case 'noUpdate': - $this->noUpdate = $item->noUpdate; - break; - } - } - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/Content/Placement.php b/components/ILIAS/LTI/src/ToolProvider/Content/Placement.php deleted file mode 100755 index eb2e89c6961c..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/Content/Placement.php +++ /dev/null @@ -1,285 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class Placement -{ - /** - * Embed placement type. - */ - public const TYPE_EMBED = 'embed'; - - /** - * iFrame placement type. - */ - public const TYPE_IFRAME = 'iframe'; - - /** - * Frame placement type. - */ - public const TYPE_FRAME = 'frame'; - - /** - * Window placement type. - */ - public const TYPE_WINDOW = 'window'; - - /** - * Popup placement type. - */ - public const TYPE_POPUP = 'popup'; - - /** - * Overlay placement type. - */ - public const TYPE_OVERLAY = 'overlay'; - - /** - * Location to open content in. - * - * @var string|null $documentTarget - */ - public ?string $documentTarget = null; - - /** - * Name of window target. - * - * @var string|null $windowTarget - */ - private ?string $windowTarget = null; - - /** - * Comma-separated list of window features. - * - * @var string|null $windowFeatures - */ - private ?string $windowFeatures = null; - - /** - * URL of iframe src. - * - * @var string|null $url - */ - private ?string $url = null; - - /** - * Width of item location. - * - * @var int|null $displayWidth - */ - private ?int $displayWidth = null; - - /** - * Height of item location. - * - * @var int|null $displayHeight - */ - private ?int $displayHeight = null; - - /** - * HTML to be embedded. - * - * @var string|null $html - */ - private ?string $html = null; - - /** - * Class constructor. - * @param string $documentTarget Location to open content in - * @param int|null $displayWidth Width of item location (optional) - * @param int|null $displayHeight Height of item location (optional) - * @param string|null $windowTarget Name of window target (optional) - * @param string|null $windowFeatures List of window features (optional) - * @param string|null $url URL for iframe src (optional) - * @param string|null $html HTML to be embedded (optional) - */ - public function __construct( - string $documentTarget, - ?int $displayWidth = null, - ?int $displayHeight = null, - ?string $windowTarget = null, - ?string $windowFeatures = null, - string $url = null, - ?string $html = null - ) { - $this->documentTarget = $documentTarget; - $this->displayWidth = $displayWidth; - $this->displayHeight = $displayHeight; - $this->windowTarget = $windowTarget; - $this->windowFeatures = $windowFeatures; - $this->url = $url; - $this->html = $html; - } - - /** - * Generate the JSON-LD object representation of the placement. - * - * @return object - */ - public function toJsonldObject(): object - { - if (!empty($this->documentTarget)) { - $placement = new \stdClass(); - $placement->presentationDocumentTarget = $this->documentTarget; - if (!is_null($this->displayHeight)) { - $placement->displayHeight = $this->displayHeight; - } - if (!is_null($this->displayWidth)) { - $placement->displayWidth = $this->displayWidth; - } - if (!empty($this->windowTarget)) { - $placement->windowTarget = $this->windowTarget; - } - } else { - $placement = null; - } - - return $placement; - } - - /** - * Generate the JSON object representation of the placement. - * - * @return object - */ - public function toJsonObject(): object - { - if (!empty($this->documentTarget)) { - $placement = new \stdClass(); - switch ($this->documentTarget) { - case self::TYPE_IFRAME: - if (!empty($this->url)) { - $placement->src = $this->url; - } - if (!is_null($this->displayWidth)) { - $placement->width = $this->displayWidth; - } - if (!is_null($this->displayHeight)) { - $placement->height = $this->displayHeight; - } - break; - case self::TYPE_WINDOW: - if (!is_null($this->displayWidth)) { - $placement->width = $this->displayWidth; - } - if (!is_null($this->displayHeight)) { - $placement->height = $this->displayHeight; - } - if (!is_null($this->windowTarget)) { - $placement->targetName = $this->windowTarget; - } - if (!is_null($this->windowFeatures)) { - $placement->windowFeatures = $this->windowFeatures; - } - break; - case self::TYPE_EMBED: - if (!empty($this->html)) { - $placement->html = $this->html; - } - break; - } - } else { - $placement = null; - } - - return $placement; - } - - /** - * Generate the Placement object from an item. - * @param object $item JSON object of item - * @param string|null $documentTarget Destination of placement to be generated (optional) - * @return Placement - */ - public static function fromJsonObject(object $item, string $documentTarget = null): ?Placement - { - $obj = null; - $displayWidth = null; - $displayHeight = null; - $windowTarget = null; - $windowFeatures = null; - $url = null; - $html = null; - if (isset($item->{'@type'})) { // Version 1 - if (empty($documentTarget) && isset($item->placementAdvice)) { - if (isset($item->placementAdvice->presentationDocumentTarget)) { - $documentTarget = $item->placementAdvice->presentationDocumentTarget; - } - } - if (!empty($documentTarget) && isset($item->placementAdvice)) { - if (isset($item->placementAdvice->displayWidth)) { - $displayWidth = $item->placementAdvice->displayWidth; - } - if (isset($item->placementAdvice->displayHeight)) { - $displayHeight = $item->placementAdvice->displayHeight; - } - if (isset($item->placementAdvice->windowTarget)) { - $windowTarget = $item->placementAdvice->windowTarget; - } - } - if (isset($item->url)) { - $url = $item->url; - } - } else { // Version 2 - if (empty($documentTarget)) { - if (isset($item->embed)) { - $documentTarget = 'embed'; - } elseif (isset($item->iframe)) { - $documentTarget = 'iframe'; - } elseif (isset($item->window)) { - $documentTarget = 'window'; - } - } elseif (!isset($item->{$documentTarget})) { - $documentTarget = null; - } - if (!empty($documentTarget)) { - if (isset($item->{$documentTarget}->width)) { - $displayWidth = $item->{$documentTarget}->width; - } - if (isset($item->{$documentTarget}->height)) { - $displayHeight = $item->{$documentTarget}->height; - } - if (isset($item->{$documentTarget}->targetName)) { - $windowTarget = $item->{$documentTarget}->targetName; - } - if (isset($item->{$documentTarget}->windowFeatures)) { - $windowFeatures = $item->{$documentTarget}->windowFeatures; - } - if (isset($item->{$documentTarget}->src)) { - $url = $item->{$documentTarget}->src; - } - if (isset($item->{$documentTarget}->html)) { - $html = $item->{$documentTarget}->html; - } - } - } - if (!empty($documentTarget)) { - $obj = new Placement($documentTarget, $displayWidth, $displayHeight, $windowTarget, $windowFeatures, $url, $html); - } - - return $obj; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/Content/TimePeriod.php b/components/ILIAS/LTI/src/ToolProvider/Content/TimePeriod.php deleted file mode 100755 index 198cc0e7eef3..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/Content/TimePeriod.php +++ /dev/null @@ -1,114 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class TimePeriod -{ - /** - * Start date/time. - * - * @var int|null $startDateTime - */ - private ?int $startDateTime = null; - - /** - * End date/time. - * - * @var int|null $endDateTime - */ - private ?int $endDateTime = null; - - /** - * Class constructor. - * @param int $startDateTime Start date/time - * @param int $endDateTime End date/time - */ - public function __construct(int $startDateTime, int $endDateTime) - { - $this->startDateTime = $startDateTime; - $this->endDateTime = $endDateTime; - } - - /** - * Generate the JSON-LD object representation of the time period. - * - * @return object - */ - public function toJsonldObject() - { - return $this->toJsonObject(); - } - - /** - * Generate the JSON object representation of the image. - * - * @return \stdClass - */ - public function toJsonObject(): \stdClass - { - $timePeriod = new \stdClass(); - if (!is_null($this->startDateTime)) { - $timePeriod->startDateTime = gmdate('Y-m-d\TH:i:s\Z', $this->startDateTime); - } - if (!is_null($this->endDateTime)) { - $timePeriod->endDateTime = gmdate('Y-m-d\TH:i:s\Z', $this->endDateTime); - } - - return $timePeriod; - } - - /** - * Generate a LineItem object from its JSON or JSON-LD representation. - * @param object $item A JSON or JSON-LD object representing a content-item - * @return TimePeriod|null The LineItem object - */ - public static function fromJsonObject(object $item): ?TimePeriod - { - $obj = null; - $startDateTime = null; - $endDateTime = null; - if (is_object($item)) { - $url = null; - foreach (get_object_vars($item) as $name => $value) { - switch ($name) { - case 'startDateTime': - $startDateTime = strtotime($item->startDateTime); - break; - case 'endDateTime': - $endDateTime = strtotime($item->endDateTime); - break; - } - } - } else { - $url = $item; - } - if ($startDateTime || $endDateTime) { - $obj = new TimePeriod($startDateTime, $endDateTime); - } - - return $obj; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/Context.php b/components/ILIAS/LTI/src/ToolProvider/Context.php deleted file mode 100755 index 11558b936a63..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/Context.php +++ /dev/null @@ -1,740 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class Context -{ - use ApiHook; - - /** - * Context ID as supplied in the last connection request. - * - * @var string|null $ltiContextId - */ - public ?string $ltiContextId = null; - - /** - * Context title. - * - * @var string|null $title - */ - public ?string $title = null; - - /** - * Context type. - * - * @var string|null $type - */ - public ?string $type = null; - - /** - * User group sets (null if the platform does not support the groups enhancement) - * - * A group set is represented by an associative array with the following elements: - * - title - * - groups (array of group IDs) - * - num_members - * - num_staff - * - num_learners - * The array key value is the group set ID. - * - * @var array|null $groupSets - */ - public ?array $groupSets = null; - - /** - * User groups (null if the platform does not support the groups enhancement) - * - * A group is represented by an associative array with the following elements: - * - title - * - set (ID of group set, array of IDs if the group belongs to more than one set, omitted if the group is not part of a set) - * The array key value is the group ID. - * - * @var array|null $groups - */ - public ?array $groups = null; - - /** - * HttpMessage object for last service request. - * - * @var HttpMessage|null $lastServiceRequest - */ - public ?HTTPMessage $lastServiceRequest = null; - - /** - * Timestamp for when the object was created. - * - * @var int|null $created - */ - public ?int $created = null; - - /** - * Timestamp for when the object was last updated. - * - * @var int|null $updated - */ - public ?int $updated = null; - - /** - * Platform for this context. - * - * @var Platform|null $platform - */ - private ?Platform $platform = null; - - /** - * Platform ID for this context. - * - * @var int|null $platformId - */ - private ?int $platformId = null; - - /** - * ID for this context. - * - * @var int|null $id - */ - private ?int $id = null; - - /** - * Setting values (LTI parameters, custom parameters and local parameters). - * - * @var array|null $settings - */ - private ?array $settings = null; - - /** - * Whether the settings value have changed since last saved. - * - * @var bool $settingsChanged - */ - private bool $settingsChanged = false; - - /** - * Data connector object or string. - * - * @var \ILIAS\LTI\ToolProvider\DataConnector\DataConnector|null $dataConnector - */ - private ?\ILIAS\LTI\ToolProvider\DataConnector\DataConnector $dataConnector = null; - - /** - * Class constructor. - */ - public function __construct() - { - $this->initialize(); - } - - /** - * Initialise the context. - */ - public function initialize() - { - $this->title = ''; - $this->settings = array(); - $this->groupSets = null; - $this->groups = null; - $this->created = null; - $this->updated = null; - } - - /** - * Initialise the context. - * - * Synonym for initialize(). - */ - public function initialise() - { - $this->initialize(); - } - - /** - * Save the context to the database. - * - * @return bool True if the context was successfully saved. - */ - public function save(): bool - { - $ok = $this->getDataConnector()->saveContext($this); - if ($ok) { - $this->settingsChanged = false; - } - - return $ok; - } - - /** - * Delete the context from the database. - * - * @return bool True if the context was successfully deleted. - */ - public function delete(): bool - { - return $this->getDataConnector()->deleteContext($this); - } - -// /** -// * Get tool consumer. -// * -// * @deprecated Use getPlatform() instead -// * @see Context::getPlatform() -// * -// * @return ToolConsumer Tool consumer object for this context. -// */ -// public function getConsumer() -// { -// Util::logDebug('Method ceLTIc\LTI\Context::getConsumer() has been deprecated; please use ceLTIc\LTI\Context::getPlatform() instead.', -// true); -// return $this->getPlatform(); -// } - -// /** -// * Set tool consumer ID. -// * -// * @deprecated Use setPlatformId() instead -// * @see Context::setPlatformId() -// * -// * @param int $consumerId Tool Consumer ID for this context. -// */ -// public function setConsumerId($consumerId) -// { -// Util::logDebug('Method ceLTIc\LTI\Context::setConsumerId() has been deprecated; please use ceLTIc\LTI\Context::setPlatformId() instead.', -// true); -// $this->setPlatformId($consumerId); -// } - - /** - * Get platform. - * - * @return Platform Platform object for this context. - */ - public function getPlatform(): ?\ilLTIPlatform - { - if (is_null($this->platform)) { - $this->platform = \ilLTIPlatform::fromRecordId($this->platformId, $this->getDataConnector()); - } - - return $this->platform; - } - - /** - * Set platform ID. - * @param int $platformId Platform ID for this context. - */ - public function setPlatformId(int $platformId) - { - $this->platform = null; - $this->platformId = $platformId; - } - - /** - * Get consumer key. - * - * @return string Consumer key value for this context. - */ - public function getKey(): string - { - return $this->getPlatform()->getKey(); - } - - /** - * Get context ID. - * - * @return string ID for this context. - */ - public function getId(): ?string - { - return $this->ltiContextId; - } - - /** - * Get the context record ID. - * - * @return int|null Context record ID value - */ - public function getRecordId(): ?int - { - return $this->id; - } - - /** - * Sets the context record ID. - * @param int $id Context record ID value - */ - public function setRecordId(int $id) - { - $this->id = $id; - } - - /** - * Get the data connector. - * - * @return mixed Data connector object or string - */ - public function getDataConnector() - { - return $this->dataConnector; - } - - /** - * Get a setting value. - * @param string $name Name of setting - * @param string $default Value to return if the setting does not exist (optional, default is an empty string) - * @return string Setting value - */ - public function getSetting(string $name, string $default = ''): string - { - if (array_key_exists($name, $this->settings)) { - $value = $this->settings[$name]; - } else { - $value = $default; - } - - return $value; - } - - /** - * Set a setting value. - * @param string $name Name of setting - * @param string|null $value Value to set, use an empty value to delete a setting (optional, default is null) - */ - public function setSetting(string $name, string $value = null) - { - $old_value = $this->getSetting($name); - if ($value !== $old_value) { - if (!empty($value)) { - $this->settings[$name] = $value; - } else { - unset($this->settings[$name]); - } - $this->settingsChanged = true; - } - } - - /** - * Get an array of all setting values. - * - * @return array Associative array of setting values - */ - public function getSettings(): ?array - { - return $this->settings; - } - - /** - * Set an array of all setting values. - * @param array $settings Associative array of setting values - */ - public function setSettings(array $settings) - { - $this->settings = $settings; - } - - /** - * Save setting values. - * - * @return bool True if the settings were successfully saved - */ - public function saveSettings(): bool - { - if ($this->settingsChanged) { - $ok = $this->save(); - } else { - $ok = true; - } - - return $ok; - } - - /** - * Check if the Tool Settings service is available. - * - * @return bool True if this context supports the Tool Settings service - */ - public function hasToolSettingsService(): bool - { - $has = !empty($this->getSetting('custom_context_setting_url')); - if (!$has) { - $has = self::hasConfiguredApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->getPlatform()->getFamilyCode(), $this); - } - return $has; - } - - /** - * Get Tool Settings. - * @param int $mode Mode for request (optional, default is current level only) - * @param bool $simple True if all the simple media type is to be used (optional, default is true) - * @return mixed The array of settings if successful, otherwise false - */ - public function getToolSettings(int $mode = Service\ToolSettings::MODE_CURRENT_LEVEL, bool $simple = true) - { - $ok = false; - $settings = array(); - if (!empty($this->getSetting('custom_context_setting_url'))) { - $url = $this->getSetting('custom_context_setting_url'); - $service = new Service\ToolSettings($this, $url, $simple); - $settings = $service->get($mode); - $this->lastServiceRequest = $service->getHttpMessage(); - $ok = $settings !== false; - } - if (!$ok && $this->hasConfiguredApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->getPlatform()->getFamilyCode(), $this)) { - $className = $this->getApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->getPlatform()->getFamilyCode()); - $hook = new $className($this); - $settings = $hook->getToolSettings($mode, $simple); - } - - return $settings; - } - - /** - * Set Tool Settings. - * @param array $settings An associative array of settings (optional, default is none) - * @return bool True if action was successful, otherwise false - */ - public function setToolSettings(array $settings = array()): bool - { - $ok = false; - if (!empty($this->getSetting('custom_context_setting_url'))) { - $url = $this->getSetting('custom_context_setting_url'); - $service = new Service\ToolSettings($this, $url); - $ok = $service->set($settings); - $this->lastServiceRequest = $service->getHttpMessage(); - } - if (!$ok && $this->hasConfiguredApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->getPlatform()->getFamilyCode(), $this)) { - $className = $this->getApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->getPlatform()->getFamilyCode()); - $hook = new $className($this); - $ok = $hook->setToolSettings($settings); - } - - return $ok; - } - - /** - * Check if a Course Group service is available. - * - * @return bool True if this context supports a Course Group service - */ - public function hasGroupService(): bool - { - $has = !empty($this->getSetting('custom_context_groups_url')); - if (!$has) { - $has = self::hasConfiguredApiHook(self::$MEMBERSHIPS_SERVICE_HOOK, $this->getPlatform()->getFamilyCode(), $this); - } - return $has; - } - - /** - * Get course group sets and groups. - * - * @return bool True if the request was successful - */ - public function getGroups(): bool - { - $groupsUrl = $this->getSetting('custom_context_groups_url'); - $groupsetsUrl = $this->getSetting('custom_context_group_sets_url'); - $service = new Service\Groups($this, $groupsUrl, $groupsetsUrl); - $ok = $service->get(); - if (!empty($service->getHttpMessage())) { - $this->lastServiceRequest = $service->getHttpMessage(); - } - if (!$ok && $this->hasConfiguredApiHook(self::$GROUPS_SERVICE_HOOK, $this->getPlatform()->getFamilyCode(), $this)) { - $className = $this->getApiHook(self::$GROUPS_SERVICE_HOOK, $this->getPlatform()->getFamilyCode()); - $hook = new $className($this); - $ok = $hook->getGroups(); - } - - return $ok; - } - - /** - * Check if the Membership service is supported. - * - * @deprecated Use hasMembershipsService() instead - * @see Context::hasMembershipsService() - * - * @return bool True if this context supports the Membership service - */ - public function hasMembershipService(): bool - { - Util::logDebug( - 'Method ceLTIc\LTI\Context::hasMembershipService() has been deprecated; please use ceLTIc\LTI\Context::hasMembershipsService() instead.', - true - ); - return $this->hasMembershipsService(); - } - - /** - * Check if a Membership service is available. - * - * @return bool True if this context supports a Memberships service - */ - public function hasMembershipsService(): bool - { - $has = !empty($this->getSetting('custom_context_memberships_url')) || !empty($this->getSetting('custom_context_memberships_v2_url')); - if (!$has) { - $has = self::hasConfiguredApiHook(self::$MEMBERSHIPS_SERVICE_HOOK, $this->getPlatform()->getFamilyCode(), $this); - } - return $has; - } - - /** - * Get Membership. - * - * @deprecated Use getMemberships() instead - * @see Context::getMemberships() - * - * @return mixed The array of UserResult objects if successful, otherwise false - */ - public function getMembership() - { - Util::logDebug( - 'Method ceLTIc\LTI\Context::getMembership() has been deprecated; please use ceLTIc\LTI\Context::getMemberships() instead.', - true - ); - return $this->getMemberships(); - } - - /** - * Get Memberships. - * @param bool $withGroups True is group information is to be requested as well - * @return mixed The array of UserResult objects if successful, otherwise false - */ - public function getMemberships(bool $withGroups = false) - { - $ok = false; - $userResults = array(); - $hasMembershipsService = !empty($this->getSetting('custom_context_memberships_url')); - $hasNRPService = !empty($this->getSetting('custom_context_memberships_v2_url')); - $hasGroupsService = !empty($this->getSetting('custom_context_groups_url')) || - $this->hasConfiguredApiHook(self::$GROUPS_SERVICE_HOOK, $this->getPlatform()->getFamilyCode(), $this); - $hasApiHook = $this->hasConfiguredApiHook(self::$MEMBERSHIPS_SERVICE_HOOK, $this->getPlatform()->getFamilyCode(), $this); - if (($hasMembershipsService || $hasNRPService) && (!$withGroups || ($hasNRPService && $hasGroupsService) || !$hasApiHook)) { - if ($hasNRPService) { - $url = $this->getSetting('custom_context_memberships_v2_url'); - $format = Service\Membership::MEDIA_TYPE_MEMBERSHIPS_NRPS; - } else { - $url = $this->getSetting('custom_context_memberships_url'); - $format = Service\Membership::MEDIA_TYPE_MEMBERSHIPS_V1; - } - $service = new Service\Membership($this, $url, $format); - if (!$withGroups || !$hasNRPService) { - $userResults = $service->get(); - } else { - $userResults = $service->getWithGroups(); - } - if (!empty($service->getHttpMessage())) { - $this->lastServiceRequest = $service->getHttpMessage(); - } - $ok = $userResults !== false; - } - if (!$ok && $hasApiHook) { - $className = $this->getApiHook(self::$MEMBERSHIPS_SERVICE_HOOK, $this->getPlatform()->getFamilyCode()); - $hook = new $className($this); - $userResults = $hook->getMemberships($withGroups); - } - - return $userResults; - } - - /** - * Check if the Line Item service is available. - * - * @return bool True if this context supports the Line Item service - */ - public function hasLineItemService(): bool - { - $has = false; - if (!empty($this->getSetting('custom_ags_scopes'))) { - $scopes = explode(',', $this->getSetting('custom_ags_scopes')); - if (in_array(Service\LineItem::$SCOPE, $scopes) || in_array(Service\LineItem::$SCOPE_READONLY, $scopes)) { - $has = !empty($this->getSetting('custom_lineitems_url')); - } - } - - return $has; - } - - /** - * Check if the Score service is available. - * - * @return bool True if this context supports the Score service - */ - public function hasScoreService(): bool - { - $has = false; - if (!empty($this->getSetting('custom_ags_scopes'))) { - $scopes = explode(',', $this->getSetting('custom_ags_scopes')); - if (in_array(Service\Score::$SCOPE, $scopes)) { - $has = !empty($this->getSetting('custom_lineitems_url')); - } - } - - return $has; - } - - /** - * Check if the Result service is available. - * - * @return bool True if this context supports the Result service - */ - public function hasResultService(): bool - { - $has = false; - if (!empty($this->getSetting('custom_ags_scopes'))) { - $scopes = explode(',', $this->getSetting('custom_ags_scopes')); - if (in_array(Service\Result::$SCOPE, $scopes)) { - $has = !empty($this->getSetting('custom_lineitems_url')); - } - } - - return $has; - } - - /** - * Get line items. - * @param string|null $resourceId Tool resource ID - * @param string|null $tag Tag - * @param int|null $limit Limit of line items to be returned in each request, null for service default - * @return LineItem[]|bool Array of LineItem objects or false on error - */ - public function getLineItems(string $resourceId = null, string $tag = null, int $limit = null) - { - $lineItems = false; - $this->lastServiceRequest = null; - $lineItemService = $this->getLineItemService(); - if (!empty($lineItemService)) { - $lineItems = $lineItemService->getAll(null, $resourceId, $tag); - $http = $lineItemService->getHttpMessage(); - $this->lastServiceRequest = $http; - } - - return $lineItems; - } - - /** - * Create a new line item. - * @param LineItem $lineItem Line item object - * @return bool True if successful - */ - public function createLineItem(LineItem $lineItem): bool - { - $ok = false; - $lineItemService = $this->getLineItemService(); - if (!empty($lineItemService)) { - $ok = $lineItemService->createLineItem($lineItem); - } - - return $ok; - } - - /** - * Load the context from the database. - * @param int $id Record ID of context - * @param \ILIAS\LTI\ToolProvider\DataConnector\DataConnector $dataConnector Database connection object - * @return Context Context object - */ - public static function fromRecordId(int $id, \ILIAS\LTI\ToolProvider\DataConnector\DataConnector $dataConnector): Context - { - $context = new Context(); - $context->dataConnector = $dataConnector; - $context->load($id); - - return $context; - } - -// /** -// * Class constructor from consumer. -// * -// * @deprecated Use fromPlatform() instead -// * @see Context::fromPlatform() -// * -// * @param ToolConsumer $consumer Consumer instance -// * @param string $ltiContextId LTI Context ID value -// * -// * @return Context -// */ -// public static function fromConsumer($consumer, $ltiContextId) -// { -// return self::fromPlatform($consumer, $ltiContextId); -// } - - /** - * Class constructor from platform. - * @param Platform $platform Platform instance - * @param string $ltiContextId LTI Context ID value - * @return Context - */ - public static function fromPlatform(Platform $platform, string $ltiContextId): Context - { - $context = new Context(); - $context->platform = $platform; - $context->dataConnector = $platform->getDataConnector(); - $context->ltiContextId = $ltiContextId; - if (!empty($ltiContextId)) { - $context->load(); - } - - return $context; - } - - ### - ### PRIVATE METHODS - ### - - /** - * Load the context from the database. - * @param int|null $id Record ID of context (optional, default is null) - * @return bool True if context was successfully loaded - */ - private function load(int $id = null): bool - { - $this->initialize(); - $this->id = $id; - return $this->getDataConnector()->loadContext($this); - } - - /** - * Get the Line Item service object. - * - * @return bool|Service\LineItem Line Item service, or false if not available //UK: removed Service\\LineItem - */ - private function getLineItemService() - { - $url = $this->getSetting('custom_lineitems_url'); - if (!empty($url)) { - $lineItemService = new Service\LineItem($this->getPlatform(), $url); - } else { - $lineItemService = false; - } - - return $lineItemService; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/DataConnector/DataConnector.php b/components/ILIAS/LTI/src/ToolProvider/DataConnector/DataConnector.php deleted file mode 100755 index f2d696eecc3a..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/DataConnector/DataConnector.php +++ /dev/null @@ -1,758 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class DataConnector -{ - /** - * Default name for database table used to store platforms. - */ - public const PLATFORM_TABLE_NAME = 'lti2_consumer'; - - /** - * Default name for database table used to store platforms. - * - * @deprecated Use DataConnector::PLATFORM_TABLE_NAME instead - * @see DataConnector::PLATFORM_TABLE_NAME - */ - public const CONSUMER_TABLE_NAME = self::PLATFORM_TABLE_NAME; - - /** - * Default name for database table used to store contexts. - */ - public const CONTEXT_TABLE_NAME = 'lti2_context'; - - /** - * Default name for database table used to store resource links. - */ - public const RESOURCE_LINK_TABLE_NAME = 'lti2_resource_link'; - - /** - * Default name for database table used to store users. - */ - public const USER_RESULT_TABLE_NAME = 'lti2_user_result'; - - /** - * Default name for database table used to store resource link share keys. - */ - public const RESOURCE_LINK_SHARE_KEY_TABLE_NAME = 'lti2_share_key'; - - /** - * Default name for database table used to store nonce values. - */ - public const NONCE_TABLE_NAME = 'lti2_nonce'; - - /** - * Default name for database table used to store access token values. - */ - public const ACCESS_TOKEN_TABLE_NAME = 'lti2_access_token'; - - /** - * Default name for database table used to store tools. - */ - public const TOOL_TABLE_NAME = 'lti2_tool'; - - /** - * Database connection. - * - * @var object|resource $db - */ - protected $db = null; - - /** - * Prefix for database table names. - * - * @var string $dbTableNamePrefix - */ - protected string $dbTableNamePrefix = ''; - - /** - * SQL date format (default = 'Y-m-d') - * - * @var string $dateFormat - */ - protected string $dateFormat = 'Y-m-d'; - - /** - * SQL time format (default = 'H:i:s') - * - * @var string $timeFormat - */ - protected string $timeFormat = 'H:i:s'; - - /** - * Class constructor - * @param object $db Database connection object - * @param string $dbTableNamePrefix Prefix for database table names (optional, default is none) - */ - protected function __construct(object $db, string $dbTableNamePrefix = '') - { - $this->db = $db; - $this->dbTableNamePrefix = $dbTableNamePrefix; - } - - ### - ### Platform methods - ### - -// /** -// * Load tool consumer object. -// * -// * @deprecated Use loadPlatform() instead -// * @see DataConnector::loadPlatform() -// * -// * @param ToolConsumer $consumer Tool consumer object -// * -// * @return bool True if the tool consumer object was successfully loaded -// */ -// public function loadToolConsumer($consumer) -// { -// Util::logDebug('Method ceLTIc\LTI\DataConnector\DataConnector::loadToolConsumer() has been deprecated; please use ceLTIc\LTI\DataConnector\DataConnector::loadPlatform() instead.', -// true); -// return $this->loadPlatform($consumer); -// } - -// /** -// * Save tool consumer object. -// * -// * @deprecated Use savePlatform() instead -// * @see DataConnector::savePlatform() -// * -// * @param ToolConsumer $consumer Tool consumer object -// * -// * @return bool True if the tool consumer object was successfully saved -// */ -// public function saveToolConsumer($consumer) -// { -// Util::logDebug('Method ceLTIc\LTI\DataConnector\DataConnector::saveToolConsumer() has been deprecated; please use ceLTIc\LTI\DataConnector\DataConnector::savePlatform() instead.', -// true); -// return $this->savePlatform($consumer); -// } - -// /** -// * Delete tool consumer object. -// * -// * @deprecated Use deletePlatform() instead -// * @see DataConnector::deletePlatform() -// * -// * @param ToolConsumer $consumer Tool consumer object -// * -// * @return bool True if the tool consumer object was successfully deleted -// */ -// public function deleteToolConsumer($consumer) -// { -// Util::logDebug('Method ceLTIc\LTI\DataConnector\DataConnector::deleteToolConsumer() has been deprecated; please use ceLTIc\LTI\DataConnector\DataConnector::deletePlatform() instead.', -// true); -// return $this->deletePlatform($consumer); -// } - -// /** -// * Load tool consumer objects. -// * -// * @deprecated Use getPlatforms() instead -// * @see DataConnector::getPlatforms() -// * -// * @return ToolConsumer[] Array of all defined tool consumer objects -// */ -// public function getToolConsumers() -// { -// Util::logDebug('Method ceLTIc\LTI\DataConnector\DataConnector::getToolConsumers() has been deprecated; please use ceLTIc\LTI\DataConnector\DataConnector::getPlatforms() instead.', -// true); -// return $this->getPlatforms(); -// } - - /** - * Load platform object. - * @param Platform $platform Platform object - * @return bool True if the platform object was successfully loaded - */ - public function loadPlatform(Platform $platform): bool - { - $platform->secret = 'secret'; - $platform->enabled = true; - $now = time(); - $platform->created = $now; - $platform->updated = $now; - - return true; - } - - /** - * Save platform object. - * @param Platform $platform Platform object - * @return bool True if the platform object was successfully saved - */ - public function savePlatform(Platform $platform): bool - { - $platform->updated = time(); - - return true; - } - - /** - * Delete platform object. - * @param Platform $platform Platform object - * @return bool True if the platform object was successfully deleted - */ - public function deletePlatform(Platform $platform): bool - { - $platform->initialize(); - - return true; - } - - /** - * Load platform objects. - * - * @return Platform[] Array of all defined Platform objects - */ - public function getPlatforms(): array - { - return array(); - } - - ### - ### Context methods - ### - - /** - * Load context object. - * @param Context $context Context object - * @return bool True if the context object was successfully loaded - */ - public function loadContext(Context $context): bool - { - $now = time(); - $context->created = $now; - $context->updated = $now; - - return true; - } - - /** - * Save context object. - * @param Context $context Context object - * @return bool True if the context object was successfully saved - */ - public function saveContext(Context $context): bool - { - $context->updated = time(); - - return true; - } - - /** - * Delete context object. - * @param Context $context Context object - * @return bool True if the Context object was successfully deleted - */ - public function deleteContext(Context $context): bool - { - $context->initialize(); - - return true; - } - - ### - ### ResourceLink methods - ### - - /** - * Load resource link object. - * @param ResourceLink $resourceLink ResourceLink object - * @return bool True if the resource link object was successfully loaded - */ - public function loadResourceLink(ResourceLink $resourceLink): bool - { - $now = time(); - $resourceLink->created = $now; - $resourceLink->updated = $now; - - return true; - } - - /** - * Save resource link object. - * @param ResourceLink $resourceLink ResourceLink object - * @return bool True if the resource link object was successfully saved - */ - public function saveResourceLink(ResourceLink $resourceLink): bool - { - $resourceLink->updated = time(); - - return true; - } - - /** - * Delete resource link object. - * @param ResourceLink $resourceLink ResourceLink object - * @return bool True if the resource link object was successfully deleted - */ - public function deleteResourceLink(ResourceLink $resourceLink): bool - { - $resourceLink->initialize(); - - return true; - } - - /** - * Get array of user objects. - * Obtain an array of UserResult objects for users with a result sourcedId. The array may include users from other - * resource links which are sharing this resource link. It may also be optionally indexed by the user ID of a specified scope. - * @param ResourceLink $resourceLink Resource link object - * @param bool $localOnly True if only users within the resource link are to be returned (excluding users sharing this resource link) - * @param int $idScope Scope value to use for user IDs - * @return UserResult[] Array of UserResult objects - */ - public function getUserResultSourcedIDsResourceLink(ResourceLink $resourceLink, bool $localOnly, int $idScope): array - { - return array(); - } - - /** - * Get array of shares defined for this resource link. - * @param ResourceLink $resourceLink ResourceLink object - * @return ResourceLinkShare[] Array of ResourceLinkShare objects - */ - public function getSharesResourceLink(ResourceLink $resourceLink): array - { - return array(); - } - - ### - ### PlatformNonce methods - ### - -// /** -// * Load nonce object. -// * -// * @deprecated Use loadPlatformNonce() instead -// * @see DataConnector::loadPlatformNonce() -// * -// * @param ConsumerNonce $nonce Nonce object -// * -// * @return bool True if the nonce object was successfully loaded -// */ -// public function loadConsumerNonce($nonce) -// { -// Util::logDebug('Method ceLTIc\LTI\DataConnector\DataConnector::loadConsumerNonce() has been deprecated; please use ceLTIc\LTI\DataConnector\DataConnector::loadPlatformNonce() instead.', -// true); -// return $this->loadPlatformNonce($nonce); -// } - -// /** -// * Save nonce object. -// * -// * @deprecated Use savePlatformNonce() instead -// * @see DataConnector::savePlatformNonce() -// * -// * @param ConsumerNonce $nonce Nonce object -// * -// * @return bool True if the nonce object was successfully saved -// */ -// public function saveConsumerNonce($nonce) -// { -// Util::logDebug('Method ceLTIc\LTI\DataConnector\DataConnector::saveConsumerNonce() has been deprecated; please use ceLTIc\LTI\DataConnector\DataConnector::savePlatformNonce() instead.', -// true); -// return $this->savePlatformNonce($nonce); -// } - -// /** -// * Delete nonce object. -// * -// * @deprecated Use deletePlatformNonce() instead -// * @see DataConnector::deletePlatformNonce() -// * -// * @param ConsumerNonce $nonce Nonce object -// * -// * @return bool True if the nonce object was successfully deleted -// */ -// public function deleteConsumerNonce($nonce) -// { -// Util::logDebug('Method ceLTIc\LTI\DataConnector\DataConnector::deleteConsumerNonce() has been deprecated; please use ceLTIc\LTI\DataConnector\DataConnector::deletePlatformNonce() instead.', -// true); -// return $this->deletePlatformNonce($nonce); -// } - - /** - * Load nonce object. - * @param PlatformNonce $nonce Nonce object - * @return bool True if the nonce object was successfully loaded - */ - public function loadPlatformNonce(PlatformNonce $nonce): bool - { - return false; // assume the nonce does not already exist - } - - /** - * Save nonce object. - * @param PlatformNonce $nonce Nonce object - * @return bool True if the nonce object was successfully saved - */ - public function savePlatformNonce(PlatformNonce $nonce): bool - { - return true; - } - - /** - * Delete nonce object. - * @param PlatformNonce $nonce Nonce object - * @return bool True if the nonce object was successfully deleted - */ - public function deletePlatformNonce(PlatformNonce $nonce): bool - { - return true; - } - - ### - ### AccessToken methods - ### - - /** - * Load access token object. - * @param AccessToken $accessToken Access token object // UK: changed from - * @return bool True if the nonce object was successfully loaded - */ - public function loadAccessToken(AccessToken $accessToken): bool - { - return false; // assume the access token does not already exist - } - - /** - * Save access token object. - * @param AccessToken $accessToken Access token object - * @return bool True if the access token object was successfully saved - */ - public function saveAccessToken(AccessToken $accessToken): bool - { - return true; - } - - ### - ### ResourceLinkShareKey methods - ### - - /** - * Load resource link share key object. - * @param ResourceLinkShareKey $shareKey ResourceLink share key object - * @return bool True if the resource link share key object was successfully loaded - */ - public function loadResourceLinkShareKey(ResourceLinkShareKey $shareKey): bool - { - return true; - } - - /** - * Save resource link share key object. - * @param ResourceLinkShareKey $shareKey Resource link share key object - * @return bool True if the resource link share key object was successfully saved - */ - public function saveResourceLinkShareKey(ResourceLinkShareKey $shareKey): bool - { - return true; - } - - /** - * Delete resource link share key object. - * @param ResourceLinkShareKey $shareKey Resource link share key object - * @return bool True if the resource link share key object was successfully deleted - */ - public function deleteResourceLinkShareKey(ResourceLinkShareKey $shareKey): bool - { - return true; - } - - ### - ### UserResult methods - ### - - /** - * Load user object. - * @param UserResult $userresult UserResult object - * @return bool True if the user object was successfully loaded - */ - public function loadUserResult(UserResult $userresult): bool - { - $now = time(); - $userresult->created = $now; - $userresult->updated = $now; - - return true; - } - - /** - * Save user object. - * @param UserResult $userresult UserResult object - * @return bool True if the user object was successfully saved - */ - public function saveUserResult(UserResult $userresult): bool - { - $userresult->updated = time(); - - return true; - } - - /** - * Delete user object. - * @param UserResult $userresult UserResult object - * @return bool True if the user object was successfully deleted - */ - public function deleteUserResult(UserResult $userresult): bool - { - $userresult->initialize(); - - return true; - } - - ### - ### Tool methods - ### - - /** - * Load tool object. - * @param Tool $tool Tool object - * @return bool True if the tool object was successfully loaded - */ - public function loadTool(Tool $tool): bool - { - $tool->secret = 'secret'; - $tool->enabled = true; - $now = time(); - $tool->created = $now; - $tool->updated = $now; - - return true; - } - - /** - * Save tool object. - * @param Tool $tool Tool object - * @return bool True if the tool object was successfully saved - */ - public function saveTool(Tool $tool): bool - { - $tool->updated = time(); - - return true; - } - - /** - * Delete tool object. - * @param Tool $tool Tool object - * @return bool True if the tool object was successfully deleted - */ - public function deleteTool(Tool $tool): bool - { - $tool->initialize(); - - return true; - } - - /** - * Load platform objects. - * - * @return Tool[] Array of all defined Tool objects - */ - public function getTools(): array - { - return array(); - } - - ### - ### Other methods - ### - - /** - * Create data connector object. - * A data connector provides access to persistent storage for the different objects. - * Names of tables may be given a prefix to allow multiple versions to share the same schema. A separate sub-class is defined for - * each different database connection - the class to use is determined by inspecting the database object passed, but this can be overridden - * (for example, to use a bespoke connector) by specifying a type. If no database is passed then this class is used which acts as a dummy - * connector with no persistence. - * @param object|null $db A database connection object or string (optional, default is no persistence) - * @param string $dbTableNamePrefix Prefix for database table names (optional, default is none) - * @param string $type The type of data connector (optional, default is based on $db parameter) - * @return DataConnector Data connector object - */ - public static function getDataConnector(object $db = null, string $dbTableNamePrefix = '', string $type = ''): DataConnector - { - if (is_null($dbTableNamePrefix)) { - $dbTableNamePrefix = ''; - } - if (!is_null($db) && empty($type)) { - if (is_object($db)) { - $type = get_class($db); - } elseif (is_resource($db)) { - $type = strtok(get_resource_type($db), ' '); - } - } - $type = strtolower($type); - if ($type === 'pdo') { - if ($db->getAttribute(\PDO::ATTR_DRIVER_NAME) === 'pgsql') { - $type .= '_pgsql'; - } elseif ($db->getAttribute(\PDO::ATTR_DRIVER_NAME) === 'oci') { - $type .= '_oci'; - } - } - if (!empty($type)) { - $type = "DataConnector_{$type}"; - } else { - $type = 'DataConnector'; - } - $type = "\\ceLTIc\\LTI\\DataConnector\\{$type}"; - $dataConnector = new $type($db, $dbTableNamePrefix); - - return $dataConnector; - } - -// /** -// * Generate a random string. -// * -// * The generated string will only comprise letters (upper- and lower-case) and digits. -// * -// * @deprecated Use Util::getRandomString() instead -// * @see Util::getRandomString() -// * -// * @param int $length Length of string to be generated (optional, default is 8 characters) -// * -// * @return string Random string -// */ -// public static function getRandomString($length = 8) -// { -// Util::logDebug('Method ceLTIc\LTI\DataConnector::getRandomString() has been deprecated; please use ceLTIc\LTI\Util::getRandomString() instead.', -// true); -// return Util::getRandomString($length); -// } - - /** - * Escape a string for use in a database query. - * Any single quotes in the value passed will be replaced with two single quotes. If a null value is passed, a string - * of 'null' is returned (which will never be enclosed in quotes irrespective of the value of the $addQuotes parameter. - * @param string $value Value to be escaped - * @param bool $addQuotes If true the returned string will be enclosed in single quotes (optional, default is true) - * @return string The escaped string. - */ - public function escape(string $value, bool $addQuotes = true): string - { - return static::quoted($value, $addQuotes); - } - - /** - * Quote a string for use in a database query. - * Any single quotes in the value passed will be replaced with two single quotes. If a null value is passed, a string - * of 'null' is returned (which will never be enclosed in quotes irrespective of the value of the $addQuotes parameter. - * @param string $value Value to be quoted - * @param bool $addQuotes If true the returned string will be enclosed in single quotes (optional, default is true) - * @return string The quoted string. - */ - public static function quoted(string $value, bool $addQuotes = true): string - { - if (is_null($value)) { - $value = 'null'; - } else { - $value = str_replace('\'', '\'\'', $value); - if ($addQuotes) { - $value = "'{$value}'"; - } - } - - return $value; - } - - /** - * Adjust the settings for any platform properties being stored as a setting value. - * @param Platform $platform Platform object - * @param bool $isSave True if the settings are being saved - */ - protected function fixPlatformSettings(Platform $platform, bool $isSave) - { - if (!$isSave) { - $platform->authorizationServerId = $platform->getSetting('_authorization_server_id', $platform->authorizationServerId); - $platform->setSetting('_authorization_server_id'); - $platform->authenticationUrl = $platform->getSetting('_authentication_request_url', $platform->authenticationUrl); - $platform->setSetting('_authentication_request_url'); - $platform->accessTokenUrl = $platform->getSetting('_oauth2_access_token_url', $platform->accessTokenUrl); - $platform->setSetting('_oauth2_access_token_url'); - $platform->jku = $platform->getSetting('_jku', $platform->jku); - $platform->setSetting('_jku'); - $platform->encryptionMethod = $platform->getSetting('_encryption_method', $platform->encryptionMethod); - $platform->setSetting('_encryption_method'); - $platform->debugMode = $platform->getSetting('_debug', $platform->debugMode ? 'true' : 'false') === 'true'; - $platform->setSetting('_debug'); - if ($platform->debugMode) { - Util::$logLevel = Util::LOGLEVEL_DEBUG; - } - } else { - $platform->setSetting( - '_authorization_server_id', - !empty($platform->authorizationServerId) ? $platform->authorizationServerId : null - ); - $platform->setSetting( - '_authentication_request_url', - !empty($platform->authenticationUrl) ? $platform->authenticationUrl : null - ); - $platform->setSetting('_oauth2_access_token_url', !empty($platform->accessTokenUrl) ? $platform->accessTokenUrl : null); - $platform->setSetting('_jku', !empty($platform->jku) ? $platform->jku : null); - $platform->setSetting('_encryption_method', !empty($platform->encryptionMethod) ? $platform->encryptionMethod : null); - $platform->setSetting('_debug', $platform->debugMode ? 'true' : null); - } - } - - /** - * Adjust the settings for any tool properties being stored as a setting value. - * @param Tool $tool Tool object - * @param bool $isSave True if the settings are being saved - */ - protected function fixToolSettings(Tool $tool, bool $isSave) - { - if (!$isSave) { - $tool->encryptionMethod = $tool->getSetting('_encryption_method', $tool->encryptionMethod); - $tool->setSetting('_encryption_method'); - $tool->debugMode = $tool->getSetting('_debug', $tool->debugMode ? 'true' : 'false') === 'true'; - $tool->setSetting('_debug'); - if ($tool->debugMode) { - Util::$logLevel = Util::LOGLEVEL_DEBUG; - } - } else { - $tool->setSetting('_encryption_method', !empty($tool->encryptionMethod) ? $tool->encryptionMethod : null); - $tool->setSetting('_debug', $tool->debugMode ? 'true' : null); - } - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/Http/ClientInterface.php b/components/ILIAS/LTI/src/ToolProvider/Http/ClientInterface.php deleted file mode 100755 index 3d329703cfdd..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/Http/ClientInterface.php +++ /dev/null @@ -1,40 +0,0 @@ - - * @copyright SPV Software Products - * @license GNU Lesser General Public License, version 3 () - */ -interface ClientInterface -{ - /** - * Send the request to the target URL. - * - * @param HttpMessage $message - * - * @return bool True if the request was successful - */ - public function send(HttpMessage $message): bool; -} diff --git a/components/ILIAS/LTI/src/ToolProvider/Http/CurlClient.php b/components/ILIAS/LTI/src/ToolProvider/Http/CurlClient.php deleted file mode 100755 index 59f5a5a1c8d0..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/Http/CurlClient.php +++ /dev/null @@ -1,81 +0,0 @@ - - * @copyright SPV Software Products - * @license GNU Lesser General Public License, version 3 () - */ -class CurlClient implements ClientInterface -{ - /** - * Send the request to the target URL. - * - * @param HttpMessage $message - * - * @return bool True if the request was successful - */ - public function send(HttpMessage $message): bool - { - $ch = curl_init(); - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30); - curl_setopt($ch, CURLOPT_URL, $message->getUrl()); - if (!empty($message->requestHeaders)) { - curl_setopt($ch, CURLOPT_HTTPHEADER, $message->requestHeaders); - } else { - curl_setopt($ch, CURLOPT_HEADER, 0); - } - if ($message->getMethod() === 'POST') { - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $message->request); - } elseif ($message->getMethod() !== 'GET') { - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $message->getMethod()); - if (!is_null($message->request)) { - curl_setopt($ch, CURLOPT_POSTFIELDS, $message->request); - } - } - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLINFO_HEADER_OUT, true); - curl_setopt($ch, CURLOPT_HEADER, true); - $chResp = curl_exec($ch); - $message->requestHeaders = trim(str_replace("\r\n", "\n", curl_getinfo($ch, CURLINFO_HEADER_OUT))); - $chResp = str_replace("\r\n", "\n", $chResp); - $chRespSplit = explode("\n\n", $chResp, 2); - if ((count($chRespSplit) > 1) && (substr($chRespSplit[1], 0, 5) === 'HTTP/')) { - $chRespSplit = explode("\n\n", $chRespSplit[1], 2); - } - $message->responseHeaders = trim($chRespSplit[0]); - if (count($chRespSplit) > 1) { - $message->response = $chRespSplit[1]; - } else { - $message->response = ''; - } - $message->status = curl_getinfo($ch, CURLINFO_HTTP_CODE); - $message->ok = ($message->status >= 100) && ($message->status < 400); - if (!$message->ok) { - $message->error = curl_error($ch); - } - curl_close($ch); - - return $message->ok; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/Http/HttpMessage.php b/components/ILIAS/LTI/src/ToolProvider/Http/HttpMessage.php deleted file mode 100755 index 982f9ef006ef..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/Http/HttpMessage.php +++ /dev/null @@ -1,299 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class HttpMessage -{ - /** - * True if message was processed successfully. - * - * @var bool $ok - */ - public bool $ok = false; - - //UK:added - public object $responseJson; - - /** - * Request body. - * - * @var string|null $request - */ - public ?string $request = null; - - /** - * Request headers. - * - * @var string|array $requestHeaders - */ - public $requestHeaders = ''; - - /** - * Response body. - * - * @var string|null $response - */ - public ?string $response = null; - - /** - * Response headers. - * - * @var string|array $responseHeaders - */ - public $responseHeaders = ''; - - /** - * Relative links in response headers. - * - * @var array $relativeLinks - */ - public array $relativeLinks = array(); - - /** - * Status of response (0 if undetermined). - * - * @var int $status - */ - public int $status = 0; - - /** - * Error message - * - * @var string $error - */ - public string $error = ''; - - /** - * Request URL. - * - * @var string|null $url - */ - private ?string $url = null; - - /** - * Request method. - * - * @var string $method - */ - private ?string $method = null; - - /** - * The client used to send the request. - * - * @var ClientInterface $httpClient - */ - private static ?ClientInterface $httpClient = null; //changed ...= null - - /** - * Class constructor. - * @param string $url URL to send request to - * @param string $method Request method to use (optional, default is GET) - * @param mixed $params Associative array of parameter values to be passed or message body (optional, default is none) - * @param string|null $header Values to include in the request header (optional, default is none) - */ - public function __construct(string $url, string $method = 'GET', $params = null, string $header = null) - { - $this->url = $url; - $this->method = strtoupper($method); - if (is_array($params)) { - $this->request = http_build_query($params); - } else { - $this->request = $params; - } - if (!empty($header)) { - $this->requestHeaders = explode("\n", $header); - } - } - - /** - * Get the target URL for the request. - * - * @return string|null Request URL //UK: Changed from string to string|null - */ - public function getUrl(): ?string - { - return $this->url; - } - - /** - * Get the HTTP method for the request. - * - * @return string|null Message method //UK: Changed from string to string|null - */ - public function getMethod(): ?string - { - return $this->method; - } - - /** - * Set the HTTP client to use for sending the message. - * @param ClientInterface|null $httpClient - * @return void - */ - public static function setHttpClient(ClientInterface $httpClient = null) - { - self::$httpClient = $httpClient; - Util::logDebug('HttpClient set to \'' . get_class(self::$httpClient) . '\''); - } - - /** - * Get the HTTP client to use for sending the message. If one is not set, a default client is created. - * - * @return StreamClient|CurlClient|ClientInterface|null The HTTP client - */ - public static function getHttpClient() - { - if (!self::$httpClient) { - if (function_exists('curl_init')) { - self::$httpClient = new CurlClient(); - } elseif (ini_get('allow_url_fopen')) { - self::$httpClient = new StreamClient(); - } - if (self::$httpClient) { - Util::logDebug('HttpClient set to \'' . get_class(self::$httpClient) . '\''); - } - } - - return self::$httpClient; - } - - /** - * Send the request to the target URL. - * - * @return bool True if the request was successful - */ - public function send(): bool - { - $client = self::getHttpClient(); - $this->relativeLinks = array(); - if (empty($client)) { - $this->ok = false; - $message = 'No HTTP client interface is available'; - $this->error = $message; - Util::logError($message, true); - } elseif (empty($this->url)) { - $this->ok = false; - $message = 'No URL provided for HTTP request'; - $this->error = $message; - Util::logError($message, true); - } else { - $this->ok = $client->send($this); - $this->parseRelativeLinks(); - if (Util::$logLevel > Util::LOGLEVEL_NONE) { - $message = "Http\\HttpMessage->send {$this->method} request to '{$this->url}'"; - if (!empty($this->requestHeaders)) { - $message .= "\n{$this->requestHeaders}"; - } - if (!empty($this->request)) { - $message .= "\n\n{$this->request}"; - } - $message .= "\nResponse:"; - if (!empty($this->responseHeaders)) { - $message .= "\n{$this->responseHeaders}"; - } - if (!empty($this->response)) { - $message .= "\n\n{$this->response}"; - } - if ($this->ok) { - Util::logInfo($message); - } else { - if (!empty($this->error)) { - $message .= "\nError: {$this->error}"; - } - Util::logError($message); - } - } - } - - return $this->ok; - } - - /** - * Check whether a relative link of the specified type exists. - * @param string $rel - * @return bool True if it exists - */ - public function hasRelativeLink(string $rel): bool - { - return array_key_exists($rel, $this->relativeLinks); - } - - /** - * Get the URL from the relative link with the specified type. - * @param string $rel - * @return string|null The URL associated with the relative link, null if it is not defined - */ - public function getRelativeLink(string $rel): ?string - { - $url = null; - if ($this->hasRelativeLink($rel)) { - $url = $this->relativeLinks[$rel]; - } - - return $url; - } - - /** - * Get the relative links. - * - * @return array Associative array of relative links - */ - public function getRelativeLinks(): array - { - return $this->relativeLinks; - } - - ### - ### PRIVATE METHOD - ### - - /** - * Parse the response headers for relative links. - */ - private function parseRelativeLinks() - { - $matched = preg_match_all('/^(Link|link): *(.*)$/m', $this->responseHeaders, $matches); - if ($matched) { - for ($i = 0; $i < $matched; $i++) { - $links = explode(',', $matches[2][$i]); - foreach ($links as $link) { - if (preg_match('/^\<([^\>]+)\>; *rel=([^ ]+)$/', trim($link), $match)) { - $rel = strtolower(utf8_decode($match[2])); - if ((strpos($rel, '"') === 0) || (strpos($rel, '?') === 0)) { - $rel = substr($rel, 1, strlen($rel) - 2); - } - if ($rel === 'previous') { - $rel = 'prev'; - } - $this->relativeLinks[$rel] = $match[1]; - } - } - } - } - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/Http/StreamClient.php b/components/ILIAS/LTI/src/ToolProvider/Http/StreamClient.php deleted file mode 100755 index 6010300538a0..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/Http/StreamClient.php +++ /dev/null @@ -1,86 +0,0 @@ - - * @copyright SPV Software Products - * @license GNU Lesser General Public License, version 3 () - */ -class StreamClient implements ClientInterface -{ - /** - * Send the request to the target URL. - * - * @param HttpMessage $message - * - * @return bool True if the request was successful - */ -// public function send(HttpMessage $message) - public function send(\ILIAS\LTI\ToolProvider\Http\HttpMessage $message): bool - { - if (empty($message->requestHeaders)) { - $message->requestHeaders = ["Accept: */*"]; - } elseif (count(preg_grep("/^Accept:/", $message->requestHeaders)) === 0) { - $message->requestHeaders[] = "Accept: */*"; - } - $opts = [ - 'method' => $message->getMethod(), - 'content' => $message->request, - 'header' => $message->requestHeaders, - 'ignore_errors' => true, - ]; - - $message->requestHeaders = implode("\n", $message->requestHeaders); - try { - $ctx = stream_context_create(['http' => $opts]); - $fp = @fopen($message->getUrl(), 'rb', false, $ctx); - if ($fp) { - $resp = @stream_get_contents($fp); - $message->ok = $resp !== false; - if ($message->ok) { - $message->response = $resp; - // see http://php.net/manual/en/reserved.variables.httpresponseheader.php - if (isset($http_response_header[0])) { - $message->responseHeaders = trim(implode("\n", $http_response_header)); - if (preg_match("/HTTP\/\d.\d\s+(\d+)/", $http_response_header[0], $out)) { - $message->status = $out[1]; - } - $message->ok = $message->status < 400; - if (!$message->ok) { - $message->error = $http_response_header[0]; - } - } - return $message->ok; - } - } - } catch (\Exception $e) { - $message->error = $e->getMessage(); - $message->ok = false; - return false; - } - $message->error = error_get_last()["message"]; - $message->ok = false; - return false; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/Jwt/ClientInterface.php b/components/ILIAS/LTI/src/ToolProvider/Jwt/ClientInterface.php deleted file mode 100755 index 1504cf6d024e..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/Jwt/ClientInterface.php +++ /dev/null @@ -1,175 +0,0 @@ - - * @copyright SPV Software Products - * @license GNU Lesser General Public License, version 3 () - */ -interface ClientInterface -{ - /** - * Return an array of supported signature algorithms. - * - * @return string[] Array of algorithm names - */ - public static function getSupportedAlgorithms(): array; - - /** - * Check if a JWT is defined. - * - * @return bool True if a JWT is defined - */ - public function hasJwt(): bool; - - /** - * Check if a JWT's content is encrypted. - * - * @return bool True if a JWT is encrypted - */ - public function isEncrypted(): bool; - - /** - * Load a JWT from a string. - * @param string $jwtString JWT string - * @param string|null $privateKey Private key in PEM format for decrypting encrypted tokens (optional) - * @return bool True if the JWT was successfully loaded - */ - public function load(string $jwtString, string $privateKey = null): bool; - - /** - * Get the value of the JWE headers. - * - * @return array The value of the JWE headers - */ - public function getJweHeaders(): array; - - /** - * Check whether a JWT has a header with the specified name. - * @param string $name Header name - * @return bool True if the JWT has a header of the specified name - */ - public function hasHeader(string $name): bool; - - /** - * Get the value of the header with the specified name. - * @param string $name Header name - * @param string|null $defaultValue Default value - * @return string The value of the header with the specified name, or the default value if it does not exist - */ - public function getHeader(string $name, string $defaultValue = null): string; - - /** - * Get the value of the headers. - * - * @return array The value of the headers - */ - public function getHeaders(): array; - - /** - * Get the value of the headers for the last signed JWT (before any encryption). - * - * @return array The value of the headers - */ - public static function getLastHeaders(): array; - - /** - * Check whether a JWT has a claim with the specified name. - * @param string $name Claim name - * @return bool True if the JWT has a claim of the specified name - */ - public function hasClaim(string $name): bool; - - /** - * Get the value of the claim with the specified name. - * @param string $name Claim name - * @param string|null $defaultValue Default value - * @return string|array|object The value of the claim with the specified name, or the default value if it does not exist - */ - public function getClaim(string $name, string $defaultValue = null); - - /** - * Get the value of the payload. - * - * @return array The value of the payload - */ - public function getPayload(): array; - - /** - * Get the value of the payload for the last signed JWT (before any encryption). - * - * @return array The value of the payload - */ - public static function getLastPayload(): array; - - /** - * Verify the signature of the JWT. - * @param string $publicKey Public key of issuer - * @param string|null $jku JSON Web Key URL of issuer (optional) - * @return bool True if the JWT has a valid signature - */ - public function verify(string $publicKey, string $jku = null): bool; - - /** - * Sign the JWT. - * @param array $payload Payload - * @param string $signatureMethod Signature method - * @param string $privateKey Private key in PEM format - * @param string|null $kid Key ID (optional) - * @param string|null $jku JSON Web Key URL (optional) - * @param string|null $encryptionMethod Encryption method (optional) - * @param string|null $publicKey Public key of recipient for content encryption (optional) - * @return string Signed JWT - */ - public static function sign( - array $payload, - string $signatureMethod, - string $privateKey, - string $kid = null, - string $jku = null, - string $encryptionMethod = null, - string $publicKey = null - ): string; - - /** - * Generate a new private key in PEM format. - * @param string $signatureMethod Signature method - * @return string|null Key in PEM format - */ - public static function generateKey(string $signatureMethod = 'RS256'): ?string; - - /** - * Get the public key for a private key. - * @param string $privateKey Private key in PEM format - * @return string Public key in PEM format - */ - public static function getPublicKey(string $privateKey): string; - - /** - * Get the public JWKS from a key in PEM format. - * @param string $pemKey Private or public key in PEM format - * @param string $signatureMethod Signature method - * @param string $kid Key ID (optional) - * @return array JWKS keys - */ - public static function getJWKS(string $pemKey, string $signatureMethod, string $kid): array; -} diff --git a/components/ILIAS/LTI/src/ToolProvider/Jwt/FirebaseClient.php b/components/ILIAS/LTI/src/ToolProvider/Jwt/FirebaseClient.php deleted file mode 100755 index cdd4a2e44bda..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/Jwt/FirebaseClient.php +++ /dev/null @@ -1,429 +0,0 @@ - - * @copyright SPV Software Products - * @license GNU Lesser General Public License, version 3 () - */ -class FirebaseClient implements ClientInterface -{ - /** - * Supported signature algorithms. - */ - public const SUPPORTED_ALGORITHMS = array('RS256', 'RS384', 'RS512'); - - private ?string $jwtString = null; - private ?array $jwtHeaders = null; - private ?array $jwtPayload = null; - private static ?array $lastHeaders = null; - private static ?array $lastPayload = null; - - /** - * Return an array of supported signature algorithms. - * - * @return string[] Array of algorithm names - */ - public static function getSupportedAlgorithms(): array - { - return self::SUPPORTED_ALGORITHMS; - } - - /** - * Check if a JWT is defined. - * - * @return bool True if a JWT is defined - */ - public function hasJwt(): bool - { - return !empty($this->jwtString); - } - - /** - * Check if a JWT's content is encrypted. - * - * @return bool True if a JWT is encrypted - */ - public function isEncrypted(): bool - { - return false; // Not supported by this client - } - - /** - * Load a JWT from a string. - * @param string $jwtString JWT string - * @param string|null $privateKey Private key in PEM format for decrypting encrypted tokens (optional) - * @return bool True if the JWT was successfully loaded - */ - public function load(string $jwtString, string $privateKey = null): bool - { - $sections = explode('.', $jwtString); - $ok = count($sections) === 3; - if ($ok) { - $headers = json_decode(JWT::urlsafeB64Decode($sections[0]), true); //changed - $payload = json_decode(JWT::urlsafeB64Decode($sections[1]), true); //changed - $ok = !is_null($headers) && !is_null($payload); - } - if ($ok) { - $this->jwtString = $jwtString; - $this->jwtHeaders = $headers; - $this->jwtPayload = $payload; - } else { - $this->jwtString = null; - $this->jwtHeaders = null; - $this->jwtPayload = null; - } - - return $ok; - } - - /** - * Get the value of the JWE headers. - * - * @return array The value of the JWE headers - */ - public function getJweHeaders(): array - { - return array(); // Encryption not supported by this client - } - - /** - * Check whether a JWT has a header with the specified name. - * @param string $name Header name - * @return bool True if the JWT has a header of the specified name - */ - public function hasHeader(string $name): bool - { - return !empty($this->jwtHeaders) && isset($this->jwtHeaders[$name]); //changed - } - - /** - * Get the value of the header with the specified name. - * @param string $name Header name - * @param string|null $defaultValue Default value - * @return string The value of the header with the specified name, or the default value if it does not exist - */ - public function getHeader(string $name, string $defaultValue = null): string - { - if ($this->hasHeader($name)) { - $value = $this->jwtHeaders[$name]; //changed - } else { - $value = $defaultValue; - } - - return $value; - } - - /** - * Get the value of the headers. - * - * @return array The value of the headers - */ - public function getHeaders(): array - { - return $this->jwtHeaders; - } - - /** - * Get the value of the headers for the last signed JWT (before any encryption). - * - * @return array The value of the headers - */ - public static function getLastHeaders(): array - { - return self::$lastHeaders; - } - - /** - * Check whether a JWT has a claim with the specified name. - * @param string $name Claim name - * @return bool True if the JWT has a claim of the specified name - */ - public function hasClaim(string $name): bool - { - return !empty($this->jwtPayload) && isset($this->jwtPayload[$name]); //changed - } - - /** - * Get the value of the claim with the specified name. - * @param string $name Claim name - * @param string|null $defaultValue Default value - * @return string|array|object The value of the claim with the specified name, or the default value if it does not exist - */ - public function getClaim(string $name, string $defaultValue = null) - { - if ($this->hasClaim($name)) { - $value = $this->jwtPayload[$name]; //changed - } else { - $value = $defaultValue; - } - - return $value; - } - - /** - * Get the value of the payload. - * - * @return array The value of the payload - */ - public function getPayload(): array - { - return $this->jwtPayload; - } - - /** - * Get the value of the payload for the last signed JWT (before any encryption). - * - * @return array The value of the payload - */ - public static function getLastPayload(): array - { - return self::$lastPayload; - } - - /** - * Verify the signature of the JWT. - * @param string $publicKey Public key of issuer - * @param string|null $jku JSON Web Key URL of issuer (optional) - * @return bool True if the JWT has a valid signature - */ - public function verify(string $publicKey, string $jku = null): bool - { - $ok = false; - $hasPublicKey = !empty($publicKey); - if ($hasPublicKey) { - if (is_string($publicKey)) { - $json = json_decode($publicKey, true); - if (!is_null($json)) { - try { - $jwks = array('keys' => array($json)); - $publicKey = static::parseKeySet($jwks); - } catch (\Exception $e) { - } - } else { - $publicKey = new Key($publicKey, $this->getHeader('alg')); - } - } - } elseif (!empty($jku)) { - $publicKey = $this->fetchPublicKey($jku); - } - JWT::$leeway = Jwt::$leeway; - $retry = false; - - do { - try { - JWT::decode($this->jwtString, $publicKey); - $ok = true; - } catch (\Exception $e) { - Util::logError($e->getMessage()); - if ($retry) { - $retry = false; - } elseif ($hasPublicKey && !empty($jku)) { - try { - $publicKey = $this->fetchPublicKey($jku); - $retry = true; - } catch (\Exception $e) { - } - } - } - } while (!$ok && $retry); - - return $ok; - } - - /** - * Sign the JWT. - * @param array $payload Payload - * @param string $signatureMethod Signature method - * @param string $privateKey Private key in PEM format - * @param string|null $kid Key ID (optional) - * @param string|null $jku JSON Web Key URL (optional) - * @param string|null $encryptionMethod Encryption method (optional) - * @param string|null $publicKey Public key of recipient for content encryption (optional) - * @return string Signed JWT - */ - public static function sign( - array $payload, - string $signatureMethod, - string $privateKey, - string $kid = null, - string $jku = null, - string $encryptionMethod = null, - string $publicKey = null - ): string { - if (!empty($encryptionMethod)) { - $errorMessage = 'Encrypted tokens not supported by the Firebase JWT client'; - Util::logError($errorMessage); - throw new \Exception($errorMessage); - } - $jwtString = JWT::encode($payload, $privateKey, $signatureMethod, $kid); - $sections = explode('.', $jwtString); - self::$lastHeaders = json_decode(JWT::urlsafeB64Decode($sections[0])); - self::$lastPayload = json_decode(JWT::urlsafeB64Decode($sections[1])); - - return $jwtString; - } - - /** - * Generate a new private key in PEM format. - * @param string $signatureMethod Signature method - * @return string|null Key in PEM format - */ - public static function generateKey(string $signatureMethod = 'RS256'): ?string - { - $privateKey = null; - switch ($signatureMethod) { - case 'RS512': - $size = 4096; - break; - case 'RS384': - $size = 3072; - break; - default: - $size = 2048; - break; - } - $config = array( - "private_key_bits" => $size, - "private_key_type" => OPENSSL_KEYTYPE_RSA - ); - $res = openssl_pkey_new($config); - if (openssl_pkey_export($res, $privateKey)) { - $privateKey = str_replace('-----BEGIN PRIVATE KEY-----', '-----BEGIN RSA PRIVATE KEY-----', $privateKey); - $privateKey = str_replace('-----END PRIVATE KEY-----', '-----END RSA PRIVATE KEY-----', $privateKey); - } - - return $privateKey; - } - - /** - * Get the public key for a private key. - * @param string $privateKey Private key in PEM format - * @return string Public key in PEM format - */ - public static function getPublicKey(string $privateKey): string - { - $publicKey = null; - $res = openssl_pkey_get_private($privateKey); - if ($res !== false) { - $details = openssl_pkey_get_details($res); - $publicKey = $details['key']; - } - - return $publicKey; - } - - /** - * Get the public JWKS from a key in PEM format. - * @param string $pemKey Private or public key in PEM format - * @param string $signatureMethod Signature method - * @param string|null $kid Key ID (optional) - * @return array JWKS keys - */ - public static function getJWKS(string $pemKey, string $signatureMethod, string $kid = null): array - { - $keys['keys'] = array(); - $res = openssl_pkey_get_private($pemKey); - if ($res === false) { - $res = openssl_pkey_get_public($pemKey); - } - if ($res !== false) { - $details = openssl_pkey_get_details($res); - $key = [ - 'kty' => 'RSA', - 'n' => JWT::urlsafeB64Encode($details['rsa']['n']), - 'e' => JWT::urlsafeB64Encode($details['rsa']['e']), - 'alg' => $signatureMethod, - 'use' => 'sig' - ]; - if (!empty($kid)) { - $key['kid'] = $kid; - } - $keys['keys'][] = $key; - } - - return $keys; - } - - ### - ### PRIVATE METHODS - ### - - /** - * Fetch the public keys from a URL. - * @param string $jku Endpoint for retrieving JSON web keys - * @return array Array of keys - */ - private function fetchPublicKey(string $jku): array - { - $publicKey = array(); - $http = new HttpMessage($jku); - if ($http->send()) { - $keys = json_decode($http->response, true); - $publicKey = static::parseKeySet($keys); - } - return $publicKey; - } - - /** - * Parse a set of JWK keys. - * This function is based on Firebase\JWT\JWK::parseKeySet but returns an array containing Key objects rather than an OpenSSL key - * resource so that the algorithm associated with each key can be identified. - * @param array $jwks The JSON Web Key Set as an associative array - * @return array An associative array of Key objects - * @throws JWK::InvalidArgumentException Provided JWK Set is empty //UK:added JWK:: - * @throws JWK::UnexpectedValueException Provided JWK Set was invalid - * @throws JWK::DomainException OpenSSL failure - */ - private static function parseKeySet(array $jwks): array - { - $keys = array(); - if (!isset($jwks['keys'])) { - throw new \UnexpectedValueException('"keys" member must exist in the JWK Set'); - } - if (empty($jwks['keys'])) { - throw new \InvalidArgumentException('JWK Set did not contain any keys'); - } - - foreach ($jwks['keys'] as $k => $v) { - if (!empty($v['alg'])) { - $kid = isset($v['kid']) ? $v['kid'] : $k; - if ($key = JWK::parseKey($v)) { - $keys[$kid] = $key; //changed from - //$keys[$kid] = new Key($key, $v['alg']); - } - } - } - - if (empty($keys)) { - throw new \UnexpectedValueException('No supported algorithms found in JWK Set'); - } - - return $keys; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/Jwt/Jwt.php b/components/ILIAS/LTI/src/ToolProvider/Jwt/Jwt.php deleted file mode 100755 index 318e705f1b83..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/Jwt/Jwt.php +++ /dev/null @@ -1,93 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class Jwt -{ - /** - * Life (in seconds) of an issued JWT (default is 1 minute). - * - * @var int $life - */ - public static int $life = 60; - - /** - * Leeway (in seconds) to allow when checking timestamps (default is 3 minutes). - * - * @var int $leeway - */ - public static int $leeway = 180; - - /** - * Allow use of jku header in JWT. - * - * @var bool $allowJkuHeader - */ - public static bool $allowJkuHeader = false; - - /** - * The client used to handle JWTs. - * - * @var ClientInterface $jwtClient - */ - private static ?ClientInterface $jwtClient = null; //changed ... = null - - /** - * Class constructor. - */ - public function __construct() - { - } - - /** - * Set the JWT client to use for handling JWTs. - * - * @param \ILIAS\LTI\ToolProvider\Jwt\ClientInterface|null $jwtClient - * - * @return void - */ - public static function setJwtClient(\ILIAS\LTI\ToolProvider\Jwt\ClientInterface $jwtClient = null) - { - self::$jwtClient = $jwtClient; - Util::logDebug('JwtClient set to \'' . get_class(self::$jwtClient) . '\''); - } - - /** - * Get the JWT client to use for handling JWTs. If one is not set, a default client is created. - * - * @return ClientInterface|null The JWT client - */ - public static function getJwtClient() - { - if (empty(self::$jwtClient)) { //changed - self::$jwtClient = new FirebaseClient(); - Util::logDebug('JwtClient set to \'' . get_class(self::$jwtClient) . '\''); - } - return self::$jwtClient; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/LineItem.php b/components/ILIAS/LTI/src/ToolProvider/LineItem.php deleted file mode 100755 index 624871b264eb..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/LineItem.php +++ /dev/null @@ -1,196 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class LineItem -{ - /** - * Label value. - * - * @var string $label - */ - public ?string $label = null; - - /** - * Points possible value. - * - * @var int $pointsPossible - */ - public int $pointsPossible = 1; - - /** - * LTI Resource Link ID with which the line item is associated. - * - * @var string|null $ltiResourceLinkId - */ - public ?string $ltiResourceLinkId = null; - - /** - * Tool resource ID associated with the line item. - * - * @var string|null $resourceId - */ - public ?string $resourceId = null; - - /** - * Tag value. - * - * @var string|null $tag - */ - public ?string $tag = null; - - /** - * Outcome start submit timestamp. - * - * @var int|null $submitFrom - */ - public ?int $submitFrom = null; - - /** - * Outcome end submit timestamp. - * - * @var int|null $submitUntil - */ - public ?int $submitUntil = null; - - /** - * Line item endpoint. - * - * @var string $endpoint - */ - public ?string $endpoint = null; - - /** - * Platform for this line item. - * - * @var Platform|null $platform - */ - private ?Platform $platform = null; - - /** - * Class constructor. - * @param Platform $platform Platform object - * @param string $label Label - * @param int $pointsPossible Points possible value - */ - public function __construct(Platform $platform, string $label, int $pointsPossible) - { - $this->platform = $platform; - $this->label = $label; - $this->pointsPossible = $pointsPossible; - } - - /** - * Get platform. - * - * @return Platform Platform object for this line item. - */ - public function getPlatform(): ?Platform - { - return $this->platform; - } - - /** - * Save the line item to the platform. - * - * @return bool True if successful - */ - public function save(): bool - { - $service = new Service\LineItem($this->platform, $this->endpoint); - return $service->saveLineItem($this); - } - - /** - * Delete the line item on the platform. - * - * @return bool True if successful - */ - public function delete(): bool - { - $service = new Service\LineItem($this->platform, $this->endpoint); - return $service->deleteLineItem($this); - } - - /** - * Retrieve all outcomes. - * @param int|null $limit Limit of outcomes to be returned in each request, null for service default - * @return Outcome[]|bool Array of outcome objects, or false on error - */ - public function getOutcomes(int $limit = null) - { - $resultService = new Service\Result($this->platform, $this->endpoint); - return $resultService->getAll(); - } - - /** - * Retrieve the outcome for a user. - * @param User $user User object - * @return Outcome|null|bool Outcome object, or null if none, or false on error - */ - public function readOutcome(User $user) - { - $resultService = new Service\Result($this->platform, $this->endpoint); - return $resultService->get($user); - } - - /** - * Submit the outcome for a user. - * @param Outcome $ltiOutcome Outcome object - * @param User $user User object - * @return bool True if successful - */ - public function submitOutcome(Outcome $ltiOutcome, User $user): bool - { - $scoreService = new Service\Score($this->platform, $this->endpoint); - return $scoreService->submit($ltiOutcome, $user); - } - - /** - * Delete the outcome for a user. - * @param User $user User object - * @return bool True if successful, otherwise false - */ - public function deleteOutcome(User $user): bool - { - $ltiOutcome = new Outcome(); - $scoreService = new Service\Score($this->platform, $this->endpoint); - return $scoreService->submit($ltiOutcome, $user); - } - - /** - * Retrieve a line item definition. - * @param Platform $platform Platform object - * @param string $endpoint ID value - * @return LineItem|bool LineItem object or false on error - */ - public static function fromEndpoint(Platform $platform, string $endpoint) - { - return Service\LineItem::getLineItem($platform, $endpoint); - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/MediaType/Message.php b/components/ILIAS/LTI/src/ToolProvider/MediaType/Message.php deleted file mode 100755 index 83f84ea24752..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/MediaType/Message.php +++ /dev/null @@ -1,61 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class Message -{ - /** - * Class constructor. - * @param \ILIAS\LTI\ToolProvider\Profile\Message $message Message object //UK: changed from Message to \ILIAS\LTI\ToolProvider\Profile\Message - * @param array $capabilitiesOffered Capabilities offered - */ - public function __construct(\ILIAS\LTI\ToolProvider\Profile\Message $message, array $capabilitiesOffered) - { - $this->message_type = $message->type; - $this->path = $message->path; - $this->enabled_capability = array(); - foreach ($message->capabilities as $capability) { - if (in_array($capability, $capabilitiesOffered)) { - $this->enabled_capability[] = $capability; - } - } - $this->parameter = array(); - foreach ($message->constants as $name => $value) { - $parameter = new \stdClass(); - $parameter->name = $name; - $parameter->fixed = $value; - $this->parameter[] = $parameter; - } - foreach ($message->variables as $name => $value) { - if (in_array($value, $capabilitiesOffered)) { - $parameter = new \stdClass(); - $parameter->name = $name; - $parameter->variable = $value; - $this->parameter[] = $parameter; - } - } - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/MediaType/ResourceHandler.php b/components/ILIAS/LTI/src/ToolProvider/MediaType/ResourceHandler.php deleted file mode 100755 index fc12381f513a..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/MediaType/ResourceHandler.php +++ /dev/null @@ -1,64 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class ResourceHandler -{ - /** - * Class constructor. - * @param Tool $tool Tool object - * @param Profile\ResourceHandler $resourceHandler Profile resource handler object - */ - public function __construct(Tool $tool, Profile\ResourceHandler $resourceHandler) - { - $this->resource_type = new \stdClass(); - $this->resource_type->code = $resourceHandler->item->id; - $this->resource_name = new \stdClass(); - $this->resource_name->default_value = $resourceHandler->item->name; - $this->resource_name->key = "{$resourceHandler->item->id}.resource.name"; - $this->description = new \stdClass(); - $this->description->default_value = $resourceHandler->item->description; - $this->description->key = "{$resourceHandler->item->id}.resource.description"; - $icon_info = new \stdClass(); - $icon_info->default_location = new \stdClass(); - $icon_info->default_location->path = $resourceHandler->icon; - $icon_info->key = "{$resourceHandler->item->id}.icon.path"; - $this->icon_info = array(); - $this->icon_info[] = $icon_info; - $this->message = array(); - foreach ($resourceHandler->requiredMessages as $message) { - $this->message[] = new Message($message, $tool->platform->profile->capability_offered); - } - foreach ($resourceHandler->optionalMessages as $message) { - if (in_array($message->type, $tool->platform->profile->capability_offered)) { - $this->message[] = new Message($message, $tool->platform->profile->capability_offered); - } - } - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/MediaType/SecurityContract.php b/components/ILIAS/LTI/src/ToolProvider/MediaType/SecurityContract.php deleted file mode 100755 index 1a88e362fc86..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/MediaType/SecurityContract.php +++ /dev/null @@ -1,88 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class SecurityContract -{ - /** - * Class constructor. - * @param Tool $tool Tool instance - * @param string $secret Shared secret - */ - public function __construct(Tool $tool, string $secret) - { - $tcContexts = array(); - foreach ($tool->platform->profile->{'@context'} as $context) { - if (is_object($context)) { - $tcContexts = array_merge(get_object_vars($context), $tcContexts); - } - } - - $this->shared_secret = $secret; - $toolServices = array(); - foreach ($tool->requiredServices as $requiredService) { - foreach ($requiredService->formats as $format) { - $service = $tool->findService($format, $requiredService->actions); - if (($service !== false) && !array_key_exists($service->{'@id'}, $toolServices)) { - $id = $service->{'@id'}; - $parts = explode(':', $id, 2); - if (count($parts) > 1) { - if (array_key_exists($parts[0], $tcContexts)) { - $id = "{$tcContexts[$parts[0]]}{$parts[1]}"; - } - } - $toolService = new \stdClass(); - $toolService->{'@type'} = 'RestServiceProfile'; - $toolService->service = $id; - $toolService->action = $requiredService->actions; - $toolServices[$service->{'@id'}] = $toolService; - } - } - } - foreach ($tool->optionalServices as $optionalService) { - foreach ($optionalService->formats as $format) { - $service = $tool->findService($format, $optionalService->actions); - if (($service !== false) && !array_key_exists($service->{'@id'}, $toolServices)) { - $id = $service->{'@id'}; - $parts = explode(':', $id, 2); - if (count($parts) > 1) { - if (array_key_exists($parts[0], $tcContexts)) { - $id = "{$tcContexts[$parts[0]]}{$parts[1]}"; - } - } - $toolService = new \stdClass(); - $toolService->{'@type'} = 'RestServiceProfile'; - $toolService->service = $id; - $toolService->action = $optionalService->actions; - $toolServices[$service->{'@id'}] = $toolService; - } - } - } - $this->tool_service = array_values($toolServices); - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/OAuthDataStore.php b/components/ILIAS/LTI/src/ToolProvider/OAuthDataStore.php deleted file mode 100755 index b5a0c259dd04..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/OAuthDataStore.php +++ /dev/null @@ -1,138 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class OAuthDataStore extends LTIOAuth\OAuthDataStore -{ - /** - * System object. - * - * @var Tool|Platform|null $system - */ - private $system = null; - - /** - * Class constructor. - * - * @param Tool|Platform $system System object - */ - public function __construct($system) - { - $this->system = $system; - } - - /** - * Create an OAuthConsumer object for the system. - * @param string $consumerKey Consumer key value - * @return OAuthConsumer OAuthConsumer object - */ - public function lookup_consumer(string $consumerKey): OAuthConsumer - { - $key = $this->system->getKey(); - $secret = ''; - if (!empty($key)) { - $secret = $this->system->secret; - } elseif (($this->system instanceof Tool) && !empty($this->system->platform)) { - $key = $this->system->platform->getKey(); - $secret = $this->system->platform->secret; - } elseif (($this->system instanceof Platform) && !empty(Tool::$defaultTool)) { - $key = Tool::$defaultTool->getKey(); - $secret = Tool::$defaultTool->secret; - } - if ($key !== $consumerKey) { - throw new OAuthException('Consumer key not found'); - } - - return new OAuthConsumer($key, $secret); - } - - /** - * Create an OAuthToken object for the system. - * @param OAuthConsumer $consumer OAuthConsumer object //UK: removed string - * @param string $tokenType Token type - * @param string $token Token value - * @return OAuthToken OAuthToken object - */ - public function lookup_token(OAuthConsumer $consumer, string $tokenType, string $token): OAuthToken - { - return new OAuthToken($consumer, ''); - } - - /** - * Lookup nonce value for the system. - * @param OAuthConsumer $consumer OAuthConsumer object - * @param OAuthToken|null $token Token value //UK: removed string - * @param string $value Nonce value - * @param int $timestamp Date/time of request //UK: removed string - * @return bool True if the nonce value already exists - */ - public function lookup_nonce(OAuthConsumer $consumer, ?OAuthToken $token, string $value, int $timestamp): bool - { - if ($this->system instanceof Platform) { - $platform = $this->system; - } else { - $platform = $this->system->platform; - } - $nonce = new PlatformNonce($platform, $value); - $ok = !$nonce->load(); - if ($ok) { - $ok = $nonce->save(); - } - if (!$ok) { - $this->system->reason = 'Invalid nonce.'; - } - - return !$ok; - } - - /** - * Get new request token. - * @param OAuthConsumer $consumer OAuthConsumer object - * @param mixed $callback Callback URL //UK: removed string CHECK - * @return string Null value - */ - public function new_request_token(OAuthConsumer $consumer, $callback = null): ?string - { - return null; - } - - /** - * Get new access token. - * @param OAuthToken $token Token value //UK: removed string CHECK - * @param OAuthConsumer $consumer OAuthConsumer object - * @param string $verifier Verification code - * @return string Null value - */ - public function new_access_token(OAuthToken $token, OAuthConsumer $consumer, $verifier = null): ?string - { - return null; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/Outcome.php b/components/ILIAS/LTI/src/ToolProvider/Outcome.php deleted file mode 100755 index 0884b2f79450..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/Outcome.php +++ /dev/null @@ -1,190 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class Outcome -{ - /** - * Allowed values for Activity Progress. - */ - public const ALLOWED_ACTIVITY_PROGRESS = array( - 'Initialized', - 'Started', - 'InProgress', - 'Submitted', - 'Completed' - ); - - /** - * Allowed values for Grading Progress. - */ - public const ALLOWED_GRADING_PROGRESS = array( - 'FullyGraded', - 'Pending', - 'PendingManual', - 'Failed', - 'NotReady' - ); - - /** - * Language value. - * - * @var string|null $language - */ - public ?string $language = null; - - /** - * Outcome status value. - * - * @var string|null $status - */ - public ?string $status = null; - - /** - * Outcome date value. - * - * @var string|null $date - */ - public ?string $date = null; - - /** - * Outcome type value. - * - * @var string|null $type - */ - public ?string $type = null; - - /** - * Activity progress. - * - * @var string|null $activityProgress - */ - public ?string $activityProgress = null; - - /** - * Grading progress. - * - * @var string|null $gradingProgress - */ - public ?string $gradingProgress = null; - - /** - * Comment. - * - * @var string|null $comment - */ - public ?string $comment = null; - - /** - * Outcome data source value. - * - * @var string|null $dataSource - */ - public ?string $dataSource = null; - - /** - * Outcome value. - * - * @var mixed|string|null $value - */ - private $value = null; - - /** - * Points possible value. - * - * @var int $pointsPossible - */ - private int $pointsPossible = 1; - - /** - * Class constructor. - * @param mixed $value Outcome value (optional, default is none) - * @param int $pointsPossible Points possible value (optional, default is none) - * @param string $activityProgress Activity progress (optional, default is 'Completed') - * @param string $gradingProgress Grading progress (optional, default is 'FullyGraded') - */ - public function __construct( - $value = null, - int $pointsPossible = 1, - string $activityProgress = 'Completed', - string $gradingProgress = 'FullyGraded' - ) { - $this->value = $value; - $this->pointsPossible = $pointsPossible; - $this->language = 'en-US'; - $this->date = gmdate('Y-m-d\TH:i:s\Z', time()); - $this->type = 'decimal'; - if (in_array($activityProgress, self::ALLOWED_ACTIVITY_PROGRESS)) { - $this->activityProgress = $activityProgress; - } else { - $this->activityProgress = 'Completed'; - } - if (in_array($gradingProgress, self::ALLOWED_GRADING_PROGRESS)) { - $this->gradingProgress = $gradingProgress; - } else { - $this->gradingProgress = 'FullyGraded'; - } - $this->comment = ''; - } - - /** - * Get the outcome value. - * - * @return string Outcome value - */ - public function getValue(): ?string - { - return $this->value; - } - - /** - * Set the outcome value. - * @param string $value Outcome value - */ - public function setValue(string $value) - { - $this->value = $value; - } - - /** - * Get the points possible value. - * - * @return int|null Points possible value - */ - public function getPointsPossible(): ?int - { - return $this->pointsPossible; - } - - /** - * Set the points possible value. - * @param int|null $pointsPossible Points possible value - */ - public function setPointsPossible(?int $pointsPossible) - { - $this->pointsPossible = $pointsPossible; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/Platform.php b/components/ILIAS/LTI/src/ToolProvider/Platform.php deleted file mode 100755 index 899a02934df5..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/Platform.php +++ /dev/null @@ -1,729 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ - -class Platform -{ - use System; - use ApiHook; //added UK - - /** - * List of supported incoming message types. - */ - public static array $MESSAGE_TYPES = array( - 'ContentItemSelection', - 'LtiStartAssessment' - ); - - /** - * Local name of platform. - * - * @var string|null $name - */ - public ?string $name = null; - - /** - * Platform ID. - * - * @var string|null $platformId - */ - public ?string $platformId = null; - - /** - * Client ID. - * - * @var string|null $clientId - */ - public ?string $clientId = null; - - /** - * Deployment ID. - * - * @var string|null $deploymentId - */ - public ?string $deploymentId = null; - - /** - * Authorization server ID. - * - * @var string|null $authorizationServerId - */ - public ?string $authorizationServerId = null; - - /** - * Login authentication URL. - * - * @var string|null $authenticationUrl - */ - public ?string $authenticationUrl = null; - - /** - * Access Token service URL. - * - * @var string|null $accessTokenUrl - */ - public ?string $accessTokenUrl = null; - - /** - * LTI version (as reported by last platform connection). - * - * @var string|null $ltiVersion - */ - public ?string $ltiVersion = null; - - /** - * Name of tool consumer (as reported by last tool consumer connection). - * - * @var string|null $consumerName - */ - public ?string $consumerName = null; - - /** - * Tool consumer version (as reported by last tool consumer connection). - * - * @var string|null $consumerVersion - */ - public ?string $consumerVersion = null; - - /** - * The platform profile data. - * - * @var object|null $profile - */ - public ?object $profile = null; - - /** - * The tool proxy. - * - * @var object|null $toolProxy - */ - public ?object $toolProxy = null; - - /** - * Tool consumer GUID (as reported by first tool consumer connection). - * - * @var string|null $consumerGuid - */ - public ?string $consumerGuid = null; - - /** - * Optional CSS path (as reported by last tool consumer connection). - * - * @var string $cssPath - */ - public ?string $cssPath = null; - - /** - * Access token to authorize service requests. - * - * @var AccessToken|null $accessToken - */ - private ?AccessToken $accessToken = null; - - /** - * Get the authorization access token - * - * @return AccessToken Access token - */ - public function getAccessToken(): ?AccessToken - { - return $this->accessToken; - } - - /** - * Set the authorization access token - * @param AccessToken $accessToken Access token - */ - public function setAccessToken(AccessToken $accessToken) - { - $this->accessToken = $accessToken; - } - - /** - * Whether the platform instance is protected by matching the consumer_guid value in incoming requests. - * - * @var bool $protected - */ - public bool $protected = false; - - /** - * Default scope to use when generating an Id value for a user. - * - * @var int $idScope - */ - public int $idScope = Tool::ID_SCOPE_ID_ONLY; - - /** - * Default email address (or email domain) to use when no email address is provided for a user. - * - * @var string $defaultEmail - */ - public string $defaultEmail = ''; - - /** - * HttpMessage object for last service request. - * - * @var HttpMessage|null $lastServiceRequest - */ - public ?HTTPMessage $lastServiceRequest = null; - - /** - * Class constructor. - * @param DataConnector|null $dataConnector A data connector object - */ - public function __construct(DataConnector $dataConnector = null) - { - $this->initialize(); - if (empty($dataConnector)) { - $dataConnector = DataConnector::getDataConnector(); - } - $this->dataConnector = $dataConnector; - } - - /** - * Initialise the platform. - */ - public function initialize() - { - $this->id = null; - $this->key = null; - $this->name = null; - $this->secret = null; - $this->signatureMethod = 'HMAC-SHA1'; - $this->encryptionMethod = ''; //changed from null - $this->rsaKey = null; - $this->kid = null; - $this->jku = null; - $this->platformId = null; - $this->clientId = null; - $this->deploymentId = null; - $this->ltiVersion = null; - $this->consumerName = null; - $this->consumerVersion = null; - $this->consumerGuid = null; - $this->profile = null; - $this->toolProxy = null; - $this->settings = array(); - $this->protected = false; - $this->enabled = false; - $this->enableFrom = null; - $this->enableUntil = null; - $this->lastAccess = null; - $this->idScope = Tool::ID_SCOPE_ID_ONLY; - $this->defaultEmail = ''; - $this->created = null; - $this->updated = null; - } - - /** - * Initialise the platform. - * - * Synonym for initialize(). - */ - public function initialise() - { - $this->initialize(); - } - - /** - * Save the platform to the database. - * - * @return bool True if the object was successfully saved - */ - public function save(): bool - { - return $this->dataConnector->savePlatform($this); - } - - /** - * Delete the platform from the database. - * - * @return bool True if the object was successfully deleted - */ - public function delete(): bool - { - return $this->dataConnector->deletePlatform($this); - } - - /** - * Get the platform ID. - * - * The ID will be the consumer key if one exists, otherwise a concatenation of the platform/client/deployment IDs - * - * @return string Platform ID value - */ - public function getId(): ?string - { - if (!empty($this->key)) { - $id = $this->key; - } elseif (!empty($this->platformId)) { - $id = $this->platformId; - if (!empty($this->clientId)) { - $id .= '/' . $this->clientId; - } - if (!empty($this->deploymentId)) { - $id .= '#' . $this->deploymentId; - } - } else { - $id = null; - } - - return $id; - } - - /** - * Get platform family code (as reported by last platform connection). - * - * @return string Family code - */ - public function getFamilyCode(): ?string - { - $familyCode = ''; - if (!empty($this->consumerVersion)) { - $familyCode = $this->consumerVersion; - $pos = strpos($familyCode, '-'); - if ($pos !== false) { - $familyCode = substr($familyCode, 0, $pos); - } - } - - return $familyCode; - } - - /** - * Get the data connector. - * - * @return DataConnector|null Data connector object or string - */ - public function getDataConnector(): ?DataConnector - { - return $this->dataConnector; - } - - /** - * Is the platform available to accept launch requests? - * - * @return bool True if the platform is enabled and within any date constraints - */ - public function getIsAvailable(): bool - { - $ok = $this->enabled; - - $now = time(); - if ($ok && !is_null($this->enableFrom)) { - $ok = $this->enableFrom <= $now; - } - if ($ok && !is_null($this->enableUntil)) { - $ok = $this->enableUntil > $now; - } - - return $ok; - } - - /** - * Check if the Tool Settings service is supported. - * - * @return bool True if this platform supports the Tool Settings service - */ - public function hasToolSettingsService(): bool - { - $has = !empty($this->getSetting('custom_system_setting_url')); - if (!$has) { - $has = self::hasConfiguredApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->getFamilyCode(), $this); - } - return $has; - } - - /** - * Get Tool Settings. - * @param bool $simple True if all the simple media type is to be used (optional, default is true) - * @return mixed The array of settings if successful, otherwise false - */ - public function getToolSettings(bool $simple = true) - { - $ok = false; - $settings = array(); - if (!empty($this->getSetting('custom_system_setting_url'))) { - $url = $this->getSetting('custom_system_setting_url'); - $service = new Service\ToolSettings($this, $url, $simple); - $settings = $service->get(); - $this->lastServiceRequest = $service->getHttpMessage(); - $ok = $settings !== false; - } - if (!$ok && $this->hasConfiguredApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->getFamilyCode(), $this)) { - $className = $this->getApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->getFamilyCode()); - $hook = new $className($this); - $settings = $hook->getToolSettings($simple); - } - - return $settings; - } - - /** - * Set Tool Settings. - * @param array $settings An associative array of settings (optional, default is none) - * @return bool True if action was successful, otherwise false - */ - public function setToolSettings(array $settings = array()): bool - { - $ok = false; - if (!empty($this->getSetting('custom_system_setting_url'))) { - $url = $this->getSetting('custom_system_setting_url'); - $service = new Service\ToolSettings($this, $url); - $ok = $service->set($settings); - $this->lastServiceRequest = $service->getHttpMessage(); - } - if (!$ok && $this->hasConfiguredApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->getFamilyCode(), $this)) { - $className = $this->getApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->getFamilyCode()); - $hook = new $className($this); - $ok = $hook->setToolSettings($settings); - } - - return $ok; - } - - /** - * Get an array of defined tools - * - * @return array Array of Tool objects - */ - public function getTools(): array - { - return $this->dataConnector->getTools(); - } - - /** - * Check if the Access Token service is supported. - * - * @return bool True if this platform supports the Access Token service - */ - public function hasAccessTokenService(): bool - { - $has = !empty($this->getSetting('custom_oauth2_access_token_url')); - if (!$has) { - $has = self::hasConfiguredApiHook(self::$ACCESS_TOKEN_SERVICE_HOOK, $this->getFamilyCode(), $this); - } - return $has; - } - - /** - * Get the message parameters - * - * @return array The message parameter array - */ - public function getMessageParameters(): array - { - if ($this->ok && is_null($this->messageParameters)) { - $this->parseMessage(); - } - - return $this->messageParameters; - } - - /** - * Process an incoming request - */ - public function handleRequest() - { - $parameters = Util::getRequestParameters(); - if ($this->debugMode) { - Util::$logLevel = Util::LOGLEVEL_DEBUG; - } - if ($this->ok) { - if (!empty($parameters['client_id'])) { // Authentication request - Util::logRequest(); - $this->handleAuthenticationRequest(); - } else { // LTI message - $this->getMessageParameters(); - Util::logRequest(); - if ($this->ok && $this->authenticate()) { - $this->doCallback(); - } - } - } - if (!$this->ok) { - $this->onError(); - } - if (!$this->ok) { - $errorMessage = "Request failed with reason: '{$this->reason}'"; - if (!empty($this->details)) { - $errorMessage .= PHP_EOL . 'Debug information:'; - foreach ($this->details as $detail) { - $errorMessage .= PHP_EOL . " {$detail}"; - } - } - Util::logError($errorMessage); - } - } - - /** - * Load the platform from the database by its consumer key. - * @param string|null $key Consumer key - * @param DataConnector|null $dataConnector A data connector object - * @param bool $autoEnable true if the platform is to be enabled automatically (optional, default is false) - * @return Platform The platform object - */ - public static function fromConsumerKey(string $key = null, DataConnector $dataConnector = null, bool $autoEnable = false): Platform - { - $platform = new static($dataConnector); - $platform->key = $key; - if (!empty($dataConnector)) { - $ok = $dataConnector->loadPlatform($platform); - if ($ok && $autoEnable) { - $platform->enabled = true; - } - } - - return $platform; - } - - //changed; to be erased because of php strict standards - // /** - // * Load the platform from the database by its platform, client and deployment IDs. - // * @param string $platformId The platform ID - // * @param string $clientId The client ID - // * @param string $deploymentId The deployment ID - // * @param DataConnector|null $dataConnector A data connector object - // * @param bool $autoEnable True if the platform is to be enabled automatically (optional, default is false) - // * @return Platform The platform object - // */ - // public static function fromPlatformId(string $platformId, string $clientId, string $deploymentId, DataConnector $dataConnector = null, bool $autoEnable = false) : Platform - // { - // $platform = new static($dataConnector); - // $platform->platformId = $platformId; - // $platform->clientId = $clientId; - // $platform->deploymentId = $deploymentId; - // if ($dataConnector->loadPlatform($platform)) { - // if ($autoEnable) { - // $platform->enabled = true; - // } - // } - - // return $platform; - // } - - //changed; to be erased because of php strict standards -// /** -// * Load the platform from the database by its record ID. -// * @param int $id The platform record ID //UK: changed to int -// * @param DataConnector $dataConnector A data connector object -// * @return Platform The platform object -// */ -// public static function fromRecordId(int $id, DataConnector $dataConnector) : Platform -// { -// $platform = new static($dataConnector); -// $platform->setRecordId($id); -// $dataConnector->loadPlatform($platform); -// -// return $platform; -// } - - ### - ### PROTECTED METHODS - ### - - /** - * Save the hint and message parameters when sending an initiate login request. - * Override this method to save the data elsewhere. - * @param string $url The message URL - * @param string $loginHint The ID of the user - * @param string $ltiMessageHint The message hint being sent to the tool - * @param array $params An associative array of message parameters - */ - protected function onInitiateLogin(string &$url, string &$loginHint, string &$ltiMessageHint, array $params) - { - $hasSession = !empty(session_id()); - if (!$hasSession) { - session_start(); - } - $_SESSION['ceLTIc_lti_initiated_login'] = array( - 'messageUrl' => $url, - 'login_hint' => $loginHint, - 'lti_message_hint' => $ltiMessageHint, - 'params' => $params - ); - if (!$hasSession) { - session_write_close(); - } - } - - /** - * Check the hint and recover the message parameters for an authentication request. - * - * Override this method if the data has been saved elsewhere. - */ - protected function onAuthenticate() - { - $hasSession = !empty(session_id()); - if (!$hasSession) { - session_start(); - } - if (isset($_SESSION['ceLTIc_lti_initiated_login'])) { - $login = $_SESSION['ceLTIc_lti_initiated_login']; - $parameters = Util::getRequestParameters(); - if ($parameters['login_hint'] !== $login['login_hint'] || - (isset($login['lti_message_hint']) && (!isset($parameters['lti_message_hint']) || ($parameters['lti_message_hint'] !== $login['lti_message_hint'])))) { - $this->ok = false; - $this->messageParameters['error'] = 'access_denied'; - } else { - Tool::$defaultTool->messageUrl = $login['messageUrl']; - $this->messageParameters = $login['params']; - } - unset($_SESSION['ceLTIc_lti_initiated_login']); - } - if (!$hasSession) { - session_write_close(); - } - } - - /** - * Process a valid content-item message - */ - protected function onContentItem() - { - $this->reason = 'No onContentItem method found for platform'; - $this->onError(); - } - - /** - * Process a valid start assessment message - */ - protected function onLtiStartAssessment() - { - $this->reason = 'No onLtiStartAssessment method found for platform'; - $this->onError(); - } - - /** - * Process a response to an invalid message - */ - protected function onError() - { - $this->ok = false; - } - - ### - ### PRIVATE METHODS - ### - - /** - * Check the authenticity of the LTI message. - * - * The platform, resource link and user objects will be initialised if the request is valid. - * - * @return bool True if the request has been successfully validated. - */ - private function authenticate(): bool - { - $this->checkMessage(); - if ($this->ok) { - $this->ok = $this->verifySignature(); - } - - return $this->ok; - } - - /** - * Process an authentication request. - * - * Generates an auto-submit form to respond to the request. - */ - private function handleAuthenticationRequest() - { - $this->messageParameters = array(); - $parameters = Util::getRequestParameters(); - $this->ok = isset($parameters['scope']) && isset($parameters['response_type']) && - isset($parameters['client_id']) && isset($parameters['redirect_uri']) && - isset($parameters['login_hint']) && isset($parameters['nonce']); - if (!$this->ok) { - $this->messageParameters['error'] = 'invalid_request'; - } - if ($this->ok) { - $scopes = explode(' ', $parameters['scope']); - $this->ok = in_array('openid', $scopes); - if (!$this->ok) { - $this->messageParameters['error'] = 'invalid_scope'; - } - } - if ($this->ok && ($parameters['response_type'] !== 'id_token')) { - $this->ok = false; - $this->messageParameters['error'] = 'unsupported_response_type'; - } - if ($this->ok && ($parameters['client_id'] !== $this->clientId)) { - $this->ok = false; - $this->messageParameters['error'] = 'unauthorized_client'; - } - if ($this->ok) { - $this->ok = in_array($parameters['redirect_uri'], Tool::$defaultTool->redirectionUris); - if (!$this->ok) { - $this->messageParameters['error'] = 'invalid_request'; - $this->messageParameters['error_description'] = 'Unregistered redirect_uri'; - } - } - if ($this->ok) { - if (isset($parameters['response_mode'])) { - $this->ok = ($parameters['response_mode'] === 'form_post'); - } else { - $this->ok = false; - } - if (!$this->ok) { - $this->messageParameters['error'] = 'invalid_request'; - $this->messageParameters['error_description'] = 'Invalid response_mode'; - } - } - if ($this->ok && (!isset($parameters['prompt']) || ($parameters['prompt'] !== 'none'))) { - $this->ok = false; - $this->messageParameters['error'] = 'invalid_request'; - $this->messageParameters['error_description'] = 'Invalid prompt'; - } - - if ($this->ok) { - $this->onAuthenticate(); - } - if ($this->ok) { - $this->messageParameters = $this->addSignature( - Tool::$defaultTool->messageUrl, - $this->messageParameters, - 'POST', - null, - $parameters['nonce'] - ); - } - if (isset($parameters['state'])) { - $this->messageParameters['state'] = $parameters['state']; - } - $html = Util::sendForm($parameters['redirect_uri'], $this->messageParameters); - echo $html; - exit; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/PlatformNonce.php b/components/ILIAS/LTI/src/ToolProvider/PlatformNonce.php deleted file mode 100755 index c1a163e53815..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/PlatformNonce.php +++ /dev/null @@ -1,126 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class PlatformNonce -{ - /** - * Maximum age nonce values will be retained for (in minutes). - */ - public const MAX_NONCE_AGE = 30; // in minutes - - /** - * Maximum length which can be stored. - * - * Characters are removed from the beginning of the value when too long. - * - * @var int $maximumLength - */ - public static int $maximumLength = 50; - - /** - * Timestamp for when the nonce value expires. - * - * @var int|null $expires - */ - public ?int $expires = null; - - /** - * Platform to which this nonce applies. - * - * @var Platform|null $platform - */ - private ?Platform $platform = null; - - /** - * Nonce value. - * - * @var string|null $value - */ - private ?string $value = null; - - /** - * Class constructor. - * @param Platform $platform Platform object - * @param string|null $value Nonce value (optional, default is null) - */ - public function __construct(Platform $platform, string $value = null) - { - $this->platform = $platform; - $this->value = substr($value, -self::$maximumLength); - $this->expires = time() + (self::MAX_NONCE_AGE * 60); - } - - /** - * Load a nonce value from the database. - * - * @return bool True if the nonce value was successfully loaded - */ - public function load(): bool - { - return $this->platform->getDataConnector()->loadPlatformNonce($this); - } - - /** - * Save a nonce value in the database. - * - * @return bool True if the nonce value was successfully saved - */ - public function save(): bool - { - return $this->platform->getDataConnector()->savePlatformNonce($this); - } - - /** - * Delete a nonce value in the database. - * - * @return bool True if the nonce value was successfully deleted - */ - public function delete(): bool - { - return $this->platform->getDataConnector()->deletePlatformNonce($this); - } - - /** - * Get platform. - * - * @return Platform Platform for this nonce - */ - public function getPlatform(): ?Platform - { - return $this->platform; - } - - /** - * Get outcome value. - * - * @return string Outcome value - */ - public function getValue(): ?string - { - return $this->value; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/Profile/Item.php b/components/ILIAS/LTI/src/ToolProvider/Profile/Item.php deleted file mode 100755 index ecc202ce1395..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/Profile/Item.php +++ /dev/null @@ -1,90 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class Item -{ - /** - * ID of item. - * - * @var string|null $id - */ - public ?string $id = null; - - /** - * Name of item. - * - * @var string|null $name - */ - public ?string $name = null; - - /** - * Description of item. - * - * @var string|null $description - */ - public ?string $description = null; - - /** - * URL of item. - * - * @var string|null $url - */ - public ?string $url = null; - - /** - * Version of item. - * - * @var string|null $version - */ - public ?string $version = null; - - /** - * Timestamp of item. - * - * @var int|null $timestamp - */ - public ?int $timestamp = null; - - /** - * Class constructor. - * @param string|null $id ID of item (optional) - * @param string|null $name Name of item (optional) - * @param string|null $description Description of item (optional) - * @param string|null $url URL of item (optional) - * @param string|null $version Version of item (optional) - * @param int|null $timestamp Timestamp of item (optional) - */ - public function __construct(string $id = null, string $name = null, string $description = null, string $url = null, string $version = null, int $timestamp = null) - { - $this->id = $id; - $this->name = $name; - $this->description = $description; - $this->url = $url; - $this->version = $version; - $this->timestamp = $timestamp; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/Profile/Message.php b/components/ILIAS/LTI/src/ToolProvider/Profile/Message.php deleted file mode 100755 index cfe79f754bc6..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/Profile/Message.php +++ /dev/null @@ -1,81 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class Message -{ - /** - * LTI message type. - * - * @var string|null $type - */ - public ?string $type = null; - - /** - * Path to send message request to (used in conjunction with a base URL for the Tool). - * - * @var string|null $path - */ - public ?string $path = null; - - /** - * Capabilities required by message. - * - * @var array|null $capabilities - */ - public ?array $capabilities = null; - - /** - * Variable parameters to accompany message request. - * - * @var array|null $variables - */ - public ?array $variables = null; - - /** - * Fixed parameters to accompany message request. - * - * @var array|null $constants - */ - public ?array $constants = null; - - /** - * Class constructor. - * @param string $type LTI message type - * @param string $path Path to send message request to - * @param array $capabilities Array of capabilities required by message - * @param array $variables Array of variable parameters to accompany message request - * @param array $constants Array of fixed parameters to accompany message request - */ - public function __construct(string $type, string $path, array $capabilities = array(), array $variables = array(), array $constants = array()) - { - $this->type = $type; - $this->path = $path; - $this->capabilities = $capabilities; - $this->variables = $variables; - $this->constants = $constants; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/Profile/ResourceHandler.php b/components/ILIAS/LTI/src/ToolProvider/Profile/ResourceHandler.php deleted file mode 100755 index 16e925b05a61..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/Profile/ResourceHandler.php +++ /dev/null @@ -1,72 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class ResourceHandler -{ - /** - * General details of resource handler. - * - * @var Item|null $item - */ - public ?Item $item = null; - - /** - * URL of icon. - * - * @var string|null $icon - */ - public ?string $icon = null; - - /** - * Required Message objects for resource handler. - * - * @var array|null $requiredMessages - */ - public ?array $requiredMessages = null; - - /** - * Optional Message objects for resource handler. - * - * @var array|null $optionalMessages - */ - public ?array $optionalMessages = null; - - /** - * Class constructor. - * @param Item $item General details of resource handler - * @param string $icon URL of icon - * @param array $requiredMessages Array of required Message objects for resource handler - * @param array $optionalMessages Array of optional Message objects for resource handler - */ - public function __construct(Item $item, string $icon, array $requiredMessages, array $optionalMessages) - { - $this->item = $item; - $this->icon = $icon; - $this->requiredMessages = $requiredMessages; - $this->optionalMessages = $optionalMessages; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/Profile/ServiceDefinition.php b/components/ILIAS/LTI/src/ToolProvider/Profile/ServiceDefinition.php deleted file mode 100755 index c313c402d3ab..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/Profile/ServiceDefinition.php +++ /dev/null @@ -1,81 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class ServiceDefinition -{ - /** - * Media types supported by service. - * - * @var array|null $formats - */ - public ?array $formats = null; - - /** - * HTTP actions accepted by service. - * - * @var array|null $actions - */ - public ?array $actions = null; - - /** - * ID of service. - * - * @var string|null $id - */ - public ?string $id = null; - - /** - * URL for service requests. - * - * @var string|null $endpoint - */ - public ?string $endpoint = null; - - /** - * Class constructor. - * @param array $formats Array of media types supported by service - * @param array $actions Array of HTTP actions accepted by service - * @param string|null $id ID of service (optional) - * @param string|null $endpoint URL for service requests (optional) - */ - public function __construct(array $formats, array $actions, string $id = null, string $endpoint = null) - { - $this->formats = $formats; - $this->actions = $actions; - $this->id = $id; - $this->endpoint = $endpoint; - } - - /** - * Set ID. - * @param string $id ID of service - */ - public function setId(string $id) - { - $this->id = $id; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/ResourceLink.php b/components/ILIAS/LTI/src/ToolProvider/ResourceLink.php deleted file mode 100755 index b863a8a633f6..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/ResourceLink.php +++ /dev/null @@ -1,1824 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class ResourceLink -{ - use ApiHook; - - /** - * Read action. - */ - public const EXT_READ = 1; - - /** - * Write (create/update) action. - */ - public const EXT_WRITE = 2; - - /** - * Delete action. - */ - public const EXT_DELETE = 3; - - /** - * Create action. - */ - public const EXT_CREATE = 4; - - /** - * Update action. - */ - public const EXT_UPDATE = 5; - - /** - * Decimal outcome type. - */ - public const EXT_TYPE_DECIMAL = 'decimal'; - - /** - * Percentage outcome type. - */ - public const EXT_TYPE_PERCENTAGE = 'percentage'; - - /** - * Ratio outcome type. - */ - public const EXT_TYPE_RATIO = 'ratio'; - - /** - * Letter (A-F) outcome type. - */ - public const EXT_TYPE_LETTER_AF = 'letteraf'; - - /** - * Letter (A-F) with optional +/- outcome type. - */ - public const EXT_TYPE_LETTER_AF_PLUS = 'letterafplus'; - - /** - * Pass/fail outcome type. - */ - public const EXT_TYPE_PASS_FAIL = 'passfail'; - - /** - * Free text outcome type. - */ - public const EXT_TYPE_TEXT = 'freetext'; - - /** - * Context title. - * - * @var string|null $title - */ - public ?string $title = null; - - /** - * Resource link ID as supplied in the last connection request. - * - * @var string|null $ltiResourceLinkId - */ - public ?string $ltiResourceLinkId = null; - - /** - * User group sets (null if the platform does not support the groups enhancement) - * - * A group set is represented by an associative array with the following elements: - * - title - * - groups (array of group IDs) - * - num_members - * - num_staff - * - num_learners - * The array key value is the group set ID. - * - * @var array|null $groupSets - */ - public ?array $groupSets = null; - - /** - * User groups (null if the platform does not support the groups enhancement) - * - * A group is represented by an associative array with the following elements: - * - title - * - set (ID of group set, array of IDs if the group belongs to more than one set, omitted if the group is not part of a set) - * The array key value is the group ID. - * - * @var array|null $groups - */ - public ?array $groups = null; - - /** - * HttpMessage object for last service request. - * - * @var HttpMessage|null $lastServiceRequest - */ - public ?HTTPMessage $lastServiceRequest = null; - - /** - * Request for last service request. - * - * @var string|null $extRequest - */ - public ?string $extRequest = null; - - /** - * Request headers for last service request. - * - * @var array|null|string $extRequestHeaders //UK: added string - */ - public $extRequestHeaders = null; - - /** - * Response from last service request. - * - * @var string|null $extResponse - */ - public ?string $extResponse = null; - - /** - * Response header from last service request. - * - * @var array|null|string $extResponseHeaders //UK: added string - */ - public $extResponseHeaders = null; - - /** - * Primary key value for resource link being shared (if any). - * - * @var string|null $primaryResourceLinkId - */ - public ?string $primaryResourceLinkId = null; - - /** - * Whether the sharing request has been approved by the primary resource link. - * - * @var bool|null $shareApproved - */ - public ?bool $shareApproved = null; - - /** - * Timestamp for when the object was created. - * - * @var int|null $created - */ - public ?int $created = null; - - /** - * Timestamp for when the object was last updated. - * - * @var int|null $updated - */ - public ?int $updated = null; - - /** - * Record ID for this resource link. - * - * @var int|null $id - */ - private ?int $id = null; - - /** - * Platform for this resource link. - * - * @var Platform|null $platform - */ - private ?Platform $platform = null; - - /** - * Platform ID for this resource link. - * - * @var int|null $platformId - */ - private ?int $platformId = null; - - /** - * Context for this resource link. - * - * @var Context|null $context - */ - private ?Context $context = null; - - /** - * Context ID for this resource link. - * - * @var int|null $contextId - */ - private ?int $contextId = null; - - /** - * Setting values (LTI parameters, custom parameters and local parameters). - * - * @var array|null $settings - */ - private ?array $settings = null; - - /** - * Whether the settings value have changed since last saved. - * - * @var bool $settingsChanged - */ - private bool $settingsChanged = false; - - /** - * XML document for the last extension service request. - * - * @var DOMDocument|null $extDoc //UK: added DOMDocument; erased: string - */ - private ?DOMDocument $extDoc = null; - - /** - * XML node array for the last extension service request. - * - * @var array|null $extNodes //UK: added array|null; erased object|null - */ - private ?array $extNodes = null; - - /** - * Data connector object or string. - * - * @var DataConnector|null $dataConnector - */ - private ?DataConnector $dataConnector = null; - - /** - * Class constructor. - */ - public function __construct() - { - $this->initialize(); - } - - /** - * Initialise the resource link. - */ - public function initialize() - { - $this->title = ''; - $this->settings = array(); - $this->groupSets = null; - $this->groups = null; - $this->primaryResourceLinkId = null; - $this->shareApproved = null; - $this->created = null; - $this->updated = null; - } - - /** - * Initialise the resource link. - * - * Synonym for initialize(). - */ - public function initialise() - { - $this->initialize(); - } - - /** - * Save the resource link to the database. - * - * @return bool True if the resource link was successfully saved. - */ - public function save(): bool - { - $ok = $this->getDataConnector()->saveResourceLink($this); - if ($ok) { - $this->settingsChanged = false; - } - - return $ok; - } - - /** - * Delete the resource link from the database. - * - * @return bool True if the resource link was successfully deleted. - */ - public function delete(): bool - { - return $this->getDataConnector()->deleteResourceLink($this); - } - - // /** - // * Get tool consumer. - // * - // * @deprecated Use getPlatform() instead - // * @see Context::getPlatform() - // * - // * @return ToolConsumer Tool consumer object for this resource link. - // */ - // public function getConsumer() - // { - // Util::logDebug('Method ceLTIc\LTI\ResourceLink::getConsumer() has been deprecated; please use ceLTIc\LTI\ResourceLink::getPlatform() instead.', - // true); - // return $this->getPlatform(); - // } - - // /** - // * Get tool consumer ID. - // * - // * @deprecated Use getPlatformId() instead - // * @see Context::getPlatformId() - // * - // * @return int|null Tool Consumer ID for this resource link. - // */ - // public function getConsumerId() - // { - // Util::logDebug('Method ceLTIc\LTI\ResourceLink::getConsumerId() has been deprecated; please use ceLTIc\LTI\ResourceLink::getPlatformId() instead.', - // true); - // return $this->getPlatformId(); - // } - - // /** - // * Set tool consumer ID. - // * - // * @deprecated Use setPlatformId() instead - // * @see Context::setPlatformId() - // * - // * @param int $consumerId Tool Consumer ID for this resource link. - // */ - // public function setConsumerId($consumerId) - // { - // Util::logDebug('Method ceLTIc\LTI\ResourceLink::setConsumerId() has been deprecated; please use ceLTIc\LTI\ResourceLink::setPlatformId() instead.', - // true); - // $this->setPlatformId($consumerId); - // } - - /** - * Get platform. - * - * @return Platform Platform object for this resource link. - */ - public function getPlatform(): ?Platform - { - if (is_null($this->platform)) { - if (!is_null($this->context) || !is_null($this->contextId)) { - $this->platform = $this->getContext()->getPlatform(); - } else { - //UK changed from $this->platform = Platform::fromRecordId($this->platformId, $this->getDataConnector()); - $this->platform = \ilLTIPlatform::fromRecordId($this->platformId, $this->getDataConnector()); - } - } - - return $this->platform; - } - - /** - * Get platform ID. - * - * @return int|null Platform ID for this resource link. - */ - public function getPlatformId(): ?int - { - return $this->platformId; - } - - /** - * Set platform ID. - * @param int|null $platformId Platform ID for this resource link. //UK: added |null - */ - public function setPlatformId(?int $platformId) - { - $this->platform = null; - $this->platformId = $platformId; - } - - /** - * Get context. - * - * @return Context|null LTIContext object for this resource link. - */ - public function getContext(): ?Context - { - if (is_null($this->context) && !is_null($this->contextId)) { - $this->context = Context::fromRecordId($this->contextId, $this->getDataConnector()); - } - - return $this->context; - } - - /** - * Get context record ID. - * - * @return int|null Context record ID for this resource link. - */ - public function getContextId(): ?int - { - if (is_null($this->contextId) && !is_null($this->context)) { - $this->contextId = $this->context->getRecordId(); - } - - return $this->contextId; - } - - /** - * Set context. - * @param Context $context Context for this resource link. - */ - public function setContext(Context $context) - { - $this->context = $context; - $this->contextId = $context->getRecordId(); - } - - /** - * Set context ID. - * @param int|null $contextId Context ID for this resource link. //UK: added |null - */ - public function setContextId(?int $contextId) - { - if ($this->contextId !== $contextId) { - $this->context = null; - $this->contextId = $contextId; - } - } - - /** - * Get consumer key. - * - * @return string Consumer key value for this resource link. - */ - public function getKey(): string - { - return $this->getPlatform()->getKey(); - } - - /** - * Get resource link ID. - * - * @return string ID for this resource link. - */ - public function getId(): ?string - { - return $this->ltiResourceLinkId; - } - - /** - * Get resource link record ID. - * - * @return int|null Record ID for this resource link. - */ - public function getRecordId(): ?int - { - return $this->id; - } - - /** - * Set resource link record ID. - * @param int $id Record ID for this resource link. - */ - public function setRecordId(int $id) - { - $this->id = $id; - } - - /** - * Get the data connector. - * - * @return DataConnector|null Data connector object or string - */ - public function getDataConnector(): ?DataConnector - { - if (empty($this->dataConnector)) { - $this->getPlatform(); - if (!empty($this->platform)) { - $this->dataConnector = $this->platform->getDataConnector(); - } - } - - return $this->dataConnector; - } - - /** - * Get a setting value. - * @param string $name Name of setting - * @param string $default Value to return if the setting does not exist (optional, default is an empty string) - * @return string Setting value - */ - public function getSetting(string $name, string $default = ''): string - { - if (array_key_exists($name, $this->settings)) { - $value = $this->settings[$name]; - } else { - $value = $default; - } - - return $value; - } - - /** - * Set a setting value. - * @param string $name Name of setting - * @param string|null $value Value to set, use an empty value to delete a setting (optional, default is null) - */ - public function setSetting(string $name, string $value = null) - { - $old_value = $this->getSetting($name); - if ($value !== $old_value) { - if (!empty($value)) { - $this->settings[$name] = $value; - } else { - unset($this->settings[$name]); - } - $this->settingsChanged = true; - } - } - - /** - * Get an array of all setting values. - * - * @return array Associative array of setting values - */ - public function getSettings(): ?array - { - return $this->settings; - } - - /** - * Set an array of all setting values. - * @param array $settings Associative array of setting values - */ - public function setSettings(array $settings) - { - $this->settings = $settings; - } - - /** - * Save setting values. - * - * @return bool True if the settings were successfully saved - */ - public function saveSettings(): bool - { - if ($this->settingsChanged) { - $ok = $this->save(); - } else { - $ok = true; - } - - return $ok; - } - - /** - * Check if an Outcomes service is available. - * - * @return bool True if this resource link supports an Outcomes service - */ - public function hasOutcomesService(): bool - { - $has = !empty($this->getSetting('ext_ims_lis_basic_outcome_url')) || !empty($this->getSetting('lis_outcome_service_url')); - if (!$has && !empty($this->getSetting('custom_lineitem_url')) && !empty($this->getSetting('custom_ags_scopes'))) { - $scopes = explode(',', $this->getSetting('custom_ags_scopes')); - $has = in_array(Service\Score::$SCOPE, $scopes) && in_array(Service\Result::$SCOPE, $scopes); - } - if (!$has) { - $has = self::hasConfiguredApiHook(self::$OUTCOMES_SERVICE_HOOK, $this->getPlatform()->getFamilyCode(), $this); - } - return $has; - } - - /** - * Check if a Memberships service is available. - * - * @return bool True if this resource link supports a Memberships service - */ - public function hasMembershipsService(): bool - { - $has = false; - if (!empty($this->getContextId())) { - $has = !empty($this->getContext()->getSetting('custom_context_memberships_url')) || !empty($this->getContext()->getSetting('custom_context_memberships_v2_url')); - } - if (!$has) { - $has = !empty($this->getSetting('custom_link_memberships_url')); - } - if (!$has) { - $has = !empty($this->getSetting('ext_ims_lis_memberships_url')); - } - if (!$has) { - $has = self::hasConfiguredApiHook(self::$MEMBERSHIPS_SERVICE_HOOK, $this->getPlatform()->getFamilyCode(), $this); - } - - return $has; - } - - /** - * Check if the Setting extension service is available. - * - * @return bool True if this resource link supports the Setting extension service - */ - public function hasSettingService(): bool - { - $url = $this->getSetting('ext_ims_lti_tool_setting_url'); - - return !empty($url); - } - - /** - * Check if the Line Item service is available. - * - * @return bool True if this resource link supports the Line Item service - */ - public function hasLineItemService(): bool - { - $has = false; - if (!empty($this->getSetting('custom_ags_scopes'))) { - $scopes = explode(',', $this->getSetting('custom_ags_scopes')); - if (in_array(Service\LineItem::$SCOPE, $scopes) || in_array(Service\LineItem::$SCOPE_READONLY, $scopes)) { - $has = !empty($this->getSetting('custom_lineitems_url')); - } - } - - return $has; - } - - /** - * Check if the Score service is available. - * - * @return bool True if this resource link supports the Score service - */ - public function hasScoreService(): bool - { - $has = false; - if (!empty($this->getSetting('custom_ags_scopes'))) { - $scopes = explode(',', $this->getSetting('custom_ags_scopes')); - if (in_array(Service\Score::$SCOPE, $scopes)) { - $has = !empty($this->getSetting('custom_lineitem_url')); - } - } - - return $has; - } - - /** - * Check if the Result service is available. - * - * @return bool True if this resource link supports the Result service - */ - public function hasResultService(): bool - { - $has = false; - if (!empty($this->getSetting('custom_ags_scopes'))) { - $scopes = explode(',', $this->getSetting('custom_ags_scopes')); - if (in_array(Service\Result::$SCOPE, $scopes)) { - $has = !empty($this->getSetting('custom_lineitem_url')); - } - } - - return $has; - } - - /** - * Check if the Assessment Control service is available. - * - * @return bool True if this resource link supports the Assessment Control service - */ - public function hasAssessmentControlService(): bool - { - $url = $this->getSetting('custom_ap_acs_url'); - - return !empty($url); - } - - /** - * Perform an Outcomes service request. - * @param int $action The action type constant - * @param Outcome $ltiOutcome Outcome object - * @param UserResult $userResult UserResult object - * @return bool True if the request was successfully processed - */ - public function doOutcomesService(int $action, Outcome $ltiOutcome, UserResult $userResult): bool - { - $ok = false; - $this->extResponse = ''; - // Lookup service details from the source resource link appropriate to the user (in case the destination is being shared) - $sourceResourceLink = $userResult->getResourceLink(); - $sourcedId = $userResult->ltiResultSourcedId; - - // Use LTI 1.1 service in preference to extension service if it is available - $urlAGS = $sourceResourceLink->getSetting('custom_lineitem_url'); - $urlLTI11 = $sourceResourceLink->getSetting('lis_outcome_service_url'); - $urlExt = $sourceResourceLink->getSetting('ext_ims_lis_basic_outcome_url'); - - if (!empty($urlAGS)) { - if (($action === self::EXT_READ) && ($ltiOutcome->type === self::EXT_TYPE_DECIMAL) && $sourceResourceLink->hasResultService()) { - $ltiOutcome = $this->doResultService($userResult, $urlAGS); - $ok = !empty($ltiOutcome); - } elseif ((($action === self::EXT_WRITE) && $this->checkValueType($ltiOutcome, array(self::EXT_TYPE_DECIMAL)) && $sourceResourceLink->hasScoreService()) || - ($action === self::EXT_DELETE)) { - if ($action === self::EXT_DELETE) { - $ltiOutcome->setValue(null); - $ltiOutcome->activityProgress = 'Initialized'; - $ltiOutcome->gradingProgress = 'NotReady'; - } - $ok = $this->doScoreService($ltiOutcome, $userResult, $urlAGS); - } - } - if (!$ok && is_null($ltiOutcome->getValue())) { - $ltiOutcome->setValue(''); - } - if (!$ok && !empty($urlLTI11)) { - $do = ''; - $outcome = $ltiOutcome->getValue(); - if (($action === self::EXT_READ) && ($ltiOutcome->type === self::EXT_TYPE_DECIMAL)) { - $do = 'readResult'; - } elseif (($action === self::EXT_WRITE) && $this->checkValueType($ltiOutcome, array(self::EXT_TYPE_DECIMAL))) { - $do = 'replaceResult'; - if (($ltiOutcome->getPointsPossible() <> 1) && ($ltiOutcome->getPointsPossible() > 0)) { - $outcome = $outcome / $ltiOutcome->getPointsPossible(); - } - } elseif ($action === self::EXT_DELETE) { - $do = 'deleteResult'; - } - if (!empty($do)) { - $xml = ''; - if ($action === self::EXT_WRITE) { - $comment = (empty($ltiOutcome->comment)) ? '' : trim($ltiOutcome->comment); - if (!empty($comment) && !empty($sourceResourceLink->getSetting('ext_outcome_data_values_accepted'))) { - $resultDataTypes = explode(',', $sourceResourceLink->getSetting('ext_outcome_data_values_accepted')); - $resultDataType = ''; - if (count($resultDataTypes) === 1) { - $resultDataType = $resultDataTypes[0]; - } elseif (count($resultDataTypes) > 1) { - $isUrl = (strpos($comment, 'http://') === 0) || (strpos($comment, 'https://') === 0); - if ($isUrl && in_array('ltiLaunchUrl', $resultDataTypes)) { - $resultDataType = 'ltiLaunchUrl'; - } elseif ($isUrl && in_array('url', $resultDataTypes)) { - $resultDataType = 'url'; - } elseif (in_array('text', $resultDataTypes)) { - $resultDataType = 'text'; - } - } - if (!empty($resultDataType)) { - $xml = <<< EOF - - - <{$resultDataType}>{$comment} - -EOF; - } - } - $xml = <<< EOF - - - - {$ltiOutcome->language} - {$outcome} - {$xml} - -EOF; - } - $sourcedId = htmlentities($sourcedId); - $xml = << - - {$sourcedId} - {$xml} - -EOF; - if ($this->doLTI11Service($do, $urlLTI11, $xml)) { - switch ($action) { - case self::EXT_READ: - if (!isset($this->extNodes['imsx_POXBody']["{$do}Response"]['result']['resultScore']['textString'])) { - break; - } else { - $ltiOutcome->setValue($this->extNodes['imsx_POXBody']["{$do}Response"]['result']['resultScore']['textString']); - } - // no break - case self::EXT_WRITE: - case self::EXT_DELETE: - $ok = true; - break; - } - } - } - } - if (!$ok && !empty($urlExt)) { - $do = ''; - $outcome = $ltiOutcome->getValue(); - if (($action === self::EXT_READ) && ($ltiOutcome->type === self::EXT_TYPE_DECIMAL)) { - $do = 'basic-lis-readresult'; - } elseif (($action === self::EXT_WRITE) && $this->checkValueType($ltiOutcome, array(self::EXT_TYPE_DECIMAL))) { - $do = 'basic-lis-updateresult'; - if (($ltiOutcome->getPointsPossible() <> 1) && ($ltiOutcome->getPointsPossible() > 0)) { - $outcome = $outcome / $ltiOutcome->getPointsPossible(); - } - } elseif ($action === self::EXT_DELETE) { - $do = 'basic-lis-deleteresult'; - } - if (!empty($do)) { - $params = array(); - $params['sourcedid'] = $sourcedId; - $params['result_resultscore_textstring'] = $outcome; - if (!empty($ltiOutcome->language)) { - $params['result_resultscore_language'] = $ltiOutcome->language; - } - if (!empty($ltiOutcome->status)) { - $params['result_statusofresult'] = $ltiOutcome->status; - } - if (!empty($ltiOutcome->date)) { - $params['result_date'] = $ltiOutcome->date; - } - if (!empty($ltiOutcome->type)) { - $params['result_resultvaluesourcedid'] = $ltiOutcome->type; - } - if (!empty($ltiOutcome->dataSource)) { - $params['result_datasource'] = $ltiOutcome->dataSource; - } - if ($this->doService($do, $urlExt, $params, 'https://purl.imsglobal.org/spec/lti-ext/scope/outcomes')) { - switch ($action) { - case self::EXT_READ: - if (isset($this->extNodes['result']['resultscore']['textstring'])) { - $ltiOutcome->setValue($this->extNodes['result']['resultscore']['textstring']); - } - // no break - case self::EXT_WRITE: - case self::EXT_DELETE: - $ok = true; - break; - } - } - } - } - if ((!$ok) && $this->hasConfiguredApiHook(self::$OUTCOMES_SERVICE_HOOK, $this->getPlatform()->getFamilyCode(), $this)) { - $className = $this->getApiHook(self::$OUTCOMES_SERVICE_HOOK, $this->getPlatform()->getFamilyCode()); - $hook = new $className($this); - $response = $hook->doOutcomesService($action, $ltiOutcome, $userResult); - if ($response !== false) { - $ok = true; - if ($action === self::EXT_READ) { - $ltiOutcome->setValue($response); - } - } - } - - return $ok; - } - - // /** - // * Perform a Memberships extension service request. - // * The userResult table is updated with any user objects with lis_result_sourcedid values. - // * @param bool $withGroups True is group information is to be requested as well - // * @return mixed Array of UserResult objects or False if the request was not successful - // *@deprecated Use getMemberships() instead - // * @see ResourceLink::getMemberships() - // */ - // public function doMembershipsService(bool $withGroups = false) - // { - // Util::logDebug('Method ceLTIc\LTI\ResourceLink::doMembershipsService() has been deprecated; please use ceLTIc\LTI\ResourceLink::getMemberships() instead.', - // true); - // return $this->getMemberships($withGroups); - // } - - /** - * Perform a Setting service request. - * @param int $action The action type constant - * @param string|null $value The setting value (optional, default is null) - * @return mixed The setting value for a read action, true if a write or delete action was successful, otherwise false - */ - public function doSettingService(int $action, string $value = null) - { - $response = false; - $this->extResponse = ''; - switch ($action) { - case self::EXT_READ: - $do = 'basic-lti-loadsetting'; - break; - case self::EXT_WRITE: - $do = 'basic-lti-savesetting'; - break; - case self::EXT_DELETE: - $do = 'basic-lti-deletesetting'; - break; - } - if (isset($do)) { - $url = $this->getSetting('ext_ims_lti_tool_setting_url'); - $params = array(); - $params['id'] = $this->getSetting('ext_ims_lti_tool_setting_id'); - if (is_null($value)) { - $value = ''; - } - $params['setting'] = $value; - - if ($this->doService($do, $url, $params, 'https://purl.imsglobal.org/spec/lti-ext/scope/setting')) { - switch ($action) { - case self::EXT_READ: - if (isset($this->extNodes['setting']['value'])) { - $response = $this->extNodes['setting']['value']; - if (is_array($response)) { - $response = ''; - } - } - break; - case self::EXT_WRITE: - $this->setSetting('ext_ims_lti_tool_setting', $value); - $this->saveSettings(); - $response = true; - break; - case self::EXT_DELETE: - $response = true; - break; - } - } - } - - return $response; - } - - /** - * Check if the Tool Settings service is available. - * - * @return bool True if this resource link supports the Tool Settings service - */ - public function hasToolSettingsService(): bool - { - $has = !empty($this->getSetting('custom_link_setting_url')); - if (!$has) { - $has = self::hasConfiguredApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->getPlatform()->getFamilyCode(), $this); - } - return $has; - } - - /** - * Get Tool Settings. - * @param int $mode Mode for request (optional, default is current level only) - * @param bool $simple True if all the simple media type is to be used (optional, default is true) - * @return mixed The array of settings if successful, otherwise false - */ - public function getToolSettings(int $mode = Service\ToolSettings::MODE_CURRENT_LEVEL, bool $simple = true) - { - $ok = false; - $settings = array(); - if (!empty($this->getSetting('custom_link_setting_url'))) { - $url = $this->getSetting('custom_link_setting_url'); - $service = new Service\ToolSettings($this, $url, $simple); - $settings = $service->get($mode); - $this->lastServiceRequest = $service->getHttpMessage(); - $ok = $settings !== false; - } - if (!$ok && $this->hasConfiguredApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->getPlatform()->getFamilyCode(), $this)) { - $className = $this->getApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->getPlatform()->getFamilyCode()); - $hook = new $className($this); - $settings = $hook->getToolSettings($mode, $simple); - } - - return $settings; - } - - /** - * Set Tool Settings. - * @param array $settings An associative array of settings (optional, default is none) - * @return bool True if action was successful, otherwise false - */ - public function setToolSettings(array $settings = array()): bool - { - $ok = false; - if (!empty($this->getSetting('custom_link_setting_url'))) { - $url = $this->getSetting('custom_link_setting_url'); - $service = new Service\ToolSettings($this, $url); - $ok = $service->set($settings); - $this->lastServiceRequest = $service->getHttpMessage(); - } - if (!$ok && $this->hasConfiguredApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->getPlatform()->getFamilyCode(), $this)) { - $className = $this->getApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->getPlatform()->getFamilyCode()); - $hook = new $className($this); - $ok = $hook->setToolSettings($settings); - } - - return $ok; - } - - /** - * Check if the Membership service is supported. - * - * @deprecated Use hasMembershipsService() instead - * @see ResourceLink::hasMembershipsService() - * - * @return bool True if this resource link supports the Membership service - */ - public function hasMembershipService(): bool - { - Util::logDebug( - 'Method ceLTIc\LTI\ResourceLink::hasMembershipService() has been deprecated; please use ceLTIc\LTI\ResourceLink::hasMembershipsService() instead.', - true - ); - return $this->hasMembershipsService(); - } - - /** - * Get Membership. - * - * @deprecated Use getMemberships() instead - * @see ResourceLink::getMemberships() - * - * @return mixed The array of UserResult objects if successful, otherwise false - */ - public function getMembership() - { - Util::logDebug( - 'Method ceLTIc\LTI\ResourceLink::getMembership() has been deprecated; please use ceLTIc\LTI\ResourceLink::getMemberships() instead.', - true - ); - return $this->getMemberships(); - } - - /** - * Get Memberships. - * @param bool $withGroups True is group information is to be requested as well - * @return mixed The array of UserResult objects if successful, otherwise false - */ - public function getMemberships(bool $withGroups = false) - { - $ok = false; - $userResults = array(); - $hasLtiLinkService = !empty($this->getSetting('custom_link_memberships_url')); - $hasLtiContextService = !empty($this->getContextId()) && - (!empty($this->getContext()->getSetting('custom_context_memberships_url')) || !empty($this->getContext()->getSetting('custom_context_memberships_v2_url'))); - $hasGroupsService = !empty($this->getContextId()) && !empty($this->getContext()->getSetting('custom_context_groups_url')); - $hasExtService = !empty($this->getSetting('ext_ims_lis_memberships_url')); - $hasApiHook = $this->hasConfiguredApiHook(self::$MEMBERSHIPS_SERVICE_HOOK, $this->getPlatform()->getFamilyCode(), $this); - if (($hasLtiContextService && (!$withGroups || $hasGroupsService)) || (!$hasExtService && !$hasApiHook)) { - if (!empty($this->getContextId()) && !empty($this->getContext()->getSetting('custom_context_memberships_v2_url'))) { - $url = $this->getContext()->getSetting('custom_context_memberships_v2_url'); - $format = Service\Membership::MEDIA_TYPE_MEMBERSHIPS_NRPS; - } else { - $url = $this->getContext()->getSetting('custom_context_memberships_url'); - $format = Service\Membership::MEDIA_TYPE_MEMBERSHIPS_V1; - } - $service = new Service\Membership($this, $url, $format); - if (!$withGroups) { - $userResults = $service->get(); - } else { - $userResults = $service->getWithGroups(); - } - $this->lastServiceRequest = $service->getHttpMessage(); - $ok = $userResults !== false; - } elseif ($hasLtiLinkService) { - $id = $this->id; - $this->id = null; - $url = $this->getSetting('custom_link_memberships_url'); - $format = Service\Membership::MEDIA_TYPE_MEMBERSHIPS_V1; - $service = new Service\Membership($this, $url, $format); - if (!$withGroups) { - $userResults = $service->get(); - } else { - $userResults = $service->getWithGroups(); - } - $this->lastServiceRequest = $service->getHttpMessage(); - $this->id = $id; - $ok = $userResults !== false; - } - if (!$ok && $hasExtService) { - $this->extResponse = ''; - $url = $this->getSetting('ext_ims_lis_memberships_url'); - $params = array(); - $params['id'] = $this->getSetting('ext_ims_lis_memberships_id'); - if ($withGroups) { - $ok = $this->doService( - 'basic-lis-readmembershipsforcontextwithgroups', - $url, - $params, - 'https://purl.imsglobal.org/spec/lti-ext/scope/memberships' - ); - } - if (!$ok) { - $ok = $this->doService( - 'basic-lis-readmembershipsforcontext', - $url, - $params, - 'https://purl.imsglobal.org/spec/lti-ext/scope/memberships' - ); - } - if ($ok) { - $this->groupSets = array(); - $this->groups = array(); - if (isset($this->extNodes['memberships'])) { - $memberships = $this->extNodes['memberships']; - } elseif (isset($this->extNodes['members'])) { - $memberships = $this->extNodes['members']; - } else { - $ok = false; - } - } - if ($ok) { - if (!isset($memberships['member'])) { - $members = array(); - } elseif (!isset($memberships['member'][0])) { - $members = array(); - $members[0] = $memberships['member']; - } else { - $members = $memberships['member']; - } - - for ($i = 0; $i < count($members); $i++) { - $userresult = UserResult::fromResourceLink($this, $members[$i]['user_id']); - - // Set the user name - $firstname = (isset($members[$i]['person_name_given'])) ? $members[$i]['person_name_given'] : ''; - $lastname = (isset($members[$i]['person_name_family'])) ? $members[$i]['person_name_family'] : ''; - $fullname = (isset($members[$i]['person_name_full'])) ? $members[$i]['person_name_full'] : ''; - $userresult->setNames($firstname, $lastname, $fullname); - - // Set the sourcedId - if (isset($members[$i]['person_sourcedid'])) { - $userresult->sourcedId = $members[$i]['person_sourcedid']; - } - - // Set the user email - $email = (isset($members[$i]['person_contact_email_primary'])) ? $members[$i]['person_contact_email_primary'] : ''; - $userresult->setEmail($email, $this->getPlatform()->defaultEmail); - - // Set the user roles - if (isset($members[$i]['roles'])) { - $userresult->roles = Tool::parseRoles($members[$i]['roles']); - } - - // Set the user groups - if (!isset($members[$i]['groups']['group'])) { - $groups = array(); - } elseif (!isset($members[$i]['groups']['group'][0])) { - $groups = array(); - $groups[0] = $members[$i]['groups']['group']; - } else { - $groups = $members[$i]['groups']['group']; - } - for ($j = 0; $j < count($groups); $j++) { - $group = $groups[$j]; - if (isset($group['set'])) { - $set_id = $group['set']['id']; - if (!isset($this->groupSets[$set_id])) { - $this->groupSets[$set_id] = array('title' => $group['set']['title'], 'groups' => array(), - 'num_members' => 0, 'num_staff' => 0, 'num_learners' => 0); - } - $this->groupSets[$set_id]['num_members']++; - if ($userresult->isStaff()) { - $this->groupSets[$set_id]['num_staff']++; - } - if ($userresult->isLearner()) { - $this->groupSets[$set_id]['num_learners']++; - } - if (!in_array($group['id'], $this->groupSets[$set_id]['groups'])) { - $this->groupSets[$set_id]['groups'][] = $group['id']; - } - $this->groups[$group['id']] = array('title' => $group['title'], 'set' => $set_id); - } else { - $this->groups[$group['id']] = array('title' => $group['title']); - } - $userresult->groups[] = $group['id']; - } - if (isset($members[$i]['lis_result_sourcedid'])) { - $userresult->ltiResultSourcedId = $members[$i]['lis_result_sourcedid']; - } - $userResults[] = $userresult; - } - } else { - $userResults = false; - } - $ok = $userResults !== false; - } - if (!$ok && $hasApiHook) { - $className = $this->getApiHook(self::$MEMBERSHIPS_SERVICE_HOOK, $this->getPlatform()->getFamilyCode()); - $hook = new $className($this); - $userResults = $hook->getMemberships($withGroups); - $ok = $userResults !== false; - } - if ($ok) { - $oldUsers = $this->getUserResultSourcedIDs(true, Tool::ID_SCOPE_RESOURCE); - foreach ($userResults as $userresult) { - // If a result sourcedid is provided save the user - if (!empty($userresult->ltiResultSourcedId)) { - $userresult->save(); - } - // Remove old user (if it exists) - unset($oldUsers[$userresult->getId(Tool::ID_SCOPE_RESOURCE)]); - } - // Delete any old users which were not in the latest list from the platform - foreach ($oldUsers as $id => $userresult) { - $userresult->delete(); - } - } - - return $userResults; - } - - /** - * Obtain an array of UserResult objects for users with a result sourcedId. - * The array may include users from other resource links which are sharing this resource link. - * It may also be optionally indexed by the user ID of a specified scope. - * @param bool $localOnly True if only users from this resource link are to be returned, not users from shared resource links (optional, default is false) - * @param int|null $idScope Scope to use for ID values (optional, default is null for platform default) - * @return UserResult[] Array of UserResult objects - */ - public function getUserResultSourcedIDs(bool $localOnly = false, int $idScope = null): array - { - return $this->getDataConnector()->getUserResultSourcedIDsResourceLink($this, $localOnly, $idScope); - } - - /** - * Get an array of ResourceLinkShare objects for each resource link which is sharing this context. - * - * @return ResourceLinkShare[] Array of ResourceLinkShare objects - */ - public function getShares(): array - { - return $this->getDataConnector()->getSharesResourceLink($this); - } - - /** - * Get line items. - * @param string|null $resourceId Tool resource ID - * @param string|null $tag Tag - * @param int|null $limit Limit of line items to be returned in each request, null for service default - * @return Service\LineItem[]|bool Array of LineItem objects or false on error - */ - public function getLineItems(string $resourceId = null, string $tag = null, int $limit = null) - { - $lineItems = false; - $this->extRequest = ''; - $this->extRequestHeaders = ''; - $this->extResponse = ''; - $this->extResponseHeaders = ''; - $this->lastServiceRequest = null; - $lineItemService = $this->getLineItemService(); - if (!empty($lineItemService)) { - $lineItems = $lineItemService->getAll($this->ltiResourceLinkId, $resourceId, $tag, $limit); - $http = $lineItemService->getHttpMessage(); - $this->extResponse = $http->response; - $this->extResponseHeaders = $http->responseHeaders; - $this->extRequest = $http->request; - $this->extRequestHeaders = $http->requestHeaders; - $this->lastServiceRequest = $http; - } - - return $lineItems; - } - - /** - * Create a new line item. - * @param LineItem $lineItem Line item object - * @return bool True if successful - */ - public function createLineItem(LineItem $lineItem): bool - { - $ok = false; - $lineItemService = $this->getLineItemService(); - if (!empty($lineItemService)) { - $lineItem->ltiResourceLinkId = $this->ltiResourceLinkId; - $ok = $lineItemService->createLineItem($lineItem); - } - - return $ok; - } - - /** - * Get all outcomes. - * @param int|null $limit Limit of outcomes to be returned in each request, null for service default - * @return Outcome[]|bool Array of Outcome objects or false on error - */ - public function getOutcomes(int $limit = null) - { - $outcomes = false; - $this->extRequest = ''; - $this->extRequestHeaders = ''; - $this->extResponse = ''; - $this->extResponseHeaders = ''; - $this->lastServiceRequest = null; - $url = $this->getSetting('custom_lineitem_url'); - if (!empty($url)) { - $resultService = new Service\Result($this->getPlatform(), $url); - $outcomes = $resultService->getAll($limit); - $http = $resultService->getHttpMessage(); - $this->extResponse = $http->response; - $this->extResponseHeaders = $http->responseHeaders; - $this->extRequest = $http->request; - $this->extRequestHeaders = $http->requestHeaders; - $this->lastServiceRequest = $http; - } - - return $outcomes; - } - - /** - * Perform an Assessment Control action. - * @param AssessmentControlAction $assessmentControlAction Assessment control object - * @param User $user User object - * @param int $attemptNumber Number of attempt - * @return string|bool The response status or false if the request was not successfully processed - */ - public function doAssessmentControlAction(AssessmentControlAction $assessmentControlAction, User $user, int $attemptNumber) - { - $status = false; - $this->extRequest = ''; - $this->extRequestHeaders = ''; - $this->extResponse = ''; - $this->extResponseHeaders = ''; - $this->lastServiceRequest = null; - $url = $this->getSetting('custom_ap_acs_url'); - if (!empty($url)) { - $assessmentControlService = new Service\AssessmentControl($this, $url); - $status = $assessmentControlService->submitAction($assessmentControlAction, $user, $attemptNumber); - $http = $assessmentControlService->getHttpMessage(); - $this->extResponse = $http->response; - $this->extResponseHeaders = $http->responseHeaders; - $this->extRequest = $http->request; - $this->extRequestHeaders = $http->requestHeaders; - $this->lastServiceRequest = $http; - } - - return $status; - } - - // /** - // * Class constructor from consumer. - // * - // * @deprecated Use fromPlatform() instead - // * @see ResourceLink::fromPlatform() - // * - // * @param ToolConsumer $consumer Consumer object - // * @param string $ltiResourceLinkId Resource link ID value - // * @param string $tempId Temporary Resource link ID value (optional, default is null) - // * - // * @return ResourceLink - // */ - // public static function fromConsumer($consumer, $ltiResourceLinkId, $tempId = null) - // { - // Util::logDebug('Method ceLTIc\LTI\ResourceLink::fromConsumer() has been deprecated; please use ceLTIc\LTI\ResourceLink::fromPlatform() instead.', - // true); - // return self::fromPlatform($consumer, $ltiResourceLinkId, $tempId); - // } - - /** - * Class constructor from platform. - * @param Platform $platform Platform object - * @param string $ltiResourceLinkId Resource link ID value - * @param string|null $tempId Temporary Resource link ID value (optional, default is null) - * @return ResourceLink - */ - public static function fromPlatform(Platform $platform, string $ltiResourceLinkId, string $tempId = null): ResourceLink - { - $resourceLink = new ResourceLink(); - $resourceLink->platform = $platform; - $resourceLink->dataConnector = $platform->getDataConnector(); - $resourceLink->ltiResourceLinkId = $ltiResourceLinkId; - if (!empty($ltiResourceLinkId)) { - $resourceLink->load(); - if (is_null($resourceLink->id) && !empty($tempId)) { - $resourceLink->ltiResourceLinkId = $tempId; - $resourceLink->load(); - $resourceLink->ltiResourceLinkId = $ltiResourceLinkId; - } - } - - return $resourceLink; - } - - /** - * Class constructor from context. - * @param Context $context Context object - * @param string $ltiResourceLinkId Resource link ID value - * @param string|null $tempId Temporary Resource link ID value (optional, default is null) - * @return ResourceLink - */ - public static function fromContext(Context $context, string $ltiResourceLinkId, string $tempId = null): ResourceLink - { - $resourceLink = new ResourceLink(); - $resourceLink->setContext($context); - $resourceLink->dataConnector = $context->getDataConnector(); - $resourceLink->ltiResourceLinkId = $ltiResourceLinkId; - if (!empty($ltiResourceLinkId)) { - $resourceLink->load(); - if (is_null($resourceLink->id) && !empty($tempId)) { - $resourceLink->ltiResourceLinkId = $tempId; - $resourceLink->load(); - $resourceLink->ltiResourceLinkId = $ltiResourceLinkId; - } - $resourceLink->setContext($context); // Ensure context remains set - } - - return $resourceLink; - } - - /** - * Load the resource link from the database. - * @param int $id Record ID of resource link - * @param DataConnector $dataConnector Database connection object - * @return ResourceLink ResourceLink object - */ - public static function fromRecordId(int $id, DataConnector $dataConnector): ResourceLink - { - $resourceLink = new ResourceLink(); - $resourceLink->dataConnector = $dataConnector; - $resourceLink->load($id); - - return $resourceLink; - } - - ### - ### PRIVATE METHODS - ### - - /** - * Load the resource link from the database. - * @param int|null $id Record ID of resource link (optional, default is null) - * @return bool True if resource link was successfully loaded - */ - private function load(int $id = null): bool - { - $this->initialize(); - $this->id = $id; - - return $this->getDataConnector()->loadResourceLink($this); - } - - /** - * Convert data type of value to a supported type if possible. - * @param Outcome $ltiOutcome Outcome object - * @param array|null $supportedTypes Array of outcome types to be supported (optional, default is null to use supported types reported in the last launch for this resource link) - * @return bool True if the type/value are valid and supported - */ - private function checkValueType(Outcome $ltiOutcome, array $supportedTypes = null): bool - { - if (empty($supportedTypes)) { - $supportedTypes = explode( - ',', - str_replace(' ', '', strtolower($this->getSetting('ext_ims_lis_resultvalue_sourcedids', self::EXT_TYPE_DECIMAL))) - ); - } - $type = $ltiOutcome->type; - $value = $ltiOutcome->getValue(); - // Check whether the type is supported or there is no value - $ok = in_array($type, $supportedTypes) || empty($value); - if (!$ok) { - // Convert numeric values to decimal - if ($type === self::EXT_TYPE_PERCENTAGE) { - if (substr($value, -1) === '%') { - $value = substr($value, 0, -1); - } - $ok = is_numeric($value) && ($value >= 0) && ($value <= 100); - if ($ok) { - $ltiOutcome->setValue($value / 100); - $ltiOutcome->type = self::EXT_TYPE_DECIMAL; - } - } elseif ($type === self::EXT_TYPE_RATIO) { - $parts = explode('/', $value, 2); - $ok = (count($parts) === 2) && is_numeric($parts[0]) && is_numeric($parts[1]) && ($parts[0] >= 0) && ($parts[1] > 0); - if ($ok) { - $ltiOutcome->setValue($parts[0] / $parts[1]); - $ltiOutcome->type = self::EXT_TYPE_DECIMAL; - } - // Convert letter_af to letter_af_plus or text - } elseif ($type === self::EXT_TYPE_LETTER_AF) { - if (in_array(self::EXT_TYPE_LETTER_AF_PLUS, $supportedTypes)) { - $ok = true; - $ltiOutcome->type = self::EXT_TYPE_LETTER_AF_PLUS; - } elseif (in_array(self::EXT_TYPE_TEXT, $supportedTypes)) { - $ok = true; - $ltiOutcome->type = self::EXT_TYPE_TEXT; - } - // Convert letter_af_plus to letter_af or text - } elseif ($type === self::EXT_TYPE_LETTER_AF_PLUS) { - if (in_array(self::EXT_TYPE_LETTER_AF, $supportedTypes) && (strlen($value) === 1)) { - $ok = true; - $ltiOutcome->type = self::EXT_TYPE_LETTER_AF; - } elseif (in_array(self::EXT_TYPE_TEXT, $supportedTypes)) { - $ok = true; - $ltiOutcome->type = self::EXT_TYPE_TEXT; - } - // Convert text to decimal - } elseif ($type === self::EXT_TYPE_TEXT) { - $ok = is_numeric($value) && ($value >= 0) && ($value <= 1); - if ($ok) { - $ltiOutcome->type = self::EXT_TYPE_DECIMAL; - } elseif (substr($value, -1) === '%') { - $value = substr($value, 0, -1); - $ok = is_numeric($value) && ($value >= 0) && ($value <= 100); - if ($ok) { - if (in_array(self::EXT_TYPE_PERCENTAGE, $supportedTypes)) { - $ltiOutcome->type = self::EXT_TYPE_PERCENTAGE; - } else { - $ltiOutcome->setValue($value / 100); - $ltiOutcome->type = self::EXT_TYPE_DECIMAL; - } - } - } - } - } - - return $ok; - } - - /** - * Send an unofficial LTI service request to the platform. - * @param string $type Message type value - * @param string $url URL to send request to - * @param array $params Associative array of parameter values to be passed - * @param string $scope Scope for service - * @return bool True if the request successfully obtained a response - */ - private function doService(string $type, string $url, array $params, string $scope): bool - { - $ok = false; - $this->extRequest = ''; - $this->extRequestHeaders = ''; - $this->extResponse = ''; - $this->extResponseHeaders = ''; - $this->lastServiceRequest = null; - if (!empty($url)) { - $params['lti_version'] = $this->getPlatform()->ltiVersion; - $params['lti_message_type'] = $type; - $retry = false; - $newToken = false; - if (!$this->getPlatform()->useOAuth1()) { - $accessToken = $this->platform->getAccessToken(); - $retry = true; - if (empty($accessToken)) { - $accessToken = new AccessToken($this->platform); - $this->platform->setAccessToken($accessToken); - } - if (!$accessToken->hasScope($scope) && (empty(Tool::$defaultTool) || !in_array( - $scope, - Tool::$defaultTool->requiredScopes - ))) { - $accessToken->expires = time(); - $accessToken->get($scope, true); - $this->platform->setAccessToken($accessToken); - $newToken = true; - } - } - do { - // Add message signature - $signed = $this->getPlatform()->addSignature($url, $params, 'POST', 'application/x-www-form-urlencoded'); - // Connect to platform - if (is_array($signed)) { - $http = new HttpMessage($url, 'POST', $signed); - } else { - $http = new HttpMessage($url, 'POST', $params, $signed); - } - if ($http->send()) { - // Parse XML response - $this->extResponse = $http->response; - $this->extResponseHeaders = $http->responseHeaders; - try { - $this->extDoc = new DOMDocument(); - $this->extDoc->loadXML($http->response); - $this->extNodes = $this->domnodeToArray($this->extDoc->documentElement); - if (isset($this->extNodes['statusinfo']['codemajor']) && ($this->extNodes['statusinfo']['codemajor'] === 'Success')) { - $ok = true; - } - } catch (\Exception $e) { - } - } - $retry = $retry && !$newToken && !$ok; - if ($retry) { // Obtain a new access token just for the required scope - $accessToken = $this->platform->getAccessToken(); - $accessToken->expires = time(); - $accessToken->get($scope, true); - $this->platform->setAccessToken($accessToken); - $newToken = true; - } - } while ($retry); - $this->extRequest = $http->request; - $this->extRequestHeaders = $http->requestHeaders; - $this->lastServiceRequest = $http; - } - - return $ok; - } - - /** - * Send a request to the Result service endpoint. - * @param UserResult $userResult UserResult object - * @param string $url URL to send request to - * @return Outcome Outcome object - */ - private function doResultService(UserResult $userResult, string $url): ?Outcome - { - $outcome = null; - $this->extRequest = ''; - $this->extRequestHeaders = ''; - $this->extResponse = ''; - $this->extResponseHeaders = ''; - $this->lastServiceRequest = null; - if (!empty($url)) { - $resultService = new Service\Result($this->getPlatform(), $url); - $outcome = $resultService->get($userResult); - $http = $resultService->getHttpMessage(); - $this->extResponse = $http->response; - $this->extResponseHeaders = $http->responseHeaders; - $this->extRequest = $http->request; - $this->extRequestHeaders = $http->requestHeaders; - $this->lastServiceRequest = $http; - } - - return $outcome; - } - - /** - * Send a service request to the Score service endpoint. - * @param Outcome $ltiOutcome Outcome object - * @param UserResult $userResult UserResult object - * @param string $url URL to send request to - * @return bool True if the request successfully obtained a response - */ - private function doScoreService(Outcome $ltiOutcome, UserResult $userResult, string $url): bool - { - $ok = false; - $this->extRequest = ''; - $this->extRequestHeaders = ''; - $this->extResponse = ''; - $this->extResponseHeaders = ''; - $this->lastServiceRequest = null; - if (!empty($url)) { - $scoreService = new Service\Score($this->getPlatform(), $url); - $scoreService->submit($ltiOutcome, $userResult); - $http = $scoreService->getHttpMessage(); - $this->extResponse = $http->response; - $this->extResponseHeaders = $http->responseHeaders; - $ok = $http->ok; - $this->extRequest = $http->request; - $this->extRequestHeaders = $http->requestHeaders; - $this->lastServiceRequest = $http; - } - - return $ok; - } - - /** - * Send an LTI 1.1 service request to the platform. - * @param string $type Message type value - * @param string $url URL to send request to - * @param string $xml XML of message request - * @return bool True if the request successfully obtained a response - */ - private function doLTI11Service(string $type, string $url, string $xml): bool - { - $ok = false; - $this->extRequest = ''; - $this->extRequestHeaders = ''; - $this->extResponse = ''; - $this->extResponseHeaders = ''; - $this->lastServiceRequest = null; - if (!empty($url)) { - $id = uniqid(); - $xmlRequest = <<< EOD - - - - - V1.0 - {$id} - - - - <{$type}Request> -{$xml} - - - -EOD; - $scope = 'https://purl.imsglobal.org/spec/lti-bo/scope/basicoutcome'; - $retry = false; - $newToken = false; - if (!$this->getPlatform()->useOAuth1()) { - $accessToken = $this->platform->getAccessToken(); - $retry = true; - if (empty($accessToken)) { - $accessToken = new AccessToken($this->platform); - $this->platform->setAccessToken($accessToken); - } - if (!$accessToken->hasScope($scope) && (empty(Tool::$defaultTool) || !in_array( - $scope, - Tool::$defaultTool->requiredScopes - ))) { - $accessToken->expires = time(); - $accessToken->get($scope, true); - $this->platform->setAccessToken($accessToken); - $newToken = true; - } - } - do { - // Add message signature - $header = $this->getPlatform()->addSignature($url, $xmlRequest, 'POST', 'application/xml'); - // Connect to platform - $http = new \ILIAS\LTI\ToolProvider\Http\HttpMessage($url, 'POST', $xmlRequest, $header); - if ($http->send()) { - // Parse XML response - $this->extResponse = $http->response; - $this->extResponseHeaders = $http->responseHeaders; - try { - $this->extDoc = new DOMDocument(); - $this->extDoc->loadXML($http->response); - $this->extNodes = $this->domnodeToArray($this->extDoc->documentElement); - if (isset($this->extNodes['imsx_POXHeader']['imsx_POXResponseHeaderInfo']['imsx_statusInfo']['imsx_codeMajor']) && - ($this->extNodes['imsx_POXHeader']['imsx_POXResponseHeaderInfo']['imsx_statusInfo']['imsx_codeMajor'] === 'success')) { - $ok = true; - } - } catch (\Exception $e) { - } - } - $retry = $retry && !$newToken && !$ok; - if ($retry) { // Obtain a new access token just for the required scope - $accessToken = $this->platform->getAccessToken(); - $accessToken->expires = time(); - $accessToken->get($scope, true); - $this->platform->setAccessToken($accessToken); - $newToken = true; - } - } while ($retry); - $this->extRequest = $http->request; - $this->extRequestHeaders = $http->requestHeaders; - $this->lastServiceRequest = $http; - } - - return $ok; - } - - /** - * Get the Line Item service object. - * - * @return bool|Service\LineItem Line Item service, or false if not available //erased Service\\LineItem - */ - private function getLineItemService() - { - $url = $this->getSetting('custom_lineitems_url'); - if (!empty($url)) { - $lineItemService = new Service\LineItem($this->getPlatform(), $url); - } else { - $lineItemService = false; - } - - return $lineItemService; - } - - /** - * Convert DOM nodes to array. - * @param DOMElement|\DOMText $node XML element - * @return array|string Array of XML document elements - */ - private function domnodeToArray($node) - { - $output = array(); - switch ($node->nodeType) { - case XML_CDATA_SECTION_NODE: - case XML_TEXT_NODE: - $output = trim($node->textContent); - break; - case XML_ELEMENT_NODE: - for ($i = 0; $i < $node->childNodes->length; $i++) { - $child = $node->childNodes->item($i); - $v = $this->domnodeToArray($child); - if (isset($child->tagName)) { - $output[$child->tagName][] = $v; - } else { - $s = (string) $v; - if (!empty($s)) { - $output = $s; - } - } - } - if (is_array($output)) { - if ($node->hasAttributes()) { - foreach ($node->attributes as $attrNode) { - $output['@attributes'][$attrNode->name] = (string) $attrNode->value; - } - } - foreach ($output as $t => $v) { - if (is_array($v) && (count($v) === 1) && ($t !== '@attributes')) { - $output[$t] = $v[0]; - } - } - } - break; - } - - return $output; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/ResourceLinkShare.php b/components/ILIAS/LTI/src/ToolProvider/ResourceLinkShare.php deleted file mode 100755 index ac9e164c713a..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/ResourceLinkShare.php +++ /dev/null @@ -1,64 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class ResourceLinkShare -{ - /** - * Consumer name value. - * - * @var string|null $consumerName - */ - public ?string $consumerName = null; - - /** - * Resource link ID value. - * - * @var string|null $resourceLinkId - */ - public ?string $resourceLinkId = null; - - /** - * Title of sharing context. - * - * @var string|null $title - */ - public ?string $title = null; - - /** - * Whether sharing request is to be automatically approved on first use. - * - * @var bool|null $approved - */ - public ?bool $approved = null; - - /** - * Class constructor. - */ - public function __construct() - { - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/ResourceLinkShareKey.php b/components/ILIAS/LTI/src/ToolProvider/ResourceLinkShareKey.php deleted file mode 100755 index d34e12380331..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/ResourceLinkShareKey.php +++ /dev/null @@ -1,202 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class ResourceLinkShareKey -{ - /** - * Maximum permitted life for a share key value. - */ - public const MAX_SHARE_KEY_LIFE = 168; // in hours (1 week) - - /** - * Default life for a share key value. - */ - public const DEFAULT_SHARE_KEY_LIFE = 24; // in hours - - /** - * Minimum length for a share key value. - */ - public const MIN_SHARE_KEY_LENGTH = 5; - - /** - * Maximum length for a share key value. - */ - public const MAX_SHARE_KEY_LENGTH = 32; - - /** - * ID for resource link being shared. - * - * @var int|null $resourceLinkId - */ - public ?int $resourceLinkId = null; - - /** - * Length of share key. - * - * @var int|null $length - */ - public ?int $length = null; - - /** - * Life of share key. - * - * @var int|null $life - */ - public ?int $life = null; // in hours - - /** - * Whether the sharing arrangement should be automatically approved when first used. - * - * @var bool $autoApprove - */ - public bool $autoApprove = false; - - /** - * Timestamp for when the share key expires. - * - * @var int|null $expires - */ - public ?int $expires = null; - - /** - * Share key value. - * - * @var string|null $id - */ - private ?string $id = null; - - /** - * Data connector. - * - * @var DataConnector|null $dataConnector - */ - private ?DataConnector $dataConnector = null; - - /** - * Class constructor. - * @param ResourceLink $resourceLink ResourceLink object - * @param string|null $id Value of share key (optional, default is null) - */ - public function __construct(ResourceLink $resourceLink, string $id = null) - { - $this->initialize(); - $this->dataConnector = $resourceLink->getDataConnector(); - $this->id = $id; - if (!empty($id)) { - $this->load(); - } else { - $this->resourceLinkId = $resourceLink->getRecordId(); - } - } - - /** - * Initialise the resource link share key. - */ - public function initialize() - { - $this->length = null; - $this->life = null; - $this->autoApprove = false; - $this->expires = null; - } - - /** - * Initialise the resource link share key. - * - * Synonym for initialize(). - */ - public function initialise() - { - $this->initialize(); - } - - /** - * Save the resource link share key to the database. - * - * @return bool True if the share key was successfully saved - */ - public function save(): bool - { - if (empty($this->life)) { - $this->life = self::DEFAULT_SHARE_KEY_LIFE; - } else { - $this->life = max(min($this->life, self::MAX_SHARE_KEY_LIFE), 0); - } - $this->expires = time() + ($this->life * 60 * 60); - if (empty($this->id)) { - if (empty($this->length) || !is_numeric($this->length)) { - $this->length = self::MAX_SHARE_KEY_LENGTH; - } else { - $this->length = max(min($this->length, self::MAX_SHARE_KEY_LENGTH), self::MIN_SHARE_KEY_LENGTH); - } - $this->id = Util::getRandomString($this->length); - } - - return $this->dataConnector->saveResourceLinkShareKey($this); - } - - /** - * Delete the resource link share key from the database. - * - * @return bool True if the share key was successfully deleted - */ - public function delete(): bool - { - return $this->dataConnector->deleteResourceLinkShareKey($this); - } - - /** - * Get share key value. - * - * @return string Share key value - */ - public function getId(): ?string - { - return $this->id; - } - - ### - ### PRIVATE METHOD - ### - - /** - * Load the resource link share key from the database. - */ - private function load() - { - $this->initialize(); - $this->dataConnector->loadResourceLinkShareKey($this); - if (!is_null($this->id)) { - $this->length = strlen(strval($this->id)); - } - if (!is_null($this->expires)) { - $this->life = ($this->expires - time()) / 60 / 60; - } - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/Service/AssessmentControl.php b/components/ILIAS/LTI/src/ToolProvider/Service/AssessmentControl.php deleted file mode 100755 index 86c5abc72ff1..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/Service/AssessmentControl.php +++ /dev/null @@ -1,98 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class AssessmentControl extends Service -{ - /** - * Access scope. - */ - public static string $SCOPE = 'https://purl.imsglobal.org/spec/lti-ap/scope/control.all'; - - /** - * Resource link for this service request. - * - * @var \ILIAS\LTI\ToolProvider\ResourceLink $resourceLink - */ - private ?\ILIAS\LTI\ToolProvider\ResourceLink $resourceLink = null; - - /** - * Class constructor. - * @param \ILIAS\LTI\ToolProvider\ResourceLink $resourceLink Resource link object for this service request - * @param string $endpoint Service endpoint - */ - public function __construct(\ILIAS\LTI\ToolProvider\ResourceLink $resourceLink, string $endpoint) - { - parent::__construct($resourceLink->getPlatform(), $endpoint); - $this->resourceLink = $resourceLink; - $this->scope = self::$SCOPE; - $this->mediaType = 'application/vnd.ims.lti-ap.v1.control+json'; - } - - /** - * Submit an assessment control action. - * @param \ILIAS\LTI\ToolProvider\AssessmentControlAction $assessmentControlAction AssessmentControlAction object - * @param \ILIAS\LTI\ToolProvider\User $user User object - * @param int $attemptNumber Attempt number - * @return string|bool Value of the status response, or false if not successful - */ - // public function submitAction($assessmentControlAction, $user, $attemptNumber) - public function submitAction(\ILIAS\LTI\ToolProvider\AssessmentControlAction $assessmentControlAction, \ILIAS\LTI\ToolProvider\User $user, int $attemptNumber) - { - $status = false; - $json = array( - 'user' => array('iss' => $this->resourceLink->getPlatform()->platformId, 'sub' => $user->ltiUserId), - 'resource_link' => array('id' => $this->resourceLink->ltiResourceLinkId), - 'attempt_number' => $attemptNumber, - 'action' => $assessmentControlAction->getAction(), -// 'incident_time' => $assessmentControlAction->getDate()->format('Y-m-d\TH:i:s\Z'), //UK:changed - 'incident_time' => date(('Y-m-d\TH:i:s\Z'), $assessmentControlAction->getDate()), - 'incident_severity' => $assessmentControlAction->getSeverity() - ); - if (!empty($assessmentControlAction->extraTime)) { - $json['extra_time'] = $assessmentControlAction->extraTime; - } - if (!empty($assessmentControlAction->code)) { - $json['reason_code'] = $assessmentControlAction->code; - } - if (!empty($assessmentControlAction->message)) { - $json['reason_msg'] = $assessmentControlAction->message; - } - $data = json_encode($json); - //UK changed from $http = $this->send('POST', null, $data); - $http = $this->send('POST', [], $data); - if ($http->ok) { - $http->ok = !empty($http->responseJson->status); - if ($http->ok) { - $status = $http->responseJson->status; - } - } - - return $status; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/Service/AssignmentGrade.php b/components/ILIAS/LTI/src/ToolProvider/Service/AssignmentGrade.php deleted file mode 100755 index 47bc57c54d70..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/Service/AssignmentGrade.php +++ /dev/null @@ -1,60 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class AssignmentGrade extends Service -{ - /** - * Class constructor. - * @param \ILIAS\LTI\ToolProvider\Platform $platform Platform object for this service request - * @param string $endpoint Service endpoint - * @param string $path Path (optional) - */ - public function __construct(\ILIAS\LTI\ToolProvider\Platform $platform, string $endpoint, string $path = '') - { - $endpoint = self::addPath($endpoint, $path); - parent::__construct($platform, $endpoint); - } - - /** - * Add path to a URL. - * @param string $endpoint Service endpoint - * @param string $path Path - * @return string The endpoint with the path added - */ - private static function addPath(string $endpoint, string $path): string - { - if (strpos($endpoint, '?') === false) { - if (substr($endpoint, -strlen($path)) !== $path) { - $endpoint .= $path; - } - } elseif (strpos($endpoint, "{$path}?") === false) { - $endpoint = str_replace('?', "{$path}?", $endpoint); - } - - return $endpoint; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/Service/Groups.php b/components/ILIAS/LTI/src/ToolProvider/Service/Groups.php deleted file mode 100755 index 475e0bd2bf0c..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/Service/Groups.php +++ /dev/null @@ -1,266 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class Groups extends Service -{ - /** - * Media type for course group sets service. - */ - public const MEDIA_TYPE_COURSE_GROUP_SETS = 'application/vnd.ims.lti-gs.v1.contextgroupsetcontainer+json'; - - /** - * Media type for course groups service. - */ - public const MEDIA_TYPE_COURSE_GROUPS = 'application/vnd.ims.lti-gs.v1.contextgroupcontainer+json'; - - /** - * Access scope. - */ - public static string $SCOPE = 'https://purl.imsglobal.org/spec/lti-gs/scope/contextgroup.readonly'; - - /** - * Default limit on size of container to be returned from requests. - */ - public static ?int $defaultLimit = null; //UK added ?int - - /** - * The context to which the course groups apply. - * - * @var Context $context - */ - private ?Context $context = null; - - /** - * The endpoint for course group requests. - * - * @var string $groupsEndpoint - */ - private ?string $groupsEndpoint = null; - - /** - * The endpoint for course groupset requests. - * - * @var string $groupSetsEndpoint - */ - private ?string $groupSetsEndpoint = null; - - /** - * Limit on size of container to be returned from requests. - * - * A limit of null (or zero) will disable paging of requests - * - * @var int|null $limit - */ - private ?int $limit; - - /** - * Whether requests should be made one page at a time when a limit is set. - * - * When false, all objects will be requested, even if this requires several requests based on the limit set. - * - * @var boolean $pagingMode - */ - private bool $pagingMode; - - /** - * Class constructor. - * @param object $context The context to which the course groups apply //UK check: Context - * @param string $groupsEndpoint Service endpoint for course groups - * @param string|null $groupSetsEndpoint Service endpoint for course group sets (optional) - * @param int|null $limit Limit of objects to be returned in each request, null for all - * @param boolean $pagingMode True if only a single page should be requested when a limit is set - */ - public function __construct(Context $context, string $groupsEndpoint, string $groupSetsEndpoint = null, int $limit = null, bool $pagingMode = false) - { - $platform = $context->getPlatform(); - parent::__construct($platform, $groupsEndpoint); - $this->scope = self::$SCOPE; - $this->mediaType = self::MEDIA_TYPE_COURSE_GROUPS; - $this->context = $context; - $this->groupsEndpoint = $groupsEndpoint; - $this->groupSetsEndpoint = $groupSetsEndpoint; - $this->limit = $limit; - $this->pagingMode = $pagingMode; - } - - /** - * Get the course group sets and groups. - * @param bool $allowNonSets Include groups which are not part of a set (optional) - * @param \ILIAS\LTI\ToolProvider\User|null $user Limit response to groups for specified user (optional) - * @param int|null $limit Limit on the number of objects to be returned in each request, null for service default (optional) - * @return bool True if the operation was successful - */ - public function get(bool $allowNonSets = false, \ILIAS\LTI\ToolProvider\User $user = null, int $limit = null): bool - { - $ok = $this->getGroupSets($limit); - if ($ok) { - $ok = $this->getGroups($allowNonSets, $user, $limit); - } - if (!$ok) { - $this->context->groupSets = null; - $this->context->groups = null; - } - - return $ok; - } - - /** - * Get the course group sets. - * @param int|null $limit Limit on the number of course group sets to be returned in each request, null for service default (optional) - * @return bool True if the operation was successful - */ - public function getGroupSets(int $limit = null): bool - { - $this->endpoint = $this->groupSetsEndpoint; - $ok = !empty($this->endpoint); - if ($ok) { - $this->mediaType = self::MEDIA_TYPE_COURSE_GROUP_SETS; - $parameters = array(); - if (is_null($limit)) { - $limit = $this->limit; - } - if (is_null($limit)) { - $limit = self::$defaultLimit; - } - if (!empty($limit)) { - $parameters['limit'] = strval($limit); - } - $this->context->groupSets = array(); - $groupSets = array(); - $endpoint = $this->endpoint; - do { - $http = $this->send('GET', $parameters); - $ok = !empty($http) && $http->ok; - $url = ''; - if ($ok) { - if (isset($http->responseJson->sets)) { - foreach ($http->responseJson->sets as $set) { - $groupSets[$set->id] = array('title' => $set->name, 'groups' => array(), - 'num_members' => 0, 'num_staff' => 0, 'num_learners' => 0); - } - } - if (!$this->pagingMode && $http->hasRelativeLink('next')) { - $url = $http->getRelativeLink('next'); - $this->endpoint = $url; - $parameters = array(); - } - } - } while ($url); - $this->endpoint = $endpoint; - if ($ok) { - $this->context->groupSets = $groupSets; - } - } - - return $ok; - } - - /** - * Get the course groups. - * @param bool $allowNonSets Include groups which are not part of a set (optional) - * @param \ILIAS\LTI\ToolProvider\User|null $user Limit response to groups for specified user (optional) - * @param int|null $limit Limit on the number of course groups to be returned in each request, null for service default (optional) - * @return bool True if the operation was successful - */ - public function getGroups(bool $allowNonSets = false, \ILIAS\LTI\ToolProvider\User $user = null, int $limit = null): bool - { - $this->endpoint = $this->groupsEndpoint; - $ok = !empty($this->endpoint); - if ($ok) { - $this->mediaType = self::MEDIA_TYPE_COURSE_GROUPS; - $parameters = array(); - $ltiUserId = null; - if (!empty($user) && !empty($user->ltiUserId)) { - $ltiUserId = $user->ltiUserId; - } - if (!empty($ltiUserId)) { - $parameters['user_id'] = $ltiUserId; - } - if (is_null($limit)) { - $limit = $this->limit; - } - if (is_null($limit)) { - $limit = self::$defaultLimit; - } - if (!empty($limit)) { - $parameters['limit'] = strval($limit); - } - if (is_null($this->context->groupSets)) { - $groupSets = array(); - } else { - $groupSets = $this->context->groupSets; - } - $groups = array(); - $endpoint = $this->endpoint; - do { - $http = $this->send('GET', $parameters); - $ok = !empty($http) && $http->ok; - $url = ''; - if ($ok) { - if (isset($http->responseJson->groups)) { - foreach ($http->responseJson->groups as $agroup) { - if (!$allowNonSets && empty($agroup->set_id)) { - continue; - } - $group = array('title' => $agroup->name); - if (!empty($agroup->set_id)) { - if (!array_key_exists($agroup->set_id, $groupSets)) { - $groupSets[$agroup->set_id] = array('title' => "Set {$agroup->set_id}", 'groups' => array(), - 'num_members' => 0, 'num_staff' => 0, 'num_learners' => 0); - } - $groupSets[$agroup->set_id]['groups'][] = $agroup->id; - $group['set'] = $agroup->set_id; - } - if (!empty($agroup->tag)) { - $group['tag'] = $agroup->tag; - } - $groups[$agroup->id] = $group; - } - } - if (!$this->pagingMode && $http->hasRelativeLink('next')) { - $url = $http->getRelativeLink('next'); - $this->endpoint = $url; - $parameters = array(); - } - } - } while ($url); - $this->endpoint = $endpoint; - if ($ok) { - $this->context->groupSets = $groupSets; - if (empty($ltiUserId)) { - $this->context->groups = $groups; - } else { - $user->groups = $groups; - } - } - } - - return $ok; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/Service/LineItem.php b/components/ILIAS/LTI/src/ToolProvider/Service/LineItem.php deleted file mode 100755 index 3f7f564038c0..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/Service/LineItem.php +++ /dev/null @@ -1,304 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class LineItem extends AssignmentGrade -{ - /** - * Line item media type. - */ - public const MEDIA_TYPE_LINE_ITEM = 'application/vnd.ims.lis.v2.lineitem+json'; - - /** - * Line item container media type. - */ - public const MEDIA_TYPE_LINE_ITEMS = 'application/vnd.ims.lis.v2.lineitemcontainer+json'; - - /** - * Access scope. - */ - public static string $SCOPE = 'https://purl.imsglobal.org/spec/lti-ags/scope/lineitem'; - - /** - * Read-only access scope. - */ - public static string $SCOPE_READONLY = 'https://purl.imsglobal.org/spec/lti-ags/scope/lineitem.readonly'; - - /** - * Default limit on size of container to be returned from requests. - */ - public static ?int $defaultLimit = null; //UK added ?int - - /** - * Limit on size of container to be returned from requests. - * - * A limit of null (or zero) will disable paging of requests - * - * @var int|null $limit - */ - private ?int $limit; - - /** - * Whether requests should be made one page at a time when a limit is set. - * - * When false, all objects will be requested, even if this requires several requests based on the limit set. - * - * @var boolean $pagingMode - */ - private bool $pagingMode; - - /** - * Class constructor. - * @param Platform $platform Platform object for this service request - * @param string $endpoint Service endpoint - * @param int|null $limit Limit of line items to be returned in each request, null for all - * @param boolean $pagingMode True if only a single page should be requested when a limit is set - */ - public function __construct(Platform $platform, string $endpoint, int $limit = null, bool $pagingMode = false) - { - parent::__construct($platform, $endpoint); - $this->limit = $limit; - $this->pagingMode = $pagingMode; - $this->scope = self::$SCOPE; - } - - /** - * Retrieve all line items. - * The returned items can be filtered by a resource link ID, a resource ID and/or a tag. Requests can - * also be limited to a number of items which may mean that multiple requests will be made to retrieve the - * full list. - * @param string|null $ltiResourceLinkId LTI resource link ID (optional) - * @param string|null $resourceId Tool resource ID (optional) - * @param string|null $tag Tag (optional) - * @param int|null $limit Limit of line items to be returned in each request, null for service default (optional) - * @return LineItem[]|bool Array of LineItem objects or false on error - */ - public function getAll(string $ltiResourceLinkId = null, string $resourceId = null, string $tag = null, int $limit = null) - { - $params = array(); - if (!empty($ltiResourceLinkId)) { - $params['resource_link_id'] = $ltiResourceLinkId; - } - if (!empty($resourceId)) { - $params['resource_id'] = $resourceId; - } - if (!empty($tag)) { - $params['tag'] = $tag; - } - if (is_null($limit)) { - $limit = $this->limit; - } - if (is_null($limit)) { - $limit = self::$defaultLimit; - } - if (!empty($limit)) { - $params['limit'] = $limit; - } - $lineItems = array(); - $endpoint = $this->endpoint; - do { - $this->scope = self::$SCOPE_READONLY; - $this->mediaType = self::MEDIA_TYPE_LINE_ITEMS; - $http = $this->send('GET', $params); - $this->scope = self::$SCOPE; - $url = ''; - if ($http->ok) { - if (!empty($http->responseJson)) { - foreach ($http->responseJson as $lineItem) { - $lineItems[] = self::toLineItem($this->getPlatform(), $lineItem); - } - } - if (!$this->pagingMode && $http->hasRelativeLink('next')) { - $url = $http->getRelativeLink('next'); - $this->endpoint = $url; - $params = array(); - } - } else { - $lineItems = false; - } - } while ($url); - $this->endpoint = $endpoint; - - return $lineItems; - } - - /** - * Create a new line item. - * @param \ILIAS\LTI\ToolProvider\LineItem $lineItem Line item object //UK: changed from LTI\LineItem - * @return bool True if successful - */ - public function createLineItem(ToolProvider\LineItem $lineItem): bool - { - $lineItem->endpoint = null; - $this->mediaType = self::MEDIA_TYPE_LINE_ITEM; - //UK changed from $http = $this->send('POST', null, self::toJson($lineItem)); - $http = $this->send('POST', [], self::toJson($lineItem)); - $ok = $http->ok && !empty($http->responseJson); - if ($ok) { - $newLineItem = self::toLineItem($this->getPlatform(), $http->responseJson); - foreach (get_object_vars($newLineItem) as $key => $value) { - $lineItem->$key = $value; - } - } - - return $ok; - } - - /** - * Save a line item. - * @param \ILIAS\LTI\ToolProvider\LineItem $lineItem Line item object //UK: changed from LTI\LineItem - * @return bool True if successful - */ - public function saveLineItem(ToolProvider\LineItem $lineItem): bool - { - $this->mediaType = self::MEDIA_TYPE_LINE_ITEM; - //UK changed from $http = $this->send('PUT', null, self::toJson($lineItem)); - $http = $this->send('PUT', [], self::toJson($lineItem)); - $ok = $http->ok; - if ($ok && !empty($http->responseJson)) { - $savedLineItem = self::toLineItem($this->getPlatform(), $http->responseJson); - foreach (get_object_vars($savedLineItem) as $key => $value) { - $lineItem->$key = $value; - } - } - - return $ok; - } - - /** - * Delete a line item. - * @param ToolProvider\LineItem $lineItem Line item object //UK: changed from LTI\LineItem - * @return bool True if successful - */ - public function deleteLineItem(ToolProvider\LineItem $lineItem): bool - { - $this->mediaType = self::MEDIA_TYPE_LINE_ITEM; - $http = $this->send('DELETE'); - - return $http->ok; - } - - /** - * Retrieve a line item. - * @param Platform $platform Platform object for this service request - * @param string $endpoint Line item endpoint - * @return \ILIAS\LTI\ToolProvider\LineItem|bool LineItem object, or false on error //UK: changed from LTI\\LineItem|bool - */ - public static function getLineItem(Platform $platform, string $endpoint) - { - $service = new self($platform, $endpoint); - $service->scope = self::$SCOPE_READONLY; - $service->mediaType = self::MEDIA_TYPE_LINE_ITEM; - $http = $service->send('GET'); - $service->scope = self::$SCOPE; - if ($http->ok && !empty($http->responseJson)) { - $lineItem = self::toLineItem($platform, $http->responseJson); - } else { - $lineItem = false; - } - - return $lineItem; - } - - ### - ### PRIVATE METHODS - ### - - /** - * Create a line item from a JSON object. - * @param Platform $platform Platform object for this service request - * @param object $json JSON object to convert - * @return null|\ILIAS\LTI\ToolProvider\LineItem LineItem object, or null on error //UK: changed from LTI\\LineItem|null - */ - private static function toLineItem(Platform $platform, object $json): ?ToolProvider\LineItem - { - if (!empty($json->id) && !empty($json->label) && !empty($json->scoreMaximum)) { - // $lineItem = new LTI\LineItem($platform, $json->label, $json->scoreMaximum); - $lineItem = new \ILIAS\LTI\ToolProvider\LineItem($platform, $json->label, $json->scoreMaximum); - if (!empty($json->id)) { - $lineItem->endpoint = $json->id; - } - if (!empty($json->resourceLinkId)) { - $lineItem->ltiResourceLinkId = $json->resourceLinkId; - } - if (!empty($json->resourceId)) { - $lineItem->resourceId = $json->resourceId; - } - if (!empty($json->tag)) { - $lineItem->tag = $json->tag; - } - if (!empty($json->startDateTime)) { - $lineItem->submitFrom = strtotime($json->startDateTime); - } - if (!empty($json->endDateTime)) { - $lineItem->submitUntil = strtotime($json->endDateTime); - } - } else { - $lineItem = null; - } - - return $lineItem; - } - - /** - * Create a JSON string from a line item. - * @param ToolProvider\LineItem $lineItem Line item object //UK: changed from LTI\\LineItem|null - * @return string JSON representation of line item - */ - private static function toJson(ToolProvider\LineItem $lineItem): string - { - $json = new \stdClass(); - if (!empty($lineItem->endpoint)) { - $json->id = $lineItem->endpoint; - } - if (!empty($lineItem->label)) { - $json->label = $lineItem->label; - } - if (!empty($lineItem->pointsPossible)) { - $json->scoreMaximum = $lineItem->pointsPossible; - } - if (!empty($lineItem->ltiResourceLinkId)) { - $json->resourceLinkId = $lineItem->ltiResourceLinkId; - } - if (!empty($lineItem->resourceId)) { - $json->resourceId = $lineItem->resourceId; - } - if (!empty($lineItem->tag)) { - $json->tag = $lineItem->tag; - } - if (!empty($lineItem->submitFrom)) { - $json->startDateTime = date('Y-m-d\TH:i:sP', $lineItem->submitFrom); - } - if (!empty($lineItem->submitUntil)) { - $json->endDateTime = date('Y-m-d\TH:i:sP', $lineItem->submitUntil); - } - - return json_encode($json); - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/Service/Membership.php b/components/ILIAS/LTI/src/ToolProvider/Service/Membership.php deleted file mode 100755 index a2f2a37a8ccb..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/Service/Membership.php +++ /dev/null @@ -1,395 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class Membership extends Service -{ - /** - * Media type for version 1 of Memberships service. - */ - public const MEDIA_TYPE_MEMBERSHIPS_V1 = 'application/vnd.ims.lis.v2.membershipcontainer+json'; - - /** - * Media type for Names and Role Provisioning service. - */ - public const MEDIA_TYPE_MEMBERSHIPS_NRPS = 'application/vnd.ims.lti-nrps.v2.membershipcontainer+json'; - - /** - * Access scope. - */ - public static string $SCOPE = 'https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly'; - - /** - * Default limit on size of container to be returned from requests. - */ - public static int $defaultLimit = 100; - - /** - * The object to which the memberships apply (ResourceLink or Context). - * - * @var Context|ResourceLink $source - */ - private $source = null; - - /** - * Limit on size of container to be returned from requests. - * - * A limit of null (or zero) will disable paging of requests - * - * @var int|null $limit - */ - private ?int $limit; - - /** - * Whether requests should be made one page at a time when a limit is set. - * - * When false, all objects will be requested, even if this requires several requests based on the limit set. - * - * @var boolean $pagingMode - */ - private bool $pagingMode; - - /** - * Class constructor. - * @param object $source The object to which the memberships apply (ResourceLink or Context) - * @param string $endpoint Service endpoint - * @param string $format Format to request - * @param int|null $limit Limit of line items to be returned in each request, null for all - * @param boolean $pagingMode True if only a single page should be requested when a limit is set - */ - public function __construct($source, string $endpoint, string $format = self::MEDIA_TYPE_MEMBERSHIPS_V1, int $limit = null, bool $pagingMode = false) - { - $platform = $source->getPlatform(); - parent::__construct($platform, $endpoint); - $this->scope = self::$SCOPE; - $this->mediaType = $format; - $this->source = $source; - $this->limit = $limit; - $this->pagingMode = $pagingMode; - } - - /** - * Get the memberships. - * @param string|null $role Role for which memberships are to be requested (optional, default is all roles) - * @param int|null $limit Limit on the number of memberships to be returned in each request, null for service default (optional) - * @return mixed The array of UserResult objects if successful, otherwise false - */ - public function get(string $role = null, int $limit = null) - { - return $this->getMembers(false, $role, $limit); - } - - /** - * Get the memberships. - * @param string|null $role Role for which memberships are to be requested (optional, default is all roles) - * @param int|null $limit Limit on the number of memberships to be returned in each request, null for service default (optional) - * @return mixed The array of UserResult objects if successful, otherwise false - */ - public function getWithGroups(string $role = null, int $limit = null) - { - return $this->getMembers(true, $role, $limit); - } - - /** - * Get the memberships. - * @param bool $withGroups True is group information is to be requested as well - * @param string|null $role Role for which memberships are to be requested (optional, default is all roles) - * @param int|null $limit Limit on the number of memberships to be returned in each request, null for service default (optional) - * @return mixed The array of UserResult objects if successful, otherwise false - */ - private function getMembers(bool $withGroups, string $role = null, int $limit = null) - { - //added - $oldUsers = []; - - $isLink = is_a($this->source, 'ceLTIc\LTI\ResourceLink'); - $parameters = array(); - if (!empty($role)) { - $parameters['role'] = $role; - } - if (is_null($limit)) { - $limit = $this->limit; - } - if (is_null($limit)) { - $limit = self::$defaultLimit; - } - if (!empty($limit)) { - $parameters['limit'] = strval($limit); - } - if ($isLink) { - $context = $this->source->getContext(); - if (!empty($this->source->getId())) { - $parameters['rlid'] = $this->source->getId(); - } - if ($withGroups && ($this->mediaType === self::MEDIA_TYPE_MEMBERSHIPS_NRPS) && !empty($context)) { - $context->getGroups(); - $this->source->groupSets = $context->groupSets; - $this->source->groups = $context->groups; - $parameters['groups'] = 'true'; - } - } elseif ($withGroups && ($this->mediaType === self::MEDIA_TYPE_MEMBERSHIPS_NRPS)) { - $this->source->getGroups(); - $parameters['groups'] = 'true'; - } - $userResults = array(); - $memberships = array(); - $endpoint = $this->endpoint; - do { - $http = $this->send('GET', $parameters); - $url = ''; - if (!empty($http) && $http->ok) { - $isjsonld = false; - if (isset($http->responseJson->pageOf) && isset($http->responseJson->pageOf->membershipSubject) && - isset($http->responseJson->pageOf->membershipSubject->membership)) { - $isjsonld = true; - $memberships = array_merge($memberships, $http->responseJson->pageOf->membershipSubject->membership); - if (!empty($http->responseJson->nextPage)) { - $http->relativeLinks['next'] = $http->responseJson->nextPage; - } - } elseif (isset($http->responseJson->members)) { - $memberships = array_merge($memberships, $http->responseJson->members); - } - if (!$this->pagingMode && $http->hasRelativeLink('next')) { - $url = $http->getRelativeLink('next'); - $this->endpoint = $url; - $parameters = array(); - } - } else { - $userResults = false; - } - } while ($url); - $this->endpoint = $endpoint; - if ($userResults !== false) { - if ($isLink) { -// $oldUsers = $this->source->getUserResultSourcedIDs(true, LTI\Tool::ID_SCOPE_RESOURCE); - $oldUsers = $this->source->getUserResultSourcedIDs(true, \ILIAS\LTI\ToolProvider\Tool::ID_SCOPE_RESOURCE); - } - foreach ($memberships as $membership) { - if ($isjsonld) { - $member = $membership->member; - if ($isLink) { -// $userresult = LTI\UserResult::fromResourceLink($this->source, $member->userId); - $userresult = \ILIAS\LTI\ToolProvider\UserResult::fromResourceLink($this->source, $member->userId); - } else { -// $userresult = new LTI\UserResult(); - $userresult = new \ILIAS\LTI\ToolProvider\UserResult(); - $userresult->ltiUserId = $member->userId; - } - - // Set the user name - $firstname = (isset($member->givenName)) ? $member->givenName : ''; - $lastname = (isset($member->familyName)) ? $member->familyName : ''; - $fullname = (isset($member->name)) ? $member->name : ''; - $userresult->setNames($firstname, $lastname, $fullname); - - // Set the sourcedId - if (isset($member->sourcedId)) { - $userresult->sourcedId = $member->sourcedId; - } - - // Set the username - if (isset($member->ext_username)) { - $userresult->username = $member->ext_username; - } elseif (isset($member->ext_user_username)) { - $userresult->username = $member->ext_user_username; - } elseif (isset($member->custom_username)) { - $userresult->username = $member->custom_username; - } elseif (isset($member->custom_user_username)) { - $userresult->username = $member->custom_user_username; - } - - // Set the user email - $email = (isset($member->email)) ? $member->email : ''; - $userresult->setEmail($email, $this->source->getPlatform()->defaultEmail); - - // Set the user roles - if (isset($membership->role)) { - $roles = $this->parseContextsInArray($http->responseJson->{'@context'}, $membership->role); -// $userresult->roles = LTI\Tool::parseRoles($roles, LTI\Util::LTI_VERSION2); - $userresult->roles = \ILIAS\LTI\ToolProvider\Tool::parseRoles($roles, \ILIAS\LTI\ToolProvider\Util::LTI_VERSION2); - } - - // If a result sourcedid is provided save the user - if ($isLink) { - $doSave = false; - if (isset($membership->message)) { - foreach ($membership->message as $message) { - if (isset($message->message_type) && (($message->message_type === 'basic-lti-launch-request') || ($message->message_type) === 'LtiResourceLinkRequest')) { - if (isset($message->lis_result_sourcedid)) { - $userresult->ltiResultSourcedId = $message->lis_result_sourcedid; - $doSave = true; - } - if (isset($message->ext)) { - if (empty($userresult->username)) { - if (!empty($message->ext->username)) { - $userresult->username = $message->ext->username; - } elseif (!empty($message->ext->user_username)) { - $userresult->username = $message->ext->user_username; - } - } - } - if (isset($message->custom)) { - if (empty($userresult->username)) { - if (!empty($message->custom->username)) { - $userresult->username = $message->custom->username; - } elseif (!empty($message->custom->user_username)) { - $userresult->username = $message->custom->user_username; - } - } - } - break; - } - } - } - if (!$doSave && isset($member->resultSourcedId)) { - $userresult->setResourceLinkId($this->source->getId()); - $userresult->ltiResultSourcedId = $member->resultSourcedId; - $doSave = true; - } - if ($doSave) { - $userresult->save(); - } - } - $userResults[] = $userresult; - - // Remove old user (if it exists) - if ($isLink) { -// unset($oldUsers[$userresult->getId(LTI\Tool::ID_SCOPE_RESOURCE)]); - unset($oldUsers[$userresult->getId(\ILIAS\LTI\ToolProvider\Tool::ID_SCOPE_RESOURCE)]); - } - } else { // Version 2 - $member = $membership; - if ($isLink) { -// $userresult = LTI\UserResult::fromResourceLink($this->source, $member->user_id); - $userresult = \ILIAS\LTI\ToolProvider\UserResult::fromResourceLink($this->source, $member->user_id); - } else { -// $userresult = new LTI\UserResult(); - $userresult = new \ILIAS\LTI\ToolProvider\UserResult(); - $userresult->ltiUserId = $member->user_id; - } - - // Set the user name - $firstname = (isset($member->given_name)) ? $member->given_name : ''; - $lastname = (isset($member->family_name)) ? $member->family_name : ''; - $fullname = (isset($member->name)) ? $member->name : ''; - $userresult->setNames($firstname, $lastname, $fullname); - - // Set the sourcedId - if (isset($member->lis_person_sourcedid)) { - $userresult->sourcedId = $member->lis_person_sourcedid; - } - - // Set the user email - $email = (isset($member->email)) ? $member->email : ''; - $userresult->setEmail($email, $this->source->getPlatform()->defaultEmail); - - // Set the user roles - if (isset($member->roles)) { -// $userresult->roles = LTI\Tool::parseRoles($member->roles, LTI\Util::LTI_VERSION2); - $userresult->roles = \ILIAS\LTI\ToolProvider\Tool::parseRoles($member->roles, \ILIAS\LTI\ToolProvider\Util::LTI_VERSION2); - } - - // If a result sourcedid is provided save the user - if ($isLink) { - $doSave = false; - if (isset($member->message)) { - foreach ($member->message as $message) { - if (isset($message->{'https://purl.imsglobal.org/spec/lti/claim/message_type'}) && (($message->{'https://purl.imsglobal.org/spec/lti/claim/message_type'} === 'basic-lti-launch-request') || ($message->{'https://purl.imsglobal.org/spec/lti/claim/message_type'}) === 'LtiResourceLinkRequest')) { - if (isset($message->{'https://purl.imsglobal.org/spec/lti-bo/claim/basicoutcome'}) && - isset($message->{'https://purl.imsglobal.org/spec/lti-bo/claim/basicoutcome'}->lis_result_sourcedid)) { - $userresult->ltiResultSourcedId = $message->{'https://purl.imsglobal.org/spec/lti-bo/claim/basicoutcome'}->lis_result_sourcedid; - $doSave = true; - } - if (isset($message->{'https://purl.imsglobal.org/spec/lti/claim/ext'})) { - if (empty($userresult->username)) { - if (!empty($message->{'https://purl.imsglobal.org/spec/lti/claim/ext'}->username)) { - $userresult->username = $message->{'https://purl.imsglobal.org/spec/lti/claim/ext'}->username; - } elseif (!empty($message->{'https://purl.imsglobal.org/spec/lti/claim/ext'}->user_username)) { - $userresult->username = $message->{'https://purl.imsglobal.org/spec/lti/claim/ext'}->user_username; - } - } - } - if (isset($message->{'https://purl.imsglobal.org/spec/lti/claim/custom'})) { - if (empty($userresult->username)) { - if (!empty($message->{'https://purl.imsglobal.org/spec/lti/claim/custom'}->username)) { - $userresult->username = $message->{'https://purl.imsglobal.org/spec/lti/claim/custom'}->username; - } elseif (!empty($message->{'https://purl.imsglobal.org/spec/lti/claim/custom'}->user_username)) { - $userresult->username = $message->{'https://purl.imsglobal.org/spec/lti/claim/custom'}->user_username; - } - } - } - break; - } - } - } - if ($doSave) { - $userresult->save(); - } - } - $userResults[] = $userresult; - if (isset($member->group_enrollments)) { - $userresult->groups = array(); - foreach ($member->group_enrollments as $group) { - $groupId = $group->group_id; - if (!array_key_exists($groupId, $this->source->groups)) { - $this->source->groups[$groupId] = array('title' => "Group {$groupId}"); - } - if (!empty($this->source->groups[$groupId]['set'])) { - $this->source->groupSets[$this->source->groups[$groupId]['set']]['num_members']++; - if ($userresult->isStaff()) { - $this->source->groupSets[$this->source->groups[$groupId]['set']]['num_staff']++; - } - if ($userresult->isLearner()) { - $this->source->groupSets[$this->source->groups[$groupId]['set']]['num_learners']++; - } - } - $userresult->groups[] = $groupId; - } - } - - // Remove old user (if it exists) - if ($isLink) { -// unset($oldUsers[$userresult->getId(LTI\Tool::ID_SCOPE_RESOURCE)]); - unset($oldUsers[$userresult->getId(\ILIAS\LTI\ToolProvider\Tool::ID_SCOPE_RESOURCE)]); - } - } - } - - /// Delete any old users which were not in the latest list from the platform if request is not paged - if ($isLink && !$this->pagingMode) { - foreach ($oldUsers as $id => $userresult) { - $userresult->delete(); - } - } - } - - return $userResults; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/Service/Result.php b/components/ILIAS/LTI/src/ToolProvider/Service/Result.php deleted file mode 100755 index c4e490f4ffcb..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/Service/Result.php +++ /dev/null @@ -1,160 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class Result extends AssignmentGrade -{ - /** - * Access scope. - */ - public static string $SCOPE = 'https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly'; - - /** - * Default limit on size of container to be returned from requests. - */ - public static int $defaultLimit = 500; - - /** - * Limit on size of container to be returned from requests. - * - * A limit of null (or zero) will disable paging of requests - * - * @var int|null $limit - */ - private ?int $limit; - - /** - * Whether requests should be made one page at a time when a limit is set. - * - * When false, all objects will be requested, even if this requires several requests based on the limit set. - * - * @var boolean $pagingMode - */ - private bool $pagingMode; - - /** - * Class constructor. - * @param \ILIAS\LTI\ToolProvider\Platform $platform Platform object for this service request - * @param string $endpoint Service endpoint - * @param int|null $limit Limit of results to be returned in each request, null for all - * @param boolean $pagingMode True if only a single page should be requested when a limit is set - */ - public function __construct(\ILIAS\LTI\ToolProvider\Platform $platform, string $endpoint, int $limit = null, bool $pagingMode = false) - { - parent::__construct($platform, $endpoint, '/results'); - $this->limit = $limit; - $this->pagingMode = $pagingMode; - $this->scope = self::$SCOPE; - $this->mediaType = 'application/vnd.ims.lis.v2.resultcontainer+json'; - } - - /** - * Retrieve all outcomes for a line item. - * @param int|null $limit Limit of results to be returned in each request, null for service default - * @return Outcome[]|bool Array of Outcome objects or false on error - */ - public function getAll(int $limit = null) - { - $params = array(); - if (is_null($limit)) { - $limit = $this->limit; - } - if (is_null($limit)) { - $limit = self::$defaultLimit; - } - if (!empty($limit)) { - $params['limit'] = $limit; - } - $outcomes = array(); - $endpoint = $this->endpoint; - do { - $http = $this->send('GET', $params); - $url = ''; - if ($http->ok) { - if (!empty($http->responseJson)) { - foreach ($http->responseJson as $outcome) { - $outcomes[] = self::getOutcome($outcome); - } - } - if (!$this->pagingMode && $http->hasRelativeLink('next')) { - $url = $http->getRelativeLink('next'); - $this->endpoint = $url; - $params = array(); - } - } else { - $outcomes = false; - } - } while ($url); - $this->endpoint = $endpoint; - - return $outcomes; - } - - /** - * Retrieve an outcome for a user. - * @param User $user User object - * @return Outcome|null|bool Outcome object, or null if none, or false on error - */ - public function get(User $user) - { - $params = array('user_id' => $user->ltiUserId); - $http = $this->send('GET', $params); - if ($http->ok) { - if (!empty($http->responseJson)) { - $outcome = self::getOutcome(reset($http->responseJson)); - } else { - $outcome = null; - } - return $outcome; - } else { - return false; - } - } - - ### - ### PRIVATE METHOD - ### - - private static function getOutcome(object $json): Outcome - { - $outcome = new Outcome(); - $outcome->ltiUserId = $json->userId; - if (isset($json->resultScore)) { - $outcome->setValue($json->resultScore); - } - if (isset($json->resultMaximum)) { - $outcome->setPointsPossible($json->resultMaximum); - } - if (isset($json->comment)) { - $outcome->comment = $json->comment; - } - - return $outcome; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/Service/Score.php b/components/ILIAS/LTI/src/ToolProvider/Service/Score.php deleted file mode 100755 index c8e78b6f1869..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/Service/Score.php +++ /dev/null @@ -1,81 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class Score extends AssignmentGrade -{ - /** - * Access scope. - */ - public static string $SCOPE = 'https://purl.imsglobal.org/spec/lti-ags/scope/score'; - - /** - * Class constructor. - * @param \ILIAS\LTI\ToolProvider\Platform $platform Platform object for this service request - * @param string $endpoint Service endpoint - */ - public function __construct(\ILIAS\LTI\ToolProvider\Platform $platform, string $endpoint) - { - parent::__construct($platform, $endpoint, '/scores'); - $this->scope = self::$SCOPE; - $this->mediaType = 'application/vnd.ims.lis.v1.score+json'; - } - - /** - * Submit an outcome for a user. - * @param ToolProvider\Outcome $ltiOutcome Outcome object //UK: Changed from LTI\Outcome - * @param ToolProvider\User $user User object - * @return bool True if successful, otherwise false - */ - public function submit(ToolProvider\Outcome $ltiOutcome, ToolProvider\User $user): bool - { - $score = $ltiOutcome->getValue(); - if (!is_null($score)) { - $json = array( - 'scoreGiven' => $score, - 'scoreMaximum' => $ltiOutcome->getPointsPossible(), - 'comment' => $ltiOutcome->comment, - 'activityProgress' => $ltiOutcome->activityProgress, - 'gradingProgress' => $ltiOutcome->gradingProgress - ); - } else { - $json = array( - 'activityProgress' => 'Initialized', - 'gradingProgress' => 'NotReady' - ); - } - $json['userId'] = $user->ltiUserId; - $date = new \DateTime(); - $json['timestamp'] = date_format($date, 'Y-m-d\TH:i:s.uP'); - $data = json_encode($json); - // UK changed from $http = $this->send('POST', null, $data); - $http = $this->send('POST', [], $data); - - return $http->ok; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/Service/Service.php b/components/ILIAS/LTI/src/ToolProvider/Service/Service.php deleted file mode 100755 index 7e1a1ebbb2b3..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/Service/Service.php +++ /dev/null @@ -1,245 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class Service -{ - /** - * Whether service request should be sent unsigned. - * - * @var bool $unsigned - */ - public bool $unsigned = false; - - /** - * Service endpoint. - * - * @var string $endpoint - */ - protected ?string $endpoint = null; - - /** - * Service access scope. - * - * @var string $scope - */ - protected ?string $scope = null; - - /** - * Media type of message body. - * - * @var string $mediaType - */ - protected ?string $mediaType = null; - - /** - * Platform for this service request. - * - * @var Platform $platform - */ - private ?Platform $platform = null; - - /** - * HttpMessage object for last service request. - * - * @var HttpMessage|null $http - */ - private ?HTTPMessage $http = null; - - /** - * Class constructor. - * @param Platform $platform Platform object for this service request - * @param string $endpoint Service endpoint - */ - public function __construct(Platform $platform, string $endpoint) - { - $this->platform = $platform; - $this->endpoint = $endpoint; - } - -// /** -// * Get tool consumer. -// * -// * @deprecated Use getPlatform() instead -// * @see Service::getPlatform() -// * -// * @return ToolConsumer Consumer for this service -// */ -// public function getConsumer() -// { -// Util::logDebug('Method ceLTIc\LTI\Service::getConsumer() has been deprecated; please use ceLTIc\LTI\Service::getPlatform() instead.', -// true); -// return $this->getPlatform(); -// } - - /** - * Get platform. - * - * @return Platform Platform for this service - */ - public function getPlatform(): ?Platform - { - return $this->platform; - } - - /** - * Get access scope. - * - * @return string Access scope - */ - public function getScope(): ?string - { - return $this->scope; - } - - /** - * Send a service request. - * @param string $method The action type constant (optional, default is GET) - * @param array $parameters Query parameters to add to endpoint (optional, default is none) - * @param string|null $body Body of request (optional, default is null) - * @return HttpMessage HTTP object containing request and response details - */ - public function send(string $method, array $parameters = array(), string $body = null): ?HTTPMessage - { - $url = $this->endpoint; - if (!empty($parameters)) { - if (strpos($url, '?') === false) { - $sep = '?'; - } else { - $sep = '&'; - } - foreach ($parameters as $name => $value) { - $url .= $sep . urlencode($name) . '=' . urlencode($value); - $sep = '&'; - } - } - $header = null; - $retry = !$this->platform->useOAuth1(); - $newToken = false; - $retried = false; - do { - if (!$this->unsigned) { - $accessToken = $this->platform->getAccessToken(); - if (!$this->platform->useOAuth1()) { - if (empty($accessToken)) { - $accessToken = new AccessToken($this->platform); - $this->platform->setAccessToken($accessToken); - } - if (!$accessToken->hasScope($this->scope)) { - $accessToken->get($this->scope); - $newToken = true; - if (!$accessToken->hasScope($this->scope)) { // Try obtaining a token for just this scope - $accessToken->expires = time(); - $accessToken->get($this->scope, true); - $retried = true; - if (!$accessToken->hasScope($this->scope)) { - if (empty($this->http)) { - $this->http = new HttpMessage($url); - $this->http->error = "Unable to obtain an access token for scope: {$this->scope}"; - } - break; - } - } - } - } - $header = $this->platform->signServiceRequest($url, $method, $this->mediaType, $body); - } - // Connect to platform and parse JSON response - $this->http = new HttpMessage($url, $method, $body, $header); - if ($this->http->send() && !empty($this->http->response)) { - $this->http->responseJson = json_decode($this->http->response); - $this->http->ok = !is_null($this->http->responseJson); - } - $retry = $retry && !$retried && !$this->http->ok; - if ($retry) { - if (!$newToken) { // Invalidate existing token to force a new one to be obtained - $accessToken->expires = time(); - $newToken = true; - } elseif (count($accessToken->scopes) !== 1) { // Try obtaining a token for just this scope - $accessToken->expires = time(); - $accessToken->get($this->scope, true); - $retried = true; - } else { - $retry = false; - } - } - } while ($retry); - - return $this->http; - } - - /** - * Get HttpMessage object for last request. - * - * @return HttpMessage HTTP object containing request and response details - */ - public function getHttpMessage(): ?HTTPMessage - { - return $this->http; - } - - ### - ### PROTECTED METHODS - ### - - /** - * Parse the JSON for context references. - * @param object $contexts JSON contexts - * @param array $arr Array to be parsed - * @return array Parsed array - */ - protected function parseContextsInArray(object $contexts, array $arr): array - { - if (is_array($contexts)) { - $contextdefs = array(); - foreach ($contexts as $context) { - if (is_object($context)) { - $contextdefs = array_merge(get_object_vars($context), $contexts); - } - } - $parsed = array(); - foreach ($arr as $key => $value) { - $parts = explode(':', $value, 2); - if (count($parts) > 1) { - if (array_key_exists($parts[0], $contextdefs)) { - $parsed[$key] = $contextdefs[$parts[0]] . $parts[1]; - break; - } - } - $parsed[$key] = $value; - } - } else { - $parsed = $arr; - } - - return $parsed; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/Service/ToolSettings.php b/components/ILIAS/LTI/src/ToolProvider/Service/ToolSettings.php deleted file mode 100755 index a154cd38f600..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/Service/ToolSettings.php +++ /dev/null @@ -1,164 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class ToolSettings extends Service -{ - /** - * Settings at current level mode. - */ - public const MODE_CURRENT_LEVEL = 1; - - /** - * Settings at all levels mode. - */ - public const MODE_ALL_LEVELS = 2; - - /** - * Settings with distinct names at all levels mode. - */ - public const MODE_DISTINCT_NAMES = 3; - - /** - * Access scope. - */ - public static string $SCOPE = 'https://purl.imsglobal.org/spec/lti-ts/scope/toolsetting'; - - /** - * Names of LTI parameters to be retained in the consumer settings property. - * - * @var array $LEVEL_NAMES - */ - private static array $LEVEL_NAMES = array('ToolProxy' => 'system', - 'ToolProxyBinding' => 'context', - 'LtiLink' => 'link'); - - /** - * The object to which the settings apply (ResourceLink, Context or Platform). - * - * @var Platform|Context|ResourceLink $source - */ - private $source; - - /** - * Whether to use the simple JSON format. - * - * @var bool $simple - */ - private bool $simple; - - /** - * Class constructor. - * @param Platform|Context|ResourceLink $source The object to which the settings apply (ResourceLink, Context or Platform) - * @param string $endpoint Service endpoint - * @param bool $simple True if the simple media type is to be used (optional, default is true) - */ - public function __construct($source, string $endpoint, bool $simple = true) - { - if (is_a($source, 'ceLTIc\LTI\Platform')) { - $platform = $source; - } else { - $platform = $source->getPlatform(); - } - parent::__construct($platform, $endpoint); - $this->scope = self::$SCOPE; - if ($simple) { - $this->mediaType = 'application/vnd.ims.lti.v2.toolsettings.simple+json'; - } else { - $this->mediaType = 'application/vnd.ims.lti.v2.toolsettings+json'; - } - $this->source = $source; - $this->simple = $simple; - } - - /** - * Get the tool settings. - * @param int $mode Mode for request (optional, default is current level only) - * @return mixed The array of settings if successful, otherwise false - */ - public function get(int $mode = self::MODE_CURRENT_LEVEL) - { - $parameter = array(); - if ($mode === self::MODE_ALL_LEVELS) { - $parameter['bubble'] = 'all'; - } elseif ($mode === self::MODE_DISTINCT_NAMES) { - $parameter['bubble'] = 'distinct'; - } - $http = $this->send('GET', $parameter); - if (!$http->ok) { - $response = false; - } elseif ($this->simple) { - $response = json_decode($http->response, true); - } elseif (isset($http->responseJson->{'@graph'})) { - $response = array(); - foreach ($http->responseJson->{'@graph'} as $level) { - $settings = json_decode(json_encode($level->custom), true); - unset($settings['@id']); - $response[self::$LEVEL_NAMES[$level->{'@type'}]] = $settings; - } - } - - return $response; - } - - /** - * Set the tool settings. - * @param array $settings An associative array of settings (optional, default is null) - * @return bool True if request was successful - */ - public function set(array $settings): bool - { - if (!$this->simple) { - if (is_a($this->source, 'Platform')) { - $type = 'ToolProxy'; - } elseif (is_a($this->source, 'Context')) { - $type = 'ToolProxyBinding'; - } else { - $type = 'LtiLink'; - } - $obj = new \stdClass(); - $obj->{'@context'} = 'http://purl.imsglobal.org/ctx/lti/v2/ToolSettings'; - $obj->{'@graph'} = array(); - $level = new \stdClass(); - $level->{'@type'} = $type; - $level->{'@id'} = $this->endpoint; - $level->{'custom'} = $settings; - $obj->{'@graph'}[] = $level; - $body = json_encode($obj); - } else { - $body = json_encode($settings); - } - - //UK changed from $response = parent::send('PUT', null, $body); - $response = parent::send('PUT', [], $body); - - return $response->ok; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/System.php b/components/ILIAS/LTI/src/ToolProvider/System.php deleted file mode 100755 index 0c9cad0090be..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/System.php +++ /dev/null @@ -1,1439 +0,0 @@ -id; - } - - /** - * Sets the system record ID. - * @param int $id System record ID value - */ - public function setRecordId(int $id) - { - $this->id = $id; - } - - /** - * Get the consumer key. - * - * @return string|null Consumer key value //UK: added |null - */ - public function getKey(): ?string - { - return $this->key; - } - - /** - * Set the consumer key. - * @param string $key Consumer key value - */ - public function setKey(string $key) - { - $this->key = $key; - } - - /** - * Get a setting value. - * @param string $name Name of setting - * @param string $default Value to return if the setting does not exist (optional, default is an empty string) - * @return string Setting value - */ - public function getSetting(string $name, string $default = ''): string - { - if (array_key_exists($name, $this->settings)) { - $value = $this->settings[$name]; - } else { - $value = $default; - } - - return $value; - } - - /** - * Set a setting value. - * @param string $name Name of setting - * @param string|null $value Value to set, use an empty value to delete a setting (optional, default is null) - */ - public function setSetting(string $name, string $value = null) - { - $old_value = $this->getSetting($name); - if ($value !== $old_value) { - if (!empty($value)) { - $this->settings[$name] = $value; - } else { - unset($this->settings[$name]); - } - $this->settingsChanged = true; - } - } - - /** - * Get an array of all setting values. - * - * @return array Associative array of setting values - */ - public function getSettings(): array - { - return $this->settings; - } - - /** - * Set an array of all setting values. - * @param array $settings Associative array of setting values - */ - public function setSettings(array $settings) - { - $this->settings = $settings; - } - - /** - * Save setting values. - * - * @return bool True if the settings were successfully saved - */ - public function saveSettings(): bool - { - if ($this->settingsChanged) { - $ok = $this->save(); - } else { - $ok = true; - } - - return $ok; - } - - /** - * Check whether a JWT exists - * - * @return bool True if a JWT exists - */ - public function hasJwt(): bool - { - return !empty($this->jwt) && $this->jwt->hasJwt(); - } - - /** - * Get the JWT - * - * @return ClientInterface The JWT - */ - public function getJwt(): ClientInterface - { - return $this->jwt; - } - - /** - * Get the raw POST parameters - * - * @return array The POST parameter array - */ - public function getRawParameters(): array - { - if (is_null($this->rawParameters)) { - $this->rawParameters = LTIOAuth\OAuthUtil::parse_parameters(file_get_contents(LTIOAuth\OAuthRequest::$POST_INPUT)); -// $this->rawParameters = OAuth\OAuthUtil::parse_parameters(file_get_contents(OAuth\OAuthRequest::$POST_INPUT)); - } - - return $this->rawParameters; - } - - /** - * Get the message claims - * @param bool $fullyQualified True if claims should be fully qualified rather than grouped (default is false) - * @return array The message claim array - */ - public function getMessageClaims(bool $fullyQualified = false): array - { - $messageClaims = null; - if (!is_null($this->messageParameters)) { - $messageParameters = $this->messageParameters; - $messageType = ''; - if (!empty($messageParameters['lti_message_type'])) { - if (array_key_exists($messageParameters['lti_message_type'], Util::MESSAGE_TYPE_MAPPING)) { - $messageParameters['lti_message_type'] = Util::MESSAGE_TYPE_MAPPING[$messageParameters['lti_message_type']]; - } - $messageType = $messageParameters['lti_message_type']; - } - if (!empty($messageParameters['accept_media_types'])) { - $mediaTypes = array_filter(explode(',', str_replace(' ', '', $messageParameters['accept_media_types'])), 'strlen'); - $types = array(); - if (!empty($messageParameters['accept_types'])) { - $types = array_filter(explode(',', str_replace(' ', '', $messageParameters['accept_types'])), 'strlen'); - foreach ($mediaTypes as $mediaType) { - if (strpos($mediaType, 'application/vnd.ims.lti.') === 0) { - unset($mediaTypes[array_search($mediaType, $mediaTypes)]); - } - } - $messageParameters['accept_media_types'] = implode(',', $mediaTypes); - } else { - foreach ($mediaTypes as $mediaType) { - if ($mediaType === Item::LTI_LINK_MEDIA_TYPE) { - unset($mediaTypes[array_search(Item::LTI_LINK_MEDIA_TYPE, $mediaTypes)]); - $messageParameters['accept_media_types'] = implode(',', $mediaTypes); - $types[] = Item::TYPE_LTI_LINK; - } elseif ($mediaType === Item::LTI_ASSIGNMENT_MEDIA_TYPE) { - unset($mediaTypes[array_search(Item::LTI_ASSIGNMENT_MEDIA_TYPE, $mediaTypes)]); - $messageParameters['accept_media_types'] = implode(',', $mediaTypes); - $types[] = Item::TYPE_LTI_ASSIGNMENT; - } elseif (substr($mediaType, 0, 6) === 'image/') { - $types[] = 'image'; - $types[] = 'link'; - $types[] = 'file'; - } elseif ($mediaType === 'text/html') { - $types[] = 'html'; - $types[] = 'link'; - $types[] = 'file'; - } elseif ($mediaType === '*/*') { - $types[] = 'html'; - $types[] = 'image'; - $types[] = 'file'; - $types[] = 'link'; - } else { - $types[] = 'file'; - } - } - $types = array_unique($types); - $messageParameters['accept_types'] = implode(',', $types); - } - } - if (!empty($messageParameters['accept_presentation_document_targets'])) { - $documentTargets = array_filter(explode( - ',', - str_replace(' ', '', $messageParameters['accept_presentation_document_targets']) - ), 'strlen'); - $targets = array(); - foreach ($documentTargets as $documentTarget) { - switch ($documentTarget) { - case 'frame': - case 'popup': - case 'overlay': - case 'none': - break; - default: - $targets[] = $documentTarget; - break; - } - } - $targets = array_unique($targets); - $messageParameters['accept_presentation_document_targets'] = implode(',', $targets); - } - $messageClaims = array(); - if (!empty($messageParameters['oauth_consumer_key'])) { - $messageClaims['aud'] = array($messageParameters['oauth_consumer_key']); - } - foreach ($messageParameters as $key => $value) { - $ok = true; - if (array_key_exists($key, Util::JWT_CLAIM_MAPPING)) { - if (array_key_exists("{$key}.{$messageType}", Util::JWT_CLAIM_MAPPING)) { - $mapping = Util::JWT_CLAIM_MAPPING["{$key}.{$messageType}"]; - } else { - $mapping = Util::JWT_CLAIM_MAPPING[$key]; - } - if (isset($mapping['isObject']) && $mapping['isObject']) { - $value = json_decode($value); - } elseif (isset($mapping['isArray']) && $mapping['isArray']) { - $value = array_filter(explode(',', str_replace(' ', '', $value)), 'strlen'); - sort($value); - } elseif (isset($mapping['isBoolean']) && $mapping['isBoolean']) { - $value = (is_bool($value)) ? $value : $value === 'true'; - } elseif (isset($mapping['isInteger']) && $mapping['isInteger']) { - $value = intval($value); - } elseif (is_bool($value)) { - $value = ($value) ? 'true' : 'false'; - } else { - $value = strval($value); - } - $group = ''; - $claim = Util::JWT_CLAIM_PREFIX; - if (!empty($mapping['suffix'])) { - $claim .= "-{$mapping['suffix']}"; - } - $claim .= '/claim/'; - if (is_null($mapping['group'])) { - $claim = $mapping['claim']; - } elseif (empty($mapping['group'])) { - $claim .= $mapping['claim']; - } else { - $group = $claim . $mapping['group']; - $claim = $mapping['claim']; - } - } elseif (substr($key, 0, 7) === 'custom_') { - $group = Util::JWT_CLAIM_PREFIX . '/claim/custom'; - $claim = substr($key, 7); - } elseif (substr($key, 0, 4) === 'ext_') { - $group = Util::JWT_CLAIM_PREFIX . '/claim/ext'; - $claim = substr($key, 4); - } elseif (substr($key, 0, 7) === 'lti1p1_') { - $group = Util::JWT_CLAIM_PREFIX . '/claim/lti1p1'; - $claim = substr($key, 7); - if (empty($value)) { - $value = null; - } else { - $json = json_decode($value); - if (!is_null($json)) { - $value = $json; - } - } - } else { - $ok = false; - } - if ($ok) { - if ($fullyQualified) { - if (empty($group)) { - $messageClaims = array_merge($messageClaims, self::fullyQualifyClaim($claim, $value)); - } else { - $messageClaims = array_merge($messageClaims, self::fullyQualifyClaim("{$group}/{$claim}", $value)); - } - } elseif (empty($group)) { - $messageClaims[$claim] = $value; - } else { - $messageClaims[$group][$claim] = $value; - } - } - } - if (!empty($messageParameters['unmapped_claims'])) { - $claims = json_decode($messageParameters['unmapped_claims']); - foreach ($claims as $claim => $value) { - if ($fullyQualified) { - $messageClaims = array_merge($messageClaims, self::fullyQualifyClaim($claim, $value)); - } elseif (!is_object($value)) { - $messageClaims[$claim] = $value; - } elseif (!isset($messageClaims[$claim])) { - $messageClaims[$claim] = $value; - } else { - $objVars = get_object_vars($value); - foreach ($objVars as $attrName => $attrValue) { - if (is_object($messageClaims[$claim])) { - $messageClaims[$claim]->{$attrName} = $attrValue; - } else { - $messageClaims[$claim][$attrName] = $attrValue; - } - } - } - } - } - } - - return $messageClaims; - } - - /** - * Get an array of fully qualified user roles - * @param mixed $roles Comma-separated list of roles or array of roles - * @param string $ltiVersion LTI version (default is LTI-1p0) - * @return array Array of roles - */ - public static function parseRoles($roles, string $ltiVersion = Util::LTI_VERSION1): array - { - if (!is_array($roles)) { - $roles = array_filter(explode(',', str_replace(' ', '', $roles)), 'strlen'); - } - $parsedRoles = array(); - foreach ($roles as $role) { - $role = trim($role); - if (!empty($role)) { - if ($ltiVersion === Util::LTI_VERSION1) { - if ((substr($role, 0, 4) !== 'urn:') && - (substr($role, 0, 7) !== 'http://') && (substr($role, 0, 8) !== 'https://')) { - $role = 'urn:lti:role:ims/lis/' . $role; - } - } elseif ((substr($role, 0, 7) !== 'http://') && (substr($role, 0, 8) !== 'https://')) { - $role = 'http://purl.imsglobal.org/vocab/lis/v2/membership#' . $role; - } - $parsedRoles[] = $role; - } - } - - return $parsedRoles; - } - - /** - * Add the signature to an LTI message. - * @param string $url URL for message request - * @param string $type LTI message type - * @param string $version LTI version - * @param array $params Message parameters - * @return array|string Array of signed message parameters or request headers - */ - public function signParameters(string $url, string $type, string $version, array $params) - { - if (!empty($url)) { - // Add standard parameters - $params['lti_version'] = $version; - $params['lti_message_type'] = $type; - // Add signature - $params = $this->addSignature($url, $params, 'POST', 'application/x-www-form-urlencoded'); - } - - return $params; - } - - /** - * Add the signature to an LTI message. - * If the message is being sent from a platform using LTI 1.3, then the parameters and URL will be saved and replaced with an - * initiate login request. - * @param string $url URL for message request - * @param string $type LTI message type - * @param string $version LTI version - * @param array $params Message parameters - * @param string|null $loginHint ID of user (optional) - * @param string|null $ltiMessageHint LTI message hint (optional, use null for none) - * @return array|string Array of signed message parameters or request headers - */ - public function signMessage(string &$url, string $type, string $version, array $params, string $loginHint = null, string $ltiMessageHint = null) - { - if (($this instanceof Platform) && ($this->ltiVersion === Util::LTI_VERSION1P3)) { - if (!isset($loginHint) || (strlen($loginHint) <= 0)) { - if (isset($params['user_id']) && (strlen($params['user_id']) > 0)) { - $loginHint = $params['user_id']; - } else { - $loginHint = 'Anonymous'; - } - } - // Add standard parameters - $params['lti_version'] = $version; - $params['lti_message_type'] = $type; - $this->onInitiateLogin($url, $loginHint, $ltiMessageHint, $params); - - $params = array( - 'iss' => $this->platformId, - 'target_link_uri' => $url, - 'login_hint' => $loginHint - ); - if (!is_null($ltiMessageHint)) { - $params['lti_message_hint'] = $ltiMessageHint; - } - if (!empty($this->clientId)) { - $params['client_id'] = $this->clientId; - } - if (!empty($this->deploymentId)) { - $params['lti_deployment_id'] = $this->deploymentId; - } - if (!empty(Tool::$defaultTool)) { - $url = Tool::$defaultTool->initiateLoginUrl; - } - } else { - $params = $this->signParameters($url, $type, $version, $params); - } - - return $params; - } - - /** - * Generate a web page containing an auto-submitted form of LTI message parameters. - * @param string $url URL to which the form should be submitted - * @param string $type LTI message type - * @param array $messageParams Array of form parameters - * @param string $target Name of target (optional) - * @param string|null $userId ID of user (optional) - * @param string $hint LTI message hint (optional, use null for none) - * @return string - */ - public function sendMessage(string $url, string $type, array $messageParams, string $target = '', ?string $userId = null, string $hint = ''): string - { - $sendParams = $this->signMessage($url, $type, $this->ltiVersion, $messageParams, $userId, $hint); - $html = Util::sendForm($url, $sendParams, $target); - - return $html; - } - - /** - * Generates the headers for an LTI service request. - * @param string $url URL for message request - * @param string $method HTTP method - * @param string $type Media type - * @param string|array|null $data Data being passed in request body (optional) //UK: added array - * @return string Headers to include with service request - */ - public function signServiceRequest(string $url, string $method, string $type, $data = null): string - { - $header = ''; - if (!empty($url)) { - $header = $this->addSignature($url, $data, $method, $type); - } - - return $header; - } - - /** - * Perform a service request - * @param object $service Service object to be executed - * @param string $method HTTP action - * @param string $format Media type - * @param mixed $data Array of parameters or body string - * @return HttpMessage HTTP object containing request and response details - */ - public function doServiceRequest(object $service, string $method, string $format, $data): HTTPMessage - { - $header = $this->addSignature($service->endpoint, $data, $method, $format); - - // Connect to platform - $http = new HttpMessage($service->endpoint, $method, $data, $header); - // Parse JSON response - if ($http->send() && !empty($http->response)) { - $http->responseJson = json_decode($http->response); - $http->ok = !is_null($http->responseJson); - } - - return $http; - } - - /** - * Determine whether this consumer is using the OAuth 1 security model. - * - * @return bool True if OAuth 1 security model should be used - */ - public function useOAuth1(): bool - { - return empty($this->signatureMethod) || (substr($this->signatureMethod, 0, 2) !== 'RS'); - } - - /** - * Add the signature to an array of message parameters or to a header string. - * @param string $endpoint URL to which message is being sent - * @param mixed $data Data to be passed - * @param string $method HTTP method - * @param string|null $type Content type of data being passed - * @param string|null $nonce Nonce value for JWT - * @param string|null $hash OAuth body hash value - * @param int|null $timestamp Timestamp - * @return mixed Array of signed message parameters or header string - */ - public function addSignature(string $endpoint, $data, string $method = 'POST', ?string $type = null, ?string $nonce = '', ?string $hash = null, ?int $timestamp = null) - { - if ($this->useOAuth1()) { - return $this->addOAuth1Signature($endpoint, $data, $method, $type, $hash, $timestamp); - } else { - return $this->addJWTSignature($endpoint, $data, $method, $type, $nonce, $timestamp); - } - } - - /** - * Verify the required properties of an LTI message. - * - * @return bool True if it is a valid LTI message - */ - public function checkMessage(): bool - { - $this->ok = $_SERVER['REQUEST_METHOD'] === 'POST'; - if (!$this->ok) { - $this->reason = 'LTI messages must use HTTP POST'; - } elseif (!empty($this->jwt) && !empty($this->jwt->hasJwt())) { - $this->ok = false; - if (is_null($this->messageParameters['oauth_consumer_key']) || (strlen($this->messageParameters['oauth_consumer_key']) <= 0)) { - $this->reason = 'Missing iss claim'; - } elseif (empty($this->jwt->getClaim('iat', ''))) { - $this->reason = 'Missing iat claim'; - } elseif (empty($this->jwt->getClaim('exp', ''))) { - $this->reason = 'Missing exp claim'; - } elseif (intval($this->jwt->getClaim('iat')) > intval($this->jwt->getClaim('exp'))) { - $this->reason = 'iat claim must not have a value greater than exp claim'; - } elseif (empty($this->jwt->getClaim('nonce', ''))) { - $this->reason = 'Missing nonce claim'; - } else { - $this->ok = true; - } - } - // Set signature method from request - if (isset($this->messageParameters['oauth_signature_method'])) { - $this->signatureMethod = $this->messageParameters['oauth_signature_method']; - if (($this instanceof Tool) && !empty($this->platform)) { - $this->platform->signatureMethod = $this->signatureMethod; - } - } - // Check all required launch parameters - if ($this->ok) { - $this->ok = isset($this->messageParameters['lti_message_type']); - if (!$this->ok) { - $this->reason = 'Missing lti_message_type parameter.'; - } - } - if ($this->ok) { - $this->ok = isset($this->messageParameters['lti_version']) && in_array( - $this->messageParameters['lti_version'], - Util::$LTI_VERSIONS - ); - if (!$this->ok) { - $this->reason = 'Invalid or missing lti_version parameter.'; - } - } - - return $this->ok; - } - - /** - * Verify the signature of a message. - * - * @return bool True if the signature is valid - */ - public function verifySignature(): bool - { - $ok = false; - $key = $this->key; - $publicKey = ''; //changed - if (!empty($key)) { - $secret = $this->secret; - } elseif (($this instanceof Tool) && !empty($this->platform)) { - $key = $this->platform->getKey(); - $secret = $this->platform->secret; - } elseif (($this instanceof Platform) && !empty(Tool::$defaultTool)) { - $key = Tool::$defaultTool->getKey(); - $secret = Tool::$defaultTool->secret; - } - if ($this instanceof Tool) { - $platform = $this->platform; - $publicKey = $this->platform->rsaKey; - $jku = $this->platform->jku; - } else { - $platform = $this; - if (!empty(Tool::$defaultTool)) { - $publicKey = Tool::$defaultTool->rsaKey; - $jku = Tool::$defaultTool->jku; - } else { - $publicKey = $this->rsaKey; - $jku = $this->jku; - } - } - - if (empty($this->jwt) || empty($this->jwt->hasJwt())) { // OAuth-signed message - try { - $store = new OAuthDataStore($this); -// $server = new OAuth\OAuthServer($store); - $server = new LTIOAuth\OAuthServer($store); - //ToDo OAuthSignatureMethod -// $method = new OAuth\OAuthSignatureMethod_HMAC_SHA224(); -// $server->add_signature_method($method); -// $method = new OAuth\OAuthSignatureMethod_HMAC_SHA256(); -// $server->add_signature_method($method); -// $method = new OAuth\OAuthSignatureMethod_HMAC_SHA384(); -// $server->add_signature_method($method); -// $method = new OAuth\OAuthSignatureMethod_HMAC_SHA512(); -// $server->add_signature_method($method); -// $method = new OAuth\OAuthSignatureMethod_HMAC_SHA1(); - $method = new LTIOAuth\OAuthSignatureMethod_HMAC_SHA1(); - $server->add_signature_method($method); -// $request = OAuth\OAuthRequest::from_request(); - $request = LTIOAuth\OAuthRequest::from_request(); - $server->verify_request($request); - $ok = true; - } catch (\Exception $e) { - if (empty($this->reason)) { -// $oauthConsumer = new OAuth\OAuthConsumer($key, $secret); - $oauthConsumer = new LTIOAuth\OAuthConsumer($key, $secret); -// $signature = $request->build_signature($method, $oauthConsumer, false); - $signature = $request->build_signature($method, $oauthConsumer, null); - if ($this->debugMode) { - $this->reason = $e->getMessage(); - } - if (empty($this->reason)) { - $this->reason = 'OAuth signature check failed - perhaps an incorrect secret or timestamp.'; - } - $this->details[] = "Shared secret: '{$secret}'"; - $this->details[] = 'Current timestamp: ' . time(); - $this->details[] = "Expected signature: {$signature}"; - $this->details[] = "Base string: {$request->base_string}"; - } - } - } else { // JWT-signed message - $nonce = new PlatformNonce($platform, $this->jwt->getClaim('nonce')); - - $ok = !$nonce->load(); - if ($ok) { - $ok = $nonce->save(); - } - if (!$ok) { - $this->reason = 'Invalid nonce.'; - } elseif (!empty($publicKey) || !empty($jku) || Jwt::$allowJkuHeader) { - if (empty($publicKey)) { //added - $publicKey = ""; - } - if (empty($jku)) { //added - $jku = ""; - } - $ok = $this->jwt->verify($publicKey, $jku); - if (!$ok) { - $this->reason = 'JWT signature check failed - perhaps an invalid public key or timestamp'; - } - } else { - $ok = false; - $this->reason = 'Unable to verify JWT signature as neither a public key nor a JSON Web Key URL is specified'; - } - } - - return $ok; - } - - ### - ### PRIVATE METHODS - ### - - /** - * Parse the message - */ - private function parseMessage() - { - if (is_null($this->messageParameters)) { - $this->getRawParameters(); - if (isset($this->rawParameters['id_token']) || isset($this->rawParameters['JWT'])) { // JWT-signed message - try { - $this->jwt = Jwt::getJwtClient(); - if (isset($this->rawParameters['id_token'])) { - $this->ok = $this->jwt->load($this->rawParameters['id_token'], $this->rsaKey); - } else { - $this->ok = $this->jwt->load($this->rawParameters['JWT'], $this->rsaKey); - } - if (!$this->ok) { - $this->reason = 'Message does not contain a valid JWT'; - } else { - $this->ok = $this->jwt->hasClaim('iss') && $this->jwt->hasClaim('aud') && - $this->jwt->hasClaim(Util::JWT_CLAIM_PREFIX . '/claim/deployment_id'); - if ($this->ok) { - $iss = $this->jwt->getClaim('iss'); - $aud = $this->jwt->getClaim('aud'); - $deploymentId = $this->jwt->getClaim(Util::JWT_CLAIM_PREFIX . '/claim/deployment_id'); - $this->ok = !empty($iss) && !empty($aud) && !empty($deploymentId); - if (!$this->ok) { - $this->reason = 'iss, aud and/or deployment_id claim is empty'; - } elseif (is_array($aud)) { - if ($this->jwt->hasClaim('azp')) { - $this->ok = !empty($this->jwt->getClaim('azp')); - if (!$this->ok) { - $this->reason = 'azp claim is empty'; - } else { - $this->ok = in_array($this->jwt->getClaim('azp'), $aud); - if ($this->ok) { - $aud = $this->jwt->getClaim('azp'); - } else { - $this->reason = 'azp claim value is not included in aud claim'; - } - } - } else { - $aud = $aud[0]; - $this->ok = !empty($aud); - if (!$this->ok) { - $this->reason = 'First element of aud claim is empty'; - } - } - } elseif ($this->jwt->hasClaim('azp')) { - $this->ok = $this->jwt->getClaim('azp') === $aud; - if (!$this->ok) { - $this->reason = 'aud claim does not match the azp claim'; - } - } - if ($this->ok) { - if ($this instanceof Tool) { - $this->platform = \ilLTIPlatform::fromPlatformId($iss, $aud, $deploymentId, $this->dataConnector); - $this->platform->platformId = $iss; - if (isset($this->rawParameters['id_token'])) { - $this->ok = !empty($this->rawParameters['state']); - if ($this->ok) { - $nonce = new PlatformNonce($this->platform, $this->rawParameters['state']); - $this->ok = $nonce->load(); - if (!$this->ok) { - $platform = \ilLTIPlatform::fromPlatformId($iss, $aud, null, $this->dataConnector); - $nonce = new PlatformNonce($platform, $this->rawParameters['state']); - $this->ok = $nonce->load(); - } - if (!$this->ok) { - $platform = \ilLTIPlatform::fromPlatformId($iss, null, null, $this->dataConnector); - $nonce = new PlatformNonce($platform, $this->rawParameters['state']); - $this->ok = $nonce->load(); - } - if ($this->ok) { - $this->ok = $nonce->delete(); - } - } - } - } - if ($this->ok) { - $this->messageParameters = array(); - $this->messageParameters['oauth_consumer_key'] = $aud; - $this->messageParameters['oauth_signature_method'] = $this->jwt->getHeader('alg'); - $this->parseClaims(); - } else { - $this->reason = 'state parameter is invalid or missing'; - } - } - } else { - $this->reason = 'iss, aud and/or deployment_id claim not found'; - } - } - } catch (\Exception $e) { - $this->ok = false; - $this->reason = 'Message does not contain a valid JWT'; - } - } elseif (isset($this->rawParameters['error'])) { // Error with JWT-signed message - $this->ok = false; - $this->reason = $this->rawParameters['error']; - if (!empty($this->rawParameters['error_description'])) { - $this->reason .= ": {$this->rawParameters['error_description']}"; - } - } else { // OAuth - if (isset($this->rawParameters['oauth_consumer_key']) && ($this instanceof Tool)) { - //changed UK temporary -// $this->platform = Platform::fromConsumerKey($this->rawParameters['oauth_consumer_key'], $this->dataConnector); - $this->platform = \ilLTIPlatform::fromConsumerKey($this->rawParameters['oauth_consumer_key'], $this->dataConnector); - } - $this->messageParameters = $this->rawParameters; - } - } - } - - /** - * Parse the claims - */ - private function parseClaims() - { - $payload = Util::cloneObject((object) $this->jwt->getPayload()); //UK: added object - $errors = array(); - foreach (Util::JWT_CLAIM_MAPPING as $key => $mapping) { - $claim = Util::JWT_CLAIM_PREFIX; - if (!empty($mapping['suffix'])) { - $claim .= "-{$mapping['suffix']}"; - } - $claim .= '/claim/'; - if (is_null($mapping['group'])) { - $claim = $mapping['claim']; - } elseif (empty($mapping['group'])) { - $claim .= $mapping['claim']; - } else { - $claim .= $mapping['group']; - } - if ($this->jwt->hasClaim($claim)) { - $value = null; - if (empty($mapping['group'])) { - unset($payload->{$claim}); - $value = $this->jwt->getClaim($claim); - } else { - $group = $this->jwt->getClaim($claim); - if (is_array($group) && array_key_exists($mapping['claim'], $group)) { - unset($payload->{$claim}[$mapping['claim']]); - $value = $group[$mapping['claim']]; - } elseif (is_object($group) && isset($group->{$mapping['claim']})) { - unset($payload->{$claim}->{$mapping['claim']}); - $value = $group->{$mapping['claim']}; - } - } - if (!is_null($value)) { - if (isset($mapping['isArray']) && $mapping['isArray']) { - if (!is_array($value)) { - $errors[] = "'{$claim}' claim must be an array"; - } else { - $value = implode(',', $value); - } - } elseif (isset($mapping['isObject']) && $mapping['isObject']) { - $value = json_encode($value); - } elseif (isset($mapping['isBoolean']) && $mapping['isBoolean']) { - $value = $value ? 'true' : 'false'; - } elseif (isset($mapping['isInteger']) && $mapping['isInteger']) { - $value = strval($value); - } - } - if (!is_null($value) && is_string($value)) { - $this->messageParameters[$key] = $value; - } - } - } - if (!empty($this->messageParameters['lti_message_type']) && - in_array($this->messageParameters['lti_message_type'], array_values(Util::MESSAGE_TYPE_MAPPING))) { - $this->messageParameters['lti_message_type'] = array_search( - $this->messageParameters['lti_message_type'], - Util::MESSAGE_TYPE_MAPPING - ); - } - if (!empty($this->messageParameters['accept_types'])) { - $types = array_filter(explode(',', str_replace(' ', '', $this->messageParameters['accept_types'])), 'strlen'); - $mediaTypes = array(); - if (!empty($this->messageParameters['accept_media_types'])) { - $mediaTypes = array_filter( - explode(',', str_replace(' ', '', $this->messageParameters['accept_media_types'])), - 'strlen' - ); - } - if (in_array(Item::TYPE_LTI_LINK, $types)) { - $mediaTypes[] = Item::LTI_LINK_MEDIA_TYPE; - } - if (in_array(Item::TYPE_LTI_ASSIGNMENT, $types)) { - $mediaTypes[] = Item::LTI_ASSIGNMENT_MEDIA_TYPE; - } - if (in_array('html', $types) && !in_array('*/*', $mediaTypes)) { - $mediaTypes[] = 'text/html'; - } - if (in_array('image', $types) && !in_array('*/*', $mediaTypes)) { - $mediaTypes[] = 'image/*'; - } - $mediaTypes = array_unique($mediaTypes); - $this->messageParameters['accept_media_types'] = implode(',', $mediaTypes); - } - $claim = Util::JWT_CLAIM_PREFIX . '/claim/custom'; - if ($this->jwt->hasClaim($claim)) { - unset($payload->{$claim}); - $custom = $this->jwt->getClaim($claim); - if (!is_array($custom) && !is_object($custom)) { - $errors[] = "'{$claim}' claim must be an object"; - } else { - foreach ($custom as $key => $value) { - $this->messageParameters["custom_{$key}"] = $value; - } - } - } - $claim = Util::JWT_CLAIM_PREFIX . '/claim/ext'; - if ($this->jwt->hasClaim($claim)) { - unset($payload->{$claim}); - $ext = $this->jwt->getClaim($claim); - if (!is_array($ext) && !is_object($ext)) { - $errors[] = "'{$claim}' claim must be an object"; - } else { - foreach ($ext as $key => $value) { - $this->messageParameters["ext_{$key}"] = $value; - } - } - } - $claim = Util::JWT_CLAIM_PREFIX . '/claim/lti1p1'; - if ($this->jwt->hasClaim($claim)) { - unset($payload->{$claim}); - $lti1p1 = $this->jwt->getClaim($claim); - if (!is_array($lti1p1) && !is_object($lti1p1)) { - $errors[] = "'{$claim}' claim must be an object"; - } else { - foreach ($lti1p1 as $key => $value) { - if (is_null($value)) { - $value = ''; - } elseif (is_object($value)) { - $value = json_encode($value); - } - $this->messageParameters["lti1p1_{$key}"] = $value; - } - } - } - if (!empty($payload)) { - $objVars = get_object_vars($payload); - foreach ($objVars as $attrName => $attrValue) { - if (empty((array) $attrValue)) { - unset($payload->{$attrName}); - } - } - $this->messageParameters['unmapped_claims'] = json_encode($payload); - } - if (!empty($errors)) { - $this->ok = false; - $this->reason = 'Invalid JWT: ' . implode(', ', $errors); - } - } - - /** - * Call any callback function for the requested action. - * - * This function may set the redirect_url and output properties. - */ - private function doCallback() - { - if (array_key_exists($this->messageParameters['lti_message_type'], Util::$METHOD_NAMES)) { - $callback = Util::$METHOD_NAMES[$this->messageParameters['lti_message_type']]; - } else { - $callback = "on{$this->messageParameters['lti_message_type']}"; - } - if (method_exists($this, $callback)) { - $this->$callback(); - } elseif ($this->ok) { - $this->ok = false; - $this->reason = "Message type not supported: {$this->messageParameters['lti_message_type']}"; - } - } - - /** - * Add the OAuth 1 signature to an array of message parameters or to a header string. - * @param string $endpoint URL to which message is being sent - * @param mixed $data Data to be passed - * @param string $method HTTP method - * @param string|null $type Content type of data being passed - * @param string|null $hash OAuth body hash value - * @param int|null $timestamp Timestamp - * @return string[]|string Array of signed message parameters or header string - */ - private function addOAuth1Signature(string $endpoint, $data, string $method, ?string $type, ?string $hash, ?int $timestamp) - { - $params = array(); - if (is_array($data)) { - $params = $data; - $params['oauth_callback'] = 'about:blank'; - } - // Check for query parameters which need to be included in the signature - $queryString = parse_url($endpoint, PHP_URL_QUERY); -// $queryParams = OAuth\OAuthUtil::parse_parameters($queryString); - $queryParams = LTIOAuth\OAuthUtil::parse_parameters($queryString); - $params = array_merge_recursive($queryParams, $params); - - if (!is_array($data)) { - if (empty($hash)) { // Calculate body hash - switch ($this->signatureMethod) { - case 'HMAC-SHA224': - $hash = base64_encode(hash('sha224', $data, true)); - break; - case 'HMAC-SHA256': - $hash = base64_encode(hash('sha256', $data, true)); - break; - case 'HMAC-SHA384': - $hash = base64_encode(hash('sha384', $data, true)); - break; - case 'HMAC-SHA512': - $hash = base64_encode(hash('sha512', $data, true)); - break; - default: - $hash = base64_encode(sha1($data, true)); - break; - } - } - $params['oauth_body_hash'] = $hash; - } - if (!empty($timestamp)) { - $params['oauth_timestamp'] = $timestamp; - } - - // Add OAuth signature - switch ($this->signatureMethod) { - //UK: ToDo -// case 'HMAC-SHA224': -// $hmacMethod = new OAuth\OAuthSignatureMethod_HMAC_SHA224(); -// break; -// case 'HMAC-SHA256': -// $hmacMethod = new OAuth\OAuthSignatureMethod_HMAC_SHA256(); -// break; -// case 'HMAC-SHA384': -// $hmacMethod = new OAuth\OAuthSignatureMethod_HMAC_SHA384(); -// break; -// case 'HMAC-SHA512': -// $hmacMethod = new OAuth\OAuthSignatureMethod_HMAC_SHA512(); -// break; - default: -// $hmacMethod = new OAuth\OAuthSignatureMethod_HMAC_SHA1(); - $hmacMethod = new LTIOAuth\OAuthSignatureMethod_HMAC_SHA1(); - break; - } - $key = $this->key; - $secret = $this->secret; - if (empty($key)) { - if (($this instanceof Tool) && !empty($this->platform)) { - $key = $this->platform->getKey(); - $secret = $this->platform->secret; - } elseif (($this instanceof Platform) && !empty(Tool::$defaultTool)) { - $key = Tool::$defaultTool->getKey(); - $secret = Tool::$defaultTool->secret; - } - } -// $oauthConsumer = new OAuth\OAuthConsumer($key, $secret, null); - $oauthConsumer = new LTIOAuth\OAuthConsumer($key, $secret, null); -// $oauthReq = OAuth\OAuthRequest::from_consumer_and_token($oauthConsumer, null, $method, $endpoint, $params); - $oauthReq = LTIOAuth\OAuthRequest::from_consumer_and_token($oauthConsumer, null, $method, $endpoint, $params); - $oauthReq->sign_request($hmacMethod, $oauthConsumer, null); - if (!is_array($data)) { - $header = $oauthReq->to_header(); - if (empty($data)) { - if (!empty($type)) { - $header .= "\nAccept: {$type}"; - } - } elseif (isset($type)) { - $header .= "\nContent-Type: {$type}"; - $header .= "\nContent-Length: " . strlen($data); - } - return $header; - } else { - // Remove parameters from query string - $params = $oauthReq->get_parameters(); - foreach ($queryParams as $key => $value) { - if (!is_array($value)) { - if (!is_array($params[$key])) { - if ($params[$key] === $value) { - unset($params[$key]); - } - } else { - $params[$key] = array_diff($params[$key], array($value)); - } - } else { - foreach ($value as $element) { - $params[$key] = array_diff($params[$key], array($value)); - } - } - } - // Remove any parameters comprising an empty array of values - foreach ($params as $key => $value) { - if (is_array($value)) { - if (count($value) <= 0) { - unset($params[$key]); - } elseif (count($value) === 1) { - $params[$key] = reset($value); - } - } - } - return $params; - } - } - - /** - * Add the JWT signature to an array of message parameters or to a header string. - * @param string $endpoint URL to which message is being sent - * @param mixed $data Data to be passed - * @param string $method HTTP method - * @param string|null $type Content type of data being passed - * @param string|null $nonce Nonce value for JWT - * @param int|null $timestamp Timestamp - * @return string[]|string Array of signed message parameters or header string - */ - private function addJWTSignature(string $endpoint, $data, string $method, ?string $type, ?string $nonce, ?int $timestamp) - { - $ok = false; - if (is_array($data)) { - $ok = true; - if (empty($nonce)) { - $nonce = Util::getRandomString(32); - } - $publicKey = null; - if (!array_key_exists('grant_type', $data)) { - $this->messageParameters = $data; - $payload = $this->getMessageClaims(); - $privateKey = $this->rsaKey; - $kid = $this->kid; - $jku = $this->jku; - if ($this instanceof Platform) { - if (!empty(Tool::$defaultTool)) { - $publicKey = Tool::$defaultTool->rsaKey; - } - $payload['iss'] = $this->platformId; - $payload['aud'] = array($this->clientId); - $payload['azp'] = $this->clientId; - $payload[Util::JWT_CLAIM_PREFIX . '/claim/deployment_id'] = $this->deploymentId; - $payload[Util::JWT_CLAIM_PREFIX . '/claim/target_link_uri'] = $endpoint; - $paramName = 'id_token'; - } else { - if (!empty($this->platform)) { - $publicKey = $this->platform->rsaKey; - $payload['iss'] = $this->platform->clientId; - $payload['aud'] = array($this->platform->platformId); - $payload['azp'] = $this->platform->platformId; - $payload[Util::JWT_CLAIM_PREFIX . '/claim/deployment_id'] = $this->platform->deploymentId; - } - $paramName = 'JWT'; - } - $payload['nonce'] = $nonce; - } else { - $authorizationId = ''; - if ($this instanceof Tool) { - $sub = ''; - if (!empty($this->platform)) { - $sub = $this->platform->clientId; - $authorizationId = $this->platform->authorizationServerId; - $publicKey = $this->platform->rsaKey; - } - $privateKey = $this->rsaKey; - $kid = $this->kid; - $jku = $this->jku; - } else { // Tool-hosted services not yet defined in LTI - $sub = $this->clientId; - $kid = $this->kid; - $jku = $this->jku; - $privateKey = $this->rsaKey; - if (!empty(Tool::$defaultTool)) { - $publicKey = Tool::$defaultTool->rsaKey; - } - } - $payload['iss'] = $sub; - $payload['sub'] = $sub; - if (empty($authorizationId)) { - $authorizationId = $endpoint; - } - $payload['aud'] = array($authorizationId); - $payload['jti'] = $nonce; - $params = $data; - $paramName = 'client_assertion'; - } - } - if ($ok) { - if (empty($timestamp)) { - $timestamp = time(); - } - $payload['iat'] = $timestamp; - $payload['exp'] = $timestamp + Jwt::$life; - try { - $jwt = Jwt::getJwtClient(); - $params[$paramName] = $jwt::sign( - $payload, - $this->signatureMethod, - $privateKey, - $kid, - $jku, - $this->encryptionMethod, - $publicKey - ); - } catch (\Exception $e) { - $params = array(); - } - - return $params; - } else { - $header = ''; - if ($this instanceof Tool) { - $platform = $this->platform; - } else { - $platform = $this; - } - $accessToken = $platform->getAccessToken(); - if (empty($accessToken)) { - $accessToken = new AccessToken($platform); - $platform->setAccessToken($accessToken); - } - if (!$accessToken->hasScope()) { // Check token has not expired - $accessToken->get(); - } - if (!empty($accessToken->token)) { - $header = "Authorization: Bearer {$accessToken->token}"; - } - if (empty($data) && ($method !== 'DELETE')) { - if (!empty($type)) { - $header .= "\nAccept: {$type}"; - } - } elseif (isset($type)) { - $header .= "\nContent-Type: {$type}"; - if (!empty($data) && is_string($data)) { - $header .= "\nContent-Length: " . strlen($data); - } - } - - return $header; - } - } - - /** - * Expand a claim into an array of individual fully-qualified claims. - * @param string $claim Name of claim - * @param string $value Value of claim - * @return string[] Array of individual claims and values - */ - private static function fullyQualifyClaim(string $claim, string $value): array - { - $claims = array(); - $empty = true; - if (is_object($value)) { - foreach ($value as $c => $v) { - $empty = false; - $claims = array_merge($claims, static::fullyQualifyClaim("{$claim}/{$c}", $v)); - } - } - if ($empty) { - $claims[$claim] = $value; - } - - return $claims; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/Tool.php b/components/ILIAS/LTI/src/ToolProvider/Tool.php deleted file mode 100755 index 69f10aa5fe5e..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/Tool.php +++ /dev/null @@ -1,2151 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class Tool -{ - use System; - use ApiHook; - - /** - * Default connection error message. - */ - public const CONNECTION_ERROR_MESSAGE = 'Sorry, there was an error connecting you to the application.'; - - /** - * Use ID value only. - */ - public const ID_SCOPE_ID_ONLY = 0; - - /** - * Prefix an ID with the consumer key. - */ - public const ID_SCOPE_GLOBAL = 1; - - /** - * Prefix the ID with the consumer key and context ID. - */ - public const ID_SCOPE_CONTEXT = 2; - - /** - * Prefix the ID with the consumer key and resource ID. - */ - public const ID_SCOPE_RESOURCE = 3; - - /** - * Character used to separate each element of an ID. - */ - public const ID_SCOPE_SEPARATOR = ':'; - - /** - * List of supported incoming message types. - */ - public static array $MESSAGE_TYPES = array( - 'basic-lti-launch-request', - 'ConfigureLaunchRequest', - 'DashboardRequest', - 'ContentItemSelectionRequest', - 'ContentItemUpdateRequest', - 'ToolProxyRegistrationRequest', - 'LtiStartProctoring', - 'LtiEndAssessment' - ); - - /** - * Names of LTI parameters to be retained in the consumer settings property. - */ - private static array $LTI_CONSUMER_SETTING_NAMES = array('custom_tc_profile_url', 'custom_system_setting_url', 'custom_oauth2_access_token_url'); - - /** - * Names of LTI parameters to be retained in the context settings property. - */ - private static array $LTI_CONTEXT_SETTING_NAMES = array('custom_context_setting_url', - 'ext_ims_lis_memberships_id', 'ext_ims_lis_memberships_url', - 'custom_context_memberships_url', 'custom_context_memberships_v2_url', - 'custom_context_group_sets_url', 'custom_context_groups_url', - 'custom_lineitems_url', 'custom_ags_scopes' - ); - - /** - * Names of LTI parameters to be retained in the resource link settings property. - */ - private static array $LTI_RESOURCE_LINK_SETTING_NAMES = array('lis_result_sourcedid', 'lis_outcome_service_url', - 'ext_ims_lis_basic_outcome_url', 'ext_ims_lis_resultvalue_sourcedids', 'ext_outcome_data_values_accepted', - 'ext_ims_lis_memberships_id', 'ext_ims_lis_memberships_url', - 'ext_ims_lti_tool_setting', 'ext_ims_lti_tool_setting_id', 'ext_ims_lti_tool_setting_url', - 'custom_link_setting_url', 'custom_link_memberships_url', - 'custom_lineitems_url', 'custom_lineitem_url', 'custom_ags_scopes', - 'custom_ap_acs_url' - ); - - /** - * Names of LTI parameters to be retained even when not passed. - */ - private static array $LTI_RETAIN_SETTING_NAMES = array('custom_lineitem_url'); - - /** - * Names of LTI custom parameter substitution variables (or capabilities) and their associated default message parameter names. - */ - private static array $CUSTOM_SUBSTITUTION_VARIABLES = array('User.id' => 'user_id', - 'User.image' => 'user_image', - 'User.username' => 'username', - 'User.scope.mentor' => 'role_scope_mentor', - 'Membership.role' => 'roles', - 'Person.sourcedId' => 'lis_person_sourcedid', - 'Person.name.full' => 'lis_person_name_full', - 'Person.name.family' => 'lis_person_name_family', - 'Person.name.given' => 'lis_person_name_given', - 'Person.email.primary' => 'lis_person_contact_email_primary', - 'Context.id' => 'context_id', - 'Context.type' => 'context_type', - 'Context.title' => 'context_title', - 'Context.label' => 'context_label', - 'CourseOffering.sourcedId' => 'lis_course_offering_sourcedid', - 'CourseSection.sourcedId' => 'lis_course_section_sourcedid', - 'CourseSection.label' => 'context_label', - 'CourseSection.title' => 'context_title', - 'ResourceLink.id' => 'resource_link_id', - 'ResourceLink.title' => 'resource_link_title', - 'ResourceLink.description' => 'resource_link_description', - 'Result.sourcedId' => 'lis_result_sourcedid', - 'BasicOutcome.url' => 'lis_outcome_service_url', - 'ToolConsumerProfile.url' => 'custom_tc_profile_url', - 'ToolProxy.url' => 'tool_proxy_url', - 'ToolProxy.custom.url' => 'custom_system_setting_url', - 'ToolProxyBinding.custom.url' => 'custom_context_setting_url', - 'LtiLink.custom.url' => 'custom_link_setting_url', - 'LineItems.url' => 'custom_lineitems_url', - 'LineItem.url' => 'custom_lineitem_url', - 'ToolProxyBinding.memberships.url' => 'custom_context_memberships_url', - 'ToolProxyBinding.nrps.url' => 'custom_context_memberships_v2_url', - 'LtiLink.memberships.url' => 'custom_link_memberships_url', - 'LtiLink.acs.url' => 'custom_ap_acs_url' - ); - -// /** -// * Tool consumer object. -// * -// * @deprecated Use Tool::$platform instead -// * @see platform -// * -// * @var ToolConsumer|null $consumer -// */ -// public ?ToolConsumer $consumer = null; - - /** - * Platform object. - * - * @var \ilLTIPlatform $platform // UK Check change from Platform|null (to \ILIAS\LTI\ToolProvider\Platform) to \ilLTIPlatform - */ - public \ilLTIPlatform $platform; - - /** - * Return URL provided by platform. - * - * @var string|null $returnUrl - */ - public ?string $returnUrl = null; - - /** - * UserResult object. - * - * @var UserResult|null $userResult - */ - public ?UserResult $userResult = null; - - /** - * Resource link object. - * - * @var ResourceLink|null $resourceLink - */ - public ?ResourceLink $resourceLink = null; - - /** - * Context object. - * - * @var Context|null $context - */ - public ?Context $context = null; - - /** - * Default email domain. - * - * @var string $defaultEmail - */ - public string $defaultEmail = ''; - - /** - * Scope to use for user IDs. - * - * @var int $idScope - */ - public int $idScope = self::ID_SCOPE_ID_ONLY; - - /** - * Whether shared resource link arrangements are permitted. - * - * @var bool $allowSharing - */ - public bool $allowSharing = false; - - /** - * Message for last request processed - * - * @var string $message - */ - public ?string $message = null; - - /** - * Base URL for tool service - * - * @var string|null $baseUrl - */ - public ?string $baseUrl = null; - - /** - * Vendor details - * - * @var Item|null $vendor - */ - public ?Item $vendor = null; - - /** - * Product details - * - * @var Item|null $product - */ - public ?Item $product = null; - - /** - * Services required by Tool - * - * @var array|null $requiredServices - */ - public ?array $requiredServices = null; - - /** - * Optional services used by Tool - * - * @var array|null $optionalServices - */ - public ?array $optionalServices = null; - - /** - * Resource handlers for Tool - * - * @var array|null $resourceHandlers - */ - public ?array $resourceHandlers = null; - - /** - * Message URL for Tool - * - * @var string|null $messageUrl - */ - public ?string $messageUrl = null; - - /** - * Initiate Login request URL for Tool - * - * @var string|null $initiateLoginUrl - */ - public ?string $initiateLoginUrl = null; - - /** - * Redirection URIs for Tool - * - * @var array|null $redirectionUris - */ - public ?array $redirectionUris = null; - - /** - * Default tool for use with service requests - * - * @var Tool|null $defaultTool - */ - public static ?Tool $defaultTool = null; - - /** - * Use GET method for authentication request messages when true - * - * @var bool $authenticateUsingGet - */ - public static bool $authenticateUsingGet = false; - - /** - * URL to redirect user to on successful completion of the request. - * - * @var string|null $redirectUrl - */ - protected ?string $redirectUrl = null; - - /** - * Media types accepted by the platform. - * - * @var array|null $mediaTypes - */ - protected ?array $mediaTypes = null; - - /** - * Content item types accepted by the platform. - * - * @var array|null $contentTypes - */ - protected ?array $contentTypes = null; - - /** - * File types accepted by the platform. - * - * @var array|null $fileTypes - */ - protected ?array $fileTypes = null; - - /** - * Document targets accepted by the platform. - * - * @var array|null $documentTargets - */ - protected ?array $documentTargets = null; - - /** - * Default HTML to be displayed on a successful completion of the request. - * - * @var string|null $output - */ - protected ?string $output = null; - - /** - * HTML to be displayed on an unsuccessful completion of the request and no return URL is available. - * - * @var string|null $errorOutput - */ - protected ?string $errorOutput = null; - - /** - * LTI parameter constraints for auto validation checks. - * - * @var array|null $constraints - */ - private ?array $constraints = null; - - /** - * Class constructor - * @param DataConnector|null $dataConnector Object containing a database connection object - */ - public function __construct(DataConnector $dataConnector = null) - { -// $this->consumer = &$this->platform; //UK: deprecated - $this->initialize(); - if (empty($dataConnector)) { - $dataConnector = DataConnector::getDataConnector(); - } - $this->dataConnector = $dataConnector; - } - - /** - * Initialise the tool. - */ - public function initialize() - { - $this->id = null; - $this->key = null; - $this->name = null; - $this->secret = null; - $this->messageUrl = null; - $this->initiateLoginUrl = null; - $this->redirectionUris = null; - $this->rsaKey = null; - $this->signatureMethod = 'HMAC-SHA1'; - $this->encryptionMethod = null; - $this->ltiVersion = null; - $this->settings = array(); - $this->enabled = false; - $this->enableFrom = null; - $this->enableUntil = null; - $this->lastAccess = null; - $this->created = null; - $this->updated = null; - $this->constraints = array(); -// $this->vendor = new Profile\Item(); //Changed UK - $this->vendor = new \ILIAS\LTI\ToolProvider\Content\Item(null); -// $this->product = new Profile\Item(); - $this->product = new \ILIAS\LTI\ToolProvider\Content\Item(null); - $this->requiredServices = array(); - $this->optionalServices = array(); - $this->resourceHandlers = array(); - } - - /** - * Save the tool to the database. - * - * @return bool True if the object was successfully saved - */ - public function save(): bool - { - return $this->dataConnector->saveTool($this); - } - - /** - * Delete the tool from the database. - * - * @return bool True if the object was successfully deleted - */ - public function delete(): bool - { - return $this->dataConnector->deleteTool($this); - } - - /** - * Get the message parameters - * - * @return array The message parameter array - */ - public function getMessageParameters(): array - { - if (is_null($this->messageParameters)) { - $this->parseMessage(); - // Set debug mode - if (Util::$logLevel < Util::LOGLEVEL_DEBUG) { - $this->debugMode = (isset($this->messageParameters['custom_debug']) && - (strtolower($this->messageParameters['custom_debug']) === 'true')); - if ($this->debugMode) { - Util::$logLevel = Util::LOGLEVEL_DEBUG; - } - } - // Set return URL if available - if (!empty($this->messageParameters['lti_message_type']) && - (($this->messageParameters['lti_message_type'] === 'ContentItemSelectionRequest') || ($this->messageParameters['lti_message_type'] === 'ContentItemUpdateRequest')) && - !empty($this->messageParameters['content_item_return_url'])) { - $this->returnUrl = $this->messageParameters['content_item_return_url']; - } - if (empty($this->returnUrl) && !empty($this->messageParameters['launch_presentation_return_url'])) { - $this->returnUrl = $this->messageParameters['launch_presentation_return_url']; - } - } - - return $this->messageParameters; - } - - /** - * Process an incoming request - * @param bool $strictMode True if full compliance with the LTI specification is required (optional, default is false) - */ - public function handleRequest(bool $strictMode = false) - { - $parameters = Util::getRequestParameters(); - if ($this->debugMode) { - Util::$logLevel = Util::LOGLEVEL_DEBUG; - } - if ($_SERVER['REQUEST_METHOD'] === 'HEAD') { // Ignore HEAD requests - Util::logRequest(true); - } elseif (isset($parameters['iss']) && (strlen($parameters['iss']) > 0)) { // Initiate login request - Util::logRequest(); - if (!isset($parameters['login_hint']) || (strlen($parameters['login_hint']) <= 0)) { - $this->ok = false; - $this->reason = 'Missing login_hint parameter'; - } elseif (!isset($parameters['target_link_uri']) || (strlen($parameters['target_link_uri']) <= 0)) { - $this->ok = false; - $this->reason = 'Missing target_link_uri parameter'; - } else { - $this->ok = $this->sendAuthenticationRequest($parameters); - } - } elseif (isset($parameters['openid_configuration']) && (strlen($parameters['openid_configuration']) > 0)) { // Dynamic registration request - Util::logRequest(); - $this->onRegistration(); - } else { // LTI message - $this->getMessageParameters(); - Util::logRequest(); - if ($this->ok && $this->authenticate($strictMode)) { - if (empty($this->output)) { - $this->doCallback(); - if ($this->ok && ($this->messageParameters['lti_message_type'] === 'ToolProxyRegistrationRequest')) { - $this->platform->save(); - } - } - } - } - if (!$this->ok) { - $errorMessage = "Request failed with reason: '{$this->reason}'"; - if (!empty($this->details)) { - $errorMessage .= PHP_EOL . 'Debug information:'; - foreach ($this->details as $detail) { - $errorMessage .= PHP_EOL . " {$detail}"; - } - } - Util::logError($errorMessage); - } - - $this->result(); - } - - /** - * Add a parameter constraint to be checked on launch - * @param string $name Name of parameter to be checked - * @param bool $required True if parameter is required (optional, default is true) - * @param int|null $maxLength Maximum permitted length of parameter value (optional, default is null) - * @param array|null $messageTypes Array of message types to which the constraint applies (optional, default is all) - */ - public function setParameterConstraint(string $name, bool $required = true, int $maxLength = null, array $messageTypes = null) - { - $name = trim($name); - if (!empty($name)) { - $this->constraints[$name] = array('required' => $required, 'max_length' => $maxLength, 'messages' => $messageTypes); - } - } - -// /** -// * Get an array of defined tool consumers -// * -// * @deprecated Use getPlatforms() instead -// * @see Tool::getPlatforms() -// * -// * @return array Array of ToolConsumer objects -// */ -// public function getConsumers() : array -// { -// Util::logDebug( -// 'Method ceLTIc\LTI\Tool::getConsumers() has been deprecated; please use ceLTIc\LTI\Tool::getPlatforms() instead.', -// true -// ); -// return $this->getPlatforms(); -// } - - /** - * Get an array of defined platforms - * - * @return array Array of Platform objects - */ - public function getPlatforms(): array - { - return $this->dataConnector->getPlatforms(); - } - - /** - * Find an offered service based on a media type and HTTP action(s) - * @param string $format Media type required - * @param array $methods Array of HTTP actions required - * @return object|bool The service object - */ - public function findService(string $format, array $methods) - { - $found = false; - $services = $this->platform->profile->service_offered; - if (is_array($services)) { - $n = -1; - foreach ($services as $service) { - $n++; - if (!is_array($service->format) || !in_array($format, $service->format)) { - continue; - } - $missing = array(); - foreach ($methods as $method) { - if (!is_array($service->action) || !in_array($method, $service->action)) { - $missing[] = $method; - } - } - $methods = $missing; - if (count($methods) <= 0) { - $found = $service; - break; - } - } - } - - return $found; - } - - //not necessary because LTI V 2 -// /** -// * Send the tool proxy to the platform -// * -// * @return bool True if the tool proxy was accepted -// */ -// public function doToolProxyService() : bool -// { -// // Create tool proxy -// $toolProxyService = $this->findService('application/vnd.ims.lti.v2.toolproxy+json', array('POST')); -// $secret = Util::getRandomString(12); -// $toolProxy = new MediaType\ToolProxy($this, $toolProxyService, $secret); -// $http = $this->platform->doServiceRequest($toolProxyService, 'POST', 'application/vnd.ims.lti.v2.toolproxy+json', -// json_encode($toolProxy)); -// $ok = $http->ok && ($http->status === 201) && !empty($http->responseJson->tool_proxy_guid); -// if ($ok) { -// $this->platform->setKey($http->responseJson->tool_proxy_guid); -// $this->platform->secret = $toolProxy->security_contract->shared_secret; -// $this->platform->toolProxy = $toolProxy; //UK: changed from json_encode($toolProxy); -// $this->platform->save(); -// } -// -// return $ok; -// } - -// /** -// * Generate a web page containing an auto-submitted form of parameters. -// * @param string $url URL to which the form should be submitted -// * @param array $params Array of form parameters -// * @param string $target Name of target (optional) -// * @return string -// *@deprecated Use Util::sendForm() instead -// * @see Util::sendForm() -// */ -// public static function sendForm(string $url, array $params, string $target = '') : string -// { -// Util::logDebug('Method ceLTIc\LTI\Tool::sendForm() has been deprecated; please use ceLTIc\LTI\Util::sendForm() instead.', -// true); -// Util::sendForm($url, $params, $target); -// } - - ### - ### PROTECTED METHODS - ### - - /** - * Process a valid launch request - */ - protected function onLaunch() - { - $this->reason = 'No onLaunch method found for tool'; - $this->onError(); - } - - /** - * Process a valid configure request - */ - protected function onConfigure() - { - $this->reason = 'No onConfigure method found for tool'; - $this->onError(); - } - - /** - * Process a valid dashboard request - */ - protected function onDashboard() - { - $this->reason = 'No onDashboard method found for tool'; - $this->onError(); - } - - /** - * Process a valid content-item request - */ - protected function onContentItem() - { - $this->reason = 'No onContentItem method found for tool'; - $this->onError(); - } - - /** - * Process a valid content-item update request - */ - protected function onContentItemUpdate() - { - $this->reason = 'No onContentItemUpdate method found for tool'; - $this->onError(); - } - - /** - * Process a valid tool proxy registration request - */ - protected function onRegister() - { - $this->reason = 'No onRegister method found for tool'; - $this->onError(); - } - - /** - * Process a dynamic registration request - */ - protected function onRegistration() - { - $platformConfig = $this->getPlatformConfiguration(); - if ($this->ok) { - $toolConfig = $this->getConfiguration($platformConfig); - $registrationConfig = $this->sendRegistration($platformConfig, $toolConfig); - if ($this->ok) { - $this->getPlatformToRegister($platformConfig, $registrationConfig); - } - } - $this->getRegistrationResponsePage($toolConfig); - $this->ok = true; - } - - /** - * Process a valid start proctoring request - */ - protected function onLtiStartProctoring() - { - $this->reason = 'No onLtiStartProctoring method found for tool'; - $this->onError(); - } - - /** - * Process a valid end assessment request - */ - protected function onLtiEndAssessment() - { - $this->reason = 'No onLtiEndAssessment method found for tool'; - $this->onError(); - } - - /** - * Process a login initiation request - * @param array $requestParameters Request parameters - * @param array $authParameters Authentication request parameters - */ - - protected function onInitiateLogin(array $requestParameters, array &$authParameters) - { - } - - /** - * Process a response to an invalid request - */ - protected function onError() - { - $this->ok = false; - } - - /** - * Fetch a platform's configuration data - * - * @return array|null Platform configuration data - */ - protected function getPlatformConfiguration(): ?array - { - if ($this->ok) { - $parameters = Util::getRequestParameters(); - $this->ok = !empty($parameters['openid_configuration']); - if ($this->ok) { - $http = new HttpMessage($parameters['openid_configuration']); - $this->ok = $http->send(); - if ($this->ok) { - $platformConfig = json_decode($http->response, true); - $this->ok = !empty($platformConfig); - } - if (!$this->ok) { - $this->reason = 'Unable to access platform configuration details.'; - } - } else { - $this->reason = 'Invalid registration request: missing openid_configuration parameter.'; - } - if ($this->ok) { - $this->ok = !empty($platformConfig['registration_endpoint']) && !empty($platformConfig['jwks_uri']) && !empty($platformConfig['authorization_endpoint']) && - !empty($platformConfig['token_endpoint']) && !empty($platformConfig['https://purl.imsglobal.org/spec/lti-platform-configuration']) && - !empty($platformConfig['claims_supported']) && !empty($platformConfig['scopes_supported']) && - !empty($platformConfig['id_token_signing_alg_values_supported']) && - !empty($platformConfig['https://purl.imsglobal.org/spec/lti-platform-configuration']['product_family_code']) && - !empty($platformConfig['https://purl.imsglobal.org/spec/lti-platform-configuration']['version']) && - !empty($platformConfig['https://purl.imsglobal.org/spec/lti-platform-configuration']['messages_supported']); - if (!$this->ok) { - $this->reason = 'Invalid platform configuration details.'; - } - } - if ($this->ok) { - Jwt::setJwtClient(); //added - check - $jwtClient = Jwt::getJwtClient(); - $algorithms = \array_intersect( - $jwtClient::getSupportedAlgorithms(), - $platformConfig['id_token_signing_alg_values_supported'] - ); - $this->ok = !empty($algorithms); - if ($this->ok) { - rsort($platformConfig['id_token_signing_alg_values_supported']); - } else { - $this->reason = 'None of the signature algorithms offered by the platform is supported.'; - } - } - } - if (!$this->ok) { - $platformConfig = null; - } - - return $platformConfig; - } - - /** - * Prepare the tool's configuration data - * @param array $platformConfig Platform configuration data - * @return array Tool configuration data - */ - protected function getConfiguration(array $platformConfig): array - { - $claimsMapping = array( - 'User.id' => 'sub', - 'Person.name.full' => 'name', - 'Person.name.given' => 'given_name', - 'Person.name.family' => 'family_name', - 'Person.email.primary' => 'email' - ); - $toolName = (!empty($this->product->name)) ? $this->product->name : 'Unnamed tool'; - $toolDescription = (!empty($this->product->description)) ? $this->product->description : ''; -// $oauthRequest = OAuth\OAuthRequest::from_request(); - $oauthRequest = LTIOAuth\OAuthRequest::from_request(); - $toolUrl = $oauthRequest->get_normalized_http_url(); - $pos = strpos($toolUrl, '//'); - $domain = substr($toolUrl, $pos + 2); - $domain = substr($domain, 0, strpos($domain, '/')); - $claimsSupported = $platformConfig['claims_supported']; - $messagesSupported = $platformConfig['https://purl.imsglobal.org/spec/lti-platform-configuration']['messages_supported']; - $scopesSupported = $platformConfig['scopes_supported']; - $iconUrl = null; - $messages = array(); - $claims = array('iss'); - $variables = array(); - $constants = array(); - $redirectUris = array(); - foreach ($this->resourceHandlers as $resourceHandler) { - if (empty($iconUrl)) { - $iconUrl = $resourceHandler->icon; - } - foreach (array_merge($resourceHandler->optionalMessages, $resourceHandler->requiredMessages) as $message) { - $type = $message->type; - if (array_key_exists($type, Util::MESSAGE_TYPE_MAPPING)) { - $type = Util::MESSAGE_TYPE_MAPPING[$type]; - } - $capabilities = array(); - if ($type === 'LtiResourceLinkRequest') { - $toolUrl = "{$this->baseUrl}{$message->path}"; - $redirectUris[] = $toolUrl; - $capabilities = $message->capabilities; - $variables = array_merge($variables, $message->variables); - $constants = array_merge($constants, $message->constants); - } elseif (in_array($type, $messagesSupported)) { - $redirectUris[] = "{$this->baseUrl}{$message->path}"; - $capabilities = $message->capabilities; - $variables = array_merge($message->variables, $variables); - $constants = array_merge($message->constants, $constants); - $messages[] = array( - 'type' => $type, - 'target_link_uri' => "{$this->baseUrl}{$message->path}", - 'label' => $toolName - ); - } - foreach ($capabilities as $capability) { - if (array_key_exists($capability, $claimsMapping) && in_array($claimsMapping[$capability], $claimsSupported)) { - $claims[] = $claimsMapping[$capability]; - } - } - } - } - if (empty($redirectUris)) { - $redirectUris = array($toolUrl); - } else { - $redirectUris = array_unique($redirectUris); - } - if (!empty($claims)) { - $claims = array_unique($claims); - } - $custom = array(); - foreach ($constants as $name => $value) { - $custom[$name] = $value; - } - foreach ($variables as $name => $value) { - $custom[$name] = '$' . $value; - } - $toolConfig = array(); - $toolConfig['application_type'] = 'web'; - $toolConfig['client_name'] = $toolName; - $toolConfig['response_types'] = array('id_token'); - $toolConfig['grant_types'] = array('implicit', 'client_credentials'); - $toolConfig['initiate_login_uri'] = $toolUrl; - $toolConfig['redirect_uris'] = $redirectUris; - $toolConfig['jwks_uri'] = $this->jku; - $toolConfig['token_endpoint_auth_method'] = 'private_key_jwt'; - $toolConfig['https://purl.imsglobal.org/spec/lti-tool-configuration'] = array( - 'domain' => $domain, - 'target_link_uri' => $toolUrl, - 'custom_parameters' => $custom, - 'claims' => $claims, - 'messages' => $messages, - 'description' => $toolDescription - ); - $toolConfig['scope'] = implode(' ', array_intersect($this->requiredScopes, $scopesSupported)); - if (!empty($iconUrl)) { - $toolConfig['logo_uri'] = "{$this->baseUrl}{$iconUrl}"; - } - - return $toolConfig; - } - - /** - * Send the tool registration to the platform - * @param array $platformConfig Platform configuration data - * @param array $toolConfig Tool configuration data - * @return array Registration data - */ - protected function sendRegistration(array $platformConfig, array $toolConfig): ?array - { - if ($this->ok) { - $parameters = Util::getRequestParameters(); - $this->ok = !empty($parameters['registration_token']); - if ($this->ok) { - $body = json_encode($toolConfig); - $headers = "Content-type: application/json\n" . - "Authorization: Bearer {$parameters['registration_token']}"; - $http = new HttpMessage($platformConfig['registration_endpoint'], 'POST', $body, $headers); - $this->ok = $http->send(); - if ($this->ok) { - $registrationConfig = json_decode($http->response, true); - $this->ok = !empty($registrationConfig); - } - if (!$this->ok) { - $this->reason = 'Unable to register with platform.'; - } - } else { - $this->reason = 'Invalid registration request: missing registration_token parameter.'; - } - } - if (!$this->ok) { - $registrationConfig = null; - } - - return $registrationConfig; - } - - /** - * Initialise the platform to be registered - * @param array $platformConfig Platform configuration data - * @param array $registrationConfig Registration data - * @param bool $doSave True if the platform should be saved (optional, default is true) - * @return Platform Platform object - */ - protected function getPlatformToRegister(array $platformConfig, array $registrationConfig, bool $doSave = true): ?Platform - { - $domain = $platformConfig['issuer']; - $pos = strpos($domain, '//'); - if ($pos !== false) { - $domain = substr($domain, $pos + 2); - $pos = strpos($domain, '/'); - if ($pos !== false) { - $domain = substr($domain, 0, $pos); - } - } - $this->platform = new Platform($this->dataConnector); - $this->platform->name = $domain; - $this->platform->ltiVersion = Util::LTI_VERSION1P3; - $this->platform->signatureMethod = reset($platformConfig['id_token_signing_alg_values_supported']); - $this->platform->platformId = $platformConfig['issuer']; - $this->platform->clientId = $registrationConfig['client_id']; - $this->platform->deploymentId = $registrationConfig['https://purl.imsglobal.org/spec/lti-tool-configuration']['deployment_id']; - $this->platform->authenticationUrl = $platformConfig['authorization_endpoint']; - $this->platform->accessTokenUrl = $platformConfig['token_endpoint']; - $this->platform->jku = $platformConfig['jwks_uri']; - if ($doSave) { - $this->ok = $this->platform->save(); - if (!$this->ok) { - $this->reason = 'Sorry, an error occurred when saving the platform details.'; - } - } - - return $this->platform; - } - - /** - * Prepare the page to complete a registration request - * @param array $toolConfig Tool configuration data - */ - protected function getRegistrationResponsePage(array $toolConfig) - { - $enabled = ''; - if (!empty($this->platform)) { - $now = time(); - if (!$this->platform->enabled) { - $enabled = ', but it will need to be enabled by the tool provider before it can be used'; - } elseif (!empty($this->platform->enableFrom) && ($this->platform->enableFrom > $now)) { - $enabled = ', but you will only have access from ' . date('j F Y H:i T', $this->platform->enableFrom); - if (!empty($this->platform->enableUntil)) { - $enabled .= ' until ' . date('j F Y H:i T', $this->platform->enableUntil); - } - } elseif (!empty($this->platform->enableUntil)) { - if ($this->platform->enableUntil > $now) { - $enabled = ', but you will only have access until ' . date('j F Y H:i T', $this->platform->enableUntil); - } else { - $enabled = ', but your access was set to end at ' . date('j F Y H:i T', $this->platform->enableUntil); - } - } - } - $html = <<< EOD - - - - - LTI Tool registration - - - - -

{$toolConfig['client_name']} registration

- -EOD; - if ($this->ok) { - $html .= <<< EOD -

- The tool registration was successful{$enabled}. -

-

- -

- -EOD; - } else { - $html .= <<< EOD -

- Sorry, the registration was not successful: {$this->reason} -

- -EOD; - } - $html .= <<< EOD - - -EOD; - $this->output = $html; - } - - /** - * Load the tool from the database by its consumer key. - * @param string|null $key Consumer key - * @param DataConnector|null $dataConnector A data connector object - * @param bool $autoEnable true if the tool is to be enabled automatically (optional, default is false) - * @return Tool The tool object - */ - public static function fromConsumerKey(string $key = null, DataConnector $dataConnector = null, bool $autoEnable = false): Tool - { - $tool = new static($dataConnector); - $tool->key = $key; - if (!empty($dataConnector)) { - $ok = $dataConnector->loadTool($tool); - if ($ok && $autoEnable) { - $tool->enabled = true; - } - } - - return $tool; - } - - /** - * Load the tool from the database by its initiate login URL. - * @param string $initiateLoginUrl The initiate login URL - * @param DataConnector|null $dataConnector A data connector object - * @param bool $autoEnable True if the tool is to be enabled automatically (optional, default is false) - * @return Tool The tool object - */ - public static function fromInitiateLoginUrl(string $initiateLoginUrl, DataConnector $dataConnector = null, bool $autoEnable = false): Tool - { - $tool = new static($dataConnector); - $tool->initiateLoginUrl = $initiateLoginUrl; - if ($dataConnector->loadTool($tool)) { - if ($autoEnable) { - $tool->enabled = true; - } - } - - return $tool; - } - - /** - * Load the tool from the database by its record ID. - * @param string $id The tool record ID - * @param DataConnector $dataConnector A data connector object - * @return Tool The tool object - */ - public static function fromRecordId(string $id, DataConnector $dataConnector): Tool - { - $tool = new static($dataConnector); - $tool->setRecordId($id); - $dataConnector->loadTool($tool); - - return $tool; - } - - ### - ### PRIVATE METHODS - ### - - /** - * Perform the result of an action. - * - * This function may redirect the user to another URL rather than returning a value. - * - * returns string Output to be displayed (redirection, or display HTML or message) //UK: erased @return - */ - private function result(): void - { - if (!$this->ok) { - $this->message = self::CONNECTION_ERROR_MESSAGE . ' ' . $this->reason; - $this->onError(); - } - if (!$this->ok) { - // If not valid, return an error message to the platform if a return URL is provided - if (!empty($this->returnUrl)) { - $errorUrl = $this->returnUrl; - if (!is_null($this->platform) && isset($this->messageParameters['lti_message_type']) && - (($this->messageParameters['lti_message_type'] === 'ContentItemSelectionRequest') || - ($this->messageParameters['lti_message_type'] === 'ContentItemUpdateRequest'))) { - $formParams = array(); - if ($this->debugMode && !is_null($this->reason)) { - $formParams['lti_errormsg'] = "Debug error: {$this->reason}"; - } else { - $formParams['lti_errormsg'] = $this->message; - if (!is_null($this->reason)) { - $formParams['lti_errorlog'] = "Debug error: {$this->reason}"; - } - } - if (isset($this->messageParameters['data'])) { - $formParams['data'] = $this->messageParameters['data']; - } - $this->version = (isset($this->messageParameters['lti_version'])) ? $this->messageParameters['lti_version'] : Util::LTI_VERSION1; - $page = $this->sendMessage($errorUrl, 'ContentItemSelection', $formParams); - echo $page; - } else { - if (strpos($errorUrl, '?') === false) { - $errorUrl .= '?'; - } else { - $errorUrl .= '&'; - } - if ($this->debugMode && !is_null($this->reason)) { - $errorUrl .= 'lti_errormsg=' . urlencode("Debug error: $this->reason"); - } else { - $errorUrl .= 'lti_errormsg=' . urlencode($this->message); - if (!is_null($this->reason)) { - $errorUrl .= '<i_errorlog=' . urlencode("Debug error: $this->reason"); - } - } - header("Location: {$errorUrl}"); - } - exit; - } else { - if (!is_null($this->errorOutput)) { - echo $this->errorOutput; - } elseif ($this->debugMode && !empty($this->reason)) { - echo "Debug error: {$this->reason}"; - } else { - echo "Error: {$this->message}"; - } - exit; - } - } elseif (!is_null($this->redirectUrl)) { - header("Location: {$this->redirectUrl}"); - exit; - } elseif (!is_null($this->output)) { - echo $this->output; - exit; - } - } - - /** - * Check the authenticity of the LTI message. - * The platform, resource link and user objects will be initialised if the request is valid. - * @param bool $strictMode True if full compliance with the LTI specification is required - * @return bool True if the request has been successfully validated. - */ - private function authenticate(bool $strictMode): bool - { - $doSavePlatform = false; - $this->ok = $this->checkMessage(); - if ($this->ok && $strictMode && !empty($this->jwt) && !empty($this->jwt->hasJwt())) { - if (!empty($this->jwt->getClaim('https://purl.imsglobal.org/spec/lti/claim/context', '')) && - empty($this->messageParameters['context_id'])) { - $this->ok = false; - $this->reason = 'Missing id property in https://purl.imsglobal.org/spec/lti/claim/context claim'; - } elseif (!empty($this->jwt->getClaim('https://purl.imsglobal.org/spec/lti/claim/tool_platform', '')) && - empty($this->messageParameters['tool_consumer_instance_guid'])) { - $this->ok = false; - $this->reason = 'Missing guid property in https://purl.imsglobal.org/spec/lti/claim/tool_platform claim'; - } - } - if ($this->ok) { - if ($this->messageParameters['lti_message_type'] === 'basic-lti-launch-request') { - $this->ok = isset($this->messageParameters['resource_link_id']) && (strlen(trim($this->messageParameters['resource_link_id'])) > 0); - if (!$this->ok) { - $this->reason = 'Missing resource link ID.'; - } - if ($this->ok && ($this->messageParameters['lti_version'] === Util::LTI_VERSION1P3)) { - $this->ok = isset($this->messageParameters['roles']); - if (!$this->ok) { - $this->reason = 'Missing roles parameter.'; - } - } - } elseif (($this->messageParameters['lti_message_type'] === 'ContentItemSelectionRequest') || - ($this->messageParameters['lti_message_type'] === 'ContentItemUpdateRequest')) { - $isUpdate = ($this->messageParameters['lti_message_type'] === 'ContentItemUpdateRequest'); - $mediaTypes = array(); - $contentTypes = array(); - $fileTypes = array(); - if (isset($this->messageParameters['accept_media_types']) && (strlen(trim($this->messageParameters['accept_media_types'])) > 0)) { - $mediaTypes = array_filter( - explode(',', str_replace(' ', '', $this->messageParameters['accept_media_types'])), - 'strlen' - ); - } - $this->ok = (count($mediaTypes) > 0) || ($this->messageParameters['lti_version'] === Util::LTI_VERSION1P3); - if (!$this->ok) { - $this->reason = 'No content types specified.'; - } elseif ($isUpdate) { - if ($this->messageParameters['lti_version'] !== Util::LTI_VERSION1P3) { - if (!$this->checkValue( - $this->messageParameters['accept_media_types'], - array(Item::LTI_LINK_MEDIA_TYPE, Item::LTI_ASSIGNMENT_MEDIA_TYPE), - 'Invalid value in accept_media_types parameter: \'%s\'.', - $strictMode, - true - )) { - $this->ok = false; - } - } elseif (!$this->checkValue( - $this->messageParameters['accept_types'], - array(Item::TYPE_LTI_LINK, Item::TYPE_LTI_ASSIGNMENT), - 'Invalid value in accept_types parameter: \'%s\'.', - $strictMode, - true - )) { - $this->ok = false; - } - } - if ($this->ok) { - $mediaTypes = array_unique($mediaTypes); - foreach ($mediaTypes as $mediaType) { - if (strpos($mediaType, 'application/vnd.ims.lti.') !== 0) { - $fileTypes[] = $mediaType; - } - if (($mediaType === 'text/html') || ($mediaType === '*/*')) { - $contentTypes[] = Item::TYPE_LINK; - $contentTypes[] = Item::TYPE_HTML; - } elseif ((strpos($mediaType, 'image/') === 0) || ($mediaType === '*/*')) { - $contentTypes[] = Item::TYPE_IMAGE; - } elseif ($mediaType === Item::LTI_LINK_MEDIA_TYPE) { - $contentTypes[] = Item::TYPE_LTI_LINK; - } elseif ($mediaType === Item::LTI_ASSIGNMENT_MEDIA_TYPE) { - $contentTypes[] = Item::TYPE_LTI_ASSIGNMENT; - } - } - if (!empty($fileTypes)) { - $contentTypes[] = Item::TYPE_FILE; - } - $contentTypes = array_unique($contentTypes); - } - if ($this->ok) { - if (isset($this->messageParameters['accept_presentation_document_targets']) && - (strlen(trim($this->messageParameters['accept_presentation_document_targets'])) > 0)) { - $documentTargets = array_filter(explode( - ',', - str_replace(' ', '', $this->messageParameters['accept_presentation_document_targets']) - ), 'strlen'); - $documentTargets = array_unique($documentTargets); - $this->ok = count($documentTargets) > 0; - if (!$this->ok) { - $this->reason = 'Missing or empty accept_presentation_document_targets parameter.'; - } else { - if (empty($this->jwt) || !$this->jwt->hasJwt()) { - $permittedTargets = array('embed', 'frame', 'iframe', 'window', 'popup', 'overlay', 'none'); - } else { // JWT - $permittedTargets = array('embed', 'iframe', 'window'); - } - foreach ($documentTargets as $documentTarget) { - if (!$this->checkValue( - $documentTarget, - $permittedTargets, - 'Invalid value in accept_presentation_document_targets parameter: \'%s\'.', - $strictMode, - true - )) { - $this->ok = false; - break; - } - } - if ($this->ok) { - $this->documentTargets = $documentTargets; - } - } - } else { - $this->ok = false; - $this->reason = 'No accept_presentation_document_targets parameter found.'; - } - } - if ($this->ok) { - $this->ok = !empty($this->messageParameters['content_item_return_url']); - if (!$this->ok) { - $this->reason = 'Missing content_item_return_url parameter.'; - } else { - $this->mediaTypes = $mediaTypes; - $this->contentTypes = $contentTypes; - $this->fileTypes = $fileTypes; - } - } - } elseif ($this->messageParameters['lti_message_type'] === 'ToolProxyRegistrationRequest') { - $this->ok = ((isset($this->messageParameters['reg_key']) && (strlen(trim($this->messageParameters['reg_key'])) > 0)) && (isset($this->messageParameters['reg_password']) && (strlen(trim($this->messageParameters['reg_password'])) > - 0)) && (isset($this->messageParameters['tc_profile_url']) && (strlen(trim($this->messageParameters['tc_profile_url'])) > - 0)) && (isset($this->messageParameters['launch_presentation_return_url']) && (strlen(trim($this->messageParameters['launch_presentation_return_url'])) > 0))); - if ($this->debugMode && !$this->ok) { - $this->reason = 'Missing message parameters.'; - } - } elseif ($this->messageParameters['lti_message_type'] === 'LtiStartProctoring') { - $this->ok = isset($this->messageParameters['resource_link_id']) && (strlen(trim($this->messageParameters['resource_link_id'])) > 0); - if (!$this->ok) { - $this->reason = 'Missing resource link ID.'; - } else { - $this->ok = isset($this->messageParameters['custom_ap_attempt_number']) && (strlen(trim($this->messageParameters['custom_ap_attempt_number'])) > 0) && - is_numeric($this->messageParameters['custom_ap_attempt_number']); - if (!$this->ok) { - $this->reason = 'Missing or invalid value for attempt number.'; - } - } - if ($this->ok) { - $this->ok = isset($this->messageParameters['user_id']) && (strlen(trim($this->messageParameters['user_id'])) > 0); - if (!$this->ok) { - $this->reason = 'Missing user ID.'; - } - } - } - } - $now = time(); - // Check consumer key - if ($this->ok && ($this->messageParameters['lti_message_type'] !== 'ToolProxyRegistrationRequest')) { - $this->ok = isset($this->messageParameters['oauth_consumer_key']); - if (!$this->ok) { - $this->reason = 'Missing consumer key.'; - } - if ($this->ok) { - $this->ok = !is_null($this->platform->created); - if (!$this->ok) { - if (empty($this->jwt) || !$this->jwt->hasJwt()) { - $this->reason = "Consumer key not recognised: {$this->messageParameters['oauth_consumer_key']}"; - } else { - $this->reason = "Platform not recognised (Platform ID | Client ID | Deployment ID): {$this->messageParameters['platform_id']} | {$this->messageParameters['oauth_consumer_key']} | {$this->messageParameters['deployment_id']}"; - } - } - } - if ($this->ok) { - if ($this->messageParameters['oauth_signature_method'] !== $this->platform->signatureMethod) { - $this->platform->signatureMethod = $this->messageParameters['oauth_signature_method']; - $doSavePlatform = true; - } - $today = date('Y-m-d', $now); - if (is_null($this->platform->lastAccess)) { - $doSavePlatform = true; - } else { - $last = date('Y-m-d', $this->platform->lastAccess); - $doSavePlatform = $doSavePlatform || ($last !== $today); - } - $this->platform->lastAccess = $now; - $this->ok = $this->verifySignature(); - } - if ($this->ok) { - if ($this->platform->protected) { - if (!is_null($this->platform->consumerGuid)) { - $this->ok = empty($this->messageParameters['tool_consumer_instance_guid']) || ($this->platform->consumerGuid === $this->messageParameters['tool_consumer_instance_guid']); - if (!$this->ok) { - $this->reason = 'Request is from an invalid platform.'; - } - } else { - $this->ok = isset($this->messageParameters['tool_consumer_instance_guid']); - if (!$this->ok) { - $this->reason = 'A platform GUID must be included in the launch request.'; - } - } - } - if ($this->ok) { - $this->ok = $this->platform->enabled; - if (!$this->ok) { - $this->reason = 'Platform has not been enabled by the tool.'; - } - } - if ($this->ok) { - $this->ok = is_null($this->platform->enableFrom) || ($this->platform->enableFrom <= $now); - if ($this->ok) { - $this->ok = is_null($this->platform->enableUntil) || ($this->platform->enableUntil > $now); - if (!$this->ok) { - $this->reason = 'Platform access has expired.'; - } - } else { - $this->reason = 'Platform access is not yet available.'; - } - } - } - // Validate other message parameter values - if ($this->ok) { - if (($this->messageParameters['lti_message_type'] === 'ContentItemSelectionRequest') || - ($this->messageParameters['lti_message_type'] === 'ContentItemUpdateRequest')) { - $isUpdate = ($this->messageParameters['lti_message_type'] === 'ContentItemUpdateRequest'); - if (isset($this->messageParameters['accept_unsigned'])) { - $this->ok = $this->checkValue( - $this->messageParameters['accept_unsigned'], - array('true', 'false'), - 'Invalid value for accept_unsigned parameter: \'%s\'.', - $strictMode - ); - } - if ($this->ok && isset($this->messageParameters['accept_multiple'])) { - if (!$isUpdate) { - $this->ok = $this->checkValue( - $this->messageParameters['accept_multiple'], - array('true', 'false'), - 'Invalid value for accept_multiple parameter: \'%s\'.', - $strictMode - ); - } else { - $this->ok = $this->checkValue( - $this->messageParameters['accept_multiple'], - array('false'), - 'Invalid value for accept_multiple parameter: \'%s\'.', - $strictMode - ); - } - } - if ($this->ok && isset($this->messageParameters['accept_copy_advice'])) { - if (!$isUpdate) { - $this->ok = $this->checkValue( - $this->messageParameters['accept_copy_advice'], - array('true', 'false'), - 'Invalid value for accept_copy_advice parameter: \'%s\'.', - $strictMode - ); - } else { - $this->ok = $this->checkValue( - $this->messageParameters['accept_copy_advice'], - array('false'), - 'Invalid value for accept_copy_advice parameter: \'%s\'.', - $strictMode - ); - } - } - if ($this->ok && isset($this->messageParameters['auto_create'])) { - $this->ok = $this->checkValue( - $this->messageParameters['auto_create'], - array('true', 'false'), - 'Invalid value for auto_create parameter: \'%s\'.', - $strictMode - ); - } - if ($this->ok && isset($this->messageParameters['can_confirm'])) { - $this->ok = $this->checkValue( - $this->messageParameters['can_confirm'], - array('true', 'false'), - 'Invalid value for can_confirm parameter: \'%s\'.', - $strictMode - ); - } - } - if ($this->ok && isset($this->messageParameters['launch_presentation_document_target'])) { - $this->ok = $this->checkValue( - $this->messageParameters['launch_presentation_document_target'], - array('embed', 'frame', 'iframe', 'window', 'popup', 'overlay'), - 'Invalid value for launch_presentation_document_target parameter: \'%s\'.', - $strictMode, - true - ); - if ($this->ok && ($this->messageParameters['lti_message_type'] === 'LtiStartProctoring') && - ($this->messageParameters['launch_presentation_document_target'] !== 'window')) { - $this->ok = !isset($this->messageParameters['launch_presentation_height']) && - !isset($this->messageParameters['launch_presentation_width']); - if (!$this->ok) { - $this->reason = 'Height and width parameters must only be included for the window document target.'; - } - } - } - } - } - - if ($this->ok && ($this->messageParameters['lti_message_type'] === 'ToolProxyRegistrationRequest')) { - $this->ok = $this->messageParameters['lti_version'] === Util::LTI_VERSION2; - if (!$this->ok) { - $this->reason = 'Invalid lti_version parameter.'; - } - if ($this->ok) { - $url = $this->messageParameters['tc_profile_url']; - if (strpos($url, '?') === false) { - $url .= '?'; - } else { - $url .= '&'; - } - $url .= 'lti_version=' . Util::LTI_VERSION2; - $http = new HttpMessage($url, 'GET', null, 'Accept: application/vnd.ims.lti.v2.toolconsumerprofile+json'); - $this->ok = $http->send(); - if (!$this->ok) { - $this->reason = 'Platform profile not accessible.'; - } else { - $tcProfile = json_decode($http->response); - $this->ok = !is_null($tcProfile); - if (!$this->ok) { - $this->reason = 'Invalid JSON in platform profile.'; - } - } - } - // Check for required capabilities - if ($this->ok) { - $this->platform = Platform::fromConsumerKey($this->messageParameters['reg_key'], $this->dataConnector); - $this->platform->profile = $tcProfile; - $capabilities = $this->platform->profile->capability_offered; - $missing = array(); - foreach ($this->resourceHandlers as $resourceHandler) { - foreach ($resourceHandler->requiredMessages as $message) { - if (!in_array($message->type, $capabilities)) { - $missing[$message->type] = true; - } - } - } - foreach ($this->constraints as $name => $constraint) { - if ($constraint['required']) { - if (empty(array_intersect( - $capabilities, - array_keys(array_intersect(self::$CUSTOM_SUBSTITUTION_VARIABLES, array($name))) - ))) { - $missing[$name] = true; - } - } - } - if (!empty($missing)) { - ksort($missing); - $this->reason = 'Required capability not offered - \'' . implode('\', \'', array_keys($missing)) . '\''; - $this->ok = false; - } - } - // Check for required services - if ($this->ok) { - foreach ($this->requiredServices as $service) { - foreach ($service->formats as $format) { - if (!$this->findService($format, $service->actions)) { - if ($this->ok) { - $this->reason = 'Required service(s) not offered - '; - $this->ok = false; - } else { - $this->reason .= ', '; - } - $this->reason .= "'{$format}' [" . implode(', ', $service->actions) . ']'; - } - } - } - } - if ($this->ok) { - if ($this->messageParameters['lti_message_type'] === 'ToolProxyRegistrationRequest') { - $this->platform->profile = $tcProfile; - $this->platform->secret = $this->messageParameters['reg_password']; - $this->platform->ltiVersion = $this->messageParameters['lti_version']; - $this->platform->name = $tcProfile->product_instance->service_owner->service_owner_name->default_value; - $this->platform->consumerName = $this->platform->name; - $this->platform->consumerVersion = "{$tcProfile->product_instance->product_info->product_family->code}-{$tcProfile->product_instance->product_info->product_version}"; - $this->platform->consumerGuid = $tcProfile->product_instance->guid; - $this->platform->enabled = true; - $this->platform->protected = true; - $doSavePlatform = true; - } - } - } elseif ($this->ok && !empty($this->messageParameters['custom_tc_profile_url']) && empty($this->platform->profile)) { - $url = $this->messageParameters['custom_tc_profile_url']; - if (strpos($url, '?') === false) { - $url .= '?'; - } else { - $url .= '&'; - } - $url .= 'lti_version=' . $this->messageParameters['lti_version']; - $http = new HttpMessage($url, 'GET', null, 'Accept: application/vnd.ims.lti.v2.toolconsumerprofile+json'); - if ($http->send()) { - $tcProfile = json_decode($http->response); - if (!is_null($tcProfile)) { - $this->platform->profile = $tcProfile; - $doSavePlatform = true; - } - } - } - - if ($this->ok) { - // Check if a relaunch is being requested - if (isset($this->messageParameters['relaunch_url'])) { - if (empty($this->messageParameters['platform_state'])) { - $this->ok = false; - $this->reason = 'Missing or empty platform_state parameter.'; - } else { - $this->sendRelaunchRequest(); - } - } else { - // Validate message parameter constraints - $invalidParameters = array(); - foreach ($this->constraints as $name => $constraint) { - if (empty($constraint['messages']) || in_array( - $this->messageParameters['lti_message_type'], - $constraint['messages'] - )) { - $ok = true; - if ($constraint['required']) { - if (!isset($this->messageParameters[$name]) || (strlen(trim($this->messageParameters[$name])) <= 0)) { - $invalidParameters[] = "{$name} (missing)"; - $ok = false; - } - } - if ($ok && !is_null($constraint['max_length']) && isset($this->messageParameters[$name])) { - if (strlen(trim($this->messageParameters[$name])) > $constraint['max_length']) { - $invalidParameters[] = "{$name} (too long)"; - } - } - } - } - if (count($invalidParameters) > 0) { - $this->ok = false; - if (empty($this->reason)) { - $this->reason = 'Invalid parameter(s): ' . implode(', ', $invalidParameters) . '.'; - } - } - - if ($this->ok) { - // Set the request context - $contextId = ''; - //UK: Check if necessary - if ($this->hasConfiguredApiHook(self::$CONTEXT_ID_HOOK, $this->platform->getFamilyCode(), $this)) { - $className = $this->getApiHook(self::$CONTEXT_ID_HOOK, $this->platform->getFamilyCode()); - $tpHook = new $className($this); - $contextId = $tpHook->getContextId(); - } - if (empty($contextId) && isset($this->messageParameters['context_id'])) { - $contextId = trim($this->messageParameters['context_id']); - } - if (!empty($contextId)) { - $this->context = Context::fromPlatform($this->platform, $contextId); - $title = ''; - if (isset($this->messageParameters['context_title'])) { - $title = trim($this->messageParameters['context_title']); - } - if (empty($title)) { - $title = "Course {$this->context->getId()}"; - } - $this->context->title = $title; - if (isset($this->messageParameters['context_type'])) { - $this->context->type = trim($this->messageParameters['context_type']); - if (strpos($this->context->type, 'http://purl.imsglobal.org/vocab/lis/v2/course#') === 0) { - $this->context->type = substr($this->context->type, 46); - } - } - } - - // Set the request resource link - if (isset($this->messageParameters['resource_link_id'])) { - $contentItemId = ''; - if (isset($this->messageParameters['custom_content_item_id'])) { - $contentItemId = $this->messageParameters['custom_content_item_id']; - } - if (empty($this->context)) { - $this->resourceLink = ResourceLink::fromPlatform( - $this->platform, - trim($this->messageParameters['resource_link_id']), - $contentItemId - ); - } else { - $this->resourceLink = ResourceLink::fromContext( - $this->context, - trim($this->messageParameters['resource_link_id']), - $contentItemId - ); - } - $title = ''; - if (isset($this->messageParameters['resource_link_title'])) { - $title = trim($this->messageParameters['resource_link_title']); - } - if (empty($title)) { - $title = "Resource {$this->resourceLink->getId()}"; - } - $this->resourceLink->title = $title; - } - // Delete any existing custom parameters - foreach ($this->platform->getSettings() as $name => $value) { - if ((strpos($name, 'custom_') === 0) && (!in_array($name, self::$LTI_RETAIN_SETTING_NAMES))) { - $this->platform->setSetting($name); - $doSavePlatform = true; - } - } - if (!empty($this->context)) { - foreach ($this->context->getSettings() as $name => $value) { - if ((strpos($name, 'custom_') === 0) && (!in_array($name, self::$LTI_RETAIN_SETTING_NAMES))) { - $this->context->setSetting($name); - } - } - } - if (!empty($this->resourceLink)) { - foreach ($this->resourceLink->getSettings() as $name => $value) { - if ((strpos($name, 'custom_') === 0) && (!in_array($name, self::$LTI_RETAIN_SETTING_NAMES))) { - $this->resourceLink->setSetting($name); - } - } - } - // Save LTI parameters - foreach (self::$LTI_CONSUMER_SETTING_NAMES as $name) { - if (isset($this->messageParameters[$name])) { - $this->platform->setSetting($name, $this->messageParameters[$name]); - } elseif (!in_array($name, self::$LTI_RETAIN_SETTING_NAMES)) { - $this->platform->setSetting($name); - } - } - if (!empty($this->context)) { - foreach (self::$LTI_CONTEXT_SETTING_NAMES as $name) { - if (isset($this->messageParameters[$name])) { - $this->context->setSetting($name, $this->messageParameters[$name]); - } elseif (!in_array($name, self::$LTI_RETAIN_SETTING_NAMES)) { - $this->context->setSetting($name); - } - } - } - if (!empty($this->resourceLink)) { - foreach (self::$LTI_RESOURCE_LINK_SETTING_NAMES as $name) { - if (isset($this->messageParameters[$name])) { - $this->resourceLink->setSetting($name, $this->messageParameters[$name]); - } elseif (!in_array($name, self::$LTI_RETAIN_SETTING_NAMES)) { - $this->resourceLink->setSetting($name); - } - } - } - // Save other custom parameters at all levels - foreach ($this->messageParameters as $name => $value) { - if ((strpos($name, 'custom_') === 0) && !in_array( - $name, - array_merge( - self::$LTI_CONSUMER_SETTING_NAMES, - self::$LTI_CONTEXT_SETTING_NAMES, - self::$LTI_RESOURCE_LINK_SETTING_NAMES - ) - )) { - $this->platform->setSetting($name, $value); - if (!empty($this->context)) { - $this->context->setSetting($name, $value); - } - if (!empty($this->resourceLink)) { - $this->resourceLink->setSetting($name, $value); - } - } - } - - // Set the user instance - $userId = ''; - if ($this->hasConfiguredApiHook(self::$USER_ID_HOOK, $this->platform->getFamilyCode(), $this)) { - $className = $this->getApiHook(self::$USER_ID_HOOK, $this->platform->getFamilyCode()); - $tpHook = new $className($this); - $userId = $tpHook->getUserId(); - } - if (empty($userId) && isset($this->messageParameters['user_id'])) { - $userId = trim($this->messageParameters['user_id']); - } - - $this->userResult = UserResult::fromResourceLink($this->resourceLink, $userId); - - // Set the user name - $firstname = (isset($this->messageParameters['lis_person_name_given'])) ? $this->messageParameters['lis_person_name_given'] : ''; - $lastname = (isset($this->messageParameters['lis_person_name_family'])) ? $this->messageParameters['lis_person_name_family'] : ''; - $fullname = (isset($this->messageParameters['lis_person_name_full'])) ? $this->messageParameters['lis_person_name_full'] : ''; - $this->userResult->setNames($firstname, $lastname, $fullname); - - // Set the sourcedId - if (isset($this->messageParameters['lis_person_sourcedid'])) { - $this->userResult->sourcedId = $this->messageParameters['lis_person_sourcedid']; - } - - // Set the username - if (isset($this->messageParameters['ext_username'])) { - $this->userResult->username = $this->messageParameters['ext_username']; - } elseif (isset($this->messageParameters['ext_user_username'])) { - $this->userResult->username = $this->messageParameters['ext_user_username']; - } elseif (isset($this->messageParameters['custom_username'])) { - $this->userResult->username = $this->messageParameters['custom_username']; - } elseif (isset($this->messageParameters['custom_user_username'])) { - $this->userResult->username = $this->messageParameters['custom_user_username']; - } - - // Set the user email - $email = (isset($this->messageParameters['lis_person_contact_email_primary'])) ? $this->messageParameters['lis_person_contact_email_primary'] : ''; - $this->userResult->setEmail($email, $this->defaultEmail); - - // Set the user image URI - if (isset($this->messageParameters['user_image'])) { - $this->userResult->image = $this->messageParameters['user_image']; - } - - // Set the user roles - if (isset($this->messageParameters['roles'])) { - $this->userResult->roles = self::parseRoles( - $this->messageParameters['roles'], - $this->messageParameters['lti_version'] - ); - } - - // Initialise the platform and check for changes - $this->platform->defaultEmail = $this->defaultEmail; - if ($this->platform->ltiVersion !== $this->messageParameters['lti_version']) { - $this->platform->ltiVersion = $this->messageParameters['lti_version']; - $doSavePlatform = true; - } - if (isset($this->messageParameters['deployment_id'])) { - $this->platform->deploymentId = $this->messageParameters['deployment_id']; - } - if (isset($this->messageParameters['tool_consumer_instance_name'])) { - if ($this->platform->consumerName !== $this->messageParameters['tool_consumer_instance_name']) { - $this->platform->consumerName = $this->messageParameters['tool_consumer_instance_name']; - $doSavePlatform = true; - } - } - if (isset($this->messageParameters['tool_consumer_info_product_family_code'])) { - $version = $this->messageParameters['tool_consumer_info_product_family_code']; - if (isset($this->messageParameters['tool_consumer_info_version'])) { - $version .= "-{$this->messageParameters['tool_consumer_info_version']}"; - } - // do not delete any existing consumer version if none is passed - if ($this->platform->consumerVersion !== $version) { - $this->platform->consumerVersion = $version; - $doSavePlatform = true; - } - } elseif (isset($this->messageParameters['ext_lms']) && ($this->platform->consumerName !== $this->messageParameters['ext_lms'])) { - $this->platform->consumerVersion = $this->messageParameters['ext_lms']; - $doSavePlatform = true; - } - if (isset($this->messageParameters['tool_consumer_instance_guid'])) { - if (is_null($this->platform->consumerGuid)) { - $this->platform->consumerGuid = $this->messageParameters['tool_consumer_instance_guid']; - $doSavePlatform = true; - } elseif (!$this->platform->protected && ($this->platform->consumerGuid !== $this->messageParameters['tool_consumer_instance_guid'])) { - $this->platform->consumerGuid = $this->messageParameters['tool_consumer_instance_guid']; - $doSavePlatform = true; - } - } - if (isset($this->messageParameters['launch_presentation_css_url'])) { - if ($this->platform->cssPath !== $this->messageParameters['launch_presentation_css_url']) { - $this->platform->cssPath = $this->messageParameters['launch_presentation_css_url']; - $doSavePlatform = true; - } - } elseif (isset($this->messageParameters['ext_launch_presentation_css_url']) && ($this->platform->cssPath !== $this->messageParameters['ext_launch_presentation_css_url'])) { - $this->platform->cssPath = $this->messageParameters['ext_launch_presentation_css_url']; - $doSavePlatform = true; - } elseif (!empty($this->platform->cssPath)) { - $this->platform->cssPath = null; - $doSavePlatform = true; - } - } - - // Persist changes to platform - if ($doSavePlatform) { - $this->platform->save(); - } - - if ($this->ok) { - // Persist changes to cpntext - if (isset($this->context)) { - $this->context->save(); - } - - if (isset($this->resourceLink)) { - // Persist changes to resource link - $this->resourceLink->save(); - - // Persist changes to user instnce - $this->userResult->setResourceLinkId($this->resourceLink->getRecordId()); - if (isset($this->messageParameters['lis_result_sourcedid'])) { - if ($this->userResult->ltiResultSourcedId !== $this->messageParameters['lis_result_sourcedid']) { - $this->userResult->ltiResultSourcedId = $this->messageParameters['lis_result_sourcedid']; - $this->userResult->save(); - } - } elseif ($this->userResult->isLearner()) { // Ensure all learners are recorded in case Assignment and Grade services are used - $this->userResult->ltiResultSourcedId = ''; - $this->userResult->save(); - } - - // Check if a share arrangement is in place for this resource link - $this->ok = $this->checkForShare(); - } - } - } - } - return $this->ok; - } - - /** - * Check if a share arrangement is in place. - * - * @return bool True if no error is reported - */ - private function checkForShare(): bool - { - $ok = true; - $doSaveResourceLink = true; - - $id = $this->resourceLink->primaryResourceLinkId; - - $shareRequest = isset($this->messageParameters['custom_share_key']) && !empty($this->messageParameters['custom_share_key']); - if ($shareRequest) { - if (!$this->allowSharing) { - $ok = false; - $this->reason = 'Your sharing request has been refused because sharing is not being permitted.'; - } else { - // Check if this is a new share key - $shareKey = new ResourceLinkShareKey($this->resourceLink, $this->messageParameters['custom_share_key']); - if (!is_null($shareKey->resourceLinkId)) { - // Update resource link with sharing primary resource link details - $id = $shareKey->resourceLinkId; - $ok = ($id !== $this->resourceLink->getRecordId()); - if ($ok) { - $this->resourceLink->primaryResourceLinkId = $id; - $this->resourceLink->shareApproved = $shareKey->autoApprove; - $ok = $this->resourceLink->save(); - if ($ok) { - $doSaveResourceLink = false; - $this->userResult->getResourceLink()->primaryResourceLinkId = $id; - $this->userResult->getResourceLink()->shareApproved = $shareKey->autoApprove; - $this->userResult->getResourceLink()->updated = time(); - // Remove share key - $shareKey->delete(); - } else { - $this->reason = 'An error occurred initialising your share arrangement.'; - } - } else { - $this->reason = 'It is not possible to share your resource link with yourself.'; - } - } - if ($ok) { - $ok = !is_null($id); - if (!$ok) { - $this->reason = 'You have requested to share a resource link but none is available.'; - } else { - $ok = (!is_null($this->userResult->getResourceLink()->shareApproved) && $this->userResult->getResourceLink()->shareApproved); - if (!$ok) { - $this->reason = 'Your share request is waiting to be approved.'; - } - } - } - } - } else { - // Check no share is in place - $ok = is_null($id); - if (!$ok) { - $this->reason = 'You have not requested to share a resource link but an arrangement is currently in place.'; - } - } - - // Look up primary resource link - if ($ok && !is_null($id)) { - $resourceLink = ResourceLink::fromRecordId($id, $this->dataConnector); - $ok = !is_null($resourceLink->created); - if ($ok) { - if ($doSaveResourceLink) { - $this->resourceLink->save(); - } - $this->resourceLink = $resourceLink; - } else { - $this->reason = 'Unable to load resource link being shared.'; - } - } - - return $ok; - } - - /** - * Generate a form to perform an authentication request. - * @param array $parameters Request parameters - * @return bool True if form was generated - */ - private function sendAuthenticationRequest(array $parameters): bool - { - $clientId = null; - if (isset($parameters['client_id'])) { - $clientId = $parameters['client_id']; - } - $deploymentId = null; - if (isset($parameters['lti_deployment_id'])) { - $deploymentId = $parameters['lti_deployment_id']; - } - $currentLogLevel = Util::$logLevel; - $this->platform = \ilLTIPlatform::fromPlatformId($parameters['iss'], $clientId, $deploymentId, $this->dataConnector); - if ($this->platform->debugMode && ($currentLogLevel < Util::LOGLEVEL_INFO)) { - $this->debugMode = true; - Util::logRequest(); - } - $ok = !is_null($this->platform) && !empty($this->platform->authenticationUrl); - if (!$ok) { - $this->reason = 'Platform not found or no platform authentication request URL.'; - } else { - do { - $nonce = new PlatformNonce($this->platform, Util::getRandomString()); - $ok = !$nonce->load(); - } while (!$ok); - $nonce->expires = time() + 10; // Expire after 10 seconds - $ok = $nonce->save(); - if ($ok) { -// $oauthRequest = OAuth\OAuthRequest::from_request(); - $oauthRequest = LTIOAuth\OAuthRequest::from_request(); - $redirectUri = $oauthRequest->get_normalized_http_url(); - if (!empty($_SERVER['QUERY_STRING'])) { - if ($_SERVER['REQUEST_METHOD'] === 'POST') { - $redirectUri .= "?{$_SERVER['QUERY_STRING']}"; - } else { // Remove all parameters added by platform from query string - $queryString = ''; - $params = explode('&', $_SERVER['QUERY_STRING']); - $ignore = false; // Only include those query parameters which come before any of the standard OpenID Connect ones - foreach ($params as $param) { - $parts = explode('=', $param, 2); - if (in_array( - $parts[0], - array('iss', 'target_link_uri', 'login_hint', 'lti_message_hint', 'client_id', 'lti_deployment_id') - )) { - $ignore = true; - } elseif (!$ignore) { - if ((count($parts) <= 1) || empty($parts[1])) { // Drop equals sign for empty parameters to workaround Canvas bug - $queryString .= "&{$parts[0]}"; - } else { - $queryString .= "&{$parts[0]}={$parts[1]}"; - } - } - } - if (!empty($queryString)) { - $queryString = substr($queryString, 1); - $redirectUri .= "?{$queryString}"; - } - } - } - $params = array( - 'client_id' => $this->platform->clientId, - 'login_hint' => $parameters['login_hint'], - 'nonce' => Util::getRandomString(32), - 'prompt' => 'none', - 'redirect_uri' => $redirectUri, - 'response_mode' => 'form_post', - 'response_type' => 'id_token', - 'scope' => 'openid', - 'state' => $nonce->getValue() - ); - if (isset($parameters['lti_message_hint'])) { - $params['lti_message_hint'] = $parameters['lti_message_hint']; - } - $this->onInitiateLogin($parameters, $params); - if (!Tool::$authenticateUsingGet) { - $this->output = Util::sendForm($this->platform->authenticationUrl, $params); - } else { - Util::redirect($this->platform->authenticationUrl, $params); - } - } else { - $this->reason = 'Unable to generate a state value.'; - } - } - - return $ok; - } - - /** - * Generate a form to perform a relaunch request. - */ - private function sendRelaunchRequest() - { - do { - $nonce = new PlatformNonce($this->platform, Util::getRandomString()); - $ok = !$nonce->load(); - } while (!$ok); - $ok = $nonce->save(); - if ($ok) { - $params = array( - 'tool_state' => $nonce->getValue(), - 'platform_state' => $this->messageParameters['platform_state'] - ); - $params = $this->platform->addSignature($this->messageParameters['relaunch_url'], $params); - $this->output = Util::sendForm($this->messageParameters['relaunch_url'], $params); - } else { - $this->reason = 'Unable to generate a state value.'; - } - } - - /** - * Validate a parameter value from an array of permitted values. - * @param mixed $value Value to be checked - * @param array $values Array of permitted values - * @param string $reason Reason to generate when the value is not permitted - * @param bool $strictMode True if full compliance with the LTI specification is required - * @param bool $ignoreInvalid True if invalid values are to be ignored (optional default is false) - * @return bool True if value is valid - */ - private function checkValue(&$value, array $values, string $reason, bool $strictMode, bool $ignoreInvalid = false): bool - { - $lookupValue = $value; - if (!$strictMode) { - $lookupValue = strtolower($value); - } - $ok = in_array($lookupValue, $values); - if (!$ok && !$strictMode && $ignoreInvalid) { - Util::logInfo(sprintf($reason, $value) . " [Error ignored]"); - $ok = true; - } elseif (!$ok && !empty($reason)) { - $this->reason = sprintf($reason, $value); - } elseif ($lookupValue !== $value) { - Util::logInfo(sprintf($reason, $value) . " [Changed to '{$lookupValue}']"); - $value = $lookupValue; - } - - return $ok; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/User.php b/components/ILIAS/LTI/src/ToolProvider/User.php deleted file mode 100755 index d0d974bf52d9..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/User.php +++ /dev/null @@ -1,288 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class User -{ - /** - * User's first name. - * - * @var string $firstname - */ - public string $firstname = ''; - - /** - * User's last name (surname or family name). - * - * @var string $lastname - */ - public string $lastname = ''; - - /** - * User's fullname. - * - * @var string $fullname - */ - public string $fullname = ''; - - /** - * Allow user name field to be empty? - * - * @var bool $allowEmptyName - */ - public static bool $allowEmptyName = false; - - /** - * User's sourcedId. - * - * @var string $sourcedId - */ - public ?string $sourcedId = null; - - /** - * User's username. - * - * @var string $username - */ - public ?string $username = null; - - /** - * User's email address. - * - * @var string $email - */ - public string $email = ''; - - /** - * User's image URI. - * - * @var string $image - */ - public string $image = ''; - - /** - * Roles for user. - * - * @var array $roles - */ - public array $roles = array(); - - /** - * Groups for user. - * - * @var array $groups - */ - public array $groups = array(); - - /** - * user ID as supplied in the last connection request. - * - * @var string|null $ltiUserId - */ - public ?string $ltiUserId = null; - - /** - * Class constructor. - */ - public function __construct() - { - $this->initialize(); - } - - /** - * Initialise the user. - */ - public function initialize() - { - $this->firstname = ''; - $this->lastname = ''; - $this->fullname = ''; - $this->sourcedId = null; - $this->username = null; - $this->email = ''; - $this->image = ''; - $this->roles = array(); - $this->groups = array(); - } - - /** - * Initialise the user. - * - * Synonym for initialize(). - */ - public function initialise() - { - $this->initialize(); - } - - /** - * Set the user's name. - * @param string $firstname User's first name. - * @param string $lastname User's last name. - * @param string $fullname User's full name. - */ - public function setNames(string $firstname, string $lastname, string $fullname) - { - $names = array(0 => '', 1 => ''); - if (!empty($fullname)) { - $this->fullname = trim($fullname); - $names = preg_split("/[\s]+/", $this->fullname, 2); - } - if (!empty($firstname)) { - $this->firstname = trim($firstname); - $names[0] = $this->firstname; - } elseif (!empty($names[0])) { - $this->firstname = $names[0]; - } elseif (!static::$allowEmptyName) { - $this->firstname = 'User'; - } else { - $this->firstname = ''; - } - if (!empty($lastname)) { - $this->lastname = trim($lastname); - $names[1] = $this->lastname; - } elseif (!empty($names[1])) { - $this->lastname = $names[1]; - } elseif (!static::$allowEmptyName) { - $this->lastname = $this->ltiUserId; - } else { - $this->lastname = ''; - } - if (empty($this->fullname) && (!empty($this->firstname) || !empty($this->lastname))) { - $this->fullname = trim("{$this->firstname} {$this->lastname}"); - } - } - - /** - * Set the user's email address. - * @param string $email Email address value - * @param string|null $defaultEmail Value to use if no email is provided (optional, default is none) - */ - public function setEmail(string $email, string $defaultEmail = null) - { - if (!empty($email)) { - $this->email = $email; - } elseif (!empty($defaultEmail)) { - $this->email = $defaultEmail; - if (substr($this->email, 0, 1) === '@') { - if (!empty($this->username)) { - $this->email = "{$this->username}{$this->email}"; - } else { - $this->email = "{$this->ltiUserId}{$this->email}"; - } - } - } else { - $this->email = ''; - } - } - - /** - * Check if the user is an administrator (at any of the system, institution or context levels). - * - * @return bool True if the user has a role of administrator - */ - public function isAdmin(): bool - { - return $this->hasRole('Administrator') || $this->hasRole('urn:lti:sysrole:ims/lis/SysAdmin') || - $this->hasRole('urn:lti:sysrole:ims/lis/Administrator') || $this->hasRole('urn:lti:instrole:ims/lis/Administrator'); - } - - /** - * Check if the user is staff. - * - * @return bool True if the user has a role of instructor, contentdeveloper or teachingassistant - */ - public function isStaff(): bool - { - return ($this->hasRole('Instructor') || $this->hasRole('ContentDeveloper') || $this->hasRole('TeachingAssistant')); - } - - /** - * Check if the user is a learner. - * - * @return bool True if the user has a role of learner - */ - public function isLearner(): bool - { - return $this->hasRole('Learner'); - } - - ### - ### PRIVATE METHODS - ### - - /** - * Check whether the user has a specified role name. - * @param string $role Name of role - * @return bool True if the user has the specified role - */ - private function hasRole(string $role): bool - { - $ok = in_array($role, $this->roles); - if (!$ok && (strpos($role, 'urn:') !== 0) && (strpos($role, 'http://') !== 0) && (strpos($role, 'https://') !== 0)) { - $role = "urn:lti:role:ims/lis/{$role}"; - $ok = in_array($role, $this->roles); - } - if (!$ok) { - $role2 = null; - $role3 = null; - if (strpos($role, 'urn:') === 0) { - if (strpos($role, 'urn:lti:role:ims/lis/') === 0) { - $role2 = 'http://purl.imsglobal.org/vocab/lis/v2/membership#' . substr($role, 21); - } elseif (strpos($role, 'urn:lti:instrole:ims/lis/') === 0) { - $role2 = 'http://purl.imsglobal.org/vocab/lis/v2/person#' . substr($role, 25); - $role3 = 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#' . substr($role, 25); - } elseif (strpos($role, 'urn:lti:sysrole:ims/lis/') === 0) { - $role2 = 'http://purl.imsglobal.org/vocab/lis/v2/person#' . substr($role, 24); - $role3 = 'http://purl.imsglobal.org/vocab/lis/v2/system/person#' . substr($role, 24); - } - } elseif (strpos($role, 'http://purl.imsglobal.org/vocab/lis/v2/') === 0) { - if (strpos($role, 'http://purl.imsglobal.org/vocab/lis/v2/membership#') === 0) { - $role2 = 'urn:lti:role:ims/lis/' . substr($role, 50); - } elseif (strpos($role, 'http://purl.imsglobal.org/vocab/lis/v2/person#') === 0) { - $role2 = 'urn:lti:instrole:ims/lis/' . substr($role, 46); - $role3 = 'urn:lti:sysrole:ims/lis/' . substr($role, 46); - } elseif (strpos($role, 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#') === 0) { - $role2 = 'urn:lti:instrole:ims/lis/' . substr($role, 58); - $role3 = 'http://purl.imsglobal.org/vocab/lis/v2/person#' . substr($role, 58); - } elseif (strpos($role, 'http://purl.imsglobal.org/vocab/lis/v2/system/person#') === 0) { - $role2 = 'urn:lti:sysrole:ims/lis/' . substr($role, 53); - $role3 = 'http://purl.imsglobal.org/vocab/lis/v2/person#' . substr($role, 53); - } - } - if (!empty($role2)) { - $ok = in_array($role2, $this->roles); - if (!$ok && !empty($role3)) { - $ok = in_array($role3, $this->roles); - } - } - } - - return $ok; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/UserResult.php b/components/ILIAS/LTI/src/ToolProvider/UserResult.php deleted file mode 100755 index 06ede6a19c43..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/UserResult.php +++ /dev/null @@ -1,301 +0,0 @@ - - * @copyright SPV Software Products - * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 - */ -class UserResult extends User -{ - /** - * UserResult's result sourcedid. - * - * @var string|null $ltiResultSourcedId - */ - public ?string $ltiResultSourcedId = null; - - /** - * Date/time the record was created. - * - * @var int|null $created //UK: changed datetime to int - */ - public ?int $created = null; - - /** - * Date/time the record was last updated. - * - * @var int|null $updated //UK: changed datetime to int - */ - public ?int $updated = null; - - /** - * Resource link object. - * - * @var ResourceLink|null $resourceLink - */ - private ?ResourceLink $resourceLink = null; - - /** - * Resource link record ID. - * - * @var int|null $resourceLinkId - */ - private ?int $resourceLinkId = null; - - /** - * UserResult record ID value. - * - * @var int|null $id // UK: Changed from string|null to int|null - */ - private ?int $id = null; - - /** - * Data connector object or string. - * - * @var DataConnector|null $dataConnector - */ - private ?DataConnector $dataConnector = null; - - /** - * Class constructor. - */ - public function __construct() - { - $this->initialize(); - } - - /** - * Initialise the user. - */ - public function initialize() - { - parent::initialize(); - $this->ltiResultSourcedId = null; - $this->created = null; - $this->updated = null; - } - - /** - * Save the user to the database. - * - * @return bool True if the user object was successfully saved - */ - public function save(): bool - { - if (!is_null($this->resourceLinkId)) { - $ok = $this->getDataConnector()->saveUserResult($this); - } else { - $ok = true; - } - - return $ok; - } - - /** - * Delete the user from the database. - * - * @return bool True if the user object was successfully deleted - */ - public function delete(): bool - { - $ok = $this->getDataConnector()->deleteUserResult($this); - - return $ok; - } - - /** - * Get resource link. - * - * @return ResourceLink Resource link object - */ - public function getResourceLink(): ?ResourceLink - { - if (is_null($this->resourceLink) && !is_null($this->resourceLinkId)) { - $this->resourceLink = ResourceLink::fromRecordId($this->resourceLinkId, $this->getDataConnector()); - } - - return $this->resourceLink; - } - - /** - * Set resource link. - * @param ResourceLink $resourceLink Resource link object - */ - public function setResourceLink(ResourceLink $resourceLink) - { - $this->resourceLink = $resourceLink; - } - - /** - * Get record ID of user. - * - * @return int Record ID of user - */ - public function getRecordId(): ?int - { - return $this->id; - } - - /** - * Set record ID of user. - * @param int $id Record ID of user - */ - public function setRecordId(int $id) - { - $this->id = $id; - } - - /** - * Set resource link ID of user. - * @param int $resourceLinkId Resource link ID of user - */ - public function setResourceLinkId(int $resourceLinkId) - { - $this->resourceLink = null; - $this->resourceLinkId = $resourceLinkId; - } - - /** - * Get the data connector. - * - * @return mixed Data connector object or string - */ - public function getDataConnector() - { - return $this->dataConnector; - } - - /** - * Set the data connector. - * @param DataConnector $dataConnector Data connector object - */ - public function setDataConnector(DataConnector $dataConnector) - { - $this->dataConnector = $dataConnector; - } - - /** - * Get the user ID (which may be a compound of the platform and resource link IDs). - * @param int|null $idScope Scope to use for user ID (optional, default is null for consumer default setting) - * @param Platform|null $platform Platform for user (optional) - * @return string UserResult ID value - */ - public function getId(int $idScope = null, Platform $platform = null): string - { - $key = ''; - if (is_null($platform) && !is_null($this->getResourceLink())) { - $platform = $this->getResourceLink()->getPlatform(); - } - if (!is_null($platform)) { - $key = $platform->getId(); - } - if (is_null($idScope) && !is_null($this->getResourceLink())) { - $idScope = $this->resourceLink->getPlatform()->idScope; - } - if (is_null($idScope)) { - $idScope = Tool::ID_SCOPE_ID_ONLY; - } - switch ($idScope) { - case Tool::ID_SCOPE_GLOBAL: - $id = $key . Tool::ID_SCOPE_SEPARATOR . $this->ltiUserId; - break; - case Tool::ID_SCOPE_CONTEXT: - if ($this->resourceLink->getContext() && $this->resourceLink->getContext()->ltiContextId) { - $id = $key . Tool::ID_SCOPE_SEPARATOR . $this->resourceLink->getContext()->ltiContextId; - } - $id .= Tool::ID_SCOPE_SEPARATOR . $this->ltiUserId; - break; - case Tool::ID_SCOPE_RESOURCE: - if (!is_null($this->resourceLink) && !empty($this->resourceLink->ltiResourceLinkId)) { - $id = $key . Tool::ID_SCOPE_SEPARATOR . $this->resourceLink->ltiResourceLinkId; - } - $id .= Tool::ID_SCOPE_SEPARATOR . $this->ltiUserId; - break; - default: - $id = $this->ltiUserId; - break; - } - - return $id; - } - - /** - * Load the user from the database. - * @param int $id Record ID of user - * @param DataConnector $dataConnector Database connection object - * @return UserResult UserResult object - */ - public static function fromRecordId(int $id, DataConnector $dataConnector): UserResult - { - $userresult = new UserResult(); - $userresult->dataConnector = $dataConnector; - $userresult->load($id); - - return $userresult; - } - - /** - * Class constructor from resource link. - * @param ResourceLink $resourceLink ResourceLink object - * @param string $ltiUserId UserResult ID value - * @return UserResult UserResult object - */ - public static function fromResourceLink(ResourceLink $resourceLink, string $ltiUserId): UserResult - { - $userresult = new UserResult(); - $userresult->resourceLink = $resourceLink; - if (!is_null($resourceLink)) { - $userresult->resourceLinkId = $resourceLink->getRecordId(); - $userresult->dataConnector = $resourceLink->getDataConnector(); - } - $userresult->ltiUserId = $ltiUserId; - if (!empty($ltiUserId)) { - $userresult->load(); - } - - return $userresult; - } - - ### - ### PRIVATE METHODS - ### - - /** - * Load the user from the database. - * @param int|null $id Record ID of user (optional, default is null) - * @return bool True if the user object was successfully loaded - */ - private function load(int $id = null): bool - { - $this->initialize(); - $this->id = $id; - $dataConnector = $this->getDataConnector(); - if (!is_null($dataConnector)) { - return $dataConnector->loadUserResult($this); - } - - return false; - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/Util.php b/components/ILIAS/LTI/src/ToolProvider/Util.php deleted file mode 100755 index de5e853081fd..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/Util.php +++ /dev/null @@ -1,615 +0,0 @@ - 'LtiResourceLinkRequest', - 'ContentItemSelectionRequest' => 'LtiDeepLinkingRequest', - 'ContentItemSelection' => 'LtiDeepLinkingResponse', - 'ContentItemUpdateRequest' => 'LtiDeepLinkingUpdateRequest' - ); - - /** - * Mapping for standard message parameters to JWT claim. - */ - public const JWT_CLAIM_MAPPING = array( - 'accept_types' => array('suffix' => 'dl', 'group' => 'deep_linking_settings', 'claim' => 'accept_types', 'isArray' => true), - 'accept_copy_advice' => array('suffix' => 'dl', 'group' => 'deep_linking_settings', 'claim' => 'copyAdvice', 'isBoolean' => true), - 'accept_media_types' => array('suffix' => 'dl', 'group' => 'deep_linking_settings', 'claim' => 'accept_media_types'), - 'accept_multiple' => array('suffix' => 'dl', 'group' => 'deep_linking_settings', 'claim' => 'accept_multiple', 'isBoolean' => true), - 'accept_presentation_document_targets' => array('suffix' => 'dl', 'group' => 'deep_linking_settings', 'claim' => 'accept_presentation_document_targets', 'isArray' => true), - 'accept_unsigned' => array('suffix' => 'dl', 'group' => 'deep_linking_settings', 'claim' => 'accept_unsigned', 'isBoolean' => true), - 'auto_create' => array('suffix' => 'dl', 'group' => 'deep_linking_settings', 'claim' => 'auto_create', 'isBoolean' => true), - 'can_confirm' => array('suffix' => 'dl', 'group' => 'deep_linking_settings', 'claim' => 'can_confirm'), - 'content_item_return_url' => array('suffix' => 'dl', 'group' => 'deep_linking_settings', 'claim' => 'deep_link_return_url'), - 'content_items' => array('suffix' => 'dl', 'group' => '', 'claim' => 'content_items', 'isObject' => true), - 'data' => array('suffix' => 'dl', 'group' => 'deep_linking_settings', 'claim' => 'data'), - 'data.LtiDeepLinkingResponse' => array('suffix' => 'dl', 'group' => '', 'claim' => 'data'), - 'text' => array('suffix' => 'dl', 'group' => 'deep_linking_settings', 'claim' => 'text'), - 'title' => array('suffix' => 'dl', 'group' => 'deep_linking_settings', 'claim' => 'title'), - 'lti_msg' => array('suffix' => 'dl', 'group' => '', 'claim' => 'msg'), - 'lti_errormsg' => array('suffix' => 'dl', 'group' => '', 'claim' => 'errormsg'), - 'lti_log' => array('suffix' => 'dl', 'group' => '', 'claim' => 'log'), - 'lti_errorlog' => array('suffix' => 'dl', 'group' => '', 'claim' => 'errorlog'), - 'context_id' => array('suffix' => '', 'group' => 'context', 'claim' => 'id'), - 'context_label' => array('suffix' => '', 'group' => 'context', 'claim' => 'label'), - 'context_title' => array('suffix' => '', 'group' => 'context', 'claim' => 'title'), - 'context_type' => array('suffix' => '', 'group' => 'context', 'claim' => 'type', 'isArray' => true), - 'lis_course_offering_sourcedid' => array('suffix' => '', 'group' => 'lis', 'claim' => 'course_offering_sourcedid'), - 'lis_course_section_sourcedid' => array('suffix' => '', 'group' => 'lis', 'claim' => 'course_section_sourcedid'), - 'launch_presentation_css_url' => array('suffix' => '', 'group' => 'launch_presentation', 'claim' => 'css_url'), - 'launch_presentation_document_target' => array('suffix' => '', 'group' => 'launch_presentation', 'claim' => 'document_target'), - 'launch_presentation_height' => array('suffix' => '', 'group' => 'launch_presentation', 'claim' => 'height'), - 'launch_presentation_locale' => array('suffix' => '', 'group' => 'launch_presentation', 'claim' => 'locale'), - 'launch_presentation_return_url' => array('suffix' => '', 'group' => 'launch_presentation', 'claim' => 'return_url'), - 'launch_presentation_width' => array('suffix' => '', 'group' => 'launch_presentation', 'claim' => 'width'), - 'lis_person_contact_email_primary' => array('suffix' => '', 'group' => null, 'claim' => 'email'), - 'lis_person_name_family' => array('suffix' => '', 'group' => null, 'claim' => 'family_name'), - 'lis_person_name_full' => array('suffix' => '', 'group' => null, 'claim' => 'name'), - 'lis_person_name_given' => array('suffix' => '', 'group' => null, 'claim' => 'given_name'), - 'lis_person_sourcedid' => array('suffix' => '', 'group' => 'lis', 'claim' => 'person_sourcedid'), - 'user_id' => array('suffix' => '', 'group' => null, 'claim' => 'sub'), - 'user_image' => array('suffix' => '', 'group' => null, 'claim' => 'picture'), - 'roles' => array('suffix' => '', 'group' => '', 'claim' => 'roles', 'isArray' => true), - 'platform_id' => array('suffix' => '', 'group' => null, 'claim' => 'iss'), - 'deployment_id' => array('suffix' => '', 'group' => '', 'claim' => 'deployment_id'), - 'lti_message_type' => array('suffix' => '', 'group' => '', 'claim' => 'message_type'), - 'lti_version' => array('suffix' => '', 'group' => '', 'claim' => 'version'), - 'resource_link_description' => array('suffix' => '', 'group' => 'resource_link', 'claim' => 'description'), - 'resource_link_id' => array('suffix' => '', 'group' => 'resource_link', 'claim' => 'id'), - 'resource_link_title' => array('suffix' => '', 'group' => 'resource_link', 'claim' => 'title'), - 'target_link_uri' => array('suffix' => '', 'group' => '', 'claim' => 'target_link_uri'), - 'tool_consumer_info_product_family_code' => array('suffix' => '', 'group' => 'tool_platform', 'claim' => 'product_family_code'), - 'tool_consumer_info_version' => array('suffix' => '', 'group' => 'tool_platform', 'claim' => 'version'), - 'tool_consumer_instance_contact_email' => array('suffix' => '', 'group' => 'tool_platform', 'claim' => 'contact_email'), - 'tool_consumer_instance_description' => array('suffix' => '', 'group' => 'tool_platform', 'claim' => 'description'), - 'tool_consumer_instance_guid' => array('suffix' => '', 'group' => 'tool_platform', 'claim' => 'guid'), - 'tool_consumer_instance_name' => array('suffix' => '', 'group' => 'tool_platform', 'claim' => 'name'), - 'tool_consumer_instance_url' => array('suffix' => '', 'group' => 'tool_platform', 'claim' => 'url'), - 'tool_state' => array('suffix' => '', 'group' => 'tool', 'claim' => 'state'), - 'custom_context_memberships_v2_url' => array('suffix' => 'nrps', 'group' => 'namesroleservice', 'claim' => 'context_memberships_url'), - 'custom_nrps_versions' => array('suffix' => 'nrps', 'group' => 'namesroleservice', 'claim' => 'service_versions', 'isArray' => true), - 'custom_lineitems_url' => array('suffix' => 'ags', 'group' => 'endpoint', 'claim' => 'lineitems'), - 'custom_lineitem_url' => array('suffix' => 'ags', 'group' => 'endpoint', 'claim' => 'lineitem'), - 'custom_ags_scopes' => array('suffix' => 'ags', 'group' => 'endpoint', 'claim' => 'scope', 'isArray' => true), - 'custom_context_groups_url' => array('suffix' => 'gs', 'group' => 'groupsservice', 'claim' => 'context_groups_url'), - 'custom_context_group_sets_url' => array('suffix' => 'gs', 'group' => 'groupsservice', 'claim' => 'context_group_sets_url'), - 'custom_gs_scopes' => array('suffix' => 'gs', 'group' => 'groupsservice', 'claim' => 'scope', 'isArray' => true), - 'custom_gs_versions' => array('suffix' => 'gs', 'group' => 'groupsservice', 'claim' => 'service_versions', 'isArray' => true), - 'lis_outcome_service_url' => array('suffix' => 'bo', 'group' => 'basicoutcome', 'claim' => 'lis_outcome_service_url'), - 'lis_result_sourcedid' => array('suffix' => 'bo', 'group' => 'basicoutcome', 'claim' => 'lis_result_sourcedid'), - 'custom_ap_attempt_number' => array('suffix' => 'ap', 'group' => '', 'claim' => 'attempt_number', 'isInteger' => true), - 'custom_ap_start_assessment_url' => array('suffix' => 'ap', 'group' => '', 'claim' => 'start_assessment_url'), - 'custom_ap_session_data' => array('suffix' => 'ap', 'group' => '', 'claim' => 'session_data'), - 'custom_ap_acs_actions' => array('suffix' => 'ap', 'group' => 'acs', 'claim' => 'actions', 'isArray' => true), - 'custom_ap_acs_url' => array('suffix' => 'ap', 'group' => 'acs', 'claim' => 'assessment_control_url'), - 'custom_ap_proctoring_settings_data' => array('suffix' => 'ap', 'group' => 'proctoring_settings', 'claim' => 'data'), - 'custom_ap_email_verified' => array('suffix' => '', 'group' => null, 'claim' => 'email_verified', 'isBoolean' => true), - 'custom_ap_verified_user_given_name' => array('suffix' => 'ap', 'group' => 'verified_user', 'claim' => 'given_name'), - 'custom_ap_verified_user_family_name' => array('suffix' => 'ap', 'group' => 'verified_user', 'claim' => 'family_name'), - 'custom_ap_verified_user_full_name' => array('suffix' => 'ap', 'group' => 'verified_user', 'claim' => 'full_name'), - 'custom_ap_verified_user_image' => array('suffix' => 'ap', 'group' => 'verified_user', 'claim' => 'picture'), - 'custom_ap_end_assessment_return' => array('suffix' => 'ap', 'group' => '', 'claim' => 'end_assessment_return', 'isBoolean' => true) - ); - - /** - * No logging. - */ - public const LOGLEVEL_NONE = 0; - - /** - * Log errors only. - */ - public const LOGLEVEL_ERROR = 1; - - /** - * Log error and information messages. - */ - public const LOGLEVEL_INFO = 2; - - /** - * Log all messages. - */ - public const LOGLEVEL_DEBUG = 3; - - /** - * Permitted LTI versions for messages. - */ - public static array $LTI_VERSIONS = array(self::LTI_VERSION1, self::LTI_VERSION1P3, self::LTI_VERSION2); - - /** - * List of supported message types and associated class methods. - */ - public static array $METHOD_NAMES = array( - 'basic-lti-launch-request' => 'onLaunch', - 'ConfigureLaunchRequest' => 'onConfigure', - 'DashboardRequest' => 'onDashboard', - 'ContentItemSelectionRequest' => 'onContentItem', - 'ContentItemSelection' => 'onContentItem', - 'ContentItemUpdateRequest' => 'onContentItemUpdate', - 'ToolProxyRegistrationRequest' => 'onRegister', - 'LtiStartProctoring' => 'onLtiStartProctoring', - 'LtiStartAssessment' => 'onLtiStartAssessment', - 'LtiEndAssessment' => 'onLtiEndAssessment' - ); - - /** - * GET and POST request parameters - */ - public static ?array $requestParameters = null; - - /** - * Current logging level. - * - * @var int $logLevel - */ - public static int $logLevel = self::LOGLEVEL_NONE; - - /** - * Check whether the request received could be an LTI message. - * - * @return bool - */ - public static function isLtiMessage(): bool - { -// $isLti = ($_SERVER['REQUEST_METHOD'] === 'POST') && -// (!empty($_POST['lti_message_type']) || !empty($_POST['id_token']) || !empty($_POST['JWT']) || -// !empty($_POST['iss'])); -// if (!$isLti) { -// $isLti = ($_SERVER['REQUEST_METHOD'] === 'GET') && (!empty($_GET['iss']) || !empty($_GET['openid_configuration'])); -// } - global $DIC; - $wrapper = $DIC->http()->wrapper()->post(); - $refString = $DIC->refinery()->kindlyTo()->string(); - $isLti = ($wrapper->has('lti_message_type') && $wrapper->retrieve('lti_message_type', $refString) != '') || - ($wrapper->has('id_token') && $wrapper->retrieve('id_token', $refString) != '') || - ($wrapper->has('JWT') && $wrapper->retrieve('JWT', $refString) != '') || - ($wrapper->has('iss') && $wrapper->retrieve('iss', $refString) != ''); - if (!$isLti) { - $wrapper = $DIC->http()->wrapper()->query(); - $isLti = ($wrapper->has('iss') && $wrapper->retrieve('iss', $refString) != '') || - ($wrapper->has('openid_configuration') && $wrapper->retrieve('openid_configuration', $refString) != ''); - } - return $isLti; - } - - /** - * Return GET and POST request parameters (POST parameters take precedence) - * - * @return array|null - */ - public static function getRequestParameters(): ?array - { - if (is_null(self::$requestParameters)) { -// special for ILIAS instead of -// self::$requestParameters = array_merge($_GET, $_POST); -// also not possible -// self::$requestParameters = array_merge( -// //Argument 1 passed to Symfony\Component\HttpFoundation\Request::createRequestFromFactory() must be of the type array, object given -// (array) \Symfony\Component\HttpFoundation\Request::createFromGlobals()->query->all(), -// (array) \Symfony\Component\HttpFoundation\Request::createFromGlobals()->request->all() -// (array) $_GET, (array) $_POST -// ); - global $DIC; - $post = $DIC->http()->wrapper()->post(); - $query = $DIC->http()->wrapper()->query(); - $refinery = $DIC->refinery()->kindlyTo()->string(); - - $divAr = ['accept_copy_advice', - 'accept_media_types', - 'accept_multiple', - 'accept_presentation_document_targets', - 'accept_types', - 'accept_unsigned', - 'auto_create', - 'can_confirm', - 'client_id', - 'content_item_return_url', - 'context_id', - 'context_title', - 'context_type', - 'custom_ap_attempt_number', - 'custom_content_item_id', - 'custom_share_key', - 'custom_tc_profile_url', - 'custom_user_username', - 'custom_username', - 'deployment_id', - 'ext_launch_presentation_css_url', - 'ext_lms', - 'ext_user_username', - 'ext_username', - 'iss', - 'launch_presentation_css_url', - 'launch_presentation_document_target', - 'launch_presentation_height', - 'launch_presentation_return_url', - 'launch_presentation_width', - 'lis_person_contact_email_primary', - 'lis_person_name_family', - 'lis_person_name_full', - 'lis_person_name_given', - 'lis_person_sourcedid', - 'login_hint', - 'lti_deployment_id', - 'lti_message_hint', - 'lti_message_type', - 'lti_version', - 'oauth_consumer_key', - 'oauth_signature_method', - 'openid_configuration', - 'platform_state', - 'reg_key', - 'reg_password', - 'registration_token', - 'relaunch_url', - 'resource_link_id', - 'resource_link_title', - 'roles', - 'target_link_uri', - 'tc_profile_url', - 'tool_consumer_info_product_family_code', - 'tool_consumer_info_version', - 'tool_consumer_instance_guid', - 'tool_consumer_instance_name', - 'user_id', - 'user_image' - ]; - $LTI_CONSUMER_SETTING_NAMES = ['custom_tc_profile_url', 'custom_system_setting_url', 'custom_oauth2_access_token_url']; - $LTI_CONTEXT_SETTING_NAMES = ['custom_context_setting_url', - 'ext_ims_lis_memberships_id', 'ext_ims_lis_memberships_url', - 'custom_context_memberships_url', 'custom_context_memberships_v2_url', - 'custom_context_group_sets_url', 'custom_context_groups_url', - 'custom_lineitems_url', 'custom_ags_scopes' - ]; - $LTI_RESOURCE_LINK_SETTING_NAMES = ['lis_result_sourcedid', 'lis_outcome_service_url', - 'ext_ims_lis_basic_outcome_url', 'ext_ims_lis_resultvalue_sourcedids', 'ext_outcome_data_values_accepted', - 'ext_ims_lis_memberships_id', 'ext_ims_lis_memberships_url', - 'ext_ims_lti_tool_setting', 'ext_ims_lti_tool_setting_id', 'ext_ims_lti_tool_setting_url', - 'custom_link_setting_url', 'custom_link_memberships_url', - 'custom_lineitems_url', 'custom_lineitem_url', 'custom_ags_scopes', - 'custom_ap_acs_url' - ]; - - $requestAr = array_merge($divAr, $LTI_CONSUMER_SETTING_NAMES, $LTI_CONTEXT_SETTING_NAMES, $LTI_RESOURCE_LINK_SETTING_NAMES); - - foreach ($requestAr as $param) { - if ($query->has($param)) { - self::$requestParameters[$param] = $query->retrieve($param, $refinery); - } - if ($post->has($param)) { - self::$requestParameters[$param] = $post->retrieve($param, $refinery); - } - } - } - return self::$requestParameters; - } - - /** - * Log an error message. - * @param string $message Message to be logged - * @param bool $showSource True if the name and line number of the current file are to be included - * @return void - */ - public static function logError(string $message, bool $showSource = true) - { - if (self::$logLevel >= self::LOGLEVEL_ERROR) { - self::log("[ERROR] {$message}", $showSource); - } - } - - /** - * Log an information message. - * @param string $message Message to be logged - * @param bool $showSource True if the name and line number of the current file are to be included - * @return void - */ - public static function logInfo(string $message, bool $showSource = false) - { - if (self::$logLevel >= self::LOGLEVEL_INFO) { - self::log("[INFO] {$message}", $showSource); - } - } - - /** - * Log a debug message. - * @param string $message Message to be logged - * @param bool $showSource True if the name and line number of the current file are to be included - * @return void - */ - public static function logDebug(string $message, bool $showSource = false) - { - if (self::$logLevel >= self::LOGLEVEL_DEBUG) { - self::log("[DEBUG] {$message}", $showSource); - } - } - - /** - * Log a request received. - * @param bool $debugLevel True if the request details should be logged at the debug level (optional, default is false for information level) - * @return void - */ - public static function logRequest(bool $debugLevel = false) - { - if (!$debugLevel) { - $logLevel = self::LOGLEVEL_INFO; - } else { - $logLevel = self::LOGLEVEL_DEBUG; - } - if (self::$logLevel >= $logLevel) { - $message = "{$_SERVER['REQUEST_METHOD']} request received for '{$_SERVER['REQUEST_URI']}'"; -// $body = file_get_contents(OAuth\OAuthRequest::$POST_INPUT); - $body = file_get_contents(LTIOAuth\OAuthRequest::$POST_INPUT); - if (!empty($body)) { -// $params = OAuth\OAuthUtil::parse_parameters($body); - $params = LTIOAuth\OAuthUtil::parse_parameters($body); - if (!empty($params)) { - $message .= " with body parameters of:\n" . var_export($params, true); - } else { - $message .= " with a body of:\n" . var_export($body, true); - } - } - if (!$debugLevel) { - self::logInfo($message); - } else { - self::logDebug($message); - } - } - } - - /** - * Log a form submission. - * @param string $url URL to which the form should be submitted - * @param array $params Array of form parameters - * @param string $method HTTP Method used to submit form (optional, default is POST) - * @param bool $debugLevel True if the form details should always be logged (optional, default is false to use current log level) - * @return void - */ - public static function logForm(string $url, array $params, string $method = 'POST', bool $debugLevel = false) - { - if (!$debugLevel) { - $logLevel = self::$logLevel; - } else { - $logLevel = self::LOGLEVEL_DEBUG; - } - if (self::$logLevel >= self::LOGLEVEL_INFO) { - $message = "Form submitted using {$method} to '{$url}'"; - if (!empty($params)) { - $message .= " with parameters of:\n" . var_export($params, true); - } else { - $message .= " with no parameters"; - } - if ($logLevel < self::LOGLEVEL_DEBUG) { - self::logInfo($message); - } else { - self::logDebug($message); - } - } - } - - /** - * Log an error message irrespective of the logging level. - * @param string $message Message to be logged - * @param bool $showSource True if the name and line number of the current file are to be included - * @return void - */ - public static function log(string $message, bool $showSource = false) - { - $source = ''; - if ($showSource) { - $backtraces = debug_backtrace(); - foreach ($backtraces as $backtrace) { - if (isset($backtrace['file'])) { - $source .= PHP_EOL . " {$backtrace['file']}"; - if (isset($backtrace['line'])) { - $source .= " line {$backtrace['line']}"; - } - } - } - if (!empty($source)) { - $source = PHP_EOL . "See: {$source}"; - } - } - error_log($message . $source); - } - - /** - * Generate a web page containing an auto-submitted form of parameters. - * @param string $url URL to which the form should be submitted - * @param array $params Array of form parameters - * @param string $target Name of target (optional) - * @return string - */ - public static function sendForm(string $url, array $params, string $target = ''): string - { - self::logForm($url, $params, 'POST'); - $page = <<< EOD - - -IMS LTI message - - - -
- -EOD; - if (!empty($params)) { - foreach ($params as $key => $value) { - $key = htmlentities($key, ENT_COMPAT | ENT_HTML401, 'UTF-8'); - if (!is_array($value)) { - $value = htmlentities($value, ENT_COMPAT | ENT_HTML401, 'UTF-8'); - $page .= <<< EOD - - -EOD; - } else { - foreach ($value as $element) { - $element = htmlentities($element, ENT_COMPAT | ENT_HTML401, 'UTF-8'); - $page .= <<< EOD - - -EOD; - } - } - } - } - - $page .= <<< EOD -
- - -EOD; - - return $page; - } - - /** - * Redirect to a URL with query parameters. - * @param string $url URL to which the form should be submitted - * @param array $params Array of form parameters - * @return string - */ - public static function redirect(string $url, array $params): string - { - if (!empty($params)) { - if (strpos($url, '?') === false) { - $url .= '?'; - $sep = ''; - } else { - $sep = '&'; - } - foreach ($params as $key => $value) { - $key = urlencode($key); - if (!is_array($value)) { - $value = urlencode($value); - $url .= "{$sep}{$key}={$value}"; - $sep = '&'; - } else { - foreach ($value as $element) { - $element = urlencode($element); - $url .= "{$sep}{$key}={$element}"; - $sep = '&'; - } - } - } - } - - header("Location: {$url}"); - exit; - } - - /** - * Generate a random string. - * The generated string will only comprise letters (upper- and lower-case) and digits. - * @param int $length Length of string to be generated (optional, default is 8 characters) - * @return string Random string - */ - public static function getRandomString(int $length = 8): string - { - $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - - $value = ''; - $charsLength = strlen($chars) - 1; - - for ($i = 1; $i <= $length; $i++) { - $value .= $chars[rand(0, $charsLength)]; - } - - return $value; - } - - /** - * Strip HTML tags from a string. - * @param string $html HTML string to be stripped - * @return string - */ - public static function stripHtml(string $html): string - { - $html = strip_tags($html); - $html = html_entity_decode($html, ENT_QUOTES | ENT_HTML401); - - return $html; - } - - /** - * Clone an object and any objects it contains. - * @param object $obj Object to be cloned - * @return object - */ - public static function cloneObject(object $obj): object - { - $clone = clone $obj; - $objVars = get_object_vars($clone); - foreach ($objVars as $attrName => $attrValue) { - if (is_object($clone->$attrName)) { - $clone->$attrName = self::cloneObject($clone->$attrName); - } elseif (is_array($clone->$attrName)) { - foreach ($clone->$attrName as &$attrArrayValue) { - if (is_object($attrArrayValue)) { - $attrArrayValue = self::cloneObject($attrArrayValue); - } - unset($attrArrayValue); - } - } - } - - return $clone; - } - - - public static function logtxt(string $msg) - { - file_put_contents("log.txt", $msg . "\n", FILE_APPEND); - } -} diff --git a/components/ILIAS/LTI/src/ToolProvider/readme.md b/components/ILIAS/LTI/src/ToolProvider/readme.md deleted file mode 100755 index 69173ba60e82..000000000000 --- a/components/ILIAS/LTI/src/ToolProvider/readme.md +++ /dev/null @@ -1,2 +0,0 @@ -The sources in this directory are based on https://github.com/celtic-project/LTI-PHP without classes for OAuth and without classes to support LTI 2.0. -