| 1 | # Movable Type (r) Open Source (C) 2001-2008 Six Apart, Ltd. |
|---|
| 2 | # This program is distributed under the terms of the |
|---|
| 3 | # GNU General Public License, version 2. |
|---|
| 4 | # |
|---|
| 5 | # $Id$ |
|---|
| 6 | |
|---|
| 7 | package MT::Upgrade; |
|---|
| 8 | |
|---|
| 9 | use strict; |
|---|
| 10 | use base qw( MT::ErrorHandler ); |
|---|
| 11 | use File::Spec; |
|---|
| 12 | |
|---|
| 13 | # The upgrade process... |
|---|
| 14 | # |
|---|
| 15 | # * Database check of all data types |
|---|
| 16 | # - assign default values for 'null' columns |
|---|
| 17 | # * Template check for all blogs |
|---|
| 18 | |
|---|
| 19 | use vars qw(%classes %functions %LegacyPerms $App $DryRun $Installing $SuperUser |
|---|
| 20 | $CLI $MAX_TIME $MAX_ROWS @steps); |
|---|
| 21 | |
|---|
| 22 | sub max_rows { |
|---|
| 23 | return $MAX_ROWS; |
|---|
| 24 | } |
|---|
| 25 | |
|---|
| 26 | sub max_time { |
|---|
| 27 | return $MAX_TIME; |
|---|
| 28 | } |
|---|
| 29 | |
|---|
| 30 | sub BEGIN { |
|---|
| 31 | $MAX_TIME = 5; |
|---|
| 32 | $MAX_ROWS = 100; |
|---|
| 33 | |
|---|
| 34 | %functions = ( |
|---|
| 35 | # standard routines |
|---|
| 36 | 'core_upgrade_begin' => { |
|---|
| 37 | code => \&core_upgrade_begin, |
|---|
| 38 | priority => 1, |
|---|
| 39 | }, |
|---|
| 40 | 'core_fix_type' => { |
|---|
| 41 | code => \&core_fix_type, |
|---|
| 42 | priority => 2, |
|---|
| 43 | }, |
|---|
| 44 | 'core_add_column' => { |
|---|
| 45 | code => sub { shift->core_column_action('add', @_) }, |
|---|
| 46 | priority => 3, |
|---|
| 47 | }, |
|---|
| 48 | 'core_drop_column' => { |
|---|
| 49 | code => sub { shift->core_column_action('drop', @_) }, |
|---|
| 50 | priority => 3, |
|---|
| 51 | }, |
|---|
| 52 | 'core_alter_column' => { |
|---|
| 53 | code => sub { shift->core_column_action('alter', @_) }, |
|---|
| 54 | priority => 3, |
|---|
| 55 | }, |
|---|
| 56 | 'core_index_column' => { |
|---|
| 57 | code => sub { shift->core_column_action('index', @_) }, |
|---|
| 58 | priority => 3.5, |
|---|
| 59 | }, |
|---|
| 60 | 'core_seed_database' => { |
|---|
| 61 | code => \&seed_database, |
|---|
| 62 | priority => 4, |
|---|
| 63 | }, |
|---|
| 64 | 'core_upgrade_end' => { |
|---|
| 65 | code => \&core_upgrade_end, |
|---|
| 66 | priority => 9, |
|---|
| 67 | }, |
|---|
| 68 | 'core_finish' => { |
|---|
| 69 | code => \&core_finish, |
|---|
| 70 | priority => 10, |
|---|
| 71 | }, |
|---|
| 72 | ); |
|---|
| 73 | |
|---|
| 74 | %LegacyPerms = ( |
|---|
| 75 | # System-wide permissions |
|---|
| 76 | #[ 2**0, 'administer', 'System Administrator', 2, 'system' ], |
|---|
| 77 | #[ 2**1, 'create_blog', 'Create Blogs', 2, 'system' ], |
|---|
| 78 | #[ 2**2, 'view_log', 'View System Activity Log', 2, 'system' ], |
|---|
| 79 | #[ 2**3, 'manage_plugins', 'Manage Plugins', 'system' ], |
|---|
| 80 | |
|---|
| 81 | # Blog-specific permissions: |
|---|
| 82 | # The order here is the same order they are presented on the |
|---|
| 83 | # role definition screen. |
|---|
| 84 | 2**0 => 'comment',# 'Add Comments', 1, 'blog'], |
|---|
| 85 | 2**12 => 'administer_blog',# 'Blog Administrator', 1, 'blog'], |
|---|
| 86 | 2**6 => 'edit_config',# 'Configure Blog', 1, 'blog'], |
|---|
| 87 | 2**3 => 'edit_all_posts',# 'Edit All Entries', 1, 'blog'], |
|---|
| 88 | 2**4 => 'edit_templates',# 'Manage Templates', 1, 'blog'], |
|---|
| 89 | 2**2 => 'upload',# 'Upload File', 1, 'blog'], |
|---|
| 90 | 2**1 => 'post',# 'Create Entry', 1, 'blog'], |
|---|
| 91 | 2**16 => 'edit_assets',# 'Manage Assets', 1, 'blog'], |
|---|
| 92 | 2**15 => 'save_image_defaults',# 'Save Image Defaults', 1, 'blog'], |
|---|
| 93 | 2**9 => 'edit_categories',# 'Add/Manage Categories', 1, 'blog'], |
|---|
| 94 | 2**14 => 'edit_tags',# 'Manage Tags', 1, 'blog'], |
|---|
| 95 | 2**10 => 'edit_notifications',# 'Manage Notification List', 1, 'blog'], |
|---|
| 96 | 2**8 => 'send_notifications',# 'Send Notifications', 1, 'blog'], |
|---|
| 97 | 2**13 => 'view_blog_log',# 'View Activity Log', 1, 'blog'], |
|---|
| 98 | #[ 2**17, 'publish_post', 'Publish Post', 1, 'blog'], |
|---|
| 99 | #[ 2**18, 'manage_feedback', 'Manage Feedback', 1, 'blog'], |
|---|
| 100 | #[ 2**19, 'set_publish_paths', 'Set Publishing Paths', 1, 'blog'], |
|---|
| 101 | #[ 2**20, 'manage_pages', 'Manage Pages', 1, 'blog'], |
|---|
| 102 | # 2**5 == 32 is deprecated; reserved for future use |
|---|
| 103 | 2**7 => 'rebuild',# 'Rebuild Files', 1, 'blog'], |
|---|
| 104 | # Not a real permission but a denial thereeof; unlisted because it |
|---|
| 105 | # has no label. |
|---|
| 106 | 2**11 => 'not_comment',# '', 1, 'blog'], |
|---|
| 107 | ); |
|---|
| 108 | } |
|---|
| 109 | |
|---|
| 110 | sub init { |
|---|
| 111 | my $pkg = shift; |
|---|
| 112 | unless (%classes) { |
|---|
| 113 | my $types = MT->registry('object_types'); |
|---|
| 114 | foreach my $type (keys %$types) { |
|---|
| 115 | $classes{$type} = $types->{$type}; |
|---|
| 116 | } |
|---|
| 117 | } |
|---|
| 118 | |
|---|
| 119 | my $fns = MT::Component->registry('upgrade_functions') || []; |
|---|
| 120 | foreach my $fn_set (@$fns) { |
|---|
| 121 | %functions = ( %functions, %{ $fn_set } ); |
|---|
| 122 | } |
|---|
| 123 | |
|---|
| 124 | # Load versioned MT::Upgrade functions, ie MT::Upgrade::v3 |
|---|
| 125 | # Only load the ones that will be required for upgrading |
|---|
| 126 | # from the source SchemaVersion |
|---|
| 127 | my $from = int( MT->config->SchemaVersion || 0 ); |
|---|
| 128 | $from = 1 if $from < 1; |
|---|
| 129 | my $to = int( MT->version_number ); |
|---|
| 130 | for (my $i = $from; $i <= $to; $i++) { |
|---|
| 131 | my $vpkg = "${pkg}::v$i"; |
|---|
| 132 | eval "# line " . __LINE__ . " " . __FILE__ . "\nrequire $vpkg; 1;" or die; |
|---|
| 133 | next if $@; |
|---|
| 134 | my $fn_set = $vpkg->upgrade_functions(); |
|---|
| 135 | %functions = ( %functions, %$fn_set ) |
|---|
| 136 | if $fn_set && (ref($fn_set) eq 'HASH'); |
|---|
| 137 | } |
|---|
| 138 | } |
|---|
| 139 | |
|---|
| 140 | # Step execution... |
|---|
| 141 | |
|---|
| 142 | # iterate routines: |
|---|
| 143 | # no parameters, start with offset == 0 |
|---|
| 144 | # offset parameter, pass thru |
|---|
| 145 | # if routine returns 0, routine is done |
|---|
| 146 | # if routine returns undef, routine failed |
|---|
| 147 | # if routine returns > 0, that's the new offset |
|---|
| 148 | |
|---|
| 149 | sub run_step { |
|---|
| 150 | my $self = shift; |
|---|
| 151 | my ($step) = @_; |
|---|
| 152 | my ($name, %param) = @$step; |
|---|
| 153 | |
|---|
| 154 | if (my $fn = $functions{$name}) { |
|---|
| 155 | local $MT::CallbacksEnabled = 0; |
|---|
| 156 | if (my $cond = $fn->{condition}) { |
|---|
| 157 | $cond = MT->handler_to_coderef($cond); |
|---|
| 158 | next unless $cond->($self, %param); |
|---|
| 159 | } |
|---|
| 160 | my %update_params; |
|---|
| 161 | if ($fn->{updater}) { |
|---|
| 162 | %update_params = %{$fn->{updater}}; |
|---|
| 163 | $fn->{code} ||= \&core_update_records; |
|---|
| 164 | } |
|---|
| 165 | my $code = $fn->{code} || $fn->{handler}; |
|---|
| 166 | $code = MT->handler_to_coderef($code); |
|---|
| 167 | my $result = $code->($self, %param, %update_params, step => $name); |
|---|
| 168 | if ((defined $result) && ($result > 1)) { |
|---|
| 169 | $param{offset} = $result; $result = 1; |
|---|
| 170 | $self->add_step($name, %param); |
|---|
| 171 | } |
|---|
| 172 | return $result; |
|---|
| 173 | } else { |
|---|
| 174 | return $self->error($self->translate_escape("Invalid upgrade function: [_1].", $name)); |
|---|
| 175 | } |
|---|
| 176 | 0; |
|---|
| 177 | } |
|---|
| 178 | |
|---|
| 179 | sub run_callbacks { |
|---|
| 180 | my $self = shift; |
|---|
| 181 | my ($cb, @param) = @_; |
|---|
| 182 | local $MT::CallbacksEnabled = 1; |
|---|
| 183 | MT->run_callbacks('MT::Upgrade::' . $cb, $self, @param) |
|---|
| 184 | or return $self->error(MT->callback_errstr); |
|---|
| 185 | 1; |
|---|
| 186 | } |
|---|
| 187 | |
|---|
| 188 | # Main "do" interface for controlling apparatus |
|---|
| 189 | |
|---|
| 190 | sub do_upgrade { |
|---|
| 191 | my $self = shift; |
|---|
| 192 | my (%opt) = @_; |
|---|
| 193 | |
|---|
| 194 | $self->init; |
|---|
| 195 | |
|---|
| 196 | my $harnessed = ref $opt{App} && (UNIVERSAL::can($opt{App}, 'add_step')); |
|---|
| 197 | |
|---|
| 198 | local $App = $opt{App}; |
|---|
| 199 | local $DryRun = $opt{DryRun}; |
|---|
| 200 | local $SuperUser = $opt{SuperUser} || ''; |
|---|
| 201 | local $CLI = $opt{CLI} || ''; |
|---|
| 202 | |
|---|
| 203 | @steps = (); |
|---|
| 204 | if ($opt{Install}) { |
|---|
| 205 | my %init_params = (%{$opt{User} || {}}, %{$opt{Blog} || {}}); |
|---|
| 206 | $self->install_database(\%init_params); |
|---|
| 207 | } else { |
|---|
| 208 | $self->upgrade_database(); |
|---|
| 209 | } |
|---|
| 210 | |
|---|
| 211 | # no app is running the show, so we must! |
|---|
| 212 | if (!$harnessed) { |
|---|
| 213 | # set these limits very high since we're running unharnessed |
|---|
| 214 | $MAX_TIME = 10000000; |
|---|
| 215 | $MAX_ROWS = 300; |
|---|
| 216 | my $fn = \%MT::Upgrade::functions; |
|---|
| 217 | my @these_steps = @steps; |
|---|
| 218 | while (@these_steps) { |
|---|
| 219 | my $step = shift @these_steps; |
|---|
| 220 | @steps = (); |
|---|
| 221 | $self->run_step($step); |
|---|
| 222 | if (@steps) { |
|---|
| 223 | push @these_steps, @steps; |
|---|
| 224 | @these_steps = sort { $fn->{$a->[0]}->{priority} <=> |
|---|
| 225 | $fn->{$b->[0]}->{priority} } @these_steps; |
|---|
| 226 | } |
|---|
| 227 | } |
|---|
| 228 | return 1; |
|---|
| 229 | } else { |
|---|
| 230 | return \@steps; |
|---|
| 231 | } |
|---|
| 232 | } |
|---|
| 233 | |
|---|
| 234 | sub upgrade_database { |
|---|
| 235 | my $self = shift; |
|---|
| 236 | |
|---|
| 237 | my $config_schema_ver; |
|---|
| 238 | my $schema_ver; |
|---|
| 239 | if ($config_schema_ver = MT->instance->config('SchemaVersion')) { |
|---|
| 240 | my $needs_upgrade; |
|---|
| 241 | $needs_upgrade = 1 if $config_schema_ver < MT->schema_version; |
|---|
| 242 | if (!$needs_upgrade) { |
|---|
| 243 | foreach (@MT::Components) { |
|---|
| 244 | $needs_upgrade = 1 if $_->needs_upgrade; |
|---|
| 245 | } |
|---|
| 246 | } |
|---|
| 247 | return 1 unless $needs_upgrade; |
|---|
| 248 | $schema_ver = $config_schema_ver; |
|---|
| 249 | } else { |
|---|
| 250 | $schema_ver = $self->detect_schema_version; |
|---|
| 251 | } |
|---|
| 252 | |
|---|
| 253 | # this will add steps to upgrade all tables that need it... |
|---|
| 254 | $self->add_step("core_upgrade_begin", from => $schema_ver); |
|---|
| 255 | $self->check_schema; |
|---|
| 256 | $self->add_step('core_upgrade_end', from => $schema_ver); |
|---|
| 257 | $self->add_step('core_finish'); |
|---|
| 258 | 1; |
|---|
| 259 | } |
|---|
| 260 | |
|---|
| 261 | sub install_database { |
|---|
| 262 | my $self = shift; |
|---|
| 263 | my ($user) = @_; |
|---|
| 264 | |
|---|
| 265 | $self->add_step("core_upgrade_begin"); |
|---|
| 266 | # this will add steps to install all tables... |
|---|
| 267 | $self->check_schema; |
|---|
| 268 | # this will populate them... |
|---|
| 269 | $self->add_step('core_seed_database', %$user); |
|---|
| 270 | $self->add_step('core_upgrade_end'); |
|---|
| 271 | $self->add_step('core_finish'); |
|---|
| 272 | 1; |
|---|
| 273 | } |
|---|
| 274 | |
|---|
| 275 | sub check_schema { |
|---|
| 276 | my $self = shift; |
|---|
| 277 | my $class; |
|---|
| 278 | foreach my $type (keys %classes) { |
|---|
| 279 | $class = MT->model($type) |
|---|
| 280 | or return $self->error($self->translate_escape("Error loading class [_1].", $type)); |
|---|
| 281 | $self->check_type($type); |
|---|
| 282 | } |
|---|
| 283 | 1; |
|---|
| 284 | } |
|---|
| 285 | |
|---|
| 286 | sub check_type { |
|---|
| 287 | my $self = shift; |
|---|
| 288 | my ($type) = @_; |
|---|
| 289 | |
|---|
| 290 | my $class = MT->model($type); |
|---|
| 291 | |
|---|
| 292 | # handle schema updates for meta table |
|---|
| 293 | if ($class->meta_pkg) { |
|---|
| 294 | $self->check_type($type . ':meta'); |
|---|
| 295 | } |
|---|
| 296 | |
|---|
| 297 | if (my $result = $self->type_diff($type)) { |
|---|
| 298 | if ($result->{fix}) { |
|---|
| 299 | $self->add_step('core_fix_type', type => $type); |
|---|
| 300 | } else { |
|---|
| 301 | $self->add_step('core_add_column', type => $type) |
|---|
| 302 | if $result->{add}; |
|---|
| 303 | $self->add_step('core_alter_column', type => $type) |
|---|
| 304 | if $result->{alter}; |
|---|
| 305 | $self->add_step('core_drop_column', type => $type) |
|---|
| 306 | if $result->{drop}; |
|---|
| 307 | $self->add_step('core_index_column', type => $type) |
|---|
| 308 | if $result->{index}; |
|---|
| 309 | } |
|---|
| 310 | } |
|---|
| 311 | |
|---|
| 312 | 1; |
|---|
| 313 | } |
|---|
| 314 | |
|---|
| 315 | sub type_diff { |
|---|
| 316 | my $self = shift; |
|---|
| 317 | my ($type) = @_; |
|---|
| 318 | |
|---|
| 319 | my $class = MT->model($type) or return; |
|---|
| 320 | |
|---|
| 321 | my $table = $class->datasource; |
|---|
| 322 | my $defs = $class->column_defs; |
|---|
| 323 | |
|---|
| 324 | my $ddl = $class->driver->dbd->ddl_class; |
|---|
| 325 | my $db_defs = $ddl->column_defs($class); |
|---|
| 326 | |
|---|
| 327 | my $class_idx_defs = $class->index_defs; |
|---|
| 328 | my $db_idx_defs = $ddl->index_defs($class); |
|---|
| 329 | |
|---|
| 330 | # now, compare $defs and $db_defs; |
|---|
| 331 | # here are the scenarios |
|---|
| 332 | # 1. we find something in $defs that isn't in $db_defs |
|---|
| 333 | # -- column should be inserted. this may trigger a process |
|---|
| 334 | # 2. we find something in $db_defs that isn't in $defs |
|---|
| 335 | # -- this is a-ok. user may have added a column. |
|---|
| 336 | # 3. we find a difference between $defs and $db_defs for a field |
|---|
| 337 | # a. type differs; this may trigger a process |
|---|
| 338 | # b. type is same, but null property differs; this may |
|---|
| 339 | # trigger a process |
|---|
| 340 | # c. type is same, but size differs; this may trigger a process |
|---|
| 341 | # d. key differs |
|---|
| 342 | # e. auto differs (auto-increment) |
|---|
| 343 | # 4. table doesn't exist and must be created |
|---|
| 344 | |
|---|
| 345 | my $fix_class; |
|---|
| 346 | $fix_class = 1 unless defined $db_defs; |
|---|
| 347 | |
|---|
| 348 | # we're only scanning defined columns; we don't care about |
|---|
| 349 | # columns that are unique to the table. |
|---|
| 350 | my (@cols_to_add, @cols_to_alter, @cols_to_drop, @cols_to_index); |
|---|
| 351 | |
|---|
| 352 | if (!$fix_class) { |
|---|
| 353 | my @def_cols = keys %$defs; |
|---|
| 354 | |
|---|
| 355 | foreach my $col (@def_cols) { |
|---|
| 356 | my $col_def = $defs->{$col}; |
|---|
| 357 | next if !defined $col_def; |
|---|
| 358 | |
|---|
| 359 | $col_def->{name} = $col; |
|---|
| 360 | |
|---|
| 361 | my $db_def = $db_defs->{$col}; |
|---|
| 362 | |
|---|
| 363 | if (!$db_def) { |
|---|
| 364 | # column is missing altogether; we're going to have to add it |
|---|
| 365 | push @cols_to_add, $col; |
|---|
| 366 | } else { |
|---|
| 367 | if (($col_def->{type} eq 'string') |
|---|
| 368 | && ($db_def->{type} eq 'string') |
|---|
| 369 | && ($col_def->{size} != $db_def->{size})) { |
|---|
| 370 | push @cols_to_alter, $col; |
|---|
| 371 | } elsif ($ddl->type2db($col_def) |
|---|
| 372 | ne $ddl->type2db($db_def)) { |
|---|
| 373 | push @cols_to_alter, $col; |
|---|
| 374 | } elsif (($col_def->{not_null} || 0) != ($db_def->{not_null} || 0)) { |
|---|
| 375 | push @cols_to_alter, $col; |
|---|
| 376 | } |
|---|
| 377 | } |
|---|
| 378 | } |
|---|
| 379 | |
|---|
| 380 | foreach my $key (keys %$class_idx_defs) { |
|---|
| 381 | my $db_idx_def = $db_idx_defs->{$key}; |
|---|
| 382 | if (!$db_idx_def) { |
|---|
| 383 | push @cols_to_index, $key; |
|---|
| 384 | next; |
|---|
| 385 | } |
|---|
| 386 | # if there is a mismatch in definition, add it to index |
|---|
| 387 | my $class_idx_def = $class_idx_defs->{$key}; |
|---|
| 388 | if (ref($class_idx_def)) { |
|---|
| 389 | if (!ref $db_idx_def) { |
|---|
| 390 | push @cols_to_index, $key; |
|---|
| 391 | } |
|---|
| 392 | else { |
|---|
| 393 | my $db_cols; |
|---|
| 394 | if (exists $db_idx_def->{columns}) { |
|---|
| 395 | $db_cols = join ',', @{ $db_idx_def->{columns} }; |
|---|
| 396 | } |
|---|
| 397 | else { |
|---|
| 398 | $db_cols = $key; |
|---|
| 399 | } |
|---|
| 400 | my $class_cols; |
|---|
| 401 | if (exists $class_idx_def->{columns}) { |
|---|
| 402 | $class_cols = join ',', @{ $class_idx_def->{columns} }; |
|---|
| 403 | } |
|---|
| 404 | else { |
|---|
| 405 | $class_cols = $key; |
|---|
| 406 | } |
|---|
| 407 | if ($db_cols ne $class_cols) { |
|---|
| 408 | push @cols_to_index, $key; |
|---|
| 409 | } |
|---|
| 410 | else { |
|---|
| 411 | if (($db_idx_def->{unique} || 0) != ($class_idx_def->{unique} || 0)) { |
|---|
| 412 | push @cols_to_index, $key; |
|---|
| 413 | } |
|---|
| 414 | } |
|---|
| 415 | } |
|---|
| 416 | } |
|---|
| 417 | else { |
|---|
| 418 | if (ref $db_idx_def) { |
|---|
| 419 | push @cols_to_index, $key; |
|---|
| 420 | } |
|---|
| 421 | } |
|---|
| 422 | } |
|---|
| 423 | } |
|---|
| 424 | |
|---|
| 425 | if ($fix_class || @cols_to_add || @cols_to_alter || @cols_to_drop || @cols_to_index) { |
|---|
| 426 | my %param; |
|---|
| 427 | $param{drop} = \@cols_to_drop if @cols_to_drop; |
|---|
| 428 | $param{add} = \@cols_to_add if @cols_to_add; |
|---|
| 429 | $param{alter} = \@cols_to_alter if @cols_to_alter; |
|---|
| 430 | $param{fix} = $fix_class; |
|---|
| 431 | $param{index} = \@cols_to_index if @cols_to_index; |
|---|
| 432 | if ((@cols_to_add && !$ddl->can_add_column) || |
|---|
| 433 | (@cols_to_alter && !$ddl->can_alter_column) || |
|---|
| 434 | (@cols_to_drop && !$ddl->can_drop_column)) { |
|---|
| 435 | $param{fix} = 1; |
|---|
| 436 | } |
|---|
| 437 | return \%param; |
|---|
| 438 | } |
|---|
| 439 | undef; |
|---|
| 440 | } |
|---|
| 441 | |
|---|
| 442 | sub seed_database { |
|---|
| 443 | my $self = shift; |
|---|
| 444 | my (%param) = @_; |
|---|
| 445 | $self->run_callbacks('seed_database', %param); |
|---|
| 446 | return 1; |
|---|
| 447 | } |
|---|
| 448 | |
|---|
| 449 | ### Upgrade triggers |
|---|
| 450 | |
|---|
| 451 | # we don't need these yet, but it makes me feel good to have them around |
|---|
| 452 | |
|---|
| 453 | # 'pre' triggers should execute quickly. 'post' triggers can add steps |
|---|
| 454 | # if they require processing that will take time to complete. |
|---|
| 455 | |
|---|
| 456 | sub pre_upgrade_class { |
|---|
| 457 | return 1; |
|---|
| 458 | } |
|---|
| 459 | |
|---|
| 460 | sub post_upgrade_class { |
|---|
| 461 | my $self = shift; |
|---|
| 462 | my ($class) = @_; |
|---|
| 463 | |
|---|
| 464 | # Special case for handling upgrade process for old "meta" column |
|---|
| 465 | # storage to new narrow tables; some database drivers cannot |
|---|
| 466 | # add new columns without recreating the table, so it's necessary |
|---|
| 467 | # to prioritize migration of meta column data before the schema |
|---|
| 468 | # for that class is updated and the meta column winds up getting |
|---|
| 469 | # dropped as a result. |
|---|
| 470 | return unless MT->config->SchemaVersion; |
|---|
| 471 | if (MT->config->SchemaVersion < 4.0057) { |
|---|
| 472 | return 1 unless $class =~ m/::Meta$/; |
|---|
| 473 | |
|---|
| 474 | my $pc = $class; |
|---|
| 475 | $pc =~ s/::Meta$//; |
|---|
| 476 | |
|---|
| 477 | my $type = $pc->datasource; |
|---|
| 478 | # 'page' instead of 'entry', for instance |
|---|
| 479 | $type = $pc->class_type || $type if $pc->can('class_type'); |
|---|
| 480 | |
|---|
| 481 | my %step_param = ( type => $type ); |
|---|
| 482 | $step_param{plugindata} = 1 if $type eq 'category'; |
|---|
| 483 | $step_param{meta_column} = $pc->properties->{meta_column} |
|---|
| 484 | if $pc->properties->{meta_column}; |
|---|
| 485 | $self->add_step('core_upgrade_meta_for_table', %step_param); |
|---|
| 486 | } |
|---|
| 487 | |
|---|
| 488 | return 1; |
|---|
| 489 | } |
|---|
| 490 | |
|---|
| 491 | sub pre_alter_column { 1 } |
|---|
| 492 | sub post_alter_column { 1 } |
|---|
| 493 | sub pre_drop_column { 1 } |
|---|
| 494 | sub post_drop_column { 1 } |
|---|
| 495 | sub pre_add_column { 1 } |
|---|
| 496 | sub pre_index_column { 1 } |
|---|
| 497 | sub post_index_column { 1 } |
|---|
| 498 | sub pre_schema_upgrade { 1 } |
|---|
| 499 | |
|---|
| 500 | # issued last, after all table creation... |
|---|
| 501 | |
|---|
| 502 | sub post_schema_upgrade { |
|---|
| 503 | my $self = shift; |
|---|
| 504 | my ($from) = @_; |
|---|
| 505 | |
|---|
| 506 | my $plugin_ver = MT->config('PluginSchemaVersion') || {}; |
|---|
| 507 | $plugin_ver->{'core'} = $from; |
|---|
| 508 | |
|---|
| 509 | # run any functions that define a version_limit and where the schema we're |
|---|
| 510 | # upgrading from is below that limit. |
|---|
| 511 | foreach my $fn (keys %functions) { |
|---|
| 512 | my $save_from = $from; |
|---|
| 513 | { |
|---|
| 514 | my $func = $functions{$fn}; |
|---|
| 515 | |
|---|
| 516 | if ($func->{plugin} && (UNIVERSAL::isa($func->{plugin}, 'MT::Component'))) { |
|---|
| 517 | my $id = $func->{plugin}->id; |
|---|
| 518 | $from = $plugin_ver->{$id}; |
|---|
| 519 | } |
|---|
| 520 | if ($func->{version_limit} |
|---|
| 521 | && (defined $from) |
|---|
| 522 | && ($from < $func->{version_limit})) { |
|---|
| 523 | $self->add_step($fn, from => $from); |
|---|
| 524 | } |
|---|
| 525 | elsif ($func |
|---|
| 526 | && !exists($func->{version_limit}) |
|---|
| 527 | && !defined($from)) { |
|---|
| 528 | $self->add_step($fn); |
|---|
| 529 | } |
|---|
| 530 | } |
|---|
| 531 | $from = $save_from; |
|---|
| 532 | } |
|---|
| 533 | |
|---|
| 534 | 1; |
|---|
| 535 | } |
|---|
| 536 | |
|---|
| 537 | sub pre_create_table { |
|---|
| 538 | my $self = shift; |
|---|
| 539 | my ($class) = @_; |
|---|
| 540 | $class->driver->dbd->ddl_class->drop_sequence($class); |
|---|
| 541 | } |
|---|
| 542 | |
|---|
| 543 | sub post_create_table { |
|---|
| 544 | my $self = shift; |
|---|
| 545 | my ($class) = @_; |
|---|
| 546 | |
|---|
| 547 | $class->driver->dbd->ddl_class->create_sequence($class); |
|---|
| 548 | |
|---|
| 549 | if (!$Installing) { |
|---|
| 550 | foreach (keys %functions) { |
|---|
| 551 | my $func = $functions{$_}; |
|---|
| 552 | next unless $func->{on_class}; |
|---|
| 553 | $self->add_step($_) if $func->{on_class} eq $class; |
|---|
| 554 | } |
|---|
| 555 | } |
|---|
| 556 | |
|---|
| 557 | 1; |
|---|
| 558 | } |
|---|
| 559 | |
|---|
| 560 | # Note that this trigger only fires on BerkeleyDB for columns |
|---|
| 561 | # that are non-null or indexed. |
|---|
| 562 | |
|---|
| 563 | sub post_add_column { |
|---|
| 564 | my $self = shift; |
|---|
| 565 | my ($class, $col_defs) = @_; |
|---|
| 566 | |
|---|
| 567 | if (!$Installing) { |
|---|
| 568 | my %cols = map { $_ => 1 } @$col_defs; |
|---|
| 569 | foreach (keys %functions) { |
|---|
| 570 | my $func = $functions{$_}; |
|---|
| 571 | next unless $func->{on_field}; |
|---|
| 572 | if ($func->{on_field} =~ m/^\Q$class\E->(.*)/) { |
|---|
| 573 | $self->add_step($_) if $cols{$1}; |
|---|
| 574 | } |
|---|
| 575 | } |
|---|
| 576 | } |
|---|
| 577 | 1; |
|---|
| 578 | } |
|---|
| 579 | |
|---|
| 580 | # Passthru routines-- passing to calling application... |
|---|
| 581 | |
|---|
| 582 | sub progress { |
|---|
| 583 | my $self = shift; |
|---|
| 584 | $App->progress(@_) if $App; |
|---|
| 585 | } |
|---|
| 586 | |
|---|
| 587 | sub translate_escape { |
|---|
| 588 | my $self = shift; |
|---|
| 589 | my $trans = MT->translate(@_); |
|---|
| 590 | return $trans if $CLI; |
|---|
| 591 | $trans = MT::I18N::encode_text($trans, undef, 'utf-8'); |
|---|
| 592 | return MT::Util::escape_unicode($trans); |
|---|
| 593 | } |
|---|
| 594 | |
|---|
| 595 | sub error { |
|---|
| 596 | my $self = shift; |
|---|
| 597 | my ($msg) = @_; |
|---|
| 598 | $App->error(@_) if $App; |
|---|
| 599 | return undef; |
|---|
| 600 | } |
|---|
| 601 | |
|---|
| 602 | sub add_step { |
|---|
| 603 | my $self = shift; |
|---|
| 604 | if ($App && (ref $App)) { |
|---|
| 605 | $App->add_step(@_); |
|---|
| 606 | } else { |
|---|
| 607 | push @steps, [ @_ ]; |
|---|
| 608 | } |
|---|
| 609 | } |
|---|
| 610 | |
|---|
| 611 | # Misc utilities. |
|---|
| 612 | |
|---|
| 613 | sub detect_schema_version { |
|---|
| 614 | my $self = shift; |
|---|
| 615 | |
|---|
| 616 | require MT::Object; |
|---|
| 617 | my $driver = MT::Object->driver; |
|---|
| 618 | |
|---|
| 619 | require MT::Config; |
|---|
| 620 | if ($driver->table_exists('MT::Config')) { |
|---|
| 621 | return 3.2; |
|---|
| 622 | } |
|---|
| 623 | |
|---|
| 624 | require MT::Template; |
|---|
| 625 | my $dyn_error_template = |
|---|
| 626 | MT::Template->exist({type => 'dynamic_error'}); |
|---|
| 627 | if ($dyn_error_template) { |
|---|
| 628 | return 3.1; |
|---|
| 629 | } |
|---|
| 630 | |
|---|
| 631 | my $comment_pending_template = |
|---|
| 632 | MT::Template->exist({type => 'comment_pending'}); |
|---|
| 633 | if ($comment_pending_template) { |
|---|
| 634 | return 3.0; |
|---|
| 635 | } |
|---|
| 636 | |
|---|
| 637 | require MT::TemplateMap; |
|---|
| 638 | if ($driver->table_exists('MT::TemplateMap')) { |
|---|
| 639 | return 2.0; |
|---|
| 640 | } |
|---|
| 641 | |
|---|
| 642 | 1.0; |
|---|
| 643 | } |
|---|
| 644 | |
|---|
| 645 | # A note about upgrade routines: |
|---|
| 646 | # |
|---|
| 647 | # They should all be 'safe' to execute, regardless of the |
|---|
| 648 | # active schema. In other words, running them twice in a row |
|---|
| 649 | # should not cause any errors or break the schema. |
|---|
| 650 | |
|---|
| 651 | sub core_fix_type { |
|---|
| 652 | my $self = shift; |
|---|
| 653 | my (%param) = @_; |
|---|
| 654 | |
|---|
| 655 | my $type = $param{type}; |
|---|
| 656 | my $class = MT->model($type); |
|---|
| 657 | |
|---|
| 658 | my $result = $self->type_diff($type); |
|---|
| 659 | return 1 unless $result; |
|---|
| 660 | return 1 unless $result->{fix}; |
|---|
| 661 | |
|---|
| 662 | my $alter = $result->{alter}; |
|---|
| 663 | my $add = $result->{add}; |
|---|
| 664 | my $drop = $result->{drop}; |
|---|
| 665 | my $index = $result->{index}; |
|---|
| 666 | |
|---|
| 667 | my $driver = $class->driver; |
|---|
| 668 | my $ddl = $driver->dbd->ddl_class; |
|---|
| 669 | my @stmts; |
|---|
| 670 | push @stmts, sub { $self->pre_upgrade_class($class) }; |
|---|
| 671 | push @stmts, $ddl->upgrade_begin($class); |
|---|
| 672 | push @stmts, sub { $self->pre_create_table($class) }; |
|---|
| 673 | push @stmts, sub { $self->pre_add_column($class, $add) } if $add; |
|---|
| 674 | push @stmts, sub { $self->pre_alter_column($class, $alter) } if $alter; |
|---|
| 675 | push @stmts, sub { $self->pre_drop_column($class, $drop) } if $drop; |
|---|
| 676 | push @stmts, sub { $self->pre_index_column($class, $index) } if $index; |
|---|
| 677 | push @stmts, $ddl->fix_class($class); |
|---|
| 678 | push @stmts, sub { $self->post_create_table($class) }; |
|---|
| 679 | push @stmts, sub { $self->post_add_column($class, $add) } if $add; |
|---|
| 680 | push @stmts, sub { $self->post_alter_column($class, $alter) } if $alter; |
|---|
| 681 | push @stmts, sub { $self->post_drop_column($class, $drop) } if $drop; |
|---|
| 682 | push @stmts, sub { $self->post_index_column($class, $index) } if $index; |
|---|
| 683 | push @stmts, $ddl->upgrade_end($class); |
|---|
| 684 | push @stmts, sub { $self->post_upgrade_class($class) }; |
|---|
| 685 | $self->run_statements($class, @stmts); |
|---|
| 686 | } |
|---|
| 687 | |
|---|
| 688 | sub core_column_action { |
|---|
| 689 | my $self = shift; |
|---|
| 690 | my ($action, %param) = @_; |
|---|
| 691 | |
|---|
| 692 | my $type = $param{type}; |
|---|
| 693 | my $class = MT->model($type); |
|---|
| 694 | my $defs = $class->column_defs; |
|---|
| 695 | |
|---|
| 696 | my $result = $self->type_diff($type); |
|---|
| 697 | return 1 unless $result; |
|---|
| 698 | my $columns = $result->{$action}; |
|---|
| 699 | return 1 unless $columns; |
|---|
| 700 | |
|---|
| 701 | my $pre_method = "pre_${action}_column"; |
|---|
| 702 | my $post_method = "post_${action}_column"; |
|---|
| 703 | my $method = "${action}_column"; |
|---|
| 704 | |
|---|
| 705 | my $driver = $class->driver; |
|---|
| 706 | my $ddl = $driver->dbd->ddl_class; |
|---|
| 707 | my @stmts; |
|---|
| 708 | push @stmts, sub { $self->pre_upgrade_class($class) }; |
|---|
| 709 | push @stmts, $ddl->upgrade_begin($class); |
|---|
| 710 | push @stmts, sub { $self->$pre_method($class, $columns) }; |
|---|
| 711 | push @stmts, $ddl->$method($class, $_) foreach @$columns; |
|---|
| 712 | push @stmts, sub { $self->$post_method($class, $columns) }; |
|---|
| 713 | push @stmts, $ddl->upgrade_end($class); |
|---|
| 714 | push @stmts, sub { $self->post_upgrade_class($class) }; |
|---|
| 715 | $self->run_statements($class, @stmts); |
|---|
| 716 | } |
|---|
| 717 | |
|---|
| 718 | sub run_statements { |
|---|
| 719 | my $self = shift; |
|---|
| 720 | my ($class, @stmts) = @_; |
|---|
| 721 | |
|---|
| 722 | my $driver = $class->driver; |
|---|
| 723 | my $defs = $class->column_defs; |
|---|
| 724 | my $dbh = $driver->rw_handle; |
|---|
| 725 | my $mt = MT->instance; |
|---|
| 726 | |
|---|
| 727 | my $updated = 0; |
|---|
| 728 | if (@stmts) { |
|---|
| 729 | $self->progress($self->translate_escape("Upgrading table for [_1] records...", $class->can('class_label') ? $class->class_label : $class)); |
|---|
| 730 | eval { |
|---|
| 731 | foreach my $stmt (@stmts) { |
|---|
| 732 | if (ref $stmt eq 'CODE') { |
|---|
| 733 | $stmt->() if !$DryRun; |
|---|
| 734 | } else { |
|---|
| 735 | if ($dbh && !$DryRun) { |
|---|
| 736 | my $err; |
|---|
| 737 | $dbh->do($stmt) or $err = $dbh->errstr; |
|---|
| 738 | if ($err) { |
|---|
| 739 | # ignore drop errors; the table/sequence/constraint |
|---|
| 740 | # didn't exist |
|---|
| 741 | if (($stmt !~ m/^drop /i) && ($stmt !~ m/DROP CONSTRAINT /i)) { |
|---|
| 742 | die "failed to execute statement $stmt: $err"; |
|---|
| 743 | } |
|---|
| 744 | } |
|---|
| 745 | } elsif ($dbh && $DryRun) { |
|---|
| 746 | $self->run_callbacks('SQL', $stmt); |
|---|
| 747 | } |
|---|
| 748 | } |
|---|
| 749 | $updated = 1; |
|---|
| 750 | } |
|---|
| 751 | }; |
|---|
| 752 | if ($@) { |
|---|
| 753 | return $self->error($@); |
|---|
| 754 | } |
|---|
| 755 | } |
|---|
| 756 | $updated; |
|---|
| 757 | } |
|---|
| 758 | |
|---|
| 759 | sub core_upgrade_begin { |
|---|
| 760 | my $self = shift; |
|---|
| 761 | my (%param) = @_; |
|---|
| 762 | my $from_schema = $param{from}; |
|---|
| 763 | if ($from_schema) { |
|---|
| 764 | my $cur_schema = MT->schema_version; |
|---|
| 765 | $self->progress($self->translate_escape("Upgrading database from version [_1].", $from_schema)) if $from_schema < $cur_schema; |
|---|
| 766 | $self->pre_schema_upgrade($from_schema); |
|---|
| 767 | } |
|---|
| 768 | $self->run_callbacks('upgrade_begin', %param); |
|---|
| 769 | return 1; |
|---|
| 770 | } |
|---|
| 771 | |
|---|
| 772 | sub core_upgrade_end { |
|---|
| 773 | my $self = shift; |
|---|
| 774 | my (%param) = @_; |
|---|
| 775 | |
|---|
| 776 | my $from_schema = $param{from}; |
|---|
| 777 | if ($from_schema) { |
|---|
| 778 | $self->post_schema_upgrade($from_schema); |
|---|
| 779 | } |
|---|
| 780 | $self->run_callbacks('upgrade_end', %param); |
|---|
| 781 | return 1; |
|---|
| 782 | } |
|---|
| 783 | |
|---|
| 784 | sub core_finish { |
|---|
| 785 | my $self = shift; |
|---|
| 786 | |
|---|
| 787 | my $user; |
|---|
| 788 | if ((ref $App) && ($App->{author})) { |
|---|
| 789 | $user = $App->{author}; |
|---|
| 790 | } |
|---|
| 791 | |
|---|
| 792 | my $cfg = MT->config; |
|---|
| 793 | my $cur_schema = MT->instance->schema_version; |
|---|
| 794 | my $old_schema = $cfg->SchemaVersion || 0; |
|---|
| 795 | if ($cur_schema > $old_schema) { |
|---|
| 796 | $self->progress($self->translate_escape("Database has been upgraded to version [_1].", $cur_schema)) ; |
|---|
| 797 | if ($user && !$DryRun) { |
|---|
| 798 | MT->log(MT->translate("User '[_1]' upgraded database to version [_2]", $user->name, $cur_schema)); |
|---|
| 799 | } |
|---|
| 800 | $cfg->SchemaVersion( $cur_schema, 1 ); |
|---|
| 801 | } |
|---|
| 802 | |
|---|
| 803 | my $plugin_schema = $cfg->PluginSchemaVersion || {}; |
|---|
| 804 | foreach my $plugin (@MT::Components) { |
|---|
| 805 | my $ver = $plugin->schema_version; |
|---|
| 806 | next unless $ver; |
|---|
| 807 | next if $plugin->id eq 'core'; |
|---|
| 808 | my $old_plugin_schema = $plugin_schema->{$plugin->id} || 0; |
|---|
| 809 | if ($old_plugin_schema && ($ver > $old_plugin_schema)) { |
|---|
| 810 | $self->progress($self->translate_escape("Plugin '[_1]' upgraded successfully to version [_2] (schema version [_3]).", $plugin->label, $plugin->version || '-', $ver)); |
|---|
| 811 | if ($user && !$DryRun) { |
|---|
| 812 | MT->log(MT->translate("User '[_1]' upgraded plugin '[_2]' to version [_3] (schema version [_4]).", $user->name, $plugin->label, $plugin->version || '-', $ver)); |
|---|
| 813 | } |
|---|
| 814 | } elsif ($ver && !$old_plugin_schema) { |
|---|
| 815 | $self->progress($self->translate_escape("Plugin '[_1]' installed successfully.", $plugin->label)); |
|---|
| 816 | if ($user && !$DryRun) { |
|---|
| 817 | MT->log(MT->translate("User '[_1]' installed plugin '[_2]', version [_3] (schema version [_4]).", $user->name, $plugin->label, $plugin->version || '-', $ver)); |
|---|
| 818 | } |
|---|
| 819 | } |
|---|
| 820 | $plugin_schema->{$plugin->id} = $ver; |
|---|
| 821 | } |
|---|
| 822 | if (keys %$plugin_schema) { |
|---|
| 823 | $cfg->PluginSchemaVersion($plugin_schema, 1); |
|---|
| 824 | } |
|---|
| 825 | |
|---|
| 826 | my $cur_version = MT->version_number; |
|---|
| 827 | if ( !defined($cfg->MTVersion) || ( $cur_version > $cfg->MTVersion ) ) { |
|---|
| 828 | $cfg->MTVersion( $cur_version, 1 ); |
|---|
| 829 | } |
|---|
| 830 | $cfg->save_config unless $DryRun; |
|---|
| 831 | |
|---|
| 832 | # do one last thing.... |
|---|
| 833 | if ((ref $App) && ($App->can('finish'))) { |
|---|
| 834 | $App->finish(); |
|---|
| 835 | } |
|---|
| 836 | |
|---|
| 837 | 1; |
|---|
| 838 | } |
|---|
| 839 | |
|---|
| 840 | sub core_update_records { |
|---|
| 841 | my $self = shift; |
|---|
| 842 | my (%param) = @_; |
|---|
| 843 | |
|---|
| 844 | my $class = MT->model($param{type}); |
|---|
| 845 | return $self->error($self->translate_escape("Error loading class: [_1].", $param{type})) |
|---|
| 846 | unless $class; |
|---|
| 847 | |
|---|
| 848 | my $msg; |
|---|
| 849 | my $class_label = ($class->can('class_label') ? $class->class_label : $class); |
|---|
| 850 | if ($param{label}) { |
|---|
| 851 | $msg = $param{label}; |
|---|
| 852 | if (ref $msg eq 'CODE') { |
|---|
| 853 | $msg = $msg->($class_label); |
|---|
| 854 | } |
|---|
| 855 | $msg = $self->translate_escape($msg); |
|---|
| 856 | } else { |
|---|
| 857 | $msg = $self->translate_escape($param{message} || "Updating [_1] records...", $class_label); |
|---|
| 858 | } |
|---|
| 859 | my $offset = $param{offset}; |
|---|
| 860 | if ($offset) { |
|---|
| 861 | my $count = $class->count; |
|---|
| 862 | return unless $count; |
|---|
| 863 | $self->progress(sprintf("$msg (%d%%)", ($offset/$count*100)), $param{step}); |
|---|
| 864 | } else { |
|---|
| 865 | $self->progress($msg, $param{step}); |
|---|
| 866 | } |
|---|
| 867 | |
|---|
| 868 | my $cond = MT->handler_to_coderef($param{condition}); |
|---|
| 869 | my $code = MT->handler_to_coderef($param{code}); |
|---|
| 870 | my $sql = $param{sql}; |
|---|
| 871 | |
|---|
| 872 | my $continue = 0; |
|---|
| 873 | my $driver = $class->driver; |
|---|
| 874 | |
|---|
| 875 | if ($sql && $DryRun) { |
|---|
| 876 | $self->run_callbacks('SQL', $sql); |
|---|
| 877 | } |
|---|
| 878 | return 1 if $DryRun; |
|---|
| 879 | |
|---|
| 880 | if (!$sql || !$driver->sql($sql)) { |
|---|
| 881 | my $iter= $class->load_iter(undef, { offset => $offset, limit => $MAX_ROWS+1 }); |
|---|
| 882 | my $start = time; |
|---|
| 883 | my @list; |
|---|
| 884 | while (my $obj = $iter->()) { |
|---|
| 885 | push @list, $obj; |
|---|
| 886 | $continue = 1, last if scalar @list == $MAX_ROWS; |
|---|
| 887 | } |
|---|
| 888 | $iter->end if $continue; |
|---|
| 889 | for my $obj (@list) { |
|---|
| 890 | $offset++; |
|---|
| 891 | if ($cond) { |
|---|
| 892 | next unless $cond->($obj, %param); |
|---|
| 893 | } |
|---|
| 894 | $code->($obj); |
|---|
| 895 | use Data::Dumper; |
|---|
| 896 | $obj->save() |
|---|
| 897 | or return $self->error($self->translate_escape("Error saving [_1] record # [_3]: [_2]... [_4].", $class_label, $obj->errstr, $obj->id, Dumper($obj))); |
|---|
| 898 | $continue = 1, last if time > $start + $MAX_TIME; |
|---|
| 899 | } |
|---|
| 900 | } |
|---|
| 901 | if ($continue) { |
|---|
| 902 | return $offset; |
|---|
| 903 | } else { |
|---|
| 904 | $self->progress("$msg (100%)", $param{step}); |
|---|
| 905 | } |
|---|
| 906 | 1; |
|---|
| 907 | } |
|---|
| 908 | |
|---|
| 909 | 1; |
|---|
| 910 | __END__ |
|---|
| 911 | |
|---|
| 912 | =head1 NAME |
|---|
| 913 | |
|---|
| 914 | MT::Upgrade - MT class for managing system upgrades. |
|---|
| 915 | |
|---|
| 916 | =head1 SYNOPSIS |
|---|
| 917 | |
|---|
| 918 | MT::Upgrade->do_upgrade(Install => 1); |
|---|
| 919 | |
|---|
| 920 | =head1 DESCRIPTION |
|---|
| 921 | |
|---|
| 922 | This module is responsible for handling the upgrade or installation of |
|---|
| 923 | an MT database. The framework is flexible enough for third party plugins |
|---|
| 924 | to use as well to manage their own schema (please refer to the documentation |
|---|
| 925 | in L<MT::Plugin> for more information on this). |
|---|
| 926 | |
|---|
| 927 | =head1 METHODS |
|---|
| 928 | |
|---|
| 929 | =head2 MT::Upgrade-E<gt>do_upgrade |
|---|
| 930 | |
|---|
| 931 | The main worker method for this module is I<do_upgrade>. It accepts a |
|---|
| 932 | handful of arguments, which are: |
|---|
| 933 | |
|---|
| 934 | =over 4 |
|---|
| 935 | |
|---|
| 936 | =item * Install |
|---|
| 937 | |
|---|
| 938 | Specify a value of '1' to assume a new installation along with an operation |
|---|
| 939 | to install a blog and initial user. |
|---|
| 940 | |
|---|
| 941 | =item * App |
|---|
| 942 | |
|---|
| 943 | A package name or app object that can service the following methods: |
|---|
| 944 | |
|---|
| 945 | =over 4 |
|---|
| 946 | |
|---|
| 947 | =item * progress($package, $message) |
|---|
| 948 | |
|---|
| 949 | Called during the upgrade operation to provide feedback with respect to |
|---|
| 950 | the operations the upgrade process is running. |
|---|
| 951 | |
|---|
| 952 | =item * error($package, $message) |
|---|
| 953 | |
|---|
| 954 | Called during the upgrade operation to communicate an error that has |
|---|
| 955 | occurred. |
|---|
| 956 | |
|---|
| 957 | =item * translate_escape |
|---|
| 958 | |
|---|
| 959 | Call this method to translate messages and phrases which are to appear |
|---|
| 960 | on the progress screen. DO NOT use this method to messages and phrases |
|---|
| 961 | which directly are stored in database. Use MT->translate for the purpose. |
|---|
| 962 | |
|---|
| 963 | =back |
|---|
| 964 | |
|---|
| 965 | =item * CLI |
|---|
| 966 | |
|---|
| 967 | Specified (set to '1') when invoked from a command line tool. This prevents |
|---|
| 968 | encoding response messages in the configured PublishCharset for the |
|---|
| 969 | installation. |
|---|
| 970 | |
|---|
| 971 | =item * SuperUser |
|---|
| 972 | |
|---|
| 973 | If upgrading from the command line, and running on a pre-MT 3.2 database, |
|---|
| 974 | set this to an existing author ID that should be upgrade to system |
|---|
| 975 | administrator status. |
|---|
| 976 | |
|---|
| 977 | =item * DryRun |
|---|
| 978 | |
|---|
| 979 | Specified (set to '1') to examine the database for installation/upgrade |
|---|
| 980 | needs but not actually make any physical changes to the database. This will |
|---|
| 981 | issue all the upgrade progress messages without doing the upgrade itself. |
|---|
| 982 | |
|---|
| 983 | =back |
|---|
| 984 | |
|---|
| 985 | =head2 MT::Upgrade->add_step( $function[, %params ] ) |
|---|
| 986 | |
|---|
| 987 | Adds an additional upgrade function to the upgrade pipeline. The |
|---|
| 988 | parameters given will be given to the upgrade function when invoked. |
|---|
| 989 | Note that all values in the C<%params> hash should be simple scalar |
|---|
| 990 | values, as they have to be represented in JSON notation. |
|---|
| 991 | |
|---|
| 992 | =head2 MT::Upgrade->check_schema() |
|---|
| 993 | |
|---|
| 994 | Run during the upgrade process to verify all object types are |
|---|
| 995 | up-to-date. Calls the L<check_type> method which does the work |
|---|
| 996 | for an individual object type. Returns '1' for success, or |
|---|
| 997 | undef for failure. |
|---|
| 998 | |
|---|
| 999 | =head2 MT::Upgrade->check_type($type) |
|---|
| 1000 | |
|---|
| 1001 | Issues schema checks for the specified C<$type>. If schema differences |
|---|
| 1002 | exist, upgrade steps are added to bring the object type up to date. |
|---|
| 1003 | |
|---|
| 1004 | =head2 MT::Upgrade->core_column_action($action, %params) |
|---|
| 1005 | |
|---|
| 1006 | Upgrade function to process an object type in a specific |
|---|
| 1007 | way. C<$action> may be one of C<add>, C<drop>, C<alter>, C<index>. |
|---|
| 1008 | C<%params> should contain a C<type> parameter identifying the object |
|---|
| 1009 | type to process. |
|---|
| 1010 | |
|---|
| 1011 | =head2 MT::Upgrade->core_create_config_table |
|---|
| 1012 | |
|---|
| 1013 | Upgrade function to handle the creation of the initial |
|---|
| 1014 | L<MT::Config> object. |
|---|
| 1015 | |
|---|
| 1016 | =head2 MT::Upgrade->core_create_template_maps |
|---|
| 1017 | |
|---|
| 1018 | Upgrade function to handle the creation of L<MT::TemplateMap> records |
|---|
| 1019 | for upgrading pre-2.0 MT schemas. |
|---|
| 1020 | |
|---|
| 1021 | =head2 MT::Upgrade->core_drop_meta_for_table |
|---|
| 1022 | |
|---|
| 1023 | Upgrade function to handle the removal of "meta" blob columns for |
|---|
| 1024 | pre-MT 4.2 schemas. |
|---|
| 1025 | |
|---|
| 1026 | =head2 MT::Upgrade->core_finish |
|---|
| 1027 | |
|---|
| 1028 | Upgrade function that finalizes an upgrade operation. This routine |
|---|
| 1029 | is always run at the end of the upgrade process. |
|---|
| 1030 | |
|---|
| 1031 | =head2 MT::Upgrade->core_fix_type |
|---|
| 1032 | |
|---|
| 1033 | Upgrade function that handles a table "overhaul", where it is necessary |
|---|
| 1034 | to create a new temporary table, drop the existing one, then rebuild it |
|---|
| 1035 | from scratch. This is necessary when an object driver (SQLite, for instance) |
|---|
| 1036 | does not support a kind of table manipulation that is required to upgrade |
|---|
| 1037 | it. |
|---|
| 1038 | |
|---|
| 1039 | =head2 MT::Upgrade->core_populate_author_auth_type |
|---|
| 1040 | |
|---|
| 1041 | Upgrade function to handle the assignment of the authentication type |
|---|
| 1042 | (specifically, the 'auth_type' column) for upgraded L<MT::Author> records. |
|---|
| 1043 | |
|---|
| 1044 | =head2 MT::Upgrade->core_remove_unique_constraints |
|---|
| 1045 | |
|---|
| 1046 | Upgrade function that removes some old table constraints for pre-3.2 |
|---|
| 1047 | MT schemas. |
|---|
| 1048 | |
|---|
| 1049 | =head2 MT::Upgrade->core_set_enable_archive_paths |
|---|
| 1050 | |
|---|
| 1051 | Upgrade function that enables the C<EnableArchivePaths> configuration |
|---|
| 1052 | setting, if the existing schema version is 3.2 or earlier (preserves |
|---|
| 1053 | 'archive path', 'archive url' blog settings fields). |
|---|
| 1054 | |
|---|
| 1055 | =head1 CALLBACKS |
|---|
| 1056 | |
|---|
| 1057 | The upgrade module defines the following MT callbacks: |
|---|
| 1058 | |
|---|
| 1059 | =over 4 |
|---|
| 1060 | |
|---|
| 1061 | =item * MT::Upgrade::SQL |
|---|
| 1062 | |
|---|
| 1063 | Called with each SQL statement that is executed against the database |
|---|
| 1064 | as part of the upgrade process. The parameters passed to this callback are: |
|---|
| 1065 | |
|---|
| 1066 | $callback, $upgrade_app, $sql_statement |
|---|
| 1067 | |
|---|
| 1068 | The first parameter is an L<MT::Callback> object. C<$upgrade_app> is a |
|---|
| 1069 | package name or L<MT::App> object used to drive the upgrade process. |
|---|
| 1070 | C<$sql_statement> is the actual SQL query that is about to be executed |
|---|
| 1071 | against the database. |
|---|
| 1072 | |
|---|
| 1073 | =back |
|---|
| 1074 | |
|---|
| 1075 | =head1 UPGRADE FUNCTIONS |
|---|
| 1076 | |
|---|
| 1077 | The bulk of this module consists of Movable Type upgrade operations. |
|---|
| 1078 | These are declared as upgrade functions, and are registered in the |
|---|
| 1079 | package variabled '%functions'. (Note: the word 'function' here is |
|---|
| 1080 | not meant to describe a Perl subroutine.) |
|---|
| 1081 | |
|---|
| 1082 | Some functions are invoked to manage the upgrade process from start |
|---|
| 1083 | to finish ('core_upgrade_begin' for instance, which merely displays |
|---|
| 1084 | a progress message to the calling application). The rest handle |
|---|
| 1085 | schema and data transformation from one version of the MT schema to |
|---|
| 1086 | another. |
|---|
| 1087 | |
|---|
| 1088 | Schema translation itself is handled by Movable Type automatically. |
|---|
| 1089 | MT is able to check the physical schema represenation in the database |
|---|
| 1090 | and compare it with the schema as defined by the L<MT::Object>-descended |
|---|
| 1091 | package. If a new property is added to the L<MT::Blog> package, the |
|---|
| 1092 | upgrade process sees that has happened and can issue the actual |
|---|
| 1093 | 'alter table' SQL statement necessary to add it to the database. The |
|---|
| 1094 | 'core_fix_type' function is responsible for examining a particular |
|---|
| 1095 | table used by a class like L<MT::Blog> and will append additional |
|---|
| 1096 | upgrade steps ('core_add_column', 'core_alter_column') that it finds |
|---|
| 1097 | necessary to the upgrade workflow. |
|---|
| 1098 | |
|---|
| 1099 | Following the schema translation operations, the data transformation |
|---|
| 1100 | functions would be used to manipulate the data as necessary from |
|---|
| 1101 | an older schema to the current one. For instance, the |
|---|
| 1102 | 'core_create_placements' upgrade function was written to upgrade |
|---|
| 1103 | really old MT schemas from the pre-2.0 release to the current schema. |
|---|
| 1104 | The upgrade function is registered like this: |
|---|
| 1105 | |
|---|
| 1106 | $MT::Upgrade::functions{core_create_placements} = { |
|---|
| 1107 | version_limit => 2.0, |
|---|
| 1108 | code => \&core_update_records, |
|---|
| 1109 | priority => 9.1, |
|---|
| 1110 | updater => { |
|---|
| 1111 | class => 'MT::Entry', |
|---|
| 1112 | message => 'Creating entry category placements...', |
|---|
| 1113 | condition => sub { $_[0]->category_id }, |
|---|
| 1114 | code => sub { |
|---|
| 1115 | require MT::Placement; |
|---|
| 1116 | my $entry = shift; |
|---|
| 1117 | my $existing = MT::Placement->load({ entry_id => $entry->id, |
|---|
| 1118 | category_id => $entry->category_id }); |
|---|
| 1119 | if (!$existing) { |
|---|
| 1120 | my $place = MT::Placement->new; |
|---|
| 1121 | $place->entry_id($entry->id); |
|---|
| 1122 | $place->blog_id($entry->blog_id); |
|---|
| 1123 | $place->category_id($entry->category_id); |
|---|
| 1124 | $place->is_primary(1); |
|---|
| 1125 | $place->save; |
|---|
| 1126 | } |
|---|
| 1127 | $entry->category_id(0); |
|---|
| 1128 | } |
|---|
| 1129 | } |
|---|
| 1130 | }; |
|---|
| 1131 | |
|---|
| 1132 | With MT version 2.0, the L<MT::Placement> class was introduced and |
|---|
| 1133 | immediately deprecated the use of MT::Entry-E<gt>category as a result. |
|---|
| 1134 | To facilitate upgrading the existing L<MT::Entry> objects this upgrade |
|---|
| 1135 | function is declared such that: |
|---|
| 1136 | |
|---|
| 1137 | =over 4 |
|---|
| 1138 | |
|---|
| 1139 | =item * It is limited to only run for MT schemas older than version 2.0 (the version_limit element handles this). |
|---|
| 1140 | |
|---|
| 1141 | =item * It operates on L<MT::Entry> objects (updater-E<gt>class element |
|---|
| 1142 | declares that). |
|---|
| 1143 | |
|---|
| 1144 | =item * It tells the user what is happening (updater-E<gt>message). |
|---|
| 1145 | |
|---|
| 1146 | =item * It excludes any L<MT::Entry> objects that do not have a category_id element (updater-E<gt>condition). |
|---|
| 1147 | |
|---|
| 1148 | =item * It checks for an existing L<MT::Placement> relationship; if not |
|---|
| 1149 | present, it creates one (updater-E<gt>code). |
|---|
| 1150 | |
|---|
| 1151 | =item * It empties out the category_id member of the L<MT::Entry> object |
|---|
| 1152 | being upgrade to prevent it from being processed in the future |
|---|
| 1153 | (updater-E<gt>code). |
|---|
| 1154 | |
|---|
| 1155 | =back |
|---|
| 1156 | |
|---|
| 1157 | For plugins, upgrade functions are assignable in the plugin registration |
|---|
| 1158 | hash as documented in L<MT::Plugin>. You may also return a hashref of |
|---|
| 1159 | upgrade functions from the plugin using the MT::Plugin::upgrade_functions |
|---|
| 1160 | subroutine. |
|---|
| 1161 | |
|---|
| 1162 | Let's look at the anatomy of an upgrade function declaration: |
|---|
| 1163 | |
|---|
| 1164 | =over 4 |
|---|
| 1165 | |
|---|
| 1166 | =item * version_limit (optional) |
|---|
| 1167 | |
|---|
| 1168 | The version_limit property allows you to declare that this upgrade |
|---|
| 1169 | operation is only applicable to MT B<schema> versions below the version |
|---|
| 1170 | specified. |
|---|
| 1171 | |
|---|
| 1172 | To register an upgrade function that is only applied to releases prior |
|---|
| 1173 | to the current one, specify the current schema version as the version |
|---|
| 1174 | limit. This will allow the upgrade function to run for any prior releases |
|---|
| 1175 | but prevent it from running in subsequent releases. |
|---|
| 1176 | |
|---|
| 1177 | B<NOTE>: If you are declaring a B<plugin> upgrade function, this version |
|---|
| 1178 | limit is compared with your plugin's schema version, not the Movable Type |
|---|
| 1179 | schema version. |
|---|
| 1180 | |
|---|
| 1181 | =item * priority (optional) |
|---|
| 1182 | |
|---|
| 1183 | If your upgrade operation is dependent on another being done already, |
|---|
| 1184 | it is possible to order them using the priority value. A lower value |
|---|
| 1185 | means a higher priority. |
|---|
| 1186 | |
|---|
| 1187 | =item * condition (optional) |
|---|
| 1188 | |
|---|
| 1189 | This is a coderef parameter. If specified, it should return a true or |
|---|
| 1190 | false value that determines whether the upgrade step is actually to run |
|---|
| 1191 | or not. |
|---|
| 1192 | |
|---|
| 1193 | When called, it is given the parameters normally passed to an upgrade |
|---|
| 1194 | operation (see the 'code' parameter documentation). |
|---|
| 1195 | |
|---|
| 1196 | =item * on_field (optional) |
|---|
| 1197 | |
|---|
| 1198 | If specified, this upgrade function is triggered upon the creation of |
|---|
| 1199 | the field identified by this element. For instance, |
|---|
| 1200 | |
|---|
| 1201 | on_field => 'MT::Foo->bar' |
|---|
| 1202 | |
|---|
| 1203 | This would specify that the upgrade step is only to run when the 'bar' |
|---|
| 1204 | column is being added to the table that stores data for the MT::Foo |
|---|
| 1205 | package. |
|---|
| 1206 | |
|---|
| 1207 | =item * code |
|---|
| 1208 | |
|---|
| 1209 | This coderef parameter is the declared handler for the upgrade |
|---|
| 1210 | function. It is responsible for doing the upgrade task itself. For |
|---|
| 1211 | quick operations, it is fine to do all of your work within this |
|---|
| 1212 | subroutine. However, to faciliate large databases, it is important |
|---|
| 1213 | to do that work in manageable portions so it doesn't time-out by |
|---|
| 1214 | the web server or browser client. |
|---|
| 1215 | |
|---|
| 1216 | To facilitate an iterative process for your upgrade function, the |
|---|
| 1217 | upgrade routine itself can yield a return value to signal the |
|---|
| 1218 | upgrade process on how to proceed: |
|---|
| 1219 | |
|---|
| 1220 | =over 4 |
|---|
| 1221 | |
|---|
| 1222 | =item * 0 |
|---|
| 1223 | |
|---|
| 1224 | The upgrade function completed successfully. |
|---|
| 1225 | |
|---|
| 1226 | =item * undef |
|---|
| 1227 | |
|---|
| 1228 | upgrade routine failed with error. The error should be placed using the |
|---|
| 1229 | MT::Upgrade-E<gt>error method. |
|---|
| 1230 | |
|---|
| 1231 | =item * E<gt> 0 |
|---|
| 1232 | |
|---|
| 1233 | More work to do; the return value is the 'offset' parameter |
|---|
| 1234 | to pass on the next invocation of the upgrade function. |
|---|
| 1235 | |
|---|
| 1236 | =back |
|---|
| 1237 | |
|---|
| 1238 | Due to the complexity of handling this kind of staged operation, |
|---|
| 1239 | you will most likely want to use the prebuilt |
|---|
| 1240 | 'MT::Upgrade::core_update_records' routine to do most of your upgrade |
|---|
| 1241 | operations that handle some or all records of a given package. |
|---|
| 1242 | |
|---|
| 1243 | If using the 'core_update_records' routine, you should also specify |
|---|
| 1244 | an 'updater' parameter for your upgrade function. |
|---|
| 1245 | |
|---|
| 1246 | =item * updater |
|---|
| 1247 | |
|---|
| 1248 | This parameter is only used if you've specified the 'core_update_records' |
|---|
| 1249 | routine (from the L<MT::Upgrade> package itself) for the 'code' element of |
|---|
| 1250 | your upgrade function. |
|---|
| 1251 | |
|---|
| 1252 | code => \&MT::Upgrade::core_update_records, |
|---|
| 1253 | updater => { |
|---|
| 1254 | class => 'MT::Foo', |
|---|
| 1255 | message => 'Updating Foo bars...', |
|---|
| 1256 | code => sub { |
|---|
| 1257 | my $foo = shift; |
|---|
| 1258 | $foo->bar(1); |
|---|
| 1259 | }, |
|---|
| 1260 | condition => sub { |
|---|
| 1261 | my $foo = shift; |
|---|
| 1262 | !defined $foo->bar; |
|---|
| 1263 | }, |
|---|
| 1264 | sql => 'update mt_foo set foo_bar = 1 where foo_bar is null' |
|---|
| 1265 | } |
|---|
| 1266 | |
|---|
| 1267 | This updater declaration is going to process all MT::Foo objects that |
|---|
| 1268 | are available, setting the 'bar' property to 1 if it hasn't been assigned |
|---|
| 1269 | a value already. |
|---|
| 1270 | |
|---|
| 1271 | Here's an overview of an 'updater' element: |
|---|
| 1272 | |
|---|
| 1273 | =over 4 |
|---|
| 1274 | |
|---|
| 1275 | =item * class (required) |
|---|
| 1276 | |
|---|
| 1277 | The L<MT::Object>-descendant class to be processed. |
|---|
| 1278 | |
|---|
| 1279 | =item * code (required) |
|---|
| 1280 | |
|---|
| 1281 | A coderef to execute for B<each> record of the table. The parameter to |
|---|
| 1282 | this routine is the object being processed. Following the call to your |
|---|
| 1283 | subroutine, the object is saved for you, so you don't have to save |
|---|
| 1284 | the object yourself. |
|---|
| 1285 | |
|---|
| 1286 | =item * message (optional) |
|---|
| 1287 | |
|---|
| 1288 | The status message to display when running this upgrade operation. |
|---|
| 1289 | |
|---|
| 1290 | =item * condition (optional) |
|---|
| 1291 | |
|---|
| 1292 | A coderef to use to test whether the current object needs to be upgraded |
|---|
| 1293 | or not. This routine should return true if it is to be processed; false |
|---|
| 1294 | if not. It is given the object as a parameter. |
|---|
| 1295 | |
|---|
| 1296 | =item * sql (optional) |
|---|
| 1297 | |
|---|
| 1298 | If specified, and if MT is using a SQL-based database for storing data, |
|---|
| 1299 | this SQL statement is issued instead of doing the Perl-based row-by-row |
|---|
| 1300 | upgrade. |
|---|
| 1301 | |
|---|
| 1302 | sql => 'update mt_foo set foo_bar=1 where foo_bar is null' |
|---|
| 1303 | |
|---|
| 1304 | You may also specify multiple SQL statements using an array: |
|---|
| 1305 | |
|---|
| 1306 | sql => [ |
|---|
| 1307 | 'update mt_foo set foo_bar=1 where foo_bar is null', |
|---|
| 1308 | 'update mt_foo set foo_baz=2 where foo_baz is null' |
|---|
| 1309 | ] |
|---|
| 1310 | |
|---|
| 1311 | B<WARNING>: The 'sql' property is only meant to be used for cases where you |
|---|
| 1312 | can issue simple, cross-database SQL statements. It is not advised to |
|---|
| 1313 | use any vendor-specific SQL syntax. So, if you can't do that, don't specify |
|---|
| 1314 | the 'sql' element at all and instead use the 'code' element exclusively |
|---|
| 1315 | to do the upgrade operation. |
|---|
| 1316 | |
|---|
| 1317 | =back |
|---|
| 1318 | |
|---|
| 1319 | =back |
|---|
| 1320 | |
|---|
| 1321 | The declarative style of upgrade functions make it possible for MT to |
|---|
| 1322 | fix itself, upgrading from any older schema version to the current one. |
|---|
| 1323 | Upgrade functions are selected through an introspection process, so any |
|---|
| 1324 | given upgrade operation may run a different selection of upgrade functions. |
|---|
| 1325 | As such, it is important that any upgrade functions be written with this |
|---|
| 1326 | in mind. Here are some general best practices to use when writing them: |
|---|
| 1327 | |
|---|
| 1328 | =over 4 |
|---|
| 1329 | |
|---|
| 1330 | =item * Make them fast. |
|---|
| 1331 | |
|---|
| 1332 | Use the 'sql' element for a 'core_update_records' type upgrade function |
|---|
| 1333 | so that SQL-based databases can be upgraded in one pass. |
|---|
| 1334 | |
|---|
| 1335 | =item * Make them indepedent. |
|---|
| 1336 | |
|---|
| 1337 | Don't assume that any other upgrade operation will have run within the |
|---|
| 1338 | same application request. The upgrade process can run them in most any |
|---|
| 1339 | order and across multiple application requests. You do have a guarantee |
|---|
| 1340 | that a higher priority upgrade function will be run prior to a lower-priority |
|---|
| 1341 | upgrade function (ie, assigning a priority of 1 will ensure it will run |
|---|
| 1342 | before one with a priority of 2). |
|---|
| 1343 | |
|---|
| 1344 | =item * Limit them as much as possible. |
|---|
| 1345 | |
|---|
| 1346 | Specify a version_limit so it only runs for the proper schemas. Use the |
|---|
| 1347 | condition element to bypass objects or the upgrade step altogether when |
|---|
| 1348 | possible. |
|---|
| 1349 | |
|---|
| 1350 | =item * Repeating an upgrade function should be safe. |
|---|
| 1351 | |
|---|
| 1352 | This can be made possible through use of the 'condition' elements, bypassing |
|---|
| 1353 | objects that have already been processed (see how the |
|---|
| 1354 | 'core_create_placements' upgrade function declares conditions for an |
|---|
| 1355 | example). |
|---|
| 1356 | |
|---|
| 1357 | =item * Beware which translate method to call |
|---|
| 1358 | |
|---|
| 1359 | $self->translate_escape is for messages and phrases which appear on the |
|---|
| 1360 | progress screen (therefore they are sent in JSON). Use MT->translate |
|---|
| 1361 | to messages and phrases which directly stored in the database. Log messages |
|---|
| 1362 | and objects' attributes fall into this category. |
|---|
| 1363 | |
|---|
| 1364 | =back |
|---|
| 1365 | |
|---|
| 1366 | =head1 AUTHOR & COPYRIGHTS |
|---|
| 1367 | |
|---|
| 1368 | Please see the I<MT> manpage for author, copyright, and license information. |
|---|
| 1369 | |
|---|
| 1370 | =cut |
|---|