root/branches/release-34/lib/MT/Upgrade.pm @ 1866

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

Changes to store binary state to junk_status column. BugId:79280

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