root/branches/release-32/lib/MT/Upgrade.pm @ 1583

Revision 1583, 104.8 kB (checked in by auno, 20 months ago)

More fixed for dynamic and renamed tbping_count column name to ping_count. BugzID:68482

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