| 1 | #!/usr/bin/perl -w |
|---|
| 2 | # mt-db-convert.cgi: converting your MT data between multiple db engines |
|---|
| 3 | # This is a derived work from the following: |
|---|
| 4 | # |
|---|
| 5 | # Copyright 2001-2005 Six Apart. This code cannot be redistributed without |
|---|
| 6 | # permission from www.movabletype.org. |
|---|
| 7 | # |
|---|
| 8 | # $Id: mt-db2sql.cgi 12446 2005-05-25 21:32:39Z bchoate $ |
|---|
| 9 | use strict; |
|---|
| 10 | |
|---|
| 11 | my $VERSION = '0.12'; |
|---|
| 12 | |
|---|
| 13 | my($MT_DIR); |
|---|
| 14 | BEGIN { |
|---|
| 15 | if ($0 =~ m!(.*[/\\])!) { |
|---|
| 16 | $MT_DIR = $1; |
|---|
| 17 | } else { |
|---|
| 18 | $MT_DIR = './'; |
|---|
| 19 | } |
|---|
| 20 | unshift @INC, $MT_DIR . 'lib'; |
|---|
| 21 | unshift @INC, $MT_DIR . 'extlib'; |
|---|
| 22 | } |
|---|
| 23 | |
|---|
| 24 | local $| = 1; |
|---|
| 25 | print "Content-Type: text/html\n\n"; |
|---|
| 26 | print <<HTML; |
|---|
| 27 | <html> |
|---|
| 28 | <head><title>mt-db-convert $VERSION: Converting your MT data</title></head> |
|---|
| 29 | <body> |
|---|
| 30 | <p><strong>mt-db-convert $VERSION: Coverting your MT data between DB engines</strong></p> |
|---|
| 31 | HTML |
|---|
| 32 | |
|---|
| 33 | my @CLASSES = qw( MT::Author MT::Blog MT::Category MT::Comment MT::Entry |
|---|
| 34 | MT::IPBanList MT::Log MT::Notification MT::Permission |
|---|
| 35 | MT::Placement MT::Template MT::TemplateMap MT::Trackback |
|---|
| 36 | MT::TBPing ); |
|---|
| 37 | |
|---|
| 38 | use File::Spec; |
|---|
| 39 | |
|---|
| 40 | my @DBSPECS = qw( DataSource ObjectDriver Database DBUser DBHost DBPassword ); |
|---|
| 41 | |
|---|
| 42 | require MT; |
|---|
| 43 | my $mt = MT->new(Config => $MT_DIR . 'mt.cfg', Directory => $MT_DIR) |
|---|
| 44 | or die MT->errstr; |
|---|
| 45 | my $cfg = $mt->{cfg}; |
|---|
| 46 | |
|---|
| 47 | use CGI; |
|---|
| 48 | my $q = CGI->new; |
|---|
| 49 | |
|---|
| 50 | my (%src, %dst); |
|---|
| 51 | foreach (@DBSPECS) { |
|---|
| 52 | $src{$_} = $q->param('src_' . $_) || ''; |
|---|
| 53 | $dst{$_} = $q->param('dst_' . $_) || ''; |
|---|
| 54 | } |
|---|
| 55 | |
|---|
| 56 | # If src and dst dbspecs not given |
|---|
| 57 | if (!$src{ObjectDriver} || ($src{ObjectDriver} eq 'DBM' && !$src{DataSource}) || |
|---|
| 58 | !$dst{ObjectDriver} || ($dst{ObjectDriver} eq 'DBM' && !$dst{DataSource})) { |
|---|
| 59 | |
|---|
| 60 | $src{$_} ||= $cfg->$_() foreach (@DBSPECS); |
|---|
| 61 | |
|---|
| 62 | my %src_selected = ('DBM' => '', 'DBI::mysql' => '', 'DBI::postgres' => '', 'DBI::sqlite' => ''); |
|---|
| 63 | my %dst_selected = ('DBM' => '', 'DBI::mysql' => '', 'DBI::postgres' => '', 'DBI::sqlite' => ''); |
|---|
| 64 | $src_selected{$src{ObjectDriver}} = 'selected' if $src{ObjectDriver}; |
|---|
| 65 | $dst_selected{$dst{ObjectDriver}} = 'selected' if $dst{ObjectDriver}; |
|---|
| 66 | |
|---|
| 67 | my $script_name = ($0 =~ m$!) ? $1 : 'mt-db-convert.cgi'; |
|---|
| 68 | |
|---|
| 69 | print <<HTML; |
|---|
| 70 | <p>Please fill the following:</p> |
|---|
| 71 | |
|---|
| 72 | <form method="post" action="$script_name"> |
|---|
| 73 | <style>fieldset { width: 40%; float: left; }</style> |
|---|
| 74 | <fieldset> |
|---|
| 75 | <legend>Source DB Configuration</legend> |
|---|
| 76 | <dl> |
|---|
| 77 | <dt>DataSource: (Required for BerkeleyDB)</dt> |
|---|
| 78 | <dd><input name="src_DataSource" type="text" value="$src{DataSource}" /></dd> |
|---|
| 79 | <dt>ObjectDriver:</dt> |
|---|
| 80 | <dd><select name="src_ObjectDriver"> |
|---|
| 81 | <option value="">Select a driver</option> |
|---|
| 82 | <option value="DBM" $src_selected{'DBM'}>BerkeleyDB</option> |
|---|
| 83 | <option value="DBI::mysql" $src_selected{'DBI::mysql'}>MySQL</option> |
|---|
| 84 | <option value="DBI::postgres" $src_selected{'DBI::postgres'}>PostgreSQL</option> |
|---|
| 85 | <option value="DBI::sqlite" $src_selected{'DBI::sqlite'}>SQLite</option> |
|---|
| 86 | </select></dd> |
|---|
| 87 | <dt>Database:</dt> |
|---|
| 88 | <dd><input name="src_Database" type="text" value="$src{Database}" /></dd> |
|---|
| 89 | <dt>DBUser:</dt> |
|---|
| 90 | <dd><input name="src_DBUser" type="text" value="$src{DBUser}" /></dd> |
|---|
| 91 | <dt>DBHost:</dt> |
|---|
| 92 | <dd><input name="src_DBHost" type="text" value="$src{DBHost}" /></dd> |
|---|
| 93 | <dt>DBPassword:</dt> |
|---|
| 94 | <dd><input name="src_DBPassword" type="password" value="" /></dd> |
|---|
| 95 | </dl> |
|---|
| 96 | </fieldset> |
|---|
| 97 | |
|---|
| 98 | <fieldset> |
|---|
| 99 | <legend>Destination DB Configuration</legend> |
|---|
| 100 | <dl> |
|---|
| 101 | <dt>DataSource: (Required for BerkeleyDB)</dt> |
|---|
| 102 | <dd><input name="dst_DataSource" type="text" value="$dst{DataSource}" /></dd> |
|---|
| 103 | <dt>ObjectDriver:</dt> |
|---|
| 104 | <dd><select name="dst_ObjectDriver"> |
|---|
| 105 | <option value="">Select a driver</option> |
|---|
| 106 | <option value="DBM" $dst_selected{'DBM'}>BerkeleyDB</option> |
|---|
| 107 | <option value="DBI::mysql" $dst_selected{'DBI::mysql'}>MySQL</option> |
|---|
| 108 | <option value="DBI::postgres" $dst_selected{'DBI::postgres'}>PostgreSQL</option> |
|---|
| 109 | <option value="DBI::sqlite" $dst_selected{'DBI::sqlite'}>SQLite</option> |
|---|
| 110 | </select></dd> |
|---|
| 111 | <dt>Database:</dt> |
|---|
| 112 | <dd><input name="dst_Database" type="text" value="$dst{Database}" /></dd> |
|---|
| 113 | <dt>DBUser:</dt> |
|---|
| 114 | <dd><input name="dst_DBUser" type="text" value="$dst{DBUser}" /></dd> |
|---|
| 115 | <dt>DBHost:</dt> |
|---|
| 116 | <dd><input name="dst_DBHost" type="text" value="$dst{DBHost}" /></dd> |
|---|
| 117 | <dt>DBPassword:</dt> |
|---|
| 118 | <dd><input name="dst_DBPassword" type="password" value="$dst{DBPassword}" /></dd> |
|---|
| 119 | </dl> |
|---|
| 120 | </fieldset> |
|---|
| 121 | |
|---|
| 122 | <p style="clear:both;"><input type="submit" value="Convert" /></p> |
|---|
| 123 | </form> |
|---|
| 124 | HTML |
|---|
| 125 | |
|---|
| 126 | } else { |
|---|
| 127 | |
|---|
| 128 | eval { |
|---|
| 129 | local $SIG{__WARN__} = sub { print "**** WARNING: $_[0]\n" }; |
|---|
| 130 | |
|---|
| 131 | require MT::Object; |
|---|
| 132 | my $type = ($dst{ObjectDriver} =~ /^DBI::(.*)$/) ? $1 : ''; |
|---|
| 133 | if ($type) { |
|---|
| 134 | # set dst driver |
|---|
| 135 | $cfg->set($_, $dst{$_}) foreach (@DBSPECS); |
|---|
| 136 | MT::Object->set_driver($dst{ObjectDriver}) |
|---|
| 137 | or die MT::ObjectDriver->errstr; |
|---|
| 138 | my $dbh = MT::Object->driver->{dbh}; |
|---|
| 139 | $dbh->begin_work if $type eq 'sqlite'; |
|---|
| 140 | my $schema = File::Spec->catfile($MT_DIR, 'schemas', $type . '.dump'); |
|---|
| 141 | open FH, $schema or die "Can't open schema file '$schema': $!"; |
|---|
| 142 | my $ddl; |
|---|
| 143 | { local $/; $ddl = <FH> } |
|---|
| 144 | close FH; |
|---|
| 145 | my @stmts = split /;/, $ddl; |
|---|
| 146 | print "<p>Loading database schema...</p>\n\n"; |
|---|
| 147 | for my $stmt (@stmts) { |
|---|
| 148 | $stmt =~ s!^\s*!!; |
|---|
| 149 | $stmt =~ s!\s*$!!; |
|---|
| 150 | next unless $stmt =~ /\S/; |
|---|
| 151 | $dbh->do($stmt) or die $dbh->errstr; |
|---|
| 152 | } |
|---|
| 153 | $dbh->commit if $type eq 'sqlite'; |
|---|
| 154 | } |
|---|
| 155 | |
|---|
| 156 | ## %ids will hold the highest IDs of each class. |
|---|
| 157 | my %ids; |
|---|
| 158 | |
|---|
| 159 | for my $class (@CLASSES) { |
|---|
| 160 | print "<p>Dumping $class:<br />\n"; |
|---|
| 161 | |
|---|
| 162 | # set source driver |
|---|
| 163 | $cfg->set($_, $src{$_}) foreach (@DBSPECS); |
|---|
| 164 | MT::Object->set_driver($src{ObjectDriver}); |
|---|
| 165 | |
|---|
| 166 | eval "use $class"; |
|---|
| 167 | my $iter = $class->load_iter; |
|---|
| 168 | |
|---|
| 169 | my %names; |
|---|
| 170 | my %cat_parent; |
|---|
| 171 | |
|---|
| 172 | # set dst driver |
|---|
| 173 | $cfg->set($_, $dst{$_}) foreach (@DBSPECS); |
|---|
| 174 | MT::Object->set_driver($dst{ObjectDriver}); |
|---|
| 175 | MT::Object->driver->{dbh}->begin_work if $type eq 'sqlite'; |
|---|
| 176 | |
|---|
| 177 | my $i = 0; |
|---|
| 178 | while (my $obj = $iter->()) { |
|---|
| 179 | $ids{$class} = $obj->id |
|---|
| 180 | if !$ids{$class} || $obj->id > $ids{$class}; |
|---|
| 181 | ## Look for duplicate template, category, and author names, |
|---|
| 182 | ## because we have uniqueness constraints in the DB. |
|---|
| 183 | if ($class eq 'MT::Template') { |
|---|
| 184 | my $key = lc($obj->name) . $obj->blog_id; |
|---|
| 185 | if ($names{$class}{$key}++) { |
|---|
| 186 | print " Found duplicate template name '" . |
|---|
| 187 | $obj->name; |
|---|
| 188 | $obj->name($obj->name . ' ' . $names{$class}{$key}); |
|---|
| 189 | print "'; renaming to '" . $obj->name . "'\n"; |
|---|
| 190 | } |
|---|
| 191 | ## Touch the text column to make sure we read in |
|---|
| 192 | ## any linked templates. |
|---|
| 193 | my $text = $obj->text; |
|---|
| 194 | } elsif ($class eq 'MT::Author') { |
|---|
| 195 | my $key = lc($obj->name); |
|---|
| 196 | if ($names{$class . $obj->type}{$key}++) { |
|---|
| 197 | print " Found duplicate author name '" . |
|---|
| 198 | $obj->name; |
|---|
| 199 | $obj->name($obj->name . ' ' . $names{$class}{$key}); |
|---|
| 200 | print "'; renaming to '" . $obj->name . "'\n"; |
|---|
| 201 | } |
|---|
| 202 | $obj->email('') unless defined $obj->email; |
|---|
| 203 | $obj->set_password('') unless defined $obj->password; |
|---|
| 204 | } elsif ($class eq 'MT::Category') { |
|---|
| 205 | my $key = lc($obj->label) . $obj->blog_id; |
|---|
| 206 | if ($names{$class}{$key}++) { |
|---|
| 207 | print " Found duplicate category label '" . |
|---|
| 208 | $obj->label; |
|---|
| 209 | $obj->label($obj->label . ' ' . $names{$class}{$key}); |
|---|
| 210 | print "'; renaming to '" . $obj->label . "'\n"; |
|---|
| 211 | } |
|---|
| 212 | # save the parent value for assignment at the end |
|---|
| 213 | if ($obj->parent) { |
|---|
| 214 | $cat_parent{$obj->id} = $obj->parent; |
|---|
| 215 | $obj->parent(0); |
|---|
| 216 | } |
|---|
| 217 | } elsif ($class eq 'MT::Trackback') { |
|---|
| 218 | $obj->entry_id(0) unless defined $obj->entry_id; |
|---|
| 219 | $obj->category_id(0) unless defined $obj->category_id; |
|---|
| 220 | } elsif ($class eq 'MT::Entry') { |
|---|
| 221 | $obj->allow_pings(0) |
|---|
| 222 | if defined $obj->allow_pings && $obj->allow_pings eq ''; |
|---|
| 223 | $obj->allow_comments(0) |
|---|
| 224 | if defined $obj->allow_comments && $obj->allow_comments eq ''; |
|---|
| 225 | } elsif ($class eq 'MT::Blog') { |
|---|
| 226 | $obj->touch(); # for updating children_modified_on field |
|---|
| 227 | } |
|---|
| 228 | |
|---|
| 229 | $i++; |
|---|
| 230 | $obj->save |
|---|
| 231 | or die $obj->errstr; |
|---|
| 232 | print "."; |
|---|
| 233 | $i % 10 or print " "; |
|---|
| 234 | $i % 100 or print "<br />\n"; |
|---|
| 235 | } |
|---|
| 236 | |
|---|
| 237 | # fix up the category parents |
|---|
| 238 | foreach my $id (keys %cat_parent) { |
|---|
| 239 | my $cat = MT::Category->load($id); |
|---|
| 240 | $cat->parent( $cat_parent{$id} ); |
|---|
| 241 | $cat->save; |
|---|
| 242 | } |
|---|
| 243 | |
|---|
| 244 | print "</p>\n\n"; |
|---|
| 245 | MT::Object->driver->{dbh}->commit if $type eq 'sqlite'; |
|---|
| 246 | } |
|---|
| 247 | |
|---|
| 248 | if ($type eq 'postgres') { |
|---|
| 249 | print "Updating sequences\n"; |
|---|
| 250 | my $dbh = MT::Object->driver->{dbh}; |
|---|
| 251 | for my $class (keys %ids) { |
|---|
| 252 | print " $class => $ids{$class}\n"; |
|---|
| 253 | my $seq = 'mt_' . $class->datasource . '_' . |
|---|
| 254 | $class->properties->{primary_key}; |
|---|
| 255 | $dbh->do("select setval('$seq', $ids{$class})") |
|---|
| 256 | or die $dbh->errstr; |
|---|
| 257 | } |
|---|
| 258 | } |
|---|
| 259 | }; |
|---|
| 260 | if ($@) { |
|---|
| 261 | print <<HTML; |
|---|
| 262 | <p>An error occurred while loading data: <br /> |
|---|
| 263 | $@</p> |
|---|
| 264 | HTML |
|---|
| 265 | } else { |
|---|
| 266 | print <<HTML; |
|---|
| 267 | <p>Done copying data from $src{ObjectDriver} to $dst{ObjectDriver}! All went well.</p> |
|---|
| 268 | HTML |
|---|
| 269 | } |
|---|
| 270 | |
|---|
| 271 | } |
|---|
| 272 | |
|---|
| 273 | print "</body>\n</html>\n"; |
|---|