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

Revision 2569, 119.0 kB (checked in by takayama, 18 months ago)

Fixed BugId:80106
* Added all permissions to blog administrator

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