# Movable Type (r) Open Source (C) 2001-2012 Six Apart, Ltd.
# This program is distributed under the terms of the
# GNU General Public License, version 2.
#
# $Id$

package MT::Upgrade::v5;

use strict;

sub upgrade_functions {
    return {

        #'v5_migrate_blog' => {
        #    version_limit => 5.0004,
        #    priority      => 3.2,
        #    code          => \&_v5_migrate_blog,
        #},
        'v5_create_new_role' => {
            version_limit => 5.0010,
            priority      => 3.1,
            code          => \&_v5_create_new_role,
        },
        'v5_migrate_system_privilege' => {
            version_limit => 5.0004,
            priority      => 3.1,
            code          => \&_v5_migrate_system_privilege,
        },
        'v5_migrate_mtview' => {
            version_limit => 5.0004,
            priority      => 3.1,
            code          => \&_v5_migrate_mtview,
        },
        'v5_migrate_default_site' => {
            version_limit => 5.0008,
            priority      => 3.3,
            code          => \&_v5_migrate_default_site
        },
        'v5_migrate_dashboard_widget_settings' => {
            version_limit => 5.0013,
            priority      => 3.3,
            updater       => {
                type      => 'author',
                label     => "Merging dashboard settings...",
                condition => sub { $_[0]->status == 1 && $_[0]->type == 1 },
                code      => \&_v5_migrate_dashboard,
            },
        },
        'v5_migrate_theme_privilege' => {
            version_limit => 5.0014,
            priority      => 3.1,
            code          => \&_v5_migrate_theme_privilege,
        },
        'v5_migrate_blog_only' => {
            version_limit => 5.0015,
            priority      => 3.2,
            updater       => {
                type      => 'blog',
                label     => "Classifying blogs...",
                condition => sub { !$_[0]->class },
                code      => sub { $_[0]->class('blog') },
                sql       => "update mt_blog set blog_class='blog'
                              where ( blog_class IS NULL ) or ( blog_class = '' )
                              or ( blog_class = '0' )",
            },
        },
        'v5_generate_websites_place_blogs' => {
            version_limit => 5.0015,
            priority      => 3.4,
            code          => \&_v5_generate_websites_place_blogs,
        },
        'v5_rebuild_permissions' => {
            version_limit => 5.0016,
            priority      => 3.0,
            updater       => {
                type      => 'permission',
                label     => 'Rebuilding permissions...',
                condition => sub { $_[0]->blog_id },
                code      => sub { $_[0]->rebuild; },
            },
        },
        'v5_remove_technorati' => {
            version_limit => 5.0017,
            priority      => 3.0,
            code          => \&_v5_remove_technorati,
        },
        'v5_remove_news_widget_cache' => {
            version_limit => 5.0,
            priority      => 3.0,
            code          => \&_v5_remove_news_widget_cache,
        },
        'v5_assign_entry_ceated_by' => {
            version_limit => 5.0019,
            priority      => 3.1,
            updater       => {
                type  => 'entry',
                label => 'Assigning ID of author for entries...',
                code  => sub {
                    $_[0]->created_by( $_[0]->author_id )
                        if !defined $_[0]->created_by;
                },
                sql => 'update mt_entry set entry_created_by = entry_author_id
                         where entry_created_by is null',
            },
        },
        'v5_recover_auth_type' => {
            version_limit => 5.0019,
            priority      => 3.1,
            code          => \&_v5_recover_auth_type,
        },
        'v5_remove_dashboard_widget' => {
            version_limit => 5.0020,
            priority      => 3.1,
            updater       => {
                type      => 'author',
                label     => 'Removing widget from 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 $widgets = $user->widgets();
                    if ( $widgets && %$widgets ) {
                        my @keys
                            = qw( mt_shortcuts new_version new_user new_install );
                        for my $set ( keys %$widgets ) {
                            for my $key (@keys) {
                                delete $widgets->{$set}->{$key}
                                    if $widgets->{$set}->{$key};
                            }
                        }
                        $user->widgets($widgets);
                    }
                },
            },
        },
        'v5_make_blog_category_and_folder_order' => {
            version_limit => 5.0023,
            priority      => 3.2,
            updater       => {
                type  => 'blog',
                label => 'Ordering Categories and Folders of Blogs...',
                code  => sub {
                    my ($blog) = @_;
                    my @cats = MT->model('category')->load(
                        { blog_id => $blog->id, },
                        {   sort       => 'label',
                            direction  => 'ascend',
                            fetchonly => { id => 1 },
                        }
                    );
                    my $order = join ',', ( map { $_->id } @cats );
                    $blog->category_order($order);
                    my @folders = MT->model('folder')->load(
                        { blog_id => $blog->id, },
                        {   sort       => 'label',
                            direction  => 'ascend',
                            fetchonly => { id => 1 },
                        }
                    );
                    my $folder_order = join ',', ( map { $_->id } @folders );
                    $blog->folder_order($folder_order);
                    $blog->save;
                },
            },
        },
        'v5_make_website_category_order' => {
            version_limit => 5.0023,
            priority      => 3.2,
            updater       => {
                type  => 'website',
                label => 'Ordering Folders of Websites...',
                code  => sub {
                    my ($site) = @_;
                    my @folders = MT->model('folder')->load(
                        { blog_id => $site->id, },
                        {   sort       => 'label',
                            direction  => 'ascend',
                            fetchonly => { id => 1 },
                        }
                    );
                    my $folder_order = join ',', ( map { $_->id } @folders );
                    $site->folder_order($folder_order);
                    $site->save;
                },
            },
        },
    };
}

### Subroutines

sub _v5_migrate_blog {
    my $self = shift;

    my $app     = $MT::Upgrade::App;
    my $user_id = ref $app ? $app->{author}->id : $MT::Upgrade::SuperUser;
    my $user    = MT::Author->load($user_id);
    return if $user->permissions(0)->has('administer_website');

    # Create generic website.
    my $class = MT->model('website');
    return $self->error(
        $self->translate_escape( "Error loading class: [_1].", 'Website' ) )
        unless $class;

    return if $class->count() > 0;

    $self->progress(
        $self->translate_escape(
            'Populating generic website for current blogs...')
    );

    my $website
        = $class->create_default_website( MT->translate('Generic Website') );
    $website->site_path('');
    $website->site_url('');
    $website->save
        or return $self->error(
        $self->translate_escape(
            "Error saving record: [_1].",
            $website->errstr
        )
        );

    # Load all blogs.
    my $blog_class = MT->model('blog');
    return $self->error(
        $self->translate_escape( "Error loading class: [_1].", 'Blog' ) )
        unless $blog_class;
    my $iter = $blog_class->load_iter();
    while ( my $blog = $iter->() ) {
        next if $blog->parent_id;
        $self->progress(
            $self->translate_escape(
                "Migrating [_1]([_2]).",
                $blog->name, $blog->id
            )
        );
        $blog->class('blog');
        $blog->parent_id( $website->id );
        $blog->save
            or return $self->error(
            $self->translate_escape(
                "Error saving record: [_1].",
                $blog->errstr
            )
            );
    }

    # Grant new role to system administrator
    $self->progress(
        $self->translate_escape(
            'Granting new role to system administrator...')
    );

    my $assoc_class = MT->model('association');
    return $self->error(
        $self->translate_escape(
            "Error loading class: [_1].", 'Association'
        )
    ) unless $assoc_class;
    my $role_class = MT->model('role');
    return $self->error(
        $self->translate_escape( "Error loading class: [_1].", 'Role' ) )
        unless $role_class;
    my $role = MT::Role->load_by_permission('administer_website');
    $assoc_class->link( $user => $role => $website );

    return;
}

sub _v5_create_new_role {
    my $self = shift;

    my $role_class = MT->model('role');
    return $self->error(
        $self->translate_escape( "Error loading class: [_1].", 'Role' ) )
        unless $role_class;

    $self->progress(
        $self->translate_escape('Updating existing role name...') );

    my $role
        = $role_class->load( { name => MT->translate('_WEBMASTER_MT4'), } );
    if ($role) {
        return if $role->has('administer_website');
        $role->name( MT->translate('Webmaster (MT4)') );
        $role->save
            or return $self->error(
            $self->translate_escape(
                "Error saving record: [_1].",
                $role->errstr
            )
            );
    }

    $self->progress(
        $self->translate_escape('Populating new role for website...') );
    $role = $role_class->load(
        { name => MT->translate('Website Administrator'), } );
    if ( !$role ) {
        $role = $role_class->new();
        $role->name( MT->translate('Website Administrator') );
        $role->description( MT->translate('Can administer the website.') );
        $role->clear_full_permissions;
        $role->set_these_permissions(
            [ 'administer_website', 'manage_member_blogs' ] );
        $role->save
            or return $self->error(
            $self->translate_escape(
                "Error saving record: [_1].",
                $role->errstr
            )
            );
    }

    my $new_role = $role_class->new();
    $new_role->name( MT->translate('Webmaster') );
    $new_role->description(
        MT->translate(
            'Can manage pages, Upload files and publish blog templates.')
    );
    $new_role->clear_full_permissions;
    $new_role->set_these_permissions(
        [ 'manage_pages', 'rebuild', 'upload' ] );
    $new_role->save
        or return $self->error(
        $self->translate_escape(
            "Error saving record: [_1].",
            $new_role->errstr
        )
        );
}

sub _v5_migrate_theme_privilege {
    my $self = shift;

    my $role_class = MT->model('role');
    return $self->error(
        $self->translate_escape( "Error loading class: [_1].", 'Role' ) )
        unless $role_class;

    $self->progress(
        $self->translate_escape('Updating existing role name...') );
    my $role = $role_class->load( { name => MT->translate('Designer'), } );
    if ($role) {
        return if $role->has('manage_themes');
        $role->name( MT->translate('Designer (MT4)') );
        $role->save
            or return $self->error(
            $self->translate_escape(
                "Error saving record: [_1].",
                $role->errstr
            )
            );
    }

    $self->progress(
        $self->translate_escape('Populating new role for theme...') );
    my $new_role = $role_class->new();
    $new_role->name( MT->translate('Designer') );
    $new_role->description(
        MT->translate(
            'Can edit, manage and publish blog templates and themes.')
    );
    $new_role->clear_full_permissions;
    $new_role->set_these_permissions(
        [ 'manage_themes', 'edit_templates', 'rebuild' ] );
    $new_role->save
        or return $self->error(
        $self->translate_escape(
            "Error saving record: [_1].",
            $new_role->errstr
        )
        );
}

sub _v5_migrate_system_privilege {
    my $self = shift;
    $self->progress(
        $self->translate_escape(
            'Assigning new system privilege for system administrator...')
    );

    my $perm_class = MT->model('permission');
    return $self->error(
        $self->translate_escape( "Error loading class: [_1].", 'Permission' )
    ) unless $perm_class;
    my $iter = $perm_class->load_iter( { blog_id => 0, } );
    while ( my $perm = $iter->() ) {
        if ( $perm->has('administer') ) {
            $self->progress(
                $self->translate_escape(
                    'Assigning to  [_1]...',
                    $perm->author->name
                )
            );
            $perm->set_these_permissions( ['create_website'] );
            $perm->save
                or return $self->error(
                $self->translate_escape(
                    "Error saving record: [_1].",
                    $perm->errstr
                )
                );
        }
    }
}

sub _v5_migrate_mtview {
    my $self = shift;

    $self->progress(
        $self->translate_escape('Migrating mtview.php to MT5 style...') );

    require MT::FileMgr;
    my $fmgr = MT::FileMgr->new('Local');

    my $blog_class = MT->model('blog');
    return $self->error(
        $self->translate_escape( "Error loading class: [_1].", 'Website' ) )
        unless $blog_class;

    require File::Spec;
    my $iter = $blog_class->load_iter( { class => 'blog' } );
    while ( my $blog = $iter->() ) {
        my $site_path = $blog->site_path;
        my $mtview = File::Spec->catfile( $site_path, 'mtview.php' );
        if ( $fmgr->exists($mtview) ) {
            my $data = $fmgr->get_data($mtview);
            if ($data) {
                $data =~ s/new MT/MT::get_instance/;
                $fmgr->rename( $mtview, $mtview . '.bak' );
                $fmgr->put_data( $data, $mtview );
            }
        }
    }
}

sub _v5_migrate_default_site {
    my $self = shift;

    my $site_url   = MT->config('DefaultSiteURL');
    my $site_path  = MT->config('DefaultSiteRoot');
    my $default_id = MT->config('NewUserDefaultWebsiteId');

    if ( $site_url && $site_path && !$default_id ) {
        $self->progress(
            $self->translate_escape(
                'Migrating DefaultSiteURL/DefaultSiteRoot to website...')
        );

        my $class = MT->model('website');
        return $self->error(
            $self->translate_escape(
                "Error loading class: [_1].", 'Website'
            )
        ) unless $class;

        my $website = $class->create_default_website(
            MT->translate("New user's website") );
        $website->site_path($site_path);
        $website->site_url($site_url);
        $website->save
            or return $self->error(
            $self->translate_escape(
                "Error saving record: [_1].",
                $website->errstr
            )
            );

        MT->config( 'NewUserDefaultWebsiteId', $website->id, 1 );
        MT->config( 'DefaultSiteURL',          undef,        1 );
        MT->config( 'DefaultSiteRoot',         undef,        1 );

        # Grant new role to system administrator
        my $app     = $MT::Upgrade::App;
        my $user_id = ref $app ? $app->{author}->id : $MT::Upgrade::SuperUser;
        my $user    = MT::Author->load($user_id);
        my $assoc_class = MT->model('association');
        return $self->error(
            $self->translate_escape(
                "Error loading class: [_1].",
                'Association'
            )
        ) unless $assoc_class;
        my $role_class = MT->model('role');
        return $self->error(
            $self->translate_escape( "Error loading class: [_1].", 'Role' ) )
            unless $role_class;
        my $role = MT::Role->load_by_permission('administer_website');
        $assoc_class->link( $user => $role => $website );
    }
}

sub _v5_migrate_dashboard {
    my $user = shift;
    my $conf = $user->widgets;
    return 1 unless $conf;

    my $new_widgets;
    my @keys = keys %$conf;
    foreach my $key (@keys) {
        my @widget_keys = keys %{ $conf->{$key} };
        foreach my $widget (@widget_keys) {
            if ( $widget eq 'mt_shortcuts' ) {
                next;
            }
            elsif ($widget eq 'this_is_you-1'
                || $widget eq 'new_install'
                || $widget eq 'new_user' )
            {
                $new_widgets->{ 'dashboard:user:' . $user->id }->{$widget}
                    ->{order} = $conf->{$key}->{$widget}->{order};
                $new_widgets->{ 'dashboard:user:' . $user->id }->{$widget}
                    ->{set} = 'main';
                next;
            }
            elsif ( $widget eq 'mt_news' ) {
                $new_widgets->{ 'dashboard:user:' . $user->id }->{$widget}
                    ->{order} = $conf->{$key}->{$widget}->{order};
                $new_widgets->{ 'dashboard:user:' . $user->id }->{$widget}
                    ->{set} = 'sidebar';
                next;
            }
            elsif ( $widget eq 'blog_stats' && $key eq 'dashboard:system' ) {
                next;
            }
            $new_widgets->{$key}->{$widget} = $conf->{$key}->{$widget};
        }
    }

    # New widgets from MT5
    $new_widgets->{ 'dashboard:user:' . $user->id }->{'favorite_blogs'}
        ->{order} = 3;
    $new_widgets->{ 'dashboard:user:' . $user->id }->{'favorite_blogs'}->{set}
        = 'main';
    $new_widgets->{'dashboard:system'}->{'recent_websites'}->{order} = 1;
    $new_widgets->{'dashboard:system'}->{'recent_websites'}->{set}   = 'main';

    my $class = MT->model('website');
    return unless $class;
    my $generic_website
        = $class->load( { name => MT->translate('Generic Website') } );
    return unless $generic_website;

    $new_widgets->{ 'dashboard:blog:' . $generic_website->id }
        ->{'recent_blogs'}->{order} = 1;
    $new_widgets->{ 'dashboard:blog:' . $generic_website->id }
        ->{'recent_blogs'}->{set} = 'main';

    $user->widgets($new_widgets);
    $user->save;
}

sub _v5_generate_websites_place_blogs {
    my $self = shift;

    require MT::Blog;
    my $iter = MT::Blog->load_iter(
        [   { class => 'blog' },
            '-and', [ { parent_id => \'IS NULL' }, '-or', { parent_id => 0 } ]
        ]
    );

    my %site_urls;
    while ( my $blog = $iter->() ) {
        my $blogs;
        $blogs = $site_urls{ $blog->site_url }
            if defined $site_urls{ $blog->site_url };
        push @$blogs, $blog;
        $site_urls{ $blog->site_url } = $blogs;
    }

    return unless %site_urls;

    $self->progress(
        $self->translate_escape(
            'Migrating existing [quant,_1,blog,blogs] into websites and its children...',
            scalar( keys(%site_urls) )
        )
    );

    my $assoc_class = MT->model('association');
    return $self->error(
        $self->translate_escape(
            "Error loading class: [_1].", 'Association'
        )
    ) unless $assoc_class;
    my $role_class = MT->model('role');
    return $self->error(
        $self->translate_escape( "Error loading class: [_1].", 'Role' ) )
        unless $role_class;
    my $role = $role_class->load_by_permission('administer_website');
    return $self->error(
        $self->translate_escape(
            "Error loading role: [_1].",
            'administer_website'
        )
    ) unless $role;

    my @sysadmins = MT::Author->load(
        { type => MT::Author::AUTHOR() },
        {   join => MT::Permission->join_on(
                'author_id',
                {   permissions => "\%'administer'\%",
                    blog_id     => '0',
                },
                { 'like' => { 'permissions' => 1 } }
            )
        }
    );

 # 1. find the shortest site_url, pick its domain and make it as a website url
 # 2. find urls under the domain and make it children
 # 3. find urls of that are subdomains of the domain and make it children
 # 4. go to step 1 until everything is either a child or a website

    my $website_class = MT->model('website');
    return $self->error(
        $self->translate_escape( "Error loading class: [_1].", 'Website' ) )
        unless $website_class;

    my %websites;
    while ( my @site_urls = keys %site_urls ) {
        @site_urls = sort { length($a) <=> length($b) } @site_urls;
        my $shortest = shift @site_urls;
        my ( $ssl, $domain ) = $shortest =~ m!^http(s?)://(.+?)(/|$)!gi;
        my $dot = index( $domain, '.' );

        # XXX: ignoring domain that starts with ".".
        if ( $dot > 0 ) {
            $domain =~ s!^(?:.*?)([^\.]+?)(\.\w+)$!$1$2!;
        }
        my ( $subdomain, $path )
            = $shortest =~ m!^https?://(.*)\.?$domain/(.*)$!;
        my $blogs = delete $site_urls{$shortest};
        foreach my $blog (@$blogs) {
            my $site_url = "http$ssl://$domain/";
            my $website = $website_class->load( { site_url => $site_url } );
            unless ($website) {
                $website = $website_class->create_default_website(
                    MT->translate(
                        'New WebSite [_1]', "http$ssl://$domain/"
                    ),
                    MT->config->DefaultWebsiteTheme
                );
                $website->site_path( $blog->site_path );
                $website->site_url("http$ssl://$domain/");
                $website->save
                    or return $self->error(
                    $self->translate_escape(
                        "An error occured during generating a website upon upgrade: [_1]",
                        $website->errstr
                    )
                    );

                foreach (@sysadmins) {
                    $assoc_class->link( $_ => $role => $website )
                        or die $role->name . $website->name;
                }
                $self->progress(
                    $self->translate_escape(
                        'Generated a website [_1]',
                        "http$ssl://$domain/"
                    )
                );
            }
            $path = $path . '/' if $path && $path !~ m!/$!;
            my $old_site_url = $blog->site_url;
            $blog->site_url("$subdomain/::/$path");
            $blog->parent_id( $website->id );

  # if archive_url is "under" the website url (i.e. either subdomain or path)
  # use the new data syntax (/::/).  otherwise leave it as-is.  config screens
  # and everything should handle both relative and absolute url correctly.
            if ( my $archive_url = $blog->column('archive_url') ) {
                if ( $archive_url =~ m!https?://(.+\.)?$domain/?(.*/?)$! ) {
                    $blog->archive_url("$1/::/$2");
                }
            }
            $blog->save
                or return $self->error(
                $self->translate_escape(
                    "An error occured during migrating a blog's site_url: [_1]",
                    $website->errstr
                )
                );
            $self->progress(
                $self->translate_escape(
                    'Moved blog [_1] ([_2]) under website [_3]',
                    $blog->name, $old_site_url, $domain
                )
            );

            my @children = grep { $_ =~ m!https?://.+$domain/! } @site_urls;
            foreach my $child_url (@children) {
                my ( $subdomain, $path )
                    = $child_url =~ m!^https?://(.*)\.?$domain/(.*)$!;
                my $child_blogs = delete $site_urls{$child_url};
                foreach my $child (@$child_blogs) {
                    $path = $path . '/' if $path && $path !~ m!/$!;
                    my $old_site_url = $child->site_url;
                    $child->site_url("$subdomain/::/$path");
                    $child->parent_id( $website->id );

                    # same here for archive_urls as is described above.
                    if ( my $archive_url = $child->column('archive_url') ) {
                        if ( $archive_url
                            =~ m!https?://(.+\.)?$domain/?(.*/?)$! )
                        {
                            $child->archive_url("$1/::/$2");
                        }
                    }
                    $child->save
                        or return $self->error(
                        $self->translate_escape(
                            "An error occured during migrating a blog's site_url: [_1]",
                            $website->errstr
                        )
                        );
                    $self->progress(
                        $self->translate_escape(
                            'Moved blog [_1] ([_2]) under website [_3]',
                            $child->name, $old_site_url, $domain
                        )
                    );
                }
            }
        }
    }
    1;
}

sub _v5_remove_technorati {
    my $self = shift;

    my $class = MT->model('blog');
    return $self->error(
        $self->translate_escape(
            "Error loading class: [_1].",
            $class->class_label
        )
    ) unless $class;

    my $iter = $class->load_iter( { class => '*' } );
    while ( my $blog = $iter->() ) {
        next unless ( $blog->update_pings || '' ) =~ m/technorati/i;

        my @pings = split ',', $blog->update_pings;
        @pings = grep { $_ ne 'technorati' } @pings;
        my $pings;
        $pings = join ',', @pings if @pings;
        $blog->update_pings($pings);
        $blog->save;
        $self->progress(
            $self->translate_escape(
                "Removing technorati update-ping service from [_1] (ID:[_2]).",
                $blog->name,
                $blog->id,
            )
        );
    }
}

sub _v5_remove_news_widget_cache {
    my $self = shift;
    $self->progress(
        $self->translate_escape( 'Expiring cached MT News widget...', ) );
    my $class = MT->model('session')
        or return $self->error(
        $self->translate_escape( "Error loading class: [_1].", 'session' ) );
    $class->remove( { kind => [qw( NW LW )] } );
}

sub _v5_recover_auth_type {
    my $self = shift;
    $self->progress(
        $self->translate_escape( 'Recovering type of author...', ) );

    my $authenticators = MT->registry('commenter_authenticators');
    my @keys           = keys %$authenticators;
    return unless @keys;

    my $author_class = MT->model('author')
        or return $self->error(
        $self->translate_escape( "Error loading class: [_1].", 'author' ) );

    my $auth_iter = $author_class->load_iter(
        {   type      => MT::Author::AUTHOR(),
            auth_type => \@keys,
        }
    );
    while ( my $author = $auth_iter->() ) {
        $author->type( MT::Author::COMMENTER() );
        $author->save;
    }
}

1;
__END__
