src/Entity/Core/Quiz/Quiz.php line 25

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Entity\Core\Quiz;
  4. use App\Entity\Core\Classroom\ClassroomType;
  5. use App\Entity\Core\HasMediaInterface;
  6. use App\Entity\Core\HasMediaTraits;
  7. use App\Entity\Core\Mathproblem;
  8. use App\Entity\Core\Publication;
  9. use App\Entity\Core\RelationQuizMathproblem;
  10. use App\Entity\Core\Topic\Topic;
  11. use App\Entity\Core\VideoReference;
  12. use App\Entity\User\User;
  13. use App\Services\Core\ActivityTypeMappingService;
  14. use DateTime;
  15. use Doctrine\Common\Collections\ArrayCollection;
  16. use Doctrine\Common\Collections\Collection;
  17. use Doctrine\ORM\Mapping as ORM;
  18. use Symfony\Component\HttpFoundation\Exception\BadRequestException;
  19. #[ORM\Table('quiz')]
  20. #[ORM\Entity(repositoryClass: QuizRepository::class)]
  21. class Quiz implements HasMediaInterface
  22. {
  23. use HasMediaTraits;
  24. /**
  25. * @var int
  26. */
  27. #[ORM\Column(name: 'id', type: 'integer')]
  28. #[ORM\Id]
  29. #[ORM\GeneratedValue(strategy: 'AUTO')]
  30. private $id;
  31. /**
  32. * @var int
  33. */
  34. #[ORM\Column(type: 'integer', nullable: false, options: ['default' => 0])]
  35. private $duration;
  36. /**
  37. * @var string
  38. */
  39. #[ORM\Column(name: 'name', type: 'string', length: 255, nullable: true)]
  40. private $name;
  41. /**
  42. * @var string
  43. */
  44. #[ORM\Column(name: 'description', type: 'string', length: 500, nullable: true)]
  45. private $description;
  46. /**
  47. * @var bool
  48. */
  49. #[ORM\Column(name: 'enabled', type: 'boolean', options: ['default' => 1])]
  50. private $enabled;
  51. /**
  52. * @var Publication
  53. */
  54. #[ORM\JoinColumn(name: 'publication_id', referencedColumnName: 'id')]
  55. #[ORM\ManyToOne(targetEntity: Publication::class, inversedBy: 'quizzes')]
  56. private $publication;
  57. #[ORM\OneToMany(targetEntity: RelationQuizMathproblem::class, mappedBy: 'quiz', cascade: ['persist'])]
  58. private Collection $mathproblems;
  59. /**
  60. * @var array
  61. */
  62. #[ORM\Column(name: 'problem_order', type: 'simple_array', nullable: true)]
  63. private $problemOrder;
  64. /**
  65. * @var array
  66. */
  67. #[ORM\Column(name: 'mathproblems_for_templates', type: 'simple_array', nullable: true)]
  68. private $mathproblemsForTemplates;
  69. /**
  70. * @var bool
  71. */
  72. #[ORM\Column(name: 'reusable', type: 'boolean', nullable: false)]
  73. private $reusable;
  74. /**
  75. * @var bool
  76. */
  77. #[ORM\Column(name: 'abacus', type: 'boolean', nullable: true, options: ['default' => 0])]
  78. private $abacus;
  79. /**
  80. * @var bool
  81. */
  82. #[ORM\Column(name: 'target_student', type: 'boolean', nullable: true, options: ['default' => 0])]
  83. private $targetStudent;
  84. /**
  85. * @var Topic
  86. */
  87. #[ORM\JoinColumn(name: 'topic', referencedColumnName: 'id')]
  88. #[ORM\ManyToOne(targetEntity: Topic::class)]
  89. private $topic;
  90. /**
  91. * @var DateTime
  92. */
  93. #[ORM\Column(name: 'createdAt', type: 'datetime', nullable: false)]
  94. private $createdAt;
  95. /**
  96. * @var User
  97. */
  98. #[ORM\JoinColumn(name: 'created_by', referencedColumnName: 'id')]
  99. #[ORM\ManyToOne(targetEntity: User::class)]
  100. private $createdBy;
  101. /**
  102. * @var User[]
  103. */
  104. #[ORM\JoinTable(name: 'quiz_teacher')]
  105. #[ORM\JoinColumn(name: 'quiz_id', referencedColumnName: 'id')]
  106. #[ORM\InverseJoinColumn(name: 'teacher_id', referencedColumnName: 'id')]
  107. #[ORM\ManyToMany(targetEntity: User::class)]
  108. private $teachers;
  109. /**
  110. * @var QuizSession[]
  111. */
  112. #[ORM\OneToMany(targetEntity: QuizSession::class, mappedBy: 'quiz', cascade: ['persist'])]
  113. private $quizSessions;
  114. /**
  115. * @var ClassroomType
  116. */
  117. #[ORM\JoinColumn(name: 'level', referencedColumnName: 'id', nullable: true)]
  118. #[ORM\ManyToOne(targetEntity: ClassroomType::class)]
  119. private $level;
  120. /**
  121. * @var QuizType|null
  122. */
  123. #[ORM\JoinColumn(name: 'type', referencedColumnName: 'id', nullable: true)]
  124. #[ORM\ManyToOne(targetEntity: QuizType::class)]
  125. private $type;
  126. /**
  127. * @var array
  128. */
  129. #[ORM\Column(name: 'color_wrong_count_threshold', type: 'simple_array', nullable: true)]
  130. private $colorWrongCountThreshold;
  131. /**
  132. * @var array
  133. */
  134. #[ORM\Column(name: 'color_percentage_threshold', type: 'simple_array', nullable: true)]
  135. private $colorPercentageThreshold;
  136. /**
  137. * @var string
  138. */
  139. #[ORM\Column(name: 'ibook_id', type: 'string', length: 255, nullable: true)]
  140. private ?string $iBookId;
  141. #[ORM\JoinColumn(name: 'video_reference', referencedColumnName: 'id', nullable: true)]
  142. #[ORM\OneToOne(targetEntity: VideoReference::class, inversedBy: 'quiz')]
  143. private ?VideoReference $videoReference;
  144. public function __construct()
  145. {
  146. $this->enabled = true;
  147. $this->mathproblems = new ArrayCollection();
  148. $this->teachers = new ArrayCollection();
  149. $this->quizSessions = new ArrayCollection();
  150. $this->createdAt = new DateTime("now");
  151. $this->imageReference = null;
  152. $this->videoReference = null;
  153. $this->audioReference = null;
  154. }
  155. /**
  156. * Get id.
  157. *
  158. * @return int
  159. */
  160. public function getId()
  161. {
  162. return $this->id;
  163. }
  164. /**
  165. * @return mixed
  166. */
  167. public function getName()
  168. {
  169. return $this->name;
  170. }
  171. /**
  172. * @param string $name
  173. *
  174. * @return $this
  175. */
  176. public function setName($name)
  177. {
  178. $this->name = $name;
  179. return $this;
  180. }
  181. /**
  182. * Set duration.
  183. *
  184. * @param int $duration
  185. *
  186. * @return $this
  187. */
  188. public function setDuration($duration)
  189. {
  190. $this->duration = $duration;
  191. return $this;
  192. }
  193. /**
  194. * Get duration.
  195. *
  196. * @return int
  197. */
  198. public function getDuration()
  199. {
  200. return $this->duration;
  201. }
  202. /**
  203. * @return ArrayCollection|Mathproblem[]
  204. */
  205. public function getMathproblems(): Collection
  206. {
  207. $mathproblems = new ArrayCollection();
  208. foreach ($this->mathproblems as $mathproblem) {
  209. $mathproblems->add($mathproblem->getMathproblem());
  210. }
  211. return $mathproblems;
  212. }
  213. /**
  214. * @return ArrayCollection|Mathproblem[]
  215. */
  216. public function getEnabledMathproblems(): Collection
  217. {
  218. $mathproblems = new ArrayCollection();
  219. foreach ($this->mathproblems as $mathproblem) {
  220. if ($mathproblem->getEnabled()) {
  221. $mathproblems->add($mathproblem->getMathproblem());
  222. }
  223. }
  224. return $mathproblems;
  225. }
  226. /**
  227. * @return ArrayCollection|Mathproblem[]
  228. */
  229. public function getEnabledAndPublishedMathproblems(): Collection
  230. {
  231. $mathproblems = new ArrayCollection();
  232. foreach ($this->mathproblems as $mathproblem) {
  233. if ($mathproblem->getEnabled() && $mathproblem->getMathproblem()->isPublished()) {
  234. $mathproblems->add($mathproblem->getMathproblem());
  235. }
  236. }
  237. return $mathproblems;
  238. }
  239. /**
  240. * @return array
  241. */
  242. public function getSortedMathproblems()
  243. {
  244. $problemOrder = $this->problemOrder;
  245. $mathproblems = $this->getEnabledMathproblems();
  246. if ($problemOrder && count($problemOrder) == count($mathproblems)) {
  247. $newmathproblems = [];
  248. foreach ($mathproblems as $mp) {
  249. $newkey = array_search($mp->getID(), $problemOrder);
  250. $newmathproblems[$newkey] = $mp;
  251. }
  252. ksort($newmathproblems);
  253. return $newmathproblems;
  254. } else {
  255. return $this->getEnabledMathproblems()->toArray();
  256. }
  257. }
  258. /**
  259. * @param Mathproblem[]
  260. */
  261. public function setMathproblems(array $mathproblems)
  262. {
  263. // don't allow setting problems to a quiz that already has problems
  264. if ($this->mathproblems->count() > 0) {
  265. throw new BadRequestException('Cannot set mathproblems on a quiz that already has mathproblems');
  266. }
  267. foreach ($mathproblems as $mathproblem) {
  268. $this->addMathproblem($mathproblem);
  269. }
  270. }
  271. /**
  272. * This collection can be used to edit the relations directly,
  273. * remember to also remove the entity, when removing a relation.
  274. */
  275. public function getMathproblemRelations(): ArrayCollection|Collection
  276. {
  277. return $this->mathproblems;
  278. }
  279. /**
  280. * @param Mathproblem
  281. */
  282. public function addMathproblem($mathproblem)
  283. {
  284. $relation = new RelationQuizMathproblem();
  285. $relation->setQuiz($this);
  286. $relation->setMathproblem($mathproblem);
  287. $relation->setEnabled(true);
  288. $this->mathproblems->add($relation);
  289. }
  290. public function enableProblem(Mathproblem $problem): void
  291. {
  292. foreach ($this->mathproblems as $relation) {
  293. if ($relation->getMathproblem()->getId() === $problem->getId()) {
  294. $relation->setEnabled(true);
  295. return;
  296. }
  297. }
  298. }
  299. /**
  300. * @return bool
  301. */
  302. public function getReusable()
  303. {
  304. return $this->reusable;
  305. }
  306. /**
  307. * @param bool $reusable
  308. *
  309. * @return $this
  310. */
  311. public function setReusable($reusable)
  312. {
  313. $this->reusable = $reusable;
  314. return $this;
  315. }
  316. /**
  317. * @return DateTime
  318. */
  319. public function getCreatedAt()
  320. {
  321. return $this->createdAt;
  322. }
  323. /**
  324. * @return User
  325. */
  326. public function getCreatedBy()
  327. {
  328. return $this->createdBy;
  329. }
  330. /**
  331. * @param User $createdBy
  332. *
  333. * @return $this
  334. */
  335. public function setCreatedBy($createdBy)
  336. {
  337. $this->createdBy = $createdBy;
  338. return $this;
  339. }
  340. public function getTeachers(): Collection
  341. {
  342. return $this->teachers;
  343. }
  344. /**
  345. * @return $this
  346. */
  347. public function addTeacher(User $teacher)
  348. {
  349. $this->teachers->add($teacher);
  350. return $this;
  351. }
  352. public function getQuizSessions(): Collection
  353. {
  354. return $this->quizSessions;
  355. }
  356. /**
  357. * @param QuizSession[] $quizSessions
  358. *
  359. * @return $this
  360. */
  361. public function setQuizSessions($quizSessions)
  362. {
  363. $this->quizSessions = $quizSessions;
  364. return $this;
  365. }
  366. /**
  367. * @return $this
  368. */
  369. public function addQuizSession(QuizSession $quizSession)
  370. {
  371. $this->quizSessions->add($quizSession);
  372. $quizSession->setQuiz($this);
  373. return $this;
  374. }
  375. /**
  376. * @return bool
  377. */
  378. public function isAbacus()
  379. {
  380. return $this->abacus;
  381. }
  382. /**
  383. * @param bool $abacus
  384. */
  385. public function setAbacus($abacus)
  386. {
  387. $this->abacus = $abacus;
  388. }
  389. /**
  390. * @return bool | null
  391. */
  392. public function isTargetStudent()
  393. {
  394. return $this->targetStudent;
  395. }
  396. public function setTargetStudent(bool $targetStudent)
  397. {
  398. $this->targetStudent = $targetStudent;
  399. }
  400. /**
  401. * @return Topic | null
  402. */
  403. public function getTopic()
  404. {
  405. return $this->topic;
  406. }
  407. public function setTopic(Topic $topic)
  408. {
  409. $this->topic = $topic;
  410. }
  411. public function getLevel(): ?ClassroomType
  412. {
  413. return $this->level;
  414. }
  415. public function setLevel(?ClassroomType $level): void
  416. {
  417. $this->level = $level;
  418. }
  419. public function getProblemOrder(): ?array
  420. {
  421. return $this->problemOrder;
  422. }
  423. public function setProblemOrder(array $problemOrder)
  424. {
  425. $this->problemOrder = $problemOrder;
  426. }
  427. /** Removes IDs of disabled/removed problems and appends IDs of enabled problems not yet in the order. */
  428. public function syncProblemOrder(): void
  429. {
  430. $enabledIds = $this->getEnabledMathproblems()->map(fn($mp) => $mp->getId())->toArray();
  431. $order = array_values(array_filter($this->problemOrder ?? [], fn($id) => in_array($id, $enabledIds)));
  432. $missing = array_diff($enabledIds, $order);
  433. $this->problemOrder = array_merge($order, array_values($missing));
  434. }
  435. public function getType(): QuizType | null
  436. {
  437. return $this->type;
  438. }
  439. public function setType(?QuizType $type): void
  440. {
  441. $this->type = $type;
  442. }
  443. /**
  444. * @return array|null
  445. */
  446. public function getMathproblemsForTemplates()
  447. {
  448. return $this->mathproblemsForTemplates;
  449. }
  450. public function setMathproblemsForTemplates(array $mathproblemsForTemplates): void
  451. {
  452. $this->mathproblemsForTemplates = $mathproblemsForTemplates;
  453. }
  454. public function getDescription(): string | null
  455. {
  456. return $this->description;
  457. }
  458. public function setDescription(string $description): void
  459. {
  460. $this->description = $description;
  461. }
  462. public function getColorWrongCountThreshold(): ?array
  463. {
  464. return $this->colorWrongCountThreshold;
  465. }
  466. public function getColorPercentageThreshold(): ?array
  467. {
  468. return $this->colorPercentageThreshold;
  469. }
  470. public function getPublication(): ?Publication
  471. {
  472. return $this->publication;
  473. }
  474. public function setPublication(Publication $publication): void
  475. {
  476. $this->publication = $publication;
  477. }
  478. public function getEnabled(): bool
  479. {
  480. return $this->enabled;
  481. }
  482. public function setEnabled(string $enabled): void
  483. {
  484. $this->enabled = $enabled;
  485. }
  486. public function __toString()
  487. {
  488. return ($this->name ? $this->name . ' ' : '') . '(' . $this->id . ')';
  489. }
  490. public function getCmsImagePath(): string
  491. {
  492. if (!$this->imageReference) {
  493. return 'Mangler';
  494. }
  495. if (!$this->imageReference->getFilePath()) {
  496. return 'Mangler fil';
  497. }
  498. return $this->getImagePath();
  499. }
  500. public function getVideoReference(): ?VideoReference
  501. {
  502. return $this->videoReference;
  503. }
  504. public function setVideoReference(?VideoReference $videoReference): void
  505. {
  506. $this->videoReference = $videoReference;
  507. }
  508. public function getIBookId(): ?string
  509. {
  510. return $this->iBookId;
  511. }
  512. public function setIBookId(): void
  513. {
  514. $part1 = bin2hex(random_bytes(4));
  515. $part2 = $this->id;
  516. $this->iBookId = $part1 . '_' . $part2;
  517. }
  518. public function isABaCusQuiz(): bool
  519. {
  520. // abacus/kvik quizzes have abacus=1 and targetStudent=0
  521. return $this->abacus && !$this->targetStudent;
  522. }
  523. public function setIsABaCusQuiz($isABaCusQuiz): void
  524. {
  525. if ($isABaCusQuiz) {
  526. $this->abacus = true;
  527. $this->targetStudent = false;
  528. } else {
  529. $this->abacus = null;
  530. $this->targetStudent = null;
  531. }
  532. }
  533. public function getProblemCount(): int
  534. {
  535. return count($this->getEnabledMathproblems());
  536. }
  537. public function toLightweightArray(ActivityTypeMappingService $activityTypeMappingService): array
  538. {
  539. return [
  540. 'id' => $this->getId(),
  541. 'type' => $activityTypeMappingService->getActivityTypeFromClass(Quiz::class),
  542. 'name' => $this->getName(),
  543. 'description' => $this->getDescription(),
  544. 'duration' => $this->getDuration(),
  545. 'enabled' => $this->getEnabled(),
  546. 'problemCount' => $this->getProblemCount(),
  547. 'classroomType' => $this->getLevel()?->getId(),
  548. ];
  549. }
  550. }