root/branches/release-40/lib/MT/Upgrade.pm @ 2623

Revision 2623, 119.4 kB (checked in by auno, 17 months ago)

Set template_build_type for updating. BugzID:80130

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