question.class.php 72 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167
  1. <?php
  2. /* For licensing terms, see /license.txt */
  3. /**
  4. * Class Question
  5. *
  6. * This class allows to instantiate an object of type Question
  7. *
  8. * @author Olivier Brouckaert, original author
  9. * @author Patrick Cool, LaTeX support
  10. * @author Julio Montoya <gugli100@gmail.com> lot of bug fixes
  11. * @author hubert.borderiou@grenet.fr - add question categories
  12. * @package chamilo.exercise
  13. */
  14. abstract class Question
  15. {
  16. public $id;
  17. public $question;
  18. public $description;
  19. public $weighting;
  20. public $position;
  21. public $type;
  22. public $level;
  23. public $picture;
  24. public $exerciseList; // array with the list of exercises which this question is in
  25. public $category_list;
  26. public $parent_id;
  27. public $category;
  28. public $isContent;
  29. public $course;
  30. public static $typePicture = 'new_question.png';
  31. public static $explanationLangVar = '';
  32. public $question_table_class = 'table table-striped';
  33. public static $questionTypes = array(
  34. UNIQUE_ANSWER => array('unique_answer.class.php', 'UniqueAnswer'),
  35. MULTIPLE_ANSWER => array('multiple_answer.class.php', 'MultipleAnswer'),
  36. FILL_IN_BLANKS => array('fill_blanks.class.php', 'FillBlanks'),
  37. MATCHING => array('matching.class.php', 'Matching'),
  38. FREE_ANSWER => array('freeanswer.class.php', 'FreeAnswer'),
  39. ORAL_EXPRESSION => array('oral_expression.class.php', 'OralExpression'),
  40. HOT_SPOT => array('hotspot.class.php', 'HotSpot'),
  41. HOT_SPOT_DELINEATION => array('hotspot.class.php', 'HotspotDelineation'),
  42. MULTIPLE_ANSWER_COMBINATION => array('multiple_answer_combination.class.php', 'MultipleAnswerCombination'),
  43. UNIQUE_ANSWER_NO_OPTION => array('unique_answer_no_option.class.php', 'UniqueAnswerNoOption'),
  44. MULTIPLE_ANSWER_TRUE_FALSE => array('multiple_answer_true_false.class.php', 'MultipleAnswerTrueFalse'),
  45. MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE => array(
  46. 'multiple_answer_combination_true_false.class.php',
  47. 'MultipleAnswerCombinationTrueFalse'
  48. ),
  49. GLOBAL_MULTIPLE_ANSWER => array('global_multiple_answer.class.php', 'GlobalMultipleAnswer'),
  50. CALCULATED_ANSWER => array('calculated_answer.class.php', 'CalculatedAnswer'),
  51. UNIQUE_ANSWER_IMAGE => ['UniqueAnswerImage.php', 'UniqueAnswerImage'],
  52. DRAGGABLE => ['Draggable.php', 'Draggable'],
  53. MATCHING_DRAGGABLE => ['MatchingDraggable.php', 'MatchingDraggable'],
  54. //MEDIA_QUESTION => array('media_question.class.php' , 'MediaQuestion')
  55. ANNOTATION => ['Annotation.php', 'Annotation'],
  56. READING_COMPREHENSION => ['ReadingComprehension.php', 'ReadingComprehension']
  57. );
  58. /**
  59. * constructor of the class
  60. *
  61. * @author Olivier Brouckaert
  62. */
  63. public function __construct()
  64. {
  65. $this->id = 0;
  66. $this->question = '';
  67. $this->description = '';
  68. $this->weighting = 0;
  69. $this->position = 1;
  70. $this->picture = '';
  71. $this->level = 1;
  72. $this->category = 0;
  73. // This variable is used when loading an exercise like an scenario with
  74. // an special hotspot: final_overlap, final_missing, final_excess
  75. $this->extra = '';
  76. $this->exerciseList = array();
  77. $this->course = api_get_course_info();
  78. $this->category_list = array();
  79. $this->parent_id = 0;
  80. }
  81. /**
  82. * @return int|null
  83. */
  84. public function getIsContent()
  85. {
  86. $isContent = null;
  87. if (isset($_REQUEST['isContent'])) {
  88. $isContent = intval($_REQUEST['isContent']);
  89. }
  90. return $this->isContent = $isContent;
  91. }
  92. /**
  93. * Reads question information from the data base
  94. *
  95. * @param int $id - question ID
  96. * @param int $course_id
  97. *
  98. * @return Question
  99. *
  100. * @author Olivier Brouckaert
  101. */
  102. public static function read($id, $course_id = null)
  103. {
  104. $id = intval($id);
  105. if (!empty($course_id)) {
  106. $course_info = api_get_course_info_by_id($course_id);
  107. } else {
  108. $course_info = api_get_course_info();
  109. }
  110. $course_id = $course_info['real_id'];
  111. if (empty($course_id) || $course_id == -1) {
  112. return false;
  113. }
  114. $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
  115. $TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
  116. $sql = "SELECT question, description, ponderation, position, type, picture, level, extra
  117. FROM $TBL_QUESTIONS
  118. WHERE c_id = $course_id AND id = $id ";
  119. $result = Database::query($sql);
  120. // if the question has been found
  121. if ($object = Database::fetch_object($result)) {
  122. $objQuestion = self::getInstance($object->type);
  123. if (!empty($objQuestion)) {
  124. $objQuestion->id = $id;
  125. $objQuestion->question = $object->question;
  126. $objQuestion->description = $object->description;
  127. $objQuestion->weighting = $object->ponderation;
  128. $objQuestion->position = $object->position;
  129. $objQuestion->type = $object->type;
  130. $objQuestion->picture = $object->picture;
  131. $objQuestion->level = (int) $object->level;
  132. $objQuestion->extra = $object->extra;
  133. $objQuestion->course = $course_info;
  134. $objQuestion->category = TestCategory::getCategoryForQuestion($id);
  135. $tblQuiz = Database::get_course_table(TABLE_QUIZ_TEST);
  136. $sql = "SELECT DISTINCT q.exercice_id
  137. FROM $TBL_EXERCISE_QUESTION q
  138. INNER JOIN $tblQuiz e
  139. ON e.c_id = q.c_id AND e.id = q.exercice_id
  140. WHERE
  141. q.c_id = $course_id AND
  142. q.question_id = $id AND
  143. e.active >= 0";
  144. $result = Database::query($sql);
  145. // fills the array with the exercises which this question is in
  146. if ($result) {
  147. while ($obj = Database::fetch_object($result)) {
  148. $objQuestion->exerciseList[] = $obj->exercice_id;
  149. }
  150. }
  151. return $objQuestion;
  152. }
  153. }
  154. // question not found
  155. return false;
  156. }
  157. /**
  158. * returns the question ID
  159. *
  160. * @author Olivier Brouckaert
  161. *
  162. * @return integer - question ID
  163. */
  164. public function selectId()
  165. {
  166. return $this->id;
  167. }
  168. /**
  169. * returns the question title
  170. *
  171. * @author Olivier Brouckaert
  172. * @return string - question title
  173. */
  174. public function selectTitle()
  175. {
  176. if (!api_get_configuration_value('save_titles_as_html')) {
  177. return $this->question;
  178. }
  179. return Display::div($this->question, ['style' => 'display: inline-block;']);
  180. }
  181. /**
  182. * @param int $itemNumber
  183. * @return string
  184. */
  185. public function getTitleToDisplay($itemNumber)
  186. {
  187. $showQuestionTitleHtml = api_get_configuration_value('save_titles_as_html');
  188. $title = $showQuestionTitleHtml ? '' : '<strong>';
  189. $title .= $itemNumber.'. '.$this->selectTitle();
  190. $title .= $showQuestionTitleHtml ? '' : '</strong>';
  191. return Display::div(
  192. $title,
  193. ['class' => 'question_title']
  194. );
  195. }
  196. /**
  197. * returns the question description
  198. *
  199. * @author Olivier Brouckaert
  200. * @return string - question description
  201. */
  202. public function selectDescription()
  203. {
  204. return $this->description;
  205. }
  206. /**
  207. * returns the question weighting
  208. *
  209. * @author Olivier Brouckaert
  210. * @return integer - question weighting
  211. */
  212. public function selectWeighting()
  213. {
  214. return $this->weighting;
  215. }
  216. /**
  217. * returns the question position
  218. *
  219. * @author Olivier Brouckaert
  220. * @return integer - question position
  221. */
  222. public function selectPosition()
  223. {
  224. return $this->position;
  225. }
  226. /**
  227. * returns the answer type
  228. *
  229. * @author Olivier Brouckaert
  230. * @return integer - answer type
  231. */
  232. public function selectType()
  233. {
  234. return $this->type;
  235. }
  236. /**
  237. * returns the level of the question
  238. *
  239. * @author Nicolas Raynaud
  240. * @return integer - level of the question, 0 by default.
  241. */
  242. public function getLevel()
  243. {
  244. return $this->level;
  245. }
  246. /**
  247. * returns the picture name
  248. *
  249. * @author Olivier Brouckaert
  250. * @return string - picture name
  251. */
  252. public function selectPicture()
  253. {
  254. return $this->picture;
  255. }
  256. /**
  257. * @return string
  258. */
  259. public function selectPicturePath()
  260. {
  261. if (!empty($this->picture)) {
  262. return api_get_path(WEB_COURSE_PATH).$this->course['directory'].'/document/images/'.$this->getPictureFilename();
  263. }
  264. return '';
  265. }
  266. /**
  267. * @return int|string
  268. */
  269. public function getPictureId()
  270. {
  271. // for backward compatibility
  272. // when in field picture we had the filename not the document id
  273. if (preg_match("/quiz-.*/", $this->picture)) {
  274. return DocumentManager::get_document_id(
  275. $this->course,
  276. $this->selectPicturePath(),
  277. api_get_session_id()
  278. );
  279. }
  280. return $this->picture;
  281. }
  282. /**
  283. * @param int $courseId
  284. * @param int $sessionId
  285. * @return string
  286. */
  287. public function getPictureFilename($courseId = 0, $sessionId = 0)
  288. {
  289. $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
  290. $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
  291. if (empty($courseId)) {
  292. return '';
  293. }
  294. // for backward compatibility
  295. // when in field picture we had the filename not the document id
  296. if (preg_match("/quiz-.*/", $this->picture)) {
  297. return $this->picture;
  298. }
  299. $pictureId = $this->getPictureId();
  300. $courseInfo = $this->course;
  301. $documentInfo = DocumentManager::get_document_data_by_id(
  302. $pictureId,
  303. $courseInfo['code'],
  304. false,
  305. $sessionId
  306. );
  307. $documentFilename = '';
  308. if ($documentInfo) {
  309. // document in document/images folder
  310. $documentFilename = pathinfo(
  311. $documentInfo['path'],
  312. PATHINFO_BASENAME
  313. );
  314. }
  315. return $documentFilename;
  316. }
  317. /**
  318. * returns the array with the exercise ID list
  319. *
  320. * @author Olivier Brouckaert
  321. * @return array - list of exercise ID which the question is in
  322. */
  323. public function selectExerciseList()
  324. {
  325. return $this->exerciseList;
  326. }
  327. /**
  328. * returns the number of exercises which this question is in
  329. *
  330. * @author Olivier Brouckaert
  331. * @return integer - number of exercises
  332. */
  333. public function selectNbrExercises()
  334. {
  335. return sizeof($this->exerciseList);
  336. }
  337. /**
  338. * changes the question title
  339. *
  340. * @param string $title - question title
  341. *
  342. * @author Olivier Brouckaert
  343. */
  344. public function updateTitle($title)
  345. {
  346. $this->question = $title;
  347. }
  348. /**
  349. * @param int $id
  350. */
  351. public function updateParentId($id)
  352. {
  353. $this->parent_id = intval($id);
  354. }
  355. /**
  356. * changes the question description
  357. *
  358. * @param string $description - question description
  359. *
  360. * @author Olivier Brouckaert
  361. *
  362. */
  363. public function updateDescription($description)
  364. {
  365. $this->description = $description;
  366. }
  367. /**
  368. * changes the question weighting
  369. *
  370. * @param integer $weighting - question weighting
  371. *
  372. * @author Olivier Brouckaert
  373. */
  374. public function updateWeighting($weighting)
  375. {
  376. $this->weighting = $weighting;
  377. }
  378. /**
  379. * @param array $category
  380. *
  381. * @author Hubert Borderiou 12-10-2011
  382. *
  383. */
  384. public function updateCategory($category)
  385. {
  386. $this->category = $category;
  387. }
  388. /**
  389. * @param int $value
  390. *
  391. * @author Hubert Borderiou 12-10-2011
  392. */
  393. public function updateScoreAlwaysPositive($value)
  394. {
  395. $this->scoreAlwaysPositive = $value;
  396. }
  397. /**
  398. * @param int $value
  399. *
  400. * @author Hubert Borderiou 12-10-2011
  401. */
  402. public function updateUncheckedMayScore($value)
  403. {
  404. $this->uncheckedMayScore = $value;
  405. }
  406. /**
  407. * Save category of a question
  408. *
  409. * A question can have n categories if category is empty,
  410. * then question has no category then delete the category entry
  411. *
  412. * @param array $category_list
  413. *
  414. * @author Julio Montoya - Adding multiple cat support
  415. */
  416. public function saveCategories($category_list)
  417. {
  418. if (!empty($category_list)) {
  419. $this->deleteCategory();
  420. $TBL_QUESTION_REL_CATEGORY = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
  421. // update or add category for a question
  422. foreach ($category_list as $category_id) {
  423. $category_id = intval($category_id);
  424. $question_id = intval($this->id);
  425. $sql = "SELECT count(*) AS nb
  426. FROM $TBL_QUESTION_REL_CATEGORY
  427. WHERE
  428. category_id = $category_id
  429. AND question_id = $question_id
  430. AND c_id=".api_get_course_int_id();
  431. $res = Database::query($sql);
  432. $row = Database::fetch_array($res);
  433. if ($row['nb'] > 0) {
  434. // DO nothing
  435. } else {
  436. $sql = "INSERT INTO $TBL_QUESTION_REL_CATEGORY (c_id, question_id, category_id)
  437. VALUES (".api_get_course_int_id().", $question_id, $category_id)";
  438. Database::query($sql);
  439. }
  440. }
  441. }
  442. }
  443. /**
  444. * in this version, a question can only have 1 category
  445. * if category is 0, then question has no category then delete the category entry
  446. * @param int $category
  447. *
  448. * @author Hubert Borderiou 12-10-2011
  449. */
  450. public function saveCategory($category)
  451. {
  452. if ($category <= 0) {
  453. $this->deleteCategory();
  454. } else {
  455. // update or add category for a question
  456. $table = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
  457. $category_id = intval($category);
  458. $question_id = intval($this->id);
  459. $sql = "SELECT count(*) AS nb FROM $table
  460. WHERE
  461. question_id = $question_id AND
  462. c_id=".api_get_course_int_id();
  463. $res = Database::query($sql);
  464. $row = Database::fetch_array($res);
  465. if ($row['nb'] > 0) {
  466. $sql = "UPDATE $table
  467. SET category_id = $category_id
  468. WHERE
  469. question_id = $question_id AND
  470. c_id = ".api_get_course_int_id();
  471. Database::query($sql);
  472. } else {
  473. $sql = "INSERT INTO $table (c_id, question_id, category_id)
  474. VALUES (".api_get_course_int_id().", $question_id, $category_id)";
  475. Database::query($sql);
  476. }
  477. }
  478. }
  479. /**
  480. * @author hubert borderiou 12-10-2011
  481. * delete any category entry for question id
  482. * delete the category for question
  483. */
  484. public function deleteCategory()
  485. {
  486. $table = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
  487. $question_id = intval($this->id);
  488. $sql = "DELETE FROM $table
  489. WHERE
  490. question_id = $question_id AND
  491. c_id = ".api_get_course_int_id();
  492. Database::query($sql);
  493. }
  494. /**
  495. * changes the question position
  496. *
  497. * @param integer $position - question position
  498. *
  499. * @author Olivier Brouckaert
  500. */
  501. public function updatePosition($position)
  502. {
  503. $this->position = $position;
  504. }
  505. /**
  506. * changes the question level
  507. *
  508. * @param integer $level - question level
  509. *
  510. * @author Nicolas Raynaud
  511. */
  512. public function updateLevel($level)
  513. {
  514. $this->level = $level;
  515. }
  516. /**
  517. * changes the answer type. If the user changes the type from "unique answer" to "multiple answers"
  518. * (or conversely) answers are not deleted, otherwise yes
  519. *
  520. * @param integer $type - answer type
  521. *
  522. * @author Olivier Brouckaert
  523. */
  524. public function updateType($type)
  525. {
  526. $TBL_REPONSES = Database::get_course_table(TABLE_QUIZ_ANSWER);
  527. $course_id = $this->course['real_id'];
  528. if (empty($course_id)) {
  529. $course_id = api_get_course_int_id();
  530. }
  531. // if we really change the type
  532. if ($type != $this->type) {
  533. // if we don't change from "unique answer" to "multiple answers" (or conversely)
  534. if (
  535. !in_array($this->type, array(UNIQUE_ANSWER, MULTIPLE_ANSWER)) ||
  536. !in_array($type, array(UNIQUE_ANSWER, MULTIPLE_ANSWER))
  537. ) {
  538. // removes old answers
  539. $sql = "DELETE FROM $TBL_REPONSES
  540. WHERE c_id = $course_id AND question_id = ".intval($this->id);
  541. Database::query($sql);
  542. }
  543. $this->type = $type;
  544. }
  545. }
  546. /**
  547. * Get default hot spot folder in documents
  548. * @return string
  549. */
  550. public function getHotSpotFolderInCourse()
  551. {
  552. if (empty($this->course) || empty($this->course['directory'])) {
  553. // Stop everything if course is not set.
  554. api_not_allowed();
  555. }
  556. $pictureAbsolutePath = api_get_path(SYS_COURSE_PATH).$this->course['directory'].'/document/images/';
  557. $picturePath = basename($pictureAbsolutePath);
  558. if (!is_dir($picturePath)) {
  559. create_unexisting_directory(
  560. $this->course,
  561. api_get_user_id(),
  562. 0,
  563. 0,
  564. 0,
  565. dirname($pictureAbsolutePath),
  566. '/'.$picturePath,
  567. $picturePath
  568. );
  569. }
  570. return $pictureAbsolutePath;
  571. }
  572. /**
  573. * adds a picture to the question
  574. *
  575. * @param string $picture - temporary path of the picture to upload
  576. *
  577. * @return boolean - true if uploaded, otherwise false
  578. *
  579. * @author Olivier Brouckaert
  580. */
  581. public function uploadPicture($picture)
  582. {
  583. $picturePath = $this->getHotSpotFolderInCourse();
  584. // if the question has got an ID
  585. if ($this->id) {
  586. $pictureFilename = self::generatePictureName();
  587. $img = new Image($picture);
  588. $img->send_image($picturePath.'/'.$pictureFilename, -1, 'jpg');
  589. $document_id = add_document(
  590. $this->course,
  591. '/images/'.$pictureFilename,
  592. 'file',
  593. filesize($picturePath.'/'.$pictureFilename),
  594. $pictureFilename
  595. );
  596. if ($document_id) {
  597. $this->picture = $document_id;
  598. if (!file_exists($picturePath.'/'.$pictureFilename)) {
  599. return false;
  600. }
  601. api_item_property_update(
  602. $this->course,
  603. TOOL_DOCUMENT,
  604. $document_id,
  605. 'DocumentAdded',
  606. api_get_user_id()
  607. );
  608. $this->resizePicture('width', 800);
  609. return true;
  610. }
  611. }
  612. return false;
  613. }
  614. /**
  615. * return the name for image use in hotspot question
  616. * to be unique, name is quiz-[utc unix timestamp].jpg
  617. * @param string $prefix
  618. * @param string $extension
  619. * @return string
  620. */
  621. public function generatePictureName($prefix = 'quiz-', $extension = 'jpg')
  622. {
  623. // image name is quiz-xxx.jpg in folder images/
  624. $utcTime = time();
  625. return $prefix.$utcTime.'.'.$extension;
  626. }
  627. /**
  628. * Resizes a picture || Warning!: can only be called after uploadPicture,
  629. * or if picture is already available in object.
  630. * @param string $Dimension - Resizing happens proportional according to given dimension: height|width|any
  631. * @param integer $Max - Maximum size
  632. *
  633. * @return boolean|null - true if success, false if failed
  634. *
  635. * @author Toon Keppens
  636. */
  637. private function resizePicture($Dimension, $Max)
  638. {
  639. $picturePath = $this->getHotSpotFolderInCourse();
  640. // if the question has an ID
  641. if ($this->id) {
  642. // Get dimensions from current image.
  643. $my_image = new Image($picturePath.'/'.$this->picture);
  644. $current_image_size = $my_image->get_image_size();
  645. $current_width = $current_image_size['width'];
  646. $current_height = $current_image_size['height'];
  647. if ($current_width < $Max && $current_height < $Max) {
  648. return true;
  649. } elseif ($current_height == '') {
  650. return false;
  651. }
  652. // Resize according to height.
  653. if ($Dimension == "height") {
  654. $resize_scale = $current_height / $Max;
  655. $new_height = $Max;
  656. $new_width = ceil($current_width / $resize_scale);
  657. }
  658. // Resize according to width
  659. if ($Dimension == "width") {
  660. $resize_scale = $current_width / $Max;
  661. $new_width = $Max;
  662. $new_height = ceil($current_height / $resize_scale);
  663. }
  664. // Resize according to height or width, both should not be larger than $Max after resizing.
  665. if ($Dimension == "any") {
  666. if ($current_height > $current_width || $current_height == $current_width) {
  667. $resize_scale = $current_height / $Max;
  668. $new_height = $Max;
  669. $new_width = ceil($current_width / $resize_scale);
  670. }
  671. if ($current_height < $current_width) {
  672. $resize_scale = $current_width / $Max;
  673. $new_width = $Max;
  674. $new_height = ceil($current_height / $resize_scale);
  675. }
  676. }
  677. $my_image->resize($new_width, $new_height);
  678. $result = $my_image->send_image($picturePath.'/'.$this->picture);
  679. if ($result) {
  680. return true;
  681. } else {
  682. return false;
  683. }
  684. }
  685. }
  686. /**
  687. * deletes the picture
  688. *
  689. * @author Olivier Brouckaert
  690. * @return boolean - true if removed, otherwise false
  691. */
  692. public function removePicture()
  693. {
  694. $picturePath = $this->getHotSpotFolderInCourse();
  695. // if the question has got an ID and if the picture exists
  696. if ($this->id) {
  697. $picture = $this->picture;
  698. $this->picture = '';
  699. return @unlink($picturePath.'/'.$picture) ? true : false;
  700. }
  701. return false;
  702. }
  703. /**
  704. * Exports a picture to another question
  705. *
  706. * @author Olivier Brouckaert
  707. * @param integer $questionId - ID of the target question
  708. * @param array $courseInfo
  709. * @return boolean - true if copied, otherwise false
  710. */
  711. public function exportPicture($questionId, $courseInfo)
  712. {
  713. if (empty($questionId) || empty($courseInfo)) {
  714. return false;
  715. }
  716. $course_id = $courseInfo['real_id'];
  717. $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
  718. $destination_path = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/images';
  719. $source_path = $this->getHotSpotFolderInCourse();
  720. // if the question has got an ID and if the picture exists
  721. if (!$this->id || empty($this->picture)) {
  722. return false;
  723. }
  724. $picture = $this->generatePictureName();
  725. if (file_exists($source_path.'/'.$this->picture)) {
  726. // for backward compatibility
  727. $result = @copy(
  728. $source_path.'/'.$this->picture,
  729. $destination_path.'/'.$picture
  730. );
  731. } else {
  732. $imageInfo = DocumentManager::get_document_data_by_id(
  733. $this->picture,
  734. $courseInfo['code']
  735. );
  736. if (file_exists($imageInfo['absolute_path'])) {
  737. $result = @copy(
  738. $imageInfo['absolute_path'],
  739. $destination_path.'/'.$picture
  740. );
  741. }
  742. }
  743. // If copy was correct then add to the database
  744. if (!$result) {
  745. return false;
  746. }
  747. $sql = "UPDATE $TBL_QUESTIONS SET
  748. picture = '".Database::escape_string($picture)."'
  749. WHERE c_id = $course_id AND id='".intval($questionId)."'";
  750. Database::query($sql);
  751. $documentId = add_document(
  752. $courseInfo,
  753. '/images/'.$picture,
  754. 'file',
  755. filesize($destination_path.'/'.$picture),
  756. $picture
  757. );
  758. if (!$documentId) {
  759. return false;
  760. }
  761. return api_item_property_update(
  762. $courseInfo,
  763. TOOL_DOCUMENT,
  764. $documentId,
  765. 'DocumentAdded',
  766. api_get_user_id()
  767. );
  768. }
  769. /**
  770. * Saves the picture coming from POST into a temporary file
  771. * Temporary pictures are used when we don't want to save a picture right after a form submission.
  772. * For example, if we first show a confirmation box.
  773. *
  774. * @author Olivier Brouckaert
  775. * @param string $picture - temporary path of the picture to move
  776. * @param string $pictureName - Name of the picture
  777. */
  778. public function setTmpPicture($picture, $pictureName)
  779. {
  780. $picturePath = $this->getHotSpotFolderInCourse();
  781. $pictureName = explode('.', $pictureName);
  782. $Extension = $pictureName[sizeof($pictureName) - 1];
  783. // saves the picture into a temporary file
  784. @move_uploaded_file($picture, $picturePath.'/tmp.'.$Extension);
  785. }
  786. /**
  787. * Sets the title
  788. */
  789. public function setTitle($title)
  790. {
  791. $this->question = $title;
  792. }
  793. /**
  794. * Sets extra info
  795. * @param string $extra
  796. */
  797. public function setExtra($extra)
  798. {
  799. $this->extra = $extra;
  800. }
  801. /**
  802. * updates the question in the data base
  803. * if an exercise ID is provided, we add that exercise ID into the exercise list
  804. *
  805. * @author Olivier Brouckaert
  806. * @param integer $exerciseId - exercise ID if saving in an exercise
  807. */
  808. public function save($exerciseId = 0)
  809. {
  810. $TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
  811. $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
  812. $em = Database::getManager();
  813. $id = $this->id;
  814. $question = $this->question;
  815. $description = $this->description;
  816. $weighting = $this->weighting;
  817. $position = $this->position;
  818. $type = $this->type;
  819. $picture = $this->picture;
  820. $level = $this->level;
  821. $extra = $this->extra;
  822. $c_id = $this->course['real_id'];
  823. $category = $this->category;
  824. // question already exists
  825. if (!empty($id)) {
  826. $params = [
  827. 'question' => $question,
  828. 'description' => $description,
  829. 'ponderation' => $weighting,
  830. 'position' => $position,
  831. 'type' => $type,
  832. 'picture' => $picture,
  833. 'extra' => $extra,
  834. 'level' => $level,
  835. ];
  836. Database::update(
  837. $TBL_QUESTIONS,
  838. $params,
  839. ['c_id = ? AND id = ?' => [$c_id, $id]]
  840. );
  841. $this->saveCategory($category);
  842. if (!empty($exerciseId)) {
  843. api_item_property_update(
  844. $this->course,
  845. TOOL_QUIZ,
  846. $id,
  847. 'QuizQuestionUpdated',
  848. api_get_user_id()
  849. );
  850. }
  851. if (api_get_setting('search_enabled') == 'true') {
  852. if ($exerciseId != 0) {
  853. $this -> search_engine_edit($exerciseId);
  854. } else {
  855. /**
  856. * actually there is *not* an user interface for
  857. * creating questions without a relation with an exercise
  858. */
  859. }
  860. }
  861. } else {
  862. // creates a new question
  863. $sql = "SELECT max(position)
  864. FROM $TBL_QUESTIONS as question,
  865. $TBL_EXERCISE_QUESTION as test_question
  866. WHERE
  867. question.id = test_question.question_id AND
  868. test_question.exercice_id = ".intval($exerciseId)." AND
  869. question.c_id = $c_id AND
  870. test_question.c_id = $c_id ";
  871. $result = Database::query($sql);
  872. $current_position = Database::result($result, 0, 0);
  873. $this->updatePosition($current_position + 1);
  874. $position = $this->position;
  875. $params = [
  876. 'c_id' => $c_id,
  877. 'question' => $question,
  878. 'description' => $description,
  879. 'ponderation' => $weighting,
  880. 'position' => $position,
  881. 'type' => $type,
  882. 'picture' => $picture,
  883. 'extra' => $extra,
  884. 'level' => $level
  885. ];
  886. $this->id = Database::insert($TBL_QUESTIONS, $params);
  887. if ($this->id) {
  888. $sql = "UPDATE $TBL_QUESTIONS SET id = iid WHERE iid = {$this->id}";
  889. Database::query($sql);
  890. api_item_property_update(
  891. $this->course,
  892. TOOL_QUIZ,
  893. $this->id,
  894. 'QuizQuestionAdded',
  895. api_get_user_id()
  896. );
  897. // If hotspot, create first answer
  898. if ($type == HOT_SPOT || $type == HOT_SPOT_ORDER) {
  899. $quizAnswer = new \Chamilo\CourseBundle\Entity\CQuizAnswer();
  900. $quizAnswer
  901. ->setCId($c_id)
  902. ->setQuestionId($this->id)
  903. ->setAnswer('')
  904. ->setPonderation(10)
  905. ->setPosition(1)
  906. ->setHotspotCoordinates('0;0|0|0')
  907. ->setHotspotType('square');
  908. $em->persist($quizAnswer);
  909. $em->flush();
  910. $id = $quizAnswer->getIid();
  911. if ($id) {
  912. $quizAnswer
  913. ->setId($id)
  914. ->setIdAuto($id);
  915. $em->merge($quizAnswer);
  916. $em->flush();
  917. }
  918. }
  919. if ($type == HOT_SPOT_DELINEATION) {
  920. $quizAnswer = new \Chamilo\CourseBundle\Entity\CQuizAnswer();
  921. $quizAnswer
  922. ->setCId($c_id)
  923. ->setQuestionId($this->id)
  924. ->setAnswer('')
  925. ->setPonderation(10)
  926. ->setPosition(1)
  927. ->setHotspotCoordinates('0;0|0|0')
  928. ->setHotspotType('delineation');
  929. $em->persist($quizAnswer);
  930. $em->flush();
  931. $id = $quizAnswer->getIid();
  932. if ($id) {
  933. $quizAnswer
  934. ->setId($id)
  935. ->setIdAuto($id);
  936. $em->merge($quizAnswer);
  937. $em->flush();
  938. }
  939. }
  940. if (api_get_setting('search_enabled') == 'true') {
  941. if ($exerciseId != 0) {
  942. $this->search_engine_edit($exerciseId, true);
  943. } else {
  944. /**
  945. * actually there is *not* an user interface for
  946. * creating questions without a relation with an exercise
  947. */
  948. }
  949. }
  950. }
  951. }
  952. // if the question is created in an exercise
  953. if ($exerciseId) {
  954. // adds the exercise into the exercise list of this question
  955. $this->addToList($exerciseId, true);
  956. }
  957. }
  958. public function search_engine_edit(
  959. $exerciseId,
  960. $addQs = false,
  961. $rmQs = false
  962. ) {
  963. // update search engine and its values table if enabled
  964. if (api_get_setting('search_enabled') == 'true' && extension_loaded('xapian')) {
  965. $course_id = api_get_course_id();
  966. // get search_did
  967. $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
  968. if ($addQs || $rmQs) {
  969. //there's only one row per question on normal db and one document per question on search engine db
  970. $sql = 'SELECT * FROM %s
  971. WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_second_level=%s LIMIT 1';
  972. $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
  973. } else {
  974. $sql = 'SELECT * FROM %s
  975. WHERE course_code=\'%s\' AND tool_id=\'%s\'
  976. AND ref_id_high_level=%s AND ref_id_second_level=%s LIMIT 1';
  977. $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $exerciseId, $this->id);
  978. }
  979. $res = Database::query($sql);
  980. if (Database::num_rows($res) > 0 || $addQs) {
  981. require_once(api_get_path(LIBRARY_PATH).'search/ChamiloIndexer.class.php');
  982. require_once(api_get_path(LIBRARY_PATH).'search/IndexableChunk.class.php');
  983. $di = new ChamiloIndexer();
  984. if ($addQs) {
  985. $question_exercises = array((int) $exerciseId);
  986. } else {
  987. $question_exercises = array();
  988. }
  989. isset($_POST['language']) ? $lang = Database::escape_string($_POST['language']) : $lang = 'english';
  990. $di->connectDb(null, null, $lang);
  991. // retrieve others exercise ids
  992. $se_ref = Database::fetch_array($res);
  993. $se_doc = $di->get_document((int) $se_ref['search_did']);
  994. if ($se_doc !== false) {
  995. if (($se_doc_data = $di->get_document_data($se_doc)) !== false) {
  996. $se_doc_data = unserialize($se_doc_data);
  997. if (
  998. isset($se_doc_data[SE_DATA]['type']) &&
  999. $se_doc_data[SE_DATA]['type'] == SE_DOCTYPE_EXERCISE_QUESTION
  1000. ) {
  1001. if (
  1002. isset($se_doc_data[SE_DATA]['exercise_ids']) &&
  1003. is_array($se_doc_data[SE_DATA]['exercise_ids'])
  1004. ) {
  1005. foreach ($se_doc_data[SE_DATA]['exercise_ids'] as $old_value) {
  1006. if (!in_array($old_value, $question_exercises)) {
  1007. $question_exercises[] = $old_value;
  1008. }
  1009. }
  1010. }
  1011. }
  1012. }
  1013. }
  1014. if ($rmQs) {
  1015. while (($key = array_search($exerciseId, $question_exercises)) !== false) {
  1016. unset($question_exercises[$key]);
  1017. }
  1018. }
  1019. // build the chunk to index
  1020. $ic_slide = new IndexableChunk();
  1021. $ic_slide->addValue("title", $this->question);
  1022. $ic_slide->addCourseId($course_id);
  1023. $ic_slide->addToolId(TOOL_QUIZ);
  1024. $xapian_data = array(
  1025. SE_COURSE_ID => $course_id,
  1026. SE_TOOL_ID => TOOL_QUIZ,
  1027. SE_DATA => array(
  1028. 'type' => SE_DOCTYPE_EXERCISE_QUESTION,
  1029. 'exercise_ids' => $question_exercises,
  1030. 'question_id' => (int) $this->id
  1031. ),
  1032. SE_USER => (int) api_get_user_id(),
  1033. );
  1034. $ic_slide->xapian_data = serialize($xapian_data);
  1035. $ic_slide->addValue("content", $this->description);
  1036. //TODO: index answers, see also form validation on question_admin.inc.php
  1037. $di->remove_document((int) $se_ref['search_did']);
  1038. $di->addChunk($ic_slide);
  1039. //index and return search engine document id
  1040. if (!empty($question_exercises)) { // if empty there is nothing to index
  1041. $did = $di->index();
  1042. unset($di);
  1043. }
  1044. if ($did || $rmQs) {
  1045. // save it to db
  1046. if ($addQs || $rmQs) {
  1047. $sql = "DELETE FROM %s
  1048. WHERE course_code = '%s' AND tool_id = '%s' AND ref_id_second_level = '%s'";
  1049. $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
  1050. } else {
  1051. $sql = "DELETE FROM %S
  1052. WHERE
  1053. course_code = '%s'
  1054. AND tool_id = '%s'
  1055. AND tool_id = '%s'
  1056. AND ref_id_high_level = '%s'
  1057. AND ref_id_second_level = '%s'";
  1058. $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $exerciseId, $this->id);
  1059. }
  1060. Database::query($sql);
  1061. if ($rmQs) {
  1062. if (!empty($question_exercises)) {
  1063. $sql = "INSERT INTO %s (
  1064. id, course_code, tool_id, ref_id_high_level, ref_id_second_level, search_did
  1065. )
  1066. VALUES (
  1067. NULL, '%s', '%s', %s, %s, %s
  1068. )";
  1069. $sql = sprintf(
  1070. $sql,
  1071. $tbl_se_ref,
  1072. $course_id,
  1073. TOOL_QUIZ,
  1074. array_shift($question_exercises),
  1075. $this->id,
  1076. $did
  1077. );
  1078. Database::query($sql);
  1079. }
  1080. } else {
  1081. $sql = "INSERT INTO %s (
  1082. id, course_code, tool_id, ref_id_high_level, ref_id_second_level, search_did
  1083. )
  1084. VALUES (
  1085. NULL , '%s', '%s', %s, %s, %s
  1086. )";
  1087. $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $exerciseId, $this->id, $did);
  1088. Database::query($sql);
  1089. }
  1090. }
  1091. }
  1092. }
  1093. }
  1094. /**
  1095. * adds an exercise into the exercise list
  1096. *
  1097. * @author Olivier Brouckaert
  1098. * @param integer $exerciseId - exercise ID
  1099. * @param boolean $fromSave - from $this->save() or not
  1100. */
  1101. public function addToList($exerciseId, $fromSave = false)
  1102. {
  1103. $exerciseRelQuestionTable = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
  1104. $id = $this->id;
  1105. // checks if the exercise ID is not in the list
  1106. if (!in_array($exerciseId, $this->exerciseList)) {
  1107. $this->exerciseList[] = $exerciseId;
  1108. $new_exercise = new Exercise();
  1109. $new_exercise->read($exerciseId);
  1110. $count = $new_exercise->selectNbrQuestions();
  1111. $count++;
  1112. $sql = "INSERT INTO $exerciseRelQuestionTable (c_id, question_id, exercice_id, question_order)
  1113. VALUES ({$this->course['real_id']}, ".intval($id).", ".intval($exerciseId).", '$count')";
  1114. Database::query($sql);
  1115. // we do not want to reindex if we had just saved adnd indexed the question
  1116. if (!$fromSave) {
  1117. $this->search_engine_edit($exerciseId, true);
  1118. }
  1119. }
  1120. }
  1121. /**
  1122. * removes an exercise from the exercise list
  1123. *
  1124. * @author Olivier Brouckaert
  1125. * @param integer $exerciseId - exercise ID
  1126. * @return boolean - true if removed, otherwise false
  1127. */
  1128. public function removeFromList($exerciseId)
  1129. {
  1130. $TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
  1131. $id = $this->id;
  1132. // searches the position of the exercise ID in the list
  1133. $pos = array_search($exerciseId, $this->exerciseList);
  1134. $course_id = api_get_course_int_id();
  1135. // exercise not found
  1136. if ($pos === false) {
  1137. return false;
  1138. } else {
  1139. // deletes the position in the array containing the wanted exercise ID
  1140. unset($this->exerciseList[$pos]);
  1141. //update order of other elements
  1142. $sql = "SELECT question_order
  1143. FROM $TBL_EXERCISE_QUESTION
  1144. WHERE
  1145. c_id = $course_id
  1146. AND question_id = ".intval($id)."
  1147. AND exercice_id = " . intval($exerciseId);
  1148. $res = Database::query($sql);
  1149. if (Database::num_rows($res) > 0) {
  1150. $row = Database::fetch_array($res);
  1151. if (!empty($row['question_order'])) {
  1152. $sql = "UPDATE $TBL_EXERCISE_QUESTION
  1153. SET question_order = question_order-1
  1154. WHERE
  1155. c_id = $course_id
  1156. AND exercice_id = ".intval($exerciseId)."
  1157. AND question_order > " . $row['question_order'];
  1158. Database::query($sql);
  1159. }
  1160. }
  1161. $sql = "DELETE FROM $TBL_EXERCISE_QUESTION
  1162. WHERE
  1163. c_id = $course_id
  1164. AND question_id = ".intval($id)."
  1165. AND exercice_id = " . intval($exerciseId);
  1166. Database::query($sql);
  1167. return true;
  1168. }
  1169. }
  1170. /**
  1171. * Deletes a question from the database
  1172. * the parameter tells if the question is removed from all exercises (value = 0),
  1173. * or just from one exercise (value = exercise ID)
  1174. *
  1175. * @author Olivier Brouckaert
  1176. * @param integer $deleteFromEx - exercise ID if the question is only removed from one exercise
  1177. */
  1178. public function delete($deleteFromEx = 0)
  1179. {
  1180. $course_id = api_get_course_int_id();
  1181. $TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
  1182. $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
  1183. $TBL_REPONSES = Database::get_course_table(TABLE_QUIZ_ANSWER);
  1184. $TBL_QUIZ_QUESTION_REL_CATEGORY = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
  1185. $id = intval($this->id);
  1186. // if the question must be removed from all exercises
  1187. if (!$deleteFromEx) {
  1188. //update the question_order of each question to avoid inconsistencies
  1189. $sql = "SELECT exercice_id, question_order FROM $TBL_EXERCISE_QUESTION
  1190. WHERE c_id = $course_id AND question_id = ".intval($id)."";
  1191. $res = Database::query($sql);
  1192. if (Database::num_rows($res) > 0) {
  1193. while ($row = Database::fetch_array($res)) {
  1194. if (!empty($row['question_order'])) {
  1195. $sql = "UPDATE $TBL_EXERCISE_QUESTION
  1196. SET question_order = question_order-1
  1197. WHERE
  1198. c_id= $course_id
  1199. AND exercice_id = ".intval($row['exercice_id'])."
  1200. AND question_order > " . $row['question_order'];
  1201. Database::query($sql);
  1202. }
  1203. }
  1204. }
  1205. $sql = "DELETE FROM $TBL_EXERCISE_QUESTION
  1206. WHERE c_id = $course_id AND question_id = ".$id;
  1207. Database::query($sql);
  1208. $sql = "DELETE FROM $TBL_QUESTIONS
  1209. WHERE c_id = $course_id AND id = ".$id;
  1210. Database::query($sql);
  1211. $sql = "DELETE FROM $TBL_REPONSES
  1212. WHERE c_id = $course_id AND question_id = ".$id;
  1213. Database::query($sql);
  1214. // remove the category of this question in the question_rel_category table
  1215. $sql = "DELETE FROM $TBL_QUIZ_QUESTION_REL_CATEGORY
  1216. WHERE
  1217. c_id = $course_id AND
  1218. question_id = ".$id;
  1219. Database::query($sql);
  1220. api_item_property_update(
  1221. $this->course,
  1222. TOOL_QUIZ,
  1223. $id,
  1224. 'QuizQuestionDeleted',
  1225. api_get_user_id()
  1226. );
  1227. $this->removePicture();
  1228. } else {
  1229. // just removes the exercise from the list
  1230. $this->removeFromList($deleteFromEx);
  1231. if (api_get_setting('search_enabled') == 'true' && extension_loaded('xapian')) {
  1232. // disassociate question with this exercise
  1233. $this->search_engine_edit($deleteFromEx, false, true);
  1234. }
  1235. api_item_property_update(
  1236. $this->course,
  1237. TOOL_QUIZ,
  1238. $id,
  1239. 'QuizQuestionDeleted',
  1240. api_get_user_id()
  1241. );
  1242. }
  1243. }
  1244. /**
  1245. * Duplicates the question
  1246. *
  1247. * @author Olivier Brouckaert
  1248. * @param array $course_info Course info of the destination course
  1249. * @return false|string ID of the new question
  1250. */
  1251. public function duplicate($course_info = [])
  1252. {
  1253. if (empty($course_info)) {
  1254. $course_info = $this->course;
  1255. } else {
  1256. $course_info = $course_info;
  1257. }
  1258. $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
  1259. $TBL_QUESTION_OPTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
  1260. $question = $this->question;
  1261. $description = $this->description;
  1262. $weighting = $this->weighting;
  1263. $position = $this->position;
  1264. $type = $this->type;
  1265. $level = intval($this->level);
  1266. $extra = $this->extra;
  1267. // Using the same method used in the course copy to transform URLs
  1268. if ($this->course['id'] != $course_info['id']) {
  1269. $description = DocumentManager::replace_urls_inside_content_html_from_copy_course(
  1270. $description,
  1271. $this->course['code'],
  1272. $course_info['id']
  1273. );
  1274. $question = DocumentManager::replace_urls_inside_content_html_from_copy_course(
  1275. $question,
  1276. $this->course['code'],
  1277. $course_info['id']
  1278. );
  1279. }
  1280. $course_id = $course_info['real_id'];
  1281. // Read the source options
  1282. $options = self::readQuestionOption($this->id, $this->course['real_id']);
  1283. // Inserting in the new course db / or the same course db
  1284. $params = [
  1285. 'c_id' => $course_id,
  1286. 'question' => $question,
  1287. 'description' => $description,
  1288. 'ponderation' => $weighting,
  1289. 'position' => $position,
  1290. 'type' => $type,
  1291. 'level' => $level,
  1292. 'extra' => $extra
  1293. ];
  1294. $newQuestionId = Database::insert($TBL_QUESTIONS, $params);
  1295. if ($newQuestionId) {
  1296. $sql = "UPDATE $TBL_QUESTIONS
  1297. SET id = iid
  1298. WHERE iid = $newQuestionId";
  1299. Database::query($sql);
  1300. if (!empty($options)) {
  1301. // Saving the quiz_options
  1302. foreach ($options as $item) {
  1303. $item['question_id'] = $newQuestionId;
  1304. $item['c_id'] = $course_id;
  1305. unset($item['id']);
  1306. unset($item['iid']);
  1307. $id = Database::insert($TBL_QUESTION_OPTIONS, $item);
  1308. if ($id) {
  1309. $sql = "UPDATE $TBL_QUESTION_OPTIONS
  1310. SET id = iid
  1311. WHERE iid = $id";
  1312. Database::query($sql);
  1313. }
  1314. }
  1315. }
  1316. // Duplicates the picture of the hotspot
  1317. $this->exportPicture($newQuestionId, $course_info);
  1318. }
  1319. return $newQuestionId;
  1320. }
  1321. /**
  1322. * @return string
  1323. */
  1324. public function get_question_type_name()
  1325. {
  1326. $key = self::$questionTypes[$this->type];
  1327. return get_lang($key[1]);
  1328. }
  1329. /**
  1330. * @param string $type
  1331. * @return null
  1332. */
  1333. public static function get_question_type($type)
  1334. {
  1335. if ($type == ORAL_EXPRESSION && api_get_setting('enable_record_audio') !== 'true') {
  1336. return null;
  1337. }
  1338. return self::$questionTypes[$type];
  1339. }
  1340. /**
  1341. * @return array
  1342. */
  1343. public static function get_question_type_list()
  1344. {
  1345. if (api_get_setting('enable_record_audio') !== 'true') {
  1346. self::$questionTypes[ORAL_EXPRESSION] = null;
  1347. unset(self::$questionTypes[ORAL_EXPRESSION]);
  1348. }
  1349. if (api_get_setting('enable_quiz_scenario') !== 'true') {
  1350. self::$questionTypes[HOT_SPOT_DELINEATION] = null;
  1351. unset(self::$questionTypes[HOT_SPOT_DELINEATION]);
  1352. }
  1353. return self::$questionTypes;
  1354. }
  1355. /**
  1356. * Returns an instance of the class corresponding to the type
  1357. * @param integer $type the type of the question
  1358. * @return $this instance of a Question subclass (or of Questionc class by default)
  1359. */
  1360. public static function getInstance($type)
  1361. {
  1362. if (!is_null($type)) {
  1363. list($file_name, $class_name) = self::get_question_type($type);
  1364. if (!empty($file_name)) {
  1365. include_once $file_name;
  1366. if (class_exists($class_name)) {
  1367. return new $class_name();
  1368. } else {
  1369. echo 'Can\'t instanciate class '.$class_name.' of type '.$type;
  1370. }
  1371. }
  1372. }
  1373. return null;
  1374. }
  1375. /**
  1376. * Creates the form to create / edit a question
  1377. * A subclass can redefine this function to add fields...
  1378. * @param FormValidator $form
  1379. */
  1380. public function createForm(&$form)
  1381. {
  1382. echo '<style>
  1383. .media { display:none;}
  1384. </style>';
  1385. echo '<script>
  1386. // hack to hide http://cksource.com/forums/viewtopic.php?f=6&t=8700
  1387. function FCKeditor_OnComplete( editorInstance ) {
  1388. if (document.getElementById ( \'HiddenFCK\' + editorInstance.Name)) {
  1389. HideFCKEditorByInstanceName (editorInstance.Name);
  1390. }
  1391. }
  1392. function HideFCKEditorByInstanceName ( editorInstanceName ) {
  1393. if (document.getElementById ( \'HiddenFCK\' + editorInstanceName ).className == "HideFCKEditor" ) {
  1394. document.getElementById ( \'HiddenFCK\' + editorInstanceName ).className = "media";
  1395. }
  1396. }
  1397. </script>';
  1398. // question name
  1399. if (api_get_configuration_value('save_titles_as_html')) {
  1400. $editorConfig = ['ToolbarSet' => 'Minimal'];
  1401. $form->addHtmlEditor('questionName', get_lang('Question'), false, false, $editorConfig, true);
  1402. } else {
  1403. $form->addElement('text', 'questionName', get_lang('Question'));
  1404. }
  1405. $form->addRule('questionName', get_lang('GiveQuestion'), 'required');
  1406. // default content
  1407. $isContent = isset($_REQUEST['isContent']) ? intval($_REQUEST['isContent']) : null;
  1408. // Question type
  1409. $answerType = isset($_REQUEST['answerType']) ? intval($_REQUEST['answerType']) : null;
  1410. $form->addElement('hidden', 'answerType', $answerType);
  1411. // html editor
  1412. $editorConfig = array(
  1413. 'ToolbarSet' => 'TestQuestionDescription',
  1414. 'Height' => '150'
  1415. );
  1416. if (!api_is_allowed_to_edit(null, true)) {
  1417. $editorConfig['UserStatus'] = 'student';
  1418. }
  1419. $form->addButtonAdvancedSettings('advanced_params');
  1420. $form->addElement('html', '<div id="advanced_params_options" style="display:none">');
  1421. $form->addHtmlEditor('questionDescription', get_lang('QuestionDescription'), false, false, $editorConfig);
  1422. // hidden values
  1423. $my_id = isset($_REQUEST['myid']) ? intval($_REQUEST['myid']) : null;
  1424. $form->addElement('hidden', 'myid', $my_id);
  1425. if ($this->type != MEDIA_QUESTION) {
  1426. // Advanced parameters
  1427. $select_level = self::get_default_levels();
  1428. $form->addElement('select', 'questionLevel', get_lang('Difficulty'), $select_level);
  1429. // Categories
  1430. $tabCat = TestCategory::getCategoriesIdAndName();
  1431. $form->addElement('select', 'questionCategory', get_lang('Category'), $tabCat);
  1432. global $text;
  1433. switch ($this->type) {
  1434. case UNIQUE_ANSWER:
  1435. $buttonGroup = array();
  1436. $buttonGroup[] = $form->addButton('convertAnswer', get_lang('ConvertToMultipleAnswer'), 'dot-circle-o', 'default', null, null, null, true);
  1437. $buttonGroup[] = $form->addButtonSave($text, 'submitQuestion', true);
  1438. $form->addGroup($buttonGroup);
  1439. break;
  1440. case MULTIPLE_ANSWER:
  1441. $buttonGroup = array();
  1442. $buttonGroup[] = $form->addButton('convertAnswer', get_lang('ConvertToUniqueAnswer'), 'check-square-o', 'default');
  1443. $buttonGroup[] = $form->addButtonSave($text, 'submitQuestion', true);
  1444. $form->addGroup($buttonGroup);
  1445. break;
  1446. }
  1447. //Medias
  1448. //$course_medias = self::prepare_course_media_select(api_get_course_int_id());
  1449. //$form->addElement('select', 'parent_id', get_lang('AttachToMedia'), $course_medias);
  1450. }
  1451. $form->addElement('html', '</div>');
  1452. if (!isset($_GET['fromExercise'])) {
  1453. switch ($answerType) {
  1454. case 1:
  1455. $this->question = get_lang('DefaultUniqueQuestion');
  1456. break;
  1457. case 2:
  1458. $this->question = get_lang('DefaultMultipleQuestion');
  1459. break;
  1460. case 3:
  1461. $this->question = get_lang('DefaultFillBlankQuestion');
  1462. break;
  1463. case 4:
  1464. $this->question = get_lang('DefaultMathingQuestion');
  1465. break;
  1466. case 5:
  1467. $this->question = get_lang('DefaultOpenQuestion');
  1468. break;
  1469. case 9:
  1470. $this->question = get_lang('DefaultMultipleQuestion');
  1471. break;
  1472. }
  1473. }
  1474. // default values
  1475. $defaults = array();
  1476. $defaults['questionName'] = $this->question;
  1477. $defaults['questionDescription'] = $this->description;
  1478. $defaults['questionLevel'] = $this->level;
  1479. $defaults['questionCategory'] = $this->category;
  1480. // Came from he question pool
  1481. if (isset($_GET['fromExercise'])) {
  1482. $form->setDefaults($defaults);
  1483. }
  1484. if (!empty($_REQUEST['myid'])) {
  1485. $form->setDefaults($defaults);
  1486. } else {
  1487. if ($isContent == 1) {
  1488. $form->setDefaults($defaults);
  1489. }
  1490. }
  1491. }
  1492. /**
  1493. * function which process the creation of questions
  1494. * @param FormValidator $form
  1495. * @param Exercise $objExercise
  1496. */
  1497. public function processCreation($form, $objExercise = null)
  1498. {
  1499. $this->updateTitle($form->getSubmitValue('questionName'));
  1500. $this->updateDescription($form->getSubmitValue('questionDescription'));
  1501. $this->updateLevel($form->getSubmitValue('questionLevel'));
  1502. $this->updateCategory($form->getSubmitValue('questionCategory'));
  1503. //Save normal question if NOT media
  1504. if ($this->type != MEDIA_QUESTION) {
  1505. $this->save($objExercise->id);
  1506. // modify the exercise
  1507. $objExercise->addToList($this->id);
  1508. $objExercise->update_question_positions();
  1509. }
  1510. }
  1511. /**
  1512. * abstract function which creates the form to create / edit the answers of the question
  1513. * @param FormValidator $form
  1514. */
  1515. abstract public function createAnswersForm($form);
  1516. /**
  1517. * abstract function which process the creation of answers
  1518. * @param the FormValidator $form
  1519. */
  1520. abstract public function processAnswersCreation($form);
  1521. /**
  1522. * Displays the menu of question types
  1523. *
  1524. * @param Exercise $objExercise
  1525. */
  1526. public static function display_type_menu($objExercise)
  1527. {
  1528. $feedback_type = $objExercise->feedback_type;
  1529. $exerciseId = $objExercise->id;
  1530. // 1. by default we show all the question types
  1531. $question_type_custom_list = self::get_question_type_list();
  1532. if (!isset($feedback_type)) {
  1533. $feedback_type = 0;
  1534. }
  1535. if ($feedback_type == 1) {
  1536. //2. but if it is a feedback DIRECT we only show the UNIQUE_ANSWER type that is currently available
  1537. $question_type_custom_list = array(
  1538. UNIQUE_ANSWER => self::$questionTypes[UNIQUE_ANSWER],
  1539. HOT_SPOT_DELINEATION => self::$questionTypes[HOT_SPOT_DELINEATION]
  1540. );
  1541. } else {
  1542. unset($question_type_custom_list[HOT_SPOT_DELINEATION]);
  1543. }
  1544. echo '<div class="well">';
  1545. echo '<ul class="question_menu">';
  1546. foreach ($question_type_custom_list as $i => $a_type) {
  1547. // include the class of the type
  1548. require_once $a_type[0];
  1549. // get the picture of the type and the langvar which describes it
  1550. $img = $explanation = '';
  1551. eval('$img = '.$a_type[1].'::$typePicture;');
  1552. eval('$explanation = get_lang('.$a_type[1].'::$explanationLangVar);');
  1553. echo '<li>';
  1554. echo '<div class="icon-image">';
  1555. $icon = '<a href="admin.php?'.api_get_cidreq().'&newQuestion=yes&answerType='.$i.'">'.
  1556. Display::return_icon($img, $explanation, null, ICON_SIZE_BIG).'</a>';
  1557. if ($objExercise->force_edit_exercise_in_lp === false) {
  1558. if ($objExercise->exercise_was_added_in_lp == true) {
  1559. $img = pathinfo($img);
  1560. $img = $img['filename'].'_na.'.$img['extension'];
  1561. $icon = Display::return_icon($img, $explanation, null, ICON_SIZE_BIG);
  1562. }
  1563. }
  1564. echo $icon;
  1565. echo '</div>';
  1566. echo '</li>';
  1567. }
  1568. echo '<li>';
  1569. echo '<div class="icon_image_content">';
  1570. if ($objExercise->exercise_was_added_in_lp == true) {
  1571. echo Display::return_icon('database_na.png', get_lang('GetExistingQuestion'), null, ICON_SIZE_BIG);
  1572. } else {
  1573. if ($feedback_type == 1) {
  1574. echo $url = "<a href=\"question_pool.php?".api_get_cidreq()."&type=1&fromExercise=$exerciseId\">";
  1575. } else {
  1576. echo $url = '<a href="question_pool.php?'.api_get_cidreq().'&fromExercise='.$exerciseId.'">';
  1577. }
  1578. echo Display::return_icon('database.png', get_lang('GetExistingQuestion'), null, ICON_SIZE_BIG);
  1579. }
  1580. echo '</a>';
  1581. echo '</div></li>';
  1582. echo '</ul>';
  1583. echo '</div>';
  1584. }
  1585. /**
  1586. * @param int $question_id
  1587. * @param string $name
  1588. * @param int $course_id
  1589. * @param int $position
  1590. * @return false|string
  1591. */
  1592. public static function saveQuestionOption($question_id, $name, $course_id, $position = 0)
  1593. {
  1594. $table = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
  1595. $params['question_id'] = intval($question_id);
  1596. $params['name'] = $name;
  1597. $params['position'] = $position;
  1598. $params['c_id'] = $course_id;
  1599. $result = self::readQuestionOption($question_id, $course_id);
  1600. $last_id = Database::insert($table, $params);
  1601. if ($last_id) {
  1602. $sql = "UPDATE $table SET id = iid WHERE iid = $last_id";
  1603. Database::query($sql);
  1604. }
  1605. return $last_id;
  1606. }
  1607. /**
  1608. * @param int $question_id
  1609. * @param int $course_id
  1610. */
  1611. public static function deleteAllQuestionOptions($question_id, $course_id)
  1612. {
  1613. $table = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
  1614. Database::delete(
  1615. $table,
  1616. array(
  1617. 'c_id = ? AND question_id = ?' => array(
  1618. $course_id,
  1619. $question_id
  1620. )
  1621. )
  1622. );
  1623. }
  1624. /**
  1625. * @param int $id
  1626. * @param array $params
  1627. * @param int $course_id
  1628. * @return bool|int
  1629. */
  1630. public static function updateQuestionOption($id, $params, $course_id)
  1631. {
  1632. $table = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
  1633. $result = Database::update(
  1634. $table,
  1635. $params,
  1636. array('c_id = ? AND id = ?' => array($course_id, $id))
  1637. );
  1638. return $result;
  1639. }
  1640. /**
  1641. * @param int $question_id
  1642. * @param int $course_id
  1643. * @return array
  1644. */
  1645. static function readQuestionOption($question_id, $course_id)
  1646. {
  1647. $table = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
  1648. $result = Database::select(
  1649. '*',
  1650. $table,
  1651. array(
  1652. 'where' => array(
  1653. 'c_id = ? AND question_id = ?' => array(
  1654. $course_id,
  1655. $question_id
  1656. )
  1657. ),
  1658. 'order' => 'id ASC'
  1659. )
  1660. );
  1661. return $result;
  1662. }
  1663. /**
  1664. * Shows question title an description
  1665. *
  1666. * @param string $feedback_type
  1667. * @param int $counter
  1668. * @param float $score
  1669. * @return string HTML string with the header of the question (before the answers table)
  1670. */
  1671. function return_header($feedback_type = null, $counter = null, $score = null)
  1672. {
  1673. $counter_label = '';
  1674. if (!empty($counter)) {
  1675. $counter_label = intval($counter);
  1676. }
  1677. $score_label = get_lang('Wrong');
  1678. $class = 'error';
  1679. if ($score['pass'] == true) {
  1680. $score_label = get_lang('Correct');
  1681. $class = 'success';
  1682. }
  1683. if ($this->type == FREE_ANSWER || $this->type == ORAL_EXPRESSION) {
  1684. $score['revised'] = isset($score['revised']) ? $score['revised'] : false;
  1685. if ($score['revised'] == true) {
  1686. $score_label = get_lang('Revised');
  1687. $class = '';
  1688. } else {
  1689. $score_label = get_lang('NotRevised');
  1690. $class = 'error';
  1691. }
  1692. }
  1693. $question_title = $this->question;
  1694. // display question category, if any
  1695. $header = TestCategory::returnCategoryAndTitle($this->id);
  1696. $show_media = null;
  1697. if ($show_media) {
  1698. $header .= $this->show_media_content();
  1699. }
  1700. $header .= Display::page_subheader2($counter_label.". ".$question_title);
  1701. $hideLabel = api_get_configuration_value('exercise_hide_label');
  1702. $label = "<div class=\"rib rib-$class\"><h3>$score_label</h3></div> <h4>{$score['result']}</h4>";
  1703. if ($hideLabel === true) {
  1704. $label = "<h4>{$score['result']}<h4/>";
  1705. }
  1706. $header .= Display::div(
  1707. $label,
  1708. array('class' => 'ribbon')
  1709. );
  1710. if ($this->type != READING_COMPREHENSION) {
  1711. // Do not show the description (the text to read) if the question is of type READING_COMPREHENSION
  1712. $header .= Display::div($this->description, array('class' => 'question_description'));
  1713. } else {
  1714. if ($score['pass'] == true) {
  1715. $message = Display::div(
  1716. sprintf(
  1717. get_lang('ReadingQuestionCongratsSpeedXReachedForYWords'),
  1718. ReadingComprehension::$speeds[$this->level],
  1719. $this->getWordsCount()
  1720. )
  1721. );
  1722. } else {
  1723. $message = Display::div(
  1724. sprintf(
  1725. get_lang('ReadingQuestionCongratsSpeedXNotReachedForYWords'),
  1726. ReadingComprehension::$speeds[$this->level],
  1727. $this->getWordsCount()
  1728. )
  1729. );
  1730. }
  1731. $header .= $message.'<br />';
  1732. }
  1733. return $header;
  1734. }
  1735. /**
  1736. * Create a question from a set of parameters
  1737. * @param int Quiz ID
  1738. * @param string Question name
  1739. * @param int Maximum result for the question
  1740. * @param int Type of question (see constants at beginning of question.class.php)
  1741. * @param int Question level/category
  1742. * @param string $quiz_id
  1743. */
  1744. public function create_question(
  1745. $quiz_id,
  1746. $question_name,
  1747. $question_description = '',
  1748. $max_score = 0,
  1749. $type = 1,
  1750. $level = 1
  1751. ) {
  1752. $course_id = api_get_course_int_id();
  1753. $tbl_quiz_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
  1754. $tbl_quiz_rel_question = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
  1755. $quiz_id = intval($quiz_id);
  1756. $max_score = (float) $max_score;
  1757. $type = intval($type);
  1758. $level = intval($level);
  1759. // Get the max position
  1760. $sql = "SELECT max(position) as max_position
  1761. FROM $tbl_quiz_question q
  1762. INNER JOIN $tbl_quiz_rel_question r
  1763. ON
  1764. q.id = r.question_id AND
  1765. exercice_id = $quiz_id AND
  1766. q.c_id = $course_id AND
  1767. r.c_id = $course_id";
  1768. $rs_max = Database::query($sql);
  1769. $row_max = Database::fetch_object($rs_max);
  1770. $max_position = $row_max->max_position + 1;
  1771. $params = [
  1772. 'c_id' => $course_id,
  1773. 'question' => $question_name,
  1774. 'description' => $question_description,
  1775. 'ponderation' => $max_score,
  1776. 'position' => $max_position,
  1777. 'type' => $type,
  1778. 'level' => $level,
  1779. ];
  1780. $question_id = Database::insert($tbl_quiz_question, $params);
  1781. if ($question_id) {
  1782. $sql = "UPDATE $tbl_quiz_question SET id = iid WHERE iid = $question_id";
  1783. Database::query($sql);
  1784. // Get the max question_order
  1785. $sql = "SELECT max(question_order) as max_order
  1786. FROM $tbl_quiz_rel_question
  1787. WHERE c_id = $course_id AND exercice_id = $quiz_id ";
  1788. $rs_max_order = Database::query($sql);
  1789. $row_max_order = Database::fetch_object($rs_max_order);
  1790. $max_order = $row_max_order->max_order + 1;
  1791. // Attach questions to quiz
  1792. $sql = "INSERT INTO $tbl_quiz_rel_question (c_id, question_id, exercice_id, question_order)
  1793. VALUES($course_id, $question_id, $quiz_id, $max_order)";
  1794. Database::query($sql);
  1795. }
  1796. return $question_id;
  1797. }
  1798. /**
  1799. * @return array the image filename of the question type
  1800. */
  1801. public function get_type_icon_html()
  1802. {
  1803. $type = $this->selectType();
  1804. $tabQuestionList = self::get_question_type_list(); // [0]=file to include [1]=type name
  1805. require_once $tabQuestionList[$type][0];
  1806. $img = $explanation = null;
  1807. eval('$img = '.$tabQuestionList[$type][1].'::$typePicture;');
  1808. eval('$explanation = get_lang('.$tabQuestionList[$type][1].'::$explanationLangVar);');
  1809. return array($img, $explanation);
  1810. }
  1811. /**
  1812. * Get course medias
  1813. * @param int course id
  1814. * @param integer $course_id
  1815. */
  1816. static function get_course_medias(
  1817. $course_id,
  1818. $start = 0,
  1819. $limit = 100,
  1820. $sidx = "question",
  1821. $sord = "ASC",
  1822. $where_condition = array()
  1823. ) {
  1824. $table_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
  1825. $default_where = array('c_id = ? AND parent_id = 0 AND type = ?' => array($course_id, MEDIA_QUESTION));
  1826. $result = Database::select(
  1827. '*',
  1828. $table_question,
  1829. array(
  1830. 'limit' => " $start, $limit",
  1831. 'where' => $default_where,
  1832. 'order' => "$sidx $sord"
  1833. )
  1834. );
  1835. return $result;
  1836. }
  1837. /**
  1838. * Get count course medias
  1839. * @param int course id
  1840. *
  1841. * @return int
  1842. */
  1843. static function get_count_course_medias($course_id)
  1844. {
  1845. $table_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
  1846. $result = Database::select(
  1847. 'count(*) as count',
  1848. $table_question,
  1849. array(
  1850. 'where' => array(
  1851. 'c_id = ? AND parent_id = 0 AND type = ?' => array(
  1852. $course_id,
  1853. MEDIA_QUESTION,
  1854. ),
  1855. )
  1856. ),
  1857. 'first'
  1858. );
  1859. if ($result && isset($result['count'])) {
  1860. return $result['count'];
  1861. }
  1862. return 0;
  1863. }
  1864. /**
  1865. * @param int $course_id
  1866. * @return array
  1867. */
  1868. public static function prepare_course_media_select($course_id)
  1869. {
  1870. $medias = self::get_course_medias($course_id);
  1871. $media_list = array();
  1872. $media_list[0] = get_lang('NoMedia');
  1873. if (!empty($medias)) {
  1874. foreach ($medias as $media) {
  1875. $media_list[$media['id']] = empty($media['question']) ? get_lang('Untitled') : $media['question'];
  1876. }
  1877. }
  1878. return $media_list;
  1879. }
  1880. /**
  1881. * @return integer[]
  1882. */
  1883. public static function get_default_levels()
  1884. {
  1885. $select_level = array(
  1886. 1 => 1,
  1887. 2 => 2,
  1888. 3 => 3,
  1889. 4 => 4,
  1890. 5 => 5
  1891. );
  1892. return $select_level;
  1893. }
  1894. /**
  1895. * @return string
  1896. */
  1897. public function show_media_content()
  1898. {
  1899. $html = '';
  1900. if ($this->parent_id != 0) {
  1901. $parent_question = self::read($this->parent_id);
  1902. $html = $parent_question->show_media_content();
  1903. } else {
  1904. $html .= Display::page_subheader($this->selectTitle());
  1905. $html .= $this->selectDescription();
  1906. }
  1907. return $html;
  1908. }
  1909. /**
  1910. * Swap between unique and multiple type answers
  1911. * @return UniqueAnswer|MultipleAnswer
  1912. */
  1913. public function swapSimpleAnswerTypes()
  1914. {
  1915. $oppositeAnswers = array(
  1916. UNIQUE_ANSWER => MULTIPLE_ANSWER,
  1917. MULTIPLE_ANSWER => UNIQUE_ANSWER
  1918. );
  1919. $this->type = $oppositeAnswers[$this->type];
  1920. Database::update(
  1921. Database::get_course_table(TABLE_QUIZ_QUESTION),
  1922. array('type' => $this->type),
  1923. array('c_id = ? AND id = ?' => array($this->course['real_id'], $this->id))
  1924. );
  1925. $answerClasses = array(
  1926. UNIQUE_ANSWER => 'UniqueAnswer',
  1927. MULTIPLE_ANSWER => 'MultipleAnswer'
  1928. );
  1929. $swappedAnswer = new $answerClasses[$this->type];
  1930. foreach ($this as $key => $value) {
  1931. $swappedAnswer->$key = $value;
  1932. }
  1933. return $swappedAnswer;
  1934. }
  1935. }