root/branches/release-33/lib/MT/Upgrade.pm @ 1731

Revision 1731, 105.3 kB (checked in by bchoate, 20 months ago)

Adding embedded column to MT::ObjectAsset. BugId:71500. Thanks, Tim!

  • 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 $App $DryRun $Installing $SuperUser
20            $CLI $MAX_TIME $MAX_ROWS @steps);
21sub BEGIN {
22    $MAX_TIME = 5;
23    $MAX_ROWS = 100;
24
25    %functions = (
26        # standard routines
27        'core_upgrade_begin' => {
28            code => \&core_upgrade_begin,
29            priority => 1,
30        },
31        'core_fix_type' => {
32            code => \&core_fix_type,
33            priority => 2,
34        },
35        'core_add_column' => {
36            code => sub { shift->core_column_action('add', @_) },
37            priority => 3,
38        },
39        'core_drop_column' => {
40            code => sub { shift->core_column_action('drop', @_) },
41            priority => 3,
42        },
43        'core_alter_column' => {
44            code => sub { shift->core_column_action('alter', @_) },
45            priority => 3,
46        },
47        'core_index_column' => {
48            code => sub { shift->core_column_action('index', @_) },
49            priority => 3.5,
50        },
51        'core_seed_database' => {
52            code => \&seed_database,
53            priority => 4,
54        },
55        'core_upgrade_templates' => {
56            code => \&upgrade_templates,
57            priority => 5,
58        },
59        'core_upgrade_end' => {
60            code => \&core_upgrade_end,
61            priority => 9,
62        },
63        'core_finish' => {
64            code => \&core_finish,
65            priority => 10,
66        },
67    );
68}
69
70sub core_upgrade_functions {
71    return {
72        # < 2.0
73        'core_create_placements' => {
74            version_limit => 2.0,
75            priority => 9.1,
76            updater => {
77                type => 'entry',
78                label => 'Creating entry category placements...',
79                condition => sub { $_[0]->category_id },
80                code => sub {
81                    require MT::Placement;
82                    my $entry = shift;
83                    my $existing = MT::Placement->load({ entry_id => $entry->id,
84                        category_id => $entry->category_id });
85                    if (!$existing) {
86                        my $place = MT::Placement->new;
87                        $place->entry_id($entry->id);
88                        $place->blog_id($entry->blog_id);
89                        $place->category_id($entry->category_id);
90                        $place->is_primary(1);
91                        $place->save;
92                    }
93                    $entry->category_id(0);
94                },
95            },
96        },
97        'core_create_template_maps' => {
98            version_limit => 2.0,
99            code => \&core_create_template_maps,
100            priority => 9.1,
101        },
102
103        # < 2.1
104        'core_fix_placement_blog_ids' => {
105            version_limit => 2.1,
106            priority => 9.2,
107            updater => {
108                type => 'placement',
109                label => 'Updating category placements...',
110                condition => sub { !$_[0]->blog_id },
111                code => sub {
112                    require MT::Category;
113                    my $cat = MT::Category->load($_[0]->category_id);
114                    $_[0]->blog_id($cat->blog_id) if $cat;
115                },
116            },
117        },
118
119        # < 3.0
120        'core_set_blog_allow_comments' => {
121            version_limit => 3.0,
122            priority => 9.3,
123            updater => {
124                type => 'blog',
125                label => 'Assigning comment/moderation settings...',
126                condition => sub { !(defined $_[0]->allow_unreg_comments ||
127                                     defined $_[0]->allow_reg_comments ||
128                                     defined $_[0]->manual_approve_comments ||
129                                     defined $_[0]->moderate_unreg_comments) },
130                code => sub {
131                    $_[0]->allow_unreg_comments(1)
132                        unless defined $_[0]->allow_unreg_comments;
133                    $_[0]->allow_reg_comments(1)
134                        unless defined $_[0]->allow_reg_comments;
135                    $_[0]->manual_approve_commenters(0)
136                        unless defined $_[0]->manual_approve_commenters;
137                    $_[0]->moderate_unreg_comments(0)
138                        unless defined $_[0]->moderate_unreg_comments;
139                    $_[0]->moderate_pings(0)
140                        unless defined $_[0]->moderate_pings;
141                },
142                sql => [
143                    'update mt_blog
144                        set blog_allow_unreg_comments = 1
145                      where blog_allow_unreg_comments is null',
146                    'update mt_blog
147                        set blog_allow_reg_comments = 1
148                      where blog_allow_reg_comments is null',
149                    'update mt_blog
150                        set blog_manual_approve_commenters = 0
151                      where blog_manual_approve_commenters is null',
152                    'update mt_blog
153                        set blog_moderate_unreg_comments = 0
154                      where blog_moderate_unreg_comments is null'
155                ],
156            },
157        },
158
159        # < 3.2
160        'core_set_default_basename_limit' => {
161            version_limit => 3.2,
162            priority => 9.3,
163            updater => {
164                type => 'blog',
165                label => 'Setting blog basename limits...',
166                condition => sub { !$_[0]->basename_limit },
167                code => sub { $_[0]->basename_limit(15) },
168                sql => 'update mt_blog set blog_basename_limit = 15
169                         where blog_basename_limit is null
170                            or blog_basename_limit < 15',
171            },
172        },
173        'core_set_default_blog_extension' => {
174            version_limit => 3.2,
175            priority => 9.3,
176            updater => {
177                type => 'blog',
178                label => 'Setting default blog file extension...',
179                condition => sub { !$_[0]->file_extension },
180                code => sub { $_[0]->file_extension('html') },
181            },
182        },
183        'core_set_enable_archive_paths' => {
184            version_limit => 3.2,
185            code => \&core_set_enable_archive_paths,
186            priority => 9.3,
187        },
188        # whereas init_comment_visible is done for adding new a comment visible
189        # to the comment table, this task sets all comments with a visible
190        # status of 2 to 0 since we now treat this field as a boolean.
191        'core_update_comment_visible' => {
192            version_limit => 3.2,
193            priority => 9.3,
194            updater => {
195                type => 'entry',
196                label => 'Updating comment status flags...',
197                condition => sub { $_[0]->allow_comments == 2 },
198                code => sub { $_[0]->allow_comments(0) },
199                sql => 'update mt_entry set entry_allow_comments = 0
200                         where entry_allow_comments = 2',
201            },
202        },
203        'core_remove_unique_constraints' => {
204            version_limit => 3.2,
205            code => \&core_remove_unique_constraints,
206            priority => 9.3,
207        },
208        'core_create_commenter_records' => {
209            version_limit => 3.2,
210            priority => 9.3,
211            updater => {
212                type => 'comment',
213                label => 'Updating commenter records...',
214                condition => sub { $_[0]->commenter_id },
215                code => sub {
216                    my ($comment) = @_;
217                    require MT::Permission; require MT::Author;
218                    my $perm = MT::Permission->load( { author_id => $comment->commenter_id,
219                        blog_id => $comment->blog_id } );
220                    if (!$perm) {
221                        if (my $cmtr = MT::Author->load($comment->commenter_id)) {
222                            $cmtr->pending($comment->blog_id);
223                        }
224                    }
225                }
226            }
227        },
228        'core_set_blog_admins' => {
229            version_limit => 3.2,
230            priority => 9.3,
231            updater => {
232                type => 'permission',
233                label => 'Assigning blog administration permissions...',
234                condition => sub { $_[0]->author_id && $_[0]->blog_id && $_[0]->can_edit_config },
235                code => sub {
236                    require MT::Association;
237                    require MT::Role;
238                    my ($role) = MT::Role->load_by_permission("administer_blog");
239                    if ($role) {
240                        my $user = MT::Author->load($_[0]->author_id);
241                        my $blog = MT::Author->load($_[0]->blog_id);
242                        if ($user && $blog) {
243                            MT::Association->link($role => $user => $blog);
244                        }
245                    } else {
246                        $_[0]->can_administer_blog(1)
247                    }
248                },
249            }
250        },
251        'core_set_blog_allow_pings' => {
252            version_limit => 3.2,
253            priority => 9.3,
254            updater => {
255                type => 'blog',
256                label => 'Setting blog allow pings status...',
257                condition => sub { !defined $_[0]->allow_pings },
258                code => sub { $_[0]->allow_pings($_[0]->allow_pings_default) },
259                sql => 'update mt_blog set blog_allow_pings = blog_allow_pings_default
260                         where blog_allow_pings is null',
261            }
262        },
263        'core_set_superuser' => {
264            version_limit => 3.2,
265            code => \&core_set_superuser,
266            priority => 9.3,
267        },
268        'core_conflate_require_email' => {
269            version_limit => 3.2,
270            priority => 9.3,
271            updater => {
272                type => 'blog',
273                label => 'Updating blog comment email requirements...',
274                condition => sub { !$_[0]->require_comment_emails },
275                code => sub { $_[0]->require_comment_emails(1)
276                                  if !$_[0]->allow_anon_comments },
277            }
278        },
279        'core_populate_entry_basenames' => {
280            version_limit => 3.2,
281            priority => 9.3,
282            updater => {
283                type => 'entry',
284                condition => sub { !$_[0]->basename },
285                code => sub { my $entry = shift; my %args = @_;
286                    $args{from} < 3.20021
287                        ? $entry->basename(mt32_dirify($entry->title))
288                        : 1;
289                },
290                label => 'Assigning entry basenames for old entries...',
291            }
292        },
293        'core_set_api_password' => {
294            version_limit => 3.2,
295            priority => 9.3,
296            updater => {
297                type => 'author',
298                condition => sub { ($_[0]->type == 1) && (($_[0]->api_password || '') eq '') },
299                code => sub { $_[0]->api_password($_[0]->password) },
300                label => 'Updating user web services passwords...',
301            }
302        },
303        'core_create_config_table' => {
304            version_limit => 3.2,
305            code => \&core_create_config_table,
306            priority => 9.3,
307        },
308        'core_deprecate_old_style_archive_links' => {
309            version_limit => 3.2,
310            priority => 9.3,
311            updater => {
312                type => 'blog',
313                label => 'Updating blog old archive link status...',
314                condition => sub { my $blog = shift; my %args = @_;
315                                   $blog->old_style_archive_links
316                                       || $args{from} < 3.0 },
317                code => sub {
318                    my ($blog) = @_;
319                    require MT::TemplateMap;
320                    foreach my $map (MT::TemplateMap->load({ blog_id => $blog->id })) {
321                        next if $map->file_template;
322           
323                        my $at = $map->archive_type;
324                        if ($at eq 'Individual') {
325                            $map->file_template('%e%x');
326                        } elsif ($at eq 'Daily') {
327                            $map->file_template('%y_%m_%d%x');
328                        } elsif ($at eq 'Weekly') {
329                            $map->file_template('week_%y_%m_%d%x');
330                        } elsif ($at eq 'Monthly') {
331                            $map->file_template('%y_%m%x');
332                        } elsif ($at eq 'Category') {
333                            $map->file_template('cat_%C%x');
334                        }
335                        $map->save;
336                    }
337                    $blog->old_style_archive_links(0);
338                }
339            }
340        },
341        'core_set_entry_weeknumber' => {
342            version_limit => 3.20006,
343            priority => 9.3,
344            updater => {
345                type => 'entry',
346                condition => sub { ($_[0]->week_number || 0) < 54 },
347                code => sub { 1 },
348                label => 'Updating entry week numbers...',
349            }
350        },
351        'core_set_tag_permissions' => {
352            version_limit => 3.20021,
353            priority => 9.3,
354            updater => {
355                type => 'permission',
356                condition => sub { !$_[0]->can_edit_tags && !$_[0]->can_administer_blog },
357                code => sub { $_[0]->can_edit_tags($_[0]->can_edit_categories) },
358                label => 'Updating user permissions for editing tags...',
359            }
360        },
361        'core_init_blog_entry_display_defaults' => {
362            version_limit => 3.20021,
363            code => sub {
364                require MT::Permission;
365                &core_update_records
366            },
367            priority => 9.3,
368            updater => {
369                type => 'blog',
370                condition => sub { !(MT::Permission->count({
371                    blog_id => $_[0]->id, author_id => 0 })) },
372                code => sub {
373                    my $perm = new MT::Permission;
374                    $perm->entry_prefs('Advanced|Bottom');
375                    $perm->blog_id($_[0]->id);
376                    $perm->author_id(0);
377                    $perm->save;
378                },
379                label => 'Setting new entry defaults for blogs...',
380            }
381        },
382        'core_upgrade_tag_categories' => {
383            version_limit => 3.20021,
384            priority => 9.3,
385            updater => {
386                type => 'category',
387                condition => sub { ($_[0]->description||'') =~ m/<!--tag-->/ },
388                code => sub {
389                    my ($cat) = @_;
390                    require MT::Placement;
391                    require MT::Entry;
392                    my @e = MT::Entry->load(undef, {join => ['MT::Placement', 'entry_id', { category_id => $cat->id }]});
393                    my $tag_name = $cat->label;
394                    foreach my $e (@e) {
395                        my $tags = $e->tags;
396                        $e->tags($tags, $tag_name);
397                        $e->save;
398                    }
399                },
400                label => 'Migrating any "tag" categories to new tags...',
401            }
402        },
403        'core_init_blog_custom_dynamic_templates' => {
404            on_field => 'MT::Blog->custom_dynamic_templates',
405            priority => 3.1,
406            updater => {
407                type => 'blog',
408                condition => sub { !defined $_[0]->custom_dynamic_templates },
409                code => sub { $_[0]->custom_dynamic_templates('none') },
410                label => 'Assigning custom dynamic template settings...',
411                sql => q{update mt_blog
412                            set blog_custom_dynamic_templates = 'none'
413                          where blog_custom_dynamic_templates is null},
414            }
415        },
416        'core_init_author_type' => {
417            on_field => 'MT::Author->type',
418            priority => 3.1,
419            updater => {
420                type => 'author',
421                condition => sub { !$_[0]->type },
422                code => sub { $_[0]->type(1) },
423                label => 'Assigning user types...',
424                sql => 'update mt_author set author_type = 1
425                        where author_type is null or author_type = 0',
426            }
427        },
428        'core_init_category_parent' => {
429            on_field => 'MT::Category->parent',
430            priority => 3.1,
431            updater => {
432                type => 'category',
433                condition => sub { !defined $_[0]->parent },
434                code => sub { $_[0]->parent(0) },
435                label => 'Assigning category parent fields...',
436                sql => 'update mt_category set category_parent = 0
437                        where category_parent is null',
438            }
439        },
440        'core_init_template_build_dynamic' => {
441            on_field => 'MT::Template->build_dynamic',
442            priority => 3.1,
443            updater => {
444                type => 'template',
445                condition => sub { !defined $_[0]->build_dynamic },
446                code => sub { $_[0]->build_dynamic(0) },
447                label => 'Assigning template build dynamic settings...',
448                sql => 'update mt_template set template_build_dynamic = 0
449                        where template_build_dynamic is null',
450            }
451        },
452        'core_init_comment_visible' => {
453            on_field => 'MT::Comment->visible',
454            priority => 3.1,
455            updater => {
456                type => 'comment',
457                condition => sub { !defined $_[0]->visible },
458                code => sub { $_[0]->visible(1) },
459                label => 'Assigning visible status for comments...',
460                sql => 'update mt_comment set comment_visible = 1
461                        where comment_visible is null',
462            }
463        },
464        'core_init_comment_junk_status' => {
465            on_field => 'MT::Comment->junk_status',
466            priority => 3.1,
467            updater => {
468                type => 'comment',
469                condition => sub { !defined $_[0]->junk_status },
470                code => sub { $_[0]->junk_status(1) },
471                label => 'Assigning junk status for comments...',
472                sql => 'update mt_comment set comment_junk_status = 1
473                        where comment_junk_status is null',
474            }
475        },
476        'core_init_tbping_visible' => {
477            on_field => 'MT::TBPing->visible',
478            priority => 3.1,
479            updater => {
480                type => 'tbping',
481                condition => sub { !defined $_[0]->visible },
482                code => sub { $_[0]->visible(1) },
483                label => 'Assigning visible status for TrackBacks...',
484                sql => 'update mt_tbping set tbping_visible = 1
485                        where tbping_visible is null',
486            }
487        },
488        'core_init_tbping_junk_status' => {
489            on_field => 'MT::TBPing->junk_status',
490            priority => 3.1,
491            updater => {
492                type => 'tbping',
493                condition => sub { !defined $_[0]->junk_status },
494                code => sub { $_[0]->junk_status(1) },
495                label => 'Assigning junk status for TrackBacks...',
496                sql => 'update mt_tbping set tbping_junk_status = 1
497                        where tbping_junk_status is null',
498            }
499        },
500        'core_init_category_basename' => {
501            version_limit => 3.2002,
502            priority => 3.1,
503            updater => {
504                type => 'category',
505                condition => sub { !defined $_[0]->basename },
506                code => sub { my $cat = shift; my %args = @_;
507                    $args{from} < 3.20021
508                        ? $cat->basename(mt32_dirify($cat->label))
509                        : 1;
510                },
511                label => 'Assigning basename for categories...',
512            }
513        },
514        'core_set_author_status' => {
515            version_limit => 3.301,
516            priority => 3.1,
517            updater => {
518                type => 'author',
519                label => 'Assigning user status...',
520                condition => sub {
521                    ($_[0]->type == 1) && (!defined $_[0]->status)
522                },
523                code => sub {
524                    shift->status(1);
525                },
526                sql => 'update mt_author set author_status = 1
527                        where author_type = 1 and author_status is null',
528            }
529        },
530        'core_install_default_roles' => {
531            code => \&create_default_roles,
532            on_class => 'MT::Role',
533            priority => 3.1,
534        },
535        'core_migrate_permissions_to_roles' => {
536            version_limit => 3.3101,
537            priority => 3.2,
538            updater => {
539                type => 'permission',
540                label => 'Migrating permissions to roles...',
541                condition => sub { $_[0]->blog_id },
542                code => \&_migrate_permission_to_role,
543            }
544        },
545        'core_remove_dynamic_site_bootstrapper' => {
546            code => \&remove_mtviewphp,
547            version_limit => 3.3101,
548            priority => 5.1,
549        },
550        'core_migrate_commenter_auth' => {
551            code => \&migrate_commenter_auth,
552            version_limit => 3.3101,
553            priority => 3.1,
554        },
555        'core_deprecate_bitmask_permissions' => {
556            code => \&deprecate_bitmask_permissions,
557            version_limit => 4.0002,
558            priority => 3.3,
559        },
560        'core_migrate_system_privileges' => {
561            code => \&migrate_system_privileges,
562            version_limit => 4.0002,
563            priority => 3.3,
564        },
565        'core_populate_authored_on' => {
566            version_limit => 4.0014,
567            priority => 3.1,
568            updater => {
569                type => 'entry',
570                label => 'Populating authored and published dates for entries...',
571                condition => sub {
572                    !defined $_[0]->authored_on
573                },
574                code => sub {
575                    $_[0]->authored_on($_[0]->created_on)
576                        if !defined $_[0]->authored_on;
577                },
578                sql =>
579                    'update mt_entry set entry_authored_on = entry_created_on
580                        where entry_authored_on is null',
581            },
582        },
583        'core_update_3x_system_search_templates' => {
584            version_limit => 4.0017,
585            priority => 3.1,
586            code => \&update_3x_system_search_templates,
587        },
588        'core_rename_php_plugin_filenames' => {
589            version_limit => 4.0019,
590            priority => 3.1,
591            code => \&rename_php_plugin_filenames,
592        },
593        'core_migrate_nofollow_settings' => {
594            version_limit => 4.0020,
595            priority => 3.1,
596            code => \&migrate_nofollow_settings,
597        },
598        'core_update_widget_template' => {
599            version_limit => 4.0022,
600            priority => 3.1,
601            code => \&update_widget_templates,
602        },
603        # This upgrade step is currently necessary for PostgreSQL
604        # which doesn't support adding a column, populating the existing
605        # records with a value.
606        'core_typify_category_records' => {
607            version_limit => 4.0023,
608            priority => 3.1,
609            updater => {
610                type => 'category',
611                label => 'Classifying category records...',
612                condition => sub {
613                    !$_[0]->column('class')
614                },
615                code => sub {
616                    $_[0]->class('category');
617                },
618                sql =>
619                    "update mt_category set category_class = 'category'
620                        where category_class is null",
621            },
622        },
623        'core_typify_entry_records' => {
624            version_limit => 4.0023,
625            priority => 3.1,
626            updater => {
627                type => 'entry',
628                label => 'Classifying entry records...',
629                condition => sub {
630                    !$_[0]->column('class')
631                },
632                code => sub {
633                    $_[0]->class('entry');
634                },
635                sql =>
636                    "update mt_entry set entry_class = 'entry'
637                        where entry_class is null",
638            },
639        },
640        'core_merge_comment_response_templates' => {
641            version_limit => 4.0023,
642            priority => 3.1,
643            updater => {
644                type => 'blog',
645                label => "Merging comment system templates...",
646                code => \&_merge_comment_response_templates_updater,
647            },
648        },
649        'core_populate_default_file_template' => {
650            version_limit => 4.0023,
651            priority => 3.1,
652            updater => {
653                type => 'templatemap',
654                label => 'Populating default file template for templatemaps...',
655                condition => sub {
656                    !defined $_[0]->file_template
657                },
658                code => sub {
659                    my %default_template = (
660                        Individual => '%y/%m/%f',
661                        Category => '%c/%i',
662                    );
663                    $_[0]->file_template($default_template{$_[0]->archive_type})
664                        if !defined $_[0]->file_template && exists($default_template{$_[0]->archive_type});
665                },
666            },
667        },
668        'core_remove_unused_templatemap' => {
669            version_limit => 4.0023,
670            priority => 3.0,
671            code => \&remove_unused_templatemap,
672        },
673        'core_set_author_auth_type' => {
674            version_limit => 4.0024,
675            priority => 3.2,
676            updater => {
677                type => 'author',
678                label => 'Assigning user authentication type...',
679                condition => sub {
680                    !$_[0]->auth_type;
681                },
682                code => \&core_populate_author_auth_type,
683            },
684        },
685        'core_add_newfeature_widget' => {
686            version_limit => 4.0027,
687            priority => 3.2,
688            updater => {
689                type => 'author',
690                label => 'Adding new feature widget to dashboard...',
691                condition => sub {
692                    my ($user) = @_;
693                    $user->type == MT::Author::AUTHOR(); # AUTHOR records only
694                },
695                code => sub {
696                    my ($user) = @_;
697                    my $widget_store = $user->widgets();
698                    if ($widget_store && %$widget_store) {
699                        for my $set (keys %$widget_store) {
700                            $widget_store->{$set}->{new_version} = {
701                                template => 'widget/new_version.tmpl',
702                                set      => 'main',
703                                singular => 1,
704                                order    => -1,
705                            };
706                        }
707                    }
708                    $user->widgets($widget_store);
709                },
710            },
711        },
712        'core_add_email_template' => {
713            version_limit => 4.0030,
714            priority => 9.3,
715            code => sub {
716                my $self = shift;
717                $self->upgrade_templates({ Install => 1 });
718            },
719        },
720        'core_replace_openid_username' => {
721            version_limit => 4.0033,
722            priority => 3.2,
723            updater => {
724                type => 'author',
725                label => 'Moving OpenID usernames to external_id fields...',
726                condition => sub {
727                    return 0 if 'MT' eq $_[0]->auth_type;
728                    my $auth = MT->commenter_authenticator($_[0]->auth_type);
729                    return 0 unless $auth && %$auth && exists($auth->{class});
730                    my $auth_class = $auth->{class};
731                    eval "require $auth_class;";
732                    return 0 if $@;
733                    return UNIVERSAL::isa($auth_class, 'MT::Auth::OpenID');
734                },
735                code => sub {
736                    return unless $_[0]->url;
737                    my $existing = MT::Author->load({ name => $_[0]->url, auth_type => 'OpenID' });
738                    unless ($existing) {
739                        $_[0]->external_id($_[0]->name);
740                        $_[0]->name($_[0]->url);
741                    }
742                },
743            },
744        },
745        'core_set_template_set' => {
746            version_limit => 4.0034,
747            priority => 3.2,
748            updater => {
749                type => 'blog',
750                label => 'Assigning blog template set...',
751                condition => sub {
752                    !$_[0]->template_set;
753                },
754                code => sub {
755                    $_[0]->template_set('mt_blog');
756                    MT->run_callbacks( 'blog_template_set_change', { blog => $_[0] } );
757                },
758            },
759        },
760        'core_set_page_layout' => {
761            version_limit => 4.0036,
762            priority => 3.2,
763            updater => {
764                type => 'blog',
765                label => 'Assigning blog page layout...',
766                condition => sub {
767                    !$_[0]->page_layout;
768                },
769                code => sub {
770                    my ($blog) = @_;
771                    my $layout = 'layout-wtt';
772                    require MT::Template;
773                    my $styles = MT::Template->load({ blog_id => $blog->id, identifier => 'styles' });
774                    if ($styles) {
775                        if ($styles->text =~ m{ <mt:?setvar \s+ name="page_layout" \s+ value="([^"]+)"> }xmsi) {
776                            $layout = $1;
777                        }
778                    }
779                    $blog->page_layout($layout);
780                },
781            },
782        },
783        'core_set_author_basename' => {
784            version_limit => 4.0037,
785            priority => 3.2,
786            updater => {
787                type => 'author',
788                label => 'Assigning author basename...',
789                condition => sub {
790                    $_[0]->type == 1;
791                },
792                code => sub {
793                    my ($author) = @_;
794                    my $basename = MT::Util::make_unique_author_basename($author);
795                    $author->basename($basename);
796                },
797            },
798        },
799        'core_remove_indexes' => {
800            version_limit => 4.0041,
801            priority => 3.2,
802            code => \&remove_indexes,
803        },
804        'core_set_count_columns' => {
805            version_limit => 4.0047,
806            priority      => 3.2,
807            updater       => {
808                type      => 'entry',
809                label     => 'Assigning entry comment and TrackBack counts...',
810                condition => sub {
811                    require MT::Comment;
812                    my $comment_count = MT::Comment->count(
813                        {
814                            entry_id => $_[0]->id,
815                            visible  => 1,
816                        }
817                    );
818                    $_[0]->comment_count($comment_count);
819                    require MT::Trackback;
820                    require MT::TBPing;
821                    my $tb = MT::Trackback->load( { entry_id => $_[0]->id } );
822                    my $ping_count;
823                    if ($tb) {
824                        my $ping_count = MT::TBPing->count(
825                            {
826                                tb_id   => $tb->id,
827                                visible => 1,
828                            }
829                        );
830                        $_[0]->ping_count($ping_count);
831                    }
832                    ( $comment_count || $ping_count );
833                },
834                # only count once and set it, so code do nothing.
835                # it doesn't have the unnecessary save.
836                code => sub { 1; },
837            },
838        },
839        'core_assign_object_embedded' => {
840            version_limit => 4.0052,
841            priority => 3.2,
842            updater => {
843                type => 'objectasset',
844                label => 'Assigning embedded flag to asset placements...',
845                code => sub {
846                    $_[0]->embedded(1);
847                },
848                sql => 'update mt_objectasset set objectasset_embedded=1',
849            },
850        },
851    }
852}
853
854sub core_populate_author_auth_type {
855    my ($u) = @_;
856    if ($u->type == 1) {
857        $u->auth_type(MT->config->AuthenticationModule || 'MT');
858    } else {
859        # for legacy OpenID plugin commenters
860        if ($u->name =~ m(^openid\n(.*)$)) {
861            my $url = $1;
862            if (eval { require Digest::MD5; 1; }) {
863                $url = Digest::MD5::md5_hex($url);
864            } else {
865                $url = substr $url, 0, 255;
866            }
867            $u->name($url);
868            $u->auth_type('OpenID');
869        }
870        elsif ($u->name =~ m!^[a-f0-9]{32}$!) {
871            # Vox OpenID URL; set auth_type to 'Vox'
872            if ($u->url =~ m!\.vox\.com/!) {
873                $u->auth_type('Vox');
874            }
875            # LJ OpenID URL; set auth_type to 'LiveJournal'
876            elsif ($u->url =~ m!\.livejournal\.com/!) {
877                $u->auth_type('LiveJournal');
878            }
879            else {
880                # Other custom auth, which for now means OpenID
881                $u->auth_type('OpenID');
882            }
883        }
884        else {
885            # Default to TypeKey for remaining plain name fields
886            $u->auth_type('TypeKey');
887        }
888    }
889}
890
891sub migrate_nofollow_settings {
892    my $self = shift;
893
894    $self->progress($self->translate_escape("Migrating Nofollow plugin settings..."));
895    require MT::PluginData;
896    my $cfg = MT->config;
897    my $plugins = $cfg->PluginSwitch || {};
898    my $nofollow_switch = $plugins->{'nofollow/nofollow.pl'};
899    my $enabled = defined $nofollow_switch ? ($nofollow_switch ? 1 : 0) : 1;
900    my $default_follow_auth_links = 1;
901
902    # For any configuration settings that exist
903    my @config = MT::PluginData->load({ plugin => 'Nofollow' });
904    my %blogs_saved;
905    foreach my $cfg (@config) {
906        if ($cfg->key =~ m/^configuration:blog:(\d+)/) {
907            my $blog = MT::Blog->load($1) or next;
908            my $setting = ($cfg->data || {})->{follow_auth_links};
909            $blog->follow_auth_links($setting) if defined $setting;
910            $blog->nofollow_urls($enabled);
911            $blog->save;
912            $blogs_saved{$blog->id} = 1;
913        } else {
914            my $setting = ($cfg->data || {})->{follow_auth_links};
915            $default_follow_auth_links = $setting if defined $setting;
916        }
917    }
918    $_->remove for @config;
919
920    my $blog_iter = MT::Blog->load_iter;
921    while (my $blog = $blog_iter->()) {
922        next if exists $blogs_saved{$blog->id};
923        $blog->nofollow_urls($enabled);
924        $blog->follow_auth_links($default_follow_auth_links);
925        $blog->save;
926    }
927
928    # Forcibly disable nofollow plugin now since this has become
929    # a core function.
930    $cfg->PluginSwitch('nofollow/nofollow.pl=0', 1);
931    $cfg->save_config();
932
933    return 0;
934}
935
936sub update_3x_system_search_templates {
937    my $self = shift;
938
939    require MT::Template;
940    $self->progress($self->translate_escape('Updating system search template records...'));
941    my $tmpl_iter = MT::Template->load_iter({
942        type => 'search_template',
943    });
944    my %blogs;
945    while (my $tmpl = $tmpl_iter->()) {
946        $blogs{$tmpl->blog_id} = $tmpl->id;
947        $tmpl->type('search_results');
948        $tmpl->save;
949    }
950    # for any old 'search_template' system templates, remove the
951    # newly installed 'search_results' template.
952    foreach my $blog_id (keys %blogs) {
953        my $tmpl = MT::Template->load({ type => 'search_results',
954            blog_id => $blog_id, id => $blogs{$blog_id} }, {
955            not => { id => 1 } });
956        $tmpl->remove if $tmpl;
957    }
958
959    my %terms;
960    my %args;
961    if (my @blog_ids = keys %blogs) {
962        $terms{id} = \@blog_ids;
963        $args{not} = { id => 1 };
964    }
965    my $blog_iter = MT::Blog->load_iter(\%terms, \%args);
966    # These blogs does not have search results - upgrades from 3.2 or before.
967    while (my $blog = $blog_iter->()) {
968        my $tmpl = MT::Template->load(
969          { type => 'search_results', blog_id => $blog->id,});
970        $tmpl->remove if $tmpl;
971    }
972    0;
973}
974
975my $perm_role_names = {
976    4096 => 'Blog Administrator',  # administer_blog
977    30687 => 'Blog Administrator', # 32767 - 2048(not comment) - 32(reserved) = all permissions in MT3.3
978    14303 => 'Blog Administrator', # 16383 - 2048(not comment) - 32(reserved) = all permissions in MT3.2
979    2 => 'Writer', # post
980    6 => 'Writer (can upload)', # post + upload
981    17032 => 'Editor',  # edit_all_posts + edit_tags + edit_categories + rebuild
982    17036 => 'Editor (can upload)', # Editor + upload
983    144 => 'Designer', # edit_templates + rebuild
984    17292 => 'Publisher', # Editor (can upload) + send_notifications
985};
986
987{
988    my $full_perm_mask = 0;
989    my %LegacyPerms = (
990        # System-wide permissions
991        #[ 2**0, 'administer', 'System Administrator', 2, 'system' ],
992        #[ 2**1, 'create_blog', 'Create Blogs', 2, 'system' ],
993        #[ 2**2, 'view_log', 'View System Activity Log', 2, 'system' ],
994        #[ 2**3, 'manage_plugins', 'Manage Plugins', 'system' ],
995 
996        # Blog-specific permissions:
997        # The order here is the same order they are presented on the
998        # role definition screen.
999        2**0 => 'comment',# 'Add Comments', 1, 'blog'],
1000        2**12 => 'administer_blog',# 'Blog Administrator', 1, 'blog'],
1001        2**6 => 'edit_config',# 'Configure Blog', 1, 'blog'],
1002        2**3 => 'edit_all_posts',# 'Edit All Entries', 1, 'blog'],
1003        2**4 => 'edit_templates',# 'Manage Templates', 1, 'blog'],
1004        2**2 => 'upload',# 'Upload File', 1, 'blog'],
1005        2**1 => 'post',# 'Create Entry', 1, 'blog'],
1006        2**16 => 'edit_assets',# 'Manage Assets', 1, 'blog'],
1007        2**15 => 'save_image_defaults',# 'Save Image Defaults', 1, 'blog'],
1008        2**9 => 'edit_categories',# 'Add/Manage Categories', 1, 'blog'],
1009        2**14 => 'edit_tags',# 'Manage Tags', 1, 'blog'],
1010        2**10 => 'edit_notifications',# 'Manage Notification List', 1, 'blog'],
1011        2**8 => 'send_notifications',# 'Send Notifications', 1, 'blog'],
1012        2**13 => 'view_blog_log',# 'View Activity Log', 1, 'blog'],
1013        #[ 2**17, 'publish_post', 'Publish Post', 1, 'blog'],
1014        #[ 2**18, 'manage_feedback', 'Manage Feedback', 1, 'blog'],
1015        #[ 2**19, 'set_publish_paths', 'Set Publishing Paths', 1, 'blog'],
1016        #[ 2**20, 'manage_pages', 'Manage Pages', 1, 'blog'],
1017        # 2**5 == 32 is deprecated; reserved for future use
1018        2**7 => 'rebuild',# 'Rebuild Files', 1, 'blog'],
1019        # Not a real permission but a denial thereeof; unlisted because it
1020        # has no label.
1021        2**11 => 'not_comment',# '', 1, 'blog'],
1022    );
1023
1024
1025sub _migrate_permission_to_role {
1026    my $perm = shift;
1027
1028    return unless $perm->author_id;
1029    my $user = MT::Author->load($perm->author_id);
1030    if (!$user) {
1031        $perm->remove;
1032        return;
1033    }
1034    # Don't bother with non-AUTHOR types
1035    return unless $user->type == 1;
1036
1037    my $role_mask = $perm->role_mask;
1038    $role_mask -= 32 if (32 & $role_mask) == 32; # for permissions before 3.2
1039
1040    if (!$full_perm_mask) {
1041        # only consider blog permissions that are supported (exclude
1042        # now reserved permission bits like 32).
1043        foreach my $key (keys %LegacyPerms) {
1044            next if $LegacyPerms{$key} =~ m/^not_/; # skip exclusion permissions
1045            $full_perm_mask |= $key;
1046        }
1047    }
1048
1049    $role_mask = $full_perm_mask & $role_mask;
1050
1051    # '0' permission, not used for permissions, just prefs
1052    return unless $role_mask;
1053
1054    my $name;
1055    $name = MT->translate($perm_role_names->{$role_mask})
1056        if $perm_role_names->{$role_mask};
1057    $name ||= MT->translate("Custom ([_1])", $role_mask);
1058    require MT::Role;
1059    my $role = MT::Role->load({ name => $name });
1060    if ($role) {
1061        if (($role->role_mask != $role_mask) &&
1062            ((4096 != $role->role_mask) && (30687 != $role_mask))) {
1063            $role = undef;
1064        }
1065    }
1066    unless ($role) {
1067        $role = new MT::Role;
1068        $role->name($name);
1069        $role->description(MT->translate("This role was generated by Movable Type upon upgrade."));
1070        $role->role_mask($role_mask);
1071        $role->save;
1072    }
1073    my $blog = MT::Blog->load($perm->blog_id);
1074    $user->add_role($role, $blog) if $blog;
1075}
1076
1077sub _process_masks {
1078    my $self = shift;
1079    my ($perm) = @_;
1080
1081    my $mask = $perm->role_mask;
1082    return unless $mask;
1083    my @perms;
1084    for my $key (keys %LegacyPerms) {
1085        if (int($mask) & int($key)) {
1086            if (2 eq $key) { # post
1087                push @perms, 'create_post', 'publish_post';
1088            } elsif (64 eq $key) { #edit_config
1089                push @perms, 'edit_config', 'set_publish_paths', 'manage_feedback';
1090            } elsif (4096 eq $key) { #adminsiter_blog
1091                push @perms, 'administer_blog', 'manage_pages';
1092            } elsif (2048 eq $key) { #not_comment
1093                $perm->restrictions("'comment'");
1094            } else {
1095                push @perms, $LegacyPerms{$key};
1096            }
1097        }
1098    }
1099    my $perm_str = scalar(@perms) ? "'" . join("','", @perms) . "'" : q();
1100    $perm->permissions($perm_str);
1101    $perm->role_mask(0); ## remove legacy permissions
1102    $perm;
1103}
1104
1105sub deprecate_bitmask_permissions {
1106    my $self = shift;
1107   
1108    require MT::Permission;
1109    my $perm_iter = MT::Permission->load_iter;
1110    $self->progress($self->translate_escape('Migrating permission records to new structure...'));
1111    while (my $perm = $perm_iter->()) {
1112        if ($self->_process_masks($perm)) {
1113            $perm->save;
1114        }
1115    }
1116
1117    require MT::Role;
1118    my $role_iter = MT::Role->load_iter;
1119    $self->progress($self->translate_escape('Migrating role records to new structure...'));
1120    while (my $role = $role_iter->()) {
1121        if ($self->_process_masks($role)) {
1122            $role->save;
1123        }
1124    }
1125}
1126}
1127
1128sub migrate_system_privileges {
1129    my $self = shift;
1130
1131    require MT::Permission;
1132    my $author_iter = MT::Author->load_iter({ type => MT::Author::AUTHOR() });
1133    $self->progress($self->translate_escape('Migrating system level permissions to new structure...'));
1134    while (my $author = $author_iter->()) {
1135        my @perms;
1136        push @perms, 'administer' if $author->column('is_superuser');
1137        push @perms, 'create_blog' if $author->column('can_create_blog') || $author->column('is_superuser');
1138        push @perms, 'view_log' if $author->column('can_view_log') || $author->column('is_superuser');
1139        push @perms, 'manage_plugins' if $author->column('is_superuser');
1140        if (@perms) {
1141            my $perm = MT::Permission->load({ author_id => $author->id,
1142                blog_id => 0 });
1143            if (!$perm) {
1144                $perm = MT::Permission->new;
1145                $perm->author_id($author->id);
1146                $perm->blog_id(0);
1147            }
1148            $perm->set_these_permissions(@perms);
1149            $perm->save;
1150        }
1151    }
1152}
1153
1154sub init {
1155    my $pkg = shift;
1156    unless (%classes) {
1157        my $types = MT->registry('object_types');
1158        foreach my $type (keys %$types) {
1159            $classes{$type} = $types->{$type};
1160        }
1161    }
1162    my $fns = MT::Component->registry('upgrade_functions') || [];
1163    foreach my $fn_set (@$fns) {
1164        %functions = ( %functions, %{ $fn_set } );
1165    }
1166}
1167
1168# Step execution...
1169
1170# iterate routines:
1171#     no parameters, start with offset == 0
1172#     offset parameter, pass thru
1173#     if routine returns 0, routine is done
1174#     if routine returns undef, routine failed
1175#     if routine returns > 0, that's the new offset
1176
1177sub run_step {
1178    my $self = shift;
1179    my ($step) = @_;
1180    my ($name, %param) = @$step;
1181
1182    if (my $fn = $functions{$name}) {
1183        local $MT::CallbacksEnabled = 0;
1184        if (my $cond = $fn->{condition}) {
1185            $cond = MT->handler_to_coderef($cond);
1186            next unless $cond->($self, %param);
1187        }
1188        my %update_params;
1189        if ($fn->{updater}) {
1190            %update_params = %{$fn->{updater}};
1191            $fn->{code} ||= \&core_update_records;
1192        }
1193        my $code = $fn->{code} || $fn->{handler};
1194        $code = MT->handler_to_coderef($code);
1195        my $result = $code->($self, %param, %update_params, step => $name);
1196        if ((defined $result) && ($result > 1)) {
1197            $param{offset} = $result; $result = 1;
1198            $self->add_step($name, %param);
1199        }
1200        return $result;
1201    } else {
1202        return $self->error($self->translate_escape("Invalid upgrade function: [_1].", $name));
1203    }
1204    0;
1205}
1206
1207sub run_callbacks {
1208    my $self = shift;
1209    my ($cb, @param) = @_;
1210    local $MT::CallbacksEnabled = 1;
1211    MT->run_callbacks('MT::Upgrade::' . $cb, $self, @param);
1212}
1213
1214# Main "do" interface for controlling apparatus
1215
1216sub do_upgrade {
1217    my $self = shift;
1218    my (%opt) = @_;
1219
1220    $self->init;
1221
1222    my $harnessed = ref $opt{App} && (UNIVERSAL::can($opt{App}, 'add_step'));
1223
1224    local $App = $opt{App};
1225    local $DryRun = $opt{DryRun};
1226    local $SuperUser = $opt{SuperUser} || '';
1227    local $CLI = $opt{CLI} || '';
1228
1229    @steps = ();
1230    if ($opt{Install}) {
1231        my %init_params = (%{$opt{User} || {}}, %{$opt{Blog} || {}});
1232        $self->install_database(\%init_params);
1233    } else {
1234        $self->upgrade_database();
1235    }
1236
1237    # no app is running the show, so we must!
1238    if (!$harnessed) {
1239        # set these limits very high since we're running unharnessed
1240        $MAX_TIME = 10000000;
1241        $MAX_ROWS = 300;
1242        my $fn = \%MT::Upgrade::functions;
1243        my @these_steps = @steps;
1244        while (@these_steps) {
1245            my $step = shift @these_steps;
1246            @steps = ();
1247            $self->run_step($step);
1248            if (@steps) {
1249                push @these_steps, @steps;
1250                @these_steps = sort { $fn->{$a->[0]}->{priority} <=>
1251                                      $fn->{$b->[0]}->{priority} } @these_steps;
1252            }
1253        }
1254        return 1;
1255    } else {
1256        return \@steps;
1257    }
1258}
1259
1260sub upgrade_database {
1261    my $self = shift;
1262
1263    my $config_schema_ver;
1264    my $schema_ver;
1265    if ($config_schema_ver = MT->instance->config('SchemaVersion')) {
1266        my $needs_upgrade;
1267        $needs_upgrade = 1 if $config_schema_ver < MT->schema_version;
1268        if (!$needs_upgrade) {
1269            foreach (@MT::Components) {
1270                $needs_upgrade = 1 if $_->needs_upgrade;
1271            }
1272        }
1273        return 1 unless $needs_upgrade;
1274        $schema_ver = $config_schema_ver;
1275    } else {
1276        $schema_ver = $self->detect_schema_version;
1277    }
1278
1279    # this will add steps to upgrade all tables that need it...
1280    $self->add_step("core_upgrade_begin", from => $schema_ver);
1281    $self->check_schema;
1282    $self->add_step('core_upgrade_templates');
1283    $self->add_step('core_upgrade_end', from => $schema_ver);
1284    $self->add_step('core_finish');
1285    1;
1286}
1287
1288sub install_database {
1289    my $self = shift;
1290    my ($user) = @_;
1291
1292    # this will add steps to install all tables...
1293    $self->check_schema;
1294    # this will populate them...
1295    $self->add_step('core_seed_database', %$user);
1296    $self->add_step('core_upgrade_templates');
1297    $self->add_step('core_finish');
1298    1;
1299}
1300
1301sub check_schema {
1302    my $self = shift;
1303    my $class;
1304    foreach my $type (keys %classes) {
1305        $class = MT->model($type)
1306            or return $self->error($self->translate_escape("Error loading class [_1].", $type));
1307        $self->check_type($type);
1308    }
1309    1;
1310}
1311
1312sub check_type {
1313    my $self = shift;
1314    my ($type) = @_;
1315
1316    my $class = MT->model($type);
1317    if (my $result = $self->type_diff($type)) {
1318        if ($result->{fix}) {
1319            $self->add_step('core_fix_type', type => $type);
1320        } else {
1321            $self->add_step('core_add_column', type => $type)
1322                if $result->{add};
1323            $self->add_step('core_alter_column', type => $type)
1324                if $result->{alter};
1325            $self->add_step('core_drop_column', type => $type)
1326                if $result->{drop};
1327            $self->add_step('core_index_column', type => $type)
1328                if $result->{index};
1329        }
1330    }
1331    1;
1332}
1333
1334sub type_diff {
1335    my $self = shift;
1336    my ($type) = @_;
1337
1338    my $class = MT->model($type) or return;
1339
1340    my $table = $class->datasource;
1341    my $defs = $class->column_defs;
1342
1343    my $ddl = $class->driver->dbd->ddl_class;
1344    my $db_defs = $ddl->column_defs($class);
1345
1346    my $class_idx_defs = $class->index_defs;
1347    my $db_idx_defs = $ddl->index_defs($class);
1348
1349    # now, compare $defs and $db_defs;
1350    # here are the scenarios
1351    #   1. we find something in $defs that isn't in $db_defs
1352    #      -- column should be inserted. this may trigger a process
1353    #   2. we find something in $db_defs that isn't in $defs
1354    #      -- this is a-ok. user may have added a column.
1355    #   3. we find a difference between $defs and $db_defs for a field
1356    #      a. type differs; this may trigger a process
1357    #      b. type is same, but null property differs; this may
1358    #         trigger a process
1359    #      c. type is same, but size differs; this may trigger a process
1360    #      d. key differs
1361    #      e. auto differs (auto-increment)
1362    #   4. table doesn't exist and must be created
1363
1364    my $fix_class;
1365    $fix_class = 1 unless defined $db_defs;
1366
1367    # we're only scanning defined columns; we don't care about
1368    # columns that are unique to the table.
1369    my (@cols_to_add, @cols_to_alter, @cols_to_drop, @cols_to_index);
1370
1371    if (!$fix_class) {
1372        my @def_cols = keys %$defs;
1373
1374        foreach my $col (@def_cols) {
1375            my $col_def = $defs->{$col};
1376            next if !defined $col_def;
1377
1378            $col_def->{name} = $col;
1379
1380            my $db_def = $db_defs->{$col};
1381
1382            if (!$db_def) {
1383                # column is missing altogether; we're going to have to add it
1384                push @cols_to_add, $col;
1385            } else {
1386                if (($col_def->{type} eq 'string')
1387                 && ($db_def->{type} eq 'string')
1388                 && ($col_def->{size} <= $db_def->{size})) {
1389                    if (($col_def->{not_null} || 0) != ($db_def->{not_null} || 0)) {
1390                        push @cols_to_alter, $col;
1391                    }
1392                } elsif ($ddl->type2db($col_def)
1393                      ne $ddl->type2db($db_def)) {
1394                    # types are different
1395                    # don't bother if the database has sufficient
1396                    # capacity for this field
1397                    next if ($db_def->{type} eq 'integer')
1398                         && ($col_def->{type} eq 'smallint'
1399                          || $col_def->{type} eq 'boolean');
1400                    push @cols_to_alter, $col;
1401                } elsif (($col_def->{not_null} || 0) != ($db_def->{not_null} || 0)) {
1402                    push @cols_to_alter, $col;
1403                }
1404            }
1405        }
1406
1407        foreach my $key (keys %$class_idx_defs) {
1408            my $db_idx_def = $db_idx_defs->{$key};
1409            if (!$db_idx_def) {
1410                push @cols_to_index, $key;
1411                next;
1412            }
1413            # if there is a mismatch in definition, add it to index
1414            my $class_idx_def = $class_idx_defs->{$key};
1415            if (ref($class_idx_def)) {
1416                if (!ref $db_idx_def) {
1417                    push @cols_to_index, $key;
1418                }
1419                else {
1420                    my $db_cols;
1421                    if (exists $db_idx_def->{columns}) {
1422                        $db_cols = join ',', @{ $db_idx_def->{columns} };
1423                    }
1424                    else {
1425                        $db_cols = $key;
1426                    }
1427                    my $class_cols;
1428                    if (exists $class_idx_def->{columns}) {
1429                        $class_cols = join ',', @{ $class_idx_def->{columns} };
1430                    }
1431                    else {
1432                        $class_cols = $key;
1433                    }
1434                    if ($db_cols ne $class_cols) {
1435                        push @cols_to_index, $key;
1436                    }
1437                    else {
1438                        if (($db_idx_def->{unique} || 0) != ($class_idx_def->{unique} || 0)) {
1439                            push @cols_to_index, $key;
1440                        }
1441                    }
1442                }
1443            }
1444            else {
1445                if (ref $db_idx_def) {
1446                    push @cols_to_index, $key;
1447                }
1448            }
1449        }
1450    }
1451
1452    if ($fix_class || @cols_to_add || @cols_to_alter || @cols_to_drop || @cols_to_index) {
1453        my %param;
1454        $param{drop} = \@cols_to_drop if @cols_to_drop;
1455        $param{add} = \@cols_to_add if @cols_to_add;
1456        $param{alter} = \@cols_to_alter if @cols_to_alter;
1457        $param{fix} = $fix_class;
1458        $param{index} = \@cols_to_index if @cols_to_index;
1459        if ((@cols_to_add && !$ddl->can_add_column) ||
1460            (@cols_to_alter && !$ddl->can_alter_column) ||
1461            (@cols_to_drop && !$ddl->can_drop_column)) {
1462            $param{fix} = 1;
1463        }
1464        return \%param;
1465    }
1466    undef;
1467}
1468
1469sub seed_database {
1470    my $self = shift;
1471    my (%param) = @_;
1472
1473    require MT::Author;
1474    return undef if MT::Author->count;
1475
1476    $self->progress($self->translate_escape("Creating initial blog and user records..."));
1477
1478    local $MT::CallbacksEnabled = 1;
1479
1480    require MT::L10N;
1481    my $lang = exists $param{user_lang} ? $param{user_lang} : MT->config->DefaultLanguage;
1482    my $LH = MT::L10N->get_handle($lang);
1483
1484    # TBD: parameter for username/password provided by user from $app
1485    use URI::Escape;
1486    my $author = MT::Author->new;
1487    $author->name(exists $param{user_name} ? uri_unescape($param{user_name}) : 'Melody');
1488    $author->type(MT::Author::AUTHOR());
1489    $author->set_password(exists $param{user_password} ? uri_unescape($param{user_password}) : 'Nelson');
1490    $author->email(exists $param{user_email} ? uri_unescape($param{user_email}) : '');
1491    $author->hint(exists $param{user_hint} ? uri_unescape($param{user_hint}) : '');
1492    $author->nickname(exists $param{user_nickname} ? uri_unescape($param{user_nickname}) : '');
1493    $author->is_superuser(1);
1494    $author->can_create_blog(1);
1495    $author->can_view_log(1);
1496    $author->can_manage_plugins(1);
1497    $author->preferred_language($lang);
1498    $author->external_id(MT::Author->pack_external_id($param{user_external_id})) if exists $param{user_external_id};
1499    $author->auth_type(MT->config->AuthenticationModule);
1500    $author->save or return $self->error($self->translate_escape("Error saving record: [_1].", $author->errstr));
1501    $App->{author} = $author if ref $App;
1502
1503    $self->create_default_roles(%param);
1504
1505    require MT::Blog;
1506    my $blog = MT::Blog->create_default_blog(MT->translate('First Blog'), $param{blog_template_set})
1507        or return $self->error($self->translate_escape("Error saving record: [_1].", MT::Blog->errstr));
1508    $blog->site_path(exists $param{blog_path} ? uri_unescape($param{blog_path}) : '');
1509    $blog->site_url(exists $param{blog_url} ? uri_unescape($param{blog_url}) : '');
1510    $blog->name(exists $param{blog_name} ? uri_unescape($param{blog_name}) : '');
1511    $blog->server_offset(exists $param{blog_timezone} ? ($param{blog_timezone} || 0) : 0);
1512    $blog->template_set($param{blog_template_set});
1513    $blog->save;
1514    MT->run_callbacks( 'blog_template_set_change', { blog => $blog } );
1515
1516    # Create an initial entry and comment for this blog
1517    require MT::Entry;
1518    my $entry = MT::Entry->new;
1519    $entry->blog_id($blog->id);
1520    $entry->title(MT->translate("I just finished installing Movable Type [_1]!", int(MT->product_version)));
1521    $entry->text(MT->translate("Welcome to my new blog powered by Movable Type. This is the first post on my blog and was created for me automatically when I finished the installation process. But that is ok, because I will soon be creating posts of my own!"));
1522    $entry->author_id($author->id);
1523    $entry->status(MT::Entry::RELEASE());
1524    $entry->save
1525        or return $self->error($self->translate_escape("Error saving record: [_1].", MT::Entry->errstr));
1526
1527    require MT::Comment;
1528    my $comment = MT::Comment->new;
1529    $comment->entry_id($entry->id);
1530    $comment->blog_id($blog->id);
1531    $comment->text(MT->translate("Movable Type also created a comment for me as well so that I could see what a comment will look like on my blog once people start submitting comments on all the posts I will write."));
1532    $comment->visible(1);
1533    $comment->junk_status(1);
1534    $comment->author(exists $param{user_nickname} ? uri_unescape($param{user_nickname}) : undef);
1535    $comment->save
1536        or return $self->error($self->translate_escape("Error saving record: [_1].", MT::Comment->errstr));
1537
1538    require MT::Association;
1539    require MT::Role;
1540    my ($blog_admin_role) = MT::Role->load_by_permission("administer_blog");
1541    MT::Association->link( $blog => $blog_admin_role => $author );
1542
1543    1;
1544}
1545
1546## Translation
1547# translate('Blog Administrator')
1548# translate('Can administer the blog.')
1549# translate('Editor')
1550# translate('Can upload files, edit all entries/categories/tags on a blog and publish the blog.')
1551# translate('Author')
1552# translate('Can create entries, edit their own, upload files and publish.')
1553# translate('Designer')
1554# translate('Can edit, manage and publish blog templates.')
1555# translate('Webmaster')
1556# translate('Can manage pages and publish blog templates.')
1557# translate('Contributor')
1558# translate('Can create entries, edit their own and comment.')
1559# translate('Moderator')
1560# translate('Can comment and manage feedback.')
1561# translate('Commenter')
1562# translate('Can comment.')
1563
1564sub create_default_roles {
1565    my $self = shift;
1566    my (%param) = @_;
1567
1568    my @default_roles = (
1569        { name => 'Blog Administrator',
1570          description => 'Can administer the blog.',
1571          role_mask => 2**12,
1572          perms => ['administer_blog'] },
1573        { name => 'Editor',
1574          description => 'Can upload files, edit all entries/categories/tags on a blog and publish the blog.',
1575          perms => ['comment', 'create_post', 'publish_post', 'edit_all_posts', 'edit_categories', 'edit_tags', 'manage_pages',
1576                    'rebuild', 'upload', 'send_notifications', 'manage_feedback'], },
1577        { name => 'Author',
1578          description => 'Can create entries, edit their own, upload files and publish.',
1579          perms => ['comment', 'create_post', 'publish_post', 'upload', 'send_notifications'], },
1580        { name => 'Designer',
1581          description => 'Can edit, manage and publish blog templates.',
1582          role_mask => (2**4 + 2**7),
1583          perms => ['edit_templates', 'rebuild'] },
1584        { name => 'Webmaster',
1585          description => 'Can manage pages and publish blog templates.',
1586          perms => ['manage_pages', 'rebuild'] },
1587        { name => 'Contributor',
1588          description => 'Can create entries, edit their own and comment.',
1589          perms => ['comment', 'create_post'], },
1590        { name => 'Moderator',
1591          description => 'Can comment and manage feedback.',
1592          perms => ['comment', 'manage_feedback'], },
1593        { name => 'Commenter',
1594          description => 'Can comment.',
1595          role_mask => 2**0,
1596          perms => ['comment'], },
1597    );
1598
1599    require MT::Role;
1600    return if MT::Role->count();
1601
1602    foreach my $r (@default_roles) {
1603        my $role = MT::Role->new();
1604        $role->name(MT->translate($r->{name}));
1605        $role->description(MT->translate($r->{description}));
1606        $role->clear_full_permissions;
1607        $role->set_these_permissions($r->{perms});
1608        if ($r->{name} =~ m/^System/) {
1609            $role->is_system(1);
1610        }
1611        $role->role_mask($r->{role_mask}) if exists $r->{role_mask};
1612        $role->save;
1613    }
1614
1615    1;
1616}
1617
1618sub remove_mtviewphp {
1619    my $self = shift;
1620    my (%param) = @_;
1621
1622    require MT::Template;
1623    $self->progress($self->translate_escape('Removing Dynamic Site Bootstrapper index template...'));
1624    MT::Template->remove( { type => 'index', outfile => 'mtview.php' } );
1625    1;
1626}
1627
1628sub migrate_commenter_auth {
1629    my ($self) = shift;
1630    my (%param) = @_;
1631
1632    my $iter = MT::Blog->load_iter({ 'allow_reg_comments' => 1 });
1633    while (my $blog = $iter->()) {
1634        $blog->commenter_authenticators('TypeKey') if $blog->remote_auth_token;
1635        $blog->save;
1636    }
1637    1;
1638}
1639
1640sub upgrade_templates {
1641    my $self = shift;
1642    my (%opt) = @_;
1643
1644    my $install = $opt{Install} || 0;
1645
1646    my $updated = 0;
1647
1648    my $tmpl_list;
1649    require MT::DefaultTemplates;
1650    $tmpl_list = MT::DefaultTemplates->templates || [];
1651
1652    my $mt = MT->instance;
1653    my @arch_tmpl;
1654
1655    require MT::Template;
1656    require MT::Blog;
1657
1658    my $installer = sub {
1659        my ($val, $blog_id) = @_;
1660
1661        my $terms = {};
1662        $terms->{type} = $val->{type};
1663        $terms->{name} = $val->{name}
1664            if $val->{set} ne 'system';
1665        $terms->{blog_id} = $blog_id;
1666
1667        return 1 if MT::Template->count( $terms );
1668
1669        $self->progress($self->translate_escape("Creating new template: '[_1]'.", $val->{name}));
1670
1671        my $obj = MT::Template->new;
1672        $obj->build_dynamic(0);
1673        foreach my $v (keys %$val) {
1674            $obj->column($v, $val->{$v}) if $obj->has_column($v);
1675        }
1676        $obj->blog_id($blog_id);
1677        $obj->save or return $self->error($self->translate_escape("Error saving record: [_1].", $obj->errstr));
1678        $updated = 1;
1679        if ($val->{mappings}) {
1680            push @arch_tmpl, {
1681                template => $obj,
1682                mappings => $val->{mappings},
1683            };
1684        }
1685        return 1;
1686    };
1687
1688    for my $val (@$tmpl_list) {
1689        if (!$install) {
1690            if (!$val->{global}) {
1691                next if $val->{set} ne 'system';
1692            }
1693        }
1694
1695        my $p = $val->{plugin} || $mt;
1696        $val->{name} = $p->translate($val->{name});
1697        $val->{text} = $p->translate_templatized($val->{text});
1698
1699        if ($val->{global}) {
1700            $installer->($val, 0) or return;
1701        }
1702        else {
1703            my $iter = MT::Blog->load_iter();
1704            while (my $blog = $iter->()) {
1705                $installer->($val, $blog->id);
1706            }
1707        }
1708    }
1709
1710    if (@arch_tmpl) {
1711        $self->progress($self->translate_escape("Mapping templates to blog archive types..."));
1712        require MT::TemplateMap;
1713
1714        for my $map_set (@arch_tmpl) {
1715            my $tmpl = $map_set->{template};
1716            my $mappings = $map_set->{mappings};
1717            foreach my $map_key (keys %$mappings) {
1718                my $m = $mappings->{$map_key};
1719                my $at = $m->{archive_type};
1720                # my $preferred = $mappings->{$map_key}{preferred};
1721                my $map = MT::TemplateMap->new;
1722                $map->archive_type($at);
1723                $map->is_preferred(1);
1724                $map->template_id($tmpl->id);
1725                $map->file_template($m->{file_template}) if $m->{file_template};
1726                $map->blog_id($tmpl->blog_id);
1727                $map->save;
1728            }
1729        }
1730    }
1731
1732    $updated;
1733}
1734
1735sub rename_php_plugin_filenames {
1736    my $self = shift;
1737
1738    my $server_path = MT->instance->server_path() || '';
1739    $server_path =~ s/\/*$//;
1740    my $plugin_path = File::Spec->canonpath("$server_path/php/plugins");
1741
1742    # If PHP plugins directory doesn't exist, return without failing
1743    return 0 if !-d $plugin_path;
1744
1745    opendir(DIR, $plugin_path)
1746        or return 0;
1747    my @files = grep { /^(?:function|block)\.(.*)\.php$/ } readdir(DIR);
1748    closedir(DIR);
1749
1750    return 0 unless @files;
1751
1752    $self->progress($self->translate_escape('Renaming PHP plugin file names...'));
1753    my @error_files = ();
1754    for my $file (@files) {
1755        my $newfile = lc $file;
1756        next if $file eq $newfile;
1757        if (!rename("$plugin_path/$file", "$plugin_path/$newfile")) {
1758            push @error_files, $file;
1759        }
1760    }
1761    if ($#error_files >= 0) {
1762        $self->progress($self->translate_escape('Error renaming PHP files. Please check the Activity Log.'));
1763        MT->log(
1764            {
1765                message => $self->translate_escape("Cannot rename in [_1]: [_2].", $plugin_path, join(', ', @error_files)),
1766                level   => MT::Log::ERROR(),
1767                category => 'upgrade',
1768            }
1769        );
1770    }
1771    1;
1772}
1773
1774sub update_widget_templates {
1775    my $self = shift;
1776
1777    $self->progress($self->translate_escape('Updating widget template records...'));
1778    require MT::Template;
1779    my $iter = MT::Template->load_iter(
1780        { type => 'custom' },
1781        { 'sort' => 'name',
1782          'direction' => 'ascend' }
1783    );
1784
1785    while (my $tmpl = $iter->()) {
1786        my $name = $tmpl->name();
1787        if ($name =~ s/^(?:Widget|Sidebar): ?//) {
1788            $tmpl->name($name);
1789            $tmpl->type('widget');
1790            $tmpl->save;
1791        }
1792    }
1793    1;
1794}
1795
1796sub remove_unused_templatemap {
1797    my $self = shift;
1798
1799    $self->progress($self->translate_escape('Removing unused template maps...'));
1800
1801    require MT::Blog;
1802    require MT::TemplateMap;
1803    my $iter = MT::Blog->load_iter();
1804    while (my $blog = $iter->()) {
1805        my @blog_at = map { "'$_'" } split ',', $blog->archive_type;
1806        MT::TemplateMap->remove(
1807            { blog_id => $blog->id, archive_type => \@blog_at },
1808            { not => { archive_type => 1 } }
1809        );
1810    }
1811}
1812
1813sub remove_indexes {
1814    my $self = shift;
1815
1816    $self->progress($self->translate_escape('Removing unnecessary indexes...'));
1817
1818    my $driver = MT::Object->driver;
1819
1820    if ($driver->dbd =~ m/::Pg$|::Oracle$/) {
1821        $driver->sql([
1822            'drop index mt_asset_url',
1823            'drop index mt_asset_file_path',
1824            'drop index mt_blocklist_name',
1825            'drop index mt_entry_blog_id',
1826            'drop index mt_template_build_dynamic'
1827        ]);
1828    } elsif ($driver->dbd =~ m/::mysql$/) {
1829        $driver->sql([
1830            'drop index mt_asset_url on mt_asset',
1831            'drop index mt_asset_file_path on mt_asset',
1832            'drop index mt_blocklist_name on mt_blocklist',
1833            'drop index mt_entry_blog_id on mt_entry',
1834            'drop index mt_template_build_dynamic on mt_tempalte'
1835        ]);
1836    } elsif ($driver->dbd =~ m/::mssqlserver$/) {
1837        $driver->sql([
1838            'drop index mt_asset.mt_asset_url',
1839            'drop index mt_asset.mt_asset_file_path',
1840            'drop index mt_blocklist.mt_blocklist_name',
1841            'drop index mt_entry.mt_entry_blog_id',
1842            'drop index mt_tempalte.mt_template_build_dynamic'
1843        ]);
1844    }
1845    1;
1846}
1847
1848###  Upgrade triggers
1849
1850# we don't need these yet, but it makes me feel good to have them around
1851
1852# 'pre' triggers should execute quickly. 'post' triggers can add steps
1853# if they require processing that will take time to complete.
1854
1855sub pre_upgrade_class { 1 }
1856sub post_upgrade_class { 1 }
1857sub pre_alter_column { 1 }
1858sub post_alter_column { 1 }
1859sub pre_drop_column { 1 }
1860sub post_drop_column { 1 }
1861sub pre_add_column { 1 }
1862sub pre_index_column { 1 }
1863sub post_index_column { 1 }
1864sub pre_schema_upgrade { 1 }
1865
1866# issued last, after all table creation...
1867
1868sub post_schema_upgrade {
1869    my $self = shift;
1870    my ($from) = @_;
1871
1872    my $plugin_ver = MT->config('PluginSchemaVersion') || {};
1873    $plugin_ver->{'core'} = $from;
1874
1875    # run any functions that define a version_limit and where the schema we're
1876    # upgrading from is below that limit.
1877    foreach my $fn (keys %functions) {
1878        my $save_from = $from;
1879        {
1880            my $func = $functions{$fn};
1881
1882            if ($func->{plugin} && (UNIVERSAL::isa($func->{plugin}, 'MT::Component'))) {
1883                my $id = $func->{plugin}->id;
1884                $from = $plugin_ver->{$id};
1885            }
1886            if ($func->{version_limit}
1887                && (defined $from)
1888                && ($from < $func->{version_limit})) {
1889                $self->add_step($fn, from => $from);
1890            }
1891            elsif ($func
1892                && !exists($func->{version_limit})
1893                && !defined($from)) {
1894                $self->add_step($fn);
1895            }
1896        }
1897        $from = $save_from;
1898    }
1899
1900    1;
1901}
1902
1903sub pre_create_table {
1904    my $self = shift;
1905    my ($class) = @_;
1906    $class->driver->dbd->ddl_class->drop_sequence($class);
1907}
1908
1909sub post_create_table {
1910    my $self = shift;
1911    my ($class) = @_;
1912
1913    $class->driver->dbd->ddl_class->create_sequence($class);
1914
1915    if (!$Installing) {
1916        foreach (keys %functions) {
1917            my $func = $functions{$_};
1918            next unless $func->{on_class};
1919            $self->add_step($_) if $func->{on_class} eq $class;
1920        }
1921    }
1922
1923    1;
1924}
1925
1926# Note that this trigger only fires on BerkeleyDB for columns
1927# that are non-null or indexed.
1928
1929sub post_add_column {
1930    my $self = shift;
1931    my ($class, $col_defs) = @_;
1932
1933    if (!$Installing) {
1934        my %cols = map { $_ => 1 } @$col_defs;
1935        foreach (keys %functions) {
1936            my $func = $functions{$_};
1937            next unless $func->{on_field};
1938            if ($func->{on_field} =~ m/^\Q$class\E->(.*)/) {
1939                $self->add_step($_) if $cols{$1};
1940            }
1941        }
1942    }
1943    1;
1944}
1945
1946# Passthru routines-- passing to calling application...
1947
1948sub progress {
1949    my $self = shift;
1950    $App->progress(@_) if $App;
1951}
1952
1953sub translate_escape {
1954    my $self = shift;
1955    my $trans = MT->translate(@_);
1956    return $trans if $CLI;
1957    $trans = MT::I18N::encode_text($trans, undef, 'utf-8');
1958    return MT::Util::escape_unicode($trans);
1959}
1960
1961sub error {
1962    my $self = shift;
1963    my ($msg) = @_;
1964    $App->error(@_) if $App;
1965    return undef;
1966}
1967
1968sub add_step {
1969    my $self = shift;
1970    if ($App && (ref $App)) {
1971        $App->add_step(@_);
1972    } else {
1973        push @steps, [ @_ ];
1974    }
1975}
1976
1977# Misc utilities.
1978
1979sub detect_schema_version {
1980    my $self = shift;
1981
1982    require MT::Object;
1983    my $driver = MT::Object->driver;
1984
1985    require MT::Config;
1986    if ($driver->table_exists('MT::Config')) {
1987        return 3.2;
1988    }
1989
1990    require MT::Template;
1991    my $dyn_error_template =
1992        MT::Template->count({type => 'dynamic_error'});
1993    if ($dyn_error_template) {
1994        return 3.1;
1995    }
1996
1997    my $comment_pending_template =
1998        MT::Template->count({type => 'comment_pending'});
1999    if ($comment_pending_template) {
2000        return 3.0;
2001    }
2002
2003    require MT::TemplateMap;
2004    if ($driver->table_exists('MT::TemplateMap')) {
2005        return 2.0;
2006    }
2007
2008    1.0;
2009}
2010
2011# A note about upgrade routines:
2012#
2013# They should all be 'safe' to execute, regardless of the
2014# active schema. In other words, running them twice in a row
2015# should not cause any errors or break the schema.
2016
2017sub core_fix_type {
2018    my $self = shift;
2019    my (%param) = @_;
2020
2021    my $type = $param{type};
2022    my $class = MT->model($type);
2023
2024    my $result = $self->type_diff($type);
2025    return 1 unless $result;
2026    return 1 unless $result->{fix};
2027
2028    my $alter = $result->{alter};
2029    my $add = $result->{add};
2030    my $drop = $result->{drop};
2031    my $index = $result->{index};
2032
2033    my $driver = $class->driver;
2034    my $ddl = $driver->dbd->ddl_class;
2035    my @stmts;
2036    push @stmts, sub { $self->pre_upgrade_class($class) };
2037    push @stmts, $ddl->upgrade_begin($class);
2038    push @stmts, sub { $self->pre_create_table($class) };
2039    push @stmts, sub { $self->pre_add_column($class, $add) } if $add;
2040    push @stmts, sub { $self->pre_alter_column($class, $alter) } if $alter;
2041    push @stmts, sub { $self->pre_drop_column($class, $drop) } if $drop;
2042    push @stmts, sub { $self->pre_index_column($class, $index) } if $index;
2043    push @stmts, $ddl->fix_class($class);
2044    push @stmts, sub { $self->post_create_table($class) };
2045    push @stmts, sub { $self->post_add_column($class, $add) } if $add;
2046    push @stmts, sub { $self->post_alter_column($class, $alter) } if $alter;
2047    push @stmts, sub { $self->post_drop_column($class, $drop) } if $drop;
2048    push @stmts, sub { $self->post_index_column($class, $index) } if $index;
2049    push @stmts, $ddl->upgrade_end($class);
2050    push @stmts, sub { $self->post_upgrade_class($class) };
2051    $self->run_statements($class, @stmts);
2052}
2053
2054sub core_column_action {
2055    my $self = shift;
2056    my ($action, %param) = @_;
2057
2058    my $type = $param{type};
2059    my $class = MT->model($type);
2060    my $defs = $class->column_defs;
2061
2062    my $result = $self->type_diff($type);
2063    return 1 unless $result;
2064    my $columns = $result->{$action};
2065    return 1 unless $columns;
2066
2067    my $pre_method = "pre_${action}_column";
2068    my $post_method = "post_${action}_column";
2069    my $method = "${action}_column";
2070
2071    my $driver = $class->driver;
2072    my $ddl = $driver->dbd->ddl_class;
2073    my @stmts;
2074    push @stmts, sub { $self->pre_upgrade_class($class) };
2075    push @stmts, $ddl->upgrade_begin($class);
2076    push @stmts, sub { $self->$pre_method($class, $columns) };
2077    push @stmts, $ddl->$method($class, $_) foreach @$columns;
2078    push @stmts, sub { $self->$post_method($class, $columns) };
2079    push @stmts, $ddl->upgrade_end($class);
2080    push @stmts, sub { $self->post_upgrade_class($class) };
2081    $self->run_statements($class, @stmts);
2082}
2083
2084sub run_statements {
2085    my $self = shift;
2086    my ($class, @stmts) = @_;
2087
2088    my $driver = $class->driver;
2089    my $defs = $class->column_defs;
2090    my $dbh = $driver->rw_handle;
2091    my $mt = MT->instance;
2092
2093    my $updated = 0;
2094    if (@stmts) {
2095        $self->progress($self->translate_escape("Upgrading table for [_1] records...", $class->can('class_label') ? $class->class_label : $class));
2096        eval {
2097            foreach my $stmt (@stmts) {
2098                if (ref $stmt eq 'CODE') {
2099                    $stmt->() if !$DryRun;
2100                } else {
2101                    if ($dbh && !$DryRun) {
2102                        my $err;
2103                        $dbh->do($stmt) or $err = $dbh->errstr;
2104                        if ($err) {
2105                            # ignore drop errors; the table/sequence/constraint
2106                            # didn't exist
2107                            if (($stmt !~ m/^drop /i) && ($stmt !~ m/DROP CONSTRAINT /i)) {
2108                                die "failed to execute statement $stmt: $err";
2109                            }
2110                        }
2111                    } elsif ($dbh && $DryRun) {
2112                        $self->run_callbacks('SQL', $stmt);
2113                    }
2114                }
2115                $updated = 1;
2116            }
2117        };
2118        if ($@) {
2119            return $self->error($@);
2120        }
2121    }
2122    $updated;
2123}
2124
2125sub core_upgrade_begin {
2126    my $self = shift;
2127    my (%param) = @_;
2128    my $from_schema = $param{from};
2129    if ($from_schema) {
2130        my $cur_schema = MT->schema_version;
2131        $self->progress($self->translate_escape("Upgrading database from version [_1].", $from_schema)) if $from_schema < $cur_schema;
2132        $self->pre_schema_upgrade($from_schema);
2133    }
2134}
2135
2136sub core_upgrade_end {
2137    my $self = shift;
2138    my (%param) = @_;
2139
2140    my $from_schema = $param{from};
2141    if ($from_schema) {
2142        $self->post_schema_upgrade($from_schema);
2143    }
2144    1;
2145}
2146
2147sub core_finish {
2148    my $self = shift;
2149
2150    my $user;
2151    if ((ref $App) && ($App->{author})) {
2152        $user = $App->{author};
2153    }
2154
2155    my $cfg = MT->config;
2156    my $cur_schema = MT->instance->schema_version;
2157    my $old_schema = $cfg->SchemaVersion || 0;
2158    if ($cur_schema > $old_schema) {
2159        $self->progress($self->translate_escape("Database has been upgraded to version [_1].", $cur_schema)) ;
2160        if ($user && !$DryRun) {
2161            MT->log(MT->translate("User '[_1]' upgraded database to version [_2]", $user->name, $cur_schema));
2162        }
2163        $cfg->SchemaVersion( $cur_schema, 1 );
2164    }
2165
2166    my $plugin_schema = $cfg->PluginSchemaVersion || {};
2167    foreach my $plugin (@MT::Components) {
2168        my $ver = $plugin->schema_version;
2169        next unless $ver;
2170        next if $plugin->id eq 'core';
2171        my $old_plugin_schema = $plugin_schema->{$plugin->id} || 0;
2172        if ($old_plugin_schema && ($ver > $old_plugin_schema)) {
2173            $self->progress($self->translate_escape("Plugin '[_1]' upgraded successfully to version [_2] (schema version [_3]).", $plugin->label, $plugin->version || '-', $ver));
2174            if ($user && !$DryRun) {
2175                MT->log(MT->translate("User '[_1]' upgraded plugin '[_2]' to version [_3] (schema version [_4]).", $user->name, $plugin->label, $plugin->version || '-', $ver));
2176            }
2177        } elsif ($ver && !$old_plugin_schema) {
2178            $self->progress($self->translate_escape("Plugin '[_1]' installed successfully.", $plugin->label));
2179            if ($user && !$DryRun) {
2180                MT->log(MT->translate("User '[_1]' installed plugin '[_2]', version [_3] (schema version [_4]).", $user->name, $plugin->label, $plugin->version || '-', $ver));
2181            }
2182        }
2183        $plugin_schema->{$plugin->id} = $ver;
2184    }
2185    if (keys %$plugin_schema) {
2186        $cfg->PluginSchemaVersion($plugin_schema, 1);
2187    }
2188
2189    my $cur_version = MT->version_number;
2190    if ( !defined($cfg->MTVersion) || ( $cur_version > $cfg->MTVersion ) ) {
2191        $cfg->MTVersion( $cur_version, 1 );
2192    }
2193    $cfg->save_config unless $DryRun;
2194
2195    # do one last thing....
2196    if ((ref $App) && ($App->can('finish'))) {
2197        $App->finish();
2198    }
2199
2200    1;
2201}
2202
2203sub core_set_superuser {
2204    my $self = shift;
2205
2206    my $app = $App;
2207    my $author;
2208    if ((ref $app) && ($app->{author})) {
2209        require MT::Author;
2210        $self->progress($self->translate_escape("Setting your permissions to administrator."));
2211        $author = MT::Author->load($app->{author}->id);
2212    } elsif ($SuperUser) {
2213        require MT::Author;
2214        $self->progress($self->translate_escape("Setting your permissions to administrator."));
2215        $author = MT::Author->load($SuperUser);
2216    }
2217
2218    if ($author) {
2219        $author->is_superuser(1);
2220        $author->save or return $self->error($self->translate_escape("Error saving record: [_1].", $author->errstr));
2221    }
2222
2223    1;
2224}
2225
2226sub core_remove_unique_constraints {
2227    my $self = shift;
2228
2229    my $driver = MT::Object->driver;
2230    if (ref $driver->dbd =~ m/::Pg$/) {
2231        # category, author, permission, template
2232        $driver->sql([
2233            'alter table mt_category drop constraint mt_category_category_blog_id_key',
2234            'create index mt_category_label on mt_category (category_label)',
2235
2236            'alter table mt_author drop constraint mt_author_author_name_key',
2237            'create index mt_author_name on mt_author (author_name)',
2238            'alter table mt_permission drop constraint mt_permission_permission_blog_id_key',
2239            'create index mt_permission_blog_id on mt_permission (permission_blog_id)',
2240            'alter table mt_template drop constraint mt_template_template_blog_id_key',
2241            'create index mt_template_blog_id on mt_template (template_blog_id)'
2242        ]);
2243    } elsif (ref $driver->dbd =~ m/::mysql$/) {
2244        $driver->sql([
2245            'alter table mt_category drop index category_blog_id',
2246            'create index category_blog_id on mt_category (category_blog_id)',
2247            'create index category_label on mt_category (category_label)',
2248            'alter table mt_author drop index author_name',
2249            'create index author_name on mt_author (author_name)',
2250            'alter table mt_permission drop index permission_blog_id',
2251            'create index permission_blog_id on mt_permission (permission_blog_id)',
2252            'alter table mt_template drop index template_blog_id',
2253            'create index template_blog_id on mt_template (template_blog_id)'
2254        ]);
2255    }
2256    1;
2257}
2258
2259sub _merge_comment_response_templates_updater {
2260    my ($blog) = @_;
2261    require MT::Template;
2262    my $tmpl = MT::Template->load({ blog_id => $blog->id, type => 'comment_response' });
2263    unless ($tmpl) {
2264        $tmpl = new MT::Template;
2265        $tmpl->blog_id($blog->id);
2266        $tmpl->type('comment_response');
2267    }
2268
2269    my $confirm_template = <<'EOT';
2270<MTSetVarBlock name="page_title"><__trans phrase="Comment Posted"></MTSetVarBlock>
2271
2272<MTSetVar name="heading" value="<__trans phrase="Confirmation...">">
2273
2274<MTSetVarBlock name="message">
2275<p><__trans phrase="Your comment has been posted!"></p>
2276</MTSetVarBlock>
2277EOT
2278
2279    my $pending_template = <<'EOT';
2280<MTSetVarBlock name="page_title"><__trans phrase="Comment Pending"></MTSetVarBlock>
2281
2282<MTSetVar name="heading" value="<__trans phrase="Thank you for commenting.">">
2283
2284<MTSetVarBlock name="message">
2285<p><__trans phrase="Your comment has been received and held for approval by the blog owner."></p>
2286</MTSetVarBlock>
2287EOT
2288
2289    my $error_template = <<'EOT';
2290<MTSetVarBlock name="page_title"><__trans phrase="Comment Submission Error"></MTSetVarBlock>
2291
2292<MTSetVar name="heading" value="$page_title">
2293
2294<MTSetVarBlock name="message">
2295<p><__trans phrase="Your comment submission failed for the following reasons:"></p>
2296<blockquote>
2297    <$MTErrorMessage$>
2298</blockquote>
2299</MTSetVarBlock>
2300EOT
2301
2302    my $header_template = <<'EOT';
2303<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2304    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2305<html xmlns="http://www.w3.org/1999/xhtml" id="sixapart-standard">
2306<head>
2307    <meta http-equiv="Content-Type" content="text/html; charset=<$MTPublishCharset$>" />
2308    <meta name="generator" content="<$MTProductName version="1"$>" />
2309    <link rel="stylesheet" href="<$MTBlogURL$>styles-site.css" type="text/css" />
2310    <title>
2311    <__trans phrase="[_1]: [_2]" params="<$MTBlogName encode_html="1"$>%%<$MTGetVar name="page_title"$>">
2312    </title>
2313    <script type="text/javascript" src="<$MTBlogURL$>mt-site.js"></script>
2314</head>
2315<body class="layout-one-column comment-preview" onload="individualArchivesOnLoad(commenter_name)">
2316    <div id="container">
2317        <div id="container-inner" class="pkg">
2318            <div id="banner">
2319                <div id="banner-inner" class="pkg">
2320                    <h1 id="banner-header"><a href="<$MTBlogURL$>" accesskey="1"><$MTBlogName encode_html="1"$></a></h1>
2321                    <h2 id="banner-description"><$MTBlogDescription$></h2>
2322                </div>
2323            </div>
2324            <div id="pagebody">
2325                <div id="pagebody-inner" class="pkg">
2326                    <div id="alpha">
2327                        <div id="alpha-inner" class="pkg">
2328EOT
2329
2330    my $footer_template = <<'EOT';
2331                        </div>
2332                    </div>
2333                </div>
2334            </div>
2335        </div>
2336    </div>
2337</body>
2338</html>
2339EOT
2340
2341    my $message_template = <<'EOT';
2342<h1><$MTGetVar name="heading"$></h1>
2343
2344<$MTGetVar name="message"$>
2345
2346<p><__trans phrase="Return to the <a href="[_1]">original entry</a>." params="<$MTEntryLink$>"></p>
2347EOT
2348
2349    my $mt = MT->instance;
2350    $tmpl->name($mt->translate("Comment Response"));
2351    $tmpl->text($mt->translate_templatized(<<"EOT"));
2352<MTSetVar name="system_template" value="1">
2353<MTSetVar name="feedback_template" value="1">
2354
2355<MTIf name="body_class" eq="mt-comment-pending">
2356$pending_template
2357</MTIf>
2358
2359<MTIf name="body_class" eq="mt-comment-error">
2360$error_template
2361</MTIf>
2362
2363<MTIf name="body_class" eq="mt-comment-confirmation">
2364$confirm_template
2365</MTIf>
2366
2367$header_template
2368
2369$message_template
2370
2371$footer_template
2372EOT
2373    $tmpl->save;
2374}
2375
2376sub core_create_config_table {
2377    my $self = shift;
2378
2379    require MT::Config;
2380    my $config = MT::Config->load();
2381    if (!$config) {
2382        #$self->progress($self->translate_escape("Creating configuration record."));
2383        $config = MT::Config->new;
2384        $config->data('');
2385        $config->save or return $self->error($self->translate_escape("Error saving record: [_1].", $config->errstr));
2386    }
2387    return 1;
2388}
2389
2390sub core_set_enable_archive_paths {
2391    my $self = shift;
2392    MT->config->EnableArchivePaths(1, 1);
2393    return 1;
2394}
2395
2396sub core_create_template_maps {
2397    my $self = shift;
2398    my (%param) = @_;
2399   
2400    my $offset = $param{offset};
2401    require MT::Template;
2402    require MT::TemplateMap;
2403    require MT::Blog;
2404
2405    my $msg = $self->translate_escape("Creating template maps...");
2406    if ($offset) {
2407        my $count = MT::Template->count;
2408        return 1 unless $count;
2409        $self->progress(sprintf("$msg (%d%%)", ($offset / $count * 100)), 1);
2410    } else {
2411        $self->progress($msg, 1);
2412    }
2413
2414    my $iter = MT::Template->load_iter(undef, { offset => $offset, limit => $MAX_ROWS+1 });
2415    my $start = time;
2416    my $continue = 0;
2417    my $count = 0;
2418    while (my $tmpl = $iter->()) {
2419        $offset++;
2420        my $blog = MT::Blog->load($tmpl->blog_id);
2421        my(@at);
2422        if ($tmpl->type eq 'archive') {
2423            @at = qw( Daily Weekly Monthly );
2424        } elsif ($tmpl->type eq 'category') {
2425            @at = ('Category');
2426        } elsif ($tmpl->type eq 'page') {
2427            @at = ('Page');
2428        } elsif ($tmpl->type eq 'individual') {
2429            @at = ('Individual');
2430        } else {
2431            next;
2432        }
2433        for my $at (@at) {
2434            my $meth = 'archive_tmpl_' . lc($at);
2435            my $file_tmpl = $blog->$meth();
2436            my $existing = MT::TemplateMap->load({ blog_id => $blog->id,
2437                archive_type => $at, template_id => $tmpl->id });
2438            if (!$existing) {
2439                my $map = MT::TemplateMap->new;
2440                if ($file_tmpl) {
2441                    $self->progress($self->translate_escape("Mapping template ID [_1] to [_2] ([_3]).", $tmpl->id, $at, $file_tmpl));
2442                    $map->file_template($file_tmpl);
2443                } else {
2444                    $self->progress($self->translate_escape("Mapping template ID [_1] to [_2].", $tmpl->id, $at));
2445                }
2446                $map->archive_type($at);
2447                $map->is_preferred(1);
2448                $map->template_id($tmpl->id);
2449                $map->blog_id($tmpl->blog_id);
2450                $map->save or return $self->error($self->translate_escape("Error saving record: [_1].", $map->errstr));
2451            }
2452        }
2453        $count++;
2454        $continue = 1, last if $count == $MAX_ROWS;
2455        $continue = 1, last if time > $start + $MAX_TIME;
2456    }
2457    if ($continue) {
2458        $iter->('finish');
2459        return $offset;
2460    } else {
2461        $self->progress("$msg (100%)", 1);
2462    }
2463    1;
2464}
2465
2466sub core_update_records {
2467    my $self = shift;
2468    my (%param) = @_;
2469
2470    my $class = MT->model($param{type});
2471    return $self->error($self->translate_escape("Error loading class: [_1].", $param{type}))
2472        unless $class;
2473
2474    my $msg;
2475    my $class_label = ($class->can('class_label') ? $class->class_label : $class);
2476    if ($param{label}) {
2477        $msg = $param{label};
2478        if (ref $msg eq 'CODE') {
2479            $msg = $msg->($class_label);
2480        }
2481        $msg = $self->translate_escape($msg);
2482    } else {
2483        $msg = $self->translate_escape($param{message} || "Updating [_1] records...", $class_label);
2484    }
2485    my $offset = $param{offset};
2486    if ($offset) {
2487        my $count = $class->count;
2488        return unless $count;
2489        $self->progress(sprintf("$msg (%d%%)", ($offset/$count*100)), $param{step});
2490    } else {
2491        $self->progress($msg, $param{step});
2492    }
2493
2494    my $cond = MT->handler_to_coderef($param{condition});
2495    my $code = MT->handler_to_coderef($param{code});
2496    my $sql = $param{sql};
2497
2498    my $continue = 0;
2499    my $driver = $class->driver;
2500
2501    if ($sql && $DryRun) {
2502        $self->run_callbacks('SQL', $sql);
2503    }
2504    return 1 if $DryRun;
2505
2506    if (!$sql || !$driver->sql($sql)) {
2507        my $iter= $class->load_iter(undef, { offset => $offset, limit => $MAX_ROWS+1 });
2508        my $start = time;
2509        my @list;
2510        while (my $obj = $iter->()) {
2511            push @list, $obj;
2512            $continue = 1, last if scalar @list == $MAX_ROWS;
2513        }
2514        $iter->('finish') if $continue;
2515        for my $obj (@list) {
2516            $offset++;
2517            if ($cond) {
2518                next unless $cond->($obj, %param);
2519            }
2520            $code->($obj);
2521            use Data::Dumper;
2522            $obj->save()
2523                or return $self->error($self->translate_escape("Error saving [_1] record # [_3]: [_2]... [_4].", $class_label, $obj->errstr, $obj->id, Dumper($obj)));
2524            $continue = 1, last if time > $start + $MAX_TIME;
2525        }
2526    }
2527    if ($continue) {
2528        return $offset;
2529    } else {
2530        $self->progress("$msg (100%)", $param{step});
2531    }
2532    1;
2533}
2534
2535#############################################################################
2536
2537{
2538    my %HighASCII = (
2539        "\xc0" => 'A',    # A`
2540        "\xe0" => 'a',    # a`
2541        "\xc1" => 'A',    # A'
2542        "\xe1" => 'a',    # a'
2543        "\xc2" => 'A',    # A^
2544        "\xe2" => 'a',    # a^
2545        "\xc4" => 'Ae',   # A:
2546        "\xe4" => 'ae',   # a:
2547        "\xc3" => 'A',    # A~
2548        "\xe3" => 'a',    # a~
2549        "\xc8" => 'E',    # E`
2550        "\xe8" => 'e',    # e`
2551        "\xc9" => 'E',    # E'
2552        "\xe9" => 'e',    # e'
2553        "\xca" => 'E',    # E^
2554        "\xea" => 'e',    # e^
2555        "\xcb" => 'Ee',   # E:
2556        "\xeb" => 'ee',   # e:
2557        "\xcc" => 'I',    # I`
2558        "\xec" => 'i',    # i`
2559        "\xcd" => 'I',    # I'
2560        "\xed" => 'i',    # i'
2561        "\xce" => 'I',    # I^
2562        "\xee" => 'i',    # i^
2563        "\xcf" => 'Ie',   # I:
2564        "\xef" => 'ie',   # i:
2565        "\xd2" => 'O',    # O`
2566        "\xf2" => 'o',    # o`
2567        "\xd3" => 'O',    # O'
2568        "\xf3" => 'o',    # o'
2569        "\xd4" => 'O',    # O^
2570        "\xf4" => 'o',    # o^
2571        "\xd6" => 'Oe',   # O:
2572        "\xf6" => 'oe',   # o:
2573        "\xd5" => 'O',    # O~
2574        "\xf5" => 'o',    # o~
2575        "\xd8" => 'Oe',   # O/
2576        "\xf8" => 'oe',   # o/
2577        "\xd9" => 'U',    # U`
2578        "\xf9" => 'u',    # u`
2579        "\xda" => 'U',    # U'
2580        "\xfa" => 'u',    # u'
2581        "\xdb" => 'U',    # U^
2582        "\xfb" => 'u',    # u^
2583        "\xdc" => 'Ue',   # U:
2584        "\xfc" => 'ue',   # u:
2585        "\xc7" => 'C',    # ,C
2586        "\xe7" => 'c',    # ,c
2587        "\xd1" => 'N',    # N~
2588        "\xf1" => 'n',    # n~
2589        "\xdf" => 'ss',
2590    );
2591    my $HighASCIIRE = join '|', keys %HighASCII;
2592    sub mt32_convert_high_ascii {
2593        my($s) = @_;
2594        $s =~ s/($HighASCIIRE)/$HighASCII{$1}/g;
2595        $s;
2596    }
2597}
2598
2599sub mt32_iso_dirify {
2600    my $s = $_[0];
2601    my $sep;
2602    if ($_[1] && ($_[1] ne '1')) {
2603        $sep = $_[1];
2604    } else {
2605        $sep = '_';
2606    }
2607    $s = mt32_convert_high_ascii($s);  ## convert high-ASCII chars to 7bit.
2608    $s = lc $s;                   ## lower-case.
2609    $s = MT::Util::remove_html($s);         ## remove HTML tags.
2610    $s =~ s!&[^;\s]+;!!g;         ## remove HTML entities.
2611    $s =~ s![^\w\s]!!g;           ## remove non-word/space chars.
2612    $s =~ s! +!$sep!g;             ## change space chars to underscores.
2613    $s;   
2614}
2615
2616sub mt32_utf8_dirify {
2617    my $s = $_[0];
2618    my $sep;
2619    if ($_[1] && ($_[1] ne '1')) {
2620        $sep = $_[1];
2621    } else {
2622        $sep = '_';
2623    }
2624    $s = mt32_xliterate_utf8($s);      ## convert two-byte UTF-8 chars to 7bit ASCII
2625    $s = lc $s;                   ## lower-case.
2626    $s = MT::Util::remove_html($s);         ## remove HTML tags.
2627    $s =~ s!&[^;\s]+;!!g;         ## remove HTML entities.
2628    $s =~ s![^\w\s]!!g;           ## remove non-word/space chars.
2629    $s =~ s! +!$sep!g;             ## change space chars to underscores.
2630    $s;   
2631}
2632
2633sub mt32_dirify {
2634    ($MT::VERSION && MT->instance->{cfg}->PublishCharset =~ m/utf-?8/i)
2635        ? mt32_utf8_dirify(@_) : mt32_iso_dirify(@_);
2636}
2637
2638sub mt32_xliterate_utf8 {
2639    my ($str) = @_;
2640    my %utf8_table = (
2641          "\xc3\x80" => 'A',    # A`
2642          "\xc3\xa0" => 'a',    # a`
2643          "\xc3\x81" => 'A',    # A'
2644          "\xc3\xa1" => 'a',    # a'
2645          "\xc3\x82" => 'A',    # A^
2646          "\xc3\xa2" => 'a',    # a^
2647          "\xc3\x84" => 'Ae',   # A:
2648          "\xc3\xa4" => 'ae',   # a:
2649          "\xc3\x83" => 'A',    # A~
2650          "\xc3\xa3" => 'a',    # a~
2651          "\xc3\x88" => 'E',    # E`
2652          "\xc3\xa8" => 'e',    # e`
2653          "\xc3\x89" => 'E',    # E'
2654          "\xc3\xa9" => 'e',    # e'
2655          "\xc3\x8a" => 'E',    # E^
2656          "\xc3\xaa" => 'e',    # e^
2657          "\xc3\x8b" => 'Ee',   # E:
2658          "\xc3\xab" => 'ee',   # e:
2659          "\xc3\x8c" => 'I',    # I`
2660          "\xc3\xac" => 'i',    # i`
2661          "\xc3\x8d" => 'I',    # I'
2662          "\xc3\xad" => 'i',    # i'
2663          "\xc3\x8e" => 'I',    # I^
2664          "\xc3\xae" => 'i',    # i^
2665          "\xc3\x8f" => 'Ie',   # I:
2666          "\xc3\xaf" => 'ie',   # i:
2667          "\xc3\x92" => 'O',    # O`
2668          "\xc3\xb2" => 'o',    # o`
2669          "\xc3\x93" => 'O',    # O'
2670          "\xc3\xb3" => 'o',    # o'
2671          "\xc3\x94" => 'O',    # O^
2672          "\xc3\xb4" => 'o',    # o^
2673          "\xc3\x96" => 'Oe',   # O:
2674          "\xc3\xb6" => 'oe',   # o:
2675          "\xc3\x95" => 'O',    # O~
2676          "\xc3\xb5" => 'o',    # o~
2677          "\xc3\x98" => 'Oe',   # O/
2678          "\xc3\xb8" => 'oe',   # o/
2679          "\xc3\x99" => 'U',    # U`
2680          "\xc3\xb9" => 'u',    # u`
2681          "\xc3\x9a" => 'U',    # U'
2682          "\xc3\xba" => 'u',    # u'
2683          "\xc3\x9b" => 'U',    # U^
2684          "\xc3\xbb" => 'u',    # u^
2685          "\xc3\x9c" => 'Ue',   # U:
2686          "\xc3\xbc" => 'ue',   # u:
2687          "\xc3\x87" => 'C',    # ,C
2688          "\xc3\xa7" => 'c',    # ,c
2689          "\xc3\x91" => 'N',    # N~
2690          "\xc3\xb1" => 'n',    # n~
2691          "\xc3\x9f" => 'ss',   # double-s
2692    );
2693   
2694    $str =~ s/([\200-\377]{2})/$utf8_table{$1}||''/ge;
2695    $str;
2696}
2697
26981;
2699__END__
2700
2701=head1 NAME
2702
2703MT::Upgrade - MT class for managing system upgrades.
2704
2705=head1 SYNOPSIS
2706
2707    MT::Upgrade->do_upgrade(Install => 1);
2708
2709=head1 DESCRIPTION
2710
2711This module is responsible for handling the upgrade or installation of
2712an MT database. The framework is flexible enough for third party plugins
2713to use as well to manage their own schema (please refer to the documentation
2714in L<MT::Plugin> for more information on this).
2715
2716=head1 METHODS
2717
2718=head2 MT::Upgrade-E<gt>do_upgrade
2719
2720The main worker method for this module is I<do_upgrade>. It accepts a
2721handful of arguments, which are:
2722
2723=over 4
2724
2725=item * Install
2726
2727Specify a value of '1' to assume a new installation along with an operation
2728to install a blog and initial user.
2729
2730=item * App
2731
2732A package name or app object that can service the following methods:
2733
2734=over 4
2735
2736=item * progress($package, $message)
2737
2738Called during the upgrade operation to provide feedback with respect to
2739the operations the upgrade process is running.
2740
2741=item * error($package, $message)
2742
2743Called during the upgrade operation to communicate an error that has
2744occurred.
2745
2746=item * translate_escape
2747
2748Call this method to translate messages and phrases which are to appear
2749on the progress screen.  DO NOT use this method to messages and phrases
2750which directly are stored in database.  Use MT->translate for the purpose.
2751
2752=back
2753
2754=item * CLI
2755
2756Specified (set to '1') when invoked from a command line tool. This prevents
2757encoding response messages in the configured PublishCharset for the
2758installation.
2759
2760=item * SuperUser
2761
2762If upgrading from the command line, and running on a pre-MT 3.2 database,
2763set this to an existing author ID that should be upgrade to system
2764administrator status.
2765
2766=item * DryRun
2767
2768Specified (set to '1') to examine the database for installation/upgrade
2769needs but not actually make any physical changes to the database. This will
2770issue all the upgrade progress messages without doing the upgrade itself.
2771
2772=back
2773
2774=head1 CALLBACKS
2775
2776The upgrade module defines the following MT callbacks:
2777
2778=over 4
2779
2780=item * MT::Upgrade::SQL
2781
2782Called with each SQL statement that is executed against the database
2783as part of the upgrade process. The parameters passed to this callback are:
2784
2785    $callback, $upgrade_app, $sql_statement
2786
2787The first parameter is an L<MT::Callback> object. C<$upgrade_app> is a
2788package name or L<MT::App> object used to drive the upgrade process.
2789C<$sql_statement> is the actual SQL query that is about to be executed
2790against the database.
2791
2792=back
2793
2794=head1 UPGRADE FUNCTIONS
2795
2796The bulk of this module consists of Movable Type upgrade operations.
2797These are declared as upgrade functions, and are registered in the
2798package variabled '%functions'. (Note: the word 'function' here is
2799not meant to describe a Perl subroutine.)
2800
2801Some functions are invoked to manage the upgrade process from start
2802to finish ('core_upgrade_begin' for instance, which merely displays
2803a progress message to the calling application). The rest handle
2804schema and data transformation from one version of the MT schema to
2805another.
2806
2807Schema translation itself is handled by Movable Type automatically.
2808MT is able to check the physical schema represenation in the database
2809and compare it with the schema as defined by the L<MT::Object>-descended
2810package. If a new property is added to the L<MT::Blog> package, the
2811upgrade process sees that has happened and can issue the actual
2812'alter table' SQL statement necessary to add it to the database. The
2813'core_fix_type' function is responsible for examining a particular
2814table used by a class like L<MT::Blog> and will append additional
2815upgrade steps ('core_add_column', 'core_alter_column') that it finds
2816necessary to the upgrade workflow.
2817
2818Following the schema translation operations, the data transformation
2819functions would be used to manipulate the data as necessary from
2820an older schema to the current one. For instance, the
2821'core_create_placements' upgrade function was written to upgrade
2822really old MT schemas from the pre-2.0 release to the current schema.
2823The upgrade function is registered like this:
2824
2825    $MT::Upgrade::functions{core_create_placements} = {
2826        version_limit => 2.0,
2827        code          => \&core_update_records,
2828        priority      => 9.1,
2829        updater       => {
2830            class     => 'MT::Entry',
2831            message   => 'Creating entry category placements...',
2832            condition => sub { $_[0]->category_id },
2833            code      => sub {
2834                require MT::Placement;
2835                my $entry = shift;
2836                my $existing = MT::Placement->load({ entry_id => $entry->id,
2837                    category_id => $entry->category_id });
2838                if (!$existing) {
2839                    my $place = MT::Placement->new;
2840                    $place->entry_id($entry->id);
2841                    $place->blog_id($entry->blog_id);
2842                    $place->category_id($entry->category_id);
2843                    $place->is_primary(1);
2844                    $place->save;
2845                }
2846                $entry->category_id(0);
2847            }
2848        }
2849    };
2850
2851With MT version 2.0, the L<MT::Placement> class was introduced and
2852immediately deprecated the use of MT::Entry-E<gt>category as a result.
2853To facilitate upgrading the existing L<MT::Entry> objects this upgrade
2854function is declared such that:
2855
2856=over 4
2857
2858=item * It is limited to only run for MT schemas older than version 2.0 (the version_limit element handles this).
2859
2860=item * It operates on L<MT::Entry> objects (updater-E<gt>class element
2861declares that).
2862
2863=item * It tells the user what is happening (updater-E<gt>message).
2864
2865=item * It excludes any L<MT::Entry> objects that do not have a category_id element (updater-E<gt>condition).
2866
2867=item * It checks for an existing L<MT::Placement> relationship; if not
2868present, it creates one (updater-E<gt>code).
2869
2870=item * It empties out the category_id member of the L<MT::Entry> object
2871being upgrade to prevent it from being processed in the future
2872(updater-E<gt>code).
2873
2874=back
2875
2876For plugins, upgrade functions are assignable in the plugin registration
2877hash as documented in L<MT::Plugin>. You may also return a hashref of
2878upgrade functions from the plugin using the MT::Plugin::upgrade_functions
2879subroutine.
2880
2881Let's look at the anatomy of an upgrade function declaration:
2882
2883=over 4
2884
2885=item * version_limit (optional)
2886
2887The version_limit property allows you to declare that this upgrade
2888operation is only applicable to MT B<schema> versions below the version
2889specified.
2890
2891To register an upgrade function that is only applied to releases prior
2892to the current one, specify the current schema version as the version
2893limit. This will allow the upgrade function to run for any prior releases
2894but prevent it from running in subsequent releases.
2895
2896B<NOTE>: If you are declaring a B<plugin> upgrade function, this version
2897limit is compared with your plugin's schema version, not the Movable Type
2898schema version.
2899
2900=item * priority (optional)
2901
2902If your upgrade operation is dependent on another being done already,
2903it is possible to order them using the priority value. A lower value
2904means a higher priority.
2905
2906=item * condition (optional)
2907
2908This is a coderef parameter. If specified, it should return a true or
2909false value that determines whether the upgrade step is actually to run
2910or not.
2911
2912When called, it is given the parameters normally passed to an upgrade
2913operation (see the 'code' parameter documentation).
2914
2915=item * on_field (optional)
2916
2917If specified, this upgrade function is triggered upon the creation of
2918the field identified by this element. For instance,
2919
2920    on_field => 'MT::Foo->bar'
2921
2922This would specify that the upgrade step is only to run when the 'bar'
2923column is being added to the table that stores data for the MT::Foo
2924package.
2925
2926=item * code
2927
2928This coderef parameter is the declared handler for the upgrade
2929function. It is responsible for doing the upgrade task itself. For
2930quick operations, it is fine to do all of your work within this
2931subroutine. However, to faciliate large databases, it is important
2932to do that work in manageable portions so it doesn't time-out by
2933the web server or browser client.
2934
2935To facilitate an iterative process for your upgrade function, the
2936upgrade routine itself can yield a return value to signal the
2937upgrade process on how to proceed:
2938
2939=over 4
2940
2941=item * 0
2942
2943The upgrade function completed successfully.
2944
2945=item * undef
2946
2947upgrade routine failed with error. The error should be placed using the
2948MT::Upgrade-E<gt>error method.
2949
2950=item * E<gt> 0
2951
2952More work to do; the return value is the 'offset' parameter
2953to pass on the next invocation of the upgrade function.
2954
2955=back
2956
2957Due to the complexity of handling this kind of staged operation,
2958you will most likely want to use the prebuilt
2959'MT::Upgrade::core_update_records' routine to do most of your upgrade
2960operations that handle some or all records of a given package.
2961
2962If using the 'core_update_records' routine, you should also specify
2963an 'updater' parameter for your upgrade function.
2964
2965=item * updater
2966
2967This parameter is only used if you've specified the 'core_update_records'
2968routine (from the L<MT::Upgrade> package itself) for the 'code' element of
2969your upgrade function.
2970
2971    code => \&MT::Upgrade::core_update_records,
2972    updater => {
2973        class => 'MT::Foo',
2974        message => 'Updating Foo bars...',
2975        code => sub {
2976            my $foo = shift;
2977            $foo->bar(1);
2978        },
2979        condition => sub {
2980            my $foo = shift;
2981            !defined $foo->bar;
2982        },
2983        sql => 'update mt_foo set foo_bar = 1 where foo_bar is null'
2984    }
2985
2986This updater declaration is going to process all MT::Foo objects that
2987are available, setting the 'bar' property to 1 if it hasn't been assigned
2988a value already.
2989
2990Here's an overview of an 'updater' element:
2991
2992=over 4
2993
2994=item * class (required)
2995
2996The L<MT::Object>-descendant class to be processed.
2997
2998=item * code (required)
2999
3000A coderef to execute for B<each> record of the table. The parameter to
3001this routine is the object being processed. Following the call to your
3002subroutine, the object is saved for you, so you don't have to save
3003the object yourself.
3004
3005=item * message (optional)
3006
3007The status message to display when running this upgrade operation.
3008
3009=item * condition (optional)
3010
3011A coderef to use to test whether the current object needs to be upgraded
3012or not. This routine should return true if it is to be processed; false
3013if not. It is given the object as a parameter.
3014
3015=item * sql (optional)
3016
3017If specified, and if MT is using a SQL-based database for storing data,
3018this SQL statement is issued instead of doing the Perl-based row-by-row
3019upgrade.
3020
3021    sql => 'update mt_foo set foo_bar=1 where foo_bar is null'
3022
3023You may also specify multiple SQL statements using an array:
3024
3025    sql => [
3026        'update mt_foo set foo_bar=1 where foo_bar is null',
3027        'update mt_foo set foo_baz=2 where foo_baz is null'
3028    ]
3029
3030B<WARNING>: The 'sql' property is only meant to be used for cases where you
3031can issue simple, cross-database SQL statements. It is not advised to
3032use any vendor-specific SQL syntax. So, if you can't do that, don't specify
3033the 'sql' element at all and instead use the 'code' element exclusively
3034to do the upgrade operation.
3035
3036=back
3037
3038=back
3039
3040The declarative style of upgrade functions make it possible for MT to
3041fix itself, upgrading from any older schema version to the current one.
3042Upgrade functions are selected through an introspection process, so any
3043given upgrade operation may run a different selection of upgrade functions.
3044As such, it is important that any upgrade functions be written with this
3045in mind. Here are some general best practices to use when writing them:
3046
3047=over 4
3048
3049=item * Make them fast.
3050
3051Use the 'sql' element for a 'core_update_records' type upgrade function
3052so that SQL-based databases can be upgraded in one pass.
3053
3054=item * Make them indepedent.
3055
3056Don't assume that any other upgrade operation will have run within the
3057same application request. The upgrade process can run them in most any
3058order and across multiple application requests. You do have a guarantee
3059that a higher priority upgrade function will be run prior to a lower-priority
3060upgrade function (ie, assigning a priority of 1 will ensure it will run
3061before one with a priority of 2).
3062
3063=item * Limit them as much as possible.
3064
3065Specify a version_limit so it only runs for the proper schemas. Use the
3066condition element to bypass objects or the upgrade step altogether when
3067possible.
3068
3069=item * Repeating an upgrade function should be safe.
3070
3071This can be made possible through use of the 'condition' elements, bypassing
3072objects that have already been processed (see how the
3073'core_create_placements' upgrade function declares conditions for an
3074example).
3075
3076=item * Beware which translate method to call
3077
3078$self->translate_escape is for messages and phrases which appear on the
3079progress screen (therefore they are sent in JSON).  Use MT->translate
3080to messages and phrases which directly stored in the database.  Log messages
3081and objects' attributes fall into this category.
3082
3083=back
3084
3085=head1 AUTHOR & COPYRIGHTS
3086
3087Please see the I<MT> manpage for author, copyright, and license information.
3088
3089=cut
Note: See TracBrowser for help on using the browser.