question.class.php 97 KB

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