# Movable Type (r) Open Source (C) 2001-2008 Six Apart, Ltd. # This program is distributed under the terms of the # GNU General Public License, version 2. # # $Id$ package MT::Upgrade::v4; use strict; sub upgrade_functions { return { 'core_init_comment_junk_status' => { version_limit => 4.0053, priority => 3.1, updater => { type => 'comment', condition => sub { !$_[0]->junk_status }, code => sub { $_[0]->junk_status(1) }, label => 'Assigning junk status for comments...', sql => 'update mt_comment set comment_junk_status = 1 where comment_junk_status is null or comment_junk_status=0', } }, 'core_init_tbping_junk_status' => { version_limit => 4.0053, priority => 3.1, updater => { type => 'tbping', condition => sub { !$_[0]->junk_status }, code => sub { $_[0]->junk_status(1) }, label => 'Assigning junk status for TrackBacks...', sql => 'update mt_tbping set tbping_junk_status = 1 where tbping_junk_status is null or tbping_junk_status=0', } }, 'core_deprecate_bitmask_permissions' => { code => \&deprecate_bitmask_permissions, version_limit => 4.0002, priority => 3.3, }, 'core_migrate_system_privileges' => { code => \&migrate_system_privileges, version_limit => 4.0002, priority => 3.3, }, 'core_populate_authored_on' => { version_limit => 4.0014, priority => 3.1, updater => { type => 'entry', label => 'Populating authored and published dates for entries...', condition => sub { !defined $_[0]->authored_on }, code => sub { $_[0]->authored_on($_[0]->created_on) if !defined $_[0]->authored_on; }, sql => 'update mt_entry set entry_authored_on = entry_created_on where entry_authored_on is null', }, }, 'core_update_3x_system_search_templates' => { version_limit => 4.0017, priority => 3.1, code => \&update_3x_system_search_templates, }, 'core_rename_php_plugin_filenames' => { version_limit => 4.0019, priority => 3.1, code => \&rename_php_plugin_filenames, }, 'core_migrate_nofollow_settings' => { version_limit => 4.0020, priority => 3.1, code => \&migrate_nofollow_settings, }, 'core_update_widget_template' => { version_limit => 4.0022, priority => 3.1, updater => { type => 'template', label => 'Updating widget template records...', condition => sub { return 0 unless 'custom' eq $_[0]->type; my $name = $_[0]->name; if ($name =~ s/^(?:Widget|Sidebar): ?//) { $_[0]->name($name); $_[0]->type('widget'); $_[0]->save; } 0; }, code => sub { 1; }, }, }, # This upgrade step is currently necessary for PostgreSQL # which doesn't support adding a column, populating the existing # records with a value. 'core_typify_category_records' => { version_limit => 4.0023, priority => 3.1, updater => { type => 'category', label => 'Classifying category records...', condition => sub { !$_[0]->column('class') }, code => sub { $_[0]->class('category'); }, sql => "update mt_category set category_class = 'category' where category_class is null", }, }, 'core_typify_entry_records' => { version_limit => 4.0023, priority => 3.1, updater => { type => 'entry', label => 'Classifying entry records...', condition => sub { !$_[0]->column('class') }, code => sub { $_[0]->class('entry'); }, sql => "update mt_entry set entry_class = 'entry' where entry_class is null", }, }, 'core_merge_comment_response_templates' => { version_limit => 4.0023, priority => 3.1, updater => { type => 'blog', label => "Merging comment system templates...", code => \&_merge_comment_response_templates_updater, }, }, 'core_populate_default_file_template' => { version_limit => 4.0023, priority => 3.1, updater => { type => 'templatemap', label => 'Populating default file template for templatemaps...', condition => sub { !defined $_[0]->file_template }, code => sub { my %default_template = ( Individual => '%y/%m/%f', Category => '%c/%i', ); $_[0]->file_template($default_template{$_[0]->archive_type}) if !defined $_[0]->file_template && exists($default_template{$_[0]->archive_type}); }, }, }, 'core_remove_unused_templatemap' => { version_limit => 4.0023, priority => 3.0, updater => { type => 'blog', label => 'Removing unused template maps...', condition => sub { my $blog = shift; my @blog_at = split /,/, $blog->archive_type; require MT::TemplateMap; MT::TemplateMap->remove( { blog_id => $blog->id, archive_type => \@blog_at }, { not => { archive_type => 1 } } ) if @blog_at; return 0; }, }, }, 'core_set_author_auth_type' => { version_limit => 4.0024, priority => 3.1, updater => { type => 'author', label => 'Assigning user authentication type...', condition => sub { !$_[0]->auth_type; }, code => \&core_populate_author_auth_type, }, }, 'core_add_newfeature_widget' => { version_limit => 4.0027, priority => 3.4, updater => { type => 'author', label => 'Adding new feature widget to dashboard...', condition => sub { my $App = $MT::Upgrade::App; my ($user) = @_; if ( $user->type == MT::Author::AUTHOR() ) { return 1 if $App && UNIVERSAL::isa( $App, 'MT::App' ) && ( $user->id == $App->user->id ); } return 0; }, code => sub { my ($user) = @_; my $widget_store = $user->widgets(); if ($widget_store && %$widget_store) { for my $set (keys %$widget_store) { $widget_store->{$set}->{new_version} = { template => 'widget/new_version.tmpl', set => 'main', singular => 1, order => -1, }; } } else { # FIXME: copied from MT::CMS::Dashboard my %default_widgets = ( 'new_version' => { order => -1, set => 'main', template => 'widget/new_version.tmpl', singular => 1 }, 'blog_stats' => { param => { tab => 'entry' }, order => 1, set => 'main' }, 'this_is_you-1' => { order => 1, set => 'sidebar' }, 'mt_shortcuts' => { order => 2, set => 'sidebar' }, 'mt_news' => { order => 3, set => 'sidebar' }, ); my $blog_iter = MT->model('blog')->load_iter( undef, { fetchonly => ['id'] } ); while ( my $blog = $blog_iter->() ) { my $set = 'dashboard:blog:' . $blog->id; $widget_store->{$set} = \%default_widgets; } $widget_store->{'dashboard:system'} = \%default_widgets; } $user->widgets($widget_store); }, }, }, 'core_add_email_template' => { version_limit => 4.0030, priority => 9.3, code => sub { my $self = shift; $self->add_step('core_upgrade_templates', Install => 1); }, }, 'core_replace_openid_username' => { version_limit => 4.0033, priority => 3.2, updater => { type => 'author', label => 'Moving OpenID usernames to external_id fields...', condition => sub { return 0 if 'MT' eq $_[0]->auth_type; my $auth = MT->commenter_authenticator($_[0]->auth_type); return 0 unless $auth && %$auth && exists($auth->{class}); my $auth_class = $auth->{class}; eval "require $auth_class;"; return 0 if $@; return UNIVERSAL::isa($auth_class, 'MT::Auth::OpenID'); }, code => sub { return unless $_[0]->url; my $existing = MT::Author->load({ name => $_[0]->url, auth_type => 'OpenID' }); unless ($existing) { $_[0]->external_id($_[0]->name); $_[0]->name($_[0]->url); } }, }, }, 'core_set_template_set' => { version_limit => 4.0034, priority => 3.2, updater => { type => 'blog', label => 'Assigning blog template set...', condition => sub { !$_[0]->template_set; }, code => sub { $_[0]->template_set('mt_blog'); MT->run_callbacks( 'blog_template_set_change', { blog => $_[0] } ); }, }, }, 'core_set_page_layout' => { version_limit => 4.0036, priority => 3.2, updater => { type => 'blog', label => 'Assigning blog page layout...', condition => sub { !$_[0]->page_layout; }, code => sub { my ($blog) = @_; my $layout = 'layout-wtt'; require MT::Template; my $styles = MT::Template->load({ blog_id => $blog->id, identifier => 'styles' }); if ($styles) { if ($styles->text =~ m{ <\$?mt:?setvar \s+ name="page_layout" \s+ value="([^"]+)"\$?> }xmsi) { $layout = $1; } } $blog->page_layout($layout); }, }, }, 'core_set_author_basename' => { version_limit => 4.0037, priority => 3.2, updater => { type => 'author', label => 'Assigning author basename...', condition => sub { $_[0]->type == 1; }, code => sub { my ($author) = @_; my $basename = MT::Util::make_unique_author_basename($author); $author->basename($basename); }, }, }, 'core_remove_indexes' => { version_limit => 4.0041, priority => 3.2, code => \&remove_indexes, }, 'core_set_count_columns' => { version_limit => 4.0047, priority => 3.2, code => \&core_update_entry_counts, }, 'core_assign_object_embedded' => { version_limit => 4.0052, priority => 3.2, updater => { type => 'objectasset', label => 'Assigning embedded flag to asset placements...', code => sub { $_[0]->embedded(1); }, sql => 'update mt_objectasset set objectasset_embedded=1', }, }, 'core_set_template_build_type' => { version_limit => 4.0053, priority => 3.2, updater => { type => 'blog', label => 'Updating template build types...', code => sub { my ($blog) = @_; require MT::CMS::Blog; my $App = $MT::Upgrade::App; MT::CMS::Blog::update_publishing_profile( $App, $blog ); require MT::Template; require MT::PublishOption; my @tmpls = MT::Template->load( { blog_id => $blog->id } ); foreach my $tmpl (@tmpls) { if ( $tmpl->build_dynamic ) { require MT::TemplateMap; $tmpl->build_type( MT::PublishOption::DYNAMIC() ); $tmpl->save; my @maps = MT::TemplateMap->load( { template_id => $tmpl->id } ); foreach my $map (@maps) { $map->build_type( MT::PublishOption::DYNAMIC() ); $map->save; } } if ( !$tmpl->rebuild_me && $tmpl->type eq 'index' ) { $tmpl->build_type( MT::PublishOption::MANUALLY() ); $tmpl->save; } } return 0; }, }, }, 'core_enable_address_book' => { version_limit => 4.0054, priority => 3.2, code => sub { require MT::Notification; if (MT::Notification->exist()) { my $cfg = MT->config; if (! $cfg->EnableAddressBook) { $cfg->EnableAddressBook(1, 1); $cfg->save; } } return 0; }, }, 'core_upgrade_meta' => { version_limit => 4.0057, priority => 3.2, code => \&core_upgrade_meta, }, 'core_upgrade_category_meta' => { version_limit => 4.0057, priority => 3.2, code => \&core_upgrade_category_meta, }, # Helper upgrade routines for core_upgrade_meta # and possibly other object types that require # this migration; version_limit is unspecified, so # these can only be invoked if another upgrade # operation utilizes them. 'core_upgrade_meta_for_table' => { priority => 1.5, code => \&core_upgrade_meta_for_table, }, 'core_upgrade_plugindata_meta_for_table' => { priority => 1.5, code => \&core_upgrade_plugindata_meta_for_table, }, 'core_drop_meta_for_table' => { priority => 3.4, code => \&core_drop_meta_for_table, }, 'core_replace_file_template_format' => { version_limit => 4.0058, priority => 3.2, updater => { type => 'templatemap', label => 'Replacing file formats to use CategoryLabel tag...', condition => sub { ( $_[0]->file_template || '' ) =~ m/%-?C/; }, code => sub { my ($map) = shift; my $file_template = $map->file_template(); $file_template =~ s/%C//g; $file_template =~ s/%-C//g; $map->file_template($file_template); }, }, }, 'core_update_password_recover_template' => { version_limit => 4.0068, code => \&core_update_password_recover_template, }, }; } ### Subroutines sub _process_masks { my ($perm) = @_; my $mask = $perm->role_mask; return unless $mask; my @perms; for my $key (keys %MT::Upgrade::LegacyPerms) { if (int($mask) & int($key)) { if (2 eq $key) { # post push @perms, 'create_post', 'publish_post'; } elsif (64 eq $key) { #edit_config push @perms, 'edit_config', 'set_publish_paths', 'manage_feedback'; } elsif (4096 eq $key) { #adminsiter_blog push @perms, 'administer_blog', 'manage_pages'; } elsif (2048 eq $key) { #not_comment $perm->restrictions("'comment'"); } else { push @perms, $MT::Upgrade::LegacyPerms{$key}; } } } my $perm_str = scalar(@perms) ? "'" . join("','", @perms) . "'" : q(); $perm->permissions($perm_str); $perm->role_mask(0); ## remove legacy permissions $perm; } sub deprecate_bitmask_permissions { my $self = shift; require MT::Permission; my $perm_iter = MT::Permission->load_iter; $self->progress($self->translate_escape('Migrating permission records to new structure...')); while (my $perm = $perm_iter->()) { if (_process_masks($perm)) { $perm->save; } } require MT::Role; my $role_iter = MT::Role->load_iter; $self->progress($self->translate_escape('Migrating role records to new structure...')); while (my $role = $role_iter->()) { if (_process_masks($role)) { # do not have to rebuild permissions here. # "save" here causes segfault in sqlite. $role->update; } } } sub migrate_system_privileges { my $self = shift; require MT::Permission; my $author_iter = MT::Author->load_iter({ type => MT::Author::AUTHOR() }); $self->progress($self->translate_escape('Migrating system level permissions to new structure...')); while (my $author = $author_iter->()) { my @perms; push @perms, 'administer' if $author->column('is_superuser'); push @perms, 'create_blog' if $author->column('can_create_blog') || $author->column('is_superuser'); push @perms, 'view_log' if $author->column('can_view_log') || $author->column('is_superuser'); push @perms, 'manage_plugins' if $author->column('is_superuser'); if (@perms) { my $perm = MT::Permission->load({ author_id => $author->id, blog_id => 0 }); if (!$perm) { $perm = MT::Permission->new; $perm->author_id($author->id); $perm->blog_id(0); } $perm->set_these_permissions(@perms); $perm->save; } } } sub update_3x_system_search_templates { my $self = shift; require MT::Template; $self->progress($self->translate_escape('Updating system search template records...')); my $tmpl_iter = MT::Template->load_iter({ type => 'search_template', }); my %blogs; while (my $tmpl = $tmpl_iter->()) { $blogs{$tmpl->blog_id} = $tmpl->id; $tmpl->type('search_results'); $tmpl->save; } # for any old 'search_template' system templates, remove the # newly installed 'search_results' template. foreach my $blog_id (keys %blogs) { my $tmpl = MT::Template->load({ type => 'search_results', blog_id => $blog_id, id => $blogs{$blog_id} }, { not => { id => 1 } }); $tmpl->remove if $tmpl; } if (my @blog_ids = keys %blogs) { MT::Template->remove( { type => 'search_results', blog_id => \@blog_ids }, { not => { blog_id => 1 } } ); } 0; } sub rename_php_plugin_filenames { my $self = shift; my $server_path = MT->instance->server_path() || ''; $server_path =~ s/\/*$//; my $plugin_path = File::Spec->canonpath("$server_path/php/plugins"); # If PHP plugins directory doesn't exist, return without failing return 0 if !-d $plugin_path; opendir(DIR, $plugin_path) or return 0; my @files = grep { /^(?:function|block)\.(.*)\.php$/ } readdir(DIR); closedir(DIR); return 0 unless @files; $self->progress($self->translate_escape('Renaming PHP plugin file names...')); my @error_files = (); for my $file (@files) { my $newfile = lc $file; next if $file eq $newfile; if (!rename("$plugin_path/$file", "$plugin_path/$newfile")) { push @error_files, $file; } } if ($#error_files >= 0) { $self->progress($self->translate_escape('Error renaming PHP files. Please check the Activity Log.')); MT->log( { message => $self->translate_escape("Cannot rename in [_1]: [_2].", $plugin_path, join(', ', @error_files)), level => MT::Log::ERROR(), category => 'upgrade', } ); } 1; } sub migrate_nofollow_settings { my $self = shift; $self->progress($self->translate_escape("Migrating Nofollow plugin settings...")); require MT::PluginData; my $cfg = MT->config; my $plugins = $cfg->PluginSwitch || {}; my $nofollow_switch = $plugins->{'nofollow/nofollow.pl'}; my $enabled = defined $nofollow_switch ? ($nofollow_switch ? 1 : 0) : 1; my $default_follow_auth_links = 1; # For any configuration settings that exist my @config = MT::PluginData->load({ plugin => 'Nofollow' }); my %blogs_saved; foreach my $cfg (@config) { if ($cfg->key =~ m/^configuration:blog:(\d+)/) { my $blog = MT::Blog->load($1) or next; my $setting = ($cfg->data || {})->{follow_auth_links}; $blog->follow_auth_links($setting) if defined $setting; $blog->nofollow_urls($enabled); $blog->save; $blogs_saved{$blog->id} = 1; } else { my $setting = ($cfg->data || {})->{follow_auth_links}; $default_follow_auth_links = $setting if defined $setting; } } $_->remove for @config; my $blog_iter = MT::Blog->load_iter; while (my $blog = $blog_iter->()) { next if exists $blogs_saved{$blog->id}; $blog->nofollow_urls($enabled); $blog->follow_auth_links($default_follow_auth_links); $blog->save; } # Forcibly disable nofollow plugin now since this has become # a core function. $cfg->PluginSwitch('nofollow/nofollow.pl=0', 1); $cfg->save_config(); return 0; } sub _merge_comment_response_templates_updater { my ($blog) = @_; require MT::Template; my $tmpl = MT::Template->load({ blog_id => $blog->id, type => 'comment_response' }); unless ($tmpl) { $tmpl = new MT::Template; $tmpl->blog_id($blog->id); $tmpl->type('comment_response'); } my $confirm_template = <<'EOT'; <__trans phrase="Comment Posted"> ">

<__trans phrase="Your comment has been posted!">

EOT my $pending_template = <<'EOT'; <__trans phrase="Comment Pending"> ">

<__trans phrase="Your comment has been received and held for approval by the blog owner.">

EOT my $error_template = <<'EOT'; <__trans phrase="Comment Submission Error">

<__trans phrase="Your comment submission failed for the following reasons:">

<$MTErrorMessage$>
EOT my $header_template = <<'EOT'; " /> <__trans phrase="[_1]: [_2]" params="<$MTBlogName encode_html="1"$>%%<$MTGetVar name="page_title"$>">
EOT my $footer_template = <<'EOT';
EOT my $message_template = <<'EOT';

<$MTGetVar name="heading"$>

<$MTGetVar name="message"$>

<__trans phrase="Return to the original entry." params="<$MTEntryLink$>">

EOT my $mt = MT->instance; $tmpl->name($mt->translate("Comment Response")); $tmpl->text($mt->translate_templatized(<<"EOT")); $pending_template $error_template $confirm_template $header_template $message_template $footer_template EOT $tmpl->save; } sub core_populate_author_auth_type { my ($u) = @_; if ($u->type == 1) { $u->auth_type(MT->config->AuthenticationModule || 'MT'); } else { # for legacy OpenID plugin commenters if ($u->name =~ m(^openid\n(.*)$)) { my $url = $1; if (eval { require Digest::MD5; 1; }) { $url = Digest::MD5::md5_hex($url); } else { $url = substr $url, 0, 255; } $u->name($url); $u->auth_type('OpenID'); } elsif ($u->name =~ m!^[a-f0-9]{32}$!) { # Vox OpenID URL; set auth_type to 'Vox' if ($u->url =~ m!\.vox\.com/!) { $u->auth_type('Vox'); } # LJ OpenID URL; set auth_type to 'LiveJournal' elsif ($u->url =~ m!\.livejournal\.com/!) { $u->auth_type('LiveJournal'); } else { # Other custom auth, which for now means OpenID $u->auth_type('OpenID'); } } else { # Default to TypePad for remaining plain name fields $u->auth_type('TypeKey'); } } } sub remove_indexes { my $self = shift; $self->progress($self->translate_escape('Removing unnecessary indexes...')); my $driver = MT::Object->driver; if ($driver->dbd =~ m/::Pg$|::Oracle$/) { $driver->sql([ 'drop index mt_asset_url', 'drop index mt_asset_file_path', 'drop index mt_blocklist_name', 'drop index mt_entry_blog_id', 'drop index mt_template_build_dynamic' ]); } elsif ($driver->dbd =~ m/::mysql$/) { $driver->sql([ 'drop index mt_asset_url on mt_asset', 'drop index mt_asset_file_path on mt_asset', 'drop index mt_blocklist_name on mt_blocklist', 'drop index mt_entry_blog_id on mt_entry', 'drop index mt_template_build_dynamic on mt_tempalte' ]); } elsif ($driver->dbd =~ m/::mssqlserver$/) { $driver->sql([ 'drop index mt_asset.mt_asset_url', 'drop index mt_asset.mt_asset_file_path', 'drop index mt_blocklist.mt_blocklist_name', 'drop index mt_entry.mt_entry_blog_id', 'drop index mt_tempalte.mt_template_build_dynamic' ]); } 1; } sub core_upgrade_meta { my $self = shift; my $types = MT->registry('object_types'); my %added_step; TYPE: while (my ($registry_type, $reg_class) = each %$types) { next TYPE if $registry_type eq 'plugin' && ref $reg_class; # plugin reference my $class = MT->model($registry_type); next TYPE if !$class->has_meta(); # nothing to upgrade # If this is a class-based package, find its super-most superclass with the same table. my $class_type = $class->properties->{class_type}; if ($class_type && $class_type ne $class->datasource) { if (my $super_class = MT->model($class->datasource)) { $class = $super_class if $super_class->datasource eq $class->datasource; } # If there's no appropriate superclass, go to update with the class # we have, not the class we want. } # Don't add another step for this table if we already made one. next TYPE if $added_step{$class->datasource}; # Categories' and Folders' metadata are only custom fields, which are stored # in plugindata anyway. They're converted in their own upgrade step. So don't # handle them here. next TYPE if $class->isa('MT::Category'); my %step_param = ( type => $registry_type ); $step_param{meta_column} = $class->properties->{meta_column} if $class->properties->{meta_column}; $self->add_step('core_upgrade_meta_for_table', %step_param); # Yay, we added a step for this table. $added_step{$class->datasource} = 1; } return 0; } sub _save_meta { my ($obj, $type, $value) = @_; my $meta_obj = $obj->meta_pkg->new; my @class_keys = @{ $obj->primary_key_tuple }; my @meta_keys = @{ $meta_obj->primary_key_tuple }; for my $i (0..$#class_keys) { my $class_field = $class_keys[$i]; my $meta_field = $meta_keys[$i]; $meta_obj->$meta_field($obj->$class_field()); } # Set the type without checking if it's defined, unlike real meta(). $meta_obj->type($type); # Does this meta type have a data type defined? my $datatype; if (my $field = MT::Meta->metadata_by_name(ref $obj || $obj, $type)) { if (my $type_id = $field->{type_id}) { $datatype = $MT::Meta::Types{$type_id}; } } $datatype ||= 'vblob'; $meta_obj->$datatype($value); my $meta_col_def = $meta_obj->column_def($datatype); my $meta_is_blob = $meta_col_def ? $meta_col_def->{type} eq 'blob' : 0; MT::Meta::Proxy::serialize_blob(undef, $meta_obj) if $meta_is_blob; $meta_obj->save(); MT::Meta::Proxy::unserialize_blob($meta_obj) if $meta_is_blob; } sub core_upgrade_category_meta { my $self = shift; $self->add_step('core_upgrade_plugindata_meta_for_table', type => 'category'); $self->add_step('core_upgrade_plugindata_meta_for_table', type => 'folder'); return 0; } sub core_upgrade_plugindata_meta_for_table { my $self = shift; my $Installing = $MT::Upgrade::Installing; return 0 if $Installing; my (%param) = @_; my $type = $param{type}; return 0 unless $type; my $class = MT->model($type); return 0 unless $class; my $cfclass = MT->model('field'); return 0 if !$cfclass; # this looks weird, but it winds up invoking # the loading of custom field types and the # installation of their meta properties MT->registry('tags'); # special case for types that use CustomField plugindata # for storing their custom field metadata instead of a 'meta' # column. require CustomFields::Upgrade; # TODO: really this should vary on $type but it's already translated for "categories" $self->progress($self->translate_escape('Moving metadata storage for categories...')); CustomFields::Upgrade::customfields_move_meta($self, $type); return 0; } sub core_upgrade_meta_for_table { my $self = shift; my $Installing = $MT::Upgrade::Installing; return 0 if $Installing; my (%param) = @_; my $type = $param{type}; return 0 unless $type; my $class = MT->model($type); return 0 unless $class; my $offset = int($param{offset} || 0); my $count = int($param{count} || 0); my $pid = join q{:}, $param{step} . "_type", $class; my $db_class = $class; my $class_type = $class->properties->{class_type}; if ($class_type && $class_type ne $class->datasource) { if (my $super_class = MT->model($class->datasource)) { $db_class = $super_class if $super_class->datasource eq $class->datasource; } # If there's no appropriate superclass, go to update with the class # we have, not the class we want. } my $driver = $db_class->dbi_driver; my $dbh = $driver->rw_handle; my $dbd = $driver->dbd; my $meta_col = $param{meta_column} || 'meta'; my $ddl = $driver->dbd->ddl_class; my $db_defs = $ddl->column_defs($db_class); return 0 unless $db_defs && exists($db_defs->{$meta_col}); my $terms = { $meta_col => { not_null => 1 } }; my $args = { 'limit' => 101, 'fetchonly' => [ 'id' ], # meta is added to the select list separately 'sort' => 'id', 'direction' => 'ascend', $offset ? ( 'offset' => $offset ) : () }; my $stmt = $driver->prepare_statement( $class, $terms, $args ); my $db_meta_col = $dbd->db_column_name($class->datasource, $meta_col); ## Meta column has to be added in here because it's already ## gone from the column_names - something fetchonly relies on $stmt->add_select( $db_meta_col => $db_meta_col ); my $sql = $stmt->as_sql; my $sth = $dbh->prepare($sql); return 0 if !$sth; # ignore this operation if _meta column doesn't exist $sth->execute or return $self->error($dbh->errstr || $DBI::errstr); my $msg = $self->translate_escape("Upgrading metadata storage for [_1]", $class->class_label_plural); if (!$offset) { $self->progress($msg, $pid); } else { my $count = $class->count(); return 0 unless $count; $self->progress(sprintf($msg . " (%d%%)", ($offset/$count*100)), $pid); } my $rows = 0; require MT::Serialize; my $ser = MT::Serialize->new('MT'); my %fields; my @ids; my $cfclass = MT->model('field'); while (my $row = $sth->fetchrow_arrayref) { $rows++; my ($id, $rawmeta) = @$row; ## add_select pushes the column - it should be in this order if (defined $rawmeta) { push @ids, $id; if ($rawmeta =~ m/^SERG/) { # deserialize my $metadataref = $ser->unserialize($rawmeta); if ($metadataref) { my $metadata = $$metadataref; my $obj = $class->load( { id => $id }, { no_class => 1, fetchonly => [ 'id', ( $class_type ? ( $class->properties->{class_column} ) : () ) ] }); if ($obj) { foreach my $metaname (keys %$metadata) { my $metavalue = $metadata->{$metaname}; if ($metaname eq 'customfields') { next unless $cfclass; # extra work for custom fields; # a hash into itself my $cfdata = $metavalue; next unless ref $cfdata eq 'HASH'; foreach my $cfname (keys %$cfdata) { my $cfvalue = $cfdata->{$cfname}; my $cftype = $type; if ($class_type) { $cftype = $obj->class_type; } # make sure CustomFields::Field exists my $fld = $fields{$cfname}{$cftype} ||= $cfclass->load({ basename => $cfname, obj_type => $cftype }); next unless $fld; _save_meta($obj, 'field.' . $cfname, $cfvalue); } } else { _save_meta($obj, $metaname, $metavalue); } } } } } } last if $rows == 100; } if ($rows == 100 && $sth->fetchrow_arrayref) { $rows++; } $sth->finish; if ($rows == 101) { $offset += 100; } else { # done, so lets drop that meta column, what say you? if ($dbd->ddl_class->can_drop_column) { # if the driver cannot drop a column, it is likely # to get dropped as the table is updated for other # new columns anyway. $sql = $dbd->ddl_class->drop_column_sql($class, $meta_col); $self->add_step('core_drop_meta_for_table', class => $db_class, sql => $sql); } $self->progress($msg . ' (100%)', $pid); $offset = 0; # done! } return $offset; } sub core_drop_meta_for_table { my $self = shift; my (%param) = @_; my $class = $param{class}; my $sql = $param{sql}; eval "require $class;"; my $driver = $class->dbi_driver; my $dbh = $driver->rw_handle; my $err; eval { $dbh->do($sql) or $err = $dbh->errstr; }; # ignore drop errors; the column has probably been # removed already #if ($err) { # print STDERR "$err: $sql\n"; #} return 0; } sub core_update_entry_counts { my $self = shift; my (%param) = @_; my $class = MT->model('entry'); return $self->error($self->translate_escape("Error loading class: [_1].", $param{type})) unless $class; my $msg = $self->translate_escape("Assigning entry comment and TrackBack counts..."); my $offset = $param{offset} || 0; my $count = $param{count}; if (!$count) { $count = $class->count({ class => '*' }); } return unless $count; if ($offset) { $self->progress(sprintf("$msg (%d%%)", ($offset/$count*100)), $param{step}); } else { $self->progress($msg, $param{step}); } my $continue = 0; my $driver = $class->driver; my $iter = $class->load_iter({ class => '*' }, { offset => $offset, limit => $self->max_rows + 1 }); my $start = time; my ( %touched, %c, %tb ); my $rows = 0; while (my $e = $iter->()) { $rows++; $c{$e->id} = $e; if (my $tb = $e->trackback) { $tb{$tb->id} = $e; } $continue = 1, last if scalar $rows == $self->max_rows; } if ( $continue ) { $iter->end; $offset += $rows; } # now gather counts -- comments if (my $grp_iter = MT::Comment->count_group_by({ visible => 1, entry_id => [ keys %c ], }, { group => ['entry_id'], })) { while (my ($count, $id) = $grp_iter->()) { my $e = $c{$id} or next; if ((!defined $e->comment_count) || (($e->comment_count || 0) != $count)) { $e->comment_count($count); $touched{$e->id} = $e; } } } # pings if ( %tb ) { if (my $grp_iter = MT::TBPing->count_group_by({ visible => 1, tb_id => [ keys %tb ], }, { group => ['tb_id'], })) { while (my ($count, $id) = $grp_iter->()) { my $e = $tb{$id} or next; if ((!defined $e->ping_count) || (($e->ping_count || 0) != $count)) { $e->ping_count($count); $touched{$e->id} = $e; } } } } foreach my $e (values %touched) { $e->save; } if ($continue) { return { offset => $offset, count => $count }; } else { $self->progress("$msg (100%)", $param{step}); } 1; } sub core_update_password_recover_template { my $self = shift; $self->progress($self->translate_escape("Updating password recover email template...")); require MT::DefaultTemplates; my $recover_tmpl = MT::DefaultTemplates->load ({ identifier => 'recover-password' }); my $recover_text = MT->instance->translate_templatized($recover_tmpl->{text}); require MT::Template; my @tmpls = MT::Template->load ({ type => 'email', identifier => 'recover-password' }); for my $tmpl (@tmpls) { my $backup = $tmpl->clone; delete $backup->{column_values} ->{id}; # make sure we don't overwrite original delete $backup->{changed_cols}->{id}; $backup->name( $backup->name . ' (Backup during upgrade to version 4.24)' ); $backup->type('backup'); $backup->identifier(undef); $backup->save; $tmpl->text ($recover_text); $tmpl->save; } } 1;