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

Revision 2660, 121.1 kB (checked in by mpaschal, 17 months ago)

Some questions about this new code
BugzID: 80404

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