PageRenderTime 40ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/question/upgrade.php

https://bitbucket.org/ciceidev/cicei_moodle_conditional_activities
PHP | 363 lines | 222 code | 53 blank | 88 comment | 56 complexity | 95d41651b7193e29ade84a53bb738071 MD5 | raw file
Possible License(s): LGPL-2.1, BSD-3-Clause
  1. <?php // $Id$
  2. /**
  3. * This file contains dtabase upgrade code that is called from lib/db/upgrade.php,
  4. * and also check methods that can be used for pre-install checks via
  5. * admin/environment.php and lib/environmentlib.php.
  6. *
  7. * @copyright &copy; 2007 The Open University
  8. * @author T.J.Hunt@open.ac.uk
  9. * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
  10. * @package questionbank
  11. */
  12. /**
  13. * This test is becuase the RQP question type was included in core
  14. * up to and including Moodle 1.8, and was removed before Moodle 1.9.
  15. *
  16. * Therefore, we want to check whether any rqp questions exist in the database
  17. * before doing the upgrade. However, the check is not relevant if that
  18. * question type was never installed, or if the person has chosen to
  19. * manually reinstall the rqp question type from contrib.
  20. *
  21. * @param $result the result object that can be modified.
  22. * @return null if the test is irrelevant, or true or false depending on whether the test passes.
  23. */
  24. function question_check_no_rqp_questions($result) {
  25. global $CFG;
  26. if (empty($CFG->qtype_rqp_version) || is_dir($CFG->dirroot . '/question/type/rqp')) {
  27. return null;
  28. } else {
  29. $result->setStatus(count_records('question', 'qtype', 'rqp') == 0);
  30. }
  31. return $result;
  32. }
  33. function question_remove_rqp_qtype() {
  34. global $CFG;
  35. $result = true;
  36. // Only remove the question type if the code is gone.
  37. if (!is_dir($CFG->dirroot . '/question/type/rqp')) {
  38. $table = new XMLDBTable('question_rqp_states');
  39. $result = $result && drop_table($table);
  40. $table = new XMLDBTable('question_rqp');
  41. $result = $result && drop_table($table);
  42. $table = new XMLDBTable('question_rqp_types');
  43. $result = $result && drop_table($table);
  44. $table = new XMLDBTable('question_rqp_servers');
  45. $result = $result && drop_table($table);
  46. $result = $result && unset_config('qtype_rqp_version');
  47. }
  48. return $result;
  49. }
  50. function question_remove_rqp_qtype_config_string() {
  51. global $CFG;
  52. $result = true;
  53. // An earlier, buggy version of the previous function missed out the unset_config call.
  54. if (!empty($CFG->qtype_rqp_version) && !is_dir($CFG->dirroot . '/question/type/rqp')) {
  55. $result = $result && unset_config('qtype_rqp_version');
  56. }
  57. return $result;
  58. }
  59. /**
  60. * @param $result the result object that can be modified.
  61. * @return null if the test is irrelevant, or true or false depending on whether the test passes.
  62. */
  63. function question_random_check($result){
  64. global $CFG;
  65. if (!empty($CFG->running_installer) //no test on first installation, no questions to test yet
  66. || $CFG->version >= 2007081000){//no test after upgrade seperates question cats into contexts.
  67. return null;
  68. }
  69. if (!$toupdate = question_cwqpfs_to_update()){
  70. $result->setStatus(true);//pass test
  71. } else {
  72. //set the feedback string here and not in xml file since we need something
  73. //more complex than just a string picked from admin.php lang file
  74. $a = new object();
  75. $a->reporturl = "{$CFG->wwwroot}/{$CFG->admin}/report/question/";
  76. $lang = str_replace('_utf8', '', current_language());
  77. $a->docsurl = "{$CFG->docroot}/$lang/admin/report/question/index";
  78. $result->setFeedbackStr(array('questioncwqpfscheck', 'admin', $a));
  79. $result->setStatus(false);//fail test
  80. }
  81. return $result;
  82. }
  83. /*
  84. * Delete all 'random' questions that are not been used in a quiz.
  85. */
  86. function question_delete_unused_random(){
  87. global $CFG;
  88. $tofix = array();
  89. $result = true;
  90. //delete all 'random' questions that are not been used in a quiz.
  91. if ($qqis = get_records_sql("SELECT q.* FROM {$CFG->prefix}question q LEFT JOIN ".
  92. "{$CFG->prefix}quiz_question_instances qqi ".
  93. "ON q.id = qqi.question WHERE q.qtype='random' AND qqi.question IS NULL")){
  94. $qqilist = join(array_keys($qqis), ',');
  95. $result = $result && delete_records_select('question', "id IN ($qqilist)");
  96. }
  97. return $result;
  98. }
  99. function question_cwqpfs_to_update($categories = null){
  100. global $CFG;
  101. $tofix = array();
  102. $result = true;
  103. //any cats with questions picking from subcats?
  104. if (!$cwqpfs = get_records_sql_menu("SELECT DISTINCT qc.id, 1 ".
  105. "FROM {$CFG->prefix}question q, {$CFG->prefix}question_categories qc ".
  106. "WHERE q.qtype='random' AND qc.id = q.category AND ".
  107. sql_compare_text('q.questiontext'). " = '1'")){
  108. return array();
  109. } else {
  110. if ($categories === null){
  111. $categories = get_records('question_categories');
  112. }
  113. $categorychildparents = array();
  114. foreach ($categories as $id => $category){
  115. $categorychildparents[$category->course][$id] = $category->parent;
  116. }
  117. foreach ($categories as $id => $category){
  118. if (FALSE !== array_key_exists($category->parent, $categorychildparents[$category->course])){
  119. //this is not a top level cat
  120. continue;//go to next category
  121. } else{
  122. $tofix += question_cwqpfs_check_children($id, $categories, $categorychildparents[$category->course], $cwqpfs);
  123. }
  124. }
  125. }
  126. return $tofix;
  127. }
  128. function question_cwqpfs_check_children($checkid, $categories, $categorychildparents, $cwqpfs){
  129. $tofix = array();
  130. if (array_key_exists($checkid, $cwqpfs)){//cwqpfs in this cat
  131. $getchildren = array();
  132. $getchildren[] = $checkid;
  133. //search down tree and find all children
  134. while ($nextid = array_shift($getchildren)){//repeat until $getchildren
  135. //empty;
  136. $childids = array_keys($categorychildparents, $nextid);
  137. foreach ($childids as $childid){
  138. if ($categories[$childid]->publish != $categories[$checkid]->publish){
  139. $tofix[$childid] = $categories[$checkid]->publish;
  140. }
  141. }
  142. $getchildren = array_merge($getchildren, $childids);
  143. }
  144. } else { // check children for cwqpfs
  145. $childrentocheck = array_keys($categorychildparents, $checkid);
  146. foreach ($childrentocheck as $childtocheck){
  147. $tofix += question_cwqpfs_check_children($childtocheck, $categories, $categorychildparents, $cwqpfs);
  148. }
  149. }
  150. return $tofix;
  151. }
  152. function question_category_next_parent_in($contextid, $question_categories, $id, $already_seen = array()){
  153. // Recursively look for an ancestor category of the given category that
  154. // belongs to context $contextid. (In a lot of cases, the parent will be
  155. // the one.) If there is none, return 0, meaning the top level.
  156. $already_seen[] = $id;
  157. $nextparent = $question_categories[$id]->parent;
  158. if ($nextparent == 0) {
  159. // Hit the top level, we are done.
  160. return 0;
  161. } else if (!array_key_exists($nextparent, $question_categories)) {
  162. // The category hierarchy must have been screwed up before, in that
  163. // we have run out of categories to search, but without reaching the
  164. // top level. Repair the situation by returning 0, meaning top level.
  165. notify(get_string('upgradeproblemunknowncategory', 'question', $question_categories[$id]));
  166. return 0;
  167. } else if (in_array($nextparent, $already_seen)) {
  168. // The category hierarchy must have been screwed up before, in that
  169. // we have just found a loop in the category 'tree'. That should,
  170. // of course, be impossible, but it did acutally happen in at least once.
  171. // Repair the situation by returning 0, meaning top level.
  172. notify(get_string('upgradeproblemcategoryloop', 'question', implode(', ', $already_seen)));
  173. return 0;
  174. } else if ($contextid == $question_categories[$nextparent]->contextid) {
  175. // Found a suitable category, we are done.
  176. return $nextparent;
  177. } else {
  178. // The immediate parent is not in the same context, so look further up.
  179. return question_category_next_parent_in($contextid, $question_categories, $nextparent, $already_seen);
  180. }
  181. }
  182. /**
  183. * Check that either category parent is 0 or a category shared in the same context.
  184. * Fix any categories to point to grand or grand grand parent etc in the same context or 0.
  185. */
  186. function question_category_checking($question_categories){
  187. //make an array that is easier to search
  188. $newparents = array();
  189. foreach ($question_categories as $id => $category){
  190. $newparents[$id] = question_category_next_parent_in($category->contextid, $question_categories, $id);
  191. }
  192. foreach (array_keys($question_categories) as $id){
  193. $question_categories[$id]->parent = $newparents[$id];
  194. }
  195. return $question_categories;
  196. }
  197. function question_upgrade_context_etc(){
  198. global $CFG;
  199. $result = true;
  200. $result = $result && question_delete_unused_random();
  201. $question_categories = get_records('question_categories');
  202. if ($question_categories) {
  203. //prepare content for new db structure
  204. $tofix = question_cwqpfs_to_update($question_categories);
  205. foreach ($tofix as $catid => $publish){
  206. $question_categories[$catid]->publish = $publish;
  207. }
  208. foreach ($question_categories as $id => $question_category){
  209. $course = $question_categories[$id]->course;
  210. unset($question_categories[$id]->course);
  211. if ($question_categories[$id]->publish){
  212. $context = get_context_instance(CONTEXT_SYSTEM);
  213. //new name with old course name in brackets
  214. $coursename = get_field('course', 'shortname', 'id', $course);
  215. $question_categories[$id]->name .= " ($coursename)";
  216. } else {
  217. $context = get_context_instance(CONTEXT_COURSE, $course);
  218. }
  219. $question_categories[$id]->contextid = $context->id;
  220. unset($question_categories[$id]->publish);
  221. }
  222. $question_categories = question_category_checking($question_categories);
  223. }
  224. /// Define index course (not unique) to be dropped form question_categories
  225. $table = new XMLDBTable('question_categories');
  226. $index = new XMLDBIndex('course');
  227. $index->setAttributes(XMLDB_INDEX_NOTUNIQUE, array('course'));
  228. /// Launch drop index course
  229. $result = $result && drop_index($table, $index);
  230. /// Define field course to be dropped from question_categories
  231. $field = new XMLDBField('course');
  232. /// Launch drop field course
  233. $result = $result && drop_field($table, $field);
  234. /// Define field context to be added to question_categories
  235. $field = new XMLDBField('contextid');
  236. $field->setAttributes(XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0', 'name');
  237. $field->comment = 'context that this category is shared in';
  238. /// Launch add field context
  239. $result = $result && add_field($table, $field);
  240. /// Define index context (not unique) to be added to question_categories
  241. $index = new XMLDBIndex('contextid');
  242. $index->setAttributes(XMLDB_INDEX_NOTUNIQUE, array('contextid'));
  243. $index->comment = 'links to context table';
  244. /// Launch add index context
  245. $result = $result && add_index($table, $index);
  246. $field = new XMLDBField('publish');
  247. /// Launch drop field publish
  248. $result = $result && drop_field($table, $field);
  249. /// update table contents with previously calculated new contents.
  250. if ($question_categories) {
  251. foreach ($question_categories as $question_category) {
  252. $question_category->name = addslashes($question_category->name);
  253. $question_category->info = addslashes($question_category->info);
  254. if (!$result = $result && update_record('question_categories', $question_category)){
  255. notify(get_string('upgradeproblemcouldnotupdatecategory', 'question', $question_category));
  256. }
  257. }
  258. }
  259. /// Define field timecreated to be added to question
  260. $table = new XMLDBTable('question');
  261. $field = new XMLDBField('timecreated');
  262. $field->setAttributes(XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0', 'hidden');
  263. /// Launch add field timecreated
  264. $result = $result && add_field($table, $field);
  265. /// Define field timemodified to be added to question
  266. $table = new XMLDBTable('question');
  267. $field = new XMLDBField('timemodified');
  268. $field->setAttributes(XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0', 'timecreated');
  269. /// Launch add field timemodified
  270. $result = $result && add_field($table, $field);
  271. /// Define field createdby to be added to question
  272. $table = new XMLDBTable('question');
  273. $field = new XMLDBField('createdby');
  274. $field->setAttributes(XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null, null, null, 'timemodified');
  275. /// Launch add field createdby
  276. $result = $result && add_field($table, $field);
  277. /// Define field modifiedby to be added to question
  278. $table = new XMLDBTable('question');
  279. $field = new XMLDBField('modifiedby');
  280. $field->setAttributes(XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null, null, null, 'createdby');
  281. /// Launch add field modifiedby
  282. $result = $result && add_field($table, $field);
  283. /// Define key createdby (foreign) to be added to question
  284. $table = new XMLDBTable('question');
  285. $key = new XMLDBKey('createdby');
  286. $key->setAttributes(XMLDB_KEY_FOREIGN, array('createdby'), 'user', array('id'));
  287. /// Launch add key createdby
  288. $result = $result && add_key($table, $key);
  289. /// Define key modifiedby (foreign) to be added to question
  290. $table = new XMLDBTable('question');
  291. $key = new XMLDBKey('modifiedby');
  292. $key->setAttributes(XMLDB_KEY_FOREIGN, array('modifiedby'), 'user', array('id'));
  293. /// Launch add key modifiedby
  294. $result = $result && add_key($table, $key);
  295. return $result;
  296. }
  297. /**
  298. * In Moodle, all random questions should have question.parent set to be the same
  299. * as question.id. One effect of MDL-5482 is that this will not be true for questions that
  300. * were backed up then restored. The probably does not cause many problems, except occasionally,
  301. * if the bogus question.parent happens to point to a multianswer question type, or when you
  302. * try to do a subsequent backup. Anyway, these question.parent values should be fixed, and
  303. * that is what this update does.
  304. */
  305. function question_fix_random_question_parents() {
  306. global $CFG;
  307. return execute_sql('UPDATE ' . $CFG->prefix . 'question SET parent = id ' .
  308. "WHERE qtype = 'random' AND parent <> id");
  309. }
  310. ?>