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

Revision 2672, 121.8 kB (checked in by mpaschal, 17 months ago)

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