Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.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);
        }

        $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(LtiServiceConnector::METHOD_POST, $scoreUrl);
> $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 public function createLineitem(LtiLineitem $newLineItem): LtiLineitem > { { > $request = new ServiceRequest( $request = new ServiceRequest(LtiServiceConnector::METHOD_POST, $this->getServiceData()['lineitems']); > ServiceRequest::METHOD_PUT, $request->setBody($newLineItem) > $this->getServiceData()['lineitems'], ->setContentType(static::CONTENTTYPE_LINEITEM) > ServiceRequest::TYPE_UPDATE_LINEITEM ->setAccept(static::CONTENTTYPE_LINEITEM); > ); $createdLineItems = $this->makeServiceRequest($request); > > $request->setBody($lineitemToUpdate) return new LtiLineitem($createdLineItems['body']); > ->setContentType(static::CONTENTTYPE_LINEITEM) } > ->setAccept(static::CONTENTTYPE_LINEITEM); > public function findOrCreateLineitem(LtiLineitem $newLineItem): LtiLineitem > $updatedLineitem = $this->makeServiceRequest($request); { > return $this->findLineItem($newLineItem) ?? $this->createLineitem($newLineItem); > return new LtiLineitem($updatedLineitem['body']); } > } >
< $request = new ServiceRequest(LtiServiceConnector::METHOD_POST, $this->getServiceData()['lineitems']);
> $request = new ServiceRequest( > ServiceRequest::METHOD_POST, > $this->getServiceData()['lineitems'], > ServiceRequest::TYPE_CREATE_LINEITEM > );
< $createdLineItems = $this->makeServiceRequest($request);
> $createdLineItem = $this->makeServiceRequest($request);
< return new LtiLineitem($createdLineItems['body']);
> return new LtiLineitem($createdLineItem['body']);
$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(LtiServiceConnector::METHOD_GET, $resultsUrl);
> $request = new ServiceRequest( > ServiceRequest::METHOD_GET, > $resultsUrl, > ServiceRequest::TYPE_GET_GRADES > );
$request->setAccept(static::CONTENTTYPE_RESULTCONTAINER); $scores = $this->makeServiceRequest($request); return $scores['body']; } public function getLineItems(): array { if (!in_array(LtiConstants::AGS_SCOPE_LINEITEM, $this->getScope())) { throw new LtiException('Missing required scope', 1); } $request = new ServiceRequest(
< LtiServiceConnector::METHOD_GET, < $this->getServiceData()['lineitems']
> 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); }
< $request = new ServiceRequest(LtiServiceConnector::METHOD_GET, $url);
> $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); } }