root/branches/release-39/lib/MT/Upgrade.pm @ 2548

Revision 2548, 118.4 kB (checked in by bchoate, 18 months ago)

Updates to iterator handling and use of 'window_size' argument for load_iter method of MT::Object. BugId:79247

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