question.class.php 100 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676
  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. /**
  14. * QUESTION CLASS
  15. *
  16. * This class allows to instantiate an object of type Question
  17. *
  18. * @author Olivier Brouckaert, original author
  19. * @author Patrick Cool, LaTeX support
  20. * @package chamilo.exercise
  21. */
  22. abstract class Question
  23. {
  24. public $id;
  25. public $question;
  26. public $description;
  27. public $weighting;
  28. public $position;
  29. public $type;
  30. public $level;
  31. public $picture;
  32. public $exerciseList; // array with the list of exercises which this question is in
  33. public $category_list;
  34. public $parent_id;
  35. public $isContent;
  36. public $course;
  37. public static $typePicture = 'new_question.png';
  38. public static $explanationLangVar = '';
  39. public $question_table_class = 'table table-striped';
  40. public $editionMode = 'normal';
  41. /** @var Exercise $exercise */
  42. public $exercise;
  43. public $setDefaultValues = false;
  44. public $submitClass;
  45. public $submitText;
  46. public $setDefaultQuestionValues = false;
  47. public $c_id = null;
  48. // if fastEdition is on
  49. public $textareaSettings = ' cols="50" rows="5" ';
  50. public static $questionTypes = array(
  51. UNIQUE_ANSWER => array('unique_answer.class.php', 'UniqueAnswer'),
  52. MULTIPLE_ANSWER => array('multiple_answer.class.php', 'MultipleAnswer'),
  53. FILL_IN_BLANKS => array('fill_blanks.class.php', 'FillBlanks'),
  54. MATCHING => array('matching.class.php', 'Matching'),
  55. FREE_ANSWER => array('freeanswer.class.php', 'FreeAnswer'),
  56. ORAL_EXPRESSION => array('oral_expression.class.php', 'OralExpression'),
  57. HOT_SPOT => array('hotspot.class.php', 'HotSpot'),
  58. HOT_SPOT_DELINEATION => array('hotspot.class.php', 'HotspotDelineation'),
  59. MULTIPLE_ANSWER_COMBINATION => array('multiple_answer_combination.class.php', 'MultipleAnswerCombination' ),
  60. UNIQUE_ANSWER_NO_OPTION => array('unique_answer_no_option.class.php', 'UniqueAnswerNoOption'),
  61. MULTIPLE_ANSWER_TRUE_FALSE => array('multiple_answer_true_false.class.php', 'MultipleAnswerTrueFalse'),
  62. MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE => array('multiple_answer_combination_true_false.class.php', 'MultipleAnswerCombinationTrueFalse'),
  63. GLOBAL_MULTIPLE_ANSWER => array('global_multiple_answer.class.php', 'GlobalMultipleAnswer'),
  64. MEDIA_QUESTION => array('media_question.class.php', 'MediaQuestion'),
  65. UNIQUE_ANSWER_IMAGE => array('unique_answer_image.class.php', 'UniqueAnswerImage'),
  66. DRAGGABLE => array('draggable.class.php', 'Draggable')
  67. );
  68. /**
  69. * constructor of the class
  70. *
  71. * @author - Olivier Brouckaert
  72. */
  73. public function Question()
  74. {
  75. $this->id = 0;
  76. $this->question = '';
  77. $this->description = '';
  78. $this->weighting = 0;
  79. $this->position = 1;
  80. $this->picture = '';
  81. $this->level = 1;
  82. // This variable is used when loading an exercise like an scenario
  83. //with an special hotspot: final_overlap, final_missing, final_excess
  84. $this->extra = '';
  85. $this->exerciseList = array();
  86. $this->course = api_get_course_info();
  87. $this->category_list = array();
  88. $this->parent_id = 0;
  89. $this->editionMode = 'normal';
  90. }
  91. public function getIsContent()
  92. {
  93. $isContent = null;
  94. if (isset($_REQUEST['isContent'])) {
  95. $isContent = intval($_REQUEST['isContent']);
  96. }
  97. return $this->isContent = $isContent;
  98. }
  99. /**
  100. * @param string $title
  101. * @param int $course_id
  102. * @return mixed|bool
  103. */
  104. public function readByTitle($title, $course_id = null)
  105. {
  106. if (!empty($course_id)) {
  107. $course_info = api_get_course_info_by_id($course_id);
  108. } else {
  109. $course_info = api_get_course_info();
  110. }
  111. $course_id = $course_info['real_id'];
  112. $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
  113. $title = Database::escape_string($title);
  114. $sql = "SELECT iid FROM $TBL_QUESTIONS WHERE question = '$title' AND c_id = $course_id ";
  115. $result = Database::query($sql);
  116. if (Database::num_rows($result)) {
  117. $row = Database::fetch_array($result);
  118. return self::read($row['iid'], $course_id);
  119. }
  120. return false;
  121. }
  122. /**
  123. * Reads question information from the database
  124. *
  125. * @author Olivier Brouckaert
  126. * @param int $id - question ID
  127. * @param int $course_id
  128. * @param Exercise
  129. *
  130. * @return boolean - true if question exists, otherwise false
  131. */
  132. public static function read($id, $course_id = null, Exercise $exercise = null)
  133. {
  134. $id = intval($id);
  135. if (!empty($course_id)) {
  136. $course_info = api_get_course_info_by_id($course_id);
  137. } else {
  138. $course_info = api_get_course_info();
  139. }
  140. $course_id = $course_info['real_id'];
  141. if (empty($course_id) || $course_id == -1) {
  142. //return false;
  143. }
  144. $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
  145. $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
  146. $sql = "SELECT * FROM $TBL_QUESTIONS WHERE iid = $id";
  147. $result = Database::query($sql);
  148. // if the question has been found
  149. if ($object = Database::fetch_object($result)) {
  150. $objQuestion = Question::getInstance($object->type);
  151. if (!empty($objQuestion)) {
  152. $objQuestion->id = $id;
  153. $objQuestion->question = $object->question;
  154. $objQuestion->description = $object->description;
  155. $objQuestion->weighting = $object->ponderation;
  156. $objQuestion->position = $object->position;
  157. $objQuestion->type = $object->type;
  158. $objQuestion->picture = $object->picture;
  159. $objQuestion->level = (int)$object->level;
  160. $objQuestion->extra = $object->extra;
  161. $objQuestion->course = $course_info;
  162. $objQuestion->parent_id = $object->parent_id;
  163. $objQuestion->category_list = Testcategory::getCategoryForQuestion($id);
  164. $objQuestion->exercise = $exercise;
  165. $objQuestion->c_id = $object->c_id;
  166. $sql = "SELECT exercice_id FROM $TBL_EXERCICE_QUESTION WHERE question_id = $id";
  167. $result_exercise_list = Database::query($sql);
  168. // fills the array with the exercises which this question is in
  169. if ($result_exercise_list) {
  170. while ($obj = Database::fetch_object($result_exercise_list)) {
  171. $objQuestion->exerciseList[] = $obj->exercice_id;
  172. }
  173. }
  174. return $objQuestion;
  175. }
  176. }
  177. // question not found
  178. return false;
  179. }
  180. public function setEditionMode($mode)
  181. {
  182. $this->editionMode = $mode;
  183. }
  184. public function getEditionMode()
  185. {
  186. return $this->editionMode;
  187. }
  188. /**
  189. * Returns the question ID
  190. *
  191. * @author - Olivier Brouckaert
  192. * @return int - question ID
  193. */
  194. public function selectId()
  195. {
  196. return $this->id;
  197. }
  198. /**
  199. * Returns the question title
  200. *
  201. * @author - Olivier Brouckaert
  202. * @return string - question title
  203. */
  204. public function selectTitle()
  205. {
  206. return $this->question;
  207. }
  208. /**
  209. * returns the question description
  210. *
  211. * @author - Olivier Brouckaert
  212. * @return string - question description
  213. */
  214. public function selectDescription()
  215. {
  216. $this->description = $this->description;
  217. return $this->description;
  218. }
  219. /**
  220. * returns the question weighting
  221. *
  222. * @author - Olivier Brouckaert
  223. * @return int - question weighting
  224. */
  225. public function selectWeighting()
  226. {
  227. return $this->weighting;
  228. }
  229. /**
  230. * returns the question position
  231. *
  232. * @author - Olivier Brouckaert
  233. * @return int - question position
  234. */
  235. public function selectPosition()
  236. {
  237. return $this->position;
  238. }
  239. /**
  240. * returns the answer type
  241. *
  242. * @author - Olivier Brouckaert
  243. * @return int - answer type
  244. */
  245. public function selectType()
  246. {
  247. return $this->type;
  248. }
  249. /**
  250. * returns the level of the question
  251. *
  252. * @author - Nicolas Raynaud
  253. * @return int - level of the question, 0 by default.
  254. */
  255. public function selectLevel()
  256. {
  257. return $this->level;
  258. }
  259. /**
  260. * returns the picture name
  261. *
  262. * @author - Olivier Brouckaert
  263. * @return string - picture name
  264. */
  265. public function selectPicture()
  266. {
  267. return $this->picture;
  268. }
  269. public function selectPicturePath()
  270. {
  271. if (!empty($this->picture)) {
  272. return api_get_path(WEB_COURSE_PATH).$this->course['path'].'/document/images/'.$this->picture;
  273. }
  274. return false;
  275. }
  276. /**
  277. * returns the array with the exercise ID list
  278. *
  279. * @author - Olivier Brouckaert
  280. * @return array - list of exercise ID which the question is in
  281. */
  282. public function selectExerciseList()
  283. {
  284. return $this->exerciseList;
  285. }
  286. /**
  287. * returns the number of exercises which this question is in
  288. *
  289. * @author - Olivier Brouckaert
  290. * @return integer - number of exercises
  291. */
  292. public function selectNbrExercises()
  293. {
  294. return sizeof($this->exerciseList);
  295. }
  296. /**
  297. * @param $course_id
  298. */
  299. public function updateCourse($course_id)
  300. {
  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. public function updateTitle($title)
  310. {
  311. $this->question = $title;
  312. }
  313. public 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. public 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. public 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. public 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. public 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. public 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 array category list
  368. * @author Julio Montoya - Adding multiple cat support
  369. */
  370. public function saveCategories($category_list)
  371. {
  372. $course_id = $this->course['real_id'];
  373. if (!empty($category_list)) {
  374. $this->deleteCategory();
  375. $TBL_QUESTION_REL_CATEGORY = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
  376. $category_list = array_filter($category_list);
  377. // update or add category for a question
  378. foreach ($category_list as $category_id) {
  379. if (empty($category_id)) {
  380. continue;
  381. }
  382. $category_id = intval($category_id);
  383. $question_id = Database::escape_string($this->id);
  384. $sql = "SELECT count(*) AS nb FROM $TBL_QUESTION_REL_CATEGORY
  385. WHERE category_id = $category_id AND question_id = $question_id AND c_id=".$course_id;
  386. $res = Database::query($sql);
  387. $row = Database::fetch_array($res);
  388. if ($row['nb'] > 0) {
  389. //DO nothing
  390. //$sql = "UPDATE $TBL_QUESTION_REL_CATEGORY SET category_id = $category_id WHERE question_id=$question_id AND c_id=".api_get_course_int_id();
  391. //$res = Database::query($sql);
  392. } else {
  393. $sql = "INSERT INTO $TBL_QUESTION_REL_CATEGORY (c_id, question_id, category_id) VALUES (".$course_id.", $question_id, $category_id)";
  394. Database::query($sql);
  395. }
  396. }
  397. } else {
  398. $this->deleteCategory();
  399. }
  400. }
  401. /**
  402. * @author - Hubert Borderiou 12-10-2011
  403. * @param - interger $in_positive
  404. * in this version, a question can only have 1 category
  405. * if category is 0, then question has no category then delete the category entry
  406. */
  407. public function saveCategory($in_category)
  408. {
  409. if ($in_category <= 0) {
  410. $this->deleteCategory();
  411. } else {
  412. // update or add category for a question
  413. $TBL_QUESTION_REL_CATEGORY = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
  414. $category_id = Database::escape_string($in_category);
  415. $question_id = Database::escape_string($this->id);
  416. $sql = "SELECT count(*) AS nb FROM $TBL_QUESTION_REL_CATEGORY WHERE question_id= $question_id AND c_id=".api_get_course_int_id(
  417. );
  418. $res = Database::query($sql);
  419. $row = Database::fetch_array($res);
  420. if ($row['nb'] > 0) {
  421. $sql = "UPDATE $TBL_QUESTION_REL_CATEGORY SET category_id= $category_id
  422. WHERE question_id=$question_id AND c_id=".api_get_course_int_id();
  423. Database::query($sql);
  424. } else {
  425. $sql = "INSERT INTO $TBL_QUESTION_REL_CATEGORY (c_id, question_id, category_id) VALUES (".api_get_course_int_id().", $question_id, $category_id)";
  426. Database::query($sql);
  427. }
  428. }
  429. }
  430. /**
  431. * Deletes any category entry for question id
  432. * @author hubert borderiou 12-10-2011
  433. */
  434. public function deleteCategory()
  435. {
  436. $course_id = $this->course['real_id'];
  437. $TBL_QUESTION_REL_CATEGORY = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
  438. $question_id = Database::escape_string($this->id);
  439. $sql = "DELETE FROM $TBL_QUESTION_REL_CATEGORY WHERE question_id = $question_id AND c_id = ".$course_id;
  440. Database::query($sql);
  441. }
  442. /**
  443. * Changes the question position
  444. *
  445. * @author Olivier Brouckaert
  446. * @param integer $position - question position
  447. */
  448. public function updatePosition($position)
  449. {
  450. $this->position = $position;
  451. }
  452. /**
  453. * Changes the question level
  454. *
  455. * @author Nicolas Raynaud
  456. * @param integer $level - question level
  457. */
  458. public function updateLevel($level)
  459. {
  460. $this->level = $level;
  461. }
  462. /**
  463. * changes the answer type. If the user changes the type from "unique answer" to "multiple answers"
  464. * (or conversely) answers are not deleted, otherwise yes
  465. *
  466. * @author Olivier Brouckaert
  467. * @param integer $type - answer type
  468. */
  469. public function updateType($type)
  470. {
  471. $TBL_REPONSES = Database::get_course_table(TABLE_QUIZ_ANSWER);
  472. $course_id = $this->course['real_id'];
  473. if (empty($course_id)) {
  474. $course_id = api_get_course_int_id();
  475. }
  476. // if we really change the type
  477. if ($type != $this->type) {
  478. // if we don't change from "unique answer" to "multiple answers" (or conversely)
  479. if (!in_array($this->type, array(UNIQUE_ANSWER, MULTIPLE_ANSWER)) || !in_array(
  480. $type,
  481. array(UNIQUE_ANSWER, MULTIPLE_ANSWER)
  482. )
  483. ) {
  484. // removes old answers
  485. $sql = "DELETE FROM $TBL_REPONSES WHERE question_id='".Database::escape_string($this->id)."'";
  486. Database::query($sql);
  487. }
  488. $this->type = $type;
  489. }
  490. }
  491. /**
  492. * Adds a picture to the question
  493. *
  494. * @author Olivier Brouckaert
  495. * @param string $Picture - temporary path of the picture to upload
  496. * @param string $PictureName - Name of the picture
  497. * @param string
  498. * @return bool - true if uploaded, otherwise false
  499. */
  500. public function uploadPicture($Picture, $PictureName, $picturePath = null)
  501. {
  502. if (empty($picturePath)) {
  503. global $picturePath;
  504. }
  505. if (!file_exists($picturePath)) {
  506. if (mkdir($picturePath, api_get_permissions_for_new_directories())) {
  507. // document path
  508. $documentPath = api_get_path(SYS_COURSE_PATH).$this->course['path']."/document";
  509. $path = str_replace($documentPath, '', $picturePath);
  510. $title_path = basename($picturePath);
  511. $doc_id = FileManager::add_document($this->course, $path, 'folder', 0, $title_path);
  512. api_item_property_update($this->course, TOOL_DOCUMENT, $doc_id, 'FolderCreated', api_get_user_id());
  513. }
  514. }
  515. // if the question has got an ID
  516. if ($this->id) {
  517. $extension = pathinfo($PictureName, PATHINFO_EXTENSION);
  518. $this->picture = 'quiz-'.$this->id.'.jpg';
  519. $o_img = new Image($Picture);
  520. $o_img->send_image($picturePath.'/'.$this->picture, -1, 'jpg');
  521. $document_id = FileManager::add_document(
  522. $this->course,
  523. '/images/'.$this->picture,
  524. 'file',
  525. filesize($picturePath.'/'.$this->picture),
  526. $this->picture
  527. );
  528. if ($document_id) {
  529. return api_item_property_update(
  530. $this->course,
  531. TOOL_DOCUMENT,
  532. $document_id,
  533. 'DocumentAdded',
  534. api_get_user_id()
  535. );
  536. }
  537. }
  538. return false;
  539. }
  540. /**
  541. * Resizes a picture || Warning!: can only be called after uploadPicture, or if picture is already available in object.
  542. *
  543. * @author - Toon Keppens
  544. * @param string $Dimension - Resizing happens proportional according to given dimension: height|width|any
  545. * @param integer $Max - Maximum size
  546. * @return boolean - true if success, false if failed
  547. */
  548. public function resizePicture($Dimension, $Max)
  549. {
  550. global $picturePath;
  551. // if the question has an ID
  552. if ($this->id) {
  553. // Get dimensions from current image.
  554. $my_image = new Image($picturePath.'/'.$this->picture);
  555. $current_image_size = $my_image->get_image_size();
  556. $current_width = $current_image_size['width'];
  557. $current_height = $current_image_size['height'];
  558. if ($current_width < $Max && $current_height < $Max) {
  559. return true;
  560. } elseif ($current_height == "") {
  561. return false;
  562. }
  563. // Resize according to height.
  564. if ($Dimension == "height") {
  565. $resize_scale = $current_height / $Max;
  566. $new_height = $Max;
  567. $new_width = ceil($current_width / $resize_scale);
  568. }
  569. // Resize according to width
  570. if ($Dimension == "width") {
  571. $resize_scale = $current_width / $Max;
  572. $new_width = $Max;
  573. $new_height = ceil($current_height / $resize_scale);
  574. }
  575. // Resize according to height or width, both should not be larger than $Max after resizing.
  576. if ($Dimension == "any") {
  577. if ($current_height > $current_width || $current_height == $current_width) {
  578. $resize_scale = $current_height / $Max;
  579. $new_height = $Max;
  580. $new_width = ceil($current_width / $resize_scale);
  581. }
  582. if ($current_height < $current_width) {
  583. $resize_scale = $current_width / $Max;
  584. $new_width = $Max;
  585. $new_height = ceil($current_height / $resize_scale);
  586. }
  587. }
  588. $my_image->resize($new_width, $new_height);
  589. $result = $my_image->send_image($picturePath.'/'.$this->picture);
  590. if ($result) {
  591. return true;
  592. } else {
  593. return false;
  594. }
  595. }
  596. }
  597. /**
  598. * Deletes the picture
  599. *
  600. * @author Olivier Brouckaert
  601. * @return boolean - true if removed, otherwise false
  602. */
  603. public function removePicture()
  604. {
  605. global $picturePath;
  606. // if the question has got an ID and if the picture exists
  607. if ($this->id) {
  608. $picture = $this->picture;
  609. $this->picture = '';
  610. return @unlink($picturePath.'/'.$picture) ? true : false;
  611. }
  612. return false;
  613. }
  614. /**
  615. * Exports a picture to another question
  616. *
  617. * @author - Olivier Brouckaert
  618. * @param integer $questionId - ID of the target question
  619. * @return boolean - true if copied, otherwise false
  620. */
  621. public function exportPicture($questionId, $course_info)
  622. {
  623. $course_id = $course_info['real_id'];
  624. $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
  625. $destination_path = api_get_path(SYS_COURSE_PATH).$course_info['path'].'/document/images';
  626. $source_path = api_get_path(SYS_COURSE_PATH).$this->course['path'].'/document/images';
  627. // if the question has got an ID and if the picture exists
  628. if ($this->id && !empty($this->picture)) {
  629. $picture = explode('.', $this->picture);
  630. $extension = $picture[sizeof($picture) - 1];
  631. $picture = 'quiz-'.$questionId.'.'.$extension;
  632. $result = @copy($source_path.'/'.$this->picture, $destination_path.'/'.$picture) ? true : false;
  633. //If copy was correct then add to the database
  634. if ($result) {
  635. $sql = "UPDATE $TBL_QUESTIONS SET picture='".Database::escape_string($picture)."'
  636. WHERE c_id = $course_id AND iid='".intval($questionId)."'";
  637. Database::query($sql);
  638. $document_id = FileManager::add_document(
  639. $course_info,
  640. '/images/'.$picture,
  641. 'file',
  642. filesize($destination_path.'/'.$picture),
  643. $picture
  644. );
  645. if ($document_id) {
  646. return api_item_property_update(
  647. $course_info,
  648. TOOL_DOCUMENT,
  649. $document_id,
  650. 'DocumentAdded',
  651. api_get_user_id()
  652. );
  653. }
  654. }
  655. return $result;
  656. }
  657. return false;
  658. }
  659. /**
  660. * Saves the picture coming from POST into a temporary file
  661. * Temporary pictures are used when we don't want to save a picture right after a form submission.
  662. * For example, if we first show a confirmation box.
  663. *
  664. * @author - Olivier Brouckaert
  665. * @param - string $Picture - temporary path of the picture to move
  666. * @param - string $PictureName - Name of the picture
  667. */
  668. function setTmpPicture($Picture, $PictureName)
  669. {
  670. global $picturePath;
  671. $PictureName = explode('.', $PictureName);
  672. $Extension = $PictureName[sizeof($PictureName) - 1];
  673. // saves the picture into a temporary file
  674. @move_uploaded_file($Picture, $picturePath.'/tmp.'.$Extension);
  675. }
  676. /**
  677. Sets the title
  678. */
  679. public function setTitle($title)
  680. {
  681. $this->question = $title;
  682. }
  683. /**
  684. Sets the title
  685. */
  686. public function setExtra($extra)
  687. {
  688. $this->extra = $extra;
  689. }
  690. /**
  691. * Moves the temporary question "tmp" to "quiz-$questionId"
  692. * Temporary pictures are used when we don't want to save a picture right after a form submission.
  693. * For example, if we first show a confirmation box.
  694. *
  695. * @author Olivier Brouckaert
  696. * @return boolean - true if moved, otherwise false
  697. */
  698. public function getTmpPicture()
  699. {
  700. global $picturePath;
  701. // if the question has got an ID and if the picture exists
  702. if ($this->id) {
  703. if (file_exists($picturePath.'/tmp.jpg')) {
  704. $Extension = 'jpg';
  705. } elseif (file_exists($picturePath.'/tmp.gif')) {
  706. $Extension = 'gif';
  707. } elseif (file_exists($picturePath.'/tmp.png')) {
  708. $Extension = 'png';
  709. }
  710. $this->picture = 'quiz-'.$this->id.'.'.$Extension;
  711. return @rename($picturePath.'/tmp.'.$Extension, $picturePath.'/'.$this->picture) ? true : false;
  712. }
  713. return false;
  714. }
  715. /**
  716. * updates the question in the data base
  717. * if an exercise ID is provided, we add that exercise ID into the exercise list
  718. *
  719. * @author Olivier Brouckaert
  720. * @param integer $exerciseId - exercise ID if saving in an exercise
  721. */
  722. public function save($exerciseId = 0)
  723. {
  724. $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
  725. $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
  726. $id = $this->id;
  727. $question = $this->question;
  728. $description = $this->description;
  729. $weighting = $this->weighting;
  730. $position = $this->position;
  731. $type = $this->type;
  732. $picture = $this->picture;
  733. $level = $this->level;
  734. $extra = $this->extra;
  735. $c_id = $this->course['real_id'];
  736. $category_list = $this->category_list;
  737. // Question already exists
  738. if (!empty($id)) {
  739. $sql = "UPDATE $TBL_QUESTIONS SET
  740. question ='".Database::escape_string($question)."',
  741. description ='".Database::escape_string($description)."',
  742. ponderation ='".Database::escape_string($weighting)."',
  743. position ='".Database::escape_string($position)."',
  744. type ='".Database::escape_string($type)."',
  745. picture ='".Database::escape_string($picture)."',
  746. extra ='".Database::escape_string($extra)."',
  747. level ='".Database::escape_string($level)."',
  748. parent_id = ".$this->parent_id."
  749. WHERE iid = '".Database::escape_string($id)."'";
  750. //WHERE c_id = $c_id AND iid = '".Database::escape_string($id)."'";
  751. Database::query($sql);
  752. $this->saveCategories($category_list);
  753. if (!empty($exerciseId)) {
  754. api_item_property_update($this->course, TOOL_QUIZ, $id, 'QuizQuestionUpdated', api_get_user_id());
  755. if (api_get_setting('search_enabled') == 'true') {
  756. $this->search_engine_edit($exerciseId);
  757. }
  758. }
  759. } else {
  760. // Creates a new question
  761. $sql = "SELECT max(position) FROM $TBL_QUESTIONS as question, $TBL_EXERCICE_QUESTION as test_question
  762. WHERE question.iid = test_question.question_id AND
  763. test_question.exercice_id = '".Database::escape_string($exerciseId)."' AND
  764. question.c_id = $c_id AND
  765. test_question.c_id = $c_id ";
  766. $result = Database::query($sql);
  767. $current_position = Database::result($result, 0, 0);
  768. $this->updatePosition($current_position + 1);
  769. $position = $this->position;
  770. $sql = "INSERT INTO $TBL_QUESTIONS (c_id, question, description, ponderation, position, type, picture, extra, level, parent_id) VALUES ( ".
  771. " $c_id, ".
  772. " '".Database::escape_string($question)."', ".
  773. " '".Database::escape_string($description)."', ".
  774. " '".Database::escape_string($weighting)."', ".
  775. " '".Database::escape_string($position)."', ".
  776. " '".Database::escape_string($type)."', ".
  777. " '".Database::escape_string($picture)."', ".
  778. " '".Database::escape_string($extra)."', ".
  779. " '".Database::escape_string($level)."', ".
  780. " '".$this->parent_id."' ".
  781. " )";
  782. Database::query($sql);
  783. $this->id = Database::insert_id();
  784. api_item_property_update($this->course, TOOL_QUIZ, $this->id, 'QuizQuestionAdded', api_get_user_id());
  785. // If hotspot, create first answer
  786. if ($type == HOT_SPOT || $type == HOT_SPOT_ORDER) {
  787. $TBL_ANSWERS = Database::get_course_table(TABLE_QUIZ_ANSWER);
  788. $sql = "INSERT INTO $TBL_ANSWERS (question_id , answer , correct , comment , ponderation , position , hotspot_coordinates , hotspot_type )
  789. VALUES ('".Database::escape_string($this->id)."', '', NULL , '', '10' , '1', '0;0|0|0', 'square')";
  790. Database::query($sql);
  791. }
  792. if ($type == HOT_SPOT_DELINEATION) {
  793. $TBL_ANSWERS = Database::get_course_table(TABLE_QUIZ_ANSWER);
  794. $sql = "INSERT INTO $TBL_ANSWERS (question_id , answer , correct , comment , ponderation , position , hotspot_coordinates , hotspot_type )
  795. VALUES ('".Database::escape_string($this->id)."', '', NULL , '', '10' , '1', '0;0|0|0', 'delineation')";
  796. Database::query($sql);
  797. }
  798. if (api_get_setting('search_enabled') == 'true') {
  799. if ($exerciseId != 0) {
  800. $this->search_engine_edit($exerciseId, true);
  801. }
  802. }
  803. }
  804. // if the question is created in an exercise
  805. if ($exerciseId) {
  806. // adds the exercise into the exercise list of this question
  807. $this->addToList($exerciseId, true);
  808. }
  809. }
  810. /**
  811. * @param $exerciseId
  812. * @param bool $addQs
  813. * @param bool $rmQs
  814. */
  815. public function search_engine_edit($exerciseId, $addQs = false, $rmQs = false)
  816. {
  817. // update search engine and its values table if enabled
  818. if (api_get_setting('search_enabled') == 'true' && extension_loaded('xapian')) {
  819. $course_id = api_get_course_id();
  820. // get search_did
  821. $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
  822. if ($addQs || $rmQs) {
  823. //there's only one row per question on normal db and one document per question on search engine db
  824. $sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_second_level=%s LIMIT 1';
  825. $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
  826. } else {
  827. $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';
  828. $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $exerciseId, $this->id);
  829. }
  830. $res = Database::query($sql);
  831. if (Database::num_rows($res) > 0 || $addQs) {
  832. require_once(api_get_path(LIBRARY_PATH).'search/ChamiloIndexer.class.php');
  833. require_once(api_get_path(LIBRARY_PATH).'search/IndexableChunk.class.php');
  834. $di = new ChamiloIndexer();
  835. if ($addQs) {
  836. $question_exercises = array((int)$exerciseId);
  837. } else {
  838. $question_exercises = array();
  839. }
  840. isset($_POST['language']) ? $lang = Database::escape_string($_POST['language']) : $lang = 'english';
  841. $di->connectDb(null, null, $lang);
  842. // retrieve others exercise ids
  843. $se_ref = Database::fetch_array($res);
  844. $se_doc = $di->get_document((int)$se_ref['search_did']);
  845. if ($se_doc !== false) {
  846. if (($se_doc_data = $di->get_document_data($se_doc)) !== false) {
  847. $se_doc_data = unserialize($se_doc_data);
  848. if (isset($se_doc_data[SE_DATA]['type']) && $se_doc_data[SE_DATA]['type'] == SE_DOCTYPE_EXERCISE_QUESTION) {
  849. if (isset($se_doc_data[SE_DATA]['exercise_ids']) && is_array(
  850. $se_doc_data[SE_DATA]['exercise_ids']
  851. )
  852. ) {
  853. foreach ($se_doc_data[SE_DATA]['exercise_ids'] as $old_value) {
  854. if (!in_array($old_value, $question_exercises)) {
  855. $question_exercises[] = $old_value;
  856. }
  857. }
  858. }
  859. }
  860. }
  861. }
  862. if ($rmQs) {
  863. while (($key = array_search($exerciseId, $question_exercises)) !== false) {
  864. unset($question_exercises[$key]);
  865. }
  866. }
  867. // build the chunk to index
  868. $ic_slide = new IndexableChunk();
  869. $ic_slide->addValue("title", $this->question);
  870. $ic_slide->addCourseId($course_id);
  871. $ic_slide->addToolId(TOOL_QUIZ);
  872. $xapian_data = array(
  873. SE_COURSE_ID => $course_id,
  874. SE_TOOL_ID => TOOL_QUIZ,
  875. SE_DATA => array(
  876. 'type' => SE_DOCTYPE_EXERCISE_QUESTION,
  877. 'exercise_ids' => $question_exercises,
  878. 'question_id' => (int)$this->id
  879. ),
  880. SE_USER => (int)api_get_user_id(),
  881. );
  882. $ic_slide->xapian_data = serialize($xapian_data);
  883. $ic_slide->addValue("content", $this->description);
  884. //TODO: index answers, see also form validation on question_admin.inc.php
  885. $di->remove_document((int)$se_ref['search_did']);
  886. $di->addChunk($ic_slide);
  887. //index and return search engine document id
  888. if (!empty($question_exercises)) { // if empty there is nothing to index
  889. $did = $di->index();
  890. unset($di);
  891. }
  892. if ($did || $rmQs) {
  893. // save it to db
  894. if ($addQs || $rmQs) {
  895. $sql = 'DELETE FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_second_level=\'%s\'';
  896. $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
  897. } else {
  898. $sql = 'DELETE FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=\'%s\' AND ref_id_second_level=\'%s\'';
  899. $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $exerciseId, $this->id);
  900. }
  901. Database::query($sql);
  902. if ($rmQs) {
  903. if (!empty($question_exercises)) {
  904. $sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, ref_id_second_level, search_did)
  905. VALUES (NULL , \'%s\', \'%s\', %s, %s, %s)';
  906. $sql = sprintf(
  907. $sql,
  908. $tbl_se_ref,
  909. $course_id,
  910. TOOL_QUIZ,
  911. array_shift($question_exercises),
  912. $this->id,
  913. $did
  914. );
  915. Database::query($sql);
  916. }
  917. } else {
  918. $sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, ref_id_second_level, search_did)
  919. VALUES (NULL , \'%s\', \'%s\', %s, %s, %s)';
  920. $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $exerciseId, $this->id, $did);
  921. Database::query($sql);
  922. }
  923. }
  924. }
  925. }
  926. }
  927. /**
  928. * adds an exercise into the exercise list
  929. *
  930. * @author - Olivier Brouckaert
  931. * @param integer $exerciseId - exercise ID
  932. * @param boolean $fromSave - coming from $this->save() or not
  933. */
  934. public function addToList($exerciseId, $fromSave = false)
  935. {
  936. $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
  937. $id = $this->id;
  938. // checks if the exercise ID is not in the list
  939. if (!in_array($exerciseId, $this->exerciseList)) {
  940. $this->exerciseList[] = $exerciseId;
  941. $new_exercise = new Exercise();
  942. $new_exercise->read($exerciseId);
  943. $count = $new_exercise->selectNbrQuestions();
  944. $count++;
  945. $sql = "INSERT INTO $TBL_EXERCICE_QUESTION (c_id, question_id, exercice_id, question_order) VALUES
  946. ({$this->course['real_id']}, '".Database::escape_string($id)."','".Database::escape_string($exerciseId)."', '$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. public 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
  976. WHERE c_id = $course_id AND question_id='".Database::escape_string($id)."' AND exercice_id='".Database::escape_string($exerciseId)."'";
  977. $res = Database::query($sql);
  978. if (Database::num_rows($res) > 0) {
  979. $row = Database::fetch_array($res);
  980. if (!empty($row['question_order'])) {
  981. $sql = "UPDATE $TBL_EXERCICE_QUESTION SET question_order = question_order-1
  982. WHERE c_id = $course_id AND exercice_id='".Database::escape_string($exerciseId)."' AND question_order > ".$row['question_order'];
  983. Database::query($sql);
  984. }
  985. }
  986. $sql = "DELETE FROM $TBL_EXERCICE_QUESTION
  987. WHERE c_id = $course_id AND question_id='".Database::escape_string($id)."' AND exercice_id='".Database::escape_string($exerciseId)."'";
  988. Database::query($sql);
  989. return true;
  990. }
  991. }
  992. /**
  993. * Deletes a question from the database
  994. * the parameter tells if the question is removed from all exercises (value = 0),
  995. * or just from one exercise (value = exercise ID)
  996. *
  997. * @author Olivier Brouckaert
  998. * @param integer $deleteFromEx - exercise ID if the question is only removed from one exercise
  999. */
  1000. public function delete($deleteFromEx = 0)
  1001. {
  1002. $course_id = api_get_course_int_id();
  1003. $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
  1004. $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
  1005. $TBL_REPONSES = Database::get_course_table(TABLE_QUIZ_ANSWER);
  1006. $TBL_QUIZ_QUESTION_REL_CATEGORY = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
  1007. $id = $this->id;
  1008. if ($this->type == MEDIA_QUESTION) {
  1009. // Removing media for attached questions
  1010. $sql = "UPDATE $TBL_QUESTIONS SET parent_id = '' WHERE parent_id = $id";
  1011. Database::query($sql);
  1012. $sql = "DELETE FROM $TBL_QUESTIONS WHERE c_id = $course_id AND iid='".Database::escape_string($id)."'";
  1013. Database::query($sql);
  1014. return true;
  1015. }
  1016. // if the question must be removed from all exercises
  1017. if (!$deleteFromEx) {
  1018. //update the question_order of each question to avoid inconsistencies
  1019. $sql = "SELECT exercice_id, question_order FROM $TBL_EXERCICE_QUESTION
  1020. WHERE c_id = $course_id AND question_id='".Database::escape_string($id)."'";
  1021. $res = Database::query($sql);
  1022. if (Database::num_rows($res) > 0) {
  1023. while ($row = Database::fetch_array($res)) {
  1024. if (!empty($row['question_order'])) {
  1025. $sql = "UPDATE $TBL_EXERCICE_QUESTION
  1026. SET question_order = question_order - 1
  1027. WHERE c_id = $course_id AND
  1028. exercice_id='".Database::escape_string($row['exercice_id'])."' AND
  1029. question_order > ".$row['question_order'];
  1030. Database::query($sql);
  1031. }
  1032. }
  1033. }
  1034. $sql = "DELETE FROM $TBL_EXERCICE_QUESTION WHERE c_id = $course_id AND question_id='".Database::escape_string($id)."'";
  1035. Database::query($sql);
  1036. $sql = "DELETE FROM $TBL_QUESTIONS WHERE c_id = $course_id AND iid='".Database::escape_string($id)."'";
  1037. Database::query($sql);
  1038. $sql = "DELETE FROM $TBL_REPONSES WHERE question_id='".Database::escape_string($id)."'";
  1039. Database::query($sql);
  1040. // remove the category of this question in the question_rel_category table
  1041. $sql = "DELETE FROM $TBL_QUIZ_QUESTION_REL_CATEGORY
  1042. WHERE c_id = $course_id AND question_id='".Database::escape_string($id)."' AND c_id=".api_get_course_int_id();
  1043. Database::query($sql);
  1044. api_item_property_update($this->course, TOOL_QUIZ, $id, 'QuizQuestionDeleted', api_get_user_id());
  1045. $this->removePicture();
  1046. // resets the object
  1047. $this->Question();
  1048. } else {
  1049. // just removes the exercise from the list
  1050. $this->removeFromList($deleteFromEx);
  1051. if (api_get_setting('search_enabled') == 'true' && extension_loaded('xapian')) {
  1052. // disassociate question with this exercise
  1053. $this->search_engine_edit($deleteFromEx, false, true);
  1054. }
  1055. api_item_property_update($this->course, TOOL_QUIZ, $id, 'QuizQuestionDeleted', api_get_user_id());
  1056. }
  1057. }
  1058. /**
  1059. * Duplicates the question
  1060. *
  1061. * @author Olivier Brouckaert
  1062. * @param array Course info of the destination course
  1063. * @return int ID of the new question
  1064. */
  1065. public function duplicate($course_info = null)
  1066. {
  1067. if (empty($course_info)) {
  1068. $course_info = $this->course;
  1069. }
  1070. $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
  1071. $TBL_QUESTION_OPTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
  1072. $question = $this->question;
  1073. $description = $this->description;
  1074. //Using the same method used in the course copy to transform URLs
  1075. if ($this->course['id'] != $course_info['id']) {
  1076. $description = DocumentManager::replace_urls_inside_content_html_from_copy_course(
  1077. $description,
  1078. $this->course['id'],
  1079. $course_info['id']
  1080. );
  1081. $question = DocumentManager::replace_urls_inside_content_html_from_copy_course(
  1082. $question,
  1083. $this->course['id'],
  1084. $course_info['id']
  1085. );
  1086. }
  1087. $course_id = $course_info['real_id'];
  1088. //Read the source options
  1089. $options = self::readQuestionOption($this->id, $this->course['real_id']);
  1090. //Inserting in the new course db / or the same course db
  1091. $params = array(
  1092. 'c_id' => $course_id,
  1093. 'question' => $question,
  1094. 'description' => $description,
  1095. 'ponderation' => $this->weighting,
  1096. 'position' => $this->position,
  1097. 'type' => $this->type,
  1098. 'level' => $this->level,
  1099. 'extra' => $this->extra,
  1100. 'parent_id' => $this->parent_id,
  1101. );
  1102. $new_question_id = Database::insert($TBL_QUESTIONS, $params);
  1103. if (!empty($options)) {
  1104. //Saving the quiz_options
  1105. foreach ($options as $item) {
  1106. $item['question_id'] = $new_question_id;
  1107. $item['c_id'] = $course_id;
  1108. unset($item['iid']);
  1109. Database::insert($TBL_QUESTION_OPTIONS, $item);
  1110. }
  1111. }
  1112. $this->duplicate_category_question($new_question_id, $course_id);
  1113. // Duplicates the picture of the hotspot
  1114. $this->exportPicture($new_question_id, $course_info);
  1115. return $new_question_id;
  1116. }
  1117. public function get_categories_from_question()
  1118. {
  1119. return Testcategory::getCategoryForQuestion($this->id);
  1120. }
  1121. public function duplicate_category_question($question_id, $course_id)
  1122. {
  1123. $question = Question::read($question_id, $course_id);
  1124. $categories = $this->get_categories_from_question();
  1125. if (!empty($categories)) {
  1126. $question->saveCategories($categories);
  1127. }
  1128. }
  1129. public function get_question_type_name()
  1130. {
  1131. $key = self::$questionTypes[$this->type];
  1132. return get_lang($key[1]);
  1133. }
  1134. public static function get_question_type($type)
  1135. {
  1136. if ($type == ORAL_EXPRESSION && api_get_setting('enable_nanogong') != 'true') {
  1137. return null;
  1138. }
  1139. return self::$questionTypes[$type];
  1140. }
  1141. public static function get_question_type_list()
  1142. {
  1143. if (api_get_setting('enable_nanogong') != 'true') {
  1144. self::$questionTypes[ORAL_EXPRESSION] = null;
  1145. unset(self::$questionTypes[ORAL_EXPRESSION]);
  1146. }
  1147. return self::$questionTypes;
  1148. }
  1149. /**
  1150. * Returns an instance of the class corresponding to the type
  1151. * @param integer $type the type of the question
  1152. * @param \Exercise
  1153. * @return \Question an instance of a Question subclass (or of Questionc class by default)
  1154. */
  1155. public static function getInstance($type, Exercise $exercise = null)
  1156. {
  1157. if (!is_null($type)) {
  1158. list($file_name, $class_name) = self::get_question_type($type);
  1159. if (!empty($file_name)) {
  1160. include_once $file_name;
  1161. if (class_exists($class_name)) {
  1162. $obj = new $class_name();
  1163. $obj->exercise = $exercise;
  1164. return $obj;
  1165. } else {
  1166. echo 'Can\'t instanciate class '.$class_name.' of type '.$type;
  1167. }
  1168. }
  1169. }
  1170. return null;
  1171. }
  1172. /**
  1173. * Creates the form to create / edit a question
  1174. * A subclass can redifine this function to add fields...
  1175. *
  1176. * @param \FormValidator $form the formvalidator instance (by reference)
  1177. * @param array $fck_config
  1178. */
  1179. public function createForm(&$form, $fck_config = array())
  1180. {
  1181. $maxCategories = 1;
  1182. $url = api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?1=1';
  1183. $js = null;
  1184. if ($this->type != MEDIA_QUESTION) {
  1185. $js = '<script>
  1186. function check() {
  1187. var counter = 0;
  1188. $("#category_id option:selected").each(function() {
  1189. var id = $(this).val();
  1190. var name = $(this).text();
  1191. if (id != "" ) {
  1192. // if a media question was selected
  1193. $("#parent_id option:selected").each(function() {
  1194. var questionId = $(this).val();
  1195. if (questionId != 0) {
  1196. if (counter >= 1) {
  1197. alert("'.addslashes(get_lang('YouCantAddAnotherCategory')).'");
  1198. $("#category_id").trigger("removeItem",[{ "value" : id}]);
  1199. return;
  1200. }
  1201. }
  1202. });
  1203. $.ajax({
  1204. async: false,
  1205. url: "'.$url.'&a=exercise_category_exists",
  1206. data: "id="+id,
  1207. success: function(return_value) {
  1208. if (return_value == 0 ) {
  1209. alert("'.addslashes(get_lang('CategoryDoesNotExists')).'");
  1210. // Deleting select option tag
  1211. $("#category_id").find("option").remove();
  1212. $(".holder li").each(function () {
  1213. if ($(this).attr("rel") == id) {
  1214. $(this).remove();
  1215. }
  1216. });
  1217. }
  1218. },
  1219. });
  1220. }
  1221. counter++;
  1222. });
  1223. }
  1224. $(function() {
  1225. $("#category_id").fcbkcomplete({
  1226. json_url: "'.$url.'&a=search_category_parent&type=all&filter_by_global='.$this->exercise->getGlobalCategoryId().'",
  1227. maxitems: "'.$maxCategories.'",
  1228. addontab: false,
  1229. input_min_size: 1,
  1230. cache: false,
  1231. complete_text:"'.get_lang('StartToType').'",
  1232. firstselected: false,
  1233. onselect: check,
  1234. filter_selected: true,
  1235. newel: true
  1236. });
  1237. // Change select media
  1238. $("#parent_id").change(function(){
  1239. $("#parent_id option:selected").each(function() {
  1240. var questionId = $(this).val();
  1241. if (questionId != 0) {
  1242. $.ajax({
  1243. async: false,
  1244. dataType: "json",
  1245. url: "'.$url.'&a=get_categories_by_media&questionId='.$this->id.'&exerciseId='.$this->exercise->id.'",
  1246. data: "mediaId="+questionId,
  1247. success: function(data) {
  1248. if (data != -1) {
  1249. var all = $("#category_id").trigger("selectAll");
  1250. all.each(function(index, value) {
  1251. var selected = $(value).find("option:selected");
  1252. selected.each(function( indexSelect, valueSelect) {
  1253. valueToRemove = $(valueSelect).val();
  1254. $("#category_id").trigger("removeItem",[{ "value" : valueToRemove}]);
  1255. });
  1256. });
  1257. if (data != 0) {
  1258. $("#category_id").trigger("addItem",[{"title": data.title, "value": data.value}]);
  1259. }
  1260. }
  1261. },
  1262. });
  1263. } else {
  1264. // Removes all items
  1265. var all = $("#category_id").trigger("selectAll");
  1266. all.each(function(index, value) {
  1267. var selected = $(value).find("option:selected");
  1268. selected.each(function( indexSelect, valueSelect) {
  1269. valueToRemove = $(valueSelect).val();
  1270. $("#category_id").trigger("removeItem", [{ "value" : valueToRemove}]);
  1271. });
  1272. });
  1273. }
  1274. });
  1275. });
  1276. });
  1277. // hub 13-12-2010
  1278. function visiblerDevisibler(in_id) {
  1279. if (document.getElementById(in_id)) {
  1280. if (document.getElementById(in_id).style.display == "none") {
  1281. document.getElementById(in_id).style.display = "block";
  1282. if (document.getElementById(in_id+"Img")) {
  1283. document.getElementById(in_id+"Img").html = "'.addslashes(Display::return_icon('div_hide.gif')).'";
  1284. }
  1285. } else {
  1286. document.getElementById(in_id).style.display = "none";
  1287. if (document.getElementById(in_id+"Img")) {
  1288. document.getElementById(in_id+"Img").html = "dsdsds'.addslashes(Display::return_icon('div_show.gif')).'";
  1289. }
  1290. }
  1291. }
  1292. }
  1293. </script>';
  1294. $form->addElement('html', $js);
  1295. }
  1296. // question name
  1297. $form->addElement('text', 'questionName', get_lang('Question'), array('class' => 'span6'));
  1298. $form->addRule('questionName', get_lang('GiveQuestion'), 'required');
  1299. // Default content.
  1300. $isContent = isset($_REQUEST['isContent']) ? intval($_REQUEST['isContent']) : null;
  1301. // Question type
  1302. $answerType = isset($_REQUEST['answerType']) ? intval($_REQUEST['answerType']) : $this->selectType();
  1303. $form->addElement('hidden', 'answerType', $answerType);
  1304. // html editor
  1305. $editor_config = array('ToolbarSet' => 'TestQuestionDescription', 'Width' => '100%', 'Height' => '150');
  1306. if (is_array($fck_config)) {
  1307. $editor_config = array_merge($editor_config, $fck_config);
  1308. }
  1309. if (!api_is_allowed_to_edit(null, true)) {
  1310. $editor_config['UserStatus'] = 'student';
  1311. }
  1312. $form->add_html_editor('questionDescription', get_lang('QuestionDescription'), false, false, $editor_config);
  1313. // hidden values
  1314. $my_id = isset($_REQUEST['myid']) ? intval($_REQUEST['myid']) : null;
  1315. $form->addElement('hidden', 'myid', $my_id);
  1316. if ($this->type != MEDIA_QUESTION) {
  1317. if ($this->exercise->fastEdition == false) {
  1318. // Advanced parameters
  1319. $form->addElement('advanced_settings', 'advanced_params', get_lang('AdvancedParameters'));
  1320. $form->addElement('html', '<div id="advanced_params_options" style="display:none;">');
  1321. }
  1322. // Level (difficulty).
  1323. $select_level = Question::get_default_levels();
  1324. $form->addElement('select', 'questionLevel', get_lang('Difficulty'), $select_level);
  1325. // Media question.
  1326. $course_medias = Question::prepare_course_media_select(api_get_course_int_id());
  1327. $form->addElement('select', 'parent_id', get_lang('AttachToMedia'), $course_medias, array('id' => 'parent_id'));
  1328. // Categories.
  1329. $categoryJS = null;
  1330. if (!empty($this->category_list)) {
  1331. $trigger = '';
  1332. foreach ($this->category_list as $category_id) {
  1333. if (!empty($category_id)) {
  1334. $cat = new Testcategory($category_id);
  1335. if ($cat->id) {
  1336. $trigger .= '$("#category_id").trigger("addItem",[{"title": "'.$cat->parent_path.'", "value": "'.$cat->id.'"}]);';
  1337. }
  1338. }
  1339. }
  1340. $categoryJS .= '<script>$(function() { '.$trigger.' });</script>';
  1341. }
  1342. $form->addElement('html', $categoryJS);
  1343. $form->addElement(
  1344. 'select',
  1345. 'questionCategory',
  1346. get_lang('Category'),
  1347. array(),
  1348. array('id' => 'category_id')
  1349. );
  1350. // Extra fields. (Injecting question extra fields!)
  1351. $extraFields = new ExtraField('question');
  1352. $extraFields->addElements($form, $this->id);
  1353. if ($this->exercise->fastEdition == false) {
  1354. $form->addElement('html', '</div>');
  1355. }
  1356. }
  1357. // @todo why we need this condition??
  1358. if ($this->setDefaultQuestionValues) {
  1359. switch ($answerType) {
  1360. case 1:
  1361. $this->question = get_lang('DefaultUniqueQuestion');
  1362. break;
  1363. case 2:
  1364. $this->question = get_lang('DefaultMultipleQuestion');
  1365. break;
  1366. case 3:
  1367. $this->question = get_lang('DefaultFillBlankQuestion');
  1368. break;
  1369. case 4:
  1370. $this->question = get_lang('DefaultMathingQuestion');
  1371. break;
  1372. case 5:
  1373. $this->question = get_lang('DefaultOpenQuestion');
  1374. break;
  1375. case 9:
  1376. $this->question = get_lang('DefaultMultipleQuestion');
  1377. break;
  1378. }
  1379. }
  1380. // default values
  1381. $defaults = array();
  1382. $defaults['questionName'] = $this->question;
  1383. $defaults['questionDescription'] = $this->description;
  1384. $defaults['questionLevel'] = $this->level;
  1385. $defaults['questionCategory'] = $this->category_list;
  1386. $defaults['parent_id'] = $this->parent_id;
  1387. if (!empty($_REQUEST['myid'])) {
  1388. $form->setDefaults($defaults);
  1389. } else {
  1390. if ($isContent == 1) {
  1391. $form->setDefaults($defaults);
  1392. }
  1393. }
  1394. if ($this->setDefaultValues) {
  1395. $form->setDefaults($defaults);
  1396. }
  1397. }
  1398. /**
  1399. * function which process the creation of questions
  1400. * @param FormValidator $form the formvalidator instance
  1401. * @param Exercise $objExercise the Exercise instance
  1402. */
  1403. public function processCreation($form, $objExercise = null)
  1404. {
  1405. $this->updateParentId($form->getSubmitValue('parent_id'));
  1406. $this->updateTitle($form->getSubmitValue('questionName'));
  1407. $this->updateDescription($form->getSubmitValue('questionDescription'));
  1408. $this->updateLevel($form->getSubmitValue('questionLevel'));
  1409. $this->updateCategory($form->getSubmitValue('questionCategory'));
  1410. // Save normal question if NOT media
  1411. if ($this->type != MEDIA_QUESTION) {
  1412. $this->save($objExercise->id);
  1413. $field_value = new ExtraFieldValue('question');
  1414. $params = $form->getSubmitValues();
  1415. $params['question_id'] = $this->id;
  1416. $field_value->save_field_values($params);
  1417. if ($objExercise) {
  1418. // modify the exercise
  1419. $objExercise->addToList($this->id);
  1420. $objExercise->update_question_positions();
  1421. }
  1422. }
  1423. }
  1424. /**
  1425. * abstract function which creates the form to create / edit the answers of the question
  1426. * @param FormValidator instance
  1427. */
  1428. abstract public function createAnswersForm($form);
  1429. /**
  1430. * abstract function which process the creation of answers
  1431. * @param FormValidator instance
  1432. */
  1433. abstract public function processAnswersCreation($form);
  1434. /**
  1435. * Displays the menu of question types
  1436. * @param Exercise $objExercise
  1437. */
  1438. public static function display_type_menu(Exercise $objExercise)
  1439. {
  1440. $feedback_type = $objExercise->feedback_type;
  1441. $exerciseId = $objExercise->id;
  1442. // 1. by default we show all the question types
  1443. $question_type_custom_list = self::get_question_type_list();
  1444. if (!isset($feedback_type)) {
  1445. $feedback_type = 0;
  1446. }
  1447. if ($feedback_type == 1) {
  1448. //2. but if it is a feedback DIRECT we only show the UNIQUE_ANSWER type that is currently available
  1449. $question_type_custom_list = array(
  1450. UNIQUE_ANSWER => self::$questionTypes[UNIQUE_ANSWER],
  1451. HOT_SPOT_DELINEATION => self::$questionTypes[HOT_SPOT_DELINEATION]
  1452. );
  1453. } else {
  1454. unset($question_type_custom_list[HOT_SPOT_DELINEATION]);
  1455. }
  1456. echo '<div class="actionsbig">';
  1457. echo '<ul class="question_menu">';
  1458. $modelType = $objExercise->getModelType();
  1459. foreach ($question_type_custom_list as $i => $a_type) {
  1460. if ($modelType == EXERCISE_MODEL_TYPE_COMMITTEE) {
  1461. if ($a_type[1] != 'FreeAnswer') {
  1462. continue;
  1463. }
  1464. }
  1465. // include the class of the type
  1466. require_once $a_type[0];
  1467. // get the picture of the type and the langvar which describes it
  1468. $img = $explanation = '';
  1469. eval('$img = '.$a_type[1].'::$typePicture;');
  1470. eval('$explanation = get_lang('.$a_type[1].'::$explanationLangVar);');
  1471. echo '<li>';
  1472. echo '<div class="icon_image_content">';
  1473. if ($objExercise->exercise_was_added_in_lp == true) {
  1474. $img = pathinfo($img);
  1475. $img = $img['filename'].'_na.'.$img['extension'];
  1476. echo Display::return_icon($img, $explanation, array(), ICON_SIZE_BIG);
  1477. } else {
  1478. echo '<a href="admin.php?'.api_get_cidreq(
  1479. ).'&newQuestion=yes&answerType='.$i.'&exerciseId='.$exerciseId.'">'.Display::return_icon(
  1480. $img,
  1481. $explanation,
  1482. array(),
  1483. ICON_SIZE_BIG
  1484. ).'</a>';
  1485. }
  1486. echo '</div>';
  1487. echo '</li>';
  1488. }
  1489. echo '<li>';
  1490. echo '<div class="icon_image_content">';
  1491. if ($objExercise->exercise_was_added_in_lp == true) {
  1492. echo Display::return_icon('database_na.png', get_lang('GetExistingQuestion'));
  1493. } else {
  1494. if ($feedback_type == 1) {
  1495. //echo $url = '<a href="question_pool.php?'.api_get_cidreq().'&type=1&fromExercise='.$exerciseId.'">';
  1496. } else {
  1497. //echo $url = '<a href="question_pool.php?'.api_get_cidreq().'&fromExercise='.$exerciseId.'">';
  1498. }
  1499. echo $url = '<a href="'.api_get_path(WEB_PUBLIC_PATH).'courses/'.api_get_course_path().'/'.api_get_session_id().'/exercise/'.$exerciseId.'/question-pool">';
  1500. echo Display::return_icon('database.png', get_lang('GetExistingQuestion'));
  1501. }
  1502. echo '</a>';
  1503. echo '</div></li>';
  1504. echo '</ul>';
  1505. echo '</div>';
  1506. }
  1507. public static function saveQuestionOption($question_id, $name, $course_id, $position = 0)
  1508. {
  1509. $TBL_EXERCICE_QUESTION_OPTION = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
  1510. $params['question_id'] = intval($question_id);
  1511. $params['name'] = $name;
  1512. $params['position'] = $position;
  1513. $params['c_id'] = $course_id;
  1514. //$result = self::readQuestionOption($question_id, $course_id);
  1515. $last_id = Database::insert($TBL_EXERCICE_QUESTION_OPTION, $params);
  1516. return $last_id;
  1517. }
  1518. public static function deleteAllQuestionOptions($question_id, $course_id)
  1519. {
  1520. $TBL_EXERCICE_QUESTION_OPTION = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
  1521. Database::delete(
  1522. $TBL_EXERCICE_QUESTION_OPTION,
  1523. array('c_id = ? AND question_id = ?' => array($course_id, $question_id))
  1524. );
  1525. }
  1526. public static function updateQuestionOption($id, $params, $course_id)
  1527. {
  1528. $TBL_EXERCICE_QUESTION_OPTION = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
  1529. $result = Database::update($TBL_EXERCICE_QUESTION_OPTION, $params, array('c_id = ? AND id = ?' => array($course_id, $id)));
  1530. return $result;
  1531. }
  1532. /**
  1533. * @param int $question_id
  1534. * @param int $course_id
  1535. * @return array
  1536. */
  1537. public static function readQuestionOption($question_id, $course_id)
  1538. {
  1539. $TBL_EXERCICE_QUESTION_OPTION = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
  1540. $result = Database::select('*', $TBL_EXERCICE_QUESTION_OPTION, array(
  1541. 'where' => array(
  1542. 'c_id = ? AND question_id = ?' => array($course_id, $question_id)),
  1543. 'order' => 'iid ASC'
  1544. )
  1545. );
  1546. if (!empty($result)) {
  1547. $new_result = array();
  1548. foreach ($result as $item) {
  1549. $new_result[$item['iid']] = $item;
  1550. }
  1551. return $new_result;
  1552. }
  1553. return array();
  1554. }
  1555. /**
  1556. * Shows question title an description
  1557. *
  1558. * @param int $feedback_type
  1559. * @param int $counter
  1560. * @param array $score
  1561. * @param bool $show_media
  1562. * @param int $hideTitle
  1563. *
  1564. * @return string
  1565. */
  1566. public function return_header($feedbackType = null, $counter = null, $score = null, $show_media = false, $hideTitle = 0)
  1567. {
  1568. $counterLabel = null;
  1569. if (!empty($counter)) {
  1570. $counterLabel = $counter;
  1571. }
  1572. $score_label = get_lang('Wrong');
  1573. $class = 'error';
  1574. if ($score['pass'] == true) {
  1575. $score_label = get_lang('Correct');
  1576. $class = 'success';
  1577. }
  1578. if ($this->type == FREE_ANSWER || $this->type == ORAL_EXPRESSION) {
  1579. if ($score['revised'] == true) {
  1580. $score_label = get_lang('Revised');
  1581. $class = '';
  1582. } else {
  1583. $score_label = get_lang('NotRevised');
  1584. $class = 'error';
  1585. }
  1586. }
  1587. $header = null;
  1588. // Display question category, if any
  1589. if ($show_media) {
  1590. $header .= $this->show_media_content();
  1591. }
  1592. if ($hideTitle == 1) {
  1593. $header .= Display::page_subheader2($counterLabel);
  1594. } else {
  1595. $header .= Display::page_subheader2($counterLabel.". ".$this->question);
  1596. }
  1597. $header .= Display::div(
  1598. '<div class="rib rib-'.$class.'"><h3>'.$score_label.'</h3></div><h4>'.$score['result'].' </h4>',
  1599. array('class' => 'ribbon')
  1600. );
  1601. $header .= Display::div($this->description, array('id' => 'question_description'));
  1602. return $header;
  1603. }
  1604. /**
  1605. * Create a question from a set of parameters
  1606. * @param int Quiz ID
  1607. * @param string Question name
  1608. * @param int Maximum result for the question
  1609. * @param int Type of question (see constants at beginning of question.class.php)
  1610. * @param int Question level/category
  1611. */
  1612. public function create_question($quiz_id, $question_name, $max_score = 0, $type = 1, $level = 1)
  1613. {
  1614. $course_id = api_get_course_int_id();
  1615. $tbl_quiz_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
  1616. $tbl_quiz_rel_question = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
  1617. $quiz_id = intval($quiz_id);
  1618. $max_score = (float)$max_score;
  1619. $type = intval($type);
  1620. $level = intval($level);
  1621. // Get the max position
  1622. $sql = "SELECT max(position) as max_position"
  1623. ." FROM $tbl_quiz_question q INNER JOIN $tbl_quiz_rel_question r"
  1624. ." ON q.iid = r.question_id"
  1625. ." AND exercice_id = $quiz_id AND q.c_id = $course_id AND r.c_id = $course_id";
  1626. $rs_max = Database::query($sql);
  1627. $row_max = Database::fetch_object($rs_max);
  1628. $max_position = $row_max->max_position + 1;
  1629. // Insert the new question
  1630. $sql = "INSERT INTO $tbl_quiz_question (c_id, question, ponderation, position, type, level)
  1631. VALUES ($course_id, '".Database::escape_string($question_name)."', '$max_score', $max_position, $type, $level)";
  1632. Database::query($sql);
  1633. // Get the question ID
  1634. $question_id = Database::insert_id();
  1635. // Get the max question_order
  1636. $sql = "SELECT max(question_order) as max_order FROM $tbl_quiz_rel_question
  1637. WHERE c_id = $course_id AND exercice_id = $quiz_id ";
  1638. $rs_max_order = Database::query($sql);
  1639. $row_max_order = Database::fetch_object($rs_max_order);
  1640. $max_order = $row_max_order->max_order + 1;
  1641. // Attach questions to quiz
  1642. $sql = "INSERT INTO $tbl_quiz_rel_question (c_id, question_id, exercice_id, question_order)
  1643. VALUES ($course_id, $question_id, $quiz_id, $max_order)";
  1644. Database::query($sql);
  1645. return $question_id;
  1646. }
  1647. /**
  1648. * return the image filename of the question type
  1649. * @todo don't use eval
  1650. */
  1651. public function get_type_icon_html()
  1652. {
  1653. $type = $this->selectType();
  1654. // [0]=file to include [1]=type name
  1655. $tabQuestionList = Question::get_question_type_list();
  1656. require_once $tabQuestionList[$type][0];
  1657. eval('$img = '.$tabQuestionList[$type][1].'::$typePicture;');
  1658. eval('$explanation = get_lang('.$tabQuestionList[$type][1].'::$explanationLangVar);');
  1659. return array($img, $explanation);
  1660. }
  1661. /**
  1662. * Get course medias
  1663. * @param int course id
  1664. */
  1665. public static function get_course_medias(
  1666. $course_id,
  1667. $start = 0,
  1668. $limit = 100,
  1669. $sidx = "question",
  1670. $sord = "ASC",
  1671. $where_condition = array()
  1672. ) {
  1673. $table_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
  1674. $default_where = array('c_id = ? AND parent_id = 0 AND type = ?' => array($course_id, MEDIA_QUESTION));
  1675. if (!empty($where_condition)) {
  1676. //$where_condition
  1677. }
  1678. $result = Database::select(
  1679. '*',
  1680. $table_question,
  1681. array(
  1682. 'limit' => " $start, $limit",
  1683. 'where' => $default_where,
  1684. 'order' => "$sidx $sord"
  1685. )
  1686. );
  1687. return $result;
  1688. }
  1689. /**
  1690. * Get count course medias
  1691. * @param int course id
  1692. */
  1693. public static function get_count_course_medias($course_id)
  1694. {
  1695. $table_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
  1696. $result = Database::select(
  1697. 'count(*) as count',
  1698. $table_question,
  1699. array('where' => array('c_id = ? AND parent_id = 0 AND type = ?' => array($course_id, MEDIA_QUESTION))),
  1700. 'first'
  1701. );
  1702. if ($result && isset($result['count'])) {
  1703. return $result['count'];
  1704. }
  1705. return 0;
  1706. }
  1707. public static function prepare_course_media_select($course_id)
  1708. {
  1709. $medias = self::get_course_medias($course_id);
  1710. $media_list = array();
  1711. $media_list[0] = get_lang('NoMedia');
  1712. if (!empty($medias)) {
  1713. foreach ($medias as $media) {
  1714. $media_list[$media['iid']] = empty($media['question']) ? get_lang('Untitled') : $media['question'];
  1715. }
  1716. }
  1717. return $media_list;
  1718. }
  1719. public static function get_default_levels()
  1720. {
  1721. $select_level = array(
  1722. 1 => 1,
  1723. 2 => 2,
  1724. 3 => 3,
  1725. 4 => 4,
  1726. 5 => 5
  1727. );
  1728. return $select_level;
  1729. }
  1730. public function show_media_content()
  1731. {
  1732. $html = null;
  1733. if ($this->parent_id != 0) {
  1734. $parent_question = Question::read($this->parent_id);
  1735. $html = $parent_question->show_media_content();
  1736. } else {
  1737. $html .= Display::page_subheader($this->selectTitle());
  1738. $html .= $this->selectDescription();
  1739. }
  1740. return $html;
  1741. }
  1742. public static function question_type_no_review()
  1743. {
  1744. return array(
  1745. HOT_SPOT,
  1746. HOT_SPOT_ORDER,
  1747. HOT_SPOT_DELINEATION
  1748. );
  1749. }
  1750. public static function getMediaLabels()
  1751. {
  1752. // Shows media questions
  1753. $courseMedias = Question::prepare_course_media_select(api_get_course_int_id());
  1754. $labels = null;
  1755. if (!empty($courseMedias)) {
  1756. $labels .= get_lang('MediaQuestions').'<br />';
  1757. foreach ($courseMedias as $mediaId => $media) {
  1758. $editLink = '<a href="'.api_get_self().'?'.api_get_cidreq().'&type='.MEDIA_QUESTION.'&myid=1&editQuestion='.$mediaId.'">'.Display::return_icon('edit.png',get_lang('Modify'), array(), ICON_SIZE_SMALL).'</a>';
  1759. $deleteLink = '<a id="delete_'.$mediaId.'" class="opener" href="'.api_get_self().'?'.api_get_cidreq().'&deleteQuestion='.$mediaId.'" >'.Display::return_icon('delete.png',get_lang('Delete'), array(), ICON_SIZE_SMALL).'</a>';
  1760. if (!empty($mediaId)) {
  1761. $labels .= self::getMediaLabel($media).''.$editLink.$deleteLink.'<br />';
  1762. }
  1763. }
  1764. }
  1765. return $labels;
  1766. }
  1767. /**
  1768. * Get question columns needed for the new question pool page
  1769. * @param int course code
  1770. * @return array
  1771. */
  1772. public static function getQuestionColumns($courseCode = null, $extraFields = array(), $questionFields = array(), $checkFields = false)
  1773. {
  1774. // The order is important you need to check the the $column variable in the model.ajax.php file
  1775. $columns = array('id', get_lang('Name'));
  1776. // Column config.
  1777. $columnModel = array(
  1778. array(
  1779. 'name' => 'iid',
  1780. 'index' => 'iid',
  1781. 'width' => '20',
  1782. 'align' => 'left'
  1783. ),
  1784. array(
  1785. 'name' => 'question',
  1786. 'index' => 'question',
  1787. 'width' => '200',
  1788. 'align' => 'left'
  1789. )
  1790. );
  1791. // Extra field rules.
  1792. $extraField = new \ExtraField('question');
  1793. $rules = $extraField->getRules($columns, $columnModel, $extraFields, $checkFields);
  1794. // Exercise rules.
  1795. self::getRules($courseCode, $rules, $columns, $columnModel, $questionFields, $checkFields);
  1796. // Adding actions.
  1797. $columns[] = get_lang('Actions');
  1798. $columnModel[] = array(
  1799. 'name' => 'actions',
  1800. 'index' => 'actions',
  1801. 'width' => '30'
  1802. );
  1803. foreach ($columnModel as $col) {
  1804. $simple_column_name[] = $col['name'];
  1805. }
  1806. $return_array = array(
  1807. 'columns' => $columns,
  1808. 'column_model' => $columnModel,
  1809. 'rules' => $rules,
  1810. 'simple_column_name' => $simple_column_name
  1811. );
  1812. return $return_array;
  1813. }
  1814. /**
  1815. * Get all questions
  1816. * @param Application $app
  1817. * @param int $categoryId
  1818. * @param int $exerciseId
  1819. * @param int $courseId
  1820. * @param array $options
  1821. * @param bool $get_count
  1822. * @return array
  1823. */
  1824. public static function getQuestions($app, $categoryId, $exerciseId, $courseId, $options, $get_count = false)
  1825. {
  1826. $questionTable = Database::get_course_table(TABLE_QUIZ_QUESTION);
  1827. $questionPoolFields = array(
  1828. 'question_session_id' => array(
  1829. 'innerjoin' => " INNER JOIN ".Database::get_course_table(TABLE_QUIZ_TEST_QUESTION)." as quiz_rel_question_session ON (quiz_rel_question_session.question_id = s.iid)
  1830. INNER JOIN ".Database::get_course_table(TABLE_QUIZ_TEST)." as quizsession ON (quizsession.iid = quiz_rel_question_session.exercice_id)
  1831. INNER JOIN ".Database::get_main_table(TABLE_MAIN_SESSION)." session ON (session.id = quizsession.session_id)",
  1832. 'where' => 'session_id',
  1833. 'inject_fields' => 'session.name as question_session_id, ',
  1834. ),
  1835. 'question_category_id' => array(
  1836. 'innerjoin' => " INNER JOIN ".Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY)." as quiz_rel_cat ON (quiz_rel_cat.question_id = s.iid)
  1837. INNER JOIN ".Database::get_course_table(TABLE_QUIZ_CATEGORY)." as cat ON (cat.iid = quiz_rel_cat.category_id)",
  1838. 'where' => 'quiz_rel_cat.category_id',
  1839. 'inject_fields' => 'cat.title as question_category_id, ',
  1840. ),
  1841. 'question_exercise_id' => array(
  1842. 'innerjoin' => " INNER JOIN ".Database::get_course_table(TABLE_QUIZ_TEST_QUESTION)." as quiz_rel_question ON (quiz_rel_question.question_id = s.iid)
  1843. INNER JOIN ".Database::get_course_table(TABLE_QUIZ_TEST)." as quizexercise ON (quizexercise.iid = quiz_rel_question.exercice_id) ",
  1844. 'where' => 'quiz_rel_question.exercice_id',
  1845. 'inject_fields' => 'quizexercise.title as question_exercise_id, ',
  1846. ),
  1847. 'question_c_id' => array(
  1848. 'where' => 's.c_id',
  1849. 'innerjoin' => " INNER JOIN ".Database::get_main_table(TABLE_MAIN_COURSE)." as course ON (course.id = s.c_id) ",
  1850. 'inject_fields' => 'course.title as question_c_id, '
  1851. ),
  1852. 'question_question_type' => array(
  1853. 'where' => 's.type ',
  1854. 'inject_fields' => 's.type as question_question_type,'
  1855. ),
  1856. 'question_difficulty' => array(
  1857. 'where' => 's.level',
  1858. 'inject_fields' => 's.level as question_difficulty, '
  1859. )
  1860. );
  1861. // Checking if you're looking for orphan questions.
  1862. $isOrphanQuestion = false;
  1863. if (isset($options['question'])) {
  1864. foreach ($options['question'] as $option) {
  1865. if (isset($option['field']) && $option['field'] == 'question_exercise_id') {
  1866. if ($option['data'] == 0) {
  1867. $isOrphanQuestion = true;
  1868. break;
  1869. }
  1870. }
  1871. }
  1872. }
  1873. // Special case for orphan questions.
  1874. if ($isOrphanQuestion) {
  1875. $questionPoolFields['question_exercise_id'] = array(
  1876. 'innerjoin' => " LEFT JOIN ".Database::get_course_table(TABLE_QUIZ_TEST_QUESTION)." as quiz_rel_question ON (quiz_rel_question.question_id = s.iid)
  1877. LEFT JOIN ".Database::get_course_table(TABLE_QUIZ_TEST)." as quizexercise ON (quizexercise.iid = quiz_rel_question.exercice_id) ",
  1878. 'where' => 'quiz_rel_question.exercice_id',
  1879. 'inject_fields' => 'quizexercise.title as question_exercise_id, ',
  1880. );
  1881. }
  1882. $inject_extra_fields = null;
  1883. $inject_joins = null;
  1884. $where = $options['where'];
  1885. $newQuestionPoolField = array();
  1886. if (isset($options['question'])) {
  1887. foreach ($options['question'] as $question) {
  1888. if (isset($questionPoolFields[$question['field']])) {
  1889. $newQuestionPoolField[$question['field']] = $questionPoolFields[$question['field']];
  1890. }
  1891. }
  1892. }
  1893. $inject_question_fields = null;
  1894. $questionPoolFields = $newQuestionPoolField;
  1895. // Injecting inner joins.
  1896. foreach ($questionPoolFields as $field => $option) {
  1897. $where = str_replace($field, $option['where'], $where);
  1898. if (isset($option['innerjoin']) && !empty($option['innerjoin'])) {
  1899. $inject_joins .= $option['innerjoin'];
  1900. }
  1901. if (isset($option['inject_fields']) && !empty($option['inject_fields'])) {
  1902. $inject_question_fields .= $option['inject_fields'];
  1903. }
  1904. }
  1905. $options['where'] = $where;
  1906. $extra_field = new ExtraField('question');
  1907. $conditions = $extra_field->parseConditions($options);
  1908. $inject_joins .= $conditions['inject_joins'];
  1909. $where = $conditions['where'];
  1910. $inject_where = $conditions['inject_where'];
  1911. $inject_extra_fields = $conditions['inject_extra_fields'];
  1912. $order = $conditions['order'];
  1913. $limit = $conditions['limit'];
  1914. if ($get_count == true) {
  1915. $select = " SELECT count(*) as total_rows";
  1916. } else {
  1917. $select = " SELECT s.*, $inject_extra_fields $inject_question_fields 1 ";
  1918. }
  1919. $extraCondition = null;
  1920. // Used by the question manager
  1921. if (!empty($categoryId)) {
  1922. $categoryRelQuestionTable = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
  1923. $extraCondition = " INNER JOIN $categoryRelQuestionTable c ON (s.iid = c.question_id)";
  1924. $categoryId = intval($categoryId);
  1925. $where .= " AND category_id = $categoryId ";
  1926. }
  1927. /*if (!empty($exerciseId)) {
  1928. $exerciseRelQuestionTable = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
  1929. $extraCondition .= " INNER JOIN $exerciseRelQuestionTable e ON (s.iid = e.question_id)";
  1930. $exerciseId = intval($exerciseId);
  1931. $where .= " AND exercice_id = $exerciseId ";
  1932. }*/
  1933. // Orphan questions
  1934. if ($isOrphanQuestion) {
  1935. //$exerciseRelQuestionTable = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
  1936. //$extraCondition .= " INNER JOIN $exerciseRelQuestionTable e ON (s.iid = e.question_id)";
  1937. $where .= " OR quizexercise.active = -1 OR quiz_rel_question.exercice_id IS NULL";
  1938. }
  1939. if (!empty($courseId)) {
  1940. $courseId = intval($courseId);
  1941. $where .= " AND s.c_id = $courseId ";
  1942. }
  1943. if (isset($options['question'])) {
  1944. $courseList = CourseManager::get_course_list_of_user_as_course_admin(api_get_user_id());
  1945. foreach ($options['question'] as $questionOption) {
  1946. if ($questionOption['field'] == 'question_c_id') {
  1947. if (isset($questionOption['data'])) {
  1948. if (!isset($courseList[$questionOption['data']])) {
  1949. return array();
  1950. }
  1951. }
  1952. }
  1953. }
  1954. }
  1955. //var_dump(CourseManager::get_teacher_list_from_course_code())
  1956. //var_dump($inject_joins);
  1957. $query = " $select FROM $questionTable s $inject_joins $extraCondition WHERE 1=1 $where $inject_where $order $limit";
  1958. //echo $query.'<br />';
  1959. //var_dump($extraCondition);
  1960. //var_dump($where);
  1961. $result = Database::query($query);
  1962. $questions = array();
  1963. $exerciseList = null;
  1964. if (!empty($exerciseId)) {
  1965. $exercise = new Exercise();
  1966. $exercise->read($exerciseId);
  1967. $exerciseList = $exercise->questionList;
  1968. }
  1969. if (Database::num_rows($result)) {
  1970. $questions = Database::store_result($result, 'ASSOC');
  1971. if ($get_count) {
  1972. return $questions[0]['total_rows'];
  1973. }
  1974. $previewIcon = Display::return_icon('preview.gif', get_lang('View'), array(), ICON_SIZE_SMALL);
  1975. $copyIcon = Display::return_icon('copy.png', get_lang('Copy'), array(), ICON_SIZE_SMALL);
  1976. $reuseIcon = Display::return_icon('view_more_stats.gif', get_lang('InsertALinkToThisQuestionInTheExercise'), array(), ICON_SIZE_SMALL);
  1977. $editIcon = Display::return_icon('edit.png', get_lang('Edit'), array(), ICON_SIZE_SMALL);
  1978. //$deleteIcon = Display::return_icon('delete.png', get_lang('Delete'), array(), ICON_SIZE_SMALL);
  1979. //var_dump($exerciseId);
  1980. // Including actions
  1981. foreach ($questions as &$question) {
  1982. $type = self::get_question_type($question['type']);
  1983. $question['type'] = get_lang($type[1]);
  1984. $question['question_question_type'] = get_lang($type[1]);
  1985. if (empty($exerciseId)) {
  1986. // View.
  1987. $actions = Display::url(
  1988. $previewIcon,
  1989. $app['url_generator']->generate(
  1990. 'admin_questions_show',
  1991. array(
  1992. 'id' => $question['iid']
  1993. )
  1994. )
  1995. );
  1996. // Edit.
  1997. $actions .= Display::url(
  1998. $editIcon,
  1999. $app['url_generator']->generate(
  2000. 'admin_questions_edit',
  2001. array(
  2002. 'id' => $question['iid']
  2003. )
  2004. )
  2005. );
  2006. } else {
  2007. // View.
  2008. $actions = Display::url(
  2009. $previewIcon,
  2010. $app['url_generator']->generate(
  2011. 'question_show',
  2012. array(
  2013. 'cidReq' => api_get_course_id(),
  2014. 'id_session' => api_get_session_id(),
  2015. 'exerciseId' => $exerciseId,
  2016. 'id' => $question['iid']
  2017. )
  2018. )
  2019. );
  2020. if (isset($exerciseList) && !empty($exerciseList) && (in_array($question['iid'], $exerciseList))) {
  2021. // Copy.
  2022. //$actions .= $copyIconDisabled;
  2023. } else {
  2024. // Copy.
  2025. $actions .= Display::url(
  2026. $copyIcon,
  2027. 'javascript:void(0);',
  2028. array(
  2029. 'onclick' => 'ajaxAction(this);',
  2030. 'data-url' => $app['url_generator']->generate(
  2031. 'exercise_copy_question',
  2032. array(
  2033. 'cidReq' => api_get_course_id(),
  2034. 'id_session' => api_get_session_id(),
  2035. 'questionId' => $question['iid'],
  2036. 'exerciseId' => $exerciseId
  2037. )
  2038. )
  2039. )
  2040. );
  2041. // Reuse.
  2042. $actions .= Display::url(
  2043. $reuseIcon,
  2044. 'javascript:void(0);',
  2045. array(
  2046. 'onclick' => 'ajaxAction(this);',
  2047. 'data-url' => $app['url_generator']->generate(
  2048. 'exercise_reuse_question',
  2049. array(
  2050. 'cidReq' => api_get_course_id(),
  2051. 'id_session' => api_get_session_id(),
  2052. 'questionId' => $question['iid'],
  2053. 'exerciseId' => $exerciseId
  2054. )
  2055. ),
  2056. )
  2057. );
  2058. }
  2059. // Edit.
  2060. $actions .= Display::url(
  2061. $editIcon,
  2062. $app['url_generator']->generate(
  2063. 'exercise_question_edit',
  2064. array(
  2065. 'cidReq' => api_get_course_id(),
  2066. 'id_session' => api_get_session_id(),
  2067. 'id' => $question['iid']
  2068. )
  2069. )
  2070. );
  2071. }
  2072. $question['actions'] = $actions;
  2073. }
  2074. }
  2075. return $questions;
  2076. }
  2077. public static function getMediaLabel($title)
  2078. {
  2079. return Display::label($title, 'warning');
  2080. }
  2081. /**
  2082. * @param string $courseCode
  2083. * @param array $rules
  2084. * @param array $columns
  2085. * @param array $column_model
  2086. * @return array
  2087. */
  2088. public static function getRules($courseCode, &$rules, &$columns, &$column_model, $questionFields, $checkFields = false)
  2089. {
  2090. // sessions
  2091. // course
  2092. // categories
  2093. // exercises
  2094. // difficult
  2095. // type
  2096. if (empty($courseCode)) {
  2097. // Session.
  2098. $sessionList = SessionManager::get_sessions_by_general_coach(api_get_user_id());
  2099. $fields = array();
  2100. if (!empty($sessionList)) {
  2101. $new_options = array();
  2102. $new_options[] = "-1:".get_lang('All');
  2103. foreach ($sessionList as $session) {
  2104. $new_options[] = "{$session['id']}:{$session['name']}";
  2105. }
  2106. $string = implode(';', $new_options);
  2107. $fields[] = array(
  2108. 'field_display_text' => get_lang('Session'),
  2109. 'field_variable' => 'session_id',
  2110. 'field_type' => ExtraField::FIELD_TYPE_SELECT,
  2111. 'field_default_value' => null,
  2112. 'field_options' => $string
  2113. );
  2114. }
  2115. } else {
  2116. // $courseList = array(api_get_course_info());
  2117. //$courseList = CourseManager::get_course_list_of_user_as_course_admin(api_get_user_id());
  2118. }
  2119. // Courses.
  2120. $courseList = CourseManager::get_course_list_of_user_as_course_admin(api_get_user_id());
  2121. if (!empty($courseList)) {
  2122. $new_options = array();
  2123. $new_options[] = "-1:".get_lang('All');
  2124. foreach ($courseList as $course) {
  2125. $new_options[] = "{$course['id']}:{$course['title']}";
  2126. }
  2127. $string = implode(';', $new_options);
  2128. $fields[] = array(
  2129. 'field_display_text' => get_lang('Course'),
  2130. 'field_variable' => 'c_id',
  2131. 'field_type' => ExtraField::FIELD_TYPE_SELECT,
  2132. 'field_default_value' => null,
  2133. 'field_options' => $string
  2134. );
  2135. }
  2136. // Categories.
  2137. $string = null;
  2138. if (!empty($courseList)) {
  2139. $new_options = array();
  2140. $new_options[] = "-1:".get_lang('All');
  2141. // Global categories
  2142. // @todo use tree view
  2143. $categories = Testcategory::getCategoriesIdAndName(0);
  2144. if (!empty($categories)) {
  2145. foreach ($categories as $id => $category) {
  2146. if (!empty($id)) {
  2147. $new_options[] = "$id:[Global] - ".$category;
  2148. }
  2149. }
  2150. }
  2151. foreach ($courseList as $course) {
  2152. $categories = Testcategory::getCategoriesIdAndName($course['real_id']);
  2153. if (!empty($categories)) {
  2154. foreach ($categories as $id => $category) {
  2155. if (!empty($id)) {
  2156. $new_options[] = "$id:".$course['title']." - ".$category;
  2157. }
  2158. }
  2159. }
  2160. }
  2161. $string = implode(';', $new_options);
  2162. $fields[] = array(
  2163. 'field_display_text' => get_lang('Category'),
  2164. 'field_variable' => 'category_id',
  2165. 'field_type' => ExtraField::FIELD_TYPE_SELECT,
  2166. 'field_default_value' => null,
  2167. 'field_options' => $string
  2168. );
  2169. }
  2170. $course = api_get_course_int_id();
  2171. $sessionId = api_get_session_id();
  2172. // Exercises.
  2173. $exerciseList = ExerciseLib::get_all_exercises_for_course_id($sessionId, $course);
  2174. if (!empty($exerciseList)) {
  2175. $new_options = array();
  2176. $new_options[] = "-1:".get_lang('All');
  2177. $new_options[] = "0:".get_lang('Orphan');
  2178. foreach ($exerciseList as $exercise) {
  2179. $new_options[] = "{$exercise['iid']}:{$exercise['title']}";
  2180. }
  2181. $string = implode(';', $new_options);
  2182. $fields[] = array(
  2183. 'field_display_text' => get_lang('Exercise'),
  2184. 'field_variable' => 'exercise_id',
  2185. 'field_type' => ExtraField::FIELD_TYPE_SELECT,
  2186. 'field_default_value' => null,
  2187. 'field_options' => $string
  2188. );
  2189. }
  2190. // Question type.
  2191. $questionList = Question::get_question_type_list();
  2192. if (!empty($questionList)) {
  2193. $new_options = array();
  2194. $new_options[] = "-1:".get_lang('All');
  2195. foreach ($questionList as $key => $question) {
  2196. $new_options[] = "{$key}:".get_lang($question['1']);
  2197. }
  2198. $string = implode(';', $new_options);
  2199. $fields[] = array(
  2200. 'field_display_text' => get_lang('AnswerType'),
  2201. 'field_variable' => 'question_type',
  2202. 'field_type' => ExtraField::FIELD_TYPE_SELECT,
  2203. 'field_default_value' => null,
  2204. 'field_options' => $string
  2205. );
  2206. }
  2207. // Difficult.
  2208. $levels = Question::get_default_levels();
  2209. if (!empty($levels)) {
  2210. $new_options = array();
  2211. $new_options[] = "-1:".get_lang('All');
  2212. foreach ($levels as $key => $level) {
  2213. $new_options[] ="{$key}:{$level}";
  2214. }
  2215. $string = implode(';', $new_options);
  2216. $fields[] = array(
  2217. 'field_display_text' => get_lang('Difficulty'),
  2218. 'field_variable' => 'difficulty',
  2219. 'field_type' => ExtraField::FIELD_TYPE_SELECT,
  2220. 'field_default_value' => null,
  2221. 'field_options' => $string
  2222. );
  2223. }
  2224. $questionFieldsKeys = array();
  2225. if (!empty($questionFields)) {
  2226. foreach ($questionFields as $question) {
  2227. $questionFieldsKeys[] = $question['field'];
  2228. }
  2229. }
  2230. if (!empty($fields)) {
  2231. foreach ($fields as $field) {
  2232. $search_options = array();
  2233. $type = 'text';
  2234. if (in_array($field['field_type'], array(ExtraField::FIELD_TYPE_SELECT, ExtraField::FIELD_TYPE_DOUBLE_SELECT))) {
  2235. $type = 'select';
  2236. $search_options['sopt'] = array('eq', 'ne'); //equal not equal
  2237. //$search_options['sopt'] = array('cn', 'nc'); //contains not contains
  2238. } else {
  2239. $search_options['sopt'] = array('cn', 'nc'); //contains not contains
  2240. }
  2241. $search_options['searchhidden'] = 'true';
  2242. $search_options['defaultValue'] = isset($search_options['field_default_value']) ? $search_options['field_default_value'] : null;
  2243. $search_options['value'] = $field['field_options'];
  2244. $column_model[] = array(
  2245. 'name' => 'question_'.$field['field_variable'],
  2246. 'index' => 'question_'.$field['field_variable'],
  2247. 'width' => '100',
  2248. 'hidden' => 'true',
  2249. 'search' => 'true',
  2250. 'stype' => $type,
  2251. 'searchoptions' => $search_options
  2252. );
  2253. $columns[] = $field['field_display_text'];
  2254. $rules[] = array(
  2255. 'field' => 'question_'.$field['field_variable'],
  2256. 'op' => 'eq'
  2257. );
  2258. }
  2259. }
  2260. return $rules;
  2261. }
  2262. /**
  2263. *
  2264. * @param $exerciseId
  2265. * @param $mediaId
  2266. * @return array|bool
  2267. */
  2268. public function getQuestionsPerMediaWithCategories($exerciseId, $mediaId)
  2269. {
  2270. $exerciseId = intval($exerciseId);
  2271. $mediaId = intval($mediaId);
  2272. $questionTable = Database::get_course_table(TABLE_QUIZ_QUESTION);
  2273. $questionRelExerciseTable = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
  2274. $sql = "SELECT q.* FROM $questionTable q INNER JOIN $questionRelExerciseTable r ON (q.iid = r.question_id)
  2275. WHERE (r.exercice_id = $exerciseId AND q.parent_id = $mediaId) ";
  2276. $result = Database::query($sql);
  2277. if (Database::num_rows($result)) {
  2278. return Database::store_result($result, 'ASSOC');
  2279. }
  2280. return false;
  2281. }
  2282. /**
  2283. * @param int $exerciseId
  2284. * @param int $mediaId
  2285. * @return array
  2286. */
  2287. public function getQuestionCategoriesOfMediaQuestions($exerciseId, $mediaId)
  2288. {
  2289. $questions = $this->getQuestionsPerMediaWithCategories($exerciseId, $mediaId);
  2290. $questionCategoryList = array();
  2291. if (!empty($questions)) {
  2292. foreach ($questions as $question) {
  2293. $categories = TestCategory::getCategoryForQuestionWithCategoryData($question['iid']);
  2294. if (!empty($categories)) {
  2295. foreach ($categories as $category) {
  2296. $questionCategoryList[$question['iid']][] = $category['iid'];
  2297. }
  2298. }
  2299. }
  2300. }
  2301. return $questionCategoryList;
  2302. }
  2303. /**
  2304. * Check if the media sent matches other medias sent before
  2305. * @param int $exerciseId
  2306. * @param int $mediaId
  2307. * @return array
  2308. */
  2309. public function allQuestionWithMediaHaveTheSameCategory($exerciseId, $mediaId, $categoryListToCompare = array(), $ignoreQuestionId = null, $returnCategoryId = false)
  2310. {
  2311. $questions = $this->getQuestionCategoriesOfMediaQuestions($exerciseId, $mediaId);
  2312. $result = false;
  2313. $categoryId = null;
  2314. if (empty($questions)) {
  2315. $result = true;
  2316. } else {
  2317. $tempArray = array();
  2318. foreach ($questions as $categories) {
  2319. $diff = array_diff($tempArray, $categories);
  2320. $categoryId = $categories[0];
  2321. $tempArray = $categories;
  2322. if (empty($diff)) {
  2323. $result = true;
  2324. continue;
  2325. } else {
  2326. $result = false;
  2327. break;
  2328. }
  2329. }
  2330. }
  2331. if (isset($categoryListToCompare) && !empty($categoryListToCompare)) {
  2332. $result = false;
  2333. foreach ($questions as $questionId => $categories) {
  2334. if ($ignoreQuestionId == $questionId) {
  2335. continue;
  2336. }
  2337. $diff = array_diff($categoryListToCompare, $categories);
  2338. $categoryId = $categories[0];
  2339. if (empty($diff)) {
  2340. $result = true;
  2341. continue;
  2342. } else {
  2343. $result = false;
  2344. break;
  2345. }
  2346. }
  2347. }
  2348. if ($returnCategoryId) {
  2349. return $categoryId;
  2350. }
  2351. return $result;
  2352. }
  2353. }