MDL-13766, fixed repository instance name and configplugin language string
[moodle.git] / lib / db / upgradelib.php
1 <?php
3 /*
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!)
8  */
10 function upgrade_fix_category_depths() {
11     global $CFG, $DB;
13     // first fix incorrect parents
14     $sql = "SELECT c.id
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) {
19             $cat->depth  = 1;
20             $cat->path   = '/'.$cat->id;
21             $cat->parent = 0;
22             $DB->update_record('course_categories', $cat);
23         }
24         $rs->close();
25     }
27     // now add path and depth to top level categories
28     $sql = "UPDATE {course_categories}
29                SET depth = 1, path = ".$DB->sql_concat("'/'", "id")."
30              WHERE parent = 0";
31     $DB->execute($sql);
33     // now fix all other levels - slow but works in all supported dbs
34     $parentdepth = 1;
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);
44             }
45             $rs->close();
46         }
47         $parentdepth++;
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');
51             break;
52         }
53     }
54 }
56 /**
57  * Moves all course files except the moddata to new file storage
58  *
59  * Unfortunately this function uses core file related functions - it might be necessary to tweak it if something changes there :-(
60  */
61 function upgrade_migrate_files_courses() {
62     global $DB, $CFG;
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');
69     $i = 0;
70     foreach ($rs as $course) {
71         $i++;
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.");
76     }
77     $rs->close();
79     return true;
80 }
82 /**
83  * Internal function - do not use directly
84  */
85 function upgrade_migrate_user_icons() {
86     global $CFG, $OUTPUT, $DB;
88     $fs = get_file_storage();
90     $icon = array('component'=>'user', 'filearea'=>'icon', 'itemid'=>0, 'filepath'=>'/');
92     $count = $DB->count_records('user', array('picture'=>1, 'deleted'=>0));
93     $pbar = new progress_bar('migratecoursefiles', 500, true);
95     $rs = $DB->get_recordset('user', array('picture'=>1, 'deleted'=>0), 'id ASC', 'id, picture');
96     $i = 0;
97     foreach ($rs as $user) {
98         $i++;
99         upgrade_set_timeout(60); /// Give upgrade at least 60 more seconds
100         $pbar->update($i, $count, "Migrated user icons $i/$count.");
102         $context = get_context_instance(CONTEXT_USER, $user->id);
104         if ($fs->file_exists($context->id, 'user', 'icon', 0, '/', 'f1.jpg')) {
105             // already converted!
106             continue;
107         }
109         $level1 = floor($user->id / 1000) * 1000;
110         $userdir = "$CFG->dataroot/user/$level1/$user->id";
111         if (!file_exists("$userdir/f1.jpg") or !file_exists("$userdir/f2.jpg")) {
112             $userdir = "$CFG->dataroot/users/$user->id";
113             if (!file_exists("$userdir/f1.jpg") or !file_exists("$userdir/f2.jpg")) {
114                 // no image found, sorry
115                 $user->picture = 0;
116                 $DB->update_record('user', $user);
117                 continue;
118             }
119         }
121         $icon['contextid'] = $context->id;
122         $icon['filename']  = 'f1.jpg';
123         $fs->create_file_from_pathname($icon, "$userdir/f1.jpg");
124         $icon['filename']  = 'f2.jpg';
125         $fs->create_file_from_pathname($icon, "$userdir/f2.jpg");
126     }
127     $rs->close();
129     // purge all old user image dirs
130     remove_dir("$CFG->dataroot/user");
131     remove_dir("$CFG->dataroot/users");
134 /**
135  * Internal function - do not use directly
136  */
137 function upgrade_migrate_group_icons() {
138     global $CFG, $OUTPUT, $DB;
140     $fs = get_file_storage();
142     $icon = array('component'=>'group', 'filearea'=>'icon', 'filepath'=>'/');
144     $count = $DB->count_records('groups', array('picture'=>1));
145     $pbar = new progress_bar('migrategroupfiles', 500, true);
147     $rs = $DB->get_recordset('groups', array('picture'=>1), 'courseid ASC', 'id, picture, courseid');
148     $i = 0;
149     foreach ($rs as $group) {
150         $i++;
151         upgrade_set_timeout(60); /// Give upgrade at least 60 more seconds
152         $pbar->update($i, $count, "Migrated group icons  $i/$count.");
154         $context = get_context_instance(CONTEXT_COURSE, $group->courseid);
156         if ($fs->file_exists($context->id, 'group', 'icon', $group->id, '/', 'f1.jpg')) {
157             // already converted!
158             continue;
159         }
161         $groupdir = "$CFG->dataroot/groups/$group->id";
162         if (!file_exists("$groupdir/f1.jpg") or !file_exists("$groupdir/f2.jpg")) {
163             // no image found, sorry
164             $group->picture = 0;
165             $DB->update_record('groups', $group);
166             continue;
167         }
169         $icon['contextid'] = $context->id;
170         $icon['itemid']    = $group->id;
171         $icon['filename']  = 'f1.jpg';
172         $fs->create_file_from_pathname($icon, "$groupdir/f1.jpg");
173         $icon['filename']  = 'f2.jpg';
174         $fs->create_file_from_pathname($icon, "$groupdir/f2.jpg");
175     }
176     $rs->close();
178     // purge all old group image dirs
179     remove_dir("$CFG->dataroot/groups");
182 /**
183  * Internal function - do not use directly
184  */
185 function upgrade_migrate_files_course($context, $path, $delete) {
186     global $CFG, $OUTPUT;
188     $fullpathname = $CFG->dataroot.'/'.$context->instanceid.$path;
189     if (!file_exists($fullpathname)) {
190         return;
191     }
192     $items = new DirectoryIterator($fullpathname);
193     $fs = get_file_storage();
195     foreach ($items as $item) {
196         if ($item->isDot()) {
197             continue;
198         }
200         if ($item->isLink()) {
201             // do not delete symbolic links or its children
202             $delete_this = false;
203         } else {
204             $delete_this = $delete;
205         }
207         if (strpos($path, '/backupdata/') === 0) {
208             $component = 'backup';
209             $filearea  = 'course';
210             $filepath  = substr($path, strlen('/backupdata'));
211         } else {
212             $component = 'course';
213             $filearea  = 'legacy';
214             $filepath  = $path;
215         }
217         if ($item->isFile()) {
218             if (!$item->isReadable()) {
219                 echo $OUTPUT->notification(" File not readable, skipping: ".$fullpathname.$item->getFilename());
220                 continue;
221             }
223             $filepath = clean_param($filepath, PARAM_PATH);
224             $filename = clean_param($item->getFilename(), PARAM_FILE);
226             if ($filename === '') {
227                 //unsupported chars, sorry
228                 continue;
229             }
231             if (!$fs->file_exists($context->id, $component, $filearea, '0', $filepath, $filename)) {
232                 $file_record = array('contextid'=>$context->id, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>0, 'filepath'=>$filepath, 'filename'=>$filename,
233                                      'timecreated'=>$item->getCTime(), 'timemodified'=>$item->getMTime());
234                 if ($fs->create_file_from_pathname($file_record, $fullpathname.$item->getFilename())) {
235                     if ($delete_this) {
236                         @unlink($fullpathname.$item->getFilename());
237                     }
238                 }
239             }
241         } else {
242             if ($path == '/' and $item->getFilename() == 'moddata') {
243                 continue; // modules are responsible
244             }
246             $dirname = clean_param($item->getFilename(), PARAM_PATH);
247             if ($dirname === '') {
248                 //unsupported chars, sorry
249                 continue;
250             }
251             $filepath = ($filepath.$dirname.'/');
252             if ($filepath !== '/backupdata/') {
253                 $fs->create_directory($context->id, $component, $filearea, 0, $filepath);
254             }
256             //migrate recursively all subdirectories
257             upgrade_migrate_files_course($context, $path.$item->getFilename().'/', $delete_this);
258             if ($delete_this) {
259                 // delete dir if empty
260                 @rmdir($fullpathname.$item->getFilename());
261             }
262         }
263     }
264     unset($items); //release file handles
267 /**
268  * Moves all block attachments
269  *
270  * Unfortunately this function uses core file related functions - it might be necessary to tweak it if something changes there :-(
271  */
272 function upgrade_migrate_files_blog() {
273     global $DB, $CFG, $OUTPUT;
275     $fs = get_file_storage();
277     $count = $DB->count_records_select('post', "module='blog' AND attachment IS NOT NULL AND attachment <> '1'");
279     if ($rs = $DB->get_recordset_select('post', "module='blog' AND attachment IS NOT NULL AND attachment <> '1'")) {
281         upgrade_set_timeout(60*20); // set up timeout, may also abort execution
283         $pbar = new progress_bar('migrateblogfiles', 500, true);
285         $i = 0;
286         foreach ($rs as $entry) {
287             $i++;
288             $pathname = "$CFG->dataroot/blog/attachments/$entry->id/$entry->attachment";
289             if (!file_exists($pathname)) {
290                 $entry->attachment = NULL;
291                 $DB->update_record('post', $entry);
292                 continue;
293             }
295             $filename = clean_param($entry->attachment, PARAM_FILE);
296             if ($filename === '') {
297                 // weird file name, ignore it
298                 $entry->attachment = NULL;
299                 $DB->update_record('post', $entry);
300                 continue;
301             }
303             if (!is_readable($pathname)) {
304                 echo $OUTPUT->notification(" File not readable, skipping: ".$pathname);
305                 continue;
306             }
308             if (!$fs->file_exists(SYSCONTEXTID, 'blog', 'attachment', $entry->id, '/', $filename)) {
309                 $file_record = array('contextid'=>SYSCONTEXTID, 'component'=>'blog', 'filearea'=>'attachment', 'itemid'=>$entry->id, 'filepath'=>'/', 'filename'=>$filename,
310                                      'timecreated'=>filectime($pathname), 'timemodified'=>filemtime($pathname), 'userid'=>$entry->userid);
311                 $fs->create_file_from_pathname($file_record, $pathname);
312             }
313             @unlink($pathname);
314             @rmdir("$CFG->dataroot/blog/attachments/$entry->id/");
316             $entry->attachment = 1; // file name not needed there anymore
317             $DB->update_record('post', $entry);
318             $pbar->update($i, $count, "Migrated blog attachments - $i/$count.");
319         }
320         $rs->close();
321     }
323     @rmdir("$CFG->dataroot/blog/attachments/");
324     @rmdir("$CFG->dataroot/blog/");
327 /**
328  * This function will fix the status of the localhost/all records in the mnet_host table
329  * checking they exist and adding them if missing + redefine CFG->mnet_localhost_id  and
330  * CFG->mnet_all_hosts_id if needed + update all the users having non-existent mnethostid
331  * to correct CFG->mnet_localhost_id
332  *
333  * Implemented because, at some point, specially in old installations upgraded along
334  * multiple versions, sometimes the stuff above has ended being inconsistent, causing
335  * problems here and there (noticeably in backup/restore). MDL-16879
336  */
337 function upgrade_fix_incorrect_mnethostids() {
339     global $CFG, $DB;
341 /// Get current $CFG/mnet_host records
342     $old_mnet_localhost_id = !empty($CFG->mnet_localhost_id) ? $CFG->mnet_localhost_id : 0;
343     $old_mnet_all_hosts_id = !empty($CFG->mnet_all_hosts_id) ? $CFG->mnet_all_hosts_id : 0;
345     $current_mnet_localhost_host = $DB->get_record('mnet_host', array('wwwroot' => $CFG->wwwroot)); /// By wwwroot
346     $current_mnet_all_hosts_host = $DB->get_record_select('mnet_host', $DB->sql_isempty('mnet_host', 'wwwroot', false, false)); /// By empty wwwroot
348     if (!$moodleapplicationid = $DB->get_field('mnet_application', 'id', array('name' => 'moodle'))) {
349         $m = (object)array(
350             'name'              => 'moodle',
351             'display_name'      => 'Moodle',
352             'xmlrpc_server_url' => '/mnet/xmlrpc/server.php',
353             'sso_land_url'      => '/auth/mnet/land.php',
354             'sso_jump_url'      => '/auth/mnet/jump.php',
355         );
356         $moodleapplicationid = $DB->insert_record('mnet_application', $m);
357     }
359 /// Create localhost_host if necessary (pretty improbable but better to be 100% in the safe side)
360 /// Code stolen from mnet_environment->init
361     if (!$current_mnet_localhost_host) {
362         $current_mnet_localhost_host                     = new stdClass();
363         $current_mnet_localhost_host->wwwroot            = $CFG->wwwroot;
364         $current_mnet_localhost_host->ip_address         = '';
365         $current_mnet_localhost_host->public_key         = '';
366         $current_mnet_localhost_host->public_key_expires = 0;
367         $current_mnet_localhost_host->last_connect_time  = 0;
368         $current_mnet_localhost_host->last_log_id        = 0;
369         $current_mnet_localhost_host->deleted            = 0;
370         $current_mnet_localhost_host->name               = '';
371         $current_mnet_localhost_host->applicationid      = $moodleapplicationid;
372     /// Get the ip of the server
373         if (empty($_SERVER['SERVER_ADDR'])) {
374         /// SERVER_ADDR is only returned by Apache-like webservers
375             $count = preg_match("@^(?:http[s]?://)?([A-Z0-9\-\.]+).*@i", $current_mnet_localhost_host->wwwroot, $matches);
376             $my_hostname = $count > 0 ? $matches[1] : false;
377             $my_ip       = gethostbyname($my_hostname);  // Returns unmodified hostname on failure. DOH!
378             if ($my_ip == $my_hostname) {
379                 $current_mnet_localhost_host->ip_address = 'UNKNOWN';
380             } else {
381                 $current_mnet_localhost_host->ip_address = $my_ip;
382             }
383         } else {
384             $current_mnet_localhost_host->ip_address = $_SERVER['SERVER_ADDR'];
385         }
386         $current_mnet_localhost_host->id = $DB->insert_record('mnet_host', $current_mnet_localhost_host, true);
387     }
389 /// Create all_hosts_host if necessary (pretty improbable but better to be 100% in the safe side)
390 /// Code stolen from mnet_environment->init
391     if (!$current_mnet_all_hosts_host) {
392         $current_mnet_all_hosts_host                     = new stdClass();
393         $current_mnet_all_hosts_host->wwwroot            = '';
394         $current_mnet_all_hosts_host->ip_address         = '';
395         $current_mnet_all_hosts_host->public_key         = '';
396         $current_mnet_all_hosts_host->public_key_expires = 0;
397         $current_mnet_all_hosts_host->last_connect_time  = 0;
398         $current_mnet_all_hosts_host->last_log_id        = 0;
399         $current_mnet_all_hosts_host->deleted            = 0;
400         $current_mnet_all_hosts_host->name               = 'All Hosts';
401         $current_mnet_all_hosts_host->applicationid      = $moodleapplicationid;
402         $current_mnet_all_hosts_host->id                 = $DB->insert_record('mnet_host', $current_mnet_all_hosts_host, true);
403     }
405 /// Compare old_mnet_localhost_id and current_mnet_localhost_host
407     if ($old_mnet_localhost_id != $current_mnet_localhost_host->id) { /// Different = problems
408     /// Update $CFG->mnet_localhost_id to correct value
409         set_config('mnet_localhost_id', $current_mnet_localhost_host->id);
411     /// Delete $old_mnet_localhost_id if exists (users will be assigned to new one below)
412         $DB->delete_records('mnet_host', array('id' => $old_mnet_localhost_id));
413     }
415 /// Compare old_mnet_all_hosts_id and current_mnet_all_hosts_host
417     if ($old_mnet_all_hosts_id != $current_mnet_all_hosts_host->id) { /// Different = problems
418     /// Update $CFG->mnet_localhost_id to correct value
419         set_config('mnet_all_hosts_id', $current_mnet_all_hosts_host->id);
421     /// Delete $old_mnet_all_hosts_id if exists
422         $DB->delete_records('mnet_host', array('id' => $old_mnet_all_hosts_id));
423     }
425 /// Finally, update all the incorrect user->mnethostid to the correct CFG->mnet_localhost_id, preventing UIX dupes
426     $hosts = $DB->get_records_menu('mnet_host', null, '', 'id, id AS id2');
427     list($in_sql, $in_params) = $DB->get_in_or_equal($hosts, SQL_PARAMS_QM, null, false);
429     $sql = "SELECT id
430             FROM {user} u1
431             WHERE u1.mnethostid $in_sql
432               AND NOT EXISTS (
433                   SELECT 'x'
434                     FROM {user} u2
435                    WHERE u2.username = u1.username
436                      AND u2.mnethostid = ?)";
438     $params = array_merge($in_params, array($current_mnet_localhost_host->id));
440     if ($rs = $DB->get_recordset_sql($sql, $params)) {
441         foreach ($rs as $rec) {
442             $DB->set_field('user', 'mnethostid', $current_mnet_localhost_host->id, array('id' => $rec->id));
443             upgrade_set_timeout(60); /// Give upgrade at least 60 more seconds
444         }
445         $rs->close();
446     }
448     // fix up any host records that have incorrect ids
449     $DB->set_field_select('mnet_host', 'applicationid', $moodleapplicationid, 'id = ? or id = ?', array($current_mnet_localhost_host->id, $current_mnet_all_hosts_host->id));
453 /**
454  * This function is used as part of the great navigation upgrade of 20090828
455  * It is used to clean up contexts that are unique to a blocks that are about
456  * to be removed.
457  *
458  *
459  * Look at {@link blocklib.php::blocks_delete_instance()} the function from
460  * which I based this code. It is important to mention one very important fact
461  * before doing this I checked that the blocks did not override the
462  * {@link block_base::instance_delete()} method. Should this function ever
463  * be repeated check this again
464  *
465  * @link lib/db/upgrade.php
466  *
467  * @since navigation upgrade 20090828
468  * @param array $contextidarray An array of block instance context ids
469  * @return void
470  */
471 function upgrade_cleanup_unwanted_block_contexts($contextidarray) {
472     global $DB;
474     if (!is_array($contextidarray) || count($contextidarray)===0) {
475         // Ummmm no instances?
476         return;
477     }
479     $contextidstring = join(',', $contextidarray);
481     $blockcontexts = $DB->get_recordset_select('context', 'contextlevel = '.CONTEXT_BLOCK.' AND id IN ('.$contextidstring.')', array(), '', 'id, contextlevel');
482     $blockcontextids = array();
483     foreach ($blockcontexts as $blockcontext) {
484         $blockcontextids[] = $blockcontext->id;
485     }
487     if (count($blockcontextids)===0) {
488         // None of the instances have unique contexts
489         return;
490     }
492     $blockcontextidsstring = join(',', $blockcontextids);
494     $DB->delete_records_select('role_assignments', 'contextid IN ('.$blockcontextidsstring.')');
495     $DB->delete_records_select('role_capabilities', 'contextid IN ('.$blockcontextidsstring.')');
496     $DB->delete_records_select('role_names', 'contextid IN ('.$blockcontextidsstring.')');
497     $DB->delete_records_select('context', 'id IN ('.$blockcontextidsstring.')');