question.class.php 102 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742
  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($course_code = null)
  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($course_code);
  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 Question
  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, null, $course_info['code']);
  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, $course_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. /**
  1118. * Get categories from question
  1119. * @return array
  1120. */
  1121. public function get_categories_from_question()
  1122. {
  1123. return Testcategory::getCategoryForQuestion($this->id, $this->c_id);
  1124. }
  1125. /**
  1126. * @param int $questionId
  1127. * @param int $courseId
  1128. */
  1129. public function duplicate_category_question($questionId, $courseId)
  1130. {
  1131. $question = Question::read($questionId, $courseId);
  1132. $categories = $this->get_categories_from_question();
  1133. if (!empty($categories)) {
  1134. $question->saveCategories($categories);
  1135. }
  1136. }
  1137. /**
  1138. * @return array|string
  1139. */
  1140. public function get_question_type_name()
  1141. {
  1142. $key = self::$questionTypes[$this->type];
  1143. return get_lang($key[1]);
  1144. }
  1145. /**
  1146. * @param string $type
  1147. * @return null
  1148. */
  1149. public static function get_question_type($type)
  1150. {
  1151. if ($type == ORAL_EXPRESSION && api_get_setting('enable_nanogong') != 'true') {
  1152. return null;
  1153. }
  1154. return self::$questionTypes[$type];
  1155. }
  1156. /**
  1157. * @return array
  1158. */
  1159. public static function get_question_type_list()
  1160. {
  1161. if (api_get_setting('enable_nanogong') != 'true') {
  1162. self::$questionTypes[ORAL_EXPRESSION] = null;
  1163. unset(self::$questionTypes[ORAL_EXPRESSION]);
  1164. }
  1165. return self::$questionTypes;
  1166. }
  1167. /**
  1168. * Returns an instance of the class corresponding to the type
  1169. * @param integer $type the type of the question
  1170. * @param \Exercise
  1171. * @return \Question an instance of a Question subclass (or of Questionc class by default)
  1172. */
  1173. public static function getInstance($type, Exercise $exercise = null, $course_code = null)
  1174. {
  1175. if (!is_null($type)) {
  1176. list($file_name, $class_name) = self::get_question_type($type);
  1177. if (!empty($file_name)) {
  1178. include_once $file_name;
  1179. if (class_exists($class_name)) {
  1180. $obj = new $class_name($course_code);
  1181. $obj->exercise = $exercise;
  1182. return $obj;
  1183. } else {
  1184. echo 'Can\'t instanciate class '.$class_name.' of type '.$type;
  1185. }
  1186. }
  1187. }
  1188. return null;
  1189. }
  1190. /**
  1191. * Creates the form to create / edit a question
  1192. * A subclass can redifine this function to add fields...
  1193. *
  1194. * @param \FormValidator $form the formvalidator instance (by reference)
  1195. * @param array $fck_config
  1196. */
  1197. public function createForm(&$form, $fck_config = array())
  1198. {
  1199. $maxCategories = 1;
  1200. $url = api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?1=1';
  1201. $js = null;
  1202. if ($this->type != MEDIA_QUESTION) {
  1203. $js = '<script>
  1204. function check() {
  1205. var counter = 0;
  1206. $("#category_id option:selected").each(function() {
  1207. var id = $(this).val();
  1208. var name = $(this).text();
  1209. if (id != "" ) {
  1210. // if a media question was selected
  1211. $("#parent_id option:selected").each(function() {
  1212. var questionId = $(this).val();
  1213. if (questionId != 0) {
  1214. if (counter >= 1) {
  1215. alert("'.addslashes(get_lang('YouCantAddAnotherCategory')).'");
  1216. $("#category_id").trigger("removeItem",[{ "value" : id}]);
  1217. return;
  1218. }
  1219. }
  1220. });
  1221. $.ajax({
  1222. async: false,
  1223. url: "'.$url.'&a=exercise_category_exists",
  1224. data: "id="+id,
  1225. success: function(return_value) {
  1226. if (return_value == 0 ) {
  1227. alert("'.addslashes(get_lang('CategoryDoesNotExists')).'");
  1228. // Deleting select option tag
  1229. $("#category_id").find("option").remove();
  1230. $(".holder li").each(function () {
  1231. if ($(this).attr("rel") == id) {
  1232. $(this).remove();
  1233. }
  1234. });
  1235. }
  1236. },
  1237. });
  1238. }
  1239. counter++;
  1240. });
  1241. }
  1242. $(function() {
  1243. $("#category_id").fcbkcomplete({
  1244. json_url: "'.$url.'&a=search_category_parent&type=all&filter_by_global='.$this->exercise->getGlobalCategoryId().'",
  1245. maxitems: "'.$maxCategories.'",
  1246. addontab: false,
  1247. delay: 200,
  1248. input_min_size: 3,
  1249. cache: false,
  1250. complete_text:"'.get_lang('StartToType').'",
  1251. firstselected: false,
  1252. onselect: check,
  1253. filter_selected: true,
  1254. newel: true
  1255. });
  1256. // Change select media
  1257. $("#parent_id").change(function(){
  1258. $("#parent_id option:selected").each(function() {
  1259. var questionId = $(this).val();
  1260. if (questionId != 0) {
  1261. $.ajax({
  1262. async: false,
  1263. dataType: "json",
  1264. url: "'.$url.'&a=get_categories_by_media&questionId='.$this->id.'&exerciseId='.$this->exercise->id.'",
  1265. data: "mediaId="+questionId,
  1266. success: function(data) {
  1267. if (data != -1) {
  1268. var all = $("#category_id").trigger("selectAll");
  1269. all.each(function(index, value) {
  1270. var selected = $(value).find("option:selected");
  1271. selected.each(function( indexSelect, valueSelect) {
  1272. valueToRemove = $(valueSelect).val();
  1273. $("#category_id").trigger("removeItem",[{ "value" : valueToRemove}]);
  1274. });
  1275. });
  1276. if (data != 0) {
  1277. $("#category_id").trigger("addItem",[{"title": data.title, "value": data.value}]);
  1278. }
  1279. }
  1280. },
  1281. });
  1282. } else {
  1283. // Removes all items
  1284. var all = $("#category_id").trigger("selectAll");
  1285. all.each(function(index, value) {
  1286. var selected = $(value).find("option:selected");
  1287. selected.each(function( indexSelect, valueSelect) {
  1288. valueToRemove = $(valueSelect).val();
  1289. $("#category_id").trigger("removeItem", [{ "value" : valueToRemove}]);
  1290. });
  1291. });
  1292. }
  1293. });
  1294. });
  1295. });
  1296. // hub 13-12-2010
  1297. function visiblerDevisibler(in_id) {
  1298. if (document.getElementById(in_id)) {
  1299. if (document.getElementById(in_id).style.display == "none") {
  1300. document.getElementById(in_id).style.display = "block";
  1301. if (document.getElementById(in_id+"Img")) {
  1302. document.getElementById(in_id+"Img").html = "'.addslashes(Display::return_icon('div_hide.gif')).'";
  1303. }
  1304. } else {
  1305. document.getElementById(in_id).style.display = "none";
  1306. if (document.getElementById(in_id+"Img")) {
  1307. document.getElementById(in_id+"Img").html = "dsdsds'.addslashes(Display::return_icon('div_show.gif')).'";
  1308. }
  1309. }
  1310. }
  1311. }
  1312. </script>';
  1313. $form->addElement('html', $js);
  1314. }
  1315. // question name
  1316. $form->addElement('text', 'questionName', get_lang('Question'), array('class' => 'span6'));
  1317. $form->addRule('questionName', get_lang('GiveQuestion'), 'required');
  1318. // Default content.
  1319. $isContent = isset($_REQUEST['isContent']) ? intval($_REQUEST['isContent']) : null;
  1320. // Question type
  1321. $answerType = isset($_REQUEST['answerType']) ? intval($_REQUEST['answerType']) : $this->selectType();
  1322. $form->addElement('hidden', 'answerType', $answerType);
  1323. // html editor
  1324. $editor_config = array('ToolbarSet' => 'TestQuestionDescription', 'Width' => '100%', 'Height' => '150');
  1325. if (is_array($fck_config)) {
  1326. $editor_config = array_merge($editor_config, $fck_config);
  1327. }
  1328. if (!api_is_allowed_to_edit(null, true)) {
  1329. $editor_config['UserStatus'] = 'student';
  1330. }
  1331. $form->add_html_editor('questionDescription', get_lang('QuestionDescription'), false, false, $editor_config);
  1332. // hidden values
  1333. $my_id = isset($_REQUEST['myid']) ? intval($_REQUEST['myid']) : null;
  1334. $form->addElement('hidden', 'myid', $my_id);
  1335. if ($this->type != MEDIA_QUESTION) {
  1336. if ($this->exercise->fastEdition == false) {
  1337. // Advanced parameters
  1338. $form->addElement('advanced_settings', '<a class="btn btn-show advanced_parameters" id="advanced_params" href="javascript://">'.get_lang('AdvancedParameters').'</a>');
  1339. $form->addElement('html', '<div id="advanced_params_options" style="display:none;">');
  1340. }
  1341. // Level (difficulty).
  1342. /*$select_level = Question::get_default_levels();
  1343. $form->addElement('select', 'questionLevel', get_lang('Difficulty'), $select_level);*/
  1344. // Media question.
  1345. $course_medias = Question::prepare_course_media_select(api_get_course_int_id());
  1346. $form->addElement('select', 'parent_id', get_lang('AttachToMedia'), $course_medias, array('id' => 'parent_id'));
  1347. // Categories.
  1348. $categoryJS = null;
  1349. if (!empty($this->category_list)) {
  1350. $trigger = '';
  1351. foreach ($this->category_list as $category_id) {
  1352. if (!empty($category_id)) {
  1353. $cat = new Testcategory($category_id);
  1354. if ($cat->id) {
  1355. $trigger .= '$("#category_id").trigger("addItem",[{"title": "'.$cat->parent_path.'", "value": "'.$cat->id.'"}]);';
  1356. }
  1357. }
  1358. }
  1359. $categoryJS .= '<script>$(function() { '.$trigger.' });</script>';
  1360. }
  1361. $form->addElement('html', $categoryJS);
  1362. $form->addElement(
  1363. 'select',
  1364. 'questionCategory',
  1365. get_lang('Category'),
  1366. array(),
  1367. array('id' => 'category_id')
  1368. );
  1369. // Extra fields. (Injecting question extra fields!)
  1370. $extraFields = new ExtraField('question');
  1371. $extraFields->addElements($form, $this->id);
  1372. if ($this->exercise->fastEdition == false) {
  1373. $form->addElement('html', '</div>');
  1374. }
  1375. }
  1376. // @todo why we need this condition??
  1377. if ($this->setDefaultQuestionValues) {
  1378. switch ($answerType) {
  1379. case 1:
  1380. $this->question = get_lang('DefaultUniqueQuestion');
  1381. break;
  1382. case 2:
  1383. $this->question = get_lang('DefaultMultipleQuestion');
  1384. break;
  1385. case 3:
  1386. $this->question = get_lang('DefaultFillBlankQuestion');
  1387. break;
  1388. case 4:
  1389. $this->question = get_lang('DefaultMathingQuestion');
  1390. break;
  1391. case 5:
  1392. $this->question = get_lang('DefaultOpenQuestion');
  1393. break;
  1394. case 9:
  1395. $this->question = get_lang('DefaultMultipleQuestion');
  1396. break;
  1397. }
  1398. }
  1399. // default values
  1400. $defaults = array();
  1401. $defaults['questionName'] = $this->question;
  1402. $defaults['questionDescription'] = $this->description;
  1403. $defaults['questionLevel'] = $this->level;
  1404. $defaults['questionCategory'] = $this->category_list;
  1405. $defaults['parent_id'] = $this->parent_id;
  1406. if (!empty($_REQUEST['myid'])) {
  1407. $form->setDefaults($defaults);
  1408. } else {
  1409. if ($isContent == 1) {
  1410. $form->setDefaults($defaults);
  1411. }
  1412. }
  1413. if ($this->setDefaultValues) {
  1414. $form->setDefaults($defaults);
  1415. }
  1416. }
  1417. /**
  1418. * function which process the creation of questions
  1419. * @param FormValidator $form the formvalidator instance
  1420. * @param Exercise $objExercise the Exercise instance
  1421. */
  1422. public function processCreation($form, $objExercise = null)
  1423. {
  1424. $this->updateParentId($form->getSubmitValue('parent_id'));
  1425. $this->updateTitle($form->getSubmitValue('questionName'));
  1426. $this->updateDescription($form->getSubmitValue('questionDescription'));
  1427. $this->updateLevel($form->getSubmitValue('questionLevel'));
  1428. $this->updateCategory($form->getSubmitValue('questionCategory'));
  1429. // Save normal question if NOT media
  1430. if ($this->type != MEDIA_QUESTION) {
  1431. $this->save($objExercise->id);
  1432. $field_value = new ExtraFieldValue('question');
  1433. $params = $form->getSubmitValues();
  1434. $params['question_id'] = $this->id;
  1435. $field_value->save_field_values($params);
  1436. if ($objExercise) {
  1437. // modify the exercise
  1438. $objExercise->addToList($this->id);
  1439. $objExercise->update_question_positions();
  1440. }
  1441. }
  1442. }
  1443. /**
  1444. * abstract function which creates the form to create / edit the answers of the question
  1445. * @param FormValidator instance
  1446. */
  1447. abstract public function createAnswersForm($form);
  1448. /**
  1449. * abstract function which process the creation of answers
  1450. * @param FormValidator instance
  1451. */
  1452. abstract public function processAnswersCreation($form);
  1453. /**
  1454. * Displays the menu of question types
  1455. * @param Exercise $objExercise
  1456. */
  1457. public static function display_type_menu(Exercise $objExercise)
  1458. {
  1459. $feedback_type = $objExercise->feedback_type;
  1460. $exerciseId = $objExercise->id;
  1461. // 1. by default we show all the question types
  1462. $question_type_custom_list = self::get_question_type_list();
  1463. if (!isset($feedback_type)) {
  1464. $feedback_type = 0;
  1465. }
  1466. if ($feedback_type == 1) {
  1467. //2. but if it is a feedback DIRECT we only show the UNIQUE_ANSWER type that is currently available
  1468. $question_type_custom_list = array(
  1469. UNIQUE_ANSWER => self::$questionTypes[UNIQUE_ANSWER],
  1470. HOT_SPOT_DELINEATION => self::$questionTypes[HOT_SPOT_DELINEATION]
  1471. );
  1472. } else {
  1473. unset($question_type_custom_list[HOT_SPOT_DELINEATION]);
  1474. }
  1475. echo '<div class="actionsbig">';
  1476. echo '<ul class="question_menu">';
  1477. $modelType = $objExercise->getModelType();
  1478. foreach ($question_type_custom_list as $i => $a_type) {
  1479. if ($modelType == EXERCISE_MODEL_TYPE_COMMITTEE) {
  1480. if ($a_type[1] != 'FreeAnswer') {
  1481. continue;
  1482. }
  1483. } else {
  1484. //Skip other question types, just for minedu
  1485. if (!in_array($a_type[1],array('MediaQuestion','UniqueAnswer'))) {
  1486. continue;
  1487. }
  1488. }
  1489. // include the class of the type
  1490. require_once $a_type[0];
  1491. // get the picture of the type and the langvar which describes it
  1492. $img = $explanation = '';
  1493. eval('$img = '.$a_type[1].'::$typePicture;');
  1494. eval('$explanation = get_lang('.$a_type[1].'::$explanationLangVar);');
  1495. echo '<li>';
  1496. echo '<div class="icon_image_content">';
  1497. if ($objExercise->exercise_was_added_in_lp == true) {
  1498. $img = pathinfo($img);
  1499. $img = $img['filename'].'_na.'.$img['extension'];
  1500. echo Display::return_icon($img, $explanation, array(), ICON_SIZE_BIG);
  1501. } else {
  1502. echo '<a href="admin.php?'.api_get_cidreq(
  1503. ).'&newQuestion=yes&answerType='.$i.'&exerciseId='.$exerciseId.'">'.Display::return_icon(
  1504. $img,
  1505. $explanation,
  1506. array(),
  1507. ICON_SIZE_BIG
  1508. ).'</a>';
  1509. }
  1510. echo '</div>';
  1511. echo '</li>';
  1512. }
  1513. echo '<li>';
  1514. echo '<div class="icon_image_content">';
  1515. if ($objExercise->exercise_was_added_in_lp == true) {
  1516. echo Display::return_icon('database_na.png', get_lang('GetExistingQuestion'));
  1517. } else {
  1518. if ($feedback_type == 1) {
  1519. //echo $url = '<a href="question_pool.php?'.api_get_cidreq().'&type=1&fromExercise='.$exerciseId.'">';
  1520. } else {
  1521. //echo $url = '<a href="question_pool.php?'.api_get_cidreq().'&fromExercise='.$exerciseId.'">';
  1522. }
  1523. echo $url = '<a href="'.api_get_path(WEB_PUBLIC_PATH).'courses/'.api_get_course_path().'/'.api_get_session_id().'/exercise/'.$exerciseId.'/question-pool">';
  1524. echo Display::return_icon('database.png', get_lang('GetExistingQuestion'));
  1525. }
  1526. echo '</a>';
  1527. echo '</div></li>';
  1528. echo '</ul>';
  1529. echo '</div>';
  1530. }
  1531. public static function saveQuestionOption($question_id, $name, $course_id, $position = 0)
  1532. {
  1533. $TBL_EXERCICE_QUESTION_OPTION = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
  1534. $params['question_id'] = intval($question_id);
  1535. $params['name'] = $name;
  1536. $params['position'] = $position;
  1537. $params['c_id'] = $course_id;
  1538. //$result = self::readQuestionOption($question_id, $course_id);
  1539. $last_id = Database::insert($TBL_EXERCICE_QUESTION_OPTION, $params);
  1540. return $last_id;
  1541. }
  1542. public static function deleteAllQuestionOptions($question_id, $course_id)
  1543. {
  1544. $TBL_EXERCICE_QUESTION_OPTION = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
  1545. Database::delete(
  1546. $TBL_EXERCICE_QUESTION_OPTION,
  1547. array('c_id = ? AND question_id = ?' => array($course_id, $question_id))
  1548. );
  1549. }
  1550. public static function updateQuestionOption($id, $params, $course_id)
  1551. {
  1552. $TBL_EXERCICE_QUESTION_OPTION = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
  1553. $result = Database::update($TBL_EXERCICE_QUESTION_OPTION, $params, array('c_id = ? AND id = ?' => array($course_id, $id)));
  1554. return $result;
  1555. }
  1556. /**
  1557. * @param int $question_id
  1558. * @param int $course_id
  1559. * @return array
  1560. */
  1561. public static function readQuestionOption($question_id, $course_id)
  1562. {
  1563. $TBL_EXERCICE_QUESTION_OPTION = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
  1564. $result = Database::select('*', $TBL_EXERCICE_QUESTION_OPTION, array(
  1565. 'where' => array(
  1566. 'c_id = ? AND question_id = ?' => array($course_id, $question_id)),
  1567. 'order' => 'iid ASC'
  1568. )
  1569. );
  1570. if (!empty($result)) {
  1571. $new_result = array();
  1572. foreach ($result as $item) {
  1573. $new_result[$item['iid']] = $item;
  1574. }
  1575. return $new_result;
  1576. }
  1577. return array();
  1578. }
  1579. /**
  1580. * Shows question title an description
  1581. *
  1582. * @param int $feedback_type
  1583. * @param int $counter
  1584. * @param array $score
  1585. * @param bool $show_media
  1586. * @param int $hideTitle
  1587. *
  1588. * @return string
  1589. */
  1590. public function return_header($feedbackType = null, $counter = null, $score = null, $show_media = false, $hideTitle = 0)
  1591. {
  1592. $counterLabel = null;
  1593. if (!empty($counter)) {
  1594. $counterLabel = $counter;
  1595. }
  1596. $score_label = get_lang('Wrong');
  1597. $class = 'error';
  1598. if ($score['pass'] == true) {
  1599. $score_label = get_lang('Correct');
  1600. $class = 'success';
  1601. }
  1602. if ($this->type == FREE_ANSWER || $this->type == ORAL_EXPRESSION) {
  1603. if ($score['revised'] == true) {
  1604. $score_label = get_lang('Revised');
  1605. $class = '';
  1606. } else {
  1607. $score_label = get_lang('NotRevised');
  1608. $class = 'error';
  1609. }
  1610. }
  1611. $header = null;
  1612. // Display question category, if any
  1613. if ($show_media) {
  1614. $header .= $this->show_media_content();
  1615. }
  1616. if ($hideTitle == 1) {
  1617. $header .= Display::page_subheader2($counterLabel);
  1618. } else {
  1619. $header .= Display::page_subheader2($counterLabel.". ".$this->question);
  1620. }
  1621. $header .= Display::div(
  1622. '<div class="rib rib-'.$class.'"><h3>'.$score_label.'</h3></div><h4>'.$score['result'].' </h4>',
  1623. array('class' => 'ribbon')
  1624. );
  1625. $header .= Display::div($this->description, array('id' => 'question_description'));
  1626. return $header;
  1627. }
  1628. /**
  1629. * Create a question from a set of parameters
  1630. * @param int Quiz ID
  1631. * @param string Question name
  1632. * @param int Maximum result for the question
  1633. * @param int Type of question (see constants at beginning of question.class.php)
  1634. * @param int Question level/category
  1635. */
  1636. public function create_question($quiz_id, $question_name, $max_score = 0, $type = 1, $level = 1)
  1637. {
  1638. $course_id = api_get_course_int_id();
  1639. $tbl_quiz_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
  1640. $tbl_quiz_rel_question = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
  1641. $quiz_id = intval($quiz_id);
  1642. $max_score = (float)$max_score;
  1643. $type = intval($type);
  1644. $level = intval($level);
  1645. // Get the max position
  1646. $sql = "SELECT max(position) as max_position"
  1647. ." FROM $tbl_quiz_question q INNER JOIN $tbl_quiz_rel_question r"
  1648. ." ON q.iid = r.question_id"
  1649. ." AND exercice_id = $quiz_id AND q.c_id = $course_id AND r.c_id = $course_id";
  1650. $rs_max = Database::query($sql);
  1651. $row_max = Database::fetch_object($rs_max);
  1652. $max_position = $row_max->max_position + 1;
  1653. // Insert the new question
  1654. $sql = "INSERT INTO $tbl_quiz_question (c_id, question, ponderation, position, type, level)
  1655. VALUES ($course_id, '".Database::escape_string($question_name)."', '$max_score', $max_position, $type, $level)";
  1656. Database::query($sql);
  1657. // Get the question ID
  1658. $question_id = Database::insert_id();
  1659. // Get the max question_order
  1660. $sql = "SELECT max(question_order) as max_order FROM $tbl_quiz_rel_question
  1661. WHERE c_id = $course_id AND exercice_id = $quiz_id ";
  1662. $rs_max_order = Database::query($sql);
  1663. $row_max_order = Database::fetch_object($rs_max_order);
  1664. $max_order = $row_max_order->max_order + 1;
  1665. // Attach questions to quiz
  1666. $sql = "INSERT INTO $tbl_quiz_rel_question (c_id, question_id, exercice_id, question_order)
  1667. VALUES ($course_id, $question_id, $quiz_id, $max_order)";
  1668. Database::query($sql);
  1669. return $question_id;
  1670. }
  1671. /**
  1672. * return the image filename of the question type
  1673. * @todo don't use eval
  1674. */
  1675. public function get_type_icon_html()
  1676. {
  1677. $type = $this->selectType();
  1678. // [0]=file to include [1]=type name
  1679. $tabQuestionList = Question::get_question_type_list();
  1680. require_once $tabQuestionList[$type][0];
  1681. eval('$img = '.$tabQuestionList[$type][1].'::$typePicture;');
  1682. eval('$explanation = get_lang('.$tabQuestionList[$type][1].'::$explanationLangVar);');
  1683. return array($img, $explanation);
  1684. }
  1685. /**
  1686. * Get course medias
  1687. * @param int course id
  1688. */
  1689. public static function get_course_medias(
  1690. $course_id,
  1691. $start = 0,
  1692. $limit = 100,
  1693. $sidx = "question",
  1694. $sord = "ASC",
  1695. $where_condition = array()
  1696. ) {
  1697. $table_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
  1698. $default_where = array('c_id = ? AND parent_id = 0 AND type = ?' => array($course_id, MEDIA_QUESTION));
  1699. if (!empty($where_condition)) {
  1700. //$where_condition
  1701. }
  1702. $result = Database::select(
  1703. '*',
  1704. $table_question,
  1705. array(
  1706. 'limit' => " $start, $limit",
  1707. 'where' => $default_where,
  1708. 'order' => "$sidx $sord"
  1709. )
  1710. );
  1711. return $result;
  1712. }
  1713. /**
  1714. * Get count course medias
  1715. * @param int course id
  1716. */
  1717. public static function get_count_course_medias($course_id)
  1718. {
  1719. $table_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
  1720. $result = Database::select(
  1721. 'count(*) as count',
  1722. $table_question,
  1723. array('where' => array('c_id = ? AND parent_id = 0 AND type = ?' => array($course_id, MEDIA_QUESTION))),
  1724. 'first'
  1725. );
  1726. if ($result && isset($result['count'])) {
  1727. return $result['count'];
  1728. }
  1729. return 0;
  1730. }
  1731. public static function prepare_course_media_select($course_id)
  1732. {
  1733. $medias = self::get_course_medias($course_id);
  1734. $media_list = array();
  1735. $media_list[0] = get_lang('NoMedia');
  1736. if (!empty($medias)) {
  1737. foreach ($medias as $media) {
  1738. $media_list[$media['iid']] = empty($media['question']) ? get_lang('Untitled') : $media['question'];
  1739. }
  1740. }
  1741. return $media_list;
  1742. }
  1743. public static function get_default_levels()
  1744. {
  1745. $select_level = array(
  1746. 1 => 1,
  1747. 2 => 2,
  1748. 3 => 3,
  1749. 4 => 4,
  1750. 5 => 5
  1751. );
  1752. return $select_level;
  1753. }
  1754. public function show_media_content()
  1755. {
  1756. $html = null;
  1757. if ($this->parent_id != 0) {
  1758. $parent_question = Question::read($this->parent_id);
  1759. $html = $parent_question->show_media_content();
  1760. } else {
  1761. $html .= Display::page_subheader($this->selectTitle());
  1762. $html .= $this->selectDescription();
  1763. }
  1764. return $html;
  1765. }
  1766. public static function question_type_no_review()
  1767. {
  1768. return array(
  1769. HOT_SPOT,
  1770. HOT_SPOT_ORDER,
  1771. HOT_SPOT_DELINEATION
  1772. );
  1773. }
  1774. public static function getMediaLabels()
  1775. {
  1776. // Shows media questions
  1777. $courseMedias = Question::prepare_course_media_select(api_get_course_int_id());
  1778. $labels = null;
  1779. if (!empty($courseMedias)) {
  1780. $labels .= get_lang('MediaQuestions').'<br />';
  1781. foreach ($courseMedias as $mediaId => $media) {
  1782. $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>';
  1783. $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>';
  1784. if (!empty($mediaId)) {
  1785. $labels .= self::getMediaLabel($media).''.$editLink.$deleteLink.'<br />';
  1786. }
  1787. }
  1788. }
  1789. return $labels;
  1790. }
  1791. /**
  1792. * Get question columns needed for the new question pool page
  1793. * @param int course code
  1794. * @return array
  1795. */
  1796. public static function getQuestionColumns($courseCode = null, $extraFields = array(), $questionFields = array(), $checkFields = false)
  1797. {
  1798. // The order is important you need to check the the $column variable in the model.ajax.php file
  1799. $columns = array('id', get_lang('Name'));
  1800. // Column config.
  1801. $columnModel = array(
  1802. array(
  1803. 'name' => 'iid',
  1804. 'index' => 'iid',
  1805. 'width' => '20',
  1806. 'align' => 'left'
  1807. ),
  1808. array(
  1809. 'name' => 'question',
  1810. 'index' => 'question',
  1811. 'width' => '200',
  1812. 'align' => 'left'
  1813. )
  1814. );
  1815. // Extra field rules.
  1816. $extraField = new \ExtraField('question');
  1817. $rules = $extraField->getRules($columns, $columnModel, $extraFields, $checkFields);
  1818. // Exercise rules.
  1819. self::getRules($courseCode, $rules, $columns, $columnModel, $questionFields, $checkFields);
  1820. // Adding actions.
  1821. $columns[] = get_lang('Actions');
  1822. $columnModel[] = array(
  1823. 'name' => 'actions',
  1824. 'index' => 'actions',
  1825. 'width' => '30'
  1826. );
  1827. foreach ($columnModel as $col) {
  1828. $simple_column_name[] = $col['name'];
  1829. }
  1830. $return_array = array(
  1831. 'columns' => $columns,
  1832. 'column_model' => $columnModel,
  1833. 'rules' => $rules,
  1834. 'simple_column_name' => $simple_column_name
  1835. );
  1836. return $return_array;
  1837. }
  1838. /**
  1839. * Get all questions
  1840. * @param Application $app
  1841. * @param int $categoryId
  1842. * @param int $exerciseId
  1843. * @param int $courseId
  1844. * @param array $options
  1845. * @param bool $get_count
  1846. * @return array
  1847. */
  1848. public static function getQuestions($app, $categoryId, $exerciseId, $courseId, $options, $get_count = false)
  1849. {
  1850. $questionTable = Database::get_course_table(TABLE_QUIZ_QUESTION);
  1851. $questionPoolFields = array(
  1852. 'question_session_id' => array(
  1853. '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)
  1854. INNER JOIN ".Database::get_course_table(TABLE_QUIZ_TEST)." as quizsession ON (quizsession.iid = quiz_rel_question_session.exercice_id)
  1855. INNER JOIN ".Database::get_main_table(TABLE_MAIN_SESSION)." session ON (session.id = quizsession.session_id)",
  1856. 'where' => 'session_id',
  1857. 'inject_fields' => 'session.name as question_session_id, ',
  1858. ),
  1859. 'question_category_id' => array(
  1860. 'innerjoin' => " INNER JOIN ".Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY)." as quiz_rel_cat ON (quiz_rel_cat.question_id = s.iid)
  1861. INNER JOIN ".Database::get_course_table(TABLE_QUIZ_CATEGORY)." as cat ON (cat.iid = quiz_rel_cat.category_id)",
  1862. 'where' => 'quiz_rel_cat.category_id',
  1863. 'inject_fields' => 'cat.title as question_category_id, ',
  1864. ),
  1865. 'question_exercise_id' => array(
  1866. 'innerjoin' => " INNER JOIN ".Database::get_course_table(TABLE_QUIZ_TEST_QUESTION)." as quiz_rel_question ON (quiz_rel_question.question_id = s.iid)
  1867. INNER JOIN ".Database::get_course_table(TABLE_QUIZ_TEST)." as quizexercise ON (quizexercise.iid = quiz_rel_question.exercice_id) ",
  1868. 'where' => 'quiz_rel_question.exercice_id',
  1869. 'inject_fields' => 'quizexercise.title as question_exercise_id, ',
  1870. ),
  1871. 'question_c_id' => array(
  1872. 'where' => 's.c_id',
  1873. 'innerjoin' => " INNER JOIN ".Database::get_main_table(TABLE_MAIN_COURSE)." as course ON (course.id = s.c_id) ",
  1874. 'inject_fields' => 'course.title as question_c_id, '
  1875. ),
  1876. 'question_question_type' => array(
  1877. 'where' => 's.type ',
  1878. 'inject_fields' => 's.type as question_question_type,'
  1879. ),
  1880. 'question_difficulty' => array(
  1881. 'where' => 's.level',
  1882. 'inject_fields' => 's.level as question_difficulty, '
  1883. )
  1884. );
  1885. // Checking if you're looking for orphan questions.
  1886. $isOrphanQuestion = false;
  1887. if (isset($options['question'])) {
  1888. foreach ($options['question'] as $option) {
  1889. if (isset($option['field']) && $option['field'] == 'question_exercise_id') {
  1890. if ($option['data'] == 0) {
  1891. $isOrphanQuestion = true;
  1892. break;
  1893. }
  1894. }
  1895. }
  1896. }
  1897. // Special case for orphan questions.
  1898. if ($isOrphanQuestion) {
  1899. $questionPoolFields['question_exercise_id'] = array(
  1900. 'innerjoin' => " LEFT JOIN ".Database::get_course_table(TABLE_QUIZ_TEST_QUESTION)." as quiz_rel_question ON (quiz_rel_question.question_id = s.iid)
  1901. LEFT JOIN ".Database::get_course_table(TABLE_QUIZ_TEST)." as quizexercise ON (quizexercise.iid = quiz_rel_question.exercice_id) ",
  1902. 'where' => 'quiz_rel_question.exercice_id',
  1903. 'inject_fields' => 'quizexercise.title as question_exercise_id, ',
  1904. );
  1905. }
  1906. $inject_extra_fields = null;
  1907. $inject_joins = null;
  1908. $where = $options['where'];
  1909. $newQuestionPoolField = array();
  1910. if (isset($options['question'])) {
  1911. foreach ($options['question'] as $question) {
  1912. if (isset($questionPoolFields[$question['field']])) {
  1913. $newQuestionPoolField[$question['field']] = $questionPoolFields[$question['field']];
  1914. }
  1915. }
  1916. }
  1917. $inject_question_fields = null;
  1918. $questionPoolFields = $newQuestionPoolField;
  1919. // Injecting inner joins.
  1920. foreach ($questionPoolFields as $field => $option) {
  1921. $where = str_replace($field, $option['where'], $where);
  1922. if (isset($option['innerjoin']) && !empty($option['innerjoin'])) {
  1923. $inject_joins .= $option['innerjoin'];
  1924. }
  1925. if (isset($option['inject_fields']) && !empty($option['inject_fields'])) {
  1926. $inject_question_fields .= $option['inject_fields'];
  1927. }
  1928. }
  1929. $options['where'] = $where;
  1930. $extra_field = new ExtraField('question');
  1931. $conditions = $extra_field->parseConditions($options);
  1932. $inject_joins .= $conditions['inject_joins'];
  1933. $where = $conditions['where'];
  1934. $inject_where = $conditions['inject_where'];
  1935. $inject_extra_fields = $conditions['inject_extra_fields'];
  1936. $order = $conditions['order'];
  1937. $limit = $conditions['limit'];
  1938. if ($get_count == true) {
  1939. $select = " SELECT count(*) as total_rows";
  1940. } else {
  1941. $select = " SELECT s.*, $inject_extra_fields $inject_question_fields 1 ";
  1942. }
  1943. $extraCondition = null;
  1944. // Used by the question manager
  1945. if (!empty($categoryId)) {
  1946. $categoryRelQuestionTable = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
  1947. $extraCondition = " INNER JOIN $categoryRelQuestionTable c ON (s.iid = c.question_id)";
  1948. $categoryId = intval($categoryId);
  1949. $where .= " AND category_id = $categoryId ";
  1950. }
  1951. /*if (!empty($exerciseId)) {
  1952. $exerciseRelQuestionTable = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
  1953. $extraCondition .= " INNER JOIN $exerciseRelQuestionTable e ON (s.iid = e.question_id)";
  1954. $exerciseId = intval($exerciseId);
  1955. $where .= " AND exercice_id = $exerciseId ";
  1956. }*/
  1957. // Orphan questions
  1958. if ($isOrphanQuestion) {
  1959. //$exerciseRelQuestionTable = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
  1960. //$extraCondition .= " INNER JOIN $exerciseRelQuestionTable e ON (s.iid = e.question_id)";
  1961. $where .= " OR quizexercise.active = -1 OR quiz_rel_question.exercice_id IS NULL";
  1962. }
  1963. if (!empty($courseId)) {
  1964. $courseId = intval($courseId);
  1965. $where .= " AND s.c_id = $courseId ";
  1966. }
  1967. if (isset($options['question'])) {
  1968. $courseList = CourseManager::get_course_list_of_user_as_course_admin(api_get_user_id());
  1969. foreach ($options['question'] as $questionOption) {
  1970. if ($questionOption['field'] == 'question_c_id') {
  1971. if (isset($questionOption['data'])) {
  1972. if (!isset($courseList[$questionOption['data']])) {
  1973. return array();
  1974. }
  1975. }
  1976. }
  1977. }
  1978. }
  1979. //var_dump(CourseManager::get_teacher_list_from_course_code())
  1980. //var_dump($inject_joins);
  1981. $query = " $select FROM $questionTable s $inject_joins $extraCondition WHERE 1=1 $where $inject_where $order $limit";
  1982. //echo $query.'<br />';
  1983. //var_dump($extraCondition);
  1984. //var_dump($where);
  1985. $result = Database::query($query);
  1986. $questions = array();
  1987. $exerciseList = null;
  1988. if (!empty($exerciseId)) {
  1989. $exercise = new Exercise();
  1990. $exercise->read($exerciseId);
  1991. $exerciseList = $exercise->questionList;
  1992. }
  1993. if (Database::num_rows($result)) {
  1994. $questions = Database::store_result($result, 'ASSOC');
  1995. if ($get_count) {
  1996. return $questions[0]['total_rows'];
  1997. }
  1998. $previewIcon = Display::return_icon('preview.gif', get_lang('View'), array(), ICON_SIZE_SMALL);
  1999. $copyIcon = Display::return_icon('copy.png', get_lang('Copy'), array(), ICON_SIZE_SMALL);
  2000. $reuseIcon = Display::return_icon('view_more_stats.gif', get_lang('InsertALinkToThisQuestionInTheExercise'), array(), ICON_SIZE_SMALL);
  2001. $editIcon = Display::return_icon('edit.png', get_lang('Edit'), array(), ICON_SIZE_SMALL);
  2002. // Including actions
  2003. foreach ($questions as &$question) {
  2004. $type = self::get_question_type($question['type']);
  2005. $question['type'] = get_lang($type[1]);
  2006. $question['question_question_type'] = get_lang($type[1]);
  2007. if (empty($exerciseId)) {
  2008. // View.
  2009. /*$actions = Display::url(
  2010. $previewIcon,
  2011. $app['url_generator']->generate(
  2012. 'admin_questions_show',
  2013. array(
  2014. 'id' => $question['iid'],
  2015. 'courseId' => $question['c_id'],
  2016. )
  2017. )
  2018. );*/
  2019. // Edit.
  2020. $actions = Display::url(
  2021. $editIcon,
  2022. $app['url_generator']->generate(
  2023. 'admin_questions_edit',
  2024. array(
  2025. 'id' => $question['iid'],
  2026. 'courseId' => $question['c_id']
  2027. )
  2028. )
  2029. );
  2030. } else {
  2031. // View.
  2032. $actions = Display::url(
  2033. $previewIcon,
  2034. $app['url_generator']->generate(
  2035. 'question_show',
  2036. array(
  2037. 'cidReq' => api_get_course_id(),
  2038. 'id_session' => api_get_session_id(),
  2039. 'exerciseId' => $exerciseId,
  2040. 'id' => $question['iid']
  2041. )
  2042. )
  2043. );
  2044. if (isset($exerciseList) && !empty($exerciseList) && (in_array($question['iid'], $exerciseList))) {
  2045. // Copy.
  2046. //$actions .= $copyIconDisabled;
  2047. } else {
  2048. // Copy.
  2049. $actions .= Display::url(
  2050. $copyIcon,
  2051. 'javascript:void(0);',
  2052. array(
  2053. 'onclick' => 'ajaxAction(this);',
  2054. 'data-url' => $app['url_generator']->generate(
  2055. 'exercise_copy_question',
  2056. array(
  2057. 'cidReq' => api_get_course_id(),
  2058. 'id_session' => api_get_session_id(),
  2059. 'questionId' => $question['iid'],
  2060. 'exerciseId' => $exerciseId
  2061. )
  2062. )
  2063. )
  2064. );
  2065. // Reuse.
  2066. $actions .= Display::url(
  2067. $reuseIcon,
  2068. 'javascript:void(0);',
  2069. array(
  2070. 'onclick' => 'ajaxAction(this);',
  2071. 'data-url' => $app['url_generator']->generate(
  2072. 'exercise_reuse_question',
  2073. array(
  2074. 'cidReq' => api_get_course_id(),
  2075. 'id_session' => api_get_session_id(),
  2076. 'questionId' => $question['iid'],
  2077. 'exerciseId' => $exerciseId
  2078. )
  2079. ),
  2080. )
  2081. );
  2082. }
  2083. // Edit.
  2084. $actions .= Display::url(
  2085. $editIcon,
  2086. $app['url_generator']->generate(
  2087. 'exercise_question_edit',
  2088. array(
  2089. 'cidReq' => api_get_course_id(),
  2090. 'id_session' => api_get_session_id(),
  2091. 'id' => $question['iid']
  2092. )
  2093. )
  2094. );
  2095. }
  2096. $question['actions'] = $actions;
  2097. }
  2098. }
  2099. return $questions;
  2100. }
  2101. public static function getMediaLabel($title)
  2102. {
  2103. return Display::label($title, 'warning');
  2104. }
  2105. /**
  2106. * @param string $courseCode
  2107. * @param array $rules
  2108. * @param array $columns
  2109. * @param array $column_model
  2110. * @return array
  2111. */
  2112. public static function getRules($courseCode, &$rules, &$columns, &$column_model, $questionFields, $checkFields = false)
  2113. {
  2114. // sessions
  2115. // course
  2116. // categories
  2117. // exercises
  2118. // difficult
  2119. // type
  2120. if (empty($courseCode)) {
  2121. // Session.
  2122. $sessionList = SessionManager::get_sessions_by_general_coach(api_get_user_id());
  2123. $fields = array();
  2124. if (!empty($sessionList)) {
  2125. $new_options = array();
  2126. $new_options[] = "-1:".get_lang('All');
  2127. foreach ($sessionList as $session) {
  2128. $new_options[] = "{$session['id']}:{$session['name']}";
  2129. }
  2130. $string = implode(';', $new_options);
  2131. $fields[] = array(
  2132. 'field_display_text' => get_lang('Session'),
  2133. 'field_variable' => 'session_id',
  2134. 'field_type' => ExtraField::FIELD_TYPE_SELECT,
  2135. 'field_default_value' => null,
  2136. 'field_options' => $string
  2137. );
  2138. }
  2139. } else {
  2140. // $courseList = array(api_get_course_info());
  2141. //$courseList = CourseManager::get_course_list_of_user_as_course_admin(api_get_user_id());
  2142. }
  2143. // Courses.
  2144. $courseList = CourseManager::get_course_list_of_user_as_course_admin(api_get_user_id());
  2145. if (!empty($courseList)) {
  2146. $new_options = array();
  2147. $new_options[] = "-1:".get_lang('All');
  2148. foreach ($courseList as $course) {
  2149. $new_options[] = "{$course['id']}:{$course['title']}";
  2150. }
  2151. $string = implode(';', $new_options);
  2152. $fields[] = array(
  2153. 'field_display_text' => get_lang('Course'),
  2154. 'field_variable' => 'c_id',
  2155. 'field_type' => ExtraField::FIELD_TYPE_SELECT,
  2156. 'field_default_value' => null,
  2157. 'field_options' => $string
  2158. );
  2159. }
  2160. // Categories.
  2161. $string = null;
  2162. if (!empty($courseList)) {
  2163. $new_options = array();
  2164. $new_options[] = "-1:".get_lang('All');
  2165. // Global categories
  2166. // @todo use tree view
  2167. $categories = Testcategory::getCategoriesIdAndName(0);
  2168. if (!empty($categories)) {
  2169. foreach ($categories as $id => $category) {
  2170. if (!empty($id)) {
  2171. $new_options[] = "$id:[Global] - ".$category;
  2172. }
  2173. }
  2174. }
  2175. foreach ($courseList as $course) {
  2176. $categories = Testcategory::getCategoriesIdAndName($course['real_id']);
  2177. if (!empty($categories)) {
  2178. foreach ($categories as $id => $category) {
  2179. if (!empty($id)) {
  2180. $new_options[] = "$id:".$course['title']." - ".$category;
  2181. }
  2182. }
  2183. }
  2184. }
  2185. $string = implode(';', $new_options);
  2186. $fields[] = array(
  2187. 'field_display_text' => get_lang('Category'),
  2188. 'field_variable' => 'category_id',
  2189. 'field_type' => ExtraField::FIELD_TYPE_SELECT,
  2190. 'field_default_value' => null,
  2191. 'field_options' => $string
  2192. );
  2193. }
  2194. $course = api_get_course_int_id();
  2195. $sessionId = api_get_session_id();
  2196. // Exercises.
  2197. $exerciseList = ExerciseLib::get_all_exercises_for_course_id($sessionId, $course);
  2198. if (!empty($exerciseList)) {
  2199. $new_options = array();
  2200. $new_options[] = "-1:".get_lang('All');
  2201. $new_options[] = "0:".get_lang('Orphan');
  2202. foreach ($exerciseList as $exercise) {
  2203. $new_options[] = "{$exercise['iid']}:{$exercise['title']}";
  2204. }
  2205. $string = implode(';', $new_options);
  2206. $fields[] = array(
  2207. 'field_display_text' => get_lang('Exercise'),
  2208. 'field_variable' => 'exercise_id',
  2209. 'field_type' => ExtraField::FIELD_TYPE_SELECT,
  2210. 'field_default_value' => null,
  2211. 'field_options' => $string
  2212. );
  2213. }
  2214. // Question type.
  2215. $questionList = Question::get_question_type_list();
  2216. if (!empty($questionList)) {
  2217. $new_options = array();
  2218. $new_options[] = "-1:".get_lang('All');
  2219. foreach ($questionList as $key => $question) {
  2220. $new_options[] = "{$key}:".get_lang($question['1']);
  2221. }
  2222. $string = implode(';', $new_options);
  2223. $fields[] = array(
  2224. 'field_display_text' => get_lang('AnswerType'),
  2225. 'field_variable' => 'question_type',
  2226. 'field_type' => ExtraField::FIELD_TYPE_SELECT,
  2227. 'field_default_value' => null,
  2228. 'field_options' => $string
  2229. );
  2230. }
  2231. // Difficult.
  2232. $levels = Question::get_default_levels();
  2233. if (!empty($levels)) {
  2234. $new_options = array();
  2235. $new_options[] = "-1:".get_lang('All');
  2236. foreach ($levels as $key => $level) {
  2237. $new_options[] ="{$key}:{$level}";
  2238. }
  2239. $string = implode(';', $new_options);
  2240. $fields[] = array(
  2241. 'field_display_text' => get_lang('Difficulty'),
  2242. 'field_variable' => 'difficulty',
  2243. 'field_type' => ExtraField::FIELD_TYPE_SELECT,
  2244. 'field_default_value' => null,
  2245. 'field_options' => $string
  2246. );
  2247. }
  2248. $questionFieldsKeys = array();
  2249. if (!empty($questionFields)) {
  2250. foreach ($questionFields as $question) {
  2251. $questionFieldsKeys[] = $question['field'];
  2252. }
  2253. }
  2254. if (!empty($fields)) {
  2255. foreach ($fields as $field) {
  2256. $search_options = array();
  2257. $type = 'text';
  2258. if (in_array($field['field_type'], array(ExtraField::FIELD_TYPE_SELECT, ExtraField::FIELD_TYPE_DOUBLE_SELECT))) {
  2259. $type = 'select';
  2260. $search_options['sopt'] = array('eq', 'ne'); //equal not equal
  2261. //$search_options['sopt'] = array('cn', 'nc'); //contains not contains
  2262. } else {
  2263. $search_options['sopt'] = array('cn', 'nc'); //contains not contains
  2264. }
  2265. $search_options['searchhidden'] = 'true';
  2266. $search_options['defaultValue'] = isset($search_options['field_default_value']) ? $search_options['field_default_value'] : null;
  2267. $search_options['value'] = $field['field_options'];
  2268. $column_model[] = array(
  2269. 'name' => 'question_'.$field['field_variable'],
  2270. 'index' => 'question_'.$field['field_variable'],
  2271. 'width' => '100',
  2272. 'hidden' => 'true',
  2273. 'search' => 'true',
  2274. 'stype' => $type,
  2275. 'searchoptions' => $search_options
  2276. );
  2277. $columns[] = $field['field_display_text'];
  2278. $rules[] = array(
  2279. 'field' => 'question_'.$field['field_variable'],
  2280. 'op' => 'eq'
  2281. );
  2282. }
  2283. }
  2284. return $rules;
  2285. }
  2286. /**
  2287. *
  2288. * @param $exerciseId
  2289. * @param $mediaId
  2290. * @return array|bool
  2291. */
  2292. public function getQuestionsPerMediaWithCategories($exerciseId, $mediaId)
  2293. {
  2294. $exerciseId = intval($exerciseId);
  2295. $mediaId = intval($mediaId);
  2296. $questionTable = Database::get_course_table(TABLE_QUIZ_QUESTION);
  2297. $questionRelExerciseTable = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
  2298. $sql = "SELECT q.* FROM $questionTable q INNER JOIN $questionRelExerciseTable r ON (q.iid = r.question_id)
  2299. WHERE (r.exercice_id = $exerciseId AND q.parent_id = $mediaId) ";
  2300. $result = Database::query($sql);
  2301. if (Database::num_rows($result)) {
  2302. return Database::store_result($result, 'ASSOC');
  2303. }
  2304. return false;
  2305. }
  2306. /**
  2307. * @param int $exerciseId
  2308. * @param int $mediaId
  2309. * @return array
  2310. */
  2311. public function getQuestionCategoriesOfMediaQuestions($exerciseId, $mediaId)
  2312. {
  2313. $questions = $this->getQuestionsPerMediaWithCategories($exerciseId, $mediaId);
  2314. $questionCategoryList = array();
  2315. if (!empty($questions)) {
  2316. foreach ($questions as $question) {
  2317. $categories = Testcategory::getCategoryForQuestionWithCategoryData($question['iid']);
  2318. if (!empty($categories)) {
  2319. foreach ($categories as $category) {
  2320. $questionCategoryList[$question['iid']][] = $category['iid'];
  2321. }
  2322. }
  2323. }
  2324. }
  2325. return $questionCategoryList;
  2326. }
  2327. /**
  2328. * Check if the media sent matches other medias sent before
  2329. * @param int $exerciseId
  2330. * @param int $mediaId
  2331. * @return array
  2332. */
  2333. public function allQuestionWithMediaHaveTheSameCategory($exerciseId, $mediaId, $categoryListToCompare = array(), $ignoreQuestionId = null, $returnCategoryId = false)
  2334. {
  2335. $questions = $this->getQuestionCategoriesOfMediaQuestions($exerciseId, $mediaId);
  2336. $result = false;
  2337. $categoryId = null;
  2338. if (empty($questions)) {
  2339. $result = true;
  2340. } else {
  2341. $tempArray = array();
  2342. foreach ($questions as $categories) {
  2343. $diff = array_diff($tempArray, $categories);
  2344. $categoryId = $categories[0];
  2345. $tempArray = $categories;
  2346. if (empty($diff)) {
  2347. $result = true;
  2348. continue;
  2349. } else {
  2350. $result = false;
  2351. break;
  2352. }
  2353. }
  2354. }
  2355. if (isset($categoryListToCompare) && !empty($categoryListToCompare)) {
  2356. $result = false;
  2357. foreach ($questions as $questionId => $categories) {
  2358. if ($ignoreQuestionId == $questionId) {
  2359. continue;
  2360. }
  2361. $diff = array_diff($categoryListToCompare, $categories);
  2362. $categoryId = $categories[0];
  2363. if (empty($diff)) {
  2364. $result = true;
  2365. continue;
  2366. } else {
  2367. $result = false;
  2368. break;
  2369. }
  2370. }
  2371. }
  2372. if ($returnCategoryId) {
  2373. return $categoryId;
  2374. }
  2375. return $result;
  2376. }
  2377. /**
  2378. * Makes a question valid inside a global category in another course as well
  2379. * This might be used in combination with the ExerciseController::reuseQuestionAction()
  2380. * to make sure the "linked" question also has the global category
  2381. * Before this function, the only problematic bit was that copying a question
  2382. * didn't copy the reference to this question in the c_quiz_question_rel_category table,
  2383. * making the question itself in the new course appear without category
  2384. *
  2385. * This might get deprecated if we start using c_id = 0 in the c_quiz_question_rel_category
  2386. */
  2387. function enableGlobalCategoryInNewCourse($questionId, $courseIdDest)
  2388. {
  2389. $questionRelCategoryTable = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
  2390. $questionId = intval($questionId);
  2391. $courseIdDest = intval($courseIdDest);
  2392. // First check whether the category is global: if it's not, we don't need to do anything
  2393. $sql = "SELECT iid, c_id, question_id, category_id FROM $questionRelCategoryTable WHERE question_id = $questionId";
  2394. $res = Database::query($sql);
  2395. if (Database::num_rows($res) < 1) {
  2396. return false;
  2397. }
  2398. $origCats = array();
  2399. $destCats = array();
  2400. while ($row = Database::fetch_assoc($res)) {
  2401. $g = Testcategory::isGlobal($row['category_id']);
  2402. if ($g) {
  2403. $origCats[] = $row['category_id'];
  2404. if ($row['c_id'] == $courseIdDest) {
  2405. $destCats[] = $row['category_id'];
  2406. }
  2407. }
  2408. }
  2409. $diff = array_diff($origCats, $destCats);
  2410. foreach ($diff as $cat) {
  2411. $sql = "INSERT INTO $questionRelCategoryTable (c_id, question_id, category_id) VALUES ($courseIdDest, $questionId, $cat)";
  2412. Database::query($sql);
  2413. }
  2414. }
  2415. }