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", type="string", nullable=true)
  65. */
  66. private $mpImage;
  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. */
  268. public function getMpImage()
  269. {
  270. // @TODO delete
  271. return 'Obsolete, use getImagePath() instead';
  272. }
  273. /**
  274. * @return ?int
  275. */
  276. public function getStatus()
  277. {
  278. return $this->status;
  279. }
  280. /**
  281. * @param ?int $status
  282. */
  283. public function setStatus($status)
  284. {
  285. $this->status = $status;
  286. }
  287. /**
  288. * Status helper function
  289. */
  290. public function isPublished(): bool
  291. {
  292. // Treating null as published since status is nullable, and it would have been treated like this before, though it should not happen.
  293. return $this->status === self::STATUS_PUBLISHED || $this->status === null;
  294. }
  295. /**
  296. * Status helper function
  297. */
  298. public function setPublished(): void
  299. {
  300. $this->status = self::STATUS_PUBLISHED;
  301. }
  302. /**
  303. * Status helper function
  304. */
  305. public function isUnpublished(): bool
  306. {
  307. return $this->status === self::STATUS_UNPUBLISHED;
  308. }
  309. /**
  310. * Status helper function
  311. */
  312. public function setUnpublished(): void
  313. {
  314. $this->status = self::STATUS_UNPUBLISHED;
  315. }
  316. /**
  317. * Status helper function
  318. */
  319. public function isDeleted(): bool
  320. {
  321. return $this->status === self::STATUS_DELETED;
  322. }
  323. /**
  324. * Status helper function
  325. */
  326. public function setDeleted(): void
  327. {
  328. $this->status = self::STATUS_DELETED;
  329. }
  330. /**
  331. * @return int
  332. */
  333. public function getSubquestionNr()
  334. {
  335. return $this->subquestionNr;
  336. }
  337. /**
  338. * @param int $subquestionNr
  339. *
  340. * Specify if the math problem is part of a bigger question with several subquestions
  341. *
  342. */
  343. public function setSubquestionNr($subquestionNr)
  344. {
  345. $this->subquestionNr = $subquestionNr;
  346. }
  347. public function getGeogebra(): ?string
  348. {
  349. return $this->geogebra;
  350. }
  351. /**
  352. * @return string|null
  353. */
  354. public function getFreeText(): ?string
  355. {
  356. return $this->freeText;
  357. }
  358. /**
  359. * @param string|null $freeText
  360. */
  361. public function setFreeText(?string $freeText): void
  362. {
  363. $this->freeText = $freeText;
  364. }
  365. /**
  366. * @return string|null
  367. */
  368. public function getIntroText(): ?string
  369. {
  370. return $this->introText;
  371. }
  372. /**
  373. * @param string|null $introText
  374. */
  375. public function setIntroText(?string $introText): void
  376. {
  377. $this->introText = $introText;
  378. }
  379. public function getCorrectAnswer(): ?Answer
  380. {
  381. $correctAnswer = null;
  382. foreach ($this->answers as $answer) {
  383. if ($answer->getIsCorrect()) {
  384. $correctAnswer = $answer;
  385. break;
  386. }
  387. }
  388. return $correctAnswer;
  389. }
  390. public function getWrongAnswer(): ?Answer
  391. {
  392. $wrongAnswer = null;
  393. foreach ($this->answers as $answer) {
  394. if (!$answer->getIsCorrect()) {
  395. $wrongAnswer = $answer;
  396. break;
  397. }
  398. }
  399. return $wrongAnswer;
  400. }
  401. /**
  402. * @return array|null
  403. */
  404. public function getCorrectAnswers(): ?array
  405. {
  406. // TODO - Jon's part, try to optimize for reusing
  407. $answerString = $this->getCorrectAnswer()->getAnswerText();
  408. $type = $this->getTemplate()->getType();
  409. if ($type === TemplateMathproblem::TYPE_DROPDOWN) {
  410. // Get correct answers dynamically
  411. $stringArray = explode("#", $answerString);
  412. $correctAnswers = [];
  413. $index = 0;
  414. foreach ($stringArray as $str) {
  415. // if string contains '|', it contains dropdown options
  416. if (!(strpos($str, '|') === false)) {
  417. $dropdownChoices = explode("|", $str);
  418. $correctAnswers[$index] = $dropdownChoices[0];
  419. foreach ($dropdownChoices as $choice){
  420. if(preg_match("#^\*#", $choice) || preg_match("#\*$#", $choice)){
  421. $choice = preg_replace("#^\*#", "", $choice);
  422. $choice = preg_replace("#\*$#", "", $choice);
  423. $correctAnswers[$index] = $choice;
  424. }
  425. }
  426. $index++;
  427. }
  428. }
  429. return $correctAnswers;
  430. } elseif ($type === TemplateMathproblem::TYPE_WORD_OPTIONS) {
  431. $stringArray = explode("#", $answerString);
  432. $correctAnswers = [];
  433. for ($i = 1; $i < count($stringArray); $i += 2) {
  434. $str = $stringArray[$i];
  435. if (in_array($str, ['<br>', '<br/>'])) {
  436. continue;
  437. }
  438. if (strpos($str, '|') === false) {
  439. $correctAnswers[] = 'default';
  440. } else {
  441. $correctAnswers[] = explode('|', $str)[1];
  442. }
  443. }
  444. return $correctAnswers;
  445. } else {
  446. // TODO: perhaps make this possible for other template types too.
  447. return null;
  448. }
  449. }
  450. /**
  451. * @return bool
  452. */
  453. public function getHasNext(){
  454. return $this->hasNext;
  455. }
  456. public function setHasNext($hasNext){
  457. $this->hasNext = $hasNext;
  458. }
  459. public function getAppendices(): Collection
  460. {
  461. return $this->appendices;
  462. }
  463. public function setAppendices(ArrayCollection $appendices): void
  464. {
  465. $this->appendices = $appendices;
  466. }
  467. public function getVideo(): ?Video
  468. {
  469. return $this->video;
  470. }
  471. function jsonSerialize(): mixed
  472. {
  473. // Initialize the $video property if it's not already initialized.
  474. $this->video ??= null;
  475. $data = [
  476. "id" => $this->getId(),
  477. "name" => $this->getName(),
  478. "description" => $this->getDescription(),
  479. "introText" => $this->getIntroText(),
  480. "freeText" => $this->getFreeText(),
  481. "hint" => $this->getHint(),
  482. "correctAnswer" => $this->getCorrectAnswer(),
  483. "template" => $this->getTemplate(),
  484. "status" => $this->getStatus(),
  485. "subquestionNr" => $this->getSubquestionNr(),
  486. "georgeBra" => $this->getGeogebra(),
  487. "solution" => $this->getSolution(),
  488. "image" => $this->getImagePath(),
  489. "appendices" => $this->getAppendices()->toArray(),
  490. "hasNext" => $this->getHasNext(),
  491. "video" => $this->getVideo(),
  492. "wrongAnswer" => $this->getWrongAnswer(),
  493. "topicDisplayName" => $this->getTemplate()->getTopic()->getDisplayName(),
  494. "options" => $this->getCorrectAnswer() && $this->getCorrectAnswer()->getAnswerText() ? explode("#", $this->getCorrectAnswer()->getAnswerText()) : [],
  495. "viewMode" => $this->getViewMode(),
  496. ];
  497. for ($i=0, $count = count($this->excludeKeys); $i < $count; $i++){
  498. if(isset( $data[ $this->excludeKeys[$i] ] )){
  499. unset( $data[ $this->excludeKeys[$i] ] );
  500. }
  501. }
  502. $answers = $this->getAnswers()->toArray();
  503. if($this->getTemplate()->getShouldShuffle()){
  504. shuffle($answers);
  505. }
  506. $data['answers'] = $answers;
  507. return $data;
  508. }
  509. /**
  510. * @param array $keys
  511. * @return $this
  512. */
  513. function exclude(array $keys): self{
  514. $this->excludeKeys = $keys;
  515. return $this;
  516. }
  517. /**
  518. * @param bool $useTransform
  519. * @return $this
  520. */
  521. function setUseTransform(bool $useTransform = true) : self{
  522. $this->useTransform = $useTransform;
  523. return $this;
  524. }
  525. public function getAllowInQuizOrWorksheet(): ?bool
  526. {
  527. return $this->allowInQuizOrWorksheet;
  528. }
  529. public function setAllowInQuizOrWorksheet(?bool $allowInQuizOrWorksheet): void
  530. {
  531. $this->allowInQuizOrWorksheet = $allowInQuizOrWorksheet;
  532. }
  533. public function getViewMode(): int
  534. {
  535. return $this->viewMode;
  536. }
  537. public function getDifficulty() {
  538. return $this->template->getDifficulty();
  539. }
  540. public function getCorrectAnswerText() {
  541. return $this->getCorrectAnswer()?->getAnswerText();
  542. }
  543. /*
  544. * Used for EasyAdmin problem form
  545. *
  546. * */
  547. public function getTemplateId() {
  548. return $this->getTemplate()->getId();
  549. }
  550. public function getType() {
  551. return $this?->getTemplate()?->getType();
  552. }
  553. public function setType(int $type) {
  554. $this->getTemplate()->setType($type);
  555. }
  556. public function getQuizzes(): Collection
  557. {
  558. $quizzes = new ArrayCollection();
  559. foreach ($this->quizzes as $quiz) {
  560. $quizzes->add($quiz->getQuiz());
  561. }
  562. return $quizzes;
  563. }
  564. public function getQuizId(): ?int {
  565. $quizzes = $this->getQuizzes();
  566. return $quizzes[0]?->getId();
  567. }
  568. public function getPublicationName(): string {
  569. $quizzes = $this->getQuizzes();
  570. return $quizzes[0]?->getPublication()?->getName();
  571. }
  572. public function isValidated(): bool
  573. {
  574. return $this->validated ?? false;
  575. }
  576. public function setValidated(bool $validated): void
  577. {
  578. $this->validated = $validated;
  579. }
  580. public function getProblemText(): string {
  581. return '';
  582. }
  583. public function setProblemText(string $text) {
  584. }
  585. public function __toString(): string
  586. {
  587. return $this->name;
  588. }
  589. public function setViewMode(int $viewMode): void
  590. {
  591. $this->viewMode = $viewMode;
  592. }
  593. public function getSnippet(): string
  594. {
  595. // Placeholder for EasyAdmin, value will be overwritten, so it does not matter what is returned.
  596. return '';
  597. }
  598. /**
  599. * @return ?ProblemDnd
  600. */
  601. public function getProblemDnd()
  602. {
  603. return $this->problemDnd;
  604. }
  605. public function getVideoReference(): ?VideoReference
  606. {
  607. return $this->videoReference;
  608. }
  609. public function setVideoReference(?VideoReference $videoReference): void
  610. {
  611. $this->videoReference = $videoReference;
  612. }
  613. public function isValidatedAndHasMediaFiles(): bool
  614. {
  615. if (!$this->isValidated()) {
  616. return false;
  617. }
  618. // detect if problem has media files
  619. if ($this->mediaCouplingExistButWithoutFile(ImageReference::class)) {
  620. return false;
  621. }
  622. if ($this->mediaCouplingExistButWithoutFile(AudioReference::class)) {
  623. return false;
  624. }
  625. // detect if answers have media files
  626. foreach ($this->getAnswers() as $answer) {
  627. if ($answer->mediaCouplingExistButWithoutFile(ImageReference::class)) {
  628. return false;
  629. }
  630. if ($answer->mediaCouplingExistButWithoutFile(AudioReference::class)) {
  631. return false;
  632. }
  633. }
  634. // detect if containers have media files if dnd
  635. if ($this->getType() === 4) {
  636. $bins = $this->getProblemDnd()->getBins();
  637. foreach ($bins as $bin) {
  638. if ($bin->mediaCouplingExistButWithoutFile(ImageReference::class)) {
  639. return false;
  640. }
  641. if ($bin->mediaCouplingExistButWithoutFile(AudioReference::class)) {
  642. return false;
  643. }
  644. }
  645. }
  646. return $this->isValidated();
  647. }
  648. public function getActiveBugReports(): array
  649. {
  650. $bugReports = $this->bugReports;
  651. $activeBugReports = [];
  652. foreach ($bugReports as $bugReport) {
  653. if ($bugReport->getDateResolved() === null) {
  654. $activeBugReports[] = $bugReport->getId();
  655. }
  656. }
  657. return $activeBugReports;
  658. }
  659. public function getCreatedAt(): \DateTime
  660. {
  661. return $this->createdAt;
  662. }
  663. public function setCreatedAt(\DateTime $createdAt): void
  664. {
  665. $this->createdAt = $createdAt;
  666. }
  667. public function getTopic(): ?string
  668. {
  669. return $this->getTemplate()?->getTopic()?->getName();
  670. }
  671. public function setTopic(Topic $topic)
  672. {
  673. $this->getTemplate()?->setTopic($topic);
  674. }
  675. public function getCategory(): ?string
  676. {
  677. return $this->getTemplate()?->getTopic()?->getCategory()?->getName();
  678. }
  679. public function getImage(){
  680. return $this->getImagePath();
  681. }
  682. public function getCmsImagePath(): string
  683. {
  684. if (!$this->imageReference) {
  685. return 'Mangler';
  686. }
  687. if (!$this->imageReference->getFilePath()) {
  688. return 'Mangler fil';
  689. }
  690. return $this->getImagePath();
  691. }
  692. public function getCmsAudioPath(): string
  693. {
  694. if (!$this->audioReference) {
  695. return 'Mangler';
  696. }
  697. if (!$this->audioReference->getFilePath()) {
  698. return 'Mangler fil';
  699. }
  700. return $this->getAudioPath();
  701. }
  702. }