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

Revision 2627, 120.0 kB (checked in by takayama, 17 months ago)

Fixed BugId:80251
* Added blog_id to conditions
* Recover sysadmin's permissions

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