root/branches/release-41/lib/MT/Upgrade.pm @ 2661

Revision 2661, 121.2 kB (checked in by mpaschal, 17 months ago)

uh, another question
BugzID: 80404

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