4 * This file is used for special upgrade functions - for example groups and gradebook.
5 * These functions must use SQL and database related functions only- no other Moodle API,
6 * because it might depend on db structures that are not yet present during upgrade.
7 * (Do not use functions from accesslib.php, grades classes or group functions at all!)
10 function upgrade_fix_category_depths() {
13 // first fix incorrect parents
15 FROM {course_categories} c
16 WHERE c.parent > 0 AND c.parent NOT IN (SELECT pc.id FROM {course_categories} pc)";
17 if ($rs = $DB->get_recordset_sql($sql)) {
18 foreach ($rs as $cat) {
20 $cat->path = '/'.$cat->id;
22 $DB->update_record('course_categories', $cat);
27 // now add path and depth to top level categories
28 $sql = "UPDATE {course_categories}
29 SET depth = 1, path = ".$DB->sql_concat("'/'", "id")."
33 // now fix all other levels - slow but works in all supported dbs
35 while ($DB->record_exists('course_categories', array('depth'=>0))) {
36 $sql = "SELECT c.id, pc.path
37 FROM {course_categories} c, {course_categories} pc
38 WHERE c.parent=pc.id AND c.depth=0 AND pc.depth=?";
39 if ($rs = $DB->get_recordset_sql($sql, array($parentdepth))) {
40 foreach ($rs as $cat) {
41 $cat->depth = $parentdepth+1;
42 $cat->path = $cat->path.'/'.$cat->id;
43 $DB->update_record('course_categories', $cat);
48 if ($parentdepth > 100) {
49 //something must have gone wrong - nobody can have more than 100 levels of categories, right?
50 debugging('Unknown error fixing category depths');
57 * Moves all course files except the moddata to new file storage
59 * Unfortunately this function uses core file related functions - it might be necessary to tweak it if something changes there :-(
61 function upgrade_migrate_files_courses() {
63 require_once($CFG->libdir.'/filelib.php');
65 $count = $DB->count_records('course');
66 $pbar = new progress_bar('migratecoursefiles', 500, true);
68 $rs = $DB->get_recordset('course');
70 foreach ($rs as $course) {
72 upgrade_set_timeout(60*5); // set up timeout, may also abort execution
73 $context = get_context_instance(CONTEXT_COURSE, $course->id);
74 upgrade_migrate_files_course($context, '/', true);
75 $pbar->update($i, $count, "Migrated course files - course $i/$count.");
83 * Internal function - do not use directly
85 function upgrade_migrate_files_course($context, $path, $delete) {
88 $fullpathname = $CFG->dataroot.'/'.$context->instanceid.$path;
89 if (!file_exists($fullpathname)) {
92 $items = new DirectoryIterator($fullpathname);
93 $fs = get_file_storage();
95 foreach ($items as $item) {
100 if ($item->isLink()) {
101 // do not delete symbolic links or its children
102 $delete_this = false;
104 $delete_this = $delete;
107 if (strpos($path, '/backupdata/') === 0) {
108 $component = 'backup';
109 $filearea = 'course';
110 $filepath = substr($path, strlen('/backupdata'));
112 $component = 'course';
113 $filearea = 'legacy';
117 if ($item->isFile()) {
118 if (!$item->isReadable()) {
119 echo $OUTPUT->notification(" File not readable, skipping: ".$fullpathname.$item->getFilename());
123 $filepath = clean_param($filepath, PARAM_PATH);
124 $filename = clean_param($item->getFilename(), PARAM_FILE);
126 if ($filename === '') {
127 //unsupported chars, sorry
131 if (!$fs->file_exists($context->id, $component, $filearea, '0', $filepath, $filename)) {
132 $file_record = array('contextid'=>$context->id, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>0, 'filepath'=>$filepath, 'filename'=>$filename,
133 'timecreated'=>$item->getCTime(), 'timemodified'=>$item->getMTime());
134 if ($fs->create_file_from_pathname($file_record, $fullpathname.$item->getFilename())) {
136 @unlink($fullpathname.$item->getFilename());
142 if ($path == '/' and $item->getFilename() == 'moddata') {
143 continue; // modules are responsible
146 $dirname = clean_param($item->getFilename(), PARAM_PATH);
147 if ($dirname === '') {
148 //unsupported chars, sorry
151 $filepath = ($filepath.$dirname.'/');
152 if ($filepath !== '/backupdata/') {
153 $fs->create_directory($context->id, $component, $filearea, 0, $filepath);
156 //migrate recursively all subdirectories
157 upgrade_migrate_files_course($context, $path.$item->getFilename().'/', $delete_this);
159 // delete dir if empty
160 @rmdir($fullpathname.$item->getFilename());
164 unset($items); //release file handles
168 * Moves all block attachments
170 * Unfortunately this function uses core file related functions - it might be necessary to tweak it if something changes there :-(
172 function upgrade_migrate_files_blog() {
173 global $DB, $CFG, $OUTPUT;
175 $fs = get_file_storage();
177 $count = $DB->count_records_select('post', "module='blog' AND attachment IS NOT NULL AND attachment <> '1'");
179 if ($rs = $DB->get_recordset_select('post', "module='blog' AND attachment IS NOT NULL AND attachment <> '1'")) {
181 upgrade_set_timeout(60*20); // set up timeout, may also abort execution
183 $pbar = new progress_bar('migrateblogfiles', 500, true);
186 foreach ($rs as $entry) {
188 $pathname = "$CFG->dataroot/blog/attachments/$entry->id/$entry->attachment";
189 if (!file_exists($pathname)) {
190 $entry->attachment = NULL;
191 $DB->update_record('post', $entry);
195 $filename = clean_param($entry->attachment, PARAM_FILE);
196 if ($filename === '') {
197 // weird file name, ignore it
198 $entry->attachment = NULL;
199 $DB->update_record('post', $entry);
203 if (!is_readable($pathname)) {
204 echo $OUTPUT->notification(" File not readable, skipping: ".$pathname);
208 if (!$fs->file_exists(SYSCONTEXTID, 'blog', 'attachment', $entry->id, '/', $filename)) {
209 $file_record = array('contextid'=>SYSCONTEXTID, 'component'=>'blog', 'filearea'=>'attachment', 'itemid'=>$entry->id, 'filepath'=>'/', 'filename'=>$filename,
210 'timecreated'=>filectime($pathname), 'timemodified'=>filemtime($pathname), 'userid'=>$entry->userid);
211 $fs->create_file_from_pathname($file_record, $pathname);
214 @rmdir("$CFG->dataroot/blog/attachments/$entry->id/");
216 $entry->attachment = 1; // file name not needed there anymore
217 $DB->update_record('post', $entry);
218 $pbar->update($i, $count, "Migrated blog attachments - $i/$count.");
223 @rmdir("$CFG->dataroot/blog/attachments/");
224 @rmdir("$CFG->dataroot/blog/");
228 * This function will fix the status of the localhost/all records in the mnet_host table
229 * checking they exist and adding them if missing + redefine CFG->mnet_localhost_id and
230 * CFG->mnet_all_hosts_id if needed + update all the users having non-existent mnethostid
231 * to correct CFG->mnet_localhost_id
233 * Implemented because, at some point, specially in old installations upgraded along
234 * multiple versions, sometimes the stuff above has ended being inconsistent, causing
235 * problems here and there (noticeably in backup/restore). MDL-16879
237 function upgrade_fix_incorrect_mnethostids() {
241 /// Get current $CFG/mnet_host records
242 $old_mnet_localhost_id = !empty($CFG->mnet_localhost_id) ? $CFG->mnet_localhost_id : 0;
243 $old_mnet_all_hosts_id = !empty($CFG->mnet_all_hosts_id) ? $CFG->mnet_all_hosts_id : 0;
245 $current_mnet_localhost_host = $DB->get_record('mnet_host', array('wwwroot' => $CFG->wwwroot)); /// By wwwroot
246 $current_mnet_all_hosts_host = $DB->get_record_select('mnet_host', $DB->sql_isempty('mnet_host', 'wwwroot', false, false)); /// By empty wwwroot
248 if (!$moodleapplicationid = $DB->get_field('mnet_application', 'id', array('name' => 'moodle'))) {
251 'display_name' => 'Moodle',
252 'xmlrpc_server_url' => '/mnet/xmlrpc/server.php',
253 'sso_land_url' => '/auth/mnet/land.php',
254 'sso_jump_url' => '/auth/mnet/jump.php',
256 $moodleapplicationid = $DB->insert_record('mnet_application', $m);
259 /// Create localhost_host if necessary (pretty improbable but better to be 100% in the safe side)
260 /// Code stolen from mnet_environment->init
261 if (!$current_mnet_localhost_host) {
262 $current_mnet_localhost_host = new stdClass();
263 $current_mnet_localhost_host->wwwroot = $CFG->wwwroot;
264 $current_mnet_localhost_host->ip_address = '';
265 $current_mnet_localhost_host->public_key = '';
266 $current_mnet_localhost_host->public_key_expires = 0;
267 $current_mnet_localhost_host->last_connect_time = 0;
268 $current_mnet_localhost_host->last_log_id = 0;
269 $current_mnet_localhost_host->deleted = 0;
270 $current_mnet_localhost_host->name = '';
271 $current_mnet_localhost_host->applicationid = $moodleapplicationid;
272 /// Get the ip of the server
273 if (empty($_SERVER['SERVER_ADDR'])) {
274 /// SERVER_ADDR is only returned by Apache-like webservers
275 $count = preg_match("@^(?:http[s]?://)?([A-Z0-9\-\.]+).*@i", $current_mnet_localhost_host->wwwroot, $matches);
276 $my_hostname = $count > 0 ? $matches[1] : false;
277 $my_ip = gethostbyname($my_hostname); // Returns unmodified hostname on failure. DOH!
278 if ($my_ip == $my_hostname) {
279 $current_mnet_localhost_host->ip_address = 'UNKNOWN';
281 $current_mnet_localhost_host->ip_address = $my_ip;
284 $current_mnet_localhost_host->ip_address = $_SERVER['SERVER_ADDR'];
286 $current_mnet_localhost_host->id = $DB->insert_record('mnet_host', $current_mnet_localhost_host, true);
289 /// Create all_hosts_host if necessary (pretty improbable but better to be 100% in the safe side)
290 /// Code stolen from mnet_environment->init
291 if (!$current_mnet_all_hosts_host) {
292 $current_mnet_all_hosts_host = new stdClass();
293 $current_mnet_all_hosts_host->wwwroot = '';
294 $current_mnet_all_hosts_host->ip_address = '';
295 $current_mnet_all_hosts_host->public_key = '';
296 $current_mnet_all_hosts_host->public_key_expires = 0;
297 $current_mnet_all_hosts_host->last_connect_time = 0;
298 $current_mnet_all_hosts_host->last_log_id = 0;
299 $current_mnet_all_hosts_host->deleted = 0;
300 $current_mnet_all_hosts_host->name = 'All Hosts';
301 $current_mnet_all_hosts_host->applicationid = $moodleapplicationid;
302 $current_mnet_all_hosts_host->id = $DB->insert_record('mnet_host', $current_mnet_all_hosts_host, true);
305 /// Compare old_mnet_localhost_id and current_mnet_localhost_host
307 if ($old_mnet_localhost_id != $current_mnet_localhost_host->id) { /// Different = problems
308 /// Update $CFG->mnet_localhost_id to correct value
309 set_config('mnet_localhost_id', $current_mnet_localhost_host->id);
311 /// Delete $old_mnet_localhost_id if exists (users will be assigned to new one below)
312 $DB->delete_records('mnet_host', array('id' => $old_mnet_localhost_id));
315 /// Compare old_mnet_all_hosts_id and current_mnet_all_hosts_host
317 if ($old_mnet_all_hosts_id != $current_mnet_all_hosts_host->id) { /// Different = problems
318 /// Update $CFG->mnet_localhost_id to correct value
319 set_config('mnet_all_hosts_id', $current_mnet_all_hosts_host->id);
321 /// Delete $old_mnet_all_hosts_id if exists
322 $DB->delete_records('mnet_host', array('id' => $old_mnet_all_hosts_id));
325 /// Finally, update all the incorrect user->mnethostid to the correct CFG->mnet_localhost_id, preventing UIX dupes
326 $hosts = $DB->get_records_menu('mnet_host', null, '', 'id, id AS id2');
327 list($in_sql, $in_params) = $DB->get_in_or_equal($hosts, SQL_PARAMS_QM, null, false);
331 WHERE u1.mnethostid $in_sql
335 WHERE u2.username = u1.username
336 AND u2.mnethostid = ?)";
338 $params = array_merge($in_params, array($current_mnet_localhost_host->id));
340 if ($rs = $DB->get_recordset_sql($sql, $params)) {
341 foreach ($rs as $rec) {
342 $DB->set_field('user', 'mnethostid', $current_mnet_localhost_host->id, array('id' => $rec->id));
343 upgrade_set_timeout(60); /// Give upgrade at least 60 more seconds
348 // fix up any host records that have incorrect ids
349 $DB->set_field_select('mnet_host', 'applicationid', $moodleapplicationid, 'id = ? or id = ?', array($current_mnet_localhost_host->id, $current_mnet_all_hosts_host->id));
354 * This function is used as part of the great navigation upgrade of 20090828
355 * It is used to clean up contexts that are unique to a blocks that are about
359 * Look at {@link blocklib.php::blocks_delete_instance()} the function from
360 * which I based this code. It is important to mention one very important fact
361 * before doing this I checked that the blocks did not override the
362 * {@link block_base::instance_delete()} method. Should this function ever
363 * be repeated check this again
365 * @link lib/db/upgrade.php
367 * @since navigation upgrade 20090828
368 * @param array $contextidarray An array of block instance context ids
371 function upgrade_cleanup_unwanted_block_contexts($contextidarray) {
374 if (!is_array($contextidarray) || count($contextidarray)===0) {
375 // Ummmm no instances?
379 $contextidstring = join(',', $contextidarray);
381 $blockcontexts = $DB->get_recordset_select('context', 'contextlevel = '.CONTEXT_BLOCK.' AND id IN ('.$contextidstring.')', array(), '', 'id, contextlevel');
382 $blockcontextids = array();
383 foreach ($blockcontexts as $blockcontext) {
384 $blockcontextids[] = $blockcontext->id;
387 if (count($blockcontextids)===0) {
388 // None of the instances have unique contexts
392 $blockcontextidsstring = join(',', $blockcontextids);
394 $DB->delete_records_select('role_assignments', 'contextid IN ('.$blockcontextidsstring.')');
395 $DB->delete_records_select('role_capabilities', 'contextid IN ('.$blockcontextidsstring.')');
396 $DB->delete_records_select('role_names', 'contextid IN ('.$blockcontextidsstring.')');
397 $DB->delete_records_select('context', 'id IN ('.$blockcontextidsstring.')');