root/branches/athena/lib/MT/Upgrade.pm @ 1092

Revision 1092, 95.7 kB (checked in by hachi, 2 years ago)

Merging release-15 to athena branch. svn merge -r59987:60375 http://svn.sixapart.com/repos/eng/movabletype/branches/release-15 .

  • Property svn:keywords set to Id Revision
Line 
1# Copyright 2001-2007 Six Apart. This code cannot be redistributed without
2# permission from www.sixapart.com.  For more information, consult your
3# Movable Type license.
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_seed_database' => {
48            code => \&seed_database,
49            priority => 4,
50        },
51        'core_upgrade_templates' => {
52            code => \&upgrade_templates,
53            priority => 5,
54        },
55        'core_upgrade_end' => {
56            code => \&core_upgrade_end,
57            priority => 9,
58        },
59        'core_finish' => {
60            code => \&core_finish,
61            priority => 10,
62        },
63    );
64}
65
66sub core_upgrade_functions {
67    return {
68        # < 2.0
69        'core_create_placements' => {
70            version_limit => 2.0,
71            priority => 9.1,
72            updater => {
73                type => 'entry',
74                label => 'Creating entry category placements...',
75                condition => sub { $_[0]->category_id },
76                code => sub {
77                    require MT::Placement;
78                    my $entry = shift;
79                    my $existing = MT::Placement->load({ entry_id => $entry->id,
80                        category_id => $entry->category_id });
81                    if (!$existing) {
82                        my $place = MT::Placement->new;
83                        $place->entry_id($entry->id);
84                        $place->blog_id($entry->blog_id);
85                        $place->category_id($entry->category_id);
86                        $place->is_primary(1);
87                        $place->save;
88                    }
89                    $entry->category_id(0);
90                },
91            },
92        },
93        'core_create_template_maps' => {
94            version_limit => 2.0,
95            code => \&core_create_template_maps,
96            priority => 9.1,
97        },
98
99        # < 2.1
100        'core_fix_placement_blog_ids' => {
101            version_limit => 2.1,
102            priority => 9.2,
103            updater => {
104                type => 'placement',
105                label => 'Updating category placements...',
106                condition => sub { !$_[0]->blog_id },
107                code => sub {
108                    require MT::Category;
109                    my $cat = MT::Category->load($_[0]->category_id);
110                    $_[0]->blog_id($cat->blog_id) if $cat;
111                },
112            },
113        },
114
115        # < 3.0
116        'core_set_blog_allow_comments' => {
117            version_limit => 3.0,
118            priority => 9.3,
119            updater => {
120                type => 'blog',
121                label => 'Assigning comment/moderation settings...',
122                condition => sub { !(defined $_[0]->allow_unreg_comments ||
123                                     defined $_[0]->allow_reg_comments ||
124                                     defined $_[0]->manual_approve_comments ||
125                                     defined $_[0]->moderate_unreg_comments) },
126                code => sub {
127                    $_[0]->allow_unreg_comments(1)
128                        unless defined $_[0]->allow_unreg_comments;
129                    $_[0]->allow_reg_comments(1)
130                        unless defined $_[0]->allow_reg_comments;
131                    $_[0]->manual_approve_commenters(0)
132                        unless defined $_[0]->manual_approve_commenters;
133                    $_[0]->moderate_unreg_comments(0)
134                        unless defined $_[0]->moderate_unreg_comments;
135                    $_[0]->moderate_pings(0)
136                        unless defined $_[0]->moderate_pings;
137                },
138                sql => [
139                    'update mt_blog
140                        set blog_allow_unreg_comments = 1
141                      where blog_allow_unreg_comments is null',
142                    'update mt_blog
143                        set blog_allow_reg_comments = 1
144                      where blog_allow_reg_comments is null',
145                    'update mt_blog
146                        set blog_manual_approve_commenters = 0
147                      where blog_manual_approve_commenters is null',
148                    'update mt_blog
149                        set blog_moderate_unreg_comments = 0
150                      where blog_moderate_unreg_comments is null'
151                ],
152            },
153        },
154
155        # < 3.2
156        'core_set_default_basename_limit' => {
157            version_limit => 3.2,
158            priority => 9.3,
159            updater => {
160                type => 'blog',
161                label => 'Setting blog basename limits...',
162                condition => sub { !$_[0]->basename_limit },
163                code => sub { $_[0]->basename_limit(15) },
164                sql => 'update mt_blog set blog_basename_limit = 15
165                         where blog_basename_limit is null
166                            or blog_basename_limit < 15',
167            },
168        },
169        'core_set_default_blog_extension' => {
170            version_limit => 3.2,
171            priority => 9.3,
172            updater => {
173                type => 'blog',
174                label => 'Setting default blog file extension...',
175                condition => sub { !$_[0]->file_extension },
176                code => sub { $_[0]->file_extension('html') },
177            },
178        },
179        'core_set_enable_archive_paths' => {
180            version_limit => 3.2,
181            code => \&core_set_enable_archive_paths,
182            priority => 9.3,
183        },
184        # whereas init_comment_visible is done for adding new a comment visible
185        # to the comment table, this task sets all comments with a visible
186        # status of 2 to 0 since we now treat this field as a boolean.
187        'core_update_comment_visible' => {
188            version_limit => 3.2,
189            priority => 9.3,
190            updater => {
191                type => 'entry',
192                label => 'Updating comment status flags...',
193                condition => sub { $_[0]->allow_comments == 2 },
194                code => sub { $_[0]->allow_comments(0) },
195                sql => 'update mt_entry set entry_allow_comments = 0
196                         where entry_allow_comments = 2',
197            },
198        },
199        'core_remove_unique_constraints' => {
200            version_limit => 3.2,
201            code => \&core_remove_unique_constraints,
202            priority => 9.3,
203        },
204        'core_create_commenter_records' => {
205            version_limit => 3.2,
206            priority => 9.3,
207            updater => {
208                type => 'comment',
209                label => 'Updating commenter records...',
210                condition => sub { $_[0]->commenter_id },
211                code => sub {
212                    my ($comment) = @_;
213                    require MT::Permission; require MT::Author;
214                    my $perm = MT::Permission->load( { author_id => $comment->commenter_id,
215                        blog_id => $comment->blog_id } );
216                    if (!$perm) {
217                        if (my $cmtr = MT::Author->load($comment->commenter_id)) {
218                            $cmtr->pending($comment->blog_id);
219                        }
220                    }
221                }
222            }
223        },
224        'core_set_blog_admins' => {
225            version_limit => 3.2,
226            priority => 9.3,
227            updater => {
228                type => 'permission',
229                label => 'Assigning blog administration permissions...',
230                condition => sub { $_[0]->author_id && $_[0]->blog_id && $_[0]->can_edit_config },
231                code => sub {
232                    require MT::Association;
233                    require MT::Role;
234                    my ($role) = MT::Role->load_by_permission("administer_blog");
235                    if ($role) {
236                        my $user = MT::Author->load($_[0]->author_id);
237                        my $blog = MT::Author->load($_[0]->blog_id);
238                        if ($user && $blog) {
239                            MT::Association->link($role => $user => $blog);
240                        }
241                    } else {
242                        $_[0]->can_administer_blog(1)
243                    }
244                },
245            }
246        },
247        'core_set_blog_allow_pings' => {
248            version_limit => 3.2,
249            priority => 9.3,
250            updater => {
251                type => 'blog',
252                label => 'Setting blog allow pings status...',
253                condition => sub { !defined $_[0]->allow_pings },
254                code => sub { $_[0]->allow_pings($_[0]->allow_pings_default) },
255                sql => 'update mt_blog set blog_allow_pings = blog_allow_pings_default
256                         where blog_allow_pings is null',
257            }
258        },
259        'core_set_superuser' => {
260            version_limit => 3.2,
261            code => \&core_set_superuser,
262            priority => 9.3,
263        },
264        'core_conflate_require_email' => {
265            version_limit => 3.2,
266            priority => 9.3,
267            updater => {
268                type => 'blog',
269                label => 'Updating blog comment email requirements...',
270                condition => sub { !$_[0]->require_comment_emails },
271                code => sub { $_[0]->require_comment_emails(1)
272                                  if !$_[0]->allow_anon_comments },
273            }
274        },
275        'core_populate_entry_basenames' => {
276            version_limit => 3.2,
277            priority => 9.3,
278            updater => {
279                type => 'entry',
280                condition => sub { !$_[0]->basename },
281                code => sub { my $entry = shift; my %args = @_;
282                    $args{from} < 3.20021
283                        ? $entry->basename(mt32_dirify($entry->title))
284                        : 1;
285                },
286                label => 'Assigning entry basenames for old entries...',
287            }
288        },
289        'core_set_api_password' => {
290            version_limit => 3.2,
291            priority => 9.3,
292            updater => {
293                type => 'author',
294                condition => sub { ($_[0]->type == 1) && (($_[0]->api_password || '') eq '') },
295                code => sub { $_[0]->api_password($_[0]->password) },
296                label => 'Updating user web services passwords...',
297            }
298        },
299        'core_create_config_table' => {
300            version_limit => 3.2,
301            code => \&core_create_config_table,
302            priority => 9.3,
303        },
304        'core_deprecate_old_style_archive_links' => {
305            version_limit => 3.2,
306            priority => 9.3,
307            updater => {
308                type => 'blog',
309                label => 'Updating blog old archive link status...',
310                condition => sub { my $blog = shift; my %args = @_;
311                                   $blog->old_style_archive_links
312                                       || $args{from} < 3.0 },
313                code => sub {
314                    my ($blog) = @_;
315                    require MT::TemplateMap;
316                    foreach my $map (MT::TemplateMap->load({ blog_id => $blog->id })) {
317                        next if $map->file_template;
318           
319                        my $at = $map->archive_type;
320                        if ($at eq 'Individual') {
321                            $map->file_template('%e%x');
322                        } elsif ($at eq 'Daily') {
323                            $map->file_template('%y_%m_%d%x');
324                        } elsif ($at eq 'Weekly') {
325                            $map->file_template('week_%y_%m_%d%x');
326                        } elsif ($at eq 'Monthly') {
327                            $map->file_template('%y_%m%x');
328                        } elsif ($at eq 'Category') {
329                            $map->file_template('cat_%C%x');
330                        }
331                        $map->save;
332                    }
333                    $blog->old_style_archive_links(0);
334                }
335            }
336        },
337        'core_set_entry_weeknumber' => {
338            version_limit => 3.20006,
339            priority => 9.3,
340            updater => {
341                type => 'entry',
342                condition => sub { ($_[0]->week_number || 0) < 54 },
343                code => sub { 1 },
344                label => 'Updating entry week numbers...',
345            }
346        },
347        'core_set_tag_permissions' => {
348            version_limit => 3.20021,
349            priority => 9.3,
350            updater => {
351                type => 'permission',
352                condition => sub { !$_[0]->can_edit_tags && !$_[0]->can_administer_blog },
353                code => sub { $_[0]->can_edit_tags($_[0]->can_edit_categories) },
354                label => 'Updating user permissions for editing tags...',
355            }
356        },
357        'core_init_blog_entry_display_defaults' => {
358            version_limit => 3.20021,
359            code => sub {
360                require MT::Permission;
361                &core_update_records
362            },
363            priority => 9.3,
364            updater => {
365                type => 'blog',
366                condition => sub { !(MT::Permission->count({
367                    blog_id => $_[0]->id, author_id => 0 })) },
368                code => sub {
369                    my $perm = new MT::Permission;
370                    $perm->entry_prefs('Advanced|Bottom');
371                    $perm->blog_id($_[0]->id);
372                    $perm->author_id(0);
373                    $perm->save;
374                },
375                label => 'Setting new entry defaults for blogs...',
376            }
377        },
378        'core_upgrade_tag_categories' => {
379            version_limit => 3.20021,
380            priority => 9.3,
381            updater => {
382                type => 'category',
383                condition => sub { ($_[0]->description||'') =~ m/<!--tag-->/ },
384                code => sub {
385                    my ($cat) = @_;
386                    require MT::Placement;
387                    require MT::Entry;
388                    my @e = MT::Entry->load(undef, {join => ['MT::Placement', 'entry_id', { category_id => $cat->id }]});
389                    my $tag_name = $cat->label;
390                    foreach my $e (@e) {
391                        my $tags = $e->tags;
392                        $e->tags($tags, $tag_name);
393                        $e->save;
394                    }
395                },
396                label => 'Migrating any "tag" categories to new tags...',
397            }
398        },
399        'core_init_blog_custom_dynamic_templates' => {
400            on_field => 'MT::Blog->custom_dynamic_templates',
401            priority => 3.1,
402            updater => {
403                type => 'blog',
404                condition => sub { !defined $_[0]->custom_dynamic_templates },
405                code => sub { $_[0]->custom_dynamic_templates('none') },
406                label => 'Assigning custom dynamic template settings...',
407                sql => q{update mt_blog
408                            set blog_custom_dynamic_templates = 'none'
409                          where blog_custom_dynamic_templates is null},
410            }
411        },
412        'core_init_author_type' => {
413            on_field => 'MT::Author->type',
414            priority => 3.1,
415            updater => {
416                type => 'author',
417                condition => sub { !$_[0]->type },
418                code => sub { $_[0]->type(1) },
419                label => 'Assigning user types...',
420                sql => 'update mt_author set author_type = 1
421                        where author_type is null or author_type = 0',
422            }
423        },
424        'core_init_category_parent' => {
425            on_field => 'MT::Category->parent',
426            priority => 3.1,
427            updater => {
428                type => 'category',
429                condition => sub { !defined $_[0]->parent },
430                code => sub { $_[0]->parent(0) },
431                label => 'Assigning category parent fields...',
432                sql => 'update mt_category set category_parent = 0
433                        where category_parent is null',
434            }
435        },
436        'core_init_template_build_dynamic' => {
437            on_field => 'MT::Template->build_dynamic',
438            priority => 3.1,
439            updater => {
440                type => 'template',
441                condition => sub { !defined $_[0]->build_dynamic },
442                code => sub { $_[0]->build_dynamic(0) },
443                label => 'Assigning template build dynamic settings...',
444                sql => 'update mt_template set template_build_dynamic = 0
445                        where template_build_dynamic is null',
446            }
447        },
448        'core_init_comment_visible' => {
449            on_field => 'MT::Comment->visible',
450            priority => 3.1,
451            updater => {
452                type => 'comment',
453                condition => sub { !defined $_[0]->visible },
454                code => sub { $_[0]->visible(1) },
455                label => 'Assigning visible status for comments...',
456                sql => 'update mt_comment set comment_visible = 1
457                        where comment_visible is null',
458            }
459        },
460        'core_init_comment_junk_status' => {
461            on_field => 'MT::Comment->junk_status',
462            priority => 3.1,
463            updater => {
464                type => 'comment',
465                condition => sub { !defined $_[0]->junk_status },
466                code => sub { $_[0]->junk_status(1) },
467                label => 'Assigning junk status for comments...',
468                sql => 'update mt_comment set comment_junk_status = 1
469                        where comment_junk_status is null',
470            }
471        },
472        'core_init_tbping_visible' => {
473            on_field => 'MT::TBPing->visible',
474            priority => 3.1,
475            updater => {
476                type => 'tbping',
477                condition => sub { !defined $_[0]->visible },
478                code => sub { $_[0]->visible(1) },
479                label => 'Assigning visible status for TrackBacks...',
480                sql => 'update mt_tbping set tbping_visible = 1
481                        where tbping_visible is null',
482            }
483        },
484        'core_init_tbping_junk_status' => {
485            on_field => 'MT::TBPing->junk_status',
486            priority => 3.1,
487            updater => {
488                type => 'tbping',
489                condition => sub { !defined $_[0]->junk_status },
490                code => sub { $_[0]->junk_status(1) },
491                label => 'Assigning junk status for TrackBacks...',
492                sql => 'update mt_tbping set tbping_junk_status = 1
493                        where tbping_junk_status is null',
494            }
495        },
496        'core_init_category_basename' => {
497            version_limit => 3.2002,
498            priority => 3.1,
499            updater => {
500                type => 'category',
501                condition => sub { !defined $_[0]->basename },
502                code => sub { my $cat = shift; my %args = @_;
503                    $args{from} < 3.20021
504                        ? $cat->basename(mt32_dirify($cat->label))
505                        : 1;
506                },
507                label => 'Assigning basename for categories...',
508            }
509        },
510        'core_set_author_status' => {
511            version_limit => 3.301,
512            priority => 3.1,
513            updater => {
514                type => 'author',
515                label => 'Assigning user status...',
516                condition => sub {
517                    ($_[0]->type == 1) && (!defined $_[0]->status)
518                },
519                code => sub {
520                    shift->status(1);
521                },
522                sql => 'update mt_author set author_status = 1
523                        where author_type = 1 and author_status is null',
524            }
525        },
526        'core_install_default_roles' => {
527            code => \&create_default_roles,
528            on_class => 'MT::Role',
529            priority => 3.1,
530        },
531        'core_migrate_permissions_to_roles' => {
532            version_limit => 3.3101,
533            priority => 3.2,
534            updater => {
535                type => 'permission',
536                label => 'Migrating permissions to roles...',
537                condition => sub { $_[0]->blog_id },
538                code => \&_migrate_permission_to_role,
539            }
540        },
541        'core_remove_dynamic_site_bootstrapper' => {
542            code => \&remove_mtviewphp,
543            version_limit => 3.3101,
544            priority => 5.1,
545        },
546        'core_fix_blob_for_mssqlserver' => {
547            code => \&fix_blob_for_mssqlserver,
548            version_limit => 4.0002,
549            priority => 3.1,
550        },
551        'core_deprecate_bitmask_permissions' => {
552            code => \&deprecate_bitmask_permissions,
553            version_limit => 4.0002,
554            priority => 3.3,
555        },
556        'core_migrate_system_privileges' => {
557            code => \&migrate_system_privileges,
558            version_limit => 4.0002,
559            priority => 3.3,
560        },
561        'core_migrate_commenter_auth' => {
562            code => \&migrate_commenter_auth,
563            version_limit => 3.3101,
564            priority => 3.1,
565        },
566        'core_populate_authored_on' => {
567            version_limit => 4.0014,
568            priority => 3.1,
569            updater => {
570                type => 'entry',
571                label => 'Populating authored and published dates for entries...',
572                condition => sub {
573                    !defined $_[0]->authored_on
574                },
575                code => sub {
576                    $_[0]->authored_on($_[0]->created_on)
577                        if !defined $_[0]->authored_on;
578                },
579                sql =>
580                    'update mt_entry set entry_authored_on = entry_created_on
581                        where entry_authored_on is null',
582            },
583        },
584        'core_update_3x_system_search_templates' => {
585            version_limit => 4.0017,
586            priority => 3.1,
587            code => \&update_3x_system_search_templates,
588        },
589        'core_rename_php_plugin_filenames' => {
590            version_limit => 4.0019,
591            priority => 3.1,
592            code => \&rename_php_plugin_filenames,
593        },
594        'core_migrate_nofollow_settings' => {
595            version_limit => 4.0020,
596            priority => 3.1,
597            code => \&migrate_nofollow_settings,
598        },
599        'core_update_widget_template' => {
600            version_limit => 4.0022,
601            priority => 3.1,
602            code => \&update_widget_templates,
603        },
604        # This upgrade step is currently necessary for PostgreSQL
605        # which doesn't support adding a column, populating the existing
606        # records with a value.
607        'core_typify_category_records' => {
608            version_limit => 4.0023,
609            priority => 3.1,
610            updater => {
611                type => 'category',
612                label => 'Classifying category records...',
613                condition => sub {
614                    !$_[0]->column('class')
615                },
616                code => sub {
617                    $_[0]->class('category');
618                },
619                sql =>
620                    "update mt_category set category_class = 'category'
621                        where category_class is null",
622            },
623        },
624        'core_typify_entry_records' => {
625            version_limit => 4.0023,
626            priority => 3.1,
627            updater => {
628                type => 'entry',
629                label => 'Classifying entry records...',
630                condition => sub {
631                    !$_[0]->column('class')
632                },
633                code => sub {
634                    $_[0]->class('entry');
635                },
636                sql =>
637                    "update mt_entry set entry_class = 'entry'
638                        where entry_class is null",
639            },
640        },
641        'core_merge_comment_response_templates' => {
642            version_limit => 4.0023,
643            priority => 3.1,
644            updater => {
645                type => 'blog',
646                label => "Merging comment system templates...",
647                code => \&_merge_comment_response_templates_updater,
648            },
649        },
650        'core_populate_default_file_template' => {
651            version_limit => 4.0023,
652            priority => 3.1,
653            updater => {
654                type => 'templatemap',
655                label => 'Populating default file template for templatemaps...',
656                condition => sub {
657                    !defined $_[0]->file_template
658                },
659                code => sub {
660                    my %default_template = (
661                        Individual => '%y/%m/%f',
662                        Category => '%c/%i',
663                    );
664                    $_[0]->file_template($default_template{$_[0]->archive_type})
665                        if !defined $_[0]->file_template && exists($default_template{$_[0]->archive_type});
666                },
667            },
668        },
669        'core_remove_unused_templatemap' => {
670            version_limit => 4.0023,
671            priority => 3.0,
672            code => \&remove_unused_templatemap,
673        },
674        'core_set_author_auth_type' => {
675            version_limit => 4.0024,
676            priority => 3.2,
677            updater => {
678                type => 'author',
679                label => 'Assigning user authentication type...',
680                condition => sub {
681                    !$_[0]->auth_type;
682                },
683                code => \&core_populate_author_auth_type,
684            },
685        },
686    }
687}
688
689sub core_populate_author_auth_type {
690    my ($u) = @_;
691    if ($u->type == 1) {
692        $u->auth_type(MT->config->AuthenticationModule || 'MT');
693    } else {
694        # for legacy OpenID plugin commenters
695        if ($u->name =~ m(^openid\n(.*)$)) {
696            my $url = $1;
697            if (eval { require Digest::MD5; 1; }) {
698                $url = Digest::MD5::md5_hex($url);
699            } else {
700                $url = substr $url, 0, 255;
701            }
702            $u->name($url);
703            $u->auth_type('OpenID');
704        }
705        elsif ($u->name =~ m!^[a-f0-9]{32}$!) {
706            # Vox OpenID URL; set auth_type to 'Vox'
707            if ($u->url =~ m!\.vox\.com/!) {
708                $u->auth_type('Vox');
709            }
710            # LJ OpenID URL; set auth_type to 'LiveJournal'
711            elsif ($u->url =~ m!\.livejournal\.com/!) {
712                $u->auth_type('LiveJournal');
713            }
714            else {
715                # Other custom auth, which for now means OpenID
716                $u->auth_type('OpenID');
717            }
718        }
719        else {
720            # Default to TypeKey for remaining plain name fields
721            $u->auth_type('TypeKey');
722        }
723    }
724}
725
726sub migrate_nofollow_settings {
727    my $self = shift;
728
729    $self->progress($self->translate_escape("Migrating Nofollow plugin settings..."));
730    require MT::PluginData;
731    my $cfg = MT->config;
732    my $plugins = $cfg->PluginSwitch || {};
733    my $nofollow_switch = $plugins->{'nofollow/nofollow.pl'};
734    my $enabled = defined $nofollow_switch ? ($nofollow_switch ? 1 : 0) : 1;
735    my $default_follow_auth_links = 1;
736
737    # For any configuration settings that exist
738    my @config = MT::PluginData->load({ plugin => 'Nofollow' });
739    my %blogs_saved;
740    foreach my $cfg (@config) {
741        if ($cfg->key =~ m/^configuration:blog:(\d+)/) {
742            my $blog = MT::Blog->load($1) or next;
743            my $setting = ($cfg->data || {})->{follow_auth_links};
744            $blog->follow_auth_links($setting) if defined $setting;
745            $blog->nofollow_urls($enabled);
746            $blog->save;
747            $blogs_saved{$blog->id} = 1;
748        } else {
749            my $setting = ($cfg->data || {})->{follow_auth_links};
750            $default_follow_auth_links = $setting if defined $setting;
751        }
752    }
753    $_->remove for @config;
754
755    my $blog_iter = MT::Blog->load_iter;
756    while (my $blog = $blog_iter->()) {
757        next if exists $blogs_saved{$blog->id};
758        $blog->nofollow_urls($enabled);
759        $blog->follow_auth_links($default_follow_auth_links);
760        $blog->save;
761    }
762
763    # Forcibly disable nofollow plugin now since this has become
764    # a core function.
765    $cfg->PluginSwitch('nofollow/nofollow.pl=0', 1);
766    $cfg->save_config();
767
768    return 0;
769}
770
771sub update_3x_system_search_templates {
772    my $self = shift;
773
774    require MT::Template;
775    $self->progress($self->translate_escape('Updating system search template records...'));
776    my $tmpl_iter = MT::Template->load_iter({
777        type => 'search_template',
778    });
779    my %blogs;
780    while (my $tmpl = $tmpl_iter->()) {
781        $blogs{$tmpl->blog_id} = $tmpl->id;
782        $tmpl->type('search_results');
783        $tmpl->save;
784    }
785    # for any old 'search_template' system templates, remove the
786    # newly installed 'search_results' template.
787    foreach my $blog_id (keys %blogs) {
788        my $tmpl = MT::Template->load({ type => 'search_results',
789            blog_id => $blog_id, id => $blogs{$blog_id} }, {
790            not => { id => 1 } });
791        $tmpl->remove if $tmpl;
792    }
793
794    my %terms;
795    my %args;
796    if (my @blog_ids = keys %blogs) {
797        $terms{id} = \@blog_ids;
798        $args{not} = { id => 1 };
799    }
800    my $blog_iter = MT::Blog->load_iter(\%terms, \%args);
801    # These blogs does not have search results - upgrades from 3.2 or before.
802    while (my $blog = $blog_iter->()) {
803        my $tmpl = MT::Template->load(
804          { type => 'search_results', blog_id => $blog->id,});
805        $tmpl->remove if $tmpl;
806    }
807    0;
808}
809
810my $perm_role_names = {
811    4096 => 'Blog Administrator',  # administer_blog
812    30687 => 'Blog Administrator', # 32767 - 2048(not comment) - 32(reserved) = all permissions in MT3.3
813    14303 => 'Blog Administrator', # 16383 - 2048(not comment) - 32(reserved) = all permissions in MT3.2
814    2 => 'Writer', # post
815    6 => 'Writer (can upload)', # post + upload
816    17032 => 'Editor',  # edit_all_posts + edit_tags + edit_categories + rebuild
817    17036 => 'Editor (can upload)', # Editor + upload
818    144 => 'Designer', # edit_templates + rebuild
819    17292 => 'Publisher', # Editor (can upload) + send_notifications
820};
821
822{
823    my $full_perm_mask = 0;
824    my %LegacyPerms = (
825        # System-wide permissions
826        #[ 2**0, 'administer', 'System Administrator', 2, 'system' ],
827        #[ 2**1, 'create_blog', 'Create Blogs', 2, 'system' ],
828        #[ 2**2, 'view_log', 'View System Activity Log', 2, 'system' ],
829        #[ 2**3, 'manage_plugins', 'Manage Plugins', 'system' ],
830 
831        # Blog-specific permissions:
832        # The order here is the same order they are presented on the
833        # role definition screen.
834        2**0 => 'comment',# 'Add Comments', 1, 'blog'],
835        2**12 => 'administer_blog',# 'Blog Administrator', 1, 'blog'],
836        2**6 => 'edit_config',# 'Configure Blog', 1, 'blog'],
837        2**3 => 'edit_all_posts',# 'Edit All Entries', 1, 'blog'],
838        2**4 => 'edit_templates',# 'Manage Templates', 1, 'blog'],
839        2**2 => 'upload',# 'Upload File', 1, 'blog'],
840        2**1 => 'post',# 'Create Entry', 1, 'blog'],
841        2**16 => 'edit_assets',# 'Manage Assets', 1, 'blog'],
842        2**15 => 'save_image_defaults',# 'Save Image Defaults', 1, 'blog'],
843        2**9 => 'edit_categories',# 'Add/Manage Categories', 1, 'blog'],
844        2**14 => 'edit_tags',# 'Manage Tags', 1, 'blog'],
845        2**10 => 'edit_notifications',# 'Manage Notification List', 1, 'blog'],
846        2**8 => 'send_notifications',# 'Send Notifications', 1, 'blog'],
847        2**13 => 'view_blog_log',# 'View Activity Log', 1, 'blog'],
848        #[ 2**17, 'publish_post', 'Publish Post', 1, 'blog'],
849        #[ 2**18, 'manage_feedback', 'Manage Feedback', 1, 'blog'],
850        #[ 2**19, 'set_publish_paths', 'Set Publishing Paths', 1, 'blog'],
851        #[ 2**20, 'manage_pages', 'Manage Pages', 1, 'blog'],
852        # 2**5 == 32 is deprecated; reserved for future use
853        2**7 => 'rebuild',# 'Rebuild Files', 1, 'blog'],
854        # Not a real permission but a denial thereeof; unlisted because it
855        # has no label.
856        2**11 => 'not_comment',# '', 1, 'blog'],
857    );
858
859
860sub _migrate_permission_to_role {
861    my $perm = shift;
862
863    return unless $perm->author_id;
864    my $user = MT::Author->load($perm->author_id);
865    if (!$user) {
866        $perm->remove;
867        return;
868    }
869    # Don't bother with non-AUTHOR types
870    return unless $user->type == 1;
871
872    my $role_mask = $perm->role_mask;
873    $role_mask -= 32 if (32 & $role_mask) == 32; # for permissions before 3.2
874
875    if (!$full_perm_mask) {
876        # only consider blog permissions that are supported (exclude
877        # now reserved permission bits like 32).
878        foreach my $key (keys %LegacyPerms) {
879            next if $LegacyPerms{$key} =~ m/^not_/; # skip exclusion permissions
880            $full_perm_mask |= $key;
881        }
882    }
883
884    $role_mask = $full_perm_mask & $role_mask;
885
886    # '0' permission, not used for permissions, just prefs
887    return unless $role_mask;
888
889    my $name;
890    $name = MT->translate($perm_role_names->{$role_mask})
891        if $perm_role_names->{$role_mask};
892    $name ||= MT->translate("Custom ([_1])", $role_mask);
893    require MT::Role;
894    my $role = MT::Role->load({ name => $name });
895    if ($role) {
896        if (($role->role_mask != $role_mask) && 
897            ((4096 != $role->role_mask) && (30687 != $role_mask))) {
898            $role = undef;
899        }
900    }
901    unless ($role) {
902        $role = new MT::Role;
903        $role->name($name);
904        $role->description(MT->translate("This role was generated by Movable Type upon upgrade."));
905        $role->role_mask($role_mask);
906        $role->save;
907    }
908    my $blog = MT::Blog->load($perm->blog_id);
909    $user->add_role($role, $blog) if $blog;
910}
911
912sub _process_masks {
913    my $self = shift;
914    my ($perm) = @_;
915
916    my $mask = $perm->role_mask;
917    return unless $mask;
918    my @perms;
919    for my $key (keys %LegacyPerms) {
920        if (int($mask) & int($key)) {
921            if (2 eq $key) { # post
922                push @perms, 'create_post', 'publish_post';
923            } elsif (64 eq $key) { #edit_config
924                push @perms, 'edit_config', 'set_publish_paths', 'manage_feedback';
925            } elsif (4096 eq $key) { #adminsiter_blog
926                push @perms, 'administer_blog', 'manage_pages';
927            } elsif (2048 eq $key) { #not_comment
928                $perm->restrictions("'comment'");
929            } else {
930                push @perms, $LegacyPerms{$key};
931            }
932        }
933    }
934    my $perm_str = scalar(@perms) ? "'" . join("','", @perms) . "'" : q();
935    $perm->permissions($perm_str);
936    $perm->role_mask(0); ## remove legacy permissions
937    $perm;
938}
939
940sub deprecate_bitmask_permissions {
941    my $self = shift;
942   
943    require MT::Permission;
944    my $perm_iter = MT::Permission->load_iter;
945    $self->progress($self->translate_escape('Migrating permission records to new structure...'));
946    while (my $perm = $perm_iter->()) {
947        if ($self->_process_masks($perm)) {
948            $perm->save;
949        }
950    }
951
952    require MT::Role;
953    my $role_iter = MT::Role->load_iter;
954    $self->progress($self->translate_escape('Migrating role records to new structure...'));
955    while (my $role = $role_iter->()) {
956        if ($self->_process_masks($role)) {
957            $role->save;
958        }
959    }
960}
961}
962
963sub migrate_system_privileges {
964    my $self = shift;
965
966    require MT::Permission;
967    my $author_iter = MT::Author->load_iter({ type => MT::Author::AUTHOR() });
968    $self->progress($self->translate_escape('Migrating system level permissions to new structure...'));
969    while (my $author = $author_iter->()) {
970        my @perms;
971        push @perms, 'administer' if $author->column('is_superuser');
972        push @perms, 'create_blog' if $author->column('can_create_blog') || $author->column('is_superuser');
973        push @perms, 'view_log' if $author->column('can_view_log') || $author->column('is_superuser');
974        push @perms, 'manage_plugins' if $author->column('is_superuser');
975        if (@perms) {
976            my $perm = MT::Permission->load({ author_id => $author->id,
977                blog_id => 0 });
978            if (!$perm) {
979                $perm = MT::Permission->new;
980                $perm->author_id($author->id);
981                $perm->blog_id(0);
982            }
983            $perm->set_these_permissions(@perms);
984            $perm->save;
985        }
986    }
987}
988
989sub init {
990    my $pkg = shift;
991    unless (%classes) {
992        my $types = MT->registry('object_types');
993        foreach my $type (keys %$types) {
994            $classes{$type} = $types->{$type};
995        }
996    }
997    my $fns = MT::Component->registry('upgrade_functions') || [];
998    foreach my $fn_set (@$fns) {
999        %functions = ( %functions, %{ $fn_set } );
1000    }
1001}
1002
1003# Step execution...
1004
1005# iterate routines:
1006#     no parameters, start with offset == 0
1007#     offset parameter, pass thru
1008#     if routine returns 0, routine is done
1009#     if routine returns undef, routine failed
1010#     if routine returns > 0, that's the new offset
1011
1012sub run_step {
1013    my $self = shift;
1014    my ($step) = @_;
1015    my ($name, %param) = @$step;
1016
1017    if (my $fn = $functions{$name}) {
1018        local $MT::CallbacksEnabled = 0;
1019        if (my $cond = $fn->{condition}) {
1020            $cond = MT->handler_to_coderef($cond);
1021            next unless $cond->($self, %param);
1022        }
1023        my %update_params;
1024        if ($fn->{updater}) {
1025            %update_params = %{$fn->{updater}};
1026            $fn->{code} ||= \&core_update_records;
1027        }
1028        my $code = $fn->{code} || $fn->{handler};
1029        $code = MT->handler_to_coderef($code);
1030        my $result = $code->($self, %param, %update_params, step => $name);
1031        if ((defined $result) && ($result > 1)) {
1032            $param{offset} = $result; $result = 1;
1033            $self->add_step($name, %param);
1034        }
1035        return $result;
1036    } else {
1037        return $self->error($self->translate_escape("Invalid upgrade function: [_1].", $name));
1038    }
1039    0;
1040}
1041
1042sub run_callbacks {
1043    my $self = shift;
1044    my ($cb, @param) = @_;
1045    local $MT::CallbacksEnabled = 1;
1046    MT->run_callbacks('MT::Upgrade::' . $cb, $self, @param);
1047}
1048
1049# Main "do" interface for controlling apparatus
1050
1051sub do_upgrade {
1052    my $self = shift;
1053    my (%opt) = @_;
1054
1055    $self->init;
1056
1057    my $harnessed = ref $opt{App} && (UNIVERSAL::can($opt{App}, 'add_step'));
1058
1059    local $App = $opt{App};
1060    local $DryRun = $opt{DryRun};
1061    local $SuperUser = $opt{SuperUser} || '';
1062    local $CLI = $opt{CLI} || '';
1063
1064    @steps = ();
1065    if ($opt{Install}) {
1066        my %init_params = (%{$opt{User} || {}}, %{$opt{Blog} || {}});
1067        $self->install_database(\%init_params);
1068    } else {
1069        $self->upgrade_database();
1070    }
1071
1072    # no app is running the show, so we must!
1073    if (!$harnessed) {
1074        # set these limits very high since we're running unharnessed
1075        $MAX_TIME = 10000000;
1076        $MAX_ROWS = 300;
1077        my $fn = \%MT::Upgrade::functions;
1078        my @these_steps = @steps;
1079        while (@these_steps) {
1080            my $step = shift @these_steps;
1081            @steps = ();
1082            $self->run_step($step);
1083            if (@steps) {
1084                push @these_steps, @steps;
1085                @these_steps = sort { $fn->{$a->[0]}->{priority} <=>
1086                                      $fn->{$b->[0]}->{priority} } @these_steps;
1087            }
1088        }
1089        return 1;
1090    } else {
1091        return \@steps;
1092    }
1093}
1094
1095sub upgrade_database {
1096    my $self = shift;
1097
1098    my $config_schema_ver;
1099    my $schema_ver;
1100    if ($config_schema_ver = MT->instance->config('SchemaVersion')) {
1101        my $needs_upgrade;
1102        $needs_upgrade = 1 if $config_schema_ver < MT->schema_version;
1103        if (!$needs_upgrade) {
1104            foreach (@MT::Components) {
1105                $needs_upgrade = 1 if $_->needs_upgrade;
1106            }
1107        }
1108        return 1 unless $needs_upgrade;
1109        $schema_ver = $config_schema_ver;
1110    } else {
1111        $schema_ver = $self->detect_schema_version;
1112    }
1113
1114    # this will add steps to upgrade all tables that need it...
1115    $self->add_step("core_upgrade_begin", from => $schema_ver);
1116    $self->check_schema;
1117    $self->add_step('core_upgrade_templates');
1118    $self->add_step('core_upgrade_end', from => $schema_ver);
1119    $self->add_step('core_finish');
1120    1;
1121}
1122
1123sub install_database {
1124    my $self = shift;
1125    my ($user) = @_;
1126
1127    # this will add steps to install all tables...
1128    $self->check_schema;
1129    # this will populate them...
1130    $self->add_step('core_seed_database', %$user);
1131    $self->add_step('core_finish');
1132    1;
1133}
1134
1135sub check_schema {
1136    my $self = shift;
1137    my $class;
1138    foreach my $type (keys %classes) {
1139        $class = MT->model($type)
1140            or return $self->error($self->translate_escape("Error loading class [_1].", $type));
1141        $self->check_type($type);
1142    }
1143    1;
1144}
1145
1146sub check_type {
1147    my $self = shift;
1148    my ($type) = @_;
1149
1150    my $class = MT->model($type);
1151    if (my $result = $self->type_diff($type)) {
1152        if ($result->{fix}) {
1153            $self->add_step('core_fix_type', type => $type);
1154        } else {
1155            $self->add_step('core_add_column', type => $type)
1156                if $result->{add};
1157            $self->add_step('core_alter_column', type => $type)
1158                if $result->{alter};
1159            $self->add_step('core_drop_column', type => $type)
1160                if $result->{drop};
1161        }
1162    }
1163    1;
1164}
1165
1166sub type_diff {
1167    my $self = shift;
1168    my ($type) = @_;
1169
1170    my $class = MT->model($type) or return;
1171
1172    my $table = $class->datasource;
1173    my $defs = $class->column_defs;
1174
1175    my $ddl = $class->driver->dbd->ddl_class;
1176    my $db_defs = $ddl->column_defs($class);
1177
1178    # now, compare $defs and $db_defs;
1179    # here are the scenarios
1180    #   1. we find something in $defs that isn't in $db_defs
1181    #      -- column should be inserted. this may trigger a process
1182    #   2. we find something in $db_defs that isn't in $defs
1183    #      -- this is a-ok. user may have added a column.
1184    #   3. we find a difference between $defs and $db_defs for a field
1185    #      a. type differs; this may trigger a process
1186    #      b. type is same, but null property differs; this may
1187    #         trigger a process
1188    #      c. type is same, but size differs; this may trigger a process
1189    #      d. key differs
1190    #      e. auto differs (auto-increment)
1191    #   4. table doesn't exist and must be created
1192
1193    my $fix_class;
1194    $fix_class = 1 unless defined $db_defs;
1195
1196    # we're only scanning defined columns; we don't care about
1197    # columns that are unique to the table.
1198    my (@cols_to_add, @cols_to_alter, @cols_to_drop);
1199
1200    if (!$fix_class) {
1201        my @def_cols = keys %$defs;
1202
1203        foreach my $col (@def_cols) {
1204            my $col_def = $defs->{$col};
1205            next if !defined $col_def;
1206
1207            $col_def->{name} = $col;
1208
1209            my $db_def = $db_defs->{$col};
1210
1211            if (!$db_def) {
1212                # column is missing altogether; we're going to have to add it
1213                push @cols_to_add, $col_def;
1214            } else {
1215                if (($col_def->{type} eq 'string')
1216                 && ($db_def->{type} eq 'string')
1217                 && ($col_def->{size} <= $db_def->{size})) {
1218                    if (($col_def->{not_null} || 0) != ($db_def->{not_null} || 0)) {
1219                        push @cols_to_alter, $col_def;
1220                    }
1221                } elsif ($ddl->type2db($col_def)
1222                      ne $ddl->type2db($db_def)) {
1223                    # types are different
1224                    # don't bother if the database has sufficient
1225                    # capacity for this field
1226                    next if ($db_def->{type} eq 'integer')
1227                         && ($col_def->{type} eq 'smallint'
1228                          || $col_def->{type} eq 'boolean');
1229                    push @cols_to_alter, $col_def;
1230                } elsif (($col_def->{not_null} || 0) != ($db_def->{not_null} || 0)) {
1231                    push @cols_to_alter, $col_def;
1232                }
1233            }
1234        }
1235    }
1236
1237    if ($fix_class || @cols_to_add || @cols_to_alter || @cols_to_drop) {
1238        my %param;
1239        $param{drop} = \@cols_to_drop if @cols_to_drop;
1240        $param{add} = \@cols_to_add if @cols_to_add;
1241        $param{alter} = \@cols_to_alter if @cols_to_alter;
1242        $param{fix} = $fix_class;
1243        if ((@cols_to_add && !$ddl->can_add_column) ||
1244            (@cols_to_alter && !$ddl->can_alter_column) || 
1245            (@cols_to_drop && !$ddl->can_drop_column)) {
1246            $param{fix} = 1;
1247        }
1248        return \%param;
1249    }
1250    undef;
1251}
1252
1253sub seed_database {
1254    my $self = shift;
1255    my (%param) = @_;
1256
1257    require MT::Author;
1258    return undef if MT::Author->count;
1259
1260    $self->progress($self->translate_escape("Creating initial blog and user records..."));
1261
1262    local $MT::CallbacksEnabled = 1;
1263
1264    require MT::L10N;
1265    my $lang = exists $param{user_lang} ? $param{user_lang} : MT->config->DefaultLanguage;
1266    my $LH = MT::L10N->get_handle($lang);
1267
1268    # TBD: parameter for username/password provided by user from $app
1269    use URI::Escape;
1270    my $author = MT::Author->new;
1271    $author->name(exists $param{user_name} ? uri_unescape($param{user_name}) : 'Melody');
1272    $author->type(MT::Author::AUTHOR());
1273    $author->set_password(exists $param{user_password} ? uri_unescape($param{user_password}) : 'Nelson');
1274    $author->email(exists $param{user_email} ? uri_unescape($param{user_email}) : '');
1275    $author->hint(exists $param{user_hint} ? uri_unescape($param{user_hint}) : '');
1276    $author->nickname(exists $param{user_nickname} ? uri_unescape($param{user_nickname}) : '');
1277    $author->is_superuser(1);
1278    $author->can_create_blog(1);
1279    $author->can_view_log(1);
1280    $author->can_manage_plugins(1);
1281    $author->preferred_language($lang);
1282    $author->external_id(MT::Author->pack_external_id($param{user_external_id})) if exists $param{user_external_id};
1283    $author->auth_type(MT->config->AuthenticationModule);
1284    $author->save or return $self->error($self->translate_escape("Error saving record: [_1].", $author->errstr));
1285    $App->{author} = $author if ref $App;
1286
1287    $self->create_default_roles(%param);
1288
1289    require MT::Blog;
1290    my $blog = MT::Blog->create_default_blog(MT->translate('First Blog'))
1291        or return $self->error($self->translate_escape("Error saving record: [_1].", MT::Blog->errstr));
1292    $blog->site_path(exists $param{blog_path} ? uri_unescape($param{blog_path}) : '');
1293    $blog->site_url(exists $param{blog_url} ? uri_unescape($param{blog_url}) : '');
1294    $blog->name(exists $param{blog_name} ? uri_unescape($param{blog_name}) : '');
1295    $blog->server_offset(exists $param{blog_timezone} ? ($param{blog_timezone} || 0) : 0);
1296    $blog->save;
1297
1298    # Create an initial entry and comment for this blog
1299    require MT::Entry;
1300    my $entry = MT::Entry->new;
1301    $entry->blog_id($blog->id);
1302    $entry->title(MT->translate("I just finished installing Movable Type [_1]!", MT->product_version));
1303    $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!"));
1304    $entry->author_id($author->id);
1305    $entry->status(MT::Entry::RELEASE());
1306    $entry->save
1307        or return $self->error($self->translate_escape("Error saving record: [_1].", MT::Entry->errstr));
1308
1309    require MT::Comment;
1310    my $comment = MT::Comment->new;
1311    $comment->entry_id($entry->id);
1312    $comment->blog_id($blog->id);
1313    $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."));
1314    $comment->visible(1);
1315    $comment->junk_status(1);
1316    $comment->author(exists $param{user_nickname} ? uri_unescape($param{user_nickname}) : undef);
1317    $comment->save
1318        or return $self->error($self->translate_escape("Error saving record: [_1].", MT::Comment->errstr));
1319
1320    require MT::Association;
1321    require MT::Role;
1322    my ($blog_admin_role) = MT::Role->load_by_permission("administer_blog");
1323    MT::Association->link( $blog => $blog_admin_role => $author );
1324
1325    1;
1326}
1327
1328## Translation
1329# translate('Blog Administrator')
1330# translate('Can administer the blog.')
1331# translate('Editor')
1332# translate('Can upload files, edit all entries/categories/tags on a blog and publish the blog.')
1333# translate('Author')
1334# translate('Can create entries, edit their own, upload files and publish.')
1335# translate('Designer')
1336# translate('Can edit, manage and publish blog templates.')
1337# translate('Webmaster')
1338# translate('Can manage pages and publish blog templates.')
1339# translate('Contributor')
1340# translate('Can create entries, edit their own and comment.')
1341# translate('Moderator')
1342# translate('Can comment and manage feedback.')
1343# translate('Commenter')
1344# translate('Can comment.')
1345
1346sub create_default_roles {
1347    my $self = shift;
1348    my (%param) = @_;
1349
1350    my @default_roles = (
1351        { name => 'Blog Administrator',
1352          description => 'Can administer the blog.',
1353          role_mask => 2**12,
1354          perms => ['administer_blog'] },
1355        { name => 'Editor',
1356          description => 'Can upload files, edit all entries/categories/tags on a blog and publish the blog.',
1357          perms => ['comment', 'create_post', 'publish_post', 'edit_all_posts', 'edit_categories', 'edit_tags', 'manage_pages',
1358                    'rebuild', 'upload', 'send_notifications', 'manage_feedback'], },
1359        { name => 'Author',
1360          description => 'Can create entries, edit their own, upload files and publish.',
1361          perms => ['comment', 'create_post', 'publish_post', 'upload', 'send_notifications'], },
1362        { name => 'Designer',
1363          description => 'Can edit, manage and publish blog templates.',
1364          role_mask => (2**4 + 2**7),
1365          perms => ['edit_templates', 'rebuild'] },
1366        { name => 'Webmaster',
1367          description => 'Can manage pages and publish blog templates.',
1368          perms => ['manage_pages', 'rebuild'] },
1369        { name => 'Contributor',
1370          description => 'Can create entries, edit their own and comment.',
1371          perms => ['comment', 'create_post'], },
1372        { name => 'Moderator',
1373          description => 'Can comment and manage feedback.',
1374          perms => ['comment', 'manage_feedback'], },
1375        { name => 'Commenter',
1376          description => 'Can comment.',
1377          role_mask => 2**0,
1378          perms => ['comment'], },
1379    );
1380
1381    require MT::Role;
1382    return if MT::Role->count();
1383
1384    foreach my $r (@default_roles) {
1385        my $role = MT::Role->new();
1386        $role->name(MT->translate($r->{name}));
1387        $role->description(MT->translate($r->{description}));
1388        $role->clear_full_permissions;
1389        $role->set_these_permissions($r->{perms});
1390        if ($r->{name} =~ m/^System/) {
1391            $role->is_system(1);
1392        }
1393        $role->role_mask($r->{role_mask}) if exists $r->{role_mask};
1394        $role->save;
1395    }
1396
1397    1;
1398}
1399
1400sub remove_mtviewphp {
1401    my $self = shift;
1402    my (%param) = @_;
1403
1404    require MT::Template;
1405    $self->progress($self->translate_escape('Removing Dynamic Site Bootstrapper index template...'));
1406    MT::Template->remove( { type => 'index', outfile => 'mtview.php' } );
1407    1;
1408}
1409
1410sub fix_blob_for_mssqlserver {
1411    my $self = shift;
1412    my (%param) = @_;
1413
1414    my $driver = MT::Object->driver;
1415    return unless $driver =~ /MSSQLServer/i;
1416
1417    $self->progress($self->translate_escape('Fixing binary data for Microsoft SQL Server storage...'));
1418
1419    require MT::Session;
1420    my $sess_iter = MT::Session->load_iter();
1421    while (my $sess_obj = $sess_iter->()) {
1422        $sess_obj->data(pack('H*', $sess_obj->data)) if defined $sess_obj->data;
1423        $sess_obj->save;
1424    }
1425
1426    require MT::PluginData;
1427    my $pd_iter = MT::PluginData->load_iter();
1428    while (my $pd_obj = $pd_iter->()) {
1429        $pd_obj->data(pack('H*', $pd_obj->data)) if defined $pd_obj->data;
1430        $pd_obj->save;
1431    }
1432
1433    1;
1434}
1435
1436sub migrate_commenter_auth {
1437    my ($self) = shift;
1438    my (%param) = @_;
1439
1440    my $iter = MT::Blog->load_iter({ 'allow_reg_comments' => 1 });
1441    while (my $blog = $iter->()) {
1442        $blog->commenter_authenticators('TypeKey') if $blog->remote_auth_token;
1443        $blog->save;
1444    }
1445    1;
1446}
1447
1448sub upgrade_templates {
1449    my $self = shift;
1450    my (%opt) = @_;
1451
1452    my $install = $opt{Install} || 0;
1453
1454    my $updated = 0;
1455
1456    my $tmpl_list;
1457    require MT::DefaultTemplates;
1458    $tmpl_list = MT::DefaultTemplates->templates || [];
1459
1460    my $mt = MT->instance;
1461    my @arch_tmpl;
1462
1463    require MT::Template;
1464    require MT::Blog;
1465
1466    for my $val (@$tmpl_list) {
1467        if (!$install) {
1468            next if $val->{type} =~ m/^(archive|individual|page|category|index|custom)$/;
1469        }
1470
1471        $val->{name} = $mt->translate($val->{name});
1472        $val->{text} = $mt->translate_templatized($val->{text});
1473
1474        my $terms = {};
1475        $terms->{type} = $val->{type};
1476        $terms->{name} = $val->{name}
1477            if $val->{type} =~ m/^(archive|individual|page|category|index|custom)$/;
1478        my @exists = MT::Template->load( $terms,
1479                                         { limit => 1 } );
1480        next if @exists;
1481
1482        $self->progress($self->translate_escape("Creating new template: '[_1]'.", $val->{name}));
1483
1484        my $iter = MT::Blog->load_iter();
1485        while (my $blog = $iter->()) {
1486            my $obj = MT::Template->new;
1487            $obj->build_dynamic(0);
1488            foreach my $v (keys %$val) {
1489                $obj->column($v, $val->{$v}) if $obj->has_column($v);
1490            }
1491            $obj->blog_id($blog->id);
1492            $obj->save or return $self->error($self->translate_escape("Error saving record: [_1].", $obj->errstr));
1493            $updated = 1;
1494            if ($val->{type} eq 'archive' || $val->{type} eq 'individual' ||
1495                $val->{type} eq 'page' || $val->{type} eq 'category') {
1496                push @arch_tmpl, $obj;
1497            }
1498        }
1499    }
1500
1501    if (@arch_tmpl) {
1502        $self->progress($self->translate_escape("Mapping templates to blog archive types..."));
1503        require MT::TemplateMap;
1504   
1505        for my $tmpl (@arch_tmpl) {
1506            my(@at);
1507            if ($tmpl->type eq 'archive') {
1508                @at = qw( Daily Weekly Monthly Category-Monthly Author-Monthly Category );
1509            } elsif ($tmpl->type eq 'individual') {
1510                @at = qw( Individual );
1511            } elsif ($tmpl->type eq 'page') {
1512                @at = qw( Page );
1513            }
1514            for my $at (@at) {
1515                my $map = MT::TemplateMap->new;
1516                $map->archive_type($at);
1517                $map->is_preferred(1);
1518                $map->template_id($tmpl->id);
1519                $map->blog_id($tmpl->blog_id);
1520                $map->save or return $self->error($self->translate_escape("Error saving record: [_1].", $map->errstr));
1521            }
1522        }
1523    }
1524
1525    $updated;
1526}
1527
1528sub rename_php_plugin_filenames {
1529    my $self = shift;
1530
1531    my $server_path = MT->instance->server_path() || '';
1532    $server_path =~ s/\/*$//;
1533    my $plugin_path = File::Spec->canonpath("$server_path/php/plugins");
1534
1535    # If PHP plugins directory doesn't exist, return without failing
1536    return 0 if !-d $plugin_path;
1537
1538    opendir(DIR, $plugin_path)
1539        or return 0;
1540    my @files = grep { /^(?:function|block)\.(.*)\.php$/ } readdir(DIR);
1541    closedir(DIR);
1542
1543    return 0 unless @files;
1544
1545    $self->progress($self->translate_escape('Renaming PHP plugin file names...'));
1546    my @error_files = ();
1547    for my $file (@files) {
1548        my $newfile = lc $file;
1549        next if $file eq $newfile;
1550        if (!rename("$plugin_path/$file", "$plugin_path/$newfile")) {
1551            push @error_files, $file;
1552        }
1553    }
1554    if ($#error_files >= 0) {
1555        $self->progress($self->translate_escape('Error renaming PHP files. Please check the Activity Log.'));
1556        MT->log(
1557            {
1558                message => $self->translate_escape("Cannot rename in [_1]: [_2].", $plugin_path, join(', ', @error_files)),
1559                level   => MT::Log::ERROR(),
1560                category => 'upgrade',
1561            }
1562        );
1563    }
1564    1;
1565}
1566
1567sub update_widget_templates {
1568    my $self = shift;
1569
1570    $self->progress($self->translate_escape('Updating widget template records...'));
1571    require MT::Template;
1572    my $iter = MT::Template->load_iter(
1573        { type => 'custom' },
1574        { 'sort' => 'name',
1575          'direction' => 'ascend' }
1576    );
1577
1578    while (my $tmpl = $iter->()) {
1579        my $name = $tmpl->name();
1580        if ($name =~ s/^(?:Widget|Sidebar): ?//) {
1581            $tmpl->name($name);
1582            $tmpl->type('widget');
1583            $tmpl->save;
1584        }
1585    }
1586    1;
1587}
1588
1589sub remove_unused_templatemap {
1590    my $self = shift;
1591
1592    $self->progress($self->translate_escape('Removing unused template maps...'));
1593
1594    require MT::Blog;
1595    require MT::TemplateMap;
1596    my $iter = MT::Blog->load_iter();
1597    while (my $blog = $iter->()) {
1598        my @blog_at = map { "'$_'" } split ',', $blog->archive_type;
1599        MT::TemplateMap->remove(
1600            { blog_id => $blog->id, archive_type => \@blog_at },
1601            { not => { archive_type => 1 } }
1602        );
1603    }
1604}
1605
1606###  Upgrade triggers
1607
1608# we don't need these yet, but it makes me feel good to have them around
1609
1610# 'pre' triggers should execute quickly. 'post' triggers can add steps
1611# if they require processing that will take time to complete.
1612
1613sub pre_upgrade_class { 1 }
1614sub post_upgrade_class { 1 }
1615sub pre_alter_column { 1 }
1616sub post_alter_column { 1 }
1617sub pre_drop_column { 1 }
1618sub post_drop_column { 1 }
1619sub pre_add_column { 1 }
1620sub pre_schema_upgrade { 1 }
1621
1622# issued last, after all table creation...
1623
1624sub post_schema_upgrade {
1625    my $self = shift;
1626    my ($from) = @_;
1627
1628    my $plugin_ver = MT->config('PluginSchemaVersion') || {};
1629    $plugin_ver->{'core'} = $from;
1630
1631    # run any functions that define a version_limit and where the schema we're
1632    # upgrading from is below that limit.
1633    foreach my $fn (keys %functions) {
1634        my $save_from = $from;
1635        {
1636            my $func = $functions{$fn};
1637
1638            if ($func->{plugin} && (UNIVERSAL::isa($func->{plugin}, 'MT::Component'))) {
1639                my $id = $func->{plugin}->id;
1640                $from = $plugin_ver->{$id};
1641            }
1642            if ($func->{version_limit}
1643                && (defined $from)
1644                && ($from < $func->{version_limit})) {
1645                $self->add_step($fn, from => $from);
1646            }
1647        }
1648        $from = $save_from;
1649    }
1650
1651    1;
1652}
1653
1654sub pre_create_table {
1655    my $self = shift;
1656    my ($class) = @_;
1657    $class->driver->dbd->ddl_class->drop_sequence($class);
1658}
1659
1660sub post_create_table {
1661    my $self = shift;
1662    my ($class) = @_;
1663
1664    $class->driver->dbd->ddl_class->create_sequence($class);
1665
1666    if (!$Installing) {
1667        foreach (keys %functions) {
1668            my $func = $functions{$_};
1669            next unless $func->{on_class};
1670            $self->add_step($_) if $func->{on_class} eq $class;
1671        }
1672    }
1673
1674    1;
1675}
1676
1677# Note that this trigger only fires on BerkeleyDB for columns
1678# that are non-null or indexed.
1679
1680sub post_add_column {
1681    my $self = shift;
1682    my ($class, $col_defs) = @_;
1683
1684    if (!$Installing) {
1685        my %cols = map { $_->{name} => 1 } @$col_defs;
1686        foreach (keys %functions) {
1687            my $func = $functions{$_};
1688            next unless $func->{on_field};
1689            if ($func->{on_field} =~ m/^\Q$class\E->(.*)/) {
1690                $self->add_step($_) if $cols{$1};
1691            }
1692        }
1693    }
1694    1;
1695}
1696
1697# Passthru routines-- passing to calling application...
1698
1699sub progress {
1700    my $self = shift;
1701    $App->progress(@_) if $App;
1702}
1703
1704sub translate_escape {
1705    my $self = shift;
1706    my $trans = MT->translate(@_);
1707    return $trans if $CLI;
1708    $trans = MT::I18N::encode_text($trans, undef, 'utf-8');
1709    return MT::Util::escape_unicode($trans);
1710}
1711
1712sub error {
1713    my $self = shift;
1714    my ($msg) = @_;
1715    $App->error(@_) if $App;
1716    return undef;
1717}
1718
1719sub add_step {
1720    my $self = shift;
1721    if ($App && (ref $App)) {
1722        $App->add_step(@_);
1723    } else {
1724        push @steps, [ @_ ];
1725    }
1726}
1727
1728# Misc utilities.
1729
1730sub detect_schema_version {
1731    my $self = shift;
1732
1733    require MT::Object;
1734    my $driver = MT::Object->driver;
1735
1736    require MT::Config;
1737    if ($driver->table_exists('MT::Config')) {
1738        return 3.2;
1739    }
1740
1741    require MT::Template;
1742    my $dyn_error_template = 
1743        MT::Template->count({type => 'dynamic_error'});
1744    if ($dyn_error_template) {
1745        return 3.1;
1746    }
1747
1748    my $comment_pending_template =
1749        MT::Template->count({type => 'comment_pending'});
1750    if ($comment_pending_template) {
1751        return 3.0;
1752    }
1753
1754    require MT::TemplateMap;
1755    if ($driver->table_exists('MT::TemplateMap')) {
1756        return 2.0;
1757    }
1758
1759    1.0;
1760}
1761
1762# A note about upgrade routines:
1763#
1764# They should all be 'safe' to execute, regardless of the
1765# active schema. In other words, running them twice in a row
1766# should not cause any errors or break the schema.
1767
1768sub core_fix_type {
1769    my $self = shift;
1770    my (%param) = @_;
1771
1772    my $type = $param{type};
1773    my $class = MT->model($type);
1774
1775    my $result = $self->type_diff($type);
1776    return 1 unless $result;
1777    return 1 unless $result->{fix};
1778
1779    my $alter = $result->{alter};
1780    my $add = $result->{add};
1781    my $drop = $result->{drop};
1782
1783    my $driver = $class->driver;
1784    my $ddl = $driver->dbd->ddl_class;
1785    my @stmts;
1786    push @stmts, sub { $self->pre_upgrade_class($class) };
1787    push @stmts, $ddl->upgrade_begin($class);
1788    push @stmts, sub { $self->pre_create_table($class) };
1789    push @stmts, sub { $self->pre_add_column($class, $add) } if $add;
1790    push @stmts, sub { $self->pre_alter_column($class, $alter) } if $alter;
1791    push @stmts, sub { $self->pre_drop_column($class, $drop) } if $drop;
1792    push @stmts, $ddl->fix_class($class);
1793    push @stmts, sub { $self->post_create_table($class) };
1794    push @stmts, sub { $self->post_add_column($class, $add) } if $add;
1795    push @stmts, sub { $self->post_alter_column($class, $alter) } if $alter;
1796    push @stmts, sub { $self->post_drop_column($class, $drop) } if $drop;
1797    push @stmts, $ddl->upgrade_end($class);
1798    push @stmts, sub { $self->post_upgrade_class($class) };
1799    $self->run_statements($class, @stmts);
1800}
1801
1802sub core_column_action {
1803    my $self = shift;
1804    my ($action, %param) = @_;
1805
1806    my $type = $param{type};
1807    my $class = MT->model($type);
1808    my $defs = $class->column_defs;
1809
1810    my $result = $self->type_diff($type);
1811    return 1 unless $result;
1812    my $columns = $result->{$action};
1813    return 1 unless $columns;
1814
1815    my $pre_method = "pre_${action}_column";
1816    my $post_method = "post_${action}_column";
1817    my $method = "${action}_column";
1818
1819    my $driver = $class->driver;
1820    my $ddl = $driver->dbd->ddl_class;
1821    my @stmts;
1822    push @stmts, sub { $self->pre_upgrade_class($class) };
1823    push @stmts, $ddl->upgrade_begin($class);
1824    push @stmts, sub { $self->$pre_method($class, $columns) };
1825    push @stmts, $ddl->$method($class, $_->{name}) foreach @$columns;
1826    push @stmts, sub { $self->$post_method($class, $columns) };
1827    push @stmts, $ddl->upgrade_end($class);
1828    push @stmts, sub { $self->post_upgrade_class($class) };
1829    $self->run_statements($class, @stmts);
1830}
1831
1832sub run_statements {
1833    my $self = shift;
1834    my ($class, @stmts) = @_;
1835
1836    my $driver = $class->driver;
1837    my $defs = $class->column_defs;
1838    my $dbh = $driver->rw_handle;
1839    my $mt = MT->instance;
1840
1841    my $updated = 0;
1842    if (@stmts) {
1843        $self->progress($self->translate_escape("Upgrading table for [_1] records...", $class->can('class_label') ? $class->class_label : $class));
1844        eval {
1845            foreach my $stmt (@stmts) {
1846                if (ref $stmt eq 'CODE') {
1847                    $stmt->() if !$DryRun;
1848                } else {
1849                    if ($dbh && !$DryRun) {
1850                        my $err;
1851                        $dbh->do($stmt) or $err = $dbh->errstr;
1852                        if ($err) {
1853                            # ignore drop errors; the table/sequence didn't exist
1854                            if ($stmt !~ m/^drop /i) {
1855                                die "failed to execute statement $stmt: $err";
1856                            }
1857                        }
1858                    } elsif ($dbh && $DryRun) {
1859                        $self->run_callbacks('SQL', $stmt);
1860                    }
1861                }
1862                $updated = 1;
1863            }
1864        };
1865        if ($@) {
1866            return $self->error($@);
1867        }
1868    }
1869    $updated;
1870}
1871
1872sub core_upgrade_begin {
1873    my $self = shift;
1874    my (%param) = @_;
1875    my $from_schema = $param{from};
1876    if ($from_schema) {
1877        my $cur_schema = MT->schema_version;
1878        $self->progress($self->translate_escape("Upgrading database from version [_1].", $from_schema)) if $from_schema < $cur_schema;
1879        $self->pre_schema_upgrade($from_schema);
1880    }
1881}
1882
1883sub core_upgrade_end {
1884    my $self = shift;
1885    my (%param) = @_;
1886
1887    my $from_schema = $param{from};
1888    if ($from_schema) {
1889        $self->post_schema_upgrade($from_schema);
1890    }
1891    1;
1892}
1893
1894sub core_finish {
1895    my $self = shift;
1896
1897    my $user;
1898    if ((ref $App) && ($App->{author})) {
1899        $user = $App->{author};
1900    }
1901
1902    my $cfg = MT->config;
1903    my $cur_schema = MT->instance->schema_version;
1904    my $old_schema = $cfg->SchemaVersion || 0;
1905    if ($cur_schema > $old_schema) {
1906        $self->progress($self->translate_escape("Database has been upgraded to version [_1].", $cur_schema)) ;
1907        if ($user && !$DryRun) {
1908            MT->log(MT->translate("User '[_1]' upgraded database to version [_2]", $user->name, $cur_schema));
1909        }
1910        $cfg->SchemaVersion( $cur_schema, 1 );
1911    }
1912
1913    my $plugin_schema = $cfg->PluginSchemaVersion || {};
1914    foreach my $plugin (@MT::Components) {
1915        my $ver = $plugin->schema_version;
1916        next unless $ver;
1917        next if $plugin->id eq 'core';
1918        my $old_plugin_schema = $plugin_schema->{$plugin->id} || 0;
1919        if ($old_plugin_schema && ($ver > $old_plugin_schema)) {
1920            $self->progress($self->translate_escape("Plugin '[_1]' upgraded successfully to version [_2] (schema version [_3]).", $plugin->label, $plugin->version || '-', $ver));
1921            if ($user && !$DryRun) {
1922                MT->log(MT->translate("User '[_1]' upgraded plugin '[_2]' to version [_3] (schema version [_4]).", $user->name, $plugin->label, $plugin->version || '-', $ver));
1923            }
1924        } elsif ($ver && !$old_plugin_schema) {
1925            $self->progress($self->translate_escape("Plugin '[_1]' installed successfully.", $plugin->label));
1926            if ($user && !$DryRun) {
1927                MT->log(MT->translate("User '[_1]' installed plugin '[_2]', version [_3] (schema version [_4]).", $user->name, $plugin->label, $plugin->version || '-', $ver));
1928            }
1929        }
1930        $plugin_schema->{$plugin->id} = $ver;
1931    }
1932    if (keys %$plugin_schema) {
1933        $cfg->PluginSchemaVersion($plugin_schema, 1);
1934    }
1935    $cfg->save_config unless $DryRun;
1936
1937    # do one last thing....
1938    if ((ref $App) && ($App->can('finish'))) {
1939        $App->finish();
1940    }
1941
1942    1;
1943}
1944
1945sub core_set_superuser {
1946    my $self = shift;
1947
1948    my $app = $App;
1949    my $author;
1950    if ((ref $app) && ($app->{author})) {
1951        require MT::Author;
1952        $self->progress($self->translate_escape("Setting your permissions to administrator."));
1953        $author = MT::Author->load($app->{author}->id);
1954    } elsif ($SuperUser) {
1955        require MT::Author;
1956        $self->progress($self->translate_escape("Setting your permissions to administrator."));
1957        $author = MT::Author->load($SuperUser);
1958    }
1959
1960    if ($author) {
1961        $author->is_superuser(1);
1962        $author->save or return $self->error($self->translate_escape("Error saving record: [_1].", $author->errstr));
1963    }
1964
1965    1;
1966}
1967
1968sub core_remove_unique_constraints {
1969    my $self = shift;
1970
1971    my $driver = MT::Object->driver;
1972    if (ref $driver->dbd =~ m/::Pg$/) {
1973        # category, author, permission, template
1974        $driver->sql([
1975            'alter table mt_category drop constraint mt_category_category_blog_id_key',
1976            'create index mt_category_label on mt_category (category_label)',
1977
1978            'alter table mt_author drop constraint mt_author_author_name_key',
1979            'create index mt_author_name on mt_author (author_name)',
1980            'alter table mt_permission drop constraint mt_permission_permission_blog_id_key',
1981            'create index mt_permission_blog_id on mt_permission (permission_blog_id)',
1982            'alter table mt_template drop constraint mt_template_template_blog_id_key',
1983            'create index mt_template_blog_id on mt_template (template_blog_id)'
1984        ]);
1985    } elsif (ref $driver->dbd =~ m/::mysql$/) {
1986        $driver->sql([
1987            'alter table mt_category drop index category_blog_id',
1988            'create index category_blog_id on mt_category (category_blog_id)',
1989            'create index category_label on mt_category (category_label)',
1990            'alter table mt_author drop index author_name',
1991            'create index author_name on mt_author (author_name)',
1992            'alter table mt_permission drop index permission_blog_id',
1993            'create index permission_blog_id on mt_permission (permission_blog_id)',
1994            'alter table mt_template drop index template_blog_id',
1995            'create index template_blog_id on mt_template (template_blog_id)'
1996        ]);
1997    }
1998    1;
1999}
2000
2001sub _merge_comment_response_templates_updater {
2002    my ($blog) = @_;
2003    require MT::Template;
2004    my $tmpl = MT::Template->load({ blog_id => $blog->id, type => 'comment_response' });
2005    unless ($tmpl) {
2006        $tmpl = new MT::Template;
2007        $tmpl->blog_id($blog->id);
2008        $tmpl->type('comment_response');
2009    }
2010
2011    my $confirm_template = <<'EOT';
2012<MTSetVarBlock name="page_title"><__trans phrase="Comment Posted"></MTSetVarBlock>
2013
2014<MTSetVar name="heading" value="<__trans phrase="Confirmation...">">
2015
2016<MTSetVarBlock name="message">
2017<p><__trans phrase="Your comment has been posted!"></p>
2018</MTSetVarBlock>
2019EOT
2020
2021    my $pending_template = <<'EOT';
2022<MTSetVarBlock name="page_title"><__trans phrase="Comment Pending"></MTSetVarBlock>
2023
2024<MTSetVar name="heading" value="<__trans phrase="Thank you for commenting.">">
2025
2026<MTSetVarBlock name="message">
2027<p><__trans phrase="Your comment has been received and held for approval by the blog owner."></p>
2028</MTSetVarBlock>
2029EOT
2030
2031    my $error_template = <<'EOT';
2032<MTSetVarBlock name="page_title"><__trans phrase="Comment Submission Error"></MTSetVarBlock>
2033
2034<MTSetVar name="heading" value="$page_title">
2035
2036<MTSetVarBlock name="message">
2037<p><__trans phrase="Your comment submission failed for the following reasons:"></p>
2038<blockquote>
2039    <$MTErrorMessage$>
2040</blockquote>
2041</MTSetVarBlock>
2042EOT
2043
2044    my $header_template = <<'EOT';
2045<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2046    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2047<html xmlns="http://www.w3.org/1999/xhtml" id="sixapart-standard">
2048<head>
2049    <meta http-equiv="Content-Type" content="text/html; charset=<$MTPublishCharset$>" />
2050    <meta name="generator" content="<$MTProductName version="1"$>" />
2051    <link rel="stylesheet" href="<$MTBlogURL$>styles-site.css" type="text/css" />
2052    <title>
2053    <__trans phrase="[_1]: [_2]" params="<$MTBlogName encode_html="1"$>%%<$MTGetVar name="page_title"$>">
2054    </title>
2055    <script type="text/javascript" src="<$MTBlogURL$>mt-site.js"></script>
2056</head>
2057<body class="layout-one-column comment-preview" onload="individualArchivesOnLoad(commenter_name)">
2058    <div id="container">
2059        <div id="container-inner" class="pkg">
2060            <div id="banner">
2061                <div id="banner-inner" class="pkg">
2062                    <h1 id="banner-header"><a href="<$MTBlogURL$>" accesskey="1"><$MTBlogName encode_html="1"$></a></h1>
2063                    <h2 id="banner-description"><$MTBlogDescription$></h2>
2064                </div>
2065            </div>
2066            <div id="pagebody">
2067                <div id="pagebody-inner" class="pkg">
2068                    <div id="alpha">
2069                        <div id="alpha-inner" class="pkg">
2070EOT
2071
2072    my $footer_template = <<'EOT';
2073                        </div>
2074                    </div>
2075                </div>
2076            </div>
2077        </div>
2078    </div>
2079</body>
2080</html>
2081EOT
2082
2083    my $message_template = <<'EOT';
2084<h1><$MTGetVar name="heading"$></h1>
2085
2086<$MTGetVar name="message"$>
2087
2088<p><__trans phrase="Return to the <a href="[_1]">original entry</a>." params="<$MTEntryLink$>"></p>
2089EOT
2090
2091    my $mt = MT->instance;
2092    $tmpl->name($mt->translate("Comment Response"));
2093    $tmpl->text($mt->translate_templatized(<<"EOT"));
2094<MTSetVar name="page_layout" value="layout-one-column">
2095<MTSetVar name="system_template" value="1">
2096<MTSetVar name="feedback_template" value="1">
2097
2098<MTIf name="body_class" eq="mt-comment-pending">
2099$pending_template
2100</MTIf>
2101
2102<MTIf name="body_class" eq="mt-comment-error">
2103$error_template
2104</MTIf>
2105
2106<MTIf name="body_class" eq="mt-comment-confirmation">
2107$confirm_template
2108</MTIf>
2109
2110$header_template
2111
2112$message_template
2113
2114$footer_template
2115EOT
2116    $tmpl->save;
2117}
2118
2119sub core_create_config_table {
2120    my $self = shift;
2121
2122    require MT::Config;
2123    my $config = MT::Config->load();
2124    if (!$config) {
2125        #$self->progress($self->translate_escape("Creating configuration record."));
2126        $config = MT::Config->new;
2127        $config->data('');
2128        $config->save or return $self->error($self->translate_escape("Error saving record: [_1].", $config->errstr));
2129    }
2130    return 1;
2131}
2132
2133sub core_set_enable_archive_paths {
2134    my $self = shift;
2135    MT->config->EnableArchivePaths(1, 1);
2136    return 1;
2137}
2138
2139sub core_create_template_maps {
2140    my $self = shift;
2141    my (%param) = @_;
2142   
2143    my $offset = $param{offset};
2144    require MT::Template;
2145    require MT::TemplateMap;
2146    require MT::Blog;
2147
2148    my $msg = $self->translate_escape("Creating template maps...");
2149    if ($offset) {
2150        my $count = MT::Template->count;
2151        return 1 unless $count;
2152        $self->progress(sprintf("$msg (%d%%)", ($offset / $count * 100)), 1);
2153    } else {
2154        $self->progress($msg, 1);
2155    }
2156
2157    my $iter = MT::Template->load_iter(undef, { offset => $offset, limit => $MAX_ROWS+1 });
2158    my $start = time;
2159    my $continue = 0;
2160    my $count = 0;
2161    while (my $tmpl = $iter->()) {
2162        $offset++;
2163        my $blog = MT::Blog->load($tmpl->blog_id);
2164        my(@at);
2165        if ($tmpl->type eq 'archive') {
2166            @at = qw( Daily Weekly Monthly );
2167        } elsif ($tmpl->type eq 'category') {
2168            @at = ('Category');
2169        } elsif ($tmpl->type eq 'page') {
2170            @at = ('Page');
2171        } elsif ($tmpl->type eq 'individual') {
2172            @at = ('Individual');
2173        } else {
2174            next;
2175        }
2176        for my $at (@at) {
2177            my $meth = 'archive_tmpl_' . lc($at);
2178            my $file_tmpl = $blog->$meth();
2179            my $existing = MT::TemplateMap->load({ blog_id => $blog->id,
2180                archive_type => $at, template_id => $tmpl->id });
2181            if (!$existing) {
2182                my $map = MT::TemplateMap->new;
2183                if ($file_tmpl) {
2184                    $self->progress($self->translate_escape("Mapping template ID [_1] to [_2] ([_3]).", $tmpl->id, $at, $file_tmpl));
2185                    $map->file_template($file_tmpl);
2186                } else {
2187                    $self->progress($self->translate_escape("Mapping template ID [_1] to [_2].", $tmpl->id, $at));
2188                }
2189                $map->archive_type($at);
2190                $map->is_preferred(1);
2191                $map->template_id($tmpl->id);
2192                $map->blog_id($tmpl->blog_id);
2193                $map->save or return $self->error($self->translate_escape("Error saving record: [_1].", $map->errstr));
2194            }
2195        }
2196        $count++;
2197        $continue = 1, last if $count == $MAX_ROWS;
2198        $continue = 1, last if time > $start + $MAX_TIME;
2199    }
2200    if ($continue) {
2201        $iter->('finish');
2202        return $offset;
2203    } else {
2204        $self->progress("$msg (100%)", 1);
2205    }
2206    1;
2207}
2208
2209sub core_update_records {
2210    my $self = shift;
2211    my (%param) = @_;
2212
2213    my $class = MT->model($param{type});
2214    return $self->error($self->translate_escape("Error loading class: [_1].", $param{type}))
2215        unless $class;
2216
2217    my $msg;
2218    my $class_label = ($class->can('class_label') ? $class->class_label : $class);
2219    if ($param{label}) {
2220        $msg = $param{label};
2221        if (ref $msg eq 'CODE') {
2222            $msg = $msg->($class_label);
2223        }
2224        $msg = $self->translate_escape($msg);
2225    } else {
2226        $msg = $self->translate_escape($param{message} || "Updating [_1] records...", $class_label);
2227    }
2228    my $offset = $param{offset};
2229    if ($offset) {
2230        my $count = $class->count;
2231        return unless $count;
2232        $self->progress(sprintf("$msg (%d%%)", ($offset/$count*100)), $param{step});
2233    } else {
2234        $self->progress($msg, $param{step});
2235    }
2236
2237    my $cond = $param{condition};
2238    my $code = $param{code};
2239    my $sql = $param{sql};
2240
2241    my $continue = 0;
2242    my $driver = $class->driver;
2243
2244    if ($sql && $DryRun) {
2245        $self->run_callbacks('SQL', $sql);
2246    }
2247    return 1 if $DryRun;
2248
2249    if (!$sql || !$driver->sql($sql)) {
2250        my $iter= $class->load_iter(undef, { offset => $offset, limit => $MAX_ROWS+1 });
2251        my $start = time;
2252        my @list;
2253        while (my $obj = $iter->()) {
2254            push @list, $obj;
2255            $continue = 1, last if scalar @list == $MAX_ROWS;
2256        }
2257        $iter->('finish') if $continue;
2258        for my $obj (@list) {
2259            $offset++;
2260            if ($cond) {
2261                next unless $cond->($obj, %param);
2262            }
2263            $code->($obj);
2264            $obj->save()
2265                or return $self->error($self->translate_escape("Error saving record: [_1].", $obj->errstr));
2266            $continue = 1, last if time > $start + $MAX_TIME;
2267        }
2268    }
2269    if ($continue) {
2270        return $offset;
2271    } else {
2272        $self->progress("$msg (100%)", $param{step});
2273    }
2274    1;
2275}
2276
2277#############################################################################
2278
2279{
2280    my %HighASCII = (
2281        "\xc0" => 'A',    # A`
2282        "\xe0" => 'a',    # a`
2283        "\xc1" => 'A',    # A'
2284        "\xe1" => 'a',    # a'
2285        "\xc2" => 'A',    # A^
2286        "\xe2" => 'a',    # a^
2287        "\xc4" => 'Ae',   # A:
2288        "\xe4" => 'ae',   # a:
2289        "\xc3" => 'A',    # A~
2290        "\xe3" => 'a',    # a~
2291        "\xc8" => 'E',    # E`
2292        "\xe8" => 'e',    # e`
2293        "\xc9" => 'E',    # E'
2294        "\xe9" => 'e',    # e'
2295        "\xca" => 'E',    # E^
2296        "\xea" => 'e',    # e^
2297        "\xcb" => 'Ee',   # E:
2298        "\xeb" => 'ee',   # e:
2299        "\xcc" => 'I',    # I`
2300        "\xec" => 'i',    # i`
2301        "\xcd" => 'I',    # I'
2302        "\xed" => 'i',    # i'
2303        "\xce" => 'I',    # I^
2304        "\xee" => 'i',    # i^
2305        "\xcf" => 'Ie',   # I:
2306        "\xef" => 'ie',   # i:
2307        "\xd2" => 'O',    # O`
2308        "\xf2" => 'o',    # o`
2309        "\xd3" => 'O',    # O'
2310        "\xf3" => 'o',    # o'
2311        "\xd4" => 'O',    # O^
2312        "\xf4" => 'o',    # o^
2313        "\xd6" => 'Oe',   # O:
2314        "\xf6" => 'oe',   # o:
2315        "\xd5" => 'O',    # O~
2316        "\xf5" => 'o',    # o~
2317        "\xd8" => 'Oe',   # O/
2318        "\xf8" => 'oe',   # o/
2319        "\xd9" => 'U',    # U`
2320        "\xf9" => 'u',    # u`
2321        "\xda" => 'U',    # U'
2322        "\xfa" => 'u',    # u'
2323        "\xdb" => 'U',    # U^
2324        "\xfb" => 'u',    # u^
2325        "\xdc" => 'Ue',   # U:
2326        "\xfc" => 'ue',   # u:
2327        "\xc7" => 'C',    # ,C
2328        "\xe7" => 'c',    # ,c
2329        "\xd1" => 'N',    # N~
2330        "\xf1" => 'n',    # n~
2331        "\xdf" => 'ss',
2332    );
2333    my $HighASCIIRE = join '|', keys %HighASCII;
2334    sub mt32_convert_high_ascii {
2335        my($s) = @_;
2336        $s =~ s/($HighASCIIRE)/$HighASCII{$1}/g;
2337        $s;
2338    }
2339}
2340
2341sub mt32_iso_dirify {
2342    my $s = $_[0];
2343    my $sep;
2344    if ($_[1] && ($_[1] ne '1')) {
2345        $sep = $_[1];
2346    } else {
2347        $sep = '_';
2348    }
2349    $s = mt32_convert_high_ascii($s);  ## convert high-ASCII chars to 7bit.
2350    $s = lc $s;                   ## lower-case.
2351    $s = MT::Util::remove_html($s);         ## remove HTML tags.
2352    $s =~ s!&[^;\s]+;!!g;         ## remove HTML entities.
2353    $s =~ s![^\w\s]!!g;           ## remove non-word/space chars.
2354    $s =~ s! +!$sep!g;             ## change space chars to underscores.
2355    $s;   
2356}
2357
2358sub mt32_utf8_dirify {
2359    my $s = $_[0];
2360    my $sep;
2361    if ($_[1] && ($_[1] ne '1')) {
2362        $sep = $_[1];
2363    } else {
2364        $sep = '_';
2365    }
2366    $s = mt32_xliterate_utf8($s);      ## convert two-byte UTF-8 chars to 7bit ASCII
2367    $s = lc $s;                   ## lower-case.
2368    $s = MT::Util::remove_html($s);         ## remove HTML tags.
2369    $s =~ s!&[^;\s]+;!!g;         ## remove HTML entities.
2370    $s =~ s![^\w\s]!!g;           ## remove non-word/space chars.
2371    $s =~ s! +!$sep!g;             ## change space chars to underscores.
2372    $s;   
2373}
2374
2375sub mt32_dirify {
2376    ($MT::VERSION && MT->instance->{cfg}->PublishCharset =~ m/utf-?8/i)
2377        ? mt32_utf8_dirify(@_) : mt32_iso_dirify(@_);
2378}
2379
2380sub mt32_xliterate_utf8 {
2381    my ($str) = @_;
2382    my %utf8_table = (
2383          "\xc3\x80" => 'A',    # A`
2384          "\xc3\xa0" => 'a',    # a`
2385          "\xc3\x81" => 'A',    # A'
2386          "\xc3\xa1" => 'a',    # a'
2387          "\xc3\x82" => 'A',    # A^
2388          "\xc3\xa2" => 'a',    # a^
2389          "\xc3\x84" => 'Ae',   # A:
2390          "\xc3\xa4" => 'ae',   # a:
2391          "\xc3\x83" => 'A',    # A~
2392          "\xc3\xa3" => 'a',    # a~
2393          "\xc3\x88" => 'E',    # E`
2394          "\xc3\xa8" => 'e',    # e`
2395          "\xc3\x89" => 'E',    # E'
2396          "\xc3\xa9" => 'e',    # e'
2397          "\xc3\x8a" => 'E',    # E^
2398          "\xc3\xaa" => 'e',    # e^
2399          "\xc3\x8b" => 'Ee',   # E:
2400          "\xc3\xab" => 'ee',   # e:
2401          "\xc3\x8c" => 'I',    # I`
2402          "\xc3\xac" => 'i',    # i`
2403          "\xc3\x8d" => 'I',    # I'
2404          "\xc3\xad" => 'i',    # i'
2405          "\xc3\x8e" => 'I',    # I^
2406          "\xc3\xae" => 'i',    # i^
2407          "\xc3\x8f" => 'Ie',   # I:
2408          "\xc3\xaf" => 'ie',   # i:
2409          "\xc3\x92" => 'O',    # O`
2410          "\xc3\xb2" => 'o',    # o`
2411          "\xc3\x93" => 'O',    # O'
2412          "\xc3\xb3" => 'o',    # o'
2413          "\xc3\x94" => 'O',    # O^
2414          "\xc3\xb4" => 'o',    # o^
2415          "\xc3\x96" => 'Oe',   # O:
2416          "\xc3\xb6" => 'oe',   # o:
2417          "\xc3\x95" => 'O',    # O~
2418          "\xc3\xb5" => 'o',    # o~
2419          "\xc3\x98" => 'Oe',   # O/
2420          "\xc3\xb8" => 'oe',   # o/
2421          "\xc3\x99" => 'U',    # U`
2422          "\xc3\xb9" => 'u',    # u`
2423          "\xc3\x9a" => 'U',    # U'
2424          "\xc3\xba" => 'u',    # u'
2425          "\xc3\x9b" => 'U',    # U^
2426          "\xc3\xbb" => 'u',    # u^
2427          "\xc3\x9c" => 'Ue',   # U:
2428          "\xc3\xbc" => 'ue',   # u:
2429          "\xc3\x87" => 'C',    # ,C
2430          "\xc3\xa7" => 'c',    # ,c
2431          "\xc3\x91" => 'N',    # N~
2432          "\xc3\xb1" => 'n',    # n~
2433          "\xc3\x9f" => 'ss',   # double-s
2434    );
2435   
2436    $str =~ s/([\200-\377]{2})/$utf8_table{$1}||''/ge;
2437    $str;
2438}
2439
24401;
2441__END__
2442
2443=head1 NAME
2444
2445MT::Upgrade - MT class for managing system upgrades.
2446
2447=head1 SYNOPSIS
2448
2449    MT::Upgrade->do_upgrade(Install => 1);
2450
2451=head1 DESCRIPTION
2452
2453This module is responsible for handling the upgrade or installation of
2454an MT database. The framework is flexible enough for third party plugins
2455to use as well to manage their own schema (please refer to the documentation
2456in L<MT::Plugin> for more information on this).
2457
2458=head1 METHODS
2459
2460=head2 MT::Upgrade-E<gt>do_upgrade
2461
2462The main worker method for this module is I<do_upgrade>. It accepts a
2463handful of arguments, which are:
2464
2465=over 4
2466
2467=item * Install
2468
2469Specify a value of '1' to assume a new installation along with an operation
2470to install a blog and initial user.
2471
2472=item * App
2473
2474A package name or app object that can service the following methods:
2475
2476=over 4
2477
2478=item * progress($package, $message)
2479
2480Called during the upgrade operation to provide feedback with respect to
2481the operations the upgrade process is running.
2482
2483=item * error($package, $message)
2484
2485Called during the upgrade operation to communicate an error that has
2486occurred.
2487
2488=item * translate_escape
2489
2490Call this method to translate messages and phrases which are to appear
2491on the progress screen.  DO NOT use this method to messages and phrases
2492which directly are stored in database.  Use MT->translate for the purpose.
2493
2494=back
2495
2496=item * CLI
2497
2498Specified (set to '1') when invoked from a command line tool. This prevents
2499encoding response messages in the configured PublishCharset for the
2500installation.
2501
2502=item * SuperUser
2503
2504If upgrading from the command line, and running on a pre-MT 3.2 database,
2505set this to an existing author ID that should be upgrade to system
2506administrator status.
2507
2508=item * DryRun
2509
2510Specified (set to '1') to examine the database for installation/upgrade
2511needs but not actually make any physical changes to the database. This will
2512issue all the upgrade progress messages without doing the upgrade itself.
2513
2514=back
2515
2516=head1 CALLBACKS
2517
2518The upgrade module defines the following MT callbacks:
2519
2520=over 4
2521
2522=item * MT::Upgrade::SQL
2523
2524Called with each SQL statement that is executed against the database
2525as part of the upgrade process. The parameters passed to this callback are:
2526
2527    $callback, $upgrade_app, $sql_statement
2528
2529The first parameter is an L<MT::Callback> object. C<$upgrade_app> is a
2530package name or L<MT::App> object used to drive the upgrade process.
2531C<$sql_statement> is the actual SQL query that is about to be executed
2532against the database.
2533
2534=back
2535
2536=head1 UPGRADE FUNCTIONS
2537
2538The bulk of this module consists of Movable Type upgrade operations.
2539These are declared as upgrade functions, and are registered in the
2540package variabled '%functions'. (Note: the word 'function' here is
2541not meant to describe a Perl subroutine.)
2542
2543Some functions are invoked to manage the upgrade process from start
2544to finish ('core_upgrade_begin' for instance, which merely displays
2545a progress message to the calling application). The rest handle
2546schema and data transformation from one version of the MT schema to
2547another.
2548
2549Schema translation itself is handled by Movable Type automatically.
2550MT is able to check the physical schema represenation in the database
2551and compare it with the schema as defined by the L<MT::Object>-descended
2552package. If a new property is added to the L<MT::Blog> package, the
2553upgrade process sees that has happened and can issue the actual
2554'alter table' SQL statement necessary to add it to the database. The
2555'core_fix_type' function is responsible for examining a particular
2556table used by a class like L<MT::Blog> and will append additional
2557upgrade steps ('core_add_column', 'core_alter_column') that it finds
2558necessary to the upgrade workflow.
2559
2560Following the schema translation operations, the data transformation
2561functions would be used to manipulate the data as necessary from
2562an older schema to the current one. For instance, the
2563'core_create_placements' upgrade function was written to upgrade
2564really old MT schemas from the pre-2.0 release to the current schema.
2565The upgrade function is registered like this:
2566
2567    $MT::Upgrade::functions{core_create_placements} = {
2568        version_limit => 2.0,