root/branches/release-27/lib/MT/Upgrade.pm @ 1256

Revision 1256, 101.5 kB (checked in by auno, 23 months ago)

Fixed applied object name. BugzID:65371

  • 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->count({
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            on_field => 'MT::Comment->junk_status',
466            priority => 3.1,
467            updater => {
468                type => 'comment',
469                condition => sub { !defined $_[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            }
475        },
476        'core_init_tbping_visible' => {
477            on_field => 'MT::TBPing->visible',
478            priority => 3.1,
479            updater => {
480                type => 'tbping',
481                condition => sub { !defined $_[0]->visible },
482                code => sub { $_[0]->visible(1) },
483                label => 'Assigning visible status for TrackBacks...',
484                sql => 'update mt_tbping set tbping_visible = 1
485                        where tbping_visible is null',
486            }
487        },
488        'core_init_tbping_junk_status' => {
489            on_field => 'MT::TBPing->junk_status',
490            priority => 3.1,
491            updater => {
492                type => 'tbping',
493                condition => sub { !defined $_[0]->junk_status },
494                code => sub { $_[0]->junk_status(1) },
495                label => 'Assigning junk status for TrackBacks...',
496                sql => 'update mt_tbping set tbping_junk_status = 1
497                        where tbping_junk_status is null',
498            }
499        },
500        'core_init_category_basename' => {
501            version_limit => 3.2002,
502            priority => 3.1,
503            updater => {
504                type => 'category',
505                condition => sub { !defined $_[0]->basename },
506                code => sub { my $cat = shift; my %args = @_;
507                    $args{from} < 3.20021
508                        ? $cat->basename(mt32_dirify($cat->label))
509                        : 1;
510                },
511                label => 'Assigning basename for categories...',
512            }
513        },
514        'core_set_author_status' => {
515            version_limit => 3.301,
516            priority => 3.1,
517            updater => {
518                type => 'author',
519                label => 'Assigning user status...',
520                condition => sub {
521                    ($_[0]->type == 1) && (!defined $_[0]->status)
522                },
523                code => sub {
524                    shift->status(1);
525                },
526                sql => 'update mt_author set author_status = 1
527                        where author_type = 1 and author_status is null',
528            }
529        },
530        'core_install_default_roles' => {
531            code => \&create_default_roles,
532            on_class => 'MT::Role',
533            priority => 3.1,
534        },
535        'core_migrate_permissions_to_roles' => {
536            version_limit => 3.3101,
537            priority => 3.2,
538            updater => {
539                type => 'permission',
540                label => 'Migrating permissions to roles...',
541                condition => sub { $_[0]->blog_id },
542                code => \&_migrate_permission_to_role,
543            }
544        },
545        'core_remove_dynamic_site_bootstrapper' => {
546            code => \&remove_mtviewphp,
547            version_limit => 3.3101,
548            priority => 5.1,
549        },
550        'core_deprecate_bitmask_permissions' => {
551            code => \&deprecate_bitmask_permissions,
552            version_limit => 4.0002,
553            priority => 3.3,
554        },
555        'core_migrate_system_privileges' => {
556            code => \&migrate_system_privileges,
557            version_limit => 4.0002,
558            priority => 3.3,
559        },
560        'core_migrate_commenter_auth' => {
561            code => \&migrate_commenter_auth,
562            version_limit => 3.3101,
563            priority => 3.1,
564        },
565        'core_populate_authored_on' => {
566            version_limit => 4.0014,
567            priority => 3.1,
568            updater => {
569                type => 'entry',
570                label => 'Populating authored and published dates for entries...',
571                condition => sub {
572                    !defined $_[0]->authored_on
573                },
574                code => sub {
575                    $_[0]->authored_on($_[0]->created_on)
576                        if !defined $_[0]->authored_on;
577                },
578                sql =>
579                    'update mt_entry set entry_authored_on = entry_created_on
580                        where entry_authored_on is null',
581            },
582        },
583        'core_update_3x_system_search_templates' => {
584            version_limit => 4.0017,
585            priority => 3.1,
586            code => \&update_3x_system_search_templates,
587        },
588        'core_rename_php_plugin_filenames' => {
589            version_limit => 4.0019,
590            priority => 3.1,
591            code => \&rename_php_plugin_filenames,
592        },
593        'core_migrate_nofollow_settings' => {
594            version_limit => 4.0020,
595            priority => 3.1,
596            code => \&migrate_nofollow_settings,
597        },
598        'core_update_widget_template' => {
599            version_limit => 4.0022,
600            priority => 3.1,
601            code => \&update_widget_templates,
602        },
603        # This upgrade step is currently necessary for PostgreSQL
604        # which doesn't support adding a column, populating the existing
605        # records with a value.
606        'core_typify_category_records' => {
607            version_limit => 4.0023,
608            priority => 3.1,
609            updater => {
610                type => 'category',
611                label => 'Classifying category records...',
612                condition => sub {
613                    !$_[0]->column('class')
614                },
615                code => sub {
616                    $_[0]->class('category');
617                },
618                sql =>
619                    "update mt_category set category_class = 'category'
620                        where category_class is null",
621            },
622        },
623        'core_typify_entry_records' => {
624            version_limit => 4.0023,
625            priority => 3.1,
626            updater => {
627                type => 'entry',
628                label => 'Classifying entry records...',
629                condition => sub {
630                    !$_[0]->column('class')
631                },
632                code => sub {
633                    $_[0]->class('entry');
634                },
635                sql =>
636                    "update mt_entry set entry_class = 'entry'
637                        where entry_class is null",
638            },
639        },
640        'core_merge_comment_response_templates' => {
641            version_limit => 4.0023,
642            priority => 3.1,
643            updater => {
644                type => 'blog',
645                label => "Merging comment system templates...",
646                code => \&_merge_comment_response_templates_updater,
647            },
648        },
649        'core_populate_default_file_template' => {
650            version_limit => 4.0023,
651            priority => 3.1,
652            updater => {
653                type => 'templatemap',
654                label => 'Populating default file template for templatemaps...',
655                condition => sub {
656                    !defined $_[0]->file_template
657                },
658                code => sub {
659                    my %default_template = (
660                        Individual => '%y/%m/%f',
661                        Category => '%c/%i',
662                    );
663                    $_[0]->file_template($default_template{$_[0]->archive_type})
664                        if !defined $_[0]->file_template && exists($default_template{$_[0]->archive_type});
665                },
666            },
667        },
668        'core_remove_unused_templatemap' => {
669            version_limit => 4.0023,
670            priority => 3.0,
671            code => \&remove_unused_templatemap,
672        },
673        'core_set_author_auth_type' => {
674            version_limit => 4.0024,
675            priority => 3.2,
676            updater => {
677                type => 'author',
678                label => 'Assigning user authentication type...',
679                condition => sub {
680                    !$_[0]->auth_type;
681                },
682                code => \&core_populate_author_auth_type,
683            },
684        },
685        'core_add_newfeature_widget' => {
686            version_limit => 4.0027,
687            priority => 3.2,
688            updater => {
689                type => 'author',
690                label => 'Adding new feature widget to dashboard...',
691                code => sub {
692                    my ($user) = @_;
693                    my $widget_store = $user->widgets();
694                    if ($widget_store && %$widget_store) {
695                        for my $set (keys %$widget_store) {
696                            $widget_store->{$set}->{new_version} = {
697                                template => 'widget/new_version.tmpl',
698                                set      => 'main',
699                                singular => 1,
700                                order    => -1,
701                            };
702                        }
703                    }
704                    $user->widgets($widget_store);
705                    $user->save;
706                },
707            },
708        },
709        'core_add_email_template' => {
710            version_limit => 4.0030,
711            priority => 9.3,
712            code => sub {
713                my $self = shift;
714                $self->upgrade_templates({ Install => 1 });
715            },
716        },
717        'core_replace_openid_username' => {
718            version_limit => 4.0033,
719            priority => 3.2,
720            updater => {
721                type => 'author',
722                label => 'Moving OpenID usernames to external_id fields...',
723                condition => sub {
724                    return 0 if 'MT' eq $_[0]->auth_type;
725                    my $auth = MT->commenter_authenticator($_[0]->auth_type);
726                    return 0 unless $auth && %$auth && exists($auth->{class});
727                    my $auth_class = $auth->{class};
728                    eval "require $auth_class;";
729                    return 0 if $@;
730                    return UNIVERSAL::isa($auth_class, 'MT::Auth::OpenID');
731                },
732                code => sub {
733                    return unless $_[0]->url;
734                    my $existing = MT::Author->load({ name => $_[0]->url, auth_type => 'OpenID' });
735                    unless ($existing) {
736                        $_[0]->external_id($_[0]->name);
737                        $_[0]->name($_[0]->url);
738                    }
739                },
740            },
741        },
742        'core_set_template_set' => {
743            version_limit => 4.0034,
744            priority => 3.2,
745            updater => {
746                type => 'blog',
747                label => 'Assigning blog template set...',
748                condition => sub {
749                    !$_[0]->template_set;
750                },
751                code => sub {
752                    $_[0]->template_set('mt_blog');
753                    MT->run_callbacks( 'blog_template_set_change', { blog => $_[0] } );
754                },
755            },
756        },
757        'core_set_page_layout' => {
758            version_limit => 4.0036,
759            priority => 3.2,
760            updater => {
761                type => 'blog',
762                label => 'Assigning blog page layout...',
763                condition => sub {
764                    !$_[0]->page_layout;
765                },
766                code => sub {
767                    my ($blog) = @_;
768                    my $layout = 'layout-wtt';
769                    require MT::Template;
770                    my $styles = MT::Template->load({ blog_id => $blog->id, identifier => 'styles' });
771                    if ($styles) {
772                        if ($styles->text =~ m{ <mt:?setvar \s+ name="page_layout" \s+ value="([^"]+)"> }xmsi) {
773                            $layout = $1;
774                        }
775                    }
776                    $blog->page_layout($layout);
777                    $blog->save;
778                },
779            },
780        },
781    }
782}
783
784sub core_populate_author_auth_type {
785    my ($u) = @_;
786    if ($u->type == 1) {
787        $u->auth_type(MT->config->AuthenticationModule || 'MT');
788    } else {
789        # for legacy OpenID plugin commenters
790        if ($u->name =~ m(^openid\n(.*)$)) {
791            my $url = $1;
792            if (eval { require Digest::MD5; 1; }) {
793                $url = Digest::MD5::md5_hex($url);
794            } else {
795                $url = substr $url, 0, 255;
796            }
797            $u->name($url);
798            $u->auth_type('OpenID');
799        }
800        elsif ($u->name =~ m!^[a-f0-9]{32}$!) {
801            # Vox OpenID URL; set auth_type to 'Vox'
802            if ($u->url =~ m!\.vox\.com/!) {
803                $u->auth_type('Vox');
804            }
805            # LJ OpenID URL; set auth_type to 'LiveJournal'
806            elsif ($u->url =~ m!\.livejournal\.com/!) {
807                $u->auth_type('LiveJournal');
808            }
809            else {
810                # Other custom auth, which for now means OpenID
811                $u->auth_type('OpenID');
812            }
813        }
814        else {
815            # Default to TypeKey for remaining plain name fields
816            $u->auth_type('TypeKey');
817        }
818    }
819}
820
821sub migrate_nofollow_settings {
822    my $self = shift;
823
824    $self->progress($self->translate_escape("Migrating Nofollow plugin settings..."));
825    require MT::PluginData;
826    my $cfg = MT->config;
827    my $plugins = $cfg->PluginSwitch || {};
828    my $nofollow_switch = $plugins->{'nofollow/nofollow.pl'};
829    my $enabled = defined $nofollow_switch ? ($nofollow_switch ? 1 : 0) : 1;
830    my $default_follow_auth_links = 1;
831
832    # For any configuration settings that exist
833    my @config = MT::PluginData->load({ plugin => 'Nofollow' });
834    my %blogs_saved;
835    foreach my $cfg (@config) {
836        if ($cfg->key =~ m/^configuration:blog:(\d+)/) {
837            my $blog = MT::Blog->load($1) or next;
838            my $setting = ($cfg->data || {})->{follow_auth_links};
839            $blog->follow_auth_links($setting) if defined $setting;
840            $blog->nofollow_urls($enabled);
841            $blog->save;
842            $blogs_saved{$blog->id} = 1;
843        } else {
844            my $setting = ($cfg->data || {})->{follow_auth_links};
845            $default_follow_auth_links = $setting if defined $setting;
846        }
847    }
848    $_->remove for @config;
849
850    my $blog_iter = MT::Blog->load_iter;
851    while (my $blog = $blog_iter->()) {
852        next if exists $blogs_saved{$blog->id};
853        $blog->nofollow_urls($enabled);
854        $blog->follow_auth_links($default_follow_auth_links);
855        $blog->save;
856    }
857
858    # Forcibly disable nofollow plugin now since this has become
859    # a core function.
860    $cfg->PluginSwitch('nofollow/nofollow.pl=0', 1);
861    $cfg->save_config();
862
863    return 0;
864}
865
866sub update_3x_system_search_templates {
867    my $self = shift;
868
869    require MT::Template;
870    $self->progress($self->translate_escape('Updating system search template records...'));
871    my $tmpl_iter = MT::Template->load_iter({
872        type => 'search_template',
873    });
874    my %blogs;
875    while (my $tmpl = $tmpl_iter->()) {
876        $blogs{$tmpl->blog_id} = $tmpl->id;
877        $tmpl->type('search_results');
878        $tmpl->save;
879    }
880    # for any old 'search_template' system templates, remove the
881    # newly installed 'search_results' template.
882    foreach my $blog_id (keys %blogs) {
883        my $tmpl = MT::Template->load({ type => 'search_results',
884            blog_id => $blog_id, id => $blogs{$blog_id} }, {
885            not => { id => 1 } });
886        $tmpl->remove if $tmpl;
887    }
888
889    my %terms;
890    my %args;
891    if (my @blog_ids = keys %blogs) {
892        $terms{id} = \@blog_ids;
893        $args{not} = { id => 1 };
894    }
895    my $blog_iter = MT::Blog->load_iter(\%terms, \%args);
896    # These blogs does not have search results - upgrades from 3.2 or before.
897    while (my $blog = $blog_iter->()) {
898        my $tmpl = MT::Template->load(
899          { type => 'search_results', blog_id => $blog->id,});
900        $tmpl->remove if $tmpl;
901    }
902    0;
903}
904
905my $perm_role_names = {
906    4096 => 'Blog Administrator',  # administer_blog
907    30687 => 'Blog Administrator', # 32767 - 2048(not comment) - 32(reserved) = all permissions in MT3.3
908    14303 => 'Blog Administrator', # 16383 - 2048(not comment) - 32(reserved) = all permissions in MT3.2
909    2 => 'Writer', # post
910    6 => 'Writer (can upload)', # post + upload
911    17032 => 'Editor',  # edit_all_posts + edit_tags + edit_categories + rebuild
912    17036 => 'Editor (can upload)', # Editor + upload
913    144 => 'Designer', # edit_templates + rebuild
914    17292 => 'Publisher', # Editor (can upload) + send_notifications
915};
916
917{
918    my $full_perm_mask = 0;
919    my %LegacyPerms = (
920        # System-wide permissions
921        #[ 2**0, 'administer', 'System Administrator', 2, 'system' ],
922        #[ 2**1, 'create_blog', 'Create Blogs', 2, 'system' ],
923        #[ 2**2, 'view_log', 'View System Activity Log', 2, 'system' ],
924        #[ 2**3, 'manage_plugins', 'Manage Plugins', 'system' ],
925 
926        # Blog-specific permissions:
927        # The order here is the same order they are presented on the
928        # role definition screen.
929        2**0 => 'comment',# 'Add Comments', 1, 'blog'],
930        2**12 => 'administer_blog',# 'Blog Administrator', 1, 'blog'],
931        2**6 => 'edit_config',# 'Configure Blog', 1, 'blog'],
932        2**3 => 'edit_all_posts',# 'Edit All Entries', 1, 'blog'],
933        2**4 => 'edit_templates',# 'Manage Templates', 1, 'blog'],
934        2**2 => 'upload',# 'Upload File', 1, 'blog'],
935        2**1 => 'post',# 'Create Entry', 1, 'blog'],
936        2**16 => 'edit_assets',# 'Manage Assets', 1, 'blog'],
937        2**15 => 'save_image_defaults',# 'Save Image Defaults', 1, 'blog'],
938        2**9 => 'edit_categories',# 'Add/Manage Categories', 1, 'blog'],
939        2**14 => 'edit_tags',# 'Manage Tags', 1, 'blog'],
940        2**10 => 'edit_notifications',# 'Manage Notification List', 1, 'blog'],
941        2**8 => 'send_notifications',# 'Send Notifications', 1, 'blog'],
942        2**13 => 'view_blog_log',# 'View Activity Log', 1, 'blog'],
943        #[ 2**17, 'publish_post', 'Publish Post', 1, 'blog'],
944        #[ 2**18, 'manage_feedback', 'Manage Feedback', 1, 'blog'],
945        #[ 2**19, 'set_publish_paths', 'Set Publishing Paths', 1, 'blog'],
946        #[ 2**20, 'manage_pages', 'Manage Pages', 1, 'blog'],
947        # 2**5 == 32 is deprecated; reserved for future use
948        2**7 => 'rebuild',# 'Rebuild Files', 1, 'blog'],
949        # Not a real permission but a denial thereeof; unlisted because it
950        # has no label.
951        2**11 => 'not_comment',# '', 1, 'blog'],
952    );
953
954
955sub _migrate_permission_to_role {
956    my $perm = shift;
957
958    return unless $perm->author_id;
959    my $user = MT::Author->load($perm->author_id);
960    if (!$user) {
961        $perm->remove;
962        return;
963    }
964    # Don't bother with non-AUTHOR types
965    return unless $user->type == 1;
966
967    my $role_mask = $perm->role_mask;
968    $role_mask -= 32 if (32 & $role_mask) == 32; # for permissions before 3.2
969
970    if (!$full_perm_mask) {
971        # only consider blog permissions that are supported (exclude
972        # now reserved permission bits like 32).
973        foreach my $key (keys %LegacyPerms) {
974            next if $LegacyPerms{$key} =~ m/^not_/; # skip exclusion permissions
975            $full_perm_mask |= $key;
976        }
977    }
978
979    $role_mask = $full_perm_mask & $role_mask;
980
981    # '0' permission, not used for permissions, just prefs
982    return unless $role_mask;
983
984    my $name;
985    $name = MT->translate($perm_role_names->{$role_mask})
986        if $perm_role_names->{$role_mask};
987    $name ||= MT->translate("Custom ([_1])", $role_mask);
988    require MT::Role;
989    my $role = MT::Role->load({ name => $name });
990    if ($role) {
991        if (($role->role_mask != $role_mask) &&
992            ((4096 != $role->role_mask) && (30687 != $role_mask))) {
993            $role = undef;
994        }
995    }
996    unless ($role) {
997        $role = new MT::Role;
998        $role->name($name);
999        $role->description(MT->translate("This role was generated by Movable Type upon upgrade."));
1000        $role->role_mask($role_mask);
1001        $role->save;
1002    }
1003    my $blog = MT::Blog->load($perm->blog_id);
1004    $user->add_role($role, $blog) if $blog;
1005}
1006
1007sub _process_masks {
1008    my $self = shift;
1009    my ($perm) = @_;
1010
1011    my $mask = $perm->role_mask;
1012    return unless $mask;
1013    my @perms;
1014    for my $key (keys %LegacyPerms) {
1015        if (int($mask) & int($key)) {
1016            if (2 eq $key) { # post
1017                push @perms, 'create_post', 'publish_post';
1018            } elsif (64 eq $key) { #edit_config
1019                push @perms, 'edit_config', 'set_publish_paths', 'manage_feedback';
1020            } elsif (4096 eq $key) { #adminsiter_blog
1021                push @perms, 'administer_blog', 'manage_pages';
1022            } elsif (2048 eq $key) { #not_comment
1023                $perm->restrictions("'comment'");
1024            } else {
1025                push @perms, $LegacyPerms{$key};
1026            }
1027        }
1028    }
1029    my $perm_str = scalar(@perms) ? "'" . join("','", @perms) . "'" : q();
1030    $perm->permissions($perm_str);
1031    $perm->role_mask(0); ## remove legacy permissions
1032    $perm;
1033}
1034
1035sub deprecate_bitmask_permissions {
1036    my $self = shift;
1037   
1038    require MT::Permission;
1039    my $perm_iter = MT::Permission->load_iter;
1040    $self->progress($self->translate_escape('Migrating permission records to new structure...'));
1041    while (my $perm = $perm_iter->()) {
1042        if ($self->_process_masks($perm)) {
1043            $perm->save;
1044        }
1045    }
1046
1047    require MT::Role;
1048    my $role_iter = MT::Role->load_iter;
1049    $self->progress($self->translate_escape('Migrating role records to new structure...'));
1050    while (my $role = $role_iter->()) {
1051        if ($self->_process_masks($role)) {
1052            $role->save;
1053        }
1054    }
1055}
1056}
1057
1058sub migrate_system_privileges {
1059    my $self = shift;
1060
1061    require MT::Permission;
1062    my $author_iter = MT::Author->load_iter({ type => MT::Author::AUTHOR() });
1063    $self->progress($self->translate_escape('Migrating system level permissions to new structure...'));
1064    while (my $author = $author_iter->()) {
1065        my @perms;
1066        push @perms, 'administer' if $author->column('is_superuser');
1067        push @perms, 'create_blog' if $author->column('can_create_blog') || $author->column('is_superuser');
1068        push @perms, 'view_log' if $author->column('can_view_log') || $author->column('is_superuser');
1069        push @perms, 'manage_plugins' if $author->column('is_superuser');
1070        if (@perms) {
1071            my $perm = MT::Permission->load({ author_id => $author->id,
1072                blog_id => 0 });
1073            if (!$perm) {
1074                $perm = MT::Permission->new;
1075                $perm->author_id($author->id);
1076                $perm->blog_id(0);
1077            }
1078            $perm->set_these_permissions(@perms);
1079            $perm->save;
1080        }
1081    }
1082}
1083
1084sub init {
1085    my $pkg = shift;
1086    unless (%classes) {
1087        my $types = MT->registry('object_types');
1088        foreach my $type (keys %$types) {
1089            $classes{$type} = $types->{$type};
1090        }
1091    }
1092    my $fns = MT::Component->registry('upgrade_functions') || [];
1093    foreach my $fn_set (@$fns) {
1094        %functions = ( %functions, %{ $fn_set } );
1095    }
1096}
1097
1098# Step execution...
1099
1100# iterate routines:
1101#     no parameters, start with offset == 0
1102#     offset parameter, pass thru
1103#     if routine returns 0, routine is done
1104#     if routine returns undef, routine failed
1105#     if routine returns > 0, that's the new offset
1106
1107sub run_step {
1108    my $self = shift;
1109    my ($step) = @_;
1110    my ($name, %param) = @$step;
1111
1112    if (my $fn = $functions{$name}) {
1113        local $MT::CallbacksEnabled = 0;
1114        if (my $cond = $fn->{condition}) {
1115            $cond = MT->handler_to_coderef($cond);
1116            next unless $cond->($self, %param);
1117        }
1118        my %update_params;
1119        if ($fn->{updater}) {
1120            %update_params = %{$fn->{updater}};
1121            $fn->{code} ||= \&core_update_records;
1122        }
1123        my $code = $fn->{code} || $fn->{handler};
1124        $code = MT->handler_to_coderef($code);
1125        my $result = $code->($self, %param, %update_params, step => $name);
1126        if ((defined $result) && ($result > 1)) {
1127            $param{offset} = $result; $result = 1;
1128            $self->add_step($name, %param);
1129        }
1130        return $result;
1131    } else {
1132        return $self->error($self->translate_escape("Invalid upgrade function: [_1].", $name));
1133    }
1134    0;
1135}
1136
1137sub run_callbacks {
1138    my $self = shift;
1139    my ($cb, @param) = @_;
1140    local $MT::CallbacksEnabled = 1;
1141    MT->run_callbacks('MT::Upgrade::' . $cb, $self, @param);
1142}
1143
1144# Main "do" interface for controlling apparatus
1145
1146sub do_upgrade {
1147    my $self = shift;
1148    my (%opt) = @_;
1149
1150    $self->init;
1151
1152    my $harnessed = ref $opt{App} && (UNIVERSAL::can($opt{App}, 'add_step'));
1153
1154    local $App = $opt{App};
1155    local $DryRun = $opt{DryRun};
1156    local $SuperUser = $opt{SuperUser} || '';
1157    local $CLI = $opt{CLI} || '';
1158
1159    @steps = ();
1160    if ($opt{Install}) {
1161        my %init_params = (%{$opt{User} || {}}, %{$opt{Blog} || {}});
1162        $self->install_database(\%init_params);
1163    } else {
1164        $self->upgrade_database();
1165    }
1166
1167    # no app is running the show, so we must!
1168    if (!$harnessed) {
1169        # set these limits very high since we're running unharnessed
1170        $MAX_TIME = 10000000;
1171        $MAX_ROWS = 300;
1172        my $fn = \%MT::Upgrade::functions;
1173        my @these_steps = @steps;
1174        while (@these_steps) {
1175            my $step = shift @these_steps;
1176            @steps = ();
1177            $self->run_step($step);
1178            if (@steps) {
1179                push @these_steps, @steps;
1180                @these_steps = sort { $fn->{$a->[0]}->{priority} <=>
1181                                      $fn->{$b->[0]}->{priority} } @these_steps;
1182            }
1183        }
1184        return 1;
1185    } else {
1186        return \@steps;
1187    }
1188}
1189
1190sub upgrade_database {
1191    my $self = shift;
1192
1193    my $config_schema_ver;
1194    my $schema_ver;
1195    if ($config_schema_ver = MT->instance->config('SchemaVersion')) {
1196        my $needs_upgrade;
1197        $needs_upgrade = 1 if $config_schema_ver < MT->schema_version;
1198        if (!$needs_upgrade) {
1199            foreach (@MT::Components) {
1200                $needs_upgrade = 1 if $_->needs_upgrade;
1201            }
1202        }
1203        return 1 unless $needs_upgrade;
1204        $schema_ver = $config_schema_ver;
1205    } else {
1206        $schema_ver = $self->detect_schema_version;
1207    }
1208
1209    # this will add steps to upgrade all tables that need it...
1210    $self->add_step("core_upgrade_begin", from => $schema_ver);
1211    $self->check_schema;
1212    $self->add_step('core_upgrade_templates');
1213    $self->add_step('core_upgrade_end', from => $schema_ver);
1214    $self->add_step('core_finish');
1215    1;
1216}
1217
1218sub install_database {
1219    my $self = shift;
1220    my ($user) = @_;
1221
1222    # this will add steps to install all tables...
1223    $self->check_schema;
1224    # this will populate them...
1225    $self->add_step('core_seed_database', %$user);
1226    $self->add_step('core_upgrade_templates');
1227    $self->add_step('core_finish');
1228    1;
1229}
1230
1231sub check_schema {
1232    my $self = shift;
1233    my $class;
1234    foreach my $type (keys %classes) {
1235        $class = MT->model($type)
1236            or return $self->error($self->translate_escape("Error loading class [_1].", $type));
1237        $self->check_type($type);
1238    }
1239    1;
1240}
1241
1242sub check_type {
1243    my $self = shift;
1244    my ($type) = @_;
1245
1246    my $class = MT->model($type);
1247    if (my $result = $self->type_diff($type)) {
1248        if ($result->{fix}) {
1249            $self->add_step('core_fix_type', type => $type);
1250        } else {
1251            $self->add_step('core_add_column', type => $type)
1252                if $result->{add};
1253            $self->add_step('core_alter_column', type => $type)
1254                if $result->{alter};
1255            $self->add_step('core_drop_column', type => $type)
1256                if $result->{drop};
1257            $self->add_step('core_index_column', type => $type)
1258                if $result->{index};
1259        }
1260    }
1261    1;
1262}
1263
1264sub type_diff {
1265    my $self = shift;
1266    my ($type) = @_;
1267
1268    my $class = MT->model($type) or return;
1269
1270    my $table = $class->datasource;
1271    my $defs = $class->column_defs;
1272
1273    my $ddl = $class->driver->dbd->ddl_class;
1274    my $db_defs = $ddl->column_defs($class);
1275
1276    my $class_idx_defs = $class->index_defs;
1277    my $db_idx_defs = $ddl->index_defs($class);
1278
1279    # now, compare $defs and $db_defs;
1280    # here are the scenarios
1281    #   1. we find something in $defs that isn't in $db_defs
1282    #      -- column should be inserted. this may trigger a process
1283    #   2. we find something in $db_defs that isn't in $defs
1284    #      -- this is a-ok. user may have added a column.
1285    #   3. we find a difference between $defs and $db_defs for a field
1286    #      a. type differs; this may trigger a process
1287    #      b. type is same, but null property differs; this may
1288    #         trigger a process
1289    #      c. type is same, but size differs; this may trigger a process
1290    #      d. key differs
1291    #      e. auto differs (auto-increment)
1292    #   4. table doesn't exist and must be created
1293
1294    my $fix_class;
1295    $fix_class = 1 unless defined $db_defs;
1296
1297    # we're only scanning defined columns; we don't care about
1298    # columns that are unique to the table.
1299    my (@cols_to_add, @cols_to_alter, @cols_to_drop, @cols_to_index);
1300
1301    if (!$fix_class) {
1302        my @def_cols = keys %$defs;
1303
1304        foreach my $col (@def_cols) {
1305            my $col_def = $defs->{$col};
1306            next if !defined $col_def;
1307
1308            $col_def->{name} = $col;
1309
1310            my $db_def = $db_defs->{$col};
1311
1312            if (!$db_def) {
1313                # column is missing altogether; we're going to have to add it
1314                push @cols_to_add, $col;
1315            } else {
1316                if (($col_def->{type} eq 'string')
1317                 && ($db_def->{type} eq 'string')
1318                 && ($col_def->{size} <= $db_def->{size})) {
1319                    if (($col_def->{not_null} || 0) != ($db_def->{not_null} || 0)) {
1320                        push @cols_to_alter, $col;
1321                    }
1322                } elsif ($ddl->type2db($col_def)
1323                      ne $ddl->type2db($db_def)) {
1324                    # types are different
1325                    # don't bother if the database has sufficient
1326                    # capacity for this field
1327                    next if ($db_def->{type} eq 'integer')
1328                         && ($col_def->{type} eq 'smallint'
1329                          || $col_def->{type} eq 'boolean');
1330                    push @cols_to_alter, $col;
1331                } elsif (($col_def->{not_null} || 0) != ($db_def->{not_null} || 0)) {
1332                    push @cols_to_alter, $col;
1333                }
1334            }
1335        }
1336
1337        foreach my $key (keys %$class_idx_defs) {
1338            my $db_idx_def = $db_idx_defs->{$key};
1339            if (!$db_idx_def) {
1340                push @cols_to_index, $key;
1341                next;
1342            }
1343            # if there is a mismatch in definition, add it to index
1344            my $class_idx_def = $class_idx_defs->{$key};
1345            if (ref($class_idx_def)) {
1346                if (!ref $db_idx_def) {
1347                    push @cols_to_index, $key;
1348                }
1349                else {
1350                    my $db_cols;
1351                    if (exists $db_idx_def->{columns}) {
1352                        $db_cols = join ',', @{ $db_idx_def->{columns} };
1353                    }
1354                    else {
1355                        $db_cols = $key;
1356                    }
1357                    my $class_cols;
1358                    if (exists $class_idx_def->{columns}) {
1359                        $class_cols = join ',', @{ $class_idx_def->{columns} };
1360                    }
1361                    else {
1362                        $class_cols = $key;
1363                    }
1364                    if ($db_cols ne $class_cols) {
1365                        push @cols_to_index, $key;
1366                    }
1367                    else {
1368                        if (($db_idx_def->{unique} || 0) != ($class_idx_def->{unique} || 0)) {
1369                            push @cols_to_index, $key;
1370                        }
1371                    }
1372                }
1373            }
1374            else {
1375                if (ref $db_idx_def) {
1376                    push @cols_to_index, $key;
1377                }
1378            }
1379        }
1380    }
1381
1382    if ($fix_class || @cols_to_add || @cols_to_alter || @cols_to_drop || @cols_to_index) {
1383        my %param;
1384        $param{drop} = \@cols_to_drop if @cols_to_drop;
1385        $param{add} = \@cols_to_add if @cols_to_add;
1386        $param{alter} = \@cols_to_alter if @cols_to_alter;
1387        $param{fix} = $fix_class;
1388        $param{index} = \@cols_to_index if @cols_to_index;
1389        if ((@cols_to_add && !$ddl->can_add_column) ||
1390            (@cols_to_alter && !$ddl->can_alter_column) ||
1391            (@cols_to_drop && !$ddl->can_drop_column)) {
1392            $param{fix} = 1;
1393        }
1394        return \%param;
1395    }
1396    undef;
1397}
1398
1399sub seed_database {
1400    my $self = shift;
1401    my (%param) = @_;
1402
1403    require MT::Author;
1404    return undef if MT::Author->count;
1405
1406    $self->progress($self->translate_escape("Creating initial blog and user records..."));
1407
1408    local $MT::CallbacksEnabled = 1;
1409
1410    require MT::L10N;
1411    my $lang = exists $param{user_lang} ? $param{user_lang} : MT->config->DefaultLanguage;
1412    my $LH = MT::L10N->get_handle($lang);
1413
1414    # TBD: parameter for username/password provided by user from $app
1415    use URI::Escape;
1416    my $author = MT::Author->new;
1417    $author->name(exists $param{user_name} ? uri_unescape($param{user_name}) : 'Melody');
1418    $author->type(MT::Author::AUTHOR());
1419    $author->set_password(exists $param{user_password} ? uri_unescape($param{user_password}) : 'Nelson');
1420    $author->email(exists $param{user_email} ? uri_unescape($param{user_email}) : '');
1421    $author->hint(exists $param{user_hint} ? uri_unescape($param{user_hint}) : '');
1422    $author->nickname(exists $param{user_nickname} ? uri_unescape($param{user_nickname}) : '');
1423    $author->is_superuser(1);
1424    $author->can_create_blog(1);
1425    $author->can_view_log(1);
1426    $author->can_manage_plugins(1);
1427    $author->preferred_language($lang);
1428    $author->external_id(MT::Author->pack_external_id($param{user_external_id})) if exists $param{user_external_id};
1429    $author->auth_type(MT->config->AuthenticationModule);
1430    $author->save or return $self->error($self->translate_escape("Error saving record: [_1].", $author->errstr));
1431    $App->{author} = $author if ref $App;
1432
1433    $self->create_default_roles(%param);
1434
1435    require MT::Blog;
1436    my $blog = MT::Blog->create_default_blog(MT->translate('First Blog'), $param{blog_template_set})
1437        or return $self->error($self->translate_escape("Error saving record: [_1].", MT::Blog->errstr));
1438    $blog->site_path(exists $param{blog_path} ? uri_unescape($param{blog_path}) : '');
1439    $blog->site_url(exists $param{blog_url} ? uri_unescape($param{blog_url}) : '');
1440    $blog->name(exists $param{blog_name} ? uri_unescape($param{blog_name}) : '');
1441    $blog->server_offset(exists $param{blog_timezone} ? ($param{blog_timezone} || 0) : 0);
1442    $blog->template_set($param{blog_template_set});
1443    $blog->save;
1444    MT->run_callbacks( 'blog_template_set_change', { blog => $blog } );
1445
1446    # Create an initial entry and comment for this blog
1447    require MT::Entry;
1448    my $entry = MT::Entry->new;
1449    $entry->blog_id($blog->id);
1450    $entry->title(MT->translate("I just finished installing Movable Type [_1]!", int(MT->product_version)));
1451    $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!"));
1452    $entry->author_id($author->id);
1453    $entry->status(MT::Entry::RELEASE());
1454    $entry->save
1455        or return $self->error($self->translate_escape("Error saving record: [_1].", MT::Entry->errstr));
1456
1457    require MT::Comment;
1458    my $comment = MT::Comment->new;
1459    $comment->entry_id($entry->id);
1460    $comment->blog_id($blog->id);
1461    $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."));
1462    $comment->visible(1);
1463    $comment->junk_status(1);
1464    $comment->author(exists $param{user_nickname} ? uri_unescape($param{user_nickname}) : undef);
1465    $comment->save
1466        or return $self->error($self->translate_escape("Error saving record: [_1].", MT::Comment->errstr));
1467
1468    require MT::Association;
1469    require MT::Role;
1470    my ($blog_admin_role) = MT::Role->load_by_permission("administer_blog");
1471    MT::Association->link( $blog => $blog_admin_role => $author );
1472
1473    1;
1474}
1475
1476## Translation
1477# translate('Blog Administrator')
1478# translate('Can administer the blog.')
1479# translate('Editor')
1480# translate('Can upload files, edit all entries/categories/tags on a blog and publish the blog.')
1481# translate('Author')
1482# translate('Can create entries, edit their own, upload files and publish.')
1483# translate('Designer')
1484# translate('Can edit, manage and publish blog templates.')
1485# translate('Webmaster')
1486# translate('Can manage pages and publish blog templates.')
1487# translate('Contributor')
1488# translate('Can create entries, edit their own and comment.')
1489# translate('Moderator')
1490# translate('Can comment and manage feedback.')
1491# translate('Commenter')
1492# translate('Can comment.')
1493
1494sub create_default_roles {
1495    my $self = shift;
1496    my (%param) = @_;
1497
1498    my @default_roles = (
1499        { name => 'Blog Administrator',
1500          description => 'Can administer the blog.',
1501          role_mask => 2**12,
1502          perms => ['administer_blog'] },
1503        { name => 'Editor',
1504          description => 'Can upload files, edit all entries/categories/tags on a blog and publish the blog.',
1505          perms => ['comment', 'create_post', 'publish_post', 'edit_all_posts', 'edit_categories', 'edit_tags', 'manage_pages',
1506                    'rebuild', 'upload', 'send_notifications', 'manage_feedback'], },
1507        { name => 'Author',
1508          description => 'Can create entries, edit their own, upload files and publish.',
1509          perms => ['comment', 'create_post', 'publish_post', 'upload', 'send_notifications'], },
1510        { name => 'Designer',
1511          description => 'Can edit, manage and publish blog templates.',
1512          role_mask => (2**4 + 2**7),
1513          perms => ['edit_templates', 'rebuild'] },
1514        { name => 'Webmaster',
1515          description => 'Can manage pages and publish blog templates.',
1516          perms => ['manage_pages', 'rebuild'] },
1517        { name => 'Contributor',
1518          description => 'Can create entries, edit their own and comment.',
1519          perms => ['comment', 'create_post'], },
1520        { name => 'Moderator',
1521          description => 'Can comment and manage feedback.',
1522          perms => ['comment', 'manage_feedback'], },
1523        { name => 'Commenter',
1524          description => 'Can comment.',
1525          role_mask => 2**0,
1526          perms => ['comment'], },
1527    );
1528
1529    require MT::Role;
1530    return if MT::Role->count();
1531
1532    foreach my $r (@default_roles) {
1533        my $role = MT::Role->new();
1534        $role->name(MT->translate($r->{name}));
1535        $role->description(MT->translate($r->{description}));
1536        $role->clear_full_permissions;
1537        $role->set_these_permissions($r->{perms});
1538        if ($r->{name} =~ m/^System/) {
1539            $role->is_system(1);
1540        }
1541        $role->role_mask($r->{role_mask}) if exists $r->{role_mask};
1542        $role->save;
1543    }
1544
1545    1;
1546}
1547
1548sub remove_mtviewphp {
1549    my $self = shift;
1550    my (%param) = @_;
1551
1552    require MT::Template;
1553    $self->progress($self->translate_escape('Removing Dynamic Site Bootstrapper index template...'));
1554    MT::Template->remove( { type => 'index', outfile => 'mtview.php' } );
1555    1;
1556}
1557
1558sub migrate_commenter_auth {
1559    my ($self) = shift;
1560    my (%param) = @_;
1561
1562    my $iter = MT::Blog->load_iter({ 'allow_reg_comments' => 1 });
1563    while (my $blog = $iter->()) {
1564        $blog->commenter_authenticators('TypeKey') if $blog->remote_auth_token;
1565        $blog->save;
1566    }
1567    1;
1568}
1569
1570sub upgrade_templates {
1571    my $self = shift;
1572    my (%opt) = @_;
1573
1574    my $install = $opt{Install} || 0;
1575
1576    my $updated = 0;
1577
1578    my $tmpl_list;
1579    require MT::DefaultTemplates;
1580    $tmpl_list = MT::DefaultTemplates->templates || [];
1581
1582    my $mt = MT->instance;
1583    my @arch_tmpl;
1584
1585    require MT::Template;
1586    require MT::Blog;
1587
1588    my $installer = sub {
1589        my ($val, $blog_id) = @_;
1590
1591        my $terms = {};
1592        $terms->{type} = $val->{type};
1593        $terms->{name} = $val->{name}
1594            if $val->{set} ne 'system';
1595        $terms->{blog_id} = $blog_id;
1596
1597        return 1 if MT::Template->count( $terms );
1598
1599        $self->progress($self->translate_escape("Creating new template: '[_1]'.", $val->{name}));
1600
1601        my $obj = MT::Template->new;
1602        $obj->build_dynamic(0);
1603        foreach my $v (keys %$val) {
1604            $obj->column($v, $val->{$v}) if $obj->has_column($v);
1605        }
1606        $obj->blog_id($blog_id);
1607        $obj->save or return $self->error($self->translate_escape("Error saving record: [_1].", $obj->errstr));
1608        $updated = 1;
1609        if ($val->{mappings}) {
1610            push @arch_tmpl, {
1611                template => $obj,
1612                mappings => $val->{mappings},
1613            };
1614        }
1615        return 1;
1616    };
1617
1618    for my $val (@$tmpl_list) {
1619        if (!$install) {
1620            if (!$val->{global}) {
1621                next if $val->{set} ne 'system';
1622            }
1623        }
1624
1625        my $p = $val->{plugin} || $mt;
1626        $val->{name} = $p->translate($val->{name});
1627        $val->{text} = $p->translate_templatized($val->{text});
1628
1629        if ($val->{global}) {
1630            $installer->($val, 0) or return;
1631        }
1632        else {
1633            my $iter = MT::Blog->load_iter();
1634            while (my $blog = $iter->()) {
1635                $installer->($val, $blog->id);
1636            }
1637        }
1638    }
1639
1640    if (@arch_tmpl) {
1641        $self->progress($self->translate_escape("Mapping templates to blog archive types..."));
1642        require MT::TemplateMap;
1643
1644        for my $map_set (@arch_tmpl) {
1645            my $tmpl = $map_set->{template};
1646            my $mappings = $map_set->{mappings};
1647            foreach my $map_key (keys %$mappings) {
1648                my $m = $mappings->{$map_key};
1649                my $at = $m->{archive_type};
1650                # my $preferred = $mappings->{$map_key}{preferred};
1651                my $map = MT::TemplateMap->new;
1652                $map->archive_type($at);
1653                $map->is_preferred(1);
1654                $map->template_id($tmpl->id);
1655                $map->file_template($m->{file_template}) if $m->{file_template};
1656                $map->blog_id($tmpl->blog_id);
1657                $map->save;
1658            }
1659        }
1660    }
1661
1662    $updated;
1663}
1664
1665sub rename_php_plugin_filenames {
1666    my $self = shift;
1667
1668    my $server_path = MT->instance->server_path() || '';
1669    $server_path =~ s/\/*$//;
1670    my $plugin_path = File::Spec->canonpath("$server_path/php/plugins");
1671
1672    # If PHP plugins directory doesn't exist, return without failing
1673    return 0 if !-d $plugin_path;
1674
1675    opendir(DIR, $plugin_path)
1676        or return 0;
1677    my @files = grep { /^(?:function|block)\.(.*)\.php$/ } readdir(DIR);
1678    closedir(DIR);
1679
1680    return 0 unless @files;
1681
1682    $self->progress($self->translate_escape('Renaming PHP plugin file names...'));
1683    my @error_files = ();
1684    for my $file (@files) {
1685        my $newfile = lc $file;
1686        next if $file eq $newfile;
1687        if (!rename("$plugin_path/$file", "$plugin_path/$newfile")) {
1688            push @error_files, $file;
1689        }
1690    }
1691    if ($#error_files >= 0) {
1692        $self->progress($self->translate_escape('Error renaming PHP files. Please check the Activity Log.'));
1693        MT->log(
1694            {
1695                message => $self->translate_escape("Cannot rename in [_1]: [_2].", $plugin_path, join(', ', @error_files)),
1696                level   => MT::Log::ERROR(),
1697                category => 'upgrade',
1698            }
1699        );
1700    }
1701    1;
1702}
1703
1704sub update_widget_templates {
1705    my $self = shift;
1706
1707    $self->progress($self->translate_escape('Updating widget template records...'));
1708    require MT::Template;
1709    my $iter = MT::Template->load_iter(
1710        { type => 'custom' },
1711        { 'sort' => 'name',
1712          'direction' => 'ascend' }
1713    );
1714
1715    while (my $tmpl = $iter->()) {
1716        my $name = $tmpl->name();
1717        if ($name =~ s/^(?:Widget|Sidebar): ?//) {
1718            $tmpl->name($name);
1719            $tmpl->type('widget');
1720            $tmpl->save;
1721        }
1722    }
1723    1;
1724}
1725
1726sub remove_unused_templatemap {
1727    my $self = shift;
1728
1729    $self->progress($self->translate_escape('Removing unused template maps...'));
1730
1731    require MT::Blog;
1732    require MT::TemplateMap;
1733    my $iter = MT::Blog->load_iter();
1734    while (my $blog = $iter->()) {
1735        my @blog_at = map { "'$_'" } split ',', $blog->archive_type;
1736        MT::TemplateMap->remove(
1737            { blog_id => $blog->id, archive_type => \@blog_at },
1738            { not => { archive_type => 1 } }
1739        );
1740    }
1741}
1742
1743###  Upgrade triggers
1744
1745# we don't need these yet, but it makes me feel good to have them around
1746
1747# 'pre' triggers should execute quickly. 'post' triggers can add steps
1748# if they require processing that will take time to complete.
1749
1750sub pre_upgrade_class { 1 }
1751sub post_upgrade_class { 1 }
1752sub pre_alter_column { 1 }
1753sub post_alter_column { 1 }
1754sub pre_drop_column { 1 }
1755sub post_drop_column { 1 }
1756sub pre_add_column { 1 }
1757sub pre_index_column { 1 }
1758sub post_index_column { 1 }
1759sub pre_schema_upgrade { 1 }
1760
1761# issued last, after all table creation...
1762
1763sub post_schema_upgrade {
1764    my $self = shift;
1765    my ($from) = @_;
1766
1767    my $plugin_ver = MT->config('PluginSchemaVersion') || {};
1768    $plugin_ver->{'core'} = $from;
1769
1770    # run any functions that define a version_limit and where the schema we're
1771    # upgrading from is below that limit.
1772    foreach my $fn (keys %functions) {
1773        my $save_from = $from;
1774        {
1775            my $func = $functions{$fn};
1776
1777            if ($func->{plugin} && (UNIVERSAL::isa($func->{plugin}, 'MT::Component'))) {
1778                my $id = $func->{plugin}->id;
1779                $from = $plugin_ver->{$id};
1780            }
1781            if ($func->{version_limit}
1782                && (defined $from)
1783                && ($from < $func->{version_limit})) {
1784                $self->add_step($fn, from => $from);
1785            }
1786            elsif ($func
1787                && !exists($func->{version_limit})
1788                && !defined($from)) {
1789                $self->add_step($fn);
1790            }
1791        }
1792        $from = $save_from;
1793    }
1794
1795    1;
1796}
1797
1798sub pre_create_table {
1799    my $self = shift;
1800    my ($class) = @_;
1801    $class->driver->dbd->ddl_class->drop_sequence($class);
1802}
1803
1804sub post_create_table {
1805    my $self = shift;
1806    my ($class) = @_;
1807
1808    $class->driver->dbd->ddl_class->create_sequence($class);
1809
1810    if (!$Installing) {
1811        foreach (keys %functions) {
1812            my $func = $functions{$_};
1813            next unless $func->{on_class};
1814            $self->add_step($_) if $func->{on_class} eq $class;
1815        }
1816    }
1817
1818    1;
1819}
1820
1821# Note that this trigger only fires on BerkeleyDB for columns
1822# that are non-null or indexed.
1823
1824sub post_add_column {
1825    my $self = shift;
1826    my ($class, $col_defs) = @_;
1827
1828    if (!$Installing) {
1829        my %cols = map { $_ => 1 } @$col_defs;
1830        foreach (keys %functions) {
1831            my $func = $functions{$_};
1832            next unless $func->{on_field};
1833            if ($func->{on_field} =~ m/^\Q$class\E->(.*)/) {
1834                $self->add_step($_) if $cols{$1};
1835            }
1836        }
1837    }
1838    1;
1839}
1840
1841# Passthru routines-- passing to calling application...
1842
1843sub progress {
1844    my $self = shift;
1845    $App->progress(@_) if $App;
1846}
1847
1848sub translate_escape {
1849    my $self = shift;
1850    my $trans = MT->translate(@_);
1851    return $trans if $CLI;
1852    $trans = MT::I18N::encode_text($trans, undef, 'utf-8');
1853    return MT::Util::escape_unicode($trans);
1854}
1855
1856sub error {
1857    my $self = shift;
1858    my ($msg) = @_;
1859    $App->error(@_) if $App;
1860    return undef;
1861}
1862
1863sub add_step {
1864    my $self = shift;
1865    if ($App && (ref $App)) {
1866        $App->add_step(@_);
1867    } else {
1868        push @steps, [ @_ ];
1869    }
1870}
1871
1872# Misc utilities.
1873
1874sub detect_schema_version {
1875    my $self = shift;
1876
1877    require MT::Object;
1878    my $driver = MT::Object->driver;
1879
1880    require MT::Config;
1881    if ($driver->table_exists('MT::Config')) {
1882        return 3.2;
1883    }
1884
1885    require MT::Template;
1886    my $dyn_error_template =
1887        MT::Template->count({type => 'dynamic_error'});
1888    if ($dyn_error_template) {
1889        return 3.1;
1890    }
1891
1892    my $comment_pending_template =
1893        MT::Template->count({type => 'comment_pending'});
1894    if ($comment_pending_template) {
1895        return 3.0;
1896    }
1897
1898    require MT::TemplateMap;
1899    if ($driver->table_exists('MT::TemplateMap')) {
1900        return 2.0;
1901    }
1902
1903    1.0;
1904}
1905
1906# A note about upgrade routines:
1907#
1908# They should all be 'safe' to execute, regardless of the
1909# active schema. In other words, running them twice in a row
1910# should not cause any errors or break the schema.
1911
1912sub core_fix_type {
1913    my $self = shift;
1914    my (%param) = @_;
1915
1916    my $type = $param{type};
1917    my $class = MT->model($type);
1918
1919    my $result = $self->type_diff($type);
1920    return 1 unless $result;
1921    return 1 unless $result->{fix};
1922
1923    my $alter = $result->{alter};
1924    my $add = $result->{add};
1925    my $drop = $result->{drop};
1926    my $index = $result->{index};
1927
1928    my $driver = $class->driver;
1929    my $ddl = $driver->dbd->ddl_class;
1930    my @stmts;
1931    push @stmts, sub { $self->pre_upgrade_class($class) };
1932    push @stmts, $ddl->upgrade_begin($class);
1933    push @stmts, sub { $self->pre_create_table($class) };
1934    push @stmts, sub { $self->pre_add_column($class, $add) } if $add;
1935    push @stmts, sub { $self->pre_alter_column($class, $alter) } if $alter;
1936    push @stmts, sub { $self->pre_drop_column($class, $drop) } if $drop;
1937    push @stmts, sub { $self->pre_index_column($class, $index) } if $index;
1938    push @stmts, $ddl->fix_class($class);
1939    push @stmts, sub { $self->post_create_table($class) };
1940    push @stmts, sub { $self->post_add_column($class, $add) } if $add;
1941    push @stmts, sub { $self->post_alter_column($class, $alter) } if $alter;
1942    push @stmts, sub { $self->post_drop_column($class, $drop) } if $drop;
1943    push @stmts, sub { $self->post_index_column($class, $index) } if $index;
1944    push @stmts, $ddl->upgrade_end($class);
1945    push @stmts, sub { $self->post_upgrade_class($class) };
1946    $self->run_statements($class, @stmts);
1947}
1948
1949sub core_column_action {
1950    my $self = shift;
1951    my ($action, %param) = @_;
1952
1953    my $type = $param{type};
1954    my $class = MT->model($type);
1955    my $defs = $class->column_defs;
1956
1957    my $result = $self->type_diff($type);
1958    return 1 unless $result;
1959    my $columns = $result->{$action};
1960    return 1 unless $columns;
1961
1962    my $pre_method = "pre_${action}_column";
1963    my $post_method = "post_${action}_column";
1964    my $method = "${action}_column";
1965
1966    my $driver = $class->driver;
1967    my $ddl = $driver->dbd->ddl_class;
1968    my @stmts;
1969    push @stmts, sub { $self->pre_upgrade_class($class) };
1970    push @stmts, $ddl->upgrade_begin($class);
1971    push @stmts, sub { $self->$pre_method($class, $columns) };
1972    push @stmts, $ddl->$method($class, $_) foreach @$columns;
1973    push @stmts, sub { $self->$post_method($class, $columns) };
1974    push @stmts, $ddl->upgrade_end($class);
1975    push @stmts, sub { $self->post_upgrade_class($class) };
1976    $self->run_statements($class, @stmts);
1977}
1978
1979sub run_statements {
1980    my $self = shift;
1981    my ($class, @stmts) = @_;
1982
1983    my $driver = $class->driver;
1984    my $defs = $class->column_defs;
1985    my $dbh = $driver->rw_handle;
1986    my $mt = MT->instance;
1987
1988    my $updated = 0;
1989    if (@stmts) {
1990        $self->progress($self->translate_escape("Upgrading table for [_1] records...", $class->can('class_label') ? $class->class_label : $class));
1991        eval {
1992            foreach my $stmt (@stmts) {
1993                if (ref $stmt eq 'CODE') {
1994                    $stmt->() if !$DryRun;
1995                } else {
1996                    if ($dbh && !$DryRun) {
1997                        my $err;
1998                        $dbh->do($stmt) or $err = $dbh->errstr;
1999                        if ($err) {
2000                            # ignore drop errors; the table/sequence/constraint
2001                            # didn't exist
2002                            if (($stmt !~ m/^drop /i) && ($stmt !~ m/DROP CONSTRAINT /i)) {
2003                                die "failed to execute statement $stmt: $err";
2004                            }
2005                        }
2006                    } elsif ($dbh && $DryRun) {
2007                        $self->run_callbacks('SQL', $stmt);
2008                    }
2009                }
2010                $updated = 1;
2011            }
2012        };
2013        if ($@) {
2014            return $self->error($@);
2015        }
2016    }
2017    $updated;
2018}
2019
2020sub core_upgrade_begin {
2021    my $self = shift;
2022    my (%param) = @_;
2023    my $from_schema = $param{from};
2024    if ($from_schema) {
2025        my $cur_schema = MT->schema_version;
2026        $self->progress($self->translate_escape("Upgrading database from version [_1].", $from_schema)) if $from_schema < $cur_schema;
2027        $self->pre_schema_upgrade($from_schema);
2028    }
2029}
2030
2031sub core_upgrade_end {
2032    my $self = shift;
2033    my (%param) = @_;
2034
2035    my $from_schema = $param{from};
2036    if ($from_schema) {
2037        $self->post_schema_upgrade($from_schema);
2038    }
2039    1;
2040}
2041
2042sub core_finish {
2043    my $self = shift;
2044
2045    my $user;
2046    if ((ref $App) && ($App->{author})) {
2047        $user = $App->{author};
2048    }
2049
2050    my $cfg = MT->config;
2051    my $cur_schema = MT->instance->schema_version;
2052    my $old_schema = $cfg->SchemaVersion || 0;
2053    if ($cur_schema > $old_schema) {
2054        $self->progress($self->translate_escape("Database has been upgraded to version [_1].", $cur_schema)) ;
2055        if ($user && !$DryRun) {
2056            MT->log(MT->translate("User '[_1]' upgraded database to version [_2]", $user->name, $cur_schema));
2057        }
2058        $cfg->SchemaVersion( $cur_schema, 1 );
2059    }
2060
2061    my $plugin_schema = $cfg->PluginSchemaVersion || {};
2062    foreach my $plugin (@MT::Components) {
2063        my $ver = $plugin->schema_version;
2064        next unless $ver;
2065        next if $plugin->id eq 'core';
2066        my $old_plugin_schema = $plugin_schema->{$plugin->id} || 0;
2067        if ($old_plugin_schema && ($ver > $old_plugin_schema)) {
2068            $self->progress($self->translate_escape("Plugin '[_1]' upgraded successfully to version [_2] (schema version [_3]).", $plugin->label, $plugin->version || '-', $ver));
2069            if ($user && !$DryRun) {
2070                MT->log(MT->translate("User '[_1]' upgraded plugin '[_2]' to version [_3] (schema version [_4]).", $user->name, $plugin->label, $plugin->version || '-', $ver));
2071            }
2072        } elsif ($ver && !$old_plugin_schema) {
2073            $self->progress($self->translate_escape("Plugin '[_1]' installed successfully.", $plugin->label));
2074            if ($user && !$DryRun) {
2075                MT->log(MT->translate("User '[_1]' installed plugin '[_2]', version [_3] (schema version [_4]).", $user->name, $plugin->label, $plugin->version || '-', $ver));
2076            }
2077        }
2078        $plugin_schema->{$plugin->id} = $ver;
2079    }
2080    if (keys %$plugin_schema) {
2081        $cfg->PluginSchemaVersion($plugin_schema, 1);
2082    }
2083
2084    my $cur_version = MT->version_number;
2085    if ( !defined($cfg->MTVersion) || ( $cur_version > $cfg->MTVersion ) ) {
2086        $cfg->MTVersion( $cur_version, 1 );
2087    }
2088    $cfg->save_config unless $DryRun;
2089
2090    # do one last thing....
2091    if ((ref $App) && ($App->can('finish'))) {
2092        $App->finish();
2093    }
2094
2095    1;
2096}
2097
2098sub core_set_superuser {
2099    my $self = shift;
2100
2101    my $app = $App;
2102    my $author;
2103    if ((ref $app) && ($app->{author})) {
2104        require MT::Author;
2105        $self->progress($self->translate_escape("Setting your permissions to administrator."));
2106        $author = MT::Author->load($app->{author}->id);
2107    } elsif ($SuperUser) {
2108        require MT::Author;
2109        $self->progress($self->translate_escape("Setting your permissions to administrator."));
2110        $author = MT::Author->load($SuperUser);
2111    }
2112
2113    if ($author) {
2114        $author->is_superuser(1);
2115        $author->save or return $self->error($self->translate_escape("Error saving record: [_1].", $author->errstr));
2116    }
2117
2118    1;
2119}
2120
2121sub core_remove_unique_constraints {
2122    my $self = shift;
2123
2124    my $driver = MT::Object->driver;
2125    if (ref $driver->dbd =~ m/::Pg$/) {
2126        # category, author, permission, template
2127        $driver->sql([
2128            'alter table mt_category drop constraint mt_category_category_blog_id_key',
2129            'create index mt_category_label on mt_category (category_label)',
2130
2131            'alter table mt_author drop constraint mt_author_author_name_key',
2132            'create index mt_author_name on mt_author (author_name)',
2133            'alter table mt_permission drop constraint mt_permission_permission_blog_id_key',
2134            'create index mt_permission_blog_id on mt_permission (permission_blog_id)',
2135            'alter table mt_template drop constraint mt_template_template_blog_id_key',
2136            'create index mt_template_blog_id on mt_template (template_blog_id)'
2137        ]);
2138    } elsif (ref $driver->dbd =~ m/::mysql$/) {
2139        $driver->sql([
2140            'alter table mt_category drop index category_blog_id',
2141            'create index category_blog_id on mt_category (category_blog_id)',
2142            'create index category_label on mt_category (category_label)',
2143            'alter table mt_author drop index author_name',
2144            'create index author_name on mt_author (author_name)',
2145            'alter table mt_permission drop index permission_blog_id',
2146            'create index permission_blog_id on mt_permission (permission_blog_id)',
2147            'alter table mt_template drop index template_blog_id',
2148            'create index template_blog_id on mt_template (template_blog_id)'
2149        ]);
2150    }
2151    1;
2152}
2153
2154sub _merge_comment_response_templates_updater {
2155    my ($blog) = @_;
2156    require MT::Template;
2157    my $tmpl = MT::Template->load({ blog_id => $blog->id, type => 'comment_response' });
2158    unless ($tmpl) {
2159        $tmpl = new MT::Template;
2160        $tmpl->blog_id($blog->id);
2161        $tmpl->type('comment_response');
2162    }
2163
2164    my $confirm_template = <<'EOT';
2165<MTSetVarBlock name="page_title"><__trans phrase="Comment Posted"></MTSetVarBlock>
2166
2167<MTSetVar name="heading" value="<__trans phrase="Confirmation...">">
2168
2169<MTSetVarBlock name="message">
2170<p><__trans phrase="Your comment has been posted!"></p>
2171</MTSetVarBlock>
2172EOT
2173
2174    my $pending_template = <<'EOT';
2175<MTSetVarBlock name="page_title"><__trans phrase="Comment Pending"></MTSetVarBlock>
2176
2177<MTSetVar name="heading" value="<__trans phrase="Thank you for commenting.">">
2178
2179<MTSetVarBlock name="message">
2180<p><__trans phrase="Your comment has been received and held for approval by the blog owner."></p>
2181</MTSetVarBlock>
2182EOT
2183
2184    my $error_template = <<'EOT';
2185<MTSetVarBlock name="page_title"><__trans phrase="Comment Submission Error"></MTSetVarBlock>
2186
2187<MTSetVar name="heading" value="$page_title">
2188
2189<MTSetVarBlock name="message">
2190<p><__trans phrase="Your comment submission failed for the following reasons:"></p>
2191<blockquote>
2192    <$MTErrorMessage$>
2193</blockquote>
2194</MTSetVarBlock>
2195EOT
2196
2197    my $header_template = <<'EOT';
2198<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2199    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2200<html xmlns="http://www.w3.org/1999/xhtml" id="sixapart-standard">
2201<head>
2202    <meta http-equiv="Content-Type" content="text/html; charset=<$MTPublishCharset$>" />
2203    <meta name="generator" content="<$MTProductName version="1"$>" />
2204    <link rel="stylesheet" href="<$MTBlogURL$>styles-site.css" type="text/css" />
2205    <title>
2206    <__trans phrase="[_1]: [_2]" params="<$MTBlogName encode_html="1"$>%%<$MTGetVar name="page_title"$>">
2207    </title>
2208    <script type="text/javascript" src="<$MTBlogURL$>mt-site.js"></script>
2209</head>
2210<body class="layout-one-column comment-preview" onload="individualArchivesOnLoad(commenter_name)">
2211    <div id="container">
2212        <div id="container-inner" class="pkg">
2213            <div id="banner">
2214                <div id="banner-inner" class="pkg">
2215                    <h1 id="banner-header"><a href="<$MTBlogURL$>" accesskey="1"><$MTBlogName encode_html="1"$></a></h1>
2216                    <h2 id="banner-description"><$MTBlogDescription$></h2>
2217                </div>
2218            </div>
2219            <div id="pagebody">
2220                <div id="pagebody-inner" class="pkg">
2221                    <div id="alpha">
2222                        <div id="alpha-inner" class="pkg">
2223EOT
2224
2225    my $footer_template = <<'EOT';
2226                        </div>
2227                    </div>
2228                </div>
2229            </div>
2230        </div>
2231    </div>
2232</body>
2233</html>
2234EOT
2235
2236    my $message_template = <<'EOT';
2237<h1><$MTGetVar name="heading"$></h1>
2238
2239<$MTGetVar name="message"$>
2240
2241<p><__trans phrase="Return to the <a href="[_1]">original entry</a>." params="<$MTEntryLink$>"></p>
2242EOT
2243
2244    my $mt = MT->instance;
2245    $tmpl->name($mt->translate("Comment Response"));
2246    $tmpl->text($mt->translate_templatized(<<"EOT"));
2247<MTSetVar name="system_template" value="1">
2248<MTSetVar name="feedback_template" value="1">
2249
2250<MTIf name="body_class" eq="mt-comment-pending">
2251$pending_template
2252</MTIf>
2253
2254<MTIf name="body_class" eq="mt-comment-error">
2255$error_template
2256</MTIf>
2257
2258<MTIf name="body_class" eq="mt-comment-confirmation">
2259$confirm_template
2260</MTIf>
2261
2262$header_template
2263
2264$message_template
2265
2266$footer_template
2267EOT
2268    $tmpl->save;
2269}
2270
2271sub core_create_config_table {
2272    my $self = shift;
2273
2274    require MT::Config;
2275    my $config = MT::Config->load();
2276    if (!$config) {
2277        #$self->progress($self->translate_escape("Creating configuration record."));
2278        $config = MT::Config->new;
2279        $config->data('');
2280        $config->save or return $self->error($self->translate_escape("Error saving record: [_1].", $config->errstr));
2281    }
2282    return 1;
2283}
2284
2285sub core_set_enable_archive_paths {
2286    my $self = shift;
2287    MT->config->EnableArchivePaths(1, 1);
2288    return 1;
2289}
2290
2291sub core_create_template_maps {
2292    my $self = shift;
2293    my (%param) = @_;
2294   
2295    my $offset = $param{offset};
2296    require MT::Template;
2297    require MT::TemplateMap;
2298    require MT::Blog;
2299
2300    my $msg = $self->translate_escape("Creating template maps...");
2301    if ($offset) {
2302        my $count = MT::Template->count;
2303        return 1 unless $count;
2304        $self->progress(sprintf("$msg (%d%%)", ($offset / $count * 100)), 1);
2305    } else {
2306        $self->progress($msg, 1);
2307    }
2308
2309    my $iter = MT::Template->load_iter(undef, { offset => $offset, limit => $MAX_ROWS+1 });
2310    my $start = time;
2311    my $continue = 0;
2312    my $count = 0;
2313    while (my $tmpl = $iter->()) {
2314        $offset++;
2315        my $blog = MT::Blog->load($tmpl->blog_id);
2316        my(@at);
2317        if ($tmpl->type eq 'archive') {
2318            @at = qw( Daily Weekly Monthly );
2319        } elsif ($tmpl->type eq 'category') {
2320            @at = ('Category');
2321        } elsif ($tmpl->type eq 'page') {
2322            @at = ('Page');
2323        } elsif ($tmpl->type eq 'individual') {
2324            @at = ('Individual');
2325        } else {
2326            next;
2327        }
2328        for my $at (@at) {
2329            my $meth = 'archive_tmpl_' . lc($at);
2330            my $file_tmpl = $blog->$meth();
2331            my $existing = MT::TemplateMap->load({ blog_id => $blog->id,
2332                archive_type => $at, template_id => $tmpl->id });
2333            if (!$existing) {
2334                my $map = MT::TemplateMap->new;
2335                if ($file_tmpl) {
2336                    $self->progress($self->translate_escape("Mapping template ID [_1] to [_2] ([_3]).", $tmpl->id, $at, $file_tmpl));
2337                    $map->file_template($file_tmpl);
2338                } else {
2339                    $self->progress($self->translate_escape("Mapping template ID [_1] to [_2].", $tmpl->id, $at));
2340                }
2341                $map->archive_type($at);
2342                $map->is_preferred(1);
2343                $map->template_id($tmpl->id);
2344                $map->blog_id($tmpl->blog_id);
2345                $map->save or return $self->error($self->translate_escape("Error saving record: [_1].", $map->errstr));
2346            }
2347        }
2348        $count++;
2349        $continue = 1, last if $count == $MAX_ROWS;
2350        $continue = 1, last if time > $start + $MAX_TIME;
2351    }
2352    if ($continue) {
2353        $iter->('finish');
2354        return $offset;
2355    } else {
2356        $self->progress("$msg (100%)", 1);
2357    }
2358    1;
2359}
2360
2361sub core_update_records {
2362    my $self = shift;
2363    my (%param) = @_;
2364
2365    my $class = MT->model($param{type});
2366    return $self->error($self->translate_escape("Error loading class: [_1].", $param{type}))
2367        unless $class;
2368
2369    my $msg;
2370    my $class_label = ($class->can('class_label') ? $class->class_label : $class);
2371    if ($param{label}) {
2372        $msg = $param{label};
2373        if (ref $msg eq 'CODE') {
2374            $msg = $msg->($class_label);
2375        }
2376        $msg = $self->translate_escape($msg);
2377    } else {
2378        $msg = $self->translate_escape($param{message} || "Updating [_1] records...", $class_label);
2379    }
2380    my $offset = $param{offset};
2381    if ($offset) {
2382        my $count = $class->count;
2383        return unless $count;
2384        $self->progress(sprintf("$msg (%d%%)", ($offset/$count*100)), $param{step});
2385    } else {
2386        $self->progress($msg, $param{step});
2387    }
2388
2389    my $cond = MT->handler_to_coderef($param{condition});
2390    my $code = MT->handler_to_coderef($param{code});
2391    my $sql = $param{sql};
2392
2393    my $continue = 0;
2394    my $driver = $class->driver;
2395
2396    if ($sql && $DryRun) {
2397        $self->run_callbacks('SQL', $sql);
2398    }
2399    return 1 if $DryRun;
2400
2401    if (!$sql || !$driver->sql($sql)) {
2402        my $iter= $class->load_iter(undef, { offset => $offset, limit => $MAX_ROWS+1 });
2403        my $start = time;
2404        my @list;
2405        while (my $obj = $iter->()) {
2406            push @list, $obj;
2407            $continue = 1, last if scalar @list == $MAX_ROWS;
2408        }
2409        $iter->('finish') if $continue;
2410        for my $obj (@list) {
2411            $offset++;
2412            if ($cond) {
2413                next unless $cond->($obj, %param);
2414            }
2415            $code->($obj);
2416use Data::Dumper;
2417            $obj->save()
2418                or return $self->error($self->translate_escape("Error saving [_1] record # [_3]: [_2]... [_4].", $class_label, $obj->errstr, $obj->id, Dumper($obj)));
2419            $continue = 1, last if time > $start + $MAX_TIME;
2420        }
2421    }
2422    if ($continue) {
2423        return $offset;
2424    } else {
2425        $self->progress("$msg (100%)", $param{step});
2426    }
2427    1;
2428}
2429
2430#############################################################################
2431
2432{
2433    my %HighASCII = (
2434        "\xc0" => 'A',    # A`
2435        "\xe0" => 'a',    # a`
2436        "\xc1" => 'A',    # A'
2437        "\xe1" => 'a',    # a'
2438        "\xc2" => 'A',    # A^
2439        "\xe2" => 'a',    # a^
2440        "\xc4" => 'Ae',   # A:
2441        "\xe4" => 'ae',   # a:
2442        "\xc3" => 'A',    # A~
2443        "\xe3" => 'a',    # a~
2444        "\xc8" => 'E',    # E`
2445        "\xe8" => 'e',    # e`
2446        "\xc9" => 'E',    # E'
2447        "\xe9" => 'e',    # e'
2448        "\xca" => 'E',    # E^
2449        "\xea" => 'e',    # e^
2450        "\xcb" => 'Ee',   # E:
2451        "\xeb" => 'ee',   # e:
2452        "\xcc" => 'I',    # I`
2453        "\xec" => 'i',    # i`
2454        "\xcd" => 'I',    # I'
2455        "\xed" => 'i',    # i'
2456        "\xce" => 'I',    # I^
2457        "\xee" => 'i',    # i^
2458        "\xcf" => 'Ie',   # I:
2459        "\xef" => 'ie',   # i:
2460        "\xd2" => 'O',    # O`
2461        "\xf2" => 'o',    # o`
2462        "\xd3" => 'O',    # O'
2463        "\xf3" => 'o',    # o'
2464        "\xd4" => 'O',    # O^
2465        "\xf4" => 'o',    # o^
2466        "\xd6" => 'Oe',   # O:
2467        "\xf6" => 'oe',   # o:
2468        "\xd5" => 'O',    # O~
2469        "\xf5" => 'o',    # o~
2470        "\xd8" => 'Oe',   # O/
2471        "\xf8" => 'oe',   # o/
2472        "\xd9" => 'U',    # U`
2473        "\xf9" => 'u',    # u`
2474        "\xda" => 'U',    # U'
2475        "\xfa" => 'u',    # u'
2476        "\xdb" => 'U',    # U^
2477        "\xfb" => 'u',    # u^
2478        "\xdc" => 'Ue',   # U:
2479        "\xfc" => 'ue',   # u:
2480        "\xc7" => 'C',    # ,C
2481        "\xe7" => 'c',    # ,c
2482        "\xd1" => 'N',    # N~
2483        "\xf1" => 'n',    # n~
2484        "\xdf" => 'ss',
2485    );
2486    my $HighASCIIRE = join '|', keys %HighASCII;
2487    sub mt32_convert_high_ascii {
2488        my($s) = @_;
2489        $s =~ s/($HighASCIIRE)/$HighASCII{$1}/g;
2490        $s;
2491    }
2492}
2493
2494sub mt32_iso_dirify {
2495    my $s = $_[0];
2496    my $sep;
2497    if ($_[1] && ($_[1] ne '1')) {
2498        $sep = $_[1];
2499    } else {
2500        $sep = '_';
2501    }
2502    $s = mt32_convert_high_ascii($s);  ## convert high-ASCII chars to 7bit.
2503    $s = lc $s;                   ## lower-case.
2504    $s = MT::Util::remove_html($s);         ## remove HTML tags.
2505    $s =~ s!&[^;\s]+;!!g;         ## remove HTML entities.
2506    $s =~ s![^\w\s]!!g;           ## remove non-word/space chars.
2507    $s =~ s! +!$sep!g;             ## change space chars to underscores.
2508    $s;   
2509}
2510
2511sub mt32_utf8_dirify {
2512    my $s = $_[0];
2513    my $sep;
2514    if ($_[1] && ($_[1] ne '1')) {
2515        $sep = $_[1];
2516    } else {
2517        $sep = '_';
2518    }
2519    $s = mt32_xliterate_utf8($s);      ## convert two-byte UTF-8 chars to 7bit ASCII
2520    $s = lc $s;                   ## lower-case.
2521    $s = MT::Util::remove_html($s);         ## remove HTML tags.
2522    $s =~ s!&[^;\s]+;!!g;         ## remove HTML entities.
2523    $s =~ s![^\w\s]!!g;           ## remove non-word/space chars.
2524    $s =~ s! +!$sep!g;             ## change space chars to underscores.
2525    $s;   
2526}
2527
2528sub mt32_dirify {
2529    ($MT::VERSION && MT->instance->{cfg}->PublishCharset =~ m/utf-?8/i)
2530        ? mt32_utf8_dirify(@_) : mt32_iso_dirify(@_);
2531}
2532
2533sub mt32_xliterate_utf8 {
2534    my ($str) = @_;
2535    my %utf8_table = (
2536          "\xc3\x80" => 'A',    # A`
2537          "\xc3\xa0" => 'a',    # a`
2538          "\xc3\x81" => 'A',    # A'
2539          "\xc3\xa1" => 'a',    # a'
2540          "\xc3\x82" => 'A',    # A^
2541          "\xc3\xa2" => 'a',    # a^
2542          "\xc3\x84" => 'Ae',   # A:
2543          "\xc3\xa4" => 'ae',   # a:
2544          "\xc3\x83" => 'A',    # A~
2545          "\xc3\xa3" => 'a',    # a~
2546          "\xc3\x88" => 'E',    # E`
2547          "\xc3\xa8" => 'e',    # e`
2548          "\xc3\x89" => 'E',    # E'
2549          "\xc3\xa9" => 'e',    # e'
2550          "\xc3\x8a" => 'E',    # E^
2551          "\xc3\xaa" => 'e',    # e^
2552          "\xc3\x8b" => 'Ee',   # E:
2553          "\xc3\xab" => 'ee',   # e:
2554          "\xc3\x8c" => 'I',    # I`
2555          "\xc3\xac" => 'i',    # i`
2556          "\xc3\x8d" => 'I',    # I'
2557          "\xc3\xad" => 'i',    # i'
2558          "\xc3\x8e" => 'I',    # I^
2559          "\xc3\xae" => 'i',    # i^
2560          "\xc3\x8f" => 'Ie',   # I:
2561          "\xc3\xaf" => 'ie',   # i:
2562          "\xc3\x92" => 'O',    # O`
2563          "\xc3\xb2" => 'o',    # o`
2564          "\xc3\x93" => 'O',    # O'
2565          "\xc3\xb3" => 'o',    # o'
2566          "\xc3\x94" => 'O',    # O^
2567          "\xc3\xb4" => 'o',    # o^
2568          "\xc3\x96" => 'Oe',   # O:
2569          "\xc3\xb6" => 'oe',   # o:
2570          "\xc3\x95" => 'O',    # O~
2571          "\xc3\xb5" => 'o',    # o~
2572          "\xc3\x98" => 'Oe',   # O/
2573          "\xc3\xb8" => 'oe',   # o/
2574          "\xc3\x99" => 'U',    # U`
2575          "\xc3\xb9" => 'u',    # u`
2576          "\xc3\x9a" => 'U',    # U'
2577          "\xc3\xba" => 'u',    # u'
2578          "\xc3\x9b" => 'U',    # U^
2579          "\xc3\xbb" => 'u',    # u^
2580          "\xc3\x9c" => 'Ue',   # U:
2581          "\xc3\xbc" => 'ue',   # u:
2582          "\xc3\x87" => 'C',    # ,C
2583          "\xc3\xa7" => 'c',    # ,c
2584          "\xc3\x91" => 'N',    # N~
2585          "\xc3\xb1" => 'n',    # n~
2586          "\xc3\x9f" => 'ss',   # double-s
2587    );
2588   
2589    $str =~ s/([\200-\377]{2})/$utf8_table{$1}||''/ge;
2590    $str;
2591}
2592
25931;
2594__END__
2595
2596=head1 NAME
2597
2598MT::Upgrade - MT class for managing system upgrades.
2599
2600=head1 SYNOPSIS
2601
2602    MT::Upgrade->do_upgrade(Install => 1);
2603
2604=head1 DESCRIPTION
2605
2606This module is responsible for handling the upgrade or installation of
2607an MT database. The framework is flexible enough for third party plugins
2608to use as well to manage their own schema (please refer to the documentation
2609in L<MT::Plugin> for more information on this).
2610
2611=head1 METHODS
2612
2613=head2 MT::Upgrade-E<gt>do_upgrade
2614
2615The main worker method for this module is I<do_upgrade>. It accepts a
2616handful of arguments, which are:
2617
2618=over 4
2619
2620=item * Install
2621
2622Specify a value of '1' to assume a new installation along with an operation
2623to install a blog and initial user.
2624
2625=item * App
2626
2627A package name or app object that can service the following methods:
2628
2629=over 4
2630
2631=item * progress($package, $message)
2632
2633Called during the upgrade operation to provide feedback with respect to
2634the operations the upgrade process is running.
2635
2636=item * error($package, $message)
2637
2638Called during the upgrade operation to communicate an error that has
2639occurred.
2640
2641=item * translate_escape
2642
2643Call this method to translate messages and phrases which are to appear
2644on the progress screen.  DO NOT use this method to messages and phrases
2645which directly are stored in database.  Use MT->translate for the purpose.
2646
2647=back
2648
2649=item * CLI
2650
2651Specified (set to '1') when invoked from a command line tool. This prevents
2652encoding response messages in the configured PublishCharset for the
2653installation.
2654
2655=item * SuperUser
2656
2657If upgrading from the command line, and running on a pre-MT 3.2 database,
2658set this to an existing author ID that should be upgrade to system
2659administrator status.
2660
2661=item * DryRun
2662
2663Specified (set to '1') to examine the database for installation/upgrade
2664needs but not actually make any physical changes to the database. This will
2665issue all the upgrade progress messages without doing the upgrade itself.
2666
2667=back
2668
2669=head1 CALLBACKS
2670
2671The upgrade module defines the following MT callbacks:
2672
2673=over 4
2674
2675=item * MT::Upgrade::SQL
2676
2677Called with each SQL statement that is executed against the database
2678as part of the upgrade process. The parameters passed to this callback are:
2679
2680    $callback, $upgrade_app, $sql_statement
2681
2682The first parameter is an L<MT::Callback> object. C<$upgrade_app> is a
2683package name or L<MT::App> object used to drive the upgrade process.
2684C<$sql_statement> is the actual SQL query that is about to be executed
2685against the database.
2686
2687=back
2688
2689=head1 UPGRADE FUNCTIONS
2690
2691The bulk of this module consists of Movable Type upgrade operations.
2692These are declared as upgrade functions, and are registered in the
2693package variabled '%functions'. (Note: the word 'function' here is
2694not meant to describe a Perl subroutine.)
2695
2696Some functions are invoked to manage the upgrade process from start
2697to finish ('core_upgrade_begin' for instance, which merely displays
2698a progress message to the calling application). The rest handle
2699schema and data transformation from one version of the MT schema to
2700another.
2701
2702Schema translation itself is handled by Movable Type automatically.
2703MT is able to check the physical schema represenation in the database
2704and compare it with the schema as defined by the L<MT::Object>-descended
2705package. If a new property is added to the L<MT::Blog> package, the
2706upgrade process sees that has happened and can issue the actual
2707'alter table' SQL statement necessary to add it to the database. The
2708'core_fix_type' function is responsible for examining a particular
2709table used by a class like L<MT::Blog> and will append additional
2710upgrade steps ('core_add_column', 'core_alter_column') that it finds
2711necessary to the upgrade workflow.
2712
2713Following the schema translation operations, the data transformation
2714functions would be used to manipulate the data as necessary from
2715an older schema to the current one. For instance, the
2716'core_create_placements' upgrade function was written to upgrade
2717really old MT schemas from the pre-2.0 release to the current schema.
2718The upgrade function is registered like this:
2719
2720    $MT::Upgrade::functions{core_create_placements} = {
2721        version_limit => 2.0,
2722        code          => \&core_update_records,
2723        priority      => 9.1,
2724        updater       => {
2725            class     => 'MT::Entry',
2726            message   => 'Creating entry category placements...',
2727            condition => sub { $_[0]->category_id },
2728            code      => sub {
2729                require MT::Placement;
2730                my $entry = shift;
2731                my $existing = MT::Placement->load({ entry_id => $entry->id,
2732                    category_id => $entry->category_id });
2733                if (!$existing) {
2734                    my $place = MT::Placement->new;
2735                    $place->entry_id($entry->id);
2736                    $place->blog_id($entry->blog_id);
2737                    $place->category_id($entry->category_id);
2738                    $place->is_primary(1);
2739                    $place->save;
2740                }
2741                $entry->category_id(0);
2742            }
2743        }
2744    };
2745
2746With MT version 2.0, the L<MT::Placement> class was introduced and
2747immediately deprecated the use of MT::Entry-E<gt>category as a result.
2748To facilitate upgrading the existing L<MT::Entry> objects this upgrade
2749function is declared such that:
2750
2751=over 4
2752
2753=item * It is limited to only run for MT schemas older than version 2.0 (the version_limit element handles this).
2754
2755=item * It operates on L<MT::Entry> objects (updater-E<gt>class element
2756declares that).
2757
2758=item * It tells the user what is happening (updater-E<gt>message).
2759
2760=item * It excludes any L<MT::Entry> objects that do not have a category_id element (updater-E<gt>condition).
2761
2762=item * It checks for an existing L<MT::Placement> relationship; if not
2763present, it creates one (updater-E<gt>code).
2764
2765=item * It empties out the category_id member of the L<MT::Entry> object
2766being upgrade to prevent it from being processed in the future
2767(updater-E<gt>code).
2768
2769=back
2770
2771For plugins, upgrade functions are assignable in the plugin registration
2772hash as documented in L<MT::Plugin>. You may also return a hashref of
2773upgrade functions from the plugin using the MT::Plugin::upgrade_functions
2774subroutine.
2775
2776Let's look at the anatomy of an upgrade function declaration:
2777
2778=over 4
2779
2780=item * version_limit (optional)
2781
2782The version_limit property allows you to declare that this upgrade
2783operation is only applicable to MT B<schema> versions below the version
2784specified.
2785
2786To register an upgrade function that is only applied to releases prior
2787to the current one, specify the current schema version as the version
2788limit. This will allow the upgrade function to run for any prior releases
2789but prevent it from running in subsequent releases.
2790
2791B<NOTE>: If you are declaring a B<plugin> upgrade function, this version
2792limit is compared with your plugin's schema version, not the Movable Type
2793schema version.
2794
2795=item * priority (optional)
2796
2797If your upgrade operation is dependent on another being done already,
2798it is possible to order them using the priority value. A lower value
2799means a higher priority.
2800
2801=item * condition (optional)
2802
2803This is a coderef parameter. If specified, it should return a true or
2804false value that determines whether the upgrade step is actually to run
2805or not.
2806
2807When called, it is given the parameters normally passed to an upgrade
2808operation (see the 'code' parameter documentation).
2809
2810=item * on_field (optional)
2811
2812If specified, this upgrade function is triggered upon the creation of
2813the field identified by this element. For instance,
2814
2815    on_field => 'MT::Foo->bar'
2816
2817This would specify that the upgrade step is only to run when the 'bar'
2818column is being added to the table that stores data for the MT::Foo
2819package.
2820
2821=item * code
2822
2823This coderef parameter is the declared handler for the upgrade
2824function. It is responsible for doing the upgrade task itself. For
2825quick operations, it is fine to do all of your work within this
2826subroutine. However, to faciliate large databases, it is important
2827to do that work in manageable portions so it doesn't time-out by
2828the web server or browser client.
2829
2830To facilitate an iterative process for your upgrade function, the
2831upgrade routine itself can yield a return value to signal the
2832upgrade process on how to proceed:
2833
2834=over 4
2835
2836=item * 0
2837
2838The upgrade function completed successfully.
2839
2840=item * undef
2841
2842upgrade routine failed with error. The error should be placed using the
2843MT::Upgrade-E<gt>error method.
2844
2845=item * E<gt> 0
2846
2847More work to do; the return value is the 'offset' parameter
2848to pass on the next invocation of the upgrade function.
2849
2850=back
2851
2852Due to the complexity of handling this kind of staged operation,
2853you will most likely want to use the prebuilt
2854'MT::Upgrade::core_update_records' routine to do most of your upgrade
2855operations that handle some or all records of a given package.
2856
2857If using the 'core_update_records' routine, you should also specify
2858an 'updater' parameter for your upgrade function.
2859
2860=item * updater
2861
2862This parameter is only used if you've specified the 'core_update_records'
2863routine (from the L<MT::Upgrade> package itself) for the 'code' element of
2864your upgrade function.
2865
2866    code => \&MT::Upgrade::core_update_records,
2867    updater => {
2868        class => 'MT::Foo',
2869        message => 'Updating Foo bars...',
2870        code => sub {
2871            my $foo = shift;
2872            $foo->bar(1);
2873        },
2874        condition => sub {
2875            my $foo = shift;
2876            !defined $foo->bar;
2877        },
2878        sql => 'update mt_foo set foo_bar = 1 where foo_bar is null'
2879    }
2880
2881This updater declaration is going to process all MT::Foo objects that
2882are available, setting the 'bar' property to 1 if it hasn't been assigned
2883a value already.
2884
2885Here's an overview of an 'updater' element:
2886
2887=over 4
2888
2889=item * class (required)
2890
2891The L<MT::Object>-descendant class to be processed.
2892
2893=item * code (required)
2894
2895A coderef to execute for B<each> record of the table. The parameter to
2896this routine is the object being processed. Following the call to your
2897subroutine, the object is saved for you, so you don't have to save
2898the object yourself.
2899
2900=item * message (optional)
2901
2902The status message to display when running this upgrade operation.
2903
2904=item * condition (optional)
2905
2906A coderef to use to test whether the current object needs to be upgraded
2907or not. This routine should return true if it is to be processed; false
2908if not. It is given the object as a parameter.
2909
2910=item * sql (optional)
2911
2912If specified, and if MT is using a SQL-based database for storing data,
2913this SQL statement is issued instead of doing the Perl-based row-by-row
2914upgrade.
2915
2916    sql => 'update mt_foo set foo_bar=1 where foo_bar is null'
2917
2918You may also specify multiple SQL statements using an array:
2919
2920    sql => [
2921        'update mt_foo set foo_bar=1 where foo_bar is null',
2922        'update mt_foo set foo_baz=2 where foo_baz is null'
2923    ]
2924
2925B<WARNING>: The 'sql' property is only meant to be used for cases where you
2926can issue simple, cross-database SQL statements. It is not advised to
2927use any vendor-specific SQL syntax. So, if you can't do that, don't specify
2928the 'sql' element at all and instead use the 'code' element exclusively
2929to do the upgrade operation.
2930
2931=back
2932
2933=back
2934
2935The declarative style of upgrade functions make it possible for MT to
2936fix itself, upgrading from any older schema version to the current one.
2937Upgrade functions are selected through an introspection process, so any
2938given upgrade operation may run a different selection of upgrade functions.
2939As such, it is important that any upgrade functions be written with this
2940in mind. Here are some general best practices to use when writing them:
2941
2942=over 4
2943
2944=item * Make them fast.
2945
2946Use the 'sql' element for a 'core_update_records' type upgrade function
2947so that SQL-based databases can be upgraded in one pass.
2948
2949=item * Make them indepedent.
2950
2951Don't assume that any other upgrade operation will have run within the
2952same application request. The upgrade process can run them in most any
2953order and across multiple application requests. You do have a guarantee
2954that a higher priority upgrade function will be run prior to a lower-priority
2955upgrade function (ie, assigning a priority of 1 will ensure it will run
2956before one with a priority of 2).
2957
2958=item * Limit them as much as possible.
2959
2960Specify a version_limit so it only runs for the proper schemas. Use the
2961condition element to bypass objects or the upgrade step altogether when
2962possible.
2963
2964=item * Repeating an upgrade function should be safe.
2965
2966This can be made possible through use of the 'condition' elements, bypassing
2967objects that have already been processed (see how the
2968'core_create_placements' upgrade function declares conditions for an
2969example).
2970
2971=item * Beware which translate method to call
2972
2973$self->translate_escape is for messages and phrases which appear on the
2974progress screen (therefore they are sent in JSON).  Use MT->translate
2975to messages and phrases which directly stored in the database.  Log messages
2976and objects' attributes fall into this category.
2977
2978=back
2979
2980=head1 AUTHOR & COPYRIGHTS
2981
2982Please see the I<MT> manpage for author, copyright, and license information.
2983
2984=cut
Note: See TracBrowser for help on using the browser.