root/branches/release-39/lib/MT/Upgrade.pm @ 2427

Revision 2427, 118.2 kB (checked in by fumiakiy, 19 months ago)

Reverted r2415; assigning default values to new columns in DDL that is more generic solution. BugId:79206

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