src/CoreBundle/Entity/Mathproblem.php line 17

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace CoreBundle\Entity;
  4. use Doctrine\Common\Collections\ArrayCollection;
  5. use Doctrine\Common\Collections\Collection;
  6. use Doctrine\ORM\Mapping as ORM;
  7. use JsonSerializable;
  8. use StudentBundle\Service\Transform;
  9. /**
  10. * @ORM\Table("mathproblem")
  11. * @ORM\Entity(repositoryClass="MathproblemRepository")
  12. */
  13. class Mathproblem implements JsonSerializable, HasMediaInterface
  14. {
  15. use HasMediaTraits;
  16. const MATH_PROBLEM_QUIZ = 1;
  17. const MATH_PROBLEM_WORKSHEET = 2;
  18. const MATH_PROBLEM_QUIZ_AND_WORKSHEET = 3;
  19. const STATUS_PUBLISHED = 0;
  20. const STATUS_UNPUBLISHED = 1;
  21. const STATUS_DELETED = 2;
  22. /**
  23. * @var int
  24. *
  25. * @ORM\Column(name="id", type="integer")
  26. * @ORM\Id
  27. * @ORM\GeneratedValue(strategy="AUTO")
  28. */
  29. private $id;
  30. /**
  31. * @var bool
  32. * @ORM\Column(name="validated", type="boolean", nullable=true)
  33. */
  34. private $validated;
  35. /**
  36. * @var string
  37. *
  38. * @ORM\Column(name="name", type="string", nullable=false)
  39. */
  40. private $name;
  41. /**
  42. * @var string
  43. *
  44. * @ORM\Column(name="description", type="string", length=8000, nullable=true)
  45. */
  46. private $description;
  47. /**
  48. * The possible choices for this Mathproblem, will only be populated when type is 'multi'.
  49. *
  50. * @var ArrayCollection
  51. *
  52. * @ORM\OneToMany(targetEntity="Answer", mappedBy="mathproblem", cascade={"persist", "remove"})
  53. */
  54. private $answers;
  55. /**
  56. * @var ArrayCollection
  57. *
  58. * @ORM\OneToMany(targetEntity="BugReport", mappedBy="problem", cascade={"persist", "remove"})
  59. */
  60. private $bugReports;
  61. /**
  62. * @var string
  63. *
  64. * @ORM\Column(name="mp_image_obsolete", type="string", nullable=true)
  65. */
  66. private $mpImageObsolete;
  67. // @TODO delete
  68. /**
  69. * @ORM\ManyToOne(targetEntity="Video")
  70. * @ORM\JoinColumn(nullable=true)
  71. */
  72. private ?Video $video;
  73. /**
  74. * @var string
  75. * @ORM\Column(name="hint", type="string", length=2000, nullable=true)
  76. */
  77. private $hint;
  78. /**
  79. * @var string
  80. * @ORM\Column(name="video_hint", type="string", length=300, nullable=true)
  81. */
  82. private $videoHint;
  83. /**
  84. * @var string
  85. * @ORM\Column(name="solution", type="string", length=3000, nullable=true)
  86. */
  87. private $solution;
  88. /**
  89. * @var string
  90. *
  91. * @ORM\Column(name="free_text", type="text", nullable=true)
  92. */
  93. private $freeText;
  94. /**
  95. * @var string
  96. *
  97. * @ORM\Column(name="intro_text", type="text", nullable=true)
  98. */
  99. private $introText;
  100. /**
  101. * Mathproblem template
  102. *
  103. * @ORM\ManyToOne(targetEntity="TemplateMathproblem", inversedBy="mathproblems")
  104. * @ORM\JoinColumn(name="template_id", referencedColumnName="id")
  105. */
  106. private $template;
  107. /**
  108. * Relative path to the GeoGebra problem script file.
  109. *
  110. * @var string
  111. *
  112. * @ORM\Column(type="string", nullable=true)
  113. */
  114. private $geogebra;
  115. /**
  116. * 0 - available/published
  117. * 1 - unpublished
  118. * 2 - deleted
  119. *
  120. * @var ?int
  121. * @ORM\Column(name="status", type="integer", nullable=true)
  122. */
  123. private $status;
  124. /**
  125. * @var int
  126. * @ORM\Column(name="subquestion_nr", type="smallint", nullable=true)
  127. */
  128. private $subquestionNr;
  129. /**
  130. * Flag used to check if there's any next or previous problem
  131. * @var bool
  132. */
  133. private $hasNext;
  134. /**
  135. * Flag used to control if the problem should be searchable in a quiz
  136. * @var bool
  137. * @ORM\Column(name="allow_in_quiz_or_worksheet", type="boolean", nullable=true)
  138. */
  139. private $allowInQuizOrWorksheet;
  140. /**
  141. * @ORM\OneToMany(targetEntity="RelationQuizMathproblem", mappedBy="mathproblem")
  142. */
  143. private Collection $quizzes;
  144. /**
  145. * @var ArrayCollection
  146. *
  147. * @ORM\ManyToMany(targetEntity="Appendix", inversedBy="problems", cascade={"persist", "remove"})
  148. * @ORM\JoinTable(name="problems_appendices",
  149. * joinColumns={@ORM\JoinColumn(name="problem_id", referencedColumnName="id")},
  150. * inverseJoinColumns={@ORM\JoinColumn(name="appendix_id", referencedColumnName="id")})
  151. */
  152. private $appendices;
  153. /**
  154. * @ORM\OneToOne(targetEntity="VideoReference", inversedBy="mathproblem")
  155. * @ORM\JoinColumn(name="video_reference", referencedColumnName="id", nullable=true)
  156. */
  157. private ?VideoReference $videoReference;
  158. /**
  159. * @var \DateTime
  160. *
  161. * @ORM\Column(name="createdAt", type="datetime", nullable=false)
  162. */
  163. private \DateTime $createdAt;
  164. /**
  165. * 0 = default view
  166. * 1,2,3,... = alternate views, that can vary by problem type
  167. *
  168. * @var int
  169. * @ORM\Column(name="view_mode", type="smallint", options={"default" : 0})
  170. */
  171. private int $viewMode = 0;
  172. public array $excludeKeys = [];
  173. protected bool $useTransform = false;
  174. protected Transform $transform;
  175. /**
  176. * @ORM\OneToOne(targetEntity="ProblemDnd", mappedBy="problem")
  177. */
  178. private $problemDnd;
  179. static function setTransform(Transform $transform){
  180. self::$transform = $transform;
  181. }
  182. public function __construct()
  183. {
  184. $this->answers = new ArrayCollection();
  185. $this->appendices = new ArrayCollection();
  186. $this->quizzes = new ArrayCollection();
  187. $this->imageReference = null;
  188. $this->videoReference = null;
  189. $this->audioReference = null;
  190. }
  191. /**
  192. * @return int
  193. */
  194. public function getId()
  195. {
  196. return $this->id;
  197. }
  198. /**
  199. * @return string
  200. */
  201. public function getName()
  202. {
  203. return $this->name;
  204. }
  205. public function setName(string $name)
  206. {
  207. $this->name = $name;
  208. }
  209. /**
  210. * @return string
  211. */
  212. public function getDescription()
  213. {
  214. return $this->description ?? '';
  215. }
  216. public function setDescription(string $description)
  217. {
  218. $this->description = $description;
  219. }
  220. public function getAnswers(): Collection
  221. {
  222. return $this->answers;
  223. }
  224. public function getHint(): ?string
  225. {
  226. return $this->hint;
  227. }
  228. public function setHint(?string $hint)
  229. {
  230. $this->hint = $hint;
  231. }
  232. /**
  233. * @return string|null
  234. */
  235. public function getVideoHint(): ?string
  236. {
  237. return $this->videoHint;
  238. }
  239. /**
  240. * @param string|null $videoHint
  241. */
  242. public function setVideoHint(?string $videoHint): void
  243. {
  244. $this->videoHint = $videoHint;
  245. }
  246. public function getSolution(): ?string
  247. {
  248. return $this->solution;
  249. }
  250. public function setSolution(?string $solution)
  251. {
  252. $this->solution = $solution;
  253. }
  254. /**
  255. * @return TemplateMathproblem
  256. */
  257. public function getTemplate()
  258. {
  259. return $this->template;
  260. }
  261. public function setTemplate(TemplateMathproblem $template)
  262. {
  263. $this->template = $template;
  264. }
  265. /**
  266. * @return string
  267. * @deprecated Use getImagePath() instead. Kept while the media migration is in progress.
  268. */
  269. public function getMpImageObsolete()
  270. {
  271. // @TODO delete
  272. return 'Obsolete, use getImagePath() instead';
  273. }
  274. /**
  275. * @return ?int
  276. */
  277. public function getStatus()
  278. {
  279. return $this->status;
  280. }
  281. /**
  282. * @param ?int $status
  283. */
  284. public function setStatus($status)
  285. {
  286. $this->status = $status;
  287. }
  288. /**
  289. * Status helper function
  290. */
  291. public function isPublished(): bool
  292. {
  293. // Treating null as published since status is nullable, and it would have been treated like this before, though it should not happen.
  294. return $this->status === self::STATUS_PUBLISHED || $this->status === null;
  295. }
  296. /**
  297. * Status helper function
  298. */
  299. public function setPublished(): void
  300. {
  301. $this->status = self::STATUS_PUBLISHED;
  302. }
  303. /**
  304. * Status helper function
  305. */
  306. public function isUnpublished(): bool
  307. {
  308. return $this->status === self::STATUS_UNPUBLISHED;
  309. }
  310. /**
  311. * Status helper function
  312. */
  313. public function setUnpublished(): void
  314. {
  315. $this->status = self::STATUS_UNPUBLISHED;
  316. }
  317. /**
  318. * Status helper function
  319. */
  320. public function isDeleted(): bool
  321. {
  322. return $this->status === self::STATUS_DELETED;
  323. }
  324. /**
  325. * Status helper function
  326. */
  327. public function setDeleted(): void
  328. {
  329. $this->status = self::STATUS_DELETED;
  330. }
  331. /**
  332. * @return int
  333. */
  334. public function getSubquestionNr()
  335. {
  336. return $this->subquestionNr;
  337. }
  338. /**
  339. * @param int $subquestionNr
  340. *
  341. * Specify if the math problem is part of a bigger question with several subquestions
  342. *
  343. */
  344. public function setSubquestionNr($subquestionNr)
  345. {
  346. $this->subquestionNr = $subquestionNr;
  347. }
  348. public function getGeogebra(): ?string
  349. {
  350. return $this->geogebra;
  351. }
  352. /**
  353. * @return string|null
  354. */
  355. public function getFreeText(): ?string
  356. {
  357. return $this->freeText;
  358. }
  359. /**
  360. * @param string|null $freeText
  361. */
  362. public function setFreeText(?string $freeText): void
  363. {
  364. $this->freeText = $freeText;
  365. }
  366. /**
  367. * @return string|null
  368. */
  369. public function getIntroText(): ?string
  370. {
  371. return $this->introText;
  372. }
  373. /**
  374. * @param string|null $introText
  375. */
  376. public function setIntroText(?string $introText): void
  377. {
  378. $this->introText = $introText;
  379. }
  380. public function getCorrectAnswer(): ?Answer
  381. {
  382. $correctAnswer = null;
  383. foreach ($this->answers as $answer) {
  384. if ($answer->getIsCorrect()) {
  385. $correctAnswer = $answer;
  386. break;
  387. }
  388. }
  389. return $correctAnswer;
  390. }
  391. public function getWrongAnswer(): ?Answer
  392. {
  393. $wrongAnswer = null;
  394. foreach ($this->answers as $answer) {
  395. if (!$answer->getIsCorrect()) {
  396. $wrongAnswer = $answer;
  397. break;
  398. }
  399. }
  400. return $wrongAnswer;
  401. }
  402. /**
  403. * @return array|null
  404. */
  405. public function getCorrectAnswers(): ?array
  406. {
  407. // TODO - Jon's part, try to optimize for reusing
  408. $answerString = $this->getCorrectAnswer()->getAnswerText();
  409. $type = $this->getTemplate()->getType();
  410. if ($type === TemplateMathproblem::TYPE_DROPDOWN) {
  411. // Get correct answers dynamically
  412. $stringArray = explode("#", $answerString);
  413. $correctAnswers = [];
  414. $index = 0;
  415. foreach ($stringArray as $str) {
  416. // if string contains '|', it contains dropdown options
  417. if (!(strpos($str, '|') === false)) {
  418. $dropdownChoices = explode("|", $str);
  419. $correctAnswers[$index] = $dropdownChoices[0];
  420. foreach ($dropdownChoices as $choice){
  421. if(preg_match("#^\*#", $choice) || preg_match("#\*$#", $choice)){
  422. $choice = preg_replace("#^\*#", "", $choice);
  423. $choice = preg_replace("#\*$#", "", $choice);
  424. $correctAnswers[$index] = $choice;
  425. }
  426. }
  427. $index++;
  428. }
  429. }
  430. return $correctAnswers;
  431. } elseif ($type === TemplateMathproblem::TYPE_WORD_OPTIONS) {
  432. $stringArray = explode("#", $answerString);
  433. $correctAnswers = [];
  434. for ($i = 1; $i < count($stringArray); $i += 2) {
  435. $str = $stringArray[$i];
  436. if (in_array($str, ['<br>', '<br/>'])) {
  437. continue;
  438. }
  439. if (strpos($str, '|') === false) {
  440. $correctAnswers[] = 'default';
  441. } else {
  442. $correctAnswers[] = explode('|', $str)[1];
  443. }
  444. }
  445. return $correctAnswers;
  446. } else {
  447. // TODO: perhaps make this possible for other template types too.
  448. return null;
  449. }
  450. }
  451. /**
  452. * @return bool
  453. */
  454. public function getHasNext(){
  455. return $this->hasNext;
  456. }
  457. public function setHasNext($hasNext){
  458. $this->hasNext = $hasNext;
  459. }
  460. public function getAppendices(): Collection
  461. {
  462. return $this->appendices;
  463. }
  464. public function setAppendices(ArrayCollection $appendices): void
  465. {
  466. $this->appendices = $appendices;
  467. }
  468. public function getVideo(): ?Video
  469. {
  470. return $this->video;
  471. }
  472. function jsonSerialize(): mixed
  473. {
  474. // Initialize the $video property if it's not already initialized.
  475. $this->video ??= null;
  476. $data = [
  477. "id" => $this->getId(),
  478. "name" => $this->getName(),
  479. "description" => $this->getDescription(),
  480. "introText" => $this->getIntroText(),
  481. "freeText" => $this->getFreeText(),
  482. "hint" => $this->getHint(),
  483. "correctAnswer" => $this->getCorrectAnswer(),
  484. "template" => $this->getTemplate(),
  485. "status" => $this->getStatus(),
  486. "subquestionNr" => $this->getSubquestionNr(),
  487. "georgeBra" => $this->getGeogebra(),
  488. "solution" => $this->getSolution(),
  489. "image" => $this->getImagePath(),
  490. "appendices" => $this->getAppendices()->toArray(),
  491. "hasNext" => $this->getHasNext(),
  492. "video" => $this->getVideo(),
  493. "wrongAnswer" => $this->getWrongAnswer(),
  494. "topicDisplayName" => $this->getTemplate()->getTopic()->getDisplayName(),
  495. "options" => $this->getCorrectAnswer() && $this->getCorrectAnswer()->getAnswerText() ? explode("#", $this->getCorrectAnswer()->getAnswerText()) : [],
  496. "viewMode" => $this->getViewMode(),
  497. ];
  498. for ($i=0, $count = count($this->excludeKeys); $i < $count; $i++){
  499. if(isset( $data[ $this->excludeKeys[$i] ] )){
  500. unset( $data[ $this->excludeKeys[$i] ] );
  501. }
  502. }
  503. $answers = $this->getAnswers()->toArray();
  504. if($this->getTemplate()->getShouldShuffle()){
  505. shuffle($answers);
  506. }
  507. $data['answers'] = $answers;
  508. return $data;
  509. }
  510. /**
  511. * @param array $keys
  512. * @return $this
  513. */
  514. function exclude(array $keys): self{
  515. $this->excludeKeys = $keys;
  516. return $this;
  517. }
  518. /**
  519. * @param bool $useTransform
  520. * @return $this
  521. */
  522. function setUseTransform(bool $useTransform = true) : self{
  523. $this->useTransform = $useTransform;
  524. return $this;
  525. }
  526. public function getAllowInQuizOrWorksheet(): ?bool
  527. {
  528. return $this->allowInQuizOrWorksheet;
  529. }
  530. public function setAllowInQuizOrWorksheet(?bool $allowInQuizOrWorksheet): void
  531. {
  532. $this->allowInQuizOrWorksheet = $allowInQuizOrWorksheet;
  533. }
  534. public function getViewMode(): int
  535. {
  536. return $this->viewMode;
  537. }
  538. public function getDifficulty() {
  539. return $this->template->getDifficulty();
  540. }
  541. public function getCorrectAnswerText() {
  542. return $this->getCorrectAnswer()?->getAnswerText();
  543. }
  544. /*
  545. * Used for EasyAdmin problem form
  546. *
  547. * */
  548. public function getTemplateId() {
  549. return $this->getTemplate()->getId();
  550. }
  551. public function getType() {
  552. return $this?->getTemplate()?->getType();
  553. }
  554. public function setType(int $type) {
  555. $this->getTemplate()->setType($type);
  556. }
  557. public function getQuizzes(): Collection
  558. {
  559. $quizzes = new ArrayCollection();
  560. foreach ($this->quizzes as $quiz) {
  561. $quizzes->add($quiz->getQuiz());
  562. }
  563. return $quizzes;
  564. }
  565. public function getQuizId(): ?int {
  566. $quizzes = $this->getQuizzes();
  567. return $quizzes[0]?->getId();
  568. }
  569. public function getPublicationName(): string {
  570. $quizzes = $this->getQuizzes();
  571. return $quizzes[0]?->getPublication()?->getName();
  572. }
  573. public function isValidated(): bool
  574. {
  575. return $this->validated ?? false;
  576. }
  577. public function setValidated(bool $validated): void
  578. {
  579. $this->validated = $validated;
  580. }
  581. public function getProblemText(): string {
  582. return '';
  583. }
  584. public function setProblemText(string $text) {
  585. }
  586. public function __toString(): string
  587. {
  588. return $this->name;
  589. }
  590. public function setViewMode(int $viewMode): void
  591. {
  592. $this->viewMode = $viewMode;
  593. }
  594. public function getSnippet(): string
  595. {
  596. // Placeholder for EasyAdmin, value will be overwritten, so it does not matter what is returned.
  597. return '';
  598. }
  599. /**
  600. * @return ?ProblemDnd
  601. */
  602. public function getProblemDnd()
  603. {
  604. return $this->problemDnd;
  605. }
  606. public function getVideoReference(): ?VideoReference
  607. {
  608. return $this->videoReference;
  609. }
  610. public function setVideoReference(?VideoReference $videoReference): void
  611. {
  612. $this->videoReference = $videoReference;
  613. }
  614. public function isValidatedAndHasMediaFiles(): bool
  615. {
  616. if (!$this->isValidated()) {
  617. return false;
  618. }
  619. // detect if problem has media files
  620. if ($this->mediaCouplingExistButWithoutFile(ImageReference::class)) {
  621. return false;
  622. }
  623. if ($this->mediaCouplingExistButWithoutFile(AudioReference::class)) {
  624. return false;
  625. }
  626. // detect if answers have media files
  627. foreach ($this->getAnswers() as $answer) {
  628. if ($answer->mediaCouplingExistButWithoutFile(ImageReference::class)) {
  629. return false;
  630. }
  631. if ($answer->mediaCouplingExistButWithoutFile(AudioReference::class)) {
  632. return false;
  633. }
  634. }
  635. // detect if containers have media files if dnd
  636. if ($this->getType() === 4) {
  637. $bins = $this->getProblemDnd()->getBins();
  638. foreach ($bins as $bin) {
  639. if ($bin->mediaCouplingExistButWithoutFile(ImageReference::class)) {
  640. return false;
  641. }
  642. if ($bin->mediaCouplingExistButWithoutFile(AudioReference::class)) {
  643. return false;
  644. }
  645. }
  646. }
  647. return $this->isValidated();
  648. }
  649. public function getActiveBugReports(): array
  650. {
  651. $bugReports = $this->bugReports;
  652. $activeBugReports = [];
  653. foreach ($bugReports as $bugReport) {
  654. if ($bugReport->getDateResolved() === null) {
  655. $activeBugReports[] = $bugReport->getId();
  656. }
  657. }
  658. return $activeBugReports;
  659. }
  660. public function getCreatedAt(): \DateTime
  661. {
  662. return $this->createdAt;
  663. }
  664. public function setCreatedAt(\DateTime $createdAt): void
  665. {
  666. $this->createdAt = $createdAt;
  667. }
  668. public function getTopic(): ?string
  669. {
  670. return $this->getTemplate()?->getTopic()?->getName();
  671. }
  672. public function setTopic(Topic $topic)
  673. {
  674. $this->getTemplate()?->setTopic($topic);
  675. }
  676. public function getCategory(): ?string
  677. {
  678. return $this->getTemplate()?->getTopic()?->getCategory()?->getName();
  679. }
  680. public function getImage(){
  681. return $this->getImagePath();
  682. }
  683. public function getCmsImagePath(): string
  684. {
  685. if (!$this->imageReference) {
  686. return 'Mangler';
  687. }
  688. if (!$this->imageReference->getFilePath()) {
  689. return 'Mangler fil';
  690. }
  691. return $this->getImagePath();
  692. }
  693. public function getCmsAudioPath(): string
  694. {
  695. if (!$this->audioReference) {
  696. return 'Mangler';
  697. }
  698. if (!$this->audioReference->getFilePath()) {
  699. return 'Mangler fil';
  700. }
  701. return $this->getAudioPath();
  702. }
  703. }