Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.
<?php

namespace Packback\Lti1p3;

class LtiAssignmentsGradesService extends LtiAbstractService
{
    public const CONTENTTYPE_SCORE = 'application/vnd.ims.lis.v1.score+json';
    public const CONTENTTYPE_LINEITEM = 'application/vnd.ims.lis.v2.lineitem+json';
    public const CONTENTTYPE_LINEITEMCONTAINER = 'application/vnd.ims.lis.v2.lineitemcontainer+json';
    public const CONTENTTYPE_RESULTCONTAINER = 'application/vnd.ims.lis.v2.resultcontainer+json';

    public function getScope(): array
    {
        return $this->getServiceData()['scope'];
    }

    // https://www.imsglobal.org/spec/lti-ags/v2p0#assignment-and-grade-service-claim
    // When an LTI message is launching a resource associated to one and only one lineitem,
    // the claim must include the endpoint URL for accessing the associated line item;
    // in all other cases, this property must be either blank or not included in the claim.
    public function getResourceLaunchLineItem(): ?LtiLineitem
    {
        $serviceData = $this->getServiceData();
        if (empty($serviceData['lineitem'])) {
            return null;
        }

        return LtiLineitem::new()->setId($serviceData['lineitem']);
    }

    public function putGrade(LtiGrade $grade, LtiLineitem $lineitem = null)
    {
< if (!in_array(LtiConstants::AGS_SCOPE_SCORE, $this->getScope())) { < throw new LtiException('Missing required scope', 1); < }
> $this->validateScopes([LtiConstants::AGS_SCOPE_SCORE]);
$lineitem = $this->ensureLineItemExists($lineitem); $scoreUrl = $lineitem->getId(); // Place '/scores' before url params $pos = strpos($scoreUrl, '?'); $scoreUrl = $pos === false ? $scoreUrl.'/scores' : substr_replace($scoreUrl, '/scores', $pos, 0); $request = new ServiceRequest( ServiceRequest::METHOD_POST, $scoreUrl, ServiceRequest::TYPE_SYNC_GRADE ); $request->setBody($grade); $request->setContentType(static::CONTENTTYPE_SCORE); return $this->makeServiceRequest($request); } public function findLineItem(LtiLineitem $newLineItem): ?LtiLineitem { $lineitems = $this->getLineItems(); foreach ($lineitems as $lineitem) { if ($this->isMatchingLineitem($lineitem, $newLineItem)) { return new LtiLineitem($lineitem); } } return null; } public function updateLineitem(LtiLineItem $lineitemToUpdate): LtiLineitem { $request = new ServiceRequest( ServiceRequest::METHOD_PUT,
< $this->getServiceData()['lineitems'],
> $this->getServiceData()['lineitem'],
ServiceRequest::TYPE_UPDATE_LINEITEM ); $request->setBody($lineitemToUpdate) ->setContentType(static::CONTENTTYPE_LINEITEM) ->setAccept(static::CONTENTTYPE_LINEITEM); $updatedLineitem = $this->makeServiceRequest($request); return new LtiLineitem($updatedLineitem['body']); } public function createLineitem(LtiLineitem $newLineItem): LtiLineitem { $request = new ServiceRequest( ServiceRequest::METHOD_POST, $this->getServiceData()['lineitems'], ServiceRequest::TYPE_CREATE_LINEITEM ); $request->setBody($newLineItem) ->setContentType(static::CONTENTTYPE_LINEITEM) ->setAccept(static::CONTENTTYPE_LINEITEM); $createdLineItem = $this->makeServiceRequest($request); return new LtiLineitem($createdLineItem['body']); }
> public function deleteLineitem(): array public function findOrCreateLineitem(LtiLineitem $newLineItem): LtiLineitem > { { > $request = new ServiceRequest( return $this->findLineItem($newLineItem) ?? $this->createLineitem($newLineItem); > ServiceRequest::METHOD_DELETE, } > $this->getServiceData()['lineitem'], > ServiceRequest::TYPE_DELETE_LINEITEM public function getGrades(LtiLineitem $lineitem = null) > ); { > $lineitem = $this->ensureLineItemExists($lineitem); > return $this->makeServiceRequest($request); $resultsUrl = $lineitem->getId(); > } >
// Place '/results' before url params $pos = strpos($resultsUrl, '?'); $resultsUrl = $pos === false ? $resultsUrl.'/results' : substr_replace($resultsUrl, '/results', $pos, 0); $request = new ServiceRequest( ServiceRequest::METHOD_GET, $resultsUrl, ServiceRequest::TYPE_GET_GRADES ); $request->setAccept(static::CONTENTTYPE_RESULTCONTAINER);
< $scores = $this->makeServiceRequest($request);
< return $scores['body'];
> return $this->getAll($request);
} public function getLineItems(): array {
< if (!in_array(LtiConstants::AGS_SCOPE_LINEITEM, $this->getScope())) { < throw new LtiException('Missing required scope', 1); < }
> $this->validateScopes([LtiConstants::AGS_SCOPE_LINEITEM, LtiConstants::AGS_SCOPE_LINEITEM_READONLY]);
$request = new ServiceRequest( ServiceRequest::METHOD_GET, $this->getServiceData()['lineitems'], ServiceRequest::TYPE_GET_LINEITEMS ); $request->setAccept(static::CONTENTTYPE_LINEITEMCONTAINER); $lineitems = $this->getAll($request); // If there is only one item, then wrap it in an array so the foreach works if (isset($lineitems['body']['id'])) { $lineitems['body'] = [$lineitems['body']]; } return $lineitems; } public function getLineItem(string $url): LtiLineitem {
< if (!in_array(LtiConstants::AGS_SCOPE_LINEITEM, $this->getScope())) { < throw new LtiException('Missing required scope', 1); < }
> $this->validateScopes([LtiConstants::AGS_SCOPE_LINEITEM, LtiConstants::AGS_SCOPE_LINEITEM_READONLY]);
$request = new ServiceRequest( ServiceRequest::METHOD_GET, $url, ServiceRequest::TYPE_GET_LINEITEM ); $request->setAccept(static::CONTENTTYPE_LINEITEM); $response = $this->makeServiceRequest($request)['body']; return new LtiLineitem($response); } private function ensureLineItemExists(LtiLineitem $lineitem = null): LtiLineitem { // If no line item is passed in, attempt to use the one associated with // this launch. if (!isset($lineitem)) { $lineitem = $this->getResourceLaunchLineItem(); } // If none exists still, create a default line item. if (!isset($lineitem)) { $defaultLineitem = LtiLineitem::new() ->setLabel('default') ->setScoreMaximum(100); $lineitem = $this->createLineitem($defaultLineitem); } // If the line item does not contain an ID, find or create it. if (empty($lineitem->getId())) { $lineitem = $this->findOrCreateLineitem($lineitem); } return $lineitem; } private function isMatchingLineitem(array $lineitem, LtiLineitem $newLineItem): bool { return $newLineItem->getTag() == ($lineitem['tag'] ?? null) && $newLineItem->getResourceId() == ($lineitem['resourceId'] ?? null) && $newLineItem->getResourceLinkId() == ($lineitem['resourceLinkId'] ?? null); } }