root/trunk/lib/MT/Upgrade.pm @ 3013

Revision 3013, 43.0 kB (checked in by auno, 15 months ago)

Fixed error for during upgrade from MT3.x. BugzID:81774

  • Property svn:keywords set to Id Revision
Line 
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
7package MT::Upgrade;
8
9use strict;
10use base qw( MT::ErrorHandler );
11use 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
19use vars qw(%classes %functions %LegacyPerms $App $DryRun $Installing $SuperUser
20            $CLI $MAX_TIME $MAX_ROWS @steps);
21
22sub max_rows {
23    return $MAX_ROWS;
24}
25
26sub max_time {
27    return $MAX_TIME;
28}
29
30sub 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
110sub 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
149sub 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
179sub 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
190sub 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
234sub 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
261sub 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
275sub 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
286sub 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
315sub 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
442sub 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
456sub pre_upgrade_class {
457    return 1;
458}
459
460sub 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
491sub pre_alter_column { 1 }
492sub post_alter_column { 1 }
493sub pre_drop_column { 1 }
494sub post_drop_column { 1 }
495sub pre_add_column { 1 }
496sub pre_index_column { 1 }
497sub post_index_column { 1 }
498sub pre_schema_upgrade { 1 }
499
500# issued last, after all table creation...
501
502sub 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
537sub pre_create_table {
538    my $self = shift;
539    my ($class) = @_;
540    $class->driver->dbd->ddl_class->drop_sequence($class);
541}
542
543sub 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
563sub 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
582sub progress {
583    my $self = shift;
584    $App->progress(@_) if $App;
585}
586
587sub 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
595sub error {
596    my $self = shift;
597    my ($msg) = @_;
598    $App->error(@_) if $App;
599    return undef;
600}
601
602sub 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
613sub 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
651sub 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
688sub 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
718sub 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
759sub 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
772sub 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
784sub 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
840sub 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
9091;
910__END__
911
912=head1 NAME
913
914MT::Upgrade - MT class for managing system upgrades.
915
916=head1 SYNOPSIS
917
918    MT::Upgrade->do_upgrade(Install => 1);
919
920=head1 DESCRIPTION
921
922This module is responsible for handling the upgrade or installation of
923an MT database. The framework is flexible enough for third party plugins
924to use as well to manage their own schema (please refer to the documentation
925in L<MT::Plugin> for more information on this).
926
927=head1 METHODS
928
929=head2 MT::Upgrade-E<gt>do_upgrade
930
931The main worker method for this module is I<do_upgrade>. It accepts a
932handful of arguments, which are:
933
934=over 4
935
936=item * Install
937
938Specify a value of '1' to assume a new installation along with an operation
939to install a blog and initial user.
940
941=item * App
942
943A package name or app object that can service the following methods:
944
945=over 4
946
947=item * progress($package, $message)
948
949Called during the upgrade operation to provide feedback with respect to
950the operations the upgrade process is running.
951
952=item * error($package, $message)
953
954Called during the upgrade operation to communicate an error that has
955occurred.
956
957=item * translate_escape
958
959Call this method to translate messages and phrases which are to appear
960on the progress screen.  DO NOT use this method to messages and phrases
961which directly are stored in database.  Use MT->translate for the purpose.
962
963=back
964
965=item * CLI
966
967Specified (set to '1') when invoked from a command line tool. This prevents
968encoding response messages in the configured PublishCharset for the
969installation.
970
971=item * SuperUser
972
973If upgrading from the command line, and running on a pre-MT 3.2 database,
974set this to an existing author ID that should be upgrade to system
975administrator status.
976
977=item * DryRun
978
979Specified (set to '1') to examine the database for installation/upgrade
980needs but not actually make any physical changes to the database. This will
981issue all the upgrade progress messages without doing the upgrade itself.
982
983=back
984
985=head2 MT::Upgrade->add_step( $function[, %params ] )
986
987Adds an additional upgrade function to the upgrade pipeline. The
988parameters given will be given to the upgrade function when invoked.
989Note that all values in the C<%params> hash should be simple scalar
990values, as they have to be represented in JSON notation.
991
992=head2 MT::Upgrade->check_schema()
993
994Run during the upgrade process to verify all object types are
995up-to-date. Calls the L<check_type> method which does the work
996for an individual object type. Returns '1' for success, or
997undef for failure.
998
999=head2 MT::Upgrade->check_type($type)
1000
1001Issues schema checks for the specified C<$type>. If schema differences
1002exist, upgrade steps are added to bring the object type up to date.
1003
1004=head2 MT::Upgrade->core_column_action($action, %params)
1005
1006Upgrade function to process an object type in a specific
1007way. C<$action> may be one of C<add>, C<drop>, C<alter>, C<index>.
1008C<%params> should contain a C<type> parameter identifying the object
1009type to process.
1010
1011=head2 MT::Upgrade->core_create_config_table
1012
1013Upgrade function to handle the creation of the initial
1014L<MT::Config> object.
1015
1016=head2 MT::Upgrade->core_create_template_maps
1017
1018Upgrade function to handle the creation of L<MT::TemplateMap> records
1019for upgrading pre-2.0 MT schemas.
1020
1021=head2 MT::Upgrade->core_drop_meta_for_table
1022
1023Upgrade function to handle the removal of "meta" blob columns for
1024pre-MT 4.2 schemas.
1025
1026=head2 MT::Upgrade->core_finish
1027
1028Upgrade function that finalizes an upgrade operation. This routine
1029is always run at the end of the upgrade process.
1030
1031=head2 MT::Upgrade->core_fix_type
1032
1033Upgrade function that handles a table "overhaul", where it is necessary
1034to create a new temporary table, drop the existing one, then rebuild it
1035from scratch. This is necessary when an object driver (SQLite, for instance)
1036does not support a kind of table manipulation that is required to upgrade
1037it.
1038
1039=head2 MT::Upgrade->core_populate_author_auth_type
1040
1041Upgrade 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
1046Upgrade function that removes some old table constraints for pre-3.2
1047MT schemas.
1048
1049=head2 MT::Upgrade->core_set_enable_archive_paths
1050
1051Upgrade function that enables the C<EnableArchivePaths> configuration
1052setting, if the existing schema version is 3.2 or earlier (preserves
1053'archive path', 'archive url' blog settings fields).
1054
1055=head1 CALLBACKS
1056
1057The upgrade module defines the following MT callbacks:
1058
1059=over 4
1060
1061=item * MT::Upgrade::SQL
1062
1063Called with each SQL statement that is executed against the database
1064as part of the upgrade process. The parameters passed to this callback are:
1065
1066    $callback, $upgrade_app, $sql_statement
1067
1068The first parameter is an L<MT::Callback> object. C<$upgrade_app> is a
1069package name or L<MT::App> object used to drive the upgrade process.
1070C<$sql_statement> is the actual SQL query that is about to be executed
1071against the database.
1072
1073=back
1074
1075=head1 UPGRADE FUNCTIONS
1076
1077The bulk of this module consists of Movable Type upgrade operations.
1078These are declared as upgrade functions, and are registered in the
1079package variabled '%functions'. (Note: the word 'function' here is
1080not meant to describe a Perl subroutine.)
1081
1082Some functions are invoked to manage the upgrade process from start
1083to finish ('core_upgrade_begin' for instance, which merely displays
1084a progress message to the calling application). The rest handle
1085schema and data transformation from one version of the MT schema to
1086another.
1087
1088Schema translation itself is handled by Movable Type automatically.
1089MT is able to check the physical schema represenation in the database
1090and compare it with the schema as defined by the L<MT::Object>-descended
1091package. If a new property is added to the L<MT::Blog> package, the
1092upgrade 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
1095table used by a class like L<MT::Blog> and will append additional
1096upgrade steps ('core_add_column', 'core_alter_column') that it finds
1097necessary to the upgrade workflow.
1098
1099Following the schema translation operations, the data transformation
1100functions would be used to manipulate the data as necessary from
1101an older schema to the current one. For instance, the
1102'core_create_placements' upgrade function was written to upgrade
1103really old MT schemas from the pre-2.0 release to the current schema.
1104The 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
1132With MT version 2.0, the L<MT::Placement> class was introduced and
1133immediately deprecated the use of MT::Entry-E<gt>category as a result.
1134To facilitate upgrading the existing L<MT::Entry> objects this upgrade
1135function 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
1142declares 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
1149present, it creates one (updater-E<gt>code).
1150
1151=item * It empties out the category_id member of the L<MT::Entry> object
1152being upgrade to prevent it from being processed in the future
1153(updater-E<gt>code).
1154
1155=back
1156
1157For plugins, upgrade functions are assignable in the plugin registration
1158hash as documented in L<MT::Plugin>. You may also return a hashref of
1159upgrade functions from the plugin using the MT::Plugin::upgrade_functions
1160subroutine.
1161
1162Let's look at the anatomy of an upgrade function declaration:
1163
1164=over 4
1165
1166=item * version_limit (optional)
1167
1168The version_limit property allows you to declare that this upgrade
1169operation is only applicable to MT B<schema> versions below the version
1170specified.
1171
1172To register an upgrade function that is only applied to releases prior
1173to the current one, specify the current schema version as the version
1174limit. This will allow the upgrade function to run for any prior releases
1175but prevent it from running in subsequent releases.
1176
1177B<NOTE>: If you are declaring a B<plugin> upgrade function, this version
1178limit is compared with your plugin's schema version, not the Movable Type
1179schema version.
1180
1181=item * priority (optional)
1182
1183If your upgrade operation is dependent on another being done already,
1184it is possible to order them using the priority value. A lower value
1185means a higher priority.
1186
1187=item * condition (optional)
1188
1189This is a coderef parameter. If specified, it should return a true or
1190false value that determines whether the upgrade step is actually to run
1191or not.
1192
1193When called, it is given the parameters normally passed to an upgrade
1194operation (see the 'code' parameter documentation).
1195
1196=item * on_field (optional)
1197
1198If specified, this upgrade function is triggered upon the creation of
1199the field identified by this element. For instance,
1200
1201    on_field => 'MT::Foo->bar'
1202
1203This would specify that the upgrade step is only to run when the 'bar'
1204column is being added to the table that stores data for the MT::Foo
1205package.
1206
1207=item * code
1208
1209This coderef parameter is the declared handler for the upgrade
1210function. It is responsible for doing the upgrade task itself. For
1211quick operations, it is fine to do all of your work within this
1212subroutine. However, to faciliate large databases, it is important
1213to do that work in manageable portions so it doesn't time-out by
1214the web server or browser client.
1215
1216To facilitate an iterative process for your upgrade function, the
1217upgrade routine itself can yield a return value to signal the
1218upgrade process on how to proceed:
1219
1220=over 4
1221
1222=item * 0
1223
1224The upgrade function completed successfully.
1225
1226=item * undef
1227
1228upgrade routine failed with error. The error should be placed using the
1229MT::Upgrade-E<gt>error method.
1230
1231=item * E<gt> 0
1232
1233More work to do; the return value is the 'offset' parameter
1234to pass on the next invocation of the upgrade function.
1235
1236=back
1237
1238Due to the complexity of handling this kind of staged operation,
1239you will most likely want to use the prebuilt
1240'MT::Upgrade::core_update_records' routine to do most of your upgrade
1241operations that handle some or all records of a given package.
1242
1243If using the 'core_update_records' routine, you should also specify
1244an 'updater' parameter for your upgrade function.
1245
1246=item * updater
1247
1248This parameter is only used if you've specified the 'core_update_records'
1249routine (from the L<MT::Upgrade> package itself) for the 'code' element of
1250your 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
1267This updater declaration is going to process all MT::Foo objects that
1268are available, setting the 'bar' property to 1 if it hasn't been assigned
1269a value already.
1270
1271Here's an overview of an 'updater' element:
1272
1273=over 4
1274
1275=item * class (required)
1276
1277The L<MT::Object>-descendant class to be processed.
1278
1279=item * code (required)
1280
1281A coderef to execute for B<each> record of the table. The parameter to
1282this routine is the object being processed. Following the call to your
1283subroutine, the object is saved for you, so you don't have to save
1284the object yourself.
1285
1286=item * message (optional)
1287
1288The status message to display when running this upgrade operation.
1289
1290=item * condition (optional)
1291
1292A coderef to use to test whether the current object needs to be upgraded
1293or not. This routine should return true if it is to be processed; false
1294if not. It is given the object as a parameter.
1295
1296=item * sql (optional)
1297
1298If specified, and if MT is using a SQL-based database for storing data,
1299this SQL statement is issued instead of doing the Perl-based row-by-row
1300upgrade.
1301
1302    sql => 'update mt_foo set foo_bar=1 where foo_bar is null'
1303
1304You 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
1311B<WARNING>: The 'sql' property is only meant to be used for cases where you
1312can issue simple, cross-database SQL statements. It is not advised to
1313use any vendor-specific SQL syntax. So, if you can't do that, don't specify
1314the 'sql' element at all and instead use the 'code' element exclusively
1315to do the upgrade operation.
1316
1317=back
1318
1319=back
1320
1321The declarative style of upgrade functions make it possible for MT to
1322fix itself, upgrading from any older schema version to the current one.
1323Upgrade functions are selected through an introspection process, so any
1324given upgrade operation may run a different selection of upgrade functions.
1325As such, it is important that any upgrade functions be written with this
1326in mind. Here are some general best practices to use when writing them:
1327
1328=over 4
1329
1330=item * Make them fast.
1331
1332Use the 'sql' element for a 'core_update_records' type upgrade function
1333so that SQL-based databases can be upgraded in one pass.
1334
1335=item * Make them indepedent.
1336
1337Don't assume that any other upgrade operation will have run within the
1338same application request. The upgrade process can run them in most any
1339order and across multiple application requests. You do have a guarantee
1340that a higher priority upgrade function will be run prior to a lower-priority
1341upgrade function (ie, assigning a priority of 1 will ensure it will run
1342before one with a priority of 2).
1343
1344=item * Limit them as much as possible.
1345
1346Specify a version_limit so it only runs for the proper schemas. Use the
1347condition element to bypass objects or the upgrade step altogether when
1348possible.
1349
1350=item * Repeating an upgrade function should be safe.
1351
1352This can be made possible through use of the 'condition' elements, bypassing
1353objects that have already been processed (see how the
1354'core_create_placements' upgrade function declares conditions for an
1355example).
1356
1357=item * Beware which translate method to call
1358
1359$self->translate_escape is for messages and phrases which appear on the
1360progress screen (therefore they are sent in JSON).  Use MT->translate
1361to messages and phrases which directly stored in the database.  Log messages
1362and objects' attributes fall into this category.
1363
1364=back
1365
1366=head1 AUTHOR & COPYRIGHTS
1367
1368Please see the I<MT> manpage for author, copyright, and license information.
1369
1370=cut
Note: See TracBrowser for help on using the browser.