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.

Differences Between: [Versions 400 and 403] [Versions 401 and 403] [Versions 402 and 403]

   1  <?php
   2  
   3  namespace Packback\Lti1p3;
   4  
   5  class LtiAssignmentsGradesService extends LtiAbstractService
   6  {
   7      public const CONTENTTYPE_SCORE = 'application/vnd.ims.lis.v1.score+json';
   8      public const CONTENTTYPE_LINEITEM = 'application/vnd.ims.lis.v2.lineitem+json';
   9      public const CONTENTTYPE_LINEITEMCONTAINER = 'application/vnd.ims.lis.v2.lineitemcontainer+json';
  10      public const CONTENTTYPE_RESULTCONTAINER = 'application/vnd.ims.lis.v2.resultcontainer+json';
  11  
  12      public function getScope(): array
  13      {
  14          return $this->getServiceData()['scope'];
  15      }
  16  
  17      // https://www.imsglobal.org/spec/lti-ags/v2p0#assignment-and-grade-service-claim
  18      // When an LTI message is launching a resource associated to one and only one lineitem,
  19      // the claim must include the endpoint URL for accessing the associated line item;
  20      // in all other cases, this property must be either blank or not included in the claim.
  21      public function getResourceLaunchLineItem(): ?LtiLineitem
  22      {
  23          $serviceData = $this->getServiceData();
  24          if (empty($serviceData['lineitem'])) {
  25              return null;
  26          }
  27  
  28          return LtiLineitem::new()->setId($serviceData['lineitem']);
  29      }
  30  
  31      public function putGrade(LtiGrade $grade, LtiLineitem $lineitem = null)
  32      {
  33          $this->validateScopes([LtiConstants::AGS_SCOPE_SCORE]);
  34  
  35          $lineitem = $this->ensureLineItemExists($lineitem);
  36  
  37          $scoreUrl = $lineitem->getId();
  38  
  39          // Place '/scores' before url params
  40          $pos = strpos($scoreUrl, '?');
  41          $scoreUrl = $pos === false ? $scoreUrl.'/scores' : substr_replace($scoreUrl, '/scores', $pos, 0);
  42  
  43          $request = new ServiceRequest(
  44              ServiceRequest::METHOD_POST,
  45              $scoreUrl,
  46              ServiceRequest::TYPE_SYNC_GRADE
  47          );
  48          $request->setBody($grade);
  49          $request->setContentType(static::CONTENTTYPE_SCORE);
  50  
  51          return $this->makeServiceRequest($request);
  52      }
  53  
  54      public function findLineItem(LtiLineitem $newLineItem): ?LtiLineitem
  55      {
  56          $lineitems = $this->getLineItems();
  57  
  58          foreach ($lineitems as $lineitem) {
  59              if ($this->isMatchingLineitem($lineitem, $newLineItem)) {
  60                  return new LtiLineitem($lineitem);
  61              }
  62          }
  63  
  64          return null;
  65      }
  66  
  67      public function updateLineitem(LtiLineItem $lineitemToUpdate): LtiLineitem
  68      {
  69          $request = new ServiceRequest(
  70              ServiceRequest::METHOD_PUT,
  71              $this->getServiceData()['lineitem'],
  72              ServiceRequest::TYPE_UPDATE_LINEITEM
  73          );
  74  
  75          $request->setBody($lineitemToUpdate)
  76              ->setContentType(static::CONTENTTYPE_LINEITEM)
  77              ->setAccept(static::CONTENTTYPE_LINEITEM);
  78  
  79          $updatedLineitem = $this->makeServiceRequest($request);
  80  
  81          return new LtiLineitem($updatedLineitem['body']);
  82      }
  83  
  84      public function createLineitem(LtiLineitem $newLineItem): LtiLineitem
  85      {
  86          $request = new ServiceRequest(
  87              ServiceRequest::METHOD_POST,
  88              $this->getServiceData()['lineitems'],
  89              ServiceRequest::TYPE_CREATE_LINEITEM
  90          );
  91          $request->setBody($newLineItem)
  92              ->setContentType(static::CONTENTTYPE_LINEITEM)
  93              ->setAccept(static::CONTENTTYPE_LINEITEM);
  94          $createdLineItem = $this->makeServiceRequest($request);
  95  
  96          return new LtiLineitem($createdLineItem['body']);
  97      }
  98  
  99      public function deleteLineitem(): array
 100      {
 101          $request = new ServiceRequest(
 102              ServiceRequest::METHOD_DELETE,
 103              $this->getServiceData()['lineitem'],
 104              ServiceRequest::TYPE_DELETE_LINEITEM
 105          );
 106  
 107          return $this->makeServiceRequest($request);
 108      }
 109  
 110      public function findOrCreateLineitem(LtiLineitem $newLineItem): LtiLineitem
 111      {
 112          return $this->findLineItem($newLineItem) ?? $this->createLineitem($newLineItem);
 113      }
 114  
 115      public function getGrades(LtiLineitem $lineitem = null)
 116      {
 117          $lineitem = $this->ensureLineItemExists($lineitem);
 118          $resultsUrl = $lineitem->getId();
 119  
 120          // Place '/results' before url params
 121          $pos = strpos($resultsUrl, '?');
 122          $resultsUrl = $pos === false ? $resultsUrl.'/results' : substr_replace($resultsUrl, '/results', $pos, 0);
 123  
 124          $request = new ServiceRequest(
 125              ServiceRequest::METHOD_GET,
 126              $resultsUrl,
 127              ServiceRequest::TYPE_GET_GRADES
 128          );
 129          $request->setAccept(static::CONTENTTYPE_RESULTCONTAINER);
 130  
 131          return $this->getAll($request);
 132      }
 133  
 134      public function getLineItems(): array
 135      {
 136          $this->validateScopes([LtiConstants::AGS_SCOPE_LINEITEM, LtiConstants::AGS_SCOPE_LINEITEM_READONLY]);
 137  
 138          $request = new ServiceRequest(
 139              ServiceRequest::METHOD_GET,
 140              $this->getServiceData()['lineitems'],
 141              ServiceRequest::TYPE_GET_LINEITEMS
 142          );
 143          $request->setAccept(static::CONTENTTYPE_LINEITEMCONTAINER);
 144  
 145          $lineitems = $this->getAll($request);
 146  
 147          // If there is only one item, then wrap it in an array so the foreach works
 148          if (isset($lineitems['body']['id'])) {
 149              $lineitems['body'] = [$lineitems['body']];
 150          }
 151  
 152          return $lineitems;
 153      }
 154  
 155      public function getLineItem(string $url): LtiLineitem
 156      {
 157          $this->validateScopes([LtiConstants::AGS_SCOPE_LINEITEM, LtiConstants::AGS_SCOPE_LINEITEM_READONLY]);
 158  
 159          $request = new ServiceRequest(
 160              ServiceRequest::METHOD_GET,
 161              $url,
 162              ServiceRequest::TYPE_GET_LINEITEM
 163          );
 164          $request->setAccept(static::CONTENTTYPE_LINEITEM);
 165  
 166          $response = $this->makeServiceRequest($request)['body'];
 167  
 168          return new LtiLineitem($response);
 169      }
 170  
 171      private function ensureLineItemExists(LtiLineitem $lineitem = null): LtiLineitem
 172      {
 173          // If no line item is passed in, attempt to use the one associated with
 174          // this launch.
 175          if (!isset($lineitem)) {
 176              $lineitem = $this->getResourceLaunchLineItem();
 177          }
 178  
 179          // If none exists still, create a default line item.
 180          if (!isset($lineitem)) {
 181              $defaultLineitem = LtiLineitem::new()
 182                  ->setLabel('default')
 183                  ->setScoreMaximum(100);
 184              $lineitem = $this->createLineitem($defaultLineitem);
 185          }
 186  
 187          // If the line item does not contain an ID, find or create it.
 188          if (empty($lineitem->getId())) {
 189              $lineitem = $this->findOrCreateLineitem($lineitem);
 190          }
 191  
 192          return $lineitem;
 193      }
 194  
 195      private function isMatchingLineitem(array $lineitem, LtiLineitem $newLineItem): bool
 196      {
 197          return $newLineItem->getTag() == ($lineitem['tag'] ?? null) &&
 198              $newLineItem->getResourceId() == ($lineitem['resourceId'] ?? null) &&
 199              $newLineItem->getResourceLinkId() == ($lineitem['resourceLinkId'] ?? null);
 200      }
 201  }