* (bug 1938) Fix normalization of character references in link text and
[mediawiki.git] / maintenance / updaters.inc
blob5a82f563629b5255973f37c791399cf8444d6ff0
1 <?php
2 /**
3  * @package MediaWiki
4  * @subpackage Maintenance
5  */
6  
7  /** */
9 require_once 'convertLinks.inc';
10 require_once 'InitialiseMessages.inc';
12 $wgNewTables = array(
13 #            table          patch file (in maintenance/archives)
14         array( 'linkscc',       'patch-linkscc.sql' ),
15         array( 'hitcounter',    'patch-hitcounter.sql' ),
16         array( 'querycache',    'patch-querycache.sql' ),
17         array( 'objectcache',   'patch-objectcache.sql' ),
18         array( 'categorylinks', 'patch-categorylinks.sql' ),
19         array( 'logging',       'patch-logging.sql' ),
20         array( 'user_rights',   'patch-user_rights.sql' ),
21         array( 'group',   'patch-userlevels.sql' ),
24 $wgNewFields = array(
25 #           table            field             patch file (in maintenance/archives)
26         array( 'ipblocks',      'ipb_id',           'patch-ipblocks.sql' ),
27         array( 'ipblocks',      'ipb_expiry',       'patch-ipb_expiry.sql' ),
28         array( 'recentchanges', 'rc_type',          'patch-rc_type.sql' ),
29         array( 'recentchanges', 'rc_ip',            'patch-rc_ip.sql' ),
30         array( 'recentchanges', 'rc_id',            'patch-rc_id.sql' ),
31         array( 'recentchanges', 'rc_patrolled',     'patch-rc-patrol.sql' ),
32         array( 'user',          'user_real_name',   'patch-user-realname.sql' ),
33         array( 'user',          'user_token',       'patch-user_token.sql' ),
34         array( 'user_rights',   'ur_user',          'patch-rename-user_groups-and_rights.sql' ),
35         array( 'group',         'group_rights',     'patch-userlevels-rights.sql' ),
36         array( 'logging',       'log_params',       'patch-log_params.sql' ),
37         array( 'archive',       'ar_rev_id',        'patch-archive-rev_id.sql' ),
38         array( 'page',          'page_len',         'patch-page_len.sql' ),
39         array( 'revision',      'rev_deleted',      'patch-rev_deleted.sql' ),
40         array( 'image',         'img_width',        'patch-img_width.sql' ),
43 function add_table( $name, $patch ) {
44         global $wgDatabase;
45         if ( $wgDatabase->tableExists( $name ) ) {
46                 echo "...$name table already exists.\n";
47         } else {
48                 echo "Creating $name table...";
49                 dbsource( "maintenance/archives/$patch", $wgDatabase );
50                 echo "ok\n";
51         }
54 function add_field( $table, $field, $patch ) {
55         global $wgDatabase;
56         if ( !$wgDatabase->tableExists( $table ) ) {
57                 echo "...$table table does not exist, skipping new field patch\n";
58         } elseif ( $wgDatabase->fieldExists( $table, $field ) ) {
59                 echo "...have $field field in $table table.\n";
60         } else {
61                 echo "Adding $field field to table $table...";
62                 dbsource( "maintenance/archives/$patch" , $wgDatabase );
63                 echo "ok\n";
64         }
67 function do_revision_updates() {
68         global $wgSoftwareRevision;
69         if ( $wgSoftwareRevision < 1001 ) {
70                 update_passwords();
71         }
74 function update_passwords() {
75         wfDebugDieBacktrace( "This function needs to be updated or removed.\n" );
76         
77         global $wgDatabase;
78         $fname = "Update script: update_passwords()";
79         print "\nIt appears that you need to update the user passwords in your\n" .
80           "database. If you have already done this (if you've run this update\n" .
81           "script once before, for example), doing so again will make all your\n" .
82           "user accounts inaccessible, so be sure you only do this once.\n" .
83           "Update user passwords? (yes/no)";
85         $resp = readconsole();
86     if ( ! ( "Y" == $resp{0} || "y" == $resp{0} ) ) { return; }
88         $sql = "SELECT user_id,user_password FROM user";
89         $source = $wgDatabase->query( $sql, $fname );
91         while ( $row = $wgDatabase->fetchObject( $source ) ) {
92                 $id = $row->user_id;
93                 $oldpass = $row->user_password;
94                 $newpass = md5( "{$id}-{$oldpass}" );
96                 $sql = "UPDATE user SET user_password='{$newpass}' " .
97                   "WHERE user_id={$id}";
98                 $wgDatabase->query( $sql, $fname );
99         }
102 function do_interwiki_update() {
103         # Check that interwiki table exists; if it doesn't source it
104         global $wgDatabase;
105         if( $wgDatabase->tableExists( "interwiki" ) ) {
106                 echo "...already have interwiki table\n";
107                 return true;
108         }
109         echo "Creating interwiki table: ";
110         dbsource( "maintenance/archives/patch-interwiki.sql" );
111         echo "ok\n";
112         echo "Adding default interwiki definitions: ";
113         dbsource( "maintenance/interwiki.sql" );
114         echo "ok\n";
117 function do_index_update() {
118         # Check that proper indexes are in place
119         global $wgDatabase;
120         $meta = $wgDatabase->fieldInfo( "recentchanges", "rc_timestamp" );
121         if( $meta->multiple_key == 0 ) {
122                 echo "Updating indexes to 20031107: ";
123                 dbsource( "maintenance/archives/patch-indexes.sql" );
124                 echo "ok\n";
125                 return true;
126         }
127         echo "...indexes seem up to 20031107 standards\n";
128         return false;
131 function do_linkscc_1_3_update() {
132         // Update linkscc table to 1.3 schema if necessary
133         global $wgDatabase, $wgVersion;
134         if( $wgDatabase->tableExists( "linkscc" )
135                 && $wgDatabase->fieldExists( "linkscc", "lcc_title" ) ) {
136                 echo "Altering lcc_title field from linkscc table... ";
137                 dbsource( "maintenance/archives/patch-linkscc-1.3.sql", $wgDatabase );
138                 echo "ok\n";
139         } else {
140                 echo "...linkscc is up to date, or does not exist. Good.\n";
141         }
144 function do_image_name_unique_update() {
145         global $wgDatabase;
146         if( $wgDatabase->indexExists( 'image', 'PRIMARY' ) ) {
147                 echo "...image primary key already set.\n";
148         } else {
149                 echo "Making img_name the primary key... ";
150                 dbsource( "maintenance/archives/patch-image_name_primary.sql", $wgDatabase );
151                 echo "ok\n";
152         }
155 function do_watchlist_update() {
156         global $wgDatabase;
157         if( $wgDatabase->fieldExists( 'watchlist', 'wl_notificationtimestamp' ) ) {
158                 echo "ENOTIF: The watchlist table is already set up for email notification.\n";
159         } else {
160                 echo "ENOTIF: Adding wl_notificationtimestamp field for email notification management.";
161                 /* ALTER TABLE watchlist ADD (wl_notificationtimestamp varchar(14) binary NOT NULL default '0'); */
162                 dbsource( "maintenance/archives/patch-email-notification.sql", $wgDatabase );
163                 echo "ok\n";
164         }
167 function do_copy_newtalk_to_watchlist() {
168         global $wgDatabase;
169         global $wgCommandLineMode;      # this needs to be saved while getID() and getName() are called
171         if ( $wgDatabase->tableExists( 'user_newtalk' ) ) {
172                 $res = $wgDatabase->safeQuery( 'SELECT user_id, user_ip FROM !',
173                         $wgDatabase->tableName( 'user_newtalk' ) );
174                 $num_newtalks=$wgDatabase->numRows($res);
175                 echo "ENOTIF: Now converting ".$num_newtalks." user_newtalk entries to watchlist table entries ... \n";
177                 $user = new User();
178                 for ( $i = 1; $i <= $num_newtalks; $i++ ) {
179                         $wluser = $wgDatabase->fetchObject( $res );
180                         echo 'ENOTIF: <= user_newtalk: user_id='.$wluser->user_id.' user_ip='.$wluser->user_ip."\n";
181                         if ($wluser->user_id == 0) { # anonymous users ... have IP numbers as "names"
182                                 if ($user->isIP($wluser->user_ip)) { # do only if it really looks like an IP number (double checked)
183                                         $wgDatabase->replace( 'watchlist',
184                                                 array(array('wl_user','wl_namespace', 'wl_title', 'wl_notificationtimestamp' )),
185                                                   array('wl_user'                       => 0,
186                                                         'wl_namespace'                  => NS_USER_TALK,
187                                                         'wl_title'                      => $wluser->user_ip,
188                                                         'wl_notificationtimestamp'      => '19700101000000'
189                                                         ), 'updaters.inc::do_watchlist_update2'
190                                                 );
191                                         echo 'ENOTIF: ====> watchlist: user_id=0 '.$wluser->user_ip."\n";
192                                 }
193                         } else { # normal users ... have user_ids
194                                 $user->setID($wluser->user_id);
195                                 $wgDatabase->replace( 'watchlist',
196                                         array(array('wl_user','wl_namespace', 'wl_title', 'wl_notificationtimestamp' )),
197                                           array('wl_user'                       => $user->getID(),
198                                                 'wl_namespace'                  => NS_USER_TALK,
199                                                 'wl_title'                      => $user->getName(),
200                                                 'wl_notificationtimestamp'      => '19700101000000'
201                                                 ), 'updaters.inc::do_watchlist_update3'
202                                         );
203                                 echo 'ENOTIF: ====> watchlist: user_id='.$user->getID().' '.$user->getName()."\n";
204                         }
205                 }
206                 echo "ENOTIF: The watchlist table has got the former user_newtalk entries.\n";
207                 dbsource( "maintenance/archives/patch-drop-user_newtalk.sql", $wgDatabase );
208                 echo "ENOTIF: Deleting the user_newtalk table as its entries are now in the watchlist table.\n";
209         } else {
210                 echo "ENOTIF: No user_newtalk table found. Nothing to convert to watchlist table entries.\n";
211         }
215 function do_user_update() {
216         global $wgDatabase;
217         if( $wgDatabase->fieldExists( 'user', 'user_emailauthenticationtimestamp' ) ) {
218                 echo "EAUTHENT: The user table is already set up for email authentication.\n";
219         } else {
220                 echo "EAUTHENT: Adding user_emailauthenticationtimestamp field for email authentication management.";
221                 /* ALTER TABLE user ADD (user_emailauthenticationtimestamp varchar(14) binary NOT NULL default '0'); */
222                 dbsource( "maintenance/archives/patch-email-authentication.sql", $wgDatabase );
223                 echo "ok\n";
224         }
227 # Assumes that the group table has been added.
228 function do_group_update() {
229         global $wgDatabase;
230         $res = $wgDatabase->safeQuery( 'SELECT COUNT(*) AS c FROM !',
231                 $wgDatabase->tableName( 'group' ) );
232         $row = $wgDatabase->fetchObject( $res );
233         $wgDatabase->freeResult( $res );
234         if( $row->c == 0 ) {
235                 echo "Adding default group definitions... ";
236                 dbsource( "maintenance/archives/patch-userlevels-defaultgroups.sql", $wgDatabase );
237                 echo "ok\n";
238         } else {
239                 echo "...group definitions already in place.\n";
240                 $res = $wgDatabase->safeQuery( "SELECT COUNT(*) AS n FROM !
241                                                  WHERE group_name IN ('Sysops','Bureaucrat')
242                                                    AND group_rights NOT LIKE '%sysop%'",
243                                                $wgDatabase->tableName( 'group' ) );
244                 $row = $wgDatabase->fetchObject( $res );
245                 $wgDatabase->freeResult( $res );
246                 if( $row->n ) {
247                         echo "Fixing sysops group permissions and add group editing right... ";
248                         dbsource( "maintenance/archives/patch-group-sysopfix.sql", $wgDatabase );
249                         echo "ok\n";
250                 } else {
251                         echo "...sysop group permissions look ok.\n";
252                 }
253         }
257  * 1.4 betas were missing the 'binary' marker from logging.log_title,
258  * which causes a collation mismatch error on joins in MySQL 4.1.
259  */
260 function do_logging_encoding() {
261         global $wgDatabase;
262         $logging = $wgDatabase->tableName( 'logging' );
263         $res = $wgDatabase->query( "SELECT log_title FROM $logging LIMIT 0" );
264         $flags = explode( ' ', mysql_field_flags( $res, 0 ) );
265         $wgDatabase->freeResult( $res );
266         
267         if( in_array( 'binary', $flags ) ) {
268                 echo "Logging table has correct title encoding.\n";
269         } else {
270                 echo "Fixing title encoding on logging table... ";
271                 dbsource( 'maintenance/archives/patch-logging-title.sql', $wgDatabase );
272                 echo "ok\n";
273         }
276 function do_schema_restructuring() {
277         global $wgDatabase;
278         $fname="do_schema_restructuring";
279         if ( $wgDatabase->tableExists( 'page' ) ) {
280                 echo "...page table already exists.\n";
281         } else {
282                 echo "...converting from cur/old to page/revision/text DB structure.\n"; flush();
283                 echo "......checking for duplicate entries.\n"; flush();
284                 
285                 extract( $wgDatabase->tableNames( 'cur', 'old', 'page', 'revision', 'text' ) );
287                 $rows = $wgDatabase->query( "SELECT cur_title, cur_namespace, COUNT(cur_namespace) AS c
288                                 FROM $cur GROUP BY cur_title, cur_namespace HAVING c>1", $fname );
290                 if ( $wgDatabase->numRows( $rows ) > 0 ) {
291                         echo "......<b>Found duplicate entries</b>\n";
292                         echo ( sprintf( "<b>      %-60s %3s %5s</b>\n", 'Title', 'NS', 'Count' ) );
293                         while ( $row = $wgDatabase->fetchObject( $rows ) ) {
294                                 if ( ! isset( $duplicate[$row->cur_namespace] ) ) {
295                                         $duplicate[$row->cur_namespace] = array();
296                                 }
297                                 $duplicate[$row->cur_namespace][] = $row->cur_title;
298                                 echo ( sprintf( "      %-60s %3s %5s\n", $row->cur_title, $row->cur_namespace, $row->c ) );
299                         }
300                         $sql = "SELECT cur_title, cur_namespace, cur_id, cur_timestamp FROM $cur WHERE ";
301                         $firstCond = true; 
302                         foreach ( $duplicate as $ns => $titles ) {
303                                 if ( $firstCond ) {
304                                         $firstCond = false;
305                                 } else {
306                                         $sql .= ' OR ';
307                                 }
308                                 $sql .= "( cur_namespace = {$ns} AND cur_title in (";
309                                 $first = true;
310                                 foreach ( $titles as $t ) {
311                                         if ( $first ) {
312                                                 $sql .= $wgDatabase->addQuotes( $t );
313                                                 $first = false;
314                                         } else {
315                                                 $sql .= ', ' . $wgDatabase->addQuotes( $t );
316                                         }
317                                 }
318                                 $sql .= ") ) \n";
319                         }
320                         # By sorting descending, the most recent entry will be the first in the list.
321                         # All following entries will be deleted by the next while-loop.
322                         $sql .= 'ORDER BY cur_namespace, cur_title, cur_timestamp DESC';
324                         $rows = $wgDatabase->query( $sql, $fname );
326                         $prev_title = $prev_namespace = false;
327                         $deleteId = array();
329                         while ( $row = $wgDatabase->fetchObject( $rows ) ) {
330                                 if ( $prev_title == $row->cur_title && $prev_namespace == $row->cur_namespace ) {
331                                         $deleteId[] = $row->cur_id;
332                                 }
333                                 $prev_title     = $row->cur_title;
334                                 $prev_namespace = $row->cur_namespace;
335                         }
336                         $sql = "DELETE FROM $cur WHERE cur_id IN ( " . join( ',', $deleteId ) . ')';
337                         $rows = $wgDatabase->query( $sql, $fname );
338                         echo "......<b>Deleted</b> ".$wgDatabase->affectedRows()." records.\n";
339                 }
340                 
342                 echo "......Creating tables.\n";
343                 $wgDatabase->query(" CREATE TABLE $page (
344                         page_id int(8) unsigned NOT NULL auto_increment,
345                         page_namespace tinyint NOT NULL,
346                         page_title varchar(255) binary NOT NULL,
347                         page_restrictions tinyblob NOT NULL default '',
348                         page_counter bigint(20) unsigned NOT NULL default '0',
349                         page_is_redirect tinyint(1) unsigned NOT NULL default '0',
350                         page_is_new tinyint(1) unsigned NOT NULL default '0',
351                         page_random real unsigned NOT NULL,
352                         page_touched char(14) binary NOT NULL default '',
353                         page_latest int(8) unsigned NOT NULL,
354                         page_len int(8) unsigned NOT NULL,
356                         PRIMARY KEY page_id (page_id),
357                         UNIQUE INDEX name_title (page_namespace,page_title),
358                         INDEX (page_random),
359                         INDEX (page_len)
360                         )", $fname );
361                 $wgDatabase->query("CREATE TABLE $revision (
362                         rev_id int(8) unsigned NOT NULL auto_increment,
363                         rev_page int(8) unsigned NOT NULL,
364                         rev_comment tinyblob NOT NULL default '',
365                         rev_user int(5) unsigned NOT NULL default '0',
366                         rev_user_text varchar(255) binary NOT NULL default '',
367                         rev_timestamp char(14) binary NOT NULL default '',
368                         rev_minor_edit tinyint(1) unsigned NOT NULL default '0',
369                         rev_deleted tinyint(1) unsigned NOT NULL default '0',
370   
371                         PRIMARY KEY rev_page_id (rev_page, rev_id),
372                         UNIQUE INDEX rev_id (rev_id),
373                         INDEX rev_timestamp (rev_timestamp),
374                         INDEX page_timestamp (rev_page,rev_timestamp),
375                         INDEX user_timestamp (rev_user,rev_timestamp),
376                         INDEX usertext_timestamp (rev_user_text,rev_timestamp)
377                         )", $fname );
379                 echo "......Locking tables.\n";
380                 $wgDatabase->query( "LOCK TABLES $page WRITE, $revision WRITE, $old WRITE, $cur WRITE", $fname );
382                 $maxold = $wgDatabase->selectField( 'old', 'max(old_id)', '', $fname );
383                 echo "......maxold is {$maxold}\n";
385                 echo "......Moving text from cur.\n";
386                 $wgDatabase->query( "INSERT INTO $old (old_namespace, old_title, old_text, old_comment, old_user, old_user_text,
387                                 old_timestamp, old_minor_edit, old_flags)
388                         SELECT cur_namespace, cur_title, cur_text, cur_comment, cur_user, cur_user_text, cur_timestamp, cur_minor_edit,''
389                         FROM $cur", $fname );
391                 echo "......Setting up revision table.\n";
392                 $wgDatabase->query( "INSERT INTO $revision (rev_id, rev_page, rev_comment, rev_user, rev_user_text, rev_timestamp,
393                                 rev_minor_edit)
394                         SELECT old_id, cur_id, old_comment, old_user, old_user_text,
395                                 old_timestamp, old_minor_edit
396                         FROM $old,$cur WHERE old_namespace=cur_namespace AND old_title=cur_title", $fname );
398                 echo "......Setting up page table.\n";
399                 $wgDatabase->query( "INSERT INTO $page (page_id, page_namespace, page_title, page_restrictions, page_counter,
400                                 page_is_redirect, page_is_new, page_random, page_touched, page_latest, page_len)
401                         SELECT cur_id, cur_namespace, cur_title, cur_restrictions, cur_counter, cur_is_redirect, cur_is_new,
402                                 cur_random, cur_touched, rev_id, LENGTH(cur_text)
403                         FROM $cur,$revision
404                         WHERE cur_id=rev_page AND rev_timestamp=cur_timestamp AND rev_id > {$maxold}", $fname );
406                 echo "......Unlocking tables.\n";
407                 $wgDatabase->query( "UNLOCK TABLES", $fname );
409                 echo "......Renaming old.\n";
410                 $wgDatabase->query( "ALTER TABLE $old RENAME TO $text", $fname );
411                 echo "...done.\n";
412         }
415 function do_inverse_timestamp() {
416         global $wgDatabase;
417         $fname="do_schema_restructuring";
418         if( $wgDatabase->fieldExists( 'revision', 'inverse_timestamp' ) ) {
419                 echo "Removing revision.inverse_timestamp and fixing indexes... ";
420                 dbsource( 'maintenance/archives/patch-inverse_timestamp.sql', $wgDatabase );
421                 echo "ok\n";
422         } else {
423                 echo "revision timestamp indexes already up to 2005-03-13\n";
424         }
427 function do_text_id() {
428         global $wgDatabase;
429         if( $wgDatabase->fieldExists( 'revision', 'rev_text_id' ) ) {
430                 echo "...rev_text_id already in place.\n";
431         } else {
432                 echo "Adding rev_text_id field... ";
433                 dbsource( 'maintenance/archives/patch-rev_text_id.sql', $wgDatabase );
434                 echo "ok\n";
435         }
439 function do_all_updates() {
440         global $wgNewTables, $wgNewFields;
441         
442         # Add missing tables
443         foreach ( $wgNewTables as $tableRecord ) {
444                 add_table( $tableRecord[0], $tableRecord[1] );
445                 flush();
446         }
448         # Add missing fields
449         foreach ( $wgNewFields as $fieldRecord ) {
450                 add_field( $fieldRecord[0], $fieldRecord[1], $fieldRecord[2] );
451                 flush();
452         }
453         
454         # Add default group data
455         do_group_update(); flush();
457         # Do schema updates which require special handling
458         do_interwiki_update(); flush();
459         do_index_update(); flush();
460         do_linkscc_1_3_update(); flush();
461         convertLinks(); flush();
462         do_image_name_unique_update(); flush();
463         do_watchlist_update(); flush();
464         do_user_update(); flush();
465         do_copy_newtalk_to_watchlist(); flush();
466         do_logging_encoding(); flush();
467         
468         do_schema_restructuring(); flush();
469         do_inverse_timestamp(); flush();
470         do_text_id(); flush();
472         initialiseMessages(); flush();