question.class.php 69 KB

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