question.class.php 53 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451
  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. if(!class_exists('Question')):
  14. // answer types
  15. define('UNIQUE_ANSWER', 1);
  16. define('MULTIPLE_ANSWER', 2);
  17. define('FILL_IN_BLANKS', 3);
  18. define('MATCHING', 4);
  19. define('FREE_ANSWER', 5);
  20. define('HOT_SPOT', 6);
  21. define('HOT_SPOT_ORDER', 7);
  22. define('HOT_SPOT_DELINEATION', 8);
  23. define('MULTIPLE_ANSWER_COMBINATION', 9);
  24. define('UNIQUE_ANSWER_NO_OPTION', 10);
  25. define('MULTIPLE_ANSWER_TRUE_FALSE', 11);
  26. define('MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE', 12);
  27. if (!class_exists('Category')) include_once("testcategory.class.php"); // hub 12-10-2011
  28. /**
  29. QUESTION CLASS
  30. *
  31. * This class allows to instantiate an object of type Question
  32. *
  33. * @author Olivier Brouckaert, original author
  34. * @author Patrick Cool, LaTeX support
  35. * @package chamilo.exercise
  36. */
  37. abstract class Question
  38. {
  39. public $id;
  40. public $question;
  41. public $description;
  42. public $weighting;
  43. public $position;
  44. public $type;
  45. public $level;
  46. public $picture;
  47. public $exerciseList; // array with the list of exercises which this question is in
  48. public $category; // hub 12-10-2011
  49. private $isContent;
  50. public $course;
  51. static $typePicture = 'new_question.png';
  52. static $explanationLangVar = '';
  53. static $questionTypes = array(
  54. UNIQUE_ANSWER => array('unique_answer.class.php' , 'UniqueAnswer'),
  55. MULTIPLE_ANSWER => array('multiple_answer.class.php' , 'MultipleAnswer'),
  56. FILL_IN_BLANKS => array('fill_blanks.class.php' , 'FillBlanks'),
  57. MATCHING => array('matching.class.php' , 'Matching'),
  58. FREE_ANSWER => array('freeanswer.class.php' , 'FreeAnswer'),
  59. HOT_SPOT => array('hotspot.class.php' , 'HotSpot'),
  60. HOT_SPOT_DELINEATION => array('hotspot.class.php' , 'HotspotDelineation'),
  61. MULTIPLE_ANSWER_COMBINATION => array('multiple_answer_combination.class.php' , 'MultipleAnswerCombination'),
  62. UNIQUE_ANSWER_NO_OPTION => array('unique_answer_no_option.class.php' , 'UniqueAnswerNoOption'),
  63. MULTIPLE_ANSWER_TRUE_FALSE => array('multiple_answer_true_false.class.php' , 'MultipleAnswerTrueFalse'),
  64. MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE => array('multiple_answer_combination_true_false.class.php' , 'MultipleAnswerCombinationTrueFalse'),
  65. );
  66. /**
  67. * constructor of the class
  68. *
  69. * @author - Olivier Brouckaert
  70. */
  71. public function Question() {
  72. $this->id=0;
  73. $this->question='';
  74. $this->description='';
  75. $this->weighting=0;
  76. $this->position=1;
  77. $this->picture='';
  78. $this->level = 1;
  79. $this->category=0; // hub 12-10-2011
  80. $this->extra='';
  81. $this->exerciseList=array();
  82. $this->course = api_get_course_info();
  83. }
  84. public function getIsContent() {
  85. $isContent = null;
  86. if (isset($_REQUEST['isContent'])) {
  87. $isContent = intval($_REQUEST['isContent']);
  88. }
  89. return $this->isContent = $isContent;
  90. }
  91. /**
  92. * Reads question informations from the data base
  93. *
  94. * @author - Olivier Brouckaert
  95. * @param - integer $id - question ID
  96. * @return - boolean - true if question exists, otherwise false
  97. */
  98. static function read($id, $course_id = null) {
  99. $id = intval($id);
  100. if (!empty($course_id)) {
  101. $course_info = api_get_course_info_by_id($course_id);
  102. } else {
  103. global $course;
  104. $course_info = api_get_course_info();
  105. }
  106. $course_id = $course_info['real_id'];
  107. $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST, $course_info['db_name']);
  108. $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION, $course_info['db_name']);
  109. $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION, $course_info['db_name']);
  110. $sql = "SELECT question,description,ponderation,position,type,picture,level,extra FROM $TBL_QUESTIONS WHERE c_id = $course_id AND id = $id ";
  111. $result = Database::query($sql);
  112. // if the question has been found
  113. if ($object = Database::fetch_object($result)) {
  114. $objQuestion = Question::getInstance($object->type);
  115. $objQuestion->id = $id;
  116. $objQuestion->question = $object->question;
  117. $objQuestion->description = $object->description;
  118. $objQuestion->weighting = $object->ponderation;
  119. $objQuestion->position = $object->position;
  120. $objQuestion->type = $object->type;
  121. $objQuestion->picture = $object->picture;
  122. $objQuestion->level = (int) $object->level;
  123. $objQuestion->extra = $object->extra;
  124. $objQuestion->course = $course_info;
  125. $objQuestion->category = Testcategory::getCategoryForQuestion($id); // hub 12-10-2011
  126. $sql = "SELECT exercice_id FROM $TBL_EXERCICE_QUESTION WHERE c_id = $course_id AND question_id = $id";
  127. $result_exercise_list = Database::query($sql);
  128. // fills the array with the exercises which this question is in
  129. if ($result_exercise_list) {
  130. while ($obj = Database::fetch_object($result_exercise_list)) {
  131. $objQuestion->exerciseList[] = $obj->exercice_id;
  132. }
  133. }
  134. return $objQuestion;
  135. }
  136. // question not found
  137. return false;
  138. }
  139. /**
  140. * returns the question ID
  141. *
  142. * @author - Olivier Brouckaert
  143. * @return - integer - question ID
  144. */
  145. function selectId() {
  146. return $this->id;
  147. }
  148. /**
  149. * returns the question title
  150. *
  151. * @author - Olivier Brouckaert
  152. * @return - string - question title
  153. */
  154. function selectTitle() {
  155. $this->question=text_filter($this->question);
  156. return $this->question;
  157. }
  158. /**
  159. * returns the question description
  160. *
  161. * @author - Olivier Brouckaert
  162. * @return - string - question description
  163. */
  164. function selectDescription() {
  165. $this->description=text_filter($this->description);
  166. return $this->description;
  167. }
  168. /**
  169. * returns the question weighting
  170. *
  171. * @author - Olivier Brouckaert
  172. * @return - integer - question weighting
  173. */
  174. function selectWeighting()
  175. {
  176. return $this->weighting;
  177. }
  178. /**
  179. * returns the question position
  180. *
  181. * @author - Olivier Brouckaert
  182. * @return - integer - question position
  183. */
  184. function selectPosition()
  185. {
  186. return $this->position;
  187. }
  188. /**
  189. * returns the answer type
  190. *
  191. * @author - Olivier Brouckaert
  192. * @return - integer - answer type
  193. */
  194. function selectType()
  195. {
  196. return $this->type;
  197. }
  198. /**
  199. * returns the level of the question
  200. *
  201. * @author - Nicolas Raynaud
  202. * @return - integer - level of the question, 0 by default.
  203. */
  204. function selectLevel()
  205. {
  206. return $this->level;
  207. }
  208. /**
  209. * returns the picture name
  210. *
  211. * @author - Olivier Brouckaert
  212. * @return - string - picture name
  213. */
  214. function selectPicture()
  215. {
  216. return $this->picture;
  217. }
  218. function selectPicturePath() {
  219. if (!empty($this->picture)) {
  220. return api_get_path(WEB_COURSE_PATH).$this->course['path'].'/document/images/'.$this->picture;
  221. }
  222. return false;
  223. }
  224. /**
  225. * returns the array with the exercise ID list
  226. *
  227. * @author - Olivier Brouckaert
  228. * @return - array - list of exercise ID which the question is in
  229. */
  230. function selectExerciseList()
  231. {
  232. return $this->exerciseList;
  233. }
  234. /**
  235. * returns the number of exercises which this question is in
  236. *
  237. * @author - Olivier Brouckaert
  238. * @return - integer - number of exercises
  239. */
  240. function selectNbrExercises()
  241. {
  242. return sizeof($this->exerciseList);
  243. }
  244. /**
  245. * changes the question title
  246. *
  247. * @author - Olivier Brouckaert
  248. * @param - string $title - question title
  249. */
  250. function updateTitle($title)
  251. {
  252. $this->question=$title;
  253. }
  254. /**
  255. * changes the question description
  256. *
  257. * @author - Olivier Brouckaert
  258. * @param - string $description - question description
  259. */
  260. function updateDescription($description)
  261. {
  262. $this->description=$description;
  263. }
  264. /**
  265. * changes the question weighting
  266. *
  267. * @author - Olivier Brouckaert
  268. * @param - integer $weighting - question weighting
  269. */
  270. function updateWeighting($weighting)
  271. {
  272. $this->weighting=$weighting;
  273. }
  274. /**
  275. * @author - Hubert Borderiou 12-10-2011
  276. * @param - array of category $in_category
  277. */
  278. function updateCategory($in_category) {
  279. $this->category=$in_category;
  280. }
  281. /**
  282. * @author - Hubert Borderiou 12-10-2011
  283. * @param - interger $in_positive
  284. */
  285. function updateScoreAlwaysPositive($in_positive) {
  286. $this->scoreAlwaysPositive=$in_positive;
  287. }
  288. /**
  289. * @author - Hubert Borderiou 12-10-2011
  290. * @param - interger $in_positive
  291. */
  292. function updateUncheckedMayScore($in_positive) {
  293. $this->uncheckedMayScore=$in_positive;
  294. }
  295. /**
  296. * @author - Hubert Borderiou 12-10-2011
  297. * @param - interger $in_positive
  298. * in this version, a question can only have 1 category
  299. * if category is 0, then question has no category then delete the category entry
  300. */
  301. function saveCategory($in_category) {
  302. if ($in_category <= 0) {
  303. $this->deleteCategory();
  304. }
  305. // update or add category for a question
  306. else {
  307. $TBL_QUESTION_REL_CATEGORY = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
  308. $category_id = Database::escape_string($in_category);
  309. $question_id = Database::escape_string($this->id);
  310. $sql = "SELECT count(*) AS nb FROM $TBL_QUESTION_REL_CATEGORY WHERE question_id=$question_id AND c_id=".api_get_course_int_id();
  311. $res = Database::query($sql);
  312. $row = Database::fetch_array($res);
  313. if ($row['nb'] > 0){
  314. $sql = "UPDATE $TBL_QUESTION_REL_CATEGORY SET category_id=$category_id WHERE question_id=$question_id AND c_id=".api_get_course_int_id();
  315. $res = Database::query($sql);
  316. }
  317. else {
  318. $sql = "INSERT INTO $TBL_QUESTION_REL_CATEGORY VALUES (".api_get_course_int_id().", $question_id, $category_id)";
  319. $res = Database::query($sql);
  320. }
  321. }
  322. }
  323. /**
  324. * @author hubert borderiou 12-10-2011
  325. * delete any category entry for question id
  326. * @param : none
  327. * delte the category for question
  328. */
  329. function deleteCategory() {
  330. $TBL_QUESTION_REL_CATEGORY = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
  331. $question_id = Database::escape_string($this->id);
  332. $sql = "DELETE FROM $TBL_QUESTION_REL_CATEGORY WHERE question_id=$question_id AND c_id=".api_get_course_int_id();
  333. $res = Database::query($sql);
  334. }
  335. /**
  336. * changes the question position
  337. *
  338. * @author - Olivier Brouckaert
  339. * @param - integer $position - question position
  340. */
  341. function updatePosition($position)
  342. {
  343. $this->position=$position;
  344. }
  345. /**
  346. * changes the question level
  347. *
  348. * @author - Nicolas Raynaud
  349. * @param - integer $level - question level
  350. */
  351. function updateLevel($level)
  352. {
  353. $this->level=$level;
  354. }
  355. /**
  356. * changes the answer type. If the user changes the type from "unique answer" to "multiple answers"
  357. * (or conversely) answers are not deleted, otherwise yes
  358. *
  359. * @author - Olivier Brouckaert
  360. * @param - integer $type - answer type
  361. */
  362. function updateType($type) {
  363. $TBL_REPONSES = Database::get_course_table(TABLE_QUIZ_ANSWER, $this->course['db_name']);
  364. // if we really change the type
  365. if($type != $this->type) {
  366. // if we don't change from "unique answer" to "multiple answers" (or conversely)
  367. if(!in_array($this->type,array(UNIQUE_ANSWER,MULTIPLE_ANSWER)) || !in_array($type,array(UNIQUE_ANSWER,MULTIPLE_ANSWER))) {
  368. // removes old answers
  369. $sql="DELETE FROM $TBL_REPONSES WHERE question_id='".Database::escape_string($this->id)."'";
  370. Database::query($sql);
  371. }
  372. $this->type=$type;
  373. }
  374. }
  375. /**
  376. * adds a picture to the question
  377. *
  378. * @author - Olivier Brouckaert
  379. * @param - string $Picture - temporary path of the picture to upload
  380. * @param - string $PictureName - Name of the picture
  381. * @return - boolean - true if uploaded, otherwise false
  382. */
  383. function uploadPicture($Picture,$PictureName) {
  384. global $picturePath;
  385. if (!file_exists($picturePath)) {
  386. if (mkdir($picturePath, api_get_permissions_for_new_directories())) {
  387. // document path
  388. $documentPath = api_get_path(SYS_COURSE_PATH) . $this->course['path'] . "/document";
  389. $path = str_replace($documentPath,'',$picturePath);
  390. $title_path = basename($picturePath);
  391. $doc_id = add_document($this->course, $path, 'folder', 0,$title_path);
  392. api_item_property_update($this->course, TOOL_DOCUMENT, $doc_id, 'FolderCreated', api_get_user_id());
  393. }
  394. }
  395. // if the question has got an ID
  396. if ($this->id) {
  397. $extension = pathinfo($PictureName, PATHINFO_EXTENSION);
  398. $this->picture = 'quiz-'.$this->id.'.jpg';
  399. $o_img = new Image($Picture);
  400. $o_img->send_image($picturePath.'/'.$this->picture, -1, 'jpg');
  401. $document_id = add_document($this->course, '/images/'.$this->picture, 'file', filesize($picturePath.'/'.$this->picture),$this->picture);
  402. if ($document_id) {
  403. return api_item_property_update($this->course, TOOL_DOCUMENT, $document_id, 'DocumentAdded', api_get_user_id);
  404. }
  405. }
  406. return false;
  407. }
  408. /**
  409. * Resizes a picture || Warning!: can only be called after uploadPicture, or if picture is already available in object.
  410. *
  411. * @author - Toon Keppens
  412. * @param - string $Dimension - Resizing happens proportional according to given dimension: height|width|any
  413. * @param - integer $Max - Maximum size
  414. * @return - boolean - true if success, false if failed
  415. */
  416. function resizePicture($Dimension, $Max) {
  417. global $picturePath;
  418. // if the question has an ID
  419. if($this->id) {
  420. // Get dimensions from current image.
  421. $my_image = new Image($picturePath.'/'.$this->picture);
  422. $current_image_size = $my_image->get_image_size();
  423. $current_width = $current_image_size['width'];
  424. $current_height = $current_image_size['height'];
  425. if($current_width < $Max && $current_height <$Max)
  426. return true;
  427. elseif($current_height == "")
  428. return false;
  429. // Resize according to height.
  430. if ($Dimension == "height") {
  431. $resize_scale = $current_height / $Max;
  432. $new_height = $Max;
  433. $new_width = ceil($current_width / $resize_scale);
  434. }
  435. // Resize according to width
  436. if ($Dimension == "width") {
  437. $resize_scale = $current_width / $Max;
  438. $new_width = $Max;
  439. $new_height = ceil($current_height / $resize_scale);
  440. }
  441. // Resize according to height or width, both should not be larger than $Max after resizing.
  442. if ($Dimension == "any") {
  443. if ($current_height > $current_width || $current_height == $current_width)
  444. {
  445. $resize_scale = $current_height / $Max;
  446. $new_height = $Max;
  447. $new_width = ceil($current_width / $resize_scale);
  448. }
  449. if ($current_height < $current_width)
  450. {
  451. $resize_scale = $current_width / $Max;
  452. $new_width = $Max;
  453. $new_height = ceil($current_height / $resize_scale);
  454. }
  455. }
  456. $my_image->resize($new_width, $new_height);
  457. $result = $my_image->send_image($picturePath.'/'.$this->picture);
  458. if ($result) {
  459. return true;
  460. } else {
  461. return false;
  462. }
  463. }
  464. }
  465. /**
  466. * deletes the picture
  467. *
  468. * @author - Olivier Brouckaert
  469. * @return - boolean - true if removed, otherwise false
  470. */
  471. function removePicture()
  472. {
  473. global $picturePath;
  474. // if the question has got an ID and if the picture exists
  475. if($this->id)
  476. {
  477. $picture=$this->picture;
  478. $this->picture='';
  479. return @unlink($picturePath.'/'.$picture)?true:false;
  480. }
  481. return false;
  482. }
  483. /**
  484. * Exports a picture to another question
  485. *
  486. * @author - Olivier Brouckaert
  487. * @param - integer $questionId - ID of the target question
  488. * @return - boolean - true if copied, otherwise false
  489. */
  490. function exportPicture($questionId, $course_info) {
  491. $course_id = $course_info['real_id'];
  492. $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION, $course_info['db_name']);
  493. $destination_path = api_get_path(SYS_COURSE_PATH).$course_info['path'].'/document/images';
  494. $source_path = api_get_path(SYS_COURSE_PATH).$this->course['path'].'/document/images';
  495. // if the question has got an ID and if the picture exists
  496. if($this->id && !empty($this->picture)) {
  497. $picture=explode('.',$this->picture);
  498. $Extension=$picture[sizeof($picture)-1];
  499. $picture='quiz-'.$questionId.'.'.$Extension;
  500. $sql="UPDATE $TBL_QUESTIONS SET picture='".Database::escape_string($picture)."' WHERE c_id = $course_id AND id='".intval($questionId)."'";
  501. Database::query($sql);
  502. return @copy($source_path.'/'.$this->picture, $destination_path.'/'.$picture)?true:false;
  503. }
  504. return false;
  505. }
  506. /**
  507. * Saves the picture coming from POST into a temporary file
  508. * Temporary pictures are used when we don't want to save a picture right after a form submission.
  509. * For example, if we first show a confirmation box.
  510. *
  511. * @author - Olivier Brouckaert
  512. * @param - string $Picture - temporary path of the picture to move
  513. * @param - string $PictureName - Name of the picture
  514. */
  515. function setTmpPicture($Picture,$PictureName) {
  516. global $picturePath;
  517. $PictureName=explode('.',$PictureName);
  518. $Extension=$PictureName[sizeof($PictureName)-1];
  519. // saves the picture into a temporary file
  520. @move_uploaded_file($Picture,$picturePath.'/tmp.'.$Extension);
  521. }
  522. /**
  523. Sets the title
  524. */
  525. public function setTitle($title)
  526. {
  527. $this->question = $title;
  528. }
  529. /**
  530. Sets the title
  531. */
  532. public function setExtra($extra)
  533. {
  534. $this->extra = $extra;
  535. }
  536. /**
  537. * Moves the temporary question "tmp" to "quiz-$questionId"
  538. * Temporary pictures are used when we don't want to save a picture right after a form submission.
  539. * For example, if we first show a confirmation box.
  540. *
  541. * @author - Olivier Brouckaert
  542. * @return - boolean - true if moved, otherwise false
  543. */
  544. function getTmpPicture()
  545. {
  546. global $picturePath;
  547. // if the question has got an ID and if the picture exists
  548. if($this->id)
  549. {
  550. if(file_exists($picturePath.'/tmp.jpg'))
  551. {
  552. $Extension='jpg';
  553. }
  554. elseif(file_exists($picturePath.'/tmp.gif'))
  555. {
  556. $Extension='gif';
  557. }
  558. elseif(file_exists($picturePath.'/tmp.png'))
  559. {
  560. $Extension='png';
  561. }
  562. $this->picture='quiz-'.$this->id.'.'.$Extension;
  563. return @rename($picturePath.'/tmp.'.$Extension,$picturePath.'/'.$this->picture)?true:false;
  564. }
  565. return false;
  566. }
  567. /**
  568. * updates the question in the data base
  569. * if an exercise ID is provided, we add that exercise ID into the exercise list
  570. *
  571. * @author - Olivier Brouckaert
  572. * @param - integer $exerciseId - exercise ID if saving in an exercise
  573. */
  574. function save($exerciseId=0) {
  575. $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION, $this->course['db_name']);
  576. $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION, $this->course['db_name']);
  577. $id = $this->id;
  578. $question = $this->question;
  579. $description = $this->description;
  580. $weighting = $this->weighting;
  581. $position = $this->position;
  582. $type = $this->type;
  583. $picture = $this->picture;
  584. $level = $this->level;
  585. $extra = $this->extra;
  586. $c_id = $this->course['real_id'];
  587. $category = $this->category; // hub 12-10-2011
  588. // question already exists
  589. if(!empty($id)) {
  590. $sql="UPDATE $TBL_QUESTIONS SET
  591. question ='".Database::escape_string($question)."',
  592. description ='".Database::escape_string($description)."',
  593. ponderation ='".Database::escape_string($weighting)."',
  594. position ='".Database::escape_string($position)."',
  595. type ='".Database::escape_string($type)."',
  596. picture ='".Database::escape_string($picture)."',
  597. extra ='".Database::escape_string($extra)."',
  598. level ='".Database::escape_string($level)."'
  599. WHERE id='".Database::escape_string($id)."'";
  600. Database::query($sql);
  601. $this->saveCategory($category); // hub 12-10-2011
  602. if (!empty($exerciseId)) {
  603. api_item_property_update($this->course, TOOL_QUIZ, $id,'QuizQuestionUpdated',api_get_user_id);
  604. }
  605. if (api_get_setting('search_enabled')=='true') {
  606. if ($exerciseId != 0) {
  607. $this -> search_engine_edit($exerciseId);
  608. } else {
  609. /**
  610. * actually there is *not* an user interface for
  611. * creating questions without a relation with an exercise
  612. */
  613. }
  614. }
  615. } else {
  616. // creates a new question
  617. $sql = "SELECT max(position) FROM $TBL_QUESTIONS as question, $TBL_EXERCICE_QUESTION as test_question
  618. WHERE question.id = test_question.question_id AND
  619. test_question.exercice_id = '".Database::escape_string($exerciseId)."' AND
  620. question.c_id = $c_id AND
  621. test_question.c_id = $c_id
  622. ";
  623. $result = Database::query($sql);
  624. $current_position = Database::result($result,0,0);
  625. $this->updatePosition($current_position+1);
  626. $position = $this->position;
  627. $sql = "INSERT INTO $TBL_QUESTIONS (c_id, question, description, ponderation, position, type, picture, extra, level) VALUES (
  628. $c_id,
  629. '".Database::escape_string($question)."',
  630. '".Database::escape_string($description)."',
  631. '".Database::escape_string($weighting)."',
  632. '".Database::escape_string($position)."',
  633. '".Database::escape_string($type)."',
  634. '".Database::escape_string($picture)."',
  635. '".Database::escape_string($extra)."',
  636. '".Database::escape_string($level)."'
  637. )";
  638. Database::query($sql);
  639. $this->id = Database::insert_id();
  640. api_item_property_update($this->course, TOOL_QUIZ, $this->id,'QuizQuestionAdded',api_get_user_id());
  641. // If hotspot, create first answer
  642. if ($type == HOT_SPOT || $type == HOT_SPOT_ORDER) {
  643. $TBL_ANSWERS = Database::get_course_table(TABLE_QUIZ_ANSWER);
  644. $sql = "INSERT INTO $TBL_ANSWERS (c_id, id, question_id , answer , correct , comment , ponderation , position , hotspot_coordinates , hotspot_type )
  645. VALUES (".$c_id.", '1', '".Database::escape_string($this->id)."', '', NULL , '', '10' , '1', '0;0|0|0', 'square')";
  646. Database::query($sql);
  647. }
  648. if ($type == HOT_SPOT_DELINEATION ) {
  649. $TBL_ANSWERS = Database::get_course_table(TABLE_QUIZ_ANSWER);
  650. $sql="INSERT INTO $TBL_ANSWERS (c_id, id, question_id , answer , correct , comment , ponderation , position , hotspot_coordinates , hotspot_type )
  651. VALUES (".$c_id.", '1', '".Database::escape_string($this->id)."', '', NULL , '', '10' , '1', '0;0|0|0', 'delineation')";
  652. Database::query($sql);
  653. }
  654. if (api_get_setting('search_enabled')=='true') {
  655. if ($exerciseId != 0) {
  656. $this -> search_engine_edit($exerciseId, TRUE);
  657. }
  658. else {
  659. /**
  660. * actually there is *not* an user interface for
  661. * creating questions without a relation with an exercise
  662. */
  663. }
  664. }
  665. }
  666. // if the question is created in an exercise
  667. if($exerciseId) {
  668. /*
  669. $sql = 'UPDATE '.Database::get_course_table(TABLE_LP_ITEM).'
  670. SET max_score = '.intval($weighting).'
  671. WHERE item_type = "'.TOOL_QUIZ.'"
  672. AND path='.intval($exerciseId);
  673. Database::query($sql);
  674. */
  675. // adds the exercise into the exercise list of this question
  676. $this->addToList($exerciseId, TRUE);
  677. }
  678. }
  679. function search_engine_edit($exerciseId, $addQs=FALSE, $rmQs=FALSE) {
  680. // update search engine and its values table if enabled
  681. if (api_get_setting('search_enabled')=='true' && extension_loaded('xapian')) {
  682. $course_id = api_get_course_id();
  683. // get search_did
  684. $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
  685. if ($addQs || $rmQs) {
  686. //there's only one row per question on normal db and one document per question on search engine db
  687. $sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_second_level=%s LIMIT 1';
  688. $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
  689. } else {
  690. $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';
  691. $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $exerciseId, $this->id);
  692. }
  693. $res = Database::query($sql);
  694. if (Database::num_rows($res) > 0 || $addQs) {
  695. require_once(api_get_path(LIBRARY_PATH) . 'search/DokeosIndexer.class.php');
  696. require_once(api_get_path(LIBRARY_PATH) . 'search/IndexableChunk.class.php');
  697. $di = new DokeosIndexer();
  698. if ($addQs) {
  699. $question_exercises = array((int)$exerciseId);
  700. } else {
  701. $question_exercises = array();
  702. }
  703. isset($_POST['language'])? $lang=Database::escape_string($_POST['language']): $lang = 'english';
  704. $di->connectDb(NULL, NULL, $lang);
  705. // retrieve others exercise ids
  706. $se_ref = Database::fetch_array($res);
  707. $se_doc = $di->get_document((int)$se_ref['search_did']);
  708. if ($se_doc !== FALSE) {
  709. if ( ($se_doc_data=$di->get_document_data($se_doc)) !== FALSE ) {
  710. $se_doc_data = unserialize($se_doc_data);
  711. if (isset($se_doc_data[SE_DATA]['type']) && $se_doc_data[SE_DATA]['type'] == SE_DOCTYPE_EXERCISE_QUESTION) {
  712. if (isset($se_doc_data[SE_DATA]['exercise_ids']) && is_array($se_doc_data[SE_DATA]['exercise_ids'])) {
  713. foreach ($se_doc_data[SE_DATA]['exercise_ids'] as $old_value) {
  714. if (!in_array($old_value, $question_exercises)) {
  715. $question_exercises[] = $old_value;
  716. }
  717. }
  718. }
  719. }
  720. }
  721. }
  722. if ($rmQs) {
  723. while ( ($key=array_search($exerciseId, $question_exercises)) !== FALSE) {
  724. unset($question_exercises[$key]);
  725. }
  726. }
  727. // build the chunk to index
  728. $ic_slide = new IndexableChunk();
  729. $ic_slide->addValue("title", $this->question);
  730. $ic_slide->addCourseId($course_id);
  731. $ic_slide->addToolId(TOOL_QUIZ);
  732. $xapian_data = array(
  733. SE_COURSE_ID => $course_id,
  734. SE_TOOL_ID => TOOL_QUIZ,
  735. SE_DATA => array('type' => SE_DOCTYPE_EXERCISE_QUESTION, 'exercise_ids' => $question_exercises, 'question_id' => (int)$this->id),
  736. SE_USER => (int)api_get_user_id(),
  737. );
  738. $ic_slide->xapian_data = serialize($xapian_data);
  739. $ic_slide->addValue("content", $this->description);
  740. //TODO: index answers, see also form validation on question_admin.inc.php
  741. $di->remove_document((int)$se_ref['search_did']);
  742. $di->addChunk($ic_slide);
  743. //index and return search engine document id
  744. if (!empty($question_exercises)) { // if empty there is nothing to index
  745. $did = $di->index();
  746. unset($di);
  747. }
  748. if ($did || $rmQs) {
  749. // save it to db
  750. if ($addQs || $rmQs) {
  751. $sql = 'DELETE FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_second_level=\'%s\'';
  752. $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
  753. } else {
  754. $sql = 'DELETE FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=\'%s\' AND ref_id_second_level=\'%s\'';
  755. $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $exerciseId, $this->id);
  756. }
  757. Database::query($sql);
  758. if ($rmQs) {
  759. if (!empty($question_exercises)) {
  760. $sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, ref_id_second_level, search_did)
  761. VALUES (NULL , \'%s\', \'%s\', %s, %s, %s)';
  762. $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, array_shift($question_exercises), $this->id, $did);
  763. Database::query($sql);
  764. }
  765. } else {
  766. $sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, ref_id_second_level, search_did)
  767. VALUES (NULL , \'%s\', \'%s\', %s, %s, %s)';
  768. $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $exerciseId, $this->id, $did);
  769. Database::query($sql);
  770. }
  771. }
  772. }
  773. }
  774. }
  775. /**
  776. * adds an exercise into the exercise list
  777. *
  778. * @author - Olivier Brouckaert
  779. * @param - integer $exerciseId - exercise ID
  780. * @param - boolean $fromSave - comming from $this->save() or not
  781. */
  782. function addToList($exerciseId, $fromSave = FALSE) {
  783. $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION, $this->course['db_name']);
  784. $id = $this->id;
  785. // checks if the exercise ID is not in the list
  786. if (!in_array($exerciseId,$this->exerciseList)) {
  787. $this->exerciseList[]=$exerciseId;
  788. $new_exercise = new Exercise();
  789. $new_exercise->read($exerciseId);
  790. $count = $new_exercise->selectNbrQuestions();
  791. $count++;
  792. $sql="INSERT INTO $TBL_EXERCICE_QUESTION (c_id, question_id, exercice_id, question_order) VALUES
  793. ({$this->course['real_id']}, '".Database::escape_string($id)."','".Database::escape_string($exerciseId)."', '$count' )";
  794. Database::query($sql);
  795. // we do not want to reindex if we had just saved adnd indexed the question
  796. if (!$fromSave) {
  797. $this->search_engine_edit($exerciseId, TRUE);
  798. }
  799. }
  800. }
  801. /**
  802. * removes an exercise from the exercise list
  803. *
  804. * @author - Olivier Brouckaert
  805. * @param - integer $exerciseId - exercise ID
  806. * @return - boolean - true if removed, otherwise false
  807. */
  808. function removeFromList($exerciseId) {
  809. global $TBL_EXERCICE_QUESTION;
  810. $id=$this->id;
  811. // searches the position of the exercise ID in the list
  812. $pos=array_search($exerciseId,$this->exerciseList);
  813. // exercise not found
  814. if($pos === false) {
  815. return false;
  816. } else {
  817. // deletes the position in the array containing the wanted exercise ID
  818. unset($this->exerciseList[$pos]);
  819. //update order of other elements
  820. $sql = "SELECT question_order FROM $TBL_EXERCICE_QUESTION WHERE question_id='".Database::escape_string($id)."' AND exercice_id='".Database::escape_string($exerciseId)."'";
  821. $res = Database::query($sql);
  822. if (Database::num_rows($res)>0) {
  823. $row = Database::fetch_array($res);
  824. if (!empty($row['question_order'])) {
  825. $sql = "UPDATE $TBL_EXERCICE_QUESTION SET question_order = question_order-1 WHERE exercice_id='".Database::escape_string($exerciseId)."' AND question_order > ".$row['question_order'];
  826. $res = Database::query($sql);
  827. }
  828. }
  829. $sql="DELETE FROM $TBL_EXERCICE_QUESTION WHERE question_id='".Database::escape_string($id)."' AND exercice_id='".Database::escape_string($exerciseId)."'";
  830. Database::query($sql);
  831. return true;
  832. }
  833. }
  834. /**
  835. * Deletes a question from the database
  836. * the parameter tells if the question is removed from all exercises (value = 0),
  837. * or just from one exercise (value = exercise ID)
  838. *
  839. * @author - Olivier Brouckaert
  840. * @param - integer $deleteFromEx - exercise ID if the question is only removed from one exercise
  841. */
  842. function delete($deleteFromEx=0) {
  843. global $_course,$_user;
  844. $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION, $this->course['db_name']);
  845. $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION, $this->course['db_name']);
  846. $TBL_REPONSES = Database::get_course_table(TABLE_QUIZ_ANSWER, $this->course['db_name']);
  847. $TBL_QUIZ_QUESTION_REL_CATEGORY = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY, $this->course['db_name']); // hub 12-10-2011
  848. $id=$this->id;
  849. // if the question must be removed from all exercises
  850. if(!$deleteFromEx)
  851. {
  852. //update the question_order of each question to avoid inconsistencies
  853. $sql = "SELECT exercice_id, question_order FROM $TBL_EXERCICE_QUESTION WHERE question_id='".Database::escape_string($id)."'";
  854. $res = Database::query($sql);
  855. if (Database::num_rows($res)>0) {
  856. while ($row = Database::fetch_array($res)) {
  857. if (!empty($row['question_order'])) {
  858. $sql = "UPDATE $TBL_EXERCICE_QUESTION SET question_order = question_order-1 WHERE exercice_id='".Database::escape_string($row['exercice_id'])."' AND question_order > ".$row['question_order'];
  859. $res = Database::query($sql);
  860. }
  861. }
  862. }
  863. $sql="DELETE FROM $TBL_EXERCICE_QUESTION WHERE question_id='".Database::escape_string($id)."'";
  864. Database::query($sql);
  865. $sql="DELETE FROM $TBL_QUESTIONS WHERE id='".Database::escape_string($id)."'";
  866. Database::query($sql);
  867. $sql="DELETE FROM $TBL_REPONSES WHERE question_id='".Database::escape_string($id)."'";
  868. Database::query($sql);
  869. // hub 12-10-2011 remove the category of this question in the question_rel_category table
  870. $sql = "DELETE FROM $TBL_QUIZ_QUESTION_REL_CATEGORY WHERE question_id='".Database::escape_string($id)."' AND c_id=".api_get_course_int_id();
  871. Database::query($sql);
  872. api_item_property_update($this->course, TOOL_QUIZ, $id,'QuizQuestionDeleted',api_get_user_id());
  873. $this->removePicture();
  874. // resets the object
  875. $this->Question();
  876. }
  877. // just removes the exercise from the list
  878. else
  879. {
  880. $this->removeFromList($deleteFromEx);
  881. if (api_get_setting('search_enabled')=='true' && extension_loaded('xapian')) {
  882. // disassociate question with this exercise
  883. $this -> search_engine_edit($deleteFromEx, FALSE, TRUE);
  884. }
  885. api_item_property_update($this->course, TOOL_QUIZ, $id,'QuizQuestionDeleted',api_get_user_id());
  886. }
  887. }
  888. /**
  889. * Duplicates the question
  890. *
  891. * @author Olivier Brouckaert
  892. * @param array Course info of the destination course
  893. * @return int ID of the new question
  894. */
  895. function duplicate($course_info = null) {
  896. if (empty($course_info)) {
  897. $course_info = $this->course;
  898. } else {
  899. $course_info = $course_info;
  900. }
  901. $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION, $course_info['db_name']);
  902. $TBL_QUESTION_OPTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION, $course_info['db_name']);
  903. $question = $this->question;
  904. $description = $this->description;
  905. $weighting = $this->weighting;
  906. $position = $this->position;
  907. $type = $this->type;
  908. $level = intval($this->level);
  909. $extra = $this->extra;
  910. //Using the same method used in the course copy to transform URLs
  911. require_once api_get_path(LIBRARY_PATH).'document.lib.php';
  912. if ($course_info['db_name'] != $this->course['db_name']) {
  913. $description = DocumentManager::replace_urls_inside_content_html_from_copy_course($description, $this->course['id'], $course_info['id']);
  914. $question = DocumentManager::replace_urls_inside_content_html_from_copy_course($question, $this->course['id'], $course_info['id']);
  915. }
  916. $course_id = $course_info['real_id'];
  917. $options = self::readQuestionOption($this->id, $course_id);
  918. //Inserting in the new course db / or the same course db
  919. $sql = "INSERT INTO $TBL_QUESTIONS (c_id, question, description, ponderation, position, type, level, extra )
  920. VALUES('$course_id', '".Database::escape_string($question)."','".Database::escape_string($description)."','".Database::escape_string($weighting)."','".Database::escape_string($position)."','".Database::escape_string($type)."' ,'".Database::escape_string($level)."' ,'".Database::escape_string($extra)."' )";
  921. Database::query($sql);
  922. $new_question_id = Database::insert_id();
  923. if (!empty($options)) {
  924. //Saving the quiz_options
  925. foreach ($options as $item) {
  926. $item['question_id'] = $new_question_id;
  927. $item['c_id'] = $course_id;
  928. unset($item['id']);
  929. Database::insert($TBL_QUESTION_OPTIONS, $item);
  930. }
  931. }
  932. // Duplicates the picture
  933. $this->exportPicture($new_question_id, $course_info);
  934. return $new_question_id;
  935. }
  936. /**
  937. * Returns an instance of the class corresponding to the type
  938. * @param integer $type the type of the question
  939. * @return an instance of a Question subclass (or of Questionc class by default)
  940. */
  941. static function getInstance ($type) {
  942. if (!is_null($type)) {
  943. list($file_name,$class_name) = self::$questionTypes[$type];
  944. include_once($file_name);
  945. if (class_exists($class_name)) {
  946. return new $class_name();
  947. } else {
  948. echo 'Can\'t instanciate class '.$class_name.' of type '.$type;
  949. return null;
  950. }
  951. }
  952. }
  953. /**
  954. * Creates the form to create / edit a question
  955. * A subclass can redifine this function to add fields...
  956. * @param FormValidator $form the formvalidator instance (by reference)
  957. */
  958. function createForm (&$form,$fck_config=0) {
  959. echo ' <style>
  960. div.row div.label{ width: 8%; }
  961. div.row div.formw{ width: 88%; }
  962. .media { display:none;}
  963. </style>';
  964. echo '<script>
  965. // hack to hide http://cksource.com/forums/viewtopic.php?f=6&t=8700
  966. function FCKeditor_OnComplete( editorInstance )
  967. {
  968. if (document.getElementById ( \'HiddenFCK\' + editorInstance.Name )) {
  969. HideFCKEditorByInstanceName (editorInstance.Name);
  970. }
  971. }
  972. function HideFCKEditorByInstanceName ( editorInstanceName ) {
  973. if (document.getElementById ( \'HiddenFCK\' + editorInstanceName ).className == "HideFCKEditor" ) {
  974. document.getElementById ( \'HiddenFCK\' + editorInstanceName ).className = "media";
  975. }
  976. }
  977. function show_media()
  978. {
  979. var my_display = document.getElementById(\'HiddenFCKquestionDescription\').style.display;
  980. if(my_display== \'none\' || my_display == \'\') {
  981. document.getElementById(\'HiddenFCKquestionDescription\').style.display = \'block\';
  982. document.getElementById(\'media_icon\').innerHTML=\'&nbsp;<img style="vertical-align: middle;" src="../img/looknfeelna.png" alt="" />&nbsp;'.get_lang('EnrichQuestion').'\';
  983. } else {
  984. document.getElementById(\'HiddenFCKquestionDescription\').style.display = \'none\';
  985. document.getElementById(\'media_icon\').innerHTML=\'&nbsp;<img style="vertical-align: middle;" src="../img/looknfeel.png" alt="" />&nbsp;'.get_lang('EnrichQuestion').'\';
  986. }
  987. }
  988. // hub 13-12-2010
  989. function visiblerDevisibler(in_id) {
  990. if (document.getElementById(in_id)) {
  991. if (document.getElementById(in_id).style.display == "none") {
  992. document.getElementById(in_id).style.display = "block";
  993. if (document.getElementById(in_id+"Img")) {
  994. document.getElementById(in_id+"Img").src = "../img/div_hide.gif";
  995. }
  996. }
  997. else {
  998. document.getElementById(in_id).style.display = "none";
  999. if (document.getElementById(in_id+"Img")) {
  1000. document.getElementById(in_id+"Img").src = "../img/div_show.gif";
  1001. }
  1002. }
  1003. }
  1004. }
  1005. </script>';
  1006. $renderer = $form->defaultRenderer();
  1007. $form->addElement('html','<div class="form">');
  1008. // question name
  1009. $form->addElement('text','questionName','<span class="form_required">*</span> '.get_lang('Question'),'size="80"');
  1010. //$form->applyFilter('questionName','html_filter');
  1011. //$radios_results_enabled[] = $form->createElement('static', null, null, null);
  1012. //$test=FormValidator :: createElement ('text', 'questionName');
  1013. //$radios_results_enabled[]=$test;
  1014. $renderer->setElementTemplate('<div class="row"><div class="label">{label}</div><div class="formw" >{element}</div></div>','questionName');
  1015. $renderer->setElementTemplate('<div class="row"><div class="label">{label}</div><div class="formw">{element}</div></div>','questionLevel');
  1016. //
  1017. // Enrich question
  1018. //
  1019. $form->addRule('questionName', get_lang('GiveQuestion'), 'required');
  1020. // default content
  1021. $isContent = isset($_REQUEST['isContent']) ? intval($_REQUEST['isContent']) : null;
  1022. // question type
  1023. $answerType= intval($_REQUEST['answerType']);
  1024. $form->addElement('hidden','answerType',$_REQUEST['answerType']);
  1025. // html editor
  1026. $editor_config = array('ToolbarSet' => 'TestQuestionDescription', 'Width' => '100%', 'Height' => '150');
  1027. if(is_array($fck_config)){
  1028. $editor_config = array_merge($editor_config, $fck_config);
  1029. }
  1030. if(!api_is_allowed_to_edit(null,true)) $editor_config['UserStatus'] = 'student';
  1031. $form -> addElement('html','<div class="row">
  1032. <div class="label"></div>
  1033. <div class="formw" style="height:50px">
  1034. <a href="javascript://" onclick=" return show_media()"> <span id="media_icon"> <img style="vertical-align: middle;" src="../img/looknfeel.png" alt="" />&nbsp;'.get_lang('EnrichQuestion').'</span></a>
  1035. </div>
  1036. </div>');
  1037. $form -> addElement ('html','<div class="HideFCKEditor" id="HiddenFCKquestionDescription" >');
  1038. $form->add_html_editor('questionDescription', get_lang('langQuestionDescription'), false, false, $editor_config);
  1039. $form -> addElement ('html','</div>');
  1040. $renderer->setElementTemplate('<div class="row"><div class="label">{label}</div><div class="formw">{element}</div></div>','questionDescription');
  1041. //
  1042. // Advanced parameters
  1043. //
  1044. // question level
  1045. //@todo move levles into a table
  1046. $select_level = array (1,2,3,4,5);
  1047. //$radios_results_enabled[] =
  1048. foreach($select_level as $val) {
  1049. $radios_results_enabled[] = FormValidator :: createElement ('radio', null, null,$val,$val);
  1050. }
  1051. $form->addGroup($radios_results_enabled,'questionLevel',get_lang('Difficulty'));
  1052. // HUB 12-10-2011
  1053. // categories
  1054. $tabCat = array();
  1055. $tabCat = Testcategory::getCategoriesIdAndName();
  1056. $form->addElement('select', 'questionCategory', get_lang('Category'), $tabCat);
  1057. // note always positive for multiple answer questions
  1058. // Form's style
  1059. $renderer->setElementTemplate('<div class="row"><div class="label">{label}</div><div class="formw">{element}</div></div>&nbsp;','questionName');
  1060. $renderer->setElementTemplate('<div style="margin-left:0%;float:right;text-align:left;width:89%" class=""><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("AdvancedParameters").'</a><div id="id_advancedOption" style="background-color:#EFEFEF;display:none;"><div class="row"><div class="label">{label}</div><div class="formw">{element}</div></div>','questionLevel');
  1061. $renderer->setElementTemplate('<div class="row"><div class="label">{label}</div><div class="formw">{element}</div></div><br/></div></div><div style="clear:both">&nbsp;</div>','questionCategory');
  1062. // fhub
  1063. // hidden values
  1064. $form->addElement('hidden','myid',$_REQUEST['myid']);
  1065. if (!isset($_GET['fromExercise'])) {
  1066. switch($answerType) {
  1067. case 1: $this->question = get_lang('langDefaultUniqueQuestion'); break;
  1068. case 2: $this->question = get_lang('langDefaultMultipleQuestion'); break;
  1069. case 3: $this->question = get_lang('langDefaultFillBlankQuestion'); break;
  1070. case 4: $this->question = get_lang('langDefaultMathingQuestion'); break;
  1071. case 5: $this->question = get_lang('langDefaultOpenQuestion'); break;
  1072. case 9: $this->question = get_lang('langDefaultMultipleQuestion'); break;
  1073. }
  1074. }
  1075. $form->addElement('html','</div>');
  1076. // default values
  1077. $defaults = array();
  1078. $defaults['questionName'] = $this -> question;
  1079. $defaults['questionDescription'] = $this -> description;
  1080. $defaults['questionLevel'] = $this -> level;
  1081. $defaults['questionCategory'] = $this->category; // hub 12-10-2011
  1082. //Came from he question pool
  1083. if (isset($_GET['fromExercise'])) {
  1084. $form->setDefaults($defaults);
  1085. }
  1086. if (!empty($_REQUEST['myid'])) {
  1087. $form->setDefaults($defaults);
  1088. } else {
  1089. if ($isContent == 1) {
  1090. $form->setDefaults($defaults);
  1091. }
  1092. }
  1093. }
  1094. /**
  1095. * function which process the creation of questions
  1096. * @param FormValidator $form the formvalidator instance
  1097. * @param Exercise $objExercise the Exercise instance
  1098. */
  1099. function processCreation ($form, $objExercise) {
  1100. $this -> updateTitle($form->getSubmitValue('questionName'));
  1101. $this -> updateDescription($form->getSubmitValue('questionDescription'));
  1102. $this -> updateLevel($form->getSubmitValue('questionLevel'));
  1103. $this->updateCategory($form->getSubmitValue('questionCategory')); // hub 12-10-2011
  1104. $this -> save($objExercise -> id);
  1105. // modify the exercise
  1106. $objExercise->addToList($this -> id);
  1107. $objExercise->update_question_positions();
  1108. }
  1109. /**
  1110. * abstract function which creates the form to create / edit the answers of the question
  1111. * @param the formvalidator instance
  1112. */
  1113. abstract function createAnswersForm ($form);
  1114. /**
  1115. * abstract function which process the creation of answers
  1116. * @param the formvalidator instance
  1117. */
  1118. abstract function processAnswersCreation ($form);
  1119. /**
  1120. * Displays the menu of question types
  1121. */
  1122. static function display_type_menu ($feedbacktype = 0) {
  1123. global $exerciseId;
  1124. // 1. by default we show all the question types
  1125. $question_type_custom_list = self::$questionTypes;
  1126. if (!isset($feedbacktype)) $feedbacktype=0;
  1127. if ($feedbacktype==1) {
  1128. //2. but if it is a feedback DIRECT we only show the UNIQUE_ANSWER type that is currently available
  1129. //$question_type_custom_list = array ( UNIQUE_ANSWER => self::$questionTypes[UNIQUE_ANSWER]);
  1130. $question_type_custom_list = array ( UNIQUE_ANSWER => self::$questionTypes[UNIQUE_ANSWER],HOT_SPOT_DELINEATION => self::$questionTypes[HOT_SPOT_DELINEATION]);
  1131. } else {
  1132. unset($question_type_custom_list[HOT_SPOT_DELINEATION]);
  1133. }
  1134. //blocking edition
  1135. $show_quiz_edition = true;
  1136. if (isset($exerciseId) && !empty($exerciseId)) {
  1137. $TBL_LP_ITEM = Database::get_course_table(TABLE_LP_ITEM);
  1138. $sql="SELECT max_score FROM $TBL_LP_ITEM
  1139. WHERE item_type = '".TOOL_QUIZ."' AND path ='".Database::escape_string($exerciseId)."'";
  1140. $result = Database::query($sql);
  1141. if (Database::num_rows($result) > 0) {
  1142. $show_quiz_edition = false;
  1143. }
  1144. }
  1145. echo '<ul class="question_menu">';
  1146. foreach ($question_type_custom_list as $i=>$a_type) {
  1147. // include the class of the type
  1148. require_once($a_type[0]);
  1149. // get the picture of the type and the langvar which describes it
  1150. $img = $explanation = '';
  1151. eval('$img = '.$a_type[1].'::$typePicture;');
  1152. eval('$explanation = get_lang('.$a_type[1].'::$explanationLangVar);');
  1153. echo '<li>';
  1154. echo '<div class="icon_image_content">';
  1155. if ($show_quiz_edition) {
  1156. echo '<a href="admin.php?'.api_get_cidreq().'&newQuestion=yes&answerType='.$i.'">'.Display::return_icon($img, $explanation).'</a>';
  1157. //echo '<br>';
  1158. //echo '<a href="admin.php?'.api_get_cidreq().'&newQuestion=yes&answerType='.$i.'">'.$explanation.'</a>';
  1159. } else {
  1160. $img = pathinfo($img);
  1161. $img = $img['filename'];
  1162. echo ''.Display::return_icon($img.'_na.gif',$explanation).'';
  1163. //echo '<br>';
  1164. //echo ''.$explanation.'';
  1165. }
  1166. echo '</div>';
  1167. echo '</li>';
  1168. }
  1169. echo '<li>';
  1170. echo '<div class="icon_image_content">';
  1171. if ($show_quiz_edition) {
  1172. if ($feedbacktype==1) {
  1173. echo $url = '<a href="question_pool.php?'.api_get_cidreq().'&type=1&fromExercise='.$exerciseId.'">';
  1174. } else {
  1175. echo $url = '<a href="question_pool.php?'.api_get_cidreq().'&fromExercise='.$exerciseId.'">';
  1176. }
  1177. echo Display::return_icon('database.png', get_lang('GetExistingQuestion'), '');
  1178. } else {
  1179. echo Display::return_icon('database_na.png', get_lang('GetExistingQuestion'), '');
  1180. }
  1181. echo '</a>';
  1182. echo '</div></li>';
  1183. echo '</ul>';
  1184. }
  1185. static function get_types_information(){
  1186. return self::$questionTypes;
  1187. }
  1188. static function updateId() {
  1189. return self::$questionTypes;
  1190. }
  1191. static function saveQuestionOption($question_id, $name, $course_id, $position = 0) {
  1192. $TBL_EXERCICE_QUESTION_OPTION = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
  1193. $params['question_id'] = intval($question_id);
  1194. $params['name'] = $name;
  1195. $params['position'] = $position;
  1196. $params['c_id'] = $course_id;
  1197. $result = self::readQuestionOption($question_id, $course_id);
  1198. $last_id = Database::insert($TBL_EXERCICE_QUESTION_OPTION, $params);
  1199. return $last_id;
  1200. }
  1201. static function deleteAllQuestionOptions($question_id, $course_id) {
  1202. $TBL_EXERCICE_QUESTION_OPTION = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
  1203. Database::delete($TBL_EXERCICE_QUESTION_OPTION, array('c_id = ? AND question_id = ?'=> array($course_id, $question_id)));
  1204. }
  1205. static function updateQuestionOption($id, $params, $course_id) {
  1206. $TBL_EXERCICE_QUESTION_OPTION = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
  1207. $result = Database::update($TBL_EXERCICE_QUESTION_OPTION, $params, array('c_id = ? AND id = ?'=>array($course_id, $id)));
  1208. return $result;
  1209. }
  1210. static function readQuestionOption($question_id, $course_id) {
  1211. $TBL_EXERCICE_QUESTION_OPTION = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
  1212. $result = Database::select('*', $TBL_EXERCICE_QUESTION_OPTION, array('where'=>array('c_id = ? AND question_id = ?' =>array($course_id, $question_id)), 'order'=>'id ASC'));
  1213. return $result;
  1214. }
  1215. function return_header($feedback_type = null, $counter = null) {
  1216. $counter_label = '';
  1217. if (!empty($counter)) {
  1218. $counter_label = intval($counter);
  1219. }
  1220. echo Display::div(get_lang("Question").' '.($counter_label).' : '.$this->question, array('id'=>'question_title', 'class'=>'sectiontitle'));
  1221. echo Display::div($this->description, array('id'=>'question_description'));
  1222. }
  1223. /**
  1224. * Create a question from a set of parameters
  1225. * @param int Quiz ID
  1226. * @param string Question name
  1227. * @param int Maximum result for the question
  1228. * @param int Type of question (see constants at beginning of question.class.php)
  1229. * @param int Question level/category
  1230. */
  1231. function create_question ($quiz_id, $question_name, $max_score = 0, $type = 1, $level = 1) {
  1232. $course_id = api_get_course_int_id();
  1233. $tbl_quiz_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
  1234. $tbl_quiz_rel_question = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
  1235. $quiz_id = filter_var($quiz_id,FILTER_SANITIZE_NUMBER_INT);
  1236. $max_score = filter_var($max_score,FILTER_SANITIZE_NUMBER_FLOAT);
  1237. $type = filter_var($type,FILTER_SANITIZE_NUMBER_INT);
  1238. $level = filter_var($level,FILTER_SANITIZE_NUMBER_INT);
  1239. // Get the max position
  1240. $sql = "SELECT max(position) as max_position"
  1241. ." FROM $tbl_quiz_question q INNER JOIN $tbl_quiz_rel_question r"
  1242. ." ON q.id = r.question_id"
  1243. ." AND exercice_id = $quiz_id AND q.c_id = $course_id AND r.c_id = $course_id";
  1244. $rs_max = Database::query($sql, __FILE__, __LINE__);
  1245. $row_max = Database::fetch_object($rs_max);
  1246. $max_position = $row_max->max_position +1;
  1247. // Insert the new question
  1248. $sql = "INSERT INTO $tbl_quiz_question"
  1249. ." (c_id, question,ponderation,position,type,level) "
  1250. ." VALUES($course_id, '".Database::escape_string($question_name)."',"
  1251. ." $max_score , $max_position, $type, $level)";
  1252. $rs = Database::query($sql);
  1253. // Get the question ID
  1254. $question_id = Database::get_last_insert_id();
  1255. // Get the max question_order
  1256. $sql = "SELECT max(question_order) as max_order "
  1257. ."FROM $tbl_quiz_rel_question WHERE c_id = $course_id AND exercice_id = $quiz_id ";
  1258. $rs_max_order = Database::query($sql);
  1259. $row_max_order = Database::fetch_object($rs_max_order);
  1260. $max_order = $row_max_order->max_order + 1;
  1261. // Attach questions to quiz
  1262. $sql = "INSERT INTO $tbl_quiz_rel_question "
  1263. ."(c_id, question_id,exercice_id,question_order)"
  1264. ." VALUES($course_id, $question_id, $quiz_id, $max_order)";
  1265. $rs = Database::query($sql);
  1266. return $question_id;
  1267. }
  1268. /**
  1269. * return the image filename of the question type
  1270. *
  1271. */
  1272. public function get_type_icon_html() {
  1273. $type = $this->selectType();
  1274. $tabQuestionList = Question::get_types_information(); // [0]=file to include [1]=type name
  1275. require_once($tabQuestionList[$type][0]);
  1276. eval('$img = '.$tabQuestionList[$type][1].'::$typePicture;');
  1277. eval('$explanation = get_lang('.$tabQuestionList[$type][1].'::$explanationLangVar);');
  1278. return array($img, $explanation);
  1279. }
  1280. }
  1281. endif;