diff --git a/src/DataIntegrityChecker.php b/src/DataIntegrityChecker.php index 70c99f7d44d4252c9003dfdc6d5670d10fc6c500..6134aff6e09e542515acb29614d91dfa064e4b60 100644 --- a/src/DataIntegrityChecker.php +++ b/src/DataIntegrityChecker.php @@ -98,7 +98,15 @@ final class DataIntegrityChecker { if (\array_key_exists($course_id, $courses)) { continue; } - $courses[$course_id] = $course_status->getCourse(); + $course = $course_status->getCourse(); + + // Orphaned course status. It may be there's a data integrity error + // or this check ran before cleanup cron execution. + // @see \Drupal\lms\Hook\LmsEntityHooks::groupDelete(). + if ($course === NULL) { + continue; + } + $courses[$course_id] = $course; } return $courses; diff --git a/src/Entity/CourseStatus.php b/src/Entity/CourseStatus.php index da25ea8cb5755f3489d47ea164e3fc8df74c8f1b..9bc136d4a3b1810f7c347672a34c5e3febc51795 100644 --- a/src/Entity/CourseStatus.php +++ b/src/Entity/CourseStatus.php @@ -43,9 +43,12 @@ class CourseStatus extends ContentEntityBase implements CourseStatusInterface { /** * {@inheritdoc} */ - public function getCourse(): Course { + public function getCourse(): ?Course { + /** @var \Drupal\Core\Entity\EntityInterface|null */ $course = $this->get('gid')->entity; - \assert($course instanceof Course); + if ($course !== NULL) { + \assert($course instanceof Course); + } return $course; } @@ -222,23 +225,6 @@ class CourseStatus extends ContentEntityBase implements CourseStatusInterface { return (bool) $this->get('current')->value; } - /** - * {@inheritdoc} - */ - public static function actOnCourseDeletion(string $gid): void { - // Remove Course Status entities. - $storage = \Drupal::entityTypeManager()->getStorage('lms_course_status'); - $affected_status_ids = $storage->getQuery() - ->accessCheck(FALSE) - ->condition('gid', $gid) - ->execute(); - if (\count($affected_status_ids) === 0) { - return; - } - $statuses = $storage->loadMultiple($affected_status_ids); - $storage->delete($statuses); - } - /** * {@inheritdoc} */ diff --git a/src/Entity/CourseStatusInterface.php b/src/Entity/CourseStatusInterface.php index 4f47cdf9b5c67e09ecbbc7c174a7000e287b451a..f5339bbaaecbe99b2d2f62c06af2b70e55ac0770 100644 --- a/src/Entity/CourseStatusInterface.php +++ b/src/Entity/CourseStatusInterface.php @@ -29,10 +29,10 @@ interface CourseStatusInterface extends ContentEntityInterface { /** * Gets the related course entity of the course status entity. * - * @return \Drupal\lms\Entity\Bundle\Course + * @return \Drupal\lms\Entity\Bundle\Course|null * The related course entity. */ - public function getCourse(): Course; + public function getCourse(): ?Course; /** * Gets the related user ID of the course status entity. @@ -103,14 +103,6 @@ interface CourseStatusInterface extends ContentEntityInterface { */ public function getCurrentLessonStatus(): ?LessonStatusInterface; - /** - * Act on learning path deletion. - * - * @param string $gid - * Course group ID. - */ - public static function actOnCourseDeletion(string $gid): void; - /** * Get status and score translated string. */ diff --git a/src/Hook/LmsEntityHooks.php b/src/Hook/LmsEntityHooks.php index defd1e5151a9ca6edebda9d9cf43e7e2921eb1b3..9a00d82cf59b2788714dd02f30c8a1aa91bdda97 100644 --- a/src/Hook/LmsEntityHooks.php +++ b/src/Hook/LmsEntityHooks.php @@ -144,9 +144,21 @@ final class LmsEntityHooks { #[Hook('group_delete')] public function groupDelete(GroupInterface $group): void { - if ($group instanceof Course) { - CourseStatus::actOnCourseDeletion($group->id()); + if (!$group instanceof Course) { + return; + } + $course_status_ids = $this->entityTypeManager->getStorage('lms_course_status')->getQuery() + ->accessCheck(FALSE) + ->condition('gid', $group->id()) + ->execute(); + if (\count($course_status_ids) === 0) { + return; } + $queue = ($this->getQueue)('lms_delete_entities', TRUE); + foreach (\array_chunk($course_status_ids, 10) as $ids) { + $queue->createItem(['entity_type' => 'lms_course_status', 'ids' => $ids]); + } + } #[Hook('lms_course_status_delete')] @@ -157,7 +169,7 @@ final class LmsEntityHooks { ->condition('course_status', $course_status->id()) ->execute(); $queue = ($this->getQueue)('lms_delete_entities', TRUE); - foreach (array_chunk($lesson_status_ids, 10) as $ids) { + foreach (\array_chunk($lesson_status_ids, 10) as $ids) { $queue->createItem(['entity_type' => 'lms_lesson_status', 'ids' => $ids]); } // @todo Not sure that cache invalidation should occur here or on deletion. diff --git a/tests/src/Kernel/QueueDeletionTest.php b/tests/src/Kernel/QueueDeletionTest.php index 4d39b197ceb666661a294ba9183c47aba415f850..9698633e7ed0cac77547d14809060df7f841868b 100644 --- a/tests/src/Kernel/QueueDeletionTest.php +++ b/tests/src/Kernel/QueueDeletionTest.php @@ -73,12 +73,14 @@ final class QueueDeletionTest extends KernelTestBase { * @covers \Drupal\lms\Plugin\QueueWorker\DeleteEntitiesWorker */ public function testDeletion(): void { + $course = Group::create([ + 'type' => 'lms_course', + 'name' => 'Test course', + 'uid' => $this->createUser(), + ]); + $course->save(); $courseStatus = CourseStatus::create([ - 'gid' => Group::create([ - 'type' => 'lms_course', - 'name' => 'Test course', - 'uid' => $this->createUser(), - ])->save(), + 'gid' => $course->id(), 'uid' => $this->createUser(), ]); $courseStatus->save(); @@ -86,14 +88,17 @@ final class QueueDeletionTest extends KernelTestBase { // Create lesson statuses and answers based on the course status. $this->createContent($courseStatus); - // Delete the course status. - $courseStatus->delete(); + // Delete course. + $course->delete(); - // Asserts content still exists before running the queue. + // Course status and other entities should exist before running cron. + $reloaded = CourseStatus::load($courseStatus->id()); + self::assertInstanceOf(CourseStatus::class, $reloaded); $this->assertContentExists(); - // Run cron which also runs the queue. $this->container->get('cron')->run(); + $reloaded = CourseStatus::load($courseStatus->id()); + self::assertNull($reloaded, 'Course status should not exist.'); // Assert related lesson statuses and answers were deleted. $this->assertContentDeleted(); @@ -128,12 +133,12 @@ final class QueueDeletionTest extends KernelTestBase { private function assertContentExists(): void { for ($i = 0; $i < 10; $i++) { $reloaded = LessonStatus::load($this->lessonStatus[$i]->id()); - $this::assertInstanceOf(LessonStatus::class, $reloaded); - $this::assertSame($this->lessonStatus[$i]->id(), $reloaded->id()); + self::assertInstanceOf(LessonStatus::class, $reloaded); + self::assertSame($this->lessonStatus[$i]->id(), $reloaded->id()); for ($j = 0; $j < 10; $j++) { $reloaded = Answer::load($this->answer[$i][$j]->id()); - $this::assertInstanceOf(Answer::class, $reloaded); - $this::assertSame($this->answer[$i][$j]->id(), $reloaded->id()); + self::assertInstanceOf(Answer::class, $reloaded); + self::assertSame($this->answer[$i][$j]->id(), $reloaded->id()); } } } @@ -143,9 +148,9 @@ final class QueueDeletionTest extends KernelTestBase { */ private function assertContentDeleted(): void { for ($i = 0; $i < 10; $i++) { - $this::assertNull(LessonStatus::load($this->lessonStatus[$i]->id())); + self::assertNull(LessonStatus::load($this->lessonStatus[$i]->id())); for ($j = 0; $j < 10; $j++) { - $this::assertNull(Answer::load($this->answer[$i][$j]->id())); + self::assertNull(Answer::load($this->answer[$i][$j]->id())); } } }