Commit 1c9a1630 authored by mathieso's avatar mathieso

Harden against missing nodes.

parent a66f2e51
......@@ -31,10 +31,6 @@ Students can see own history
Add multifield for exercies on due, even if just use first one.
When add response, can have old text, instead of MT.
Didn't save text of response, when edited multiple RIs.
# Testing
......
......@@ -349,33 +349,35 @@ function _skilling_save_rubric_item_gui_data(FormStateInterface $formState, $rub
/** @var \Drupal\paragraphs\ParagraphInterface $responseParagraph */
$responseParagraph = $entityTypeManager->getStorage('paragraph')
->load($guiResponseOptionId);
if ($responseParagraph->bundle() != SkillingConstants::RUBRIC_ITEM_RESPONSE_PARAGRAPH_TYPE) {
throw new SkillingInvalidValueException(
'Not right para type $returnedResponseOptionId: ' . $guiResponseOptionId);
if ($responseParagraph) {
if ($responseParagraph->bundle() != SkillingConstants::RUBRIC_ITEM_RESPONSE_PARAGRAPH_TYPE) {
throw new SkillingInvalidValueException(
'Not right para type $returnedResponseOptionId: ' . $guiResponseOptionId);
}
$responseParagraph->set(
SkillingConstants::FIELD_RESPONSE_TO_STUDENT,
[
'value' => $guiResponseOptionResponseText,
'format' => SkillingConstants::STRIPPED_TEXT_FORMAT,
]
);
$responseParagraph->set(
SkillingConstants::FIELD_COMPLETES_RUBRIC_ITEM,
$guiResponseOptionCompletes
);
$responseParagraph->set(
SkillingConstants::FIELD_NOTES, [
'value' => $guiResponseOptionNotes,
'format' => SkillingConstants::SKILLING_TEXT_FORMAT,
]
);
$responseParagraph->set(
SkillingConstants::FIELD_EXERCISES,
$exerciseRefs
);
$responseParagraph->save();
$allResponseOptions[$responseParagraph->id()] = $responseParagraph;
}
$responseParagraph->set(
SkillingConstants::FIELD_RESPONSE_TO_STUDENT,
[
'value' => $guiResponseOptionResponseText,
'format' => SkillingConstants::STRIPPED_TEXT_FORMAT,
]
);
$responseParagraph->set(
SkillingConstants::FIELD_COMPLETES_RUBRIC_ITEM,
$guiResponseOptionCompletes
);
$responseParagraph->set(
SkillingConstants::FIELD_NOTES, [
'value' => $guiResponseOptionNotes,
'format' => SkillingConstants::SKILLING_TEXT_FORMAT,
]
);
$responseParagraph->set(
SkillingConstants::FIELD_EXERCISES,
$exerciseRefs
);
$responseParagraph->save();
$allResponseOptions[$responseParagraph->id()] = $responseParagraph;
}
}
......@@ -426,21 +428,19 @@ function _skilling_save_rubric_item_gui_data(FormStateInterface $formState, $rub
// An existing rubric item.
$node = $entityTypeManager->getStorage('node')
->load($guiRubricItemId);
if (!$node) {
throw new SkillingInvalidValueException(
'guiRubricItemId not found: ' . $guiRubricItemId);
}
if ($node->bundle() != SkillingConstants::RUBRIC_ITEM_CONTENT_TYPE) {
throw new SkillingInvalidValueException(
'Wrong content type: $guiRubricItemId: ' . $guiRubricItemId);
if ($node) {
if ($node->bundle() != SkillingConstants::RUBRIC_ITEM_CONTENT_TYPE) {
throw new SkillingInvalidValueException(
'Wrong content type: $guiRubricItemId: ' . $guiRubricItemId);
}
// Set the fields with the new values.
$node->set('title', $guiRubricItemName);
$node->set(SkillingConstants::FIELD_NOTES, [
'value' => $guiRubricItemNotes,
'format' => SkillingConstants::STRIPPED_TEXT_FORMAT
]);
$node->set(SkillingConstants::FIELD_CATEGORIES, $taxTerms);
}
// Set the fields with the new values.
$node->set('title', $guiRubricItemName);
$node->set(SkillingConstants::FIELD_NOTES, [
'value' => $guiRubricItemNotes,
'format' => SkillingConstants::STRIPPED_TEXT_FORMAT
]);
$node->set(SkillingConstants::FIELD_CATEGORIES, $taxTerms);
}
// Set the refs to the right response options.
......@@ -473,36 +473,38 @@ function _skilling_save_rubric_item_gui_data(FormStateInterface $formState, $rub
/** @var Drupal\node\NodeInterface $exercise */
$exercise = $entityTypeManager->getStorage('node')
->load($exerciseId);
// Erase the existing values.
$numItems = $exercise->get(SkillingConstants::FIELD_RUBRIC_ITEMS)->count();
for ($i = 0; $i < $numItems; $i++) {
$exercise->get(SkillingConstants::FIELD_RUBRIC_ITEMS)->removeItem(0);
}
// Make array with new ids in order.
$rubricItemIdOrder = [];
foreach ($guiRubricItemOrder as $rubricItemOrderElement) {
$order = $rubricItemOrderElement->index;
$id = $rubricItemOrderElement->rubricItemId;
// A new RI?
if (substr($id, 0, 3) == 'new') {
// Look up the real id.
$id = $newRubricItemsMapping[$id];
if ($exercise) {
// Erase the existing values.
$numItems = $exercise->get(SkillingConstants::FIELD_RUBRIC_ITEMS)->count();
for ($i = 0; $i < $numItems; $i++) {
$exercise->get(SkillingConstants::FIELD_RUBRIC_ITEMS)->removeItem(0);
}
$rubricItemIdOrder[$order] = $id;
}
// Apply the new order.
for ($index = 0; $index < count($rubricItemIdOrder); $index++) {
$id = $rubricItemIdOrder[$index];
if ($index === 0) {
$exercise->set(SkillingConstants::FIELD_RUBRIC_ITEMS, $id);
// Make array with new ids in order.
$rubricItemIdOrder = [];
foreach ($guiRubricItemOrder as $rubricItemOrderElement) {
$order = $rubricItemOrderElement->index;
$id = $rubricItemOrderElement->rubricItemId;
// A new RI?
if (substr($id, 0, 3) == 'new') {
// Look up the real id.
$id = $newRubricItemsMapping[$id];
}
$rubricItemIdOrder[$order] = $id;
}
else {
$exercise->get(SkillingConstants::FIELD_RUBRIC_ITEMS)->appendItem([
'target_id' => $id,
]);
// Apply the new order.
for ($index = 0; $index < count($rubricItemIdOrder); $index++) {
$id = $rubricItemIdOrder[$index];
if ($index === 0) {
$exercise->set(SkillingConstants::FIELD_RUBRIC_ITEMS, $id);
}
else {
$exercise->get(SkillingConstants::FIELD_RUBRIC_ITEMS)->appendItem([
'target_id' => $id,
]);
}
}
$exercise->save();
}
$exercise->save();
}
/**
......@@ -1362,6 +1364,9 @@ function skilling_node_delete(Node $node) {
/** @var \Drupal\block\Entity\Block $block */
$block = $entityTypeManager->getStorage('block')
->load($skillingBookNavBlock);
if (!$block) {
continue;
}
$bid = $block->get('settings')['book_to_show'];
if ($node->id() == $bid) {
//Delete the block.
......@@ -2635,6 +2640,9 @@ function skilling_entity_presave(Drupal\Core\Entity\EntityInterface $entity) {
foreach ($exerciseDueParaRefs as $exerciseDueParaRef) {
$exerciseDuePara = $entityTypeManager->getStorage('paragraph')
->load($exerciseDueParaRef['target_id']);
if (!$exerciseDuePara) {
continue;
}
$exerciseDuePara->delete();
}
if ($dueData && is_array($dueData) && count($dueData) > 0) {
......
......@@ -2843,10 +2843,7 @@ class SkillingAccessChecker {
// Load the root node.
$rootNode = $this->entityTypeManager->getStorage('node')->load($bid);
if (!$rootNode) {
throw new SkillingNotFoundException(
Html::escape("Nid with id $bid not found"),
__FILE__, __LINE__
);
return AccessResult::forbidden();
}
// Is the block for a design page?
if ($rootNode->bundle() === SkillingConstants::DESIGN_PAGE_CONTENT_TYPE) {
......@@ -3093,101 +3090,6 @@ class SkillingAccessChecker {
return $result;
}
/**
* Is the current user in a class with the given event on its calendar?
*
* @param \Drupal\node\NodeInterface $event
* The event.
*
* @return bool
* True if event in class.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
// protected function isCurrentUserInClassWithEvent(NodeInterface $event) {
// $calendarEventParagraphIds = $this->entityTypeManager
// ->getStorage('paragraph')->getQuery()
// // For the right paragraph type.
// ->condition('type', SkillingConstants::CALENDAR_EVENT_PARAGRAPH_TYPE)
// // For the event in question.
// ->condition('field_event.target_id', $event->id())
// ->execute();
// // Are there calendar events?
// if (count($calendarEventParagraphIds) === 0) {
// return FALSE;
// }
// // Get the calendars the paragraphs are for.
// $calendarNids = [];
// foreach ($calendarEventParagraphIds as $paragraphId) {
// /** @var \Drupal\paragraphs\ParagraphInterface $paragraph */
// $paragraph = $this->entityTypeManager->getStorage('paragraph')
// ->load($paragraphId);
// $calendarNid = $paragraph->getParentEntity()->id();
// if (!in_array($calendarNid, $calendarNids)) {
// $calendarNids[] = $calendarNid;
// }
// }
// if (count($calendarNids) === 0) {
// return FALSE;
// }
// // Get the classes using those paragraphs.
// $classNids = $this->entityTypeManager
// ->getStorage('node')->getQuery()
// // Published.
// ->condition('status', 1)
// // For the right paragraph type.
// ->condition('type', SkillingConstants::CLASS_CONTENT_TYPE)
// // For the calendars in question.
// ->condition('field_calendar.target_id', $calendarNids, 'IN')
// ->execute();
// if (count($classNids) === 0) {
// return FALSE;
// }
// // Is the current user in one of those classes?
// foreach ($classNids as $classNid) {
// if ($this->currentUser->isInClassNid($classNid)) {
// return TRUE;
// }
// }
// return FALSE;
// }
//
/**
* Is the current user in a class with the given calendar?
*
* @param \Drupal\node\NodeInterface $calendar
* The event.
*
* @return bool
* True if user in class with calendar.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
// protected function isCurrentUserInClassWithCalendar(NodeInterface $calendar) {
// // Get the classes using the calendar.
// $classNids = $this->entityTypeManager
// ->getStorage('node')->getQuery()
// // Published.
// ->condition('status', 1)
// // For the right paragraph type.
// ->condition('type', SkillingConstants::CLASS_CONTENT_TYPE)
// // For the calendars in question.
// ->condition('field_calendar.target_id', $calendar->id())
// ->execute();
// if (count($classNids) === 0) {
// return FALSE;
// }
// // Is the current user in one of those classes?
// foreach ($classNids as $classNid) {
// if ($this->currentUser->isInClassNid($classNid)) {
// return TRUE;
// }
// }
// return FALSE;
// }
/**
* Does the current user have access to a paragraph?
*
......
......@@ -105,6 +105,10 @@ class Assessment {
/** @var \Drupal\node\Entity\Node $class */
$class = $this->entityTypeManager
->getStorage('node')->load($classNid);
// Check that it exists.
if (!$class) {
continue;
}
$classUngradedSubmissions
= $this->countUngradedSubmissionsForClass($class, $includeUnpublishedExercises);
$totalSubmissionsToGrade += $classUngradedSubmissions;
......
......@@ -334,6 +334,10 @@ class CompletionScore {
foreach ($exerciseDueParaRefs as $exerciseDueParaRef) {
$exerciseDueParagraph = $this->entityTypeManager->getStorage('paragraph')
->load($exerciseDueParaRef['target_id']);
// Check that it exists.
if (!$exerciseDueParagraph) {
continue;
}
$exerciseId = (int)$exerciseDueParagraph->get(SkillingConstants::FIELD_EXERCISE)
->getValue()[0]['target_id'];
$day = (int)$exerciseDueParagraph->get(SkillingConstants::FIELD_DAY)
......@@ -349,6 +353,10 @@ class CompletionScore {
/** @var NodeInterface $exercise */
$exercise = $this->entityTypeManager->getStorage('node')
->load($exerciseId);
// Check that it exists.
if (!$exercise) {
continue;
}
if ($exercise->isPublished()) {
// Remember it.
$exercises[] = $exercise;
......@@ -589,91 +597,4 @@ class CompletionScore {
return [$fileName, $text];
}
/**
* Get events and their days for a calendar.
*
* @param \Drupal\node\NodeInterface $calendar
* The calendar.
*
* @return array
* Events and their days.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
// protected function getEventsDaysFromCalendar(NodeInterface $calendar) {
// // Get the nids of events in the calendar.
// $eventsRefsPara = $calendar->field_calendar_events->getValue();
// // Loop through the result set, extract the paragraph ids.
// $paraIds = [];
// foreach ($eventsRefsPara as $eventRefsPara) {
// $paraIds[] = $eventRefsPara['target_id'];
// }
// // Load the paragraphs.
// $calendarEvents = $this->entityTypeManager->getStorage('paragraph')
// ->loadMultiple($paraIds);
// // Extract the event nids.
// $eventNids = [];
// $eventsDays = [];
// /* @var \Drupal\node\NodeInterface $calendarEvent */
// foreach ($calendarEvents as $calendarEvent) {
// $eventNid = $calendarEvent->get('field_event')->target_id;
// $eventNids[] = $eventNid;
// $eventsDays[$eventNid] = $calendarEvent->get('field_day')->value;
// }
// // Load the events.
// $events = $this->entityTypeManager->getStorage('node')
// ->loadMultiple($eventNids);
// return [$events, $eventsDays];
// }
/**
* Get all of the exercises for some events.
*
* @param array $events
* Event nodes.
* @param array $eventsDays
* When events happen.
*
* @return array
* Exercises and their due days.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
// protected function getExercisesForEvents(array $events, array $eventsDays) {
// // Extract the exercise ids for published events.
// $exerciseNids = [];
// // Array with exercise nid as key, event id as value.
// $exerciseEventMap = [];
// /** @var \Drupal\node\NodeInterface $event */
// foreach ($events as $event) {
// $eventType = $event->get(SkillingConstants::FIELD_EVENT_TYPE)->value;
// if ($eventType === SkillingConstants::EVENT_TYPE_EXERCISE_DUE) {
// /* @noinspection PhpUndefinedFieldInspection */
// if (isset($event->field_exercise->target_id)
// && $event->field_exercise->target_id) {
// /* @noinspection PhpUndefinedFieldInspection */
// $exerciseNid = $event->field_exercise->target_id;
// $exerciseNids[] = $exerciseNid;
// $exerciseEventMap[$exerciseNid] = $event->id();
// }
// }
// } // End for each event.
// // Load the exercises.
// $exercisesRaw = $this->entityTypeManager->getStorage('node')
// ->loadMultiple($exerciseNids);
// // Test for published exercises.
// $exercises = [];
// $exerciseDueDays = [];
// /* @var \Drupal\node\NodeInterface $exercise */
// foreach ($exercisesRaw as $exercise) {
// if ($exercise->isPublished()) {
// $exercises[$exercise->id()] = $exercise;
// $exerciseDueDays[$exercise->id()] = $eventsDays[$exerciseEventMap[$exercise->id()]];
// }
// }
// return [$exercises, $exerciseDueDays];
// }
}
......@@ -1240,6 +1240,12 @@ class AssessmentController extends ControllerBase {
/** @var \Drupal\node\NodeInterface $exercise */
$exercise = $this->entityTypeManager->getStorage('node')
->load($submissionNode->field_exercise->target_id);
// Check if exists.
if (!$exercise) {
$result = [
'status' => 'ARGH!',
];
return new JsonResponse($result); }
$title = $this->t(
"Feedback for '@exercise'",
['@exercise' => $exercise->getTitle()]
......
......@@ -145,6 +145,12 @@ class ProgressBasedOnRequiredController extends ControllerBase {
/** @var \Drupal\node\NodeInterface $node */
$node = $this->entityTypeManager->getStorage('node')
->load($enrollmentNid);
if (!$node) {
$result = [
'status' => 'Argh!',
];
return new JsonResponse($result);
}
$node->set(SkillingConstants::FIELD_BASE_PROGRESS_ON_REQUIRED_ONLY, $required);
// Hacky: stop other code trying to do its own save, triggred by this save.
global $globalEnrollmentBeingSaved;
......
......@@ -329,31 +329,25 @@ class Enrollment implements EnrollmentInterface {
// Does the node exist?
/** @var \Drupal\node\Entity\Node $class */
$class = $nodeStorage->load($classId);
if (!$class) {
// Not found.
$deets = Html::escape('Class with id not found: ' . $classId);
throw new SkillingException($deets, __FILE__, __LINE__);
}
if ($class->bundle() !== SkillingConstants::CLASS_CONTENT_TYPE) {
// Not a class.
$deets = Html::escape('Not a class: ' . $classId);
throw new SkillingException($deets, __FILE__, __LINE__);
}
if ($class->isPublished()) {
// The class is published.
$enrollmentIds = $nodeStorage->getQuery()
->condition('type', SkillingConstants::ENROLLMENT_CONTENT_TYPE)
// Published.
->condition('status', 1)
// Have student role.
->condition('field_class_roles', ['student'], 'IN')
// User is not blocked.
->condition('field_user.entity.status', 1)
// Right class.
->condition('field_class', $classId)
// Class is published.
->condition('field_class.entity.status', 1)
->execute();
if ($class) {
if ($class->bundle() === SkillingConstants::CLASS_CONTENT_TYPE) {
if ($class->isPublished()) {
// The class is published.
$enrollmentIds = $nodeStorage->getQuery()
->condition('type', SkillingConstants::ENROLLMENT_CONTENT_TYPE)
// Published.
->condition('status', 1)
// Have student role.
->condition('field_class_roles', ['student'], 'IN')
// User is not blocked.
->condition('field_user.entity.status', 1)
// Right class.
->condition('field_class', $classId)
// Class is published.
->condition('field_class.entity.status', 1)
->execute();
}
}
}
return $enrollmentIds;
}
......
......@@ -148,7 +148,9 @@ class Notice {
public function setNoticeIdRead(int $noticeId) {
$notice = $this->entityTypeManager->getStorage('node')
->load($noticeId);
$this->setNoticeRead($notice);
if ($notice) {
$this->setNoticeRead($notice);
}
}
/**
......
......@@ -207,6 +207,9 @@ class SkillingBookNavBlock extends BlockBase implements ContainerFactoryPluginIn
$bookTree = $this->getBookTree($bid);
$bookNode = $this->entityTypeManager->getStorage('node')
->load($bid);
if (!$bookNode) {
return $build;
}
$build['book_nav_block_' . $bid] = [
'#theme' => 'skilling_book_tree',
'#key' => $bookTree['key'],
......
......@@ -157,6 +157,12 @@ class ReflectTag extends SkillingCustomTagBase {
/** @var \Drupal\node\Entity\Node $noteNode */
$noteNode = $this->entityTypeManager->getStorage('node')
->load($noteId);
// Should not happen.
if (!$noteNode) {
return $this->formatCustomTagError(
t("Note with id @nid not found.", ['@nid' => $noteId])
);
}
/* @noinspection PhpUndefinedFieldInspection */
$note = trim($noteNode->field_note->value);
}
......
......@@ -501,97 +501,4 @@ class SkillingTokens implements SkillingTokensInterface {
}
}
/**
* Process class tokens.
*
* @param array $tokens
* Array of tokens to be replaced.
* @param array $data
* Associative array of data objects to be used when
* generating replacement values.
* @param array $options
* Associative array of options for token replacement.
* @param \Drupal\Core\Render\BubbleableMetadata $bubbleableMetadata
* The bubbleable metadata.
*
* @return array
* Associative array of replacement values.
*
* @throws \Drupal\skilling\Exception\SkillingException
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
// protected function classTokens(array $tokens, array $data, array $options, BubbleableMetadata $bubbleableMetadata) {
// return;
// $replacements = NULL;
// foreach ($tokens as $name => $original) {
// $eventTokens = $this->tokenService->findWithPrefix($tokens, 'event-passed');
// if ($eventTokens) {
// // Has an event passed?
// $eventInternalName = array_keys($eventTokens)[0];
// // Find an event with that internal name.
// $eventIds = $this->entityTypeManager->getStorage('node')->getQuery()
// ->condition('type', SkillingConstants::EVENT_CONTENT_TYPE)
// ->condition('field_internal_name', $eventInternalName)
// ->condition('status', 1)
// ->execute();
// // Found more than one?
// if (count($eventIds) > 1) {
// throw new SkillingException(
// 'Too many events with internal name: ' . $eventInternalName,
// __FILE__, __LINE__
// );
// }
// // Find any?
// if (count($eventIds) === 1) {
// $eventId = reset($eventIds);
// // Find the current class.
// $class = $this->currentClass->getCurrentClass();
// if ($class) {
// // Find the calendar for the current class.
// $calendarId = $class->get('field_calendar')->target_id;
// // Is the event on the calendar?
// $calendarEventIds = $this->entityTypeManager
// ->getStorage('node')->getQuery()
//// REMOVING CALENDAR EVENTS.
//// ->condition('type', SkillingConstants::CALENDAR_EVENT_CONTENT_TYPE)
// ->condition('field_calendar.target_id', $calendarId)
// ->condition('field_event.target_id', $eventId)
// ->condition('status', 1)
// ->execute();
// if (count($calendarEventIds) === 1) {
// // Found the event on the current class's calendar.
// $calendarEventId = reset($calendarEventIds);
// $calendarEvent = $this->entityTypeManager
// ->getStorage('node')->load($calendarEventId);
// // How many days from the start of the class is
// // the event scheduled?
// $eventDayOffset = $calendarEvent->field_day->value;
// // Check just in case.
// if ($eventDayOffset) {
// // Get the start of the class.
// $whenCalendarStarts = $class->field_when_starts->value;
// $startDate = new DateTime($whenCalendarStarts);
// // What is today, expressed as number of days from
// // the start of course?
// $now = new DateTime();
// $todayAsDayNumber = $now->diff($startDate)->days + 1;
// // Has the event passed?
// if ($todayAsDayNumber > $eventDayOffset) {
// $replacements[$original] = self::TRUE_VALUE;
// }
// else {
// $replacements[$original] = self::FALSE_VALUE;
// }
// } // End there is a day in the calendar event record.
// } // End the event is on the calendar.
// } // End there is a current class.
// } // End an event with the given internal name exists.
// } // End there was an event-passed token.
// } // End for each token.
// if (!is_null($replacements)) {
// return $replacements;
// }
// }
//
}
This diff is collapsed.
......@@ -337,9 +337,7 @@ class Submissions {
/* @var \Drupal\node\Entity\Node $node */
$node = $this->entityTypeManager->getStorage('node')->load($nid);
if (!$node) {
throw new SkillingNotFoundException(
'Submission not found: ' . $nid, __FILE__, __LINE__
);
return NULL;
}
// Right bundle?
if ($node->bundle() !== SkillingConstants::EXERCISE_SUBMISSION_CONTENT_TYPE) {
......
......@@ -327,10 +327,7 @@ class Utilities {
/** @var \Drupal\node\Entity\Node $node */
$node = $this->entityTypeManager->getStorage('node')->load($nid);
if (!$node) {
throw new SkillingNotFoundException(
Html::escape('Node not found: ' . $nid),
__FILE__, __LINE__
);
return NULL;
}
// Right bundle?
if ($node->bundle() !== SkillingConstants::EXERCISE_CONTENT_TYPE) {
......@@ -758,42 +755,12 @@ class Utilities {
/** @var \Drupal\node\Entity\Node $submission */
$submission = $this->entityTypeManager->getStorage('node')
->load($id);
if (!$submission->isPublished()) {
if (!$submission || !$submission->isPublished()) {
$submission = NULL;
}
return $submission;
}
/**
* Get published submissions for published exercises for user with given uid.
*
* @param int $uid
* The user's id.
*
* @return \Drupal\Core\Entity\EntityInterface[]
* The submissions.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
// public function getPublishedSubmissionsForUid($uid) {