# Movable Type (r) Open Source (C) 2001-2010 Six Apart, Ltd.
# This program is distributed under the terms of the
# GNU General Public License, version 2.
#
# $Id: Import.pm 5151 2010-01-06 07:51:27Z takayama $

package MT::Import;

use strict;
use warnings;
use base qw(MT::ErrorHandler Exporter);

our @EXPORT;
our %Importers;

use Symbol;

sub new {
    my $class = shift;
    my $obj = bless {}, $class;
    $obj->init(@_);
    $obj;
}

sub importer_keys {
    init() unless %Importers;
    keys %Importers;
}

sub importer {
    my $mt = shift;
    my ($importer) = @_;
    init() unless %Importers;
    return $Importers{$importer};
}

sub init {
    my $self = shift;
    my $importers = MT->instance->registry("import_formats") || {};
    %Importers = %$importers;
}

sub core_import_formats {
    return {
        'import_mt' => {
            label => 'Movable Type',
            type => 'MT::ImportExport',
            handler => 'MT::ImportExport::import_contents',
        },
        'import_mt_format' => {
            label => 'Another system (Movable Type format)',
            type => 'MT::ImportExport',
            handler => 'MT::ImportExport::import_contents',
            options => [ 'title_start', 'title_end', 'default_status' ],
            options_template => 'import_others.tmpl',
        }
    };
}

sub _get_stream_iterator {
    my $class = shift;
    my ($stream, $cb) = @_;
    my $iter;
    if (ref($stream) eq 'Fh') {
        seek($stream, 0, 0) or return $class->error(MT->translate("Can't rewind"));
        $iter = sub {
            my $str = $stream;
            my $eof = eof($stream);
            return $eof ? undef : $str;
        };
    } elsif (ref($stream) eq 'SCALAR') {
        require IO::String;
        $stream = IO::String->new($$stream);
        $iter = sub {
            my $str = $stream;
            $stream = undef;
            $str;
        };
    } elsif (ref $stream) {
        seek($stream, 0, 0) or return $class->error(MT->translate("Can't rewind"));
        $iter = sub {
            my $str = $stream;
            $stream = undef;
            $str;
        };
    } else {
        if (-f $stream) {
            my $fh = gensym();
            open $fh, $stream or return $class->error(MT->translate("Can't open '[_1]': [_2]", $stream, $!));
            $stream = $fh;
            $iter = sub {
                my $str = $stream;
                $stream = undef;
                $str;
            };
        } elsif (-d $stream) {
            my @files_to_import;
            my $dir = $stream;
            $stream = undef;
            opendir DH, $dir or return $class->error(MT->translate(
                "Can't open directory '[_1]': [_2]", $dir, "$!"));
            for my $f (readdir DH) {
                next if $f =~ /^\./;
                my $file = File::Spec->catfile($dir, $f);
                push @files_to_import, $file if -r $file;
            }
            closedir DH;
            unless (@files_to_import) {
                return $class->error(MT->translate("No readable files could be found in your import directory [_1].", $dir));
            }
            $iter = sub {
                close $stream if $stream;
                return undef unless @files_to_import;
                my $file = shift @files_to_import;
                my $fh = gensym();
                $cb->(MT->translate("Importing entries from file '[_1]'", $file) ."\n");
                open $fh, "<$file"
                    or return $class->error(MT->translate("Can't open '[_1]': [_2]", $file, $!));
                $stream = $fh;
            };
        }
    }
    $iter;
}


sub import_contents {
    my $self = shift;
    my %param = @_;
    my $stream = delete $param{Stream};
    my $iter = $self->_get_stream_iterator($stream, $param{Callback});
    my $importer = $self->importer($param{Key});
    $param{Iter} = $iter;
    my $code = $importer->{code} || $importer->{handler};
    unless (ref $code eq 'CODE') {
        $code = $importer->{code} = MT->handler_to_coderef($code);
    }
    if ($code) {
        my $result = eval {
            $importer->{code}->($importer->{type}, %param);
        };
        print "Error: $@" if $@;
        return $result;
    } else {
        return $self->error(MT->translate("Couldn't resolve import format [_1]", $param{Key}));
    }
}

sub _get_options_tmpl {
    my $self = shift;
    my ($key) = @_;

    my $importer = $self->importer($key);
    my $tmpl = $importer->{options_template};
    return q() unless $tmpl;
    return $tmpl->($importer) if ref $tmpl eq 'CODE';
    if ($tmpl =~ /\s/) {
        return $tmpl;
    } else { # no spaces in $tmpl; must be a filename...
        if (my $c = $importer->{plugin}) {
            return $c->load_tmpl($tmpl) or die $c->errstr;
        } else {
            return MT->instance->load_tmpl($tmpl);
        }
    }
}

sub get_options_html {
    my $self = shift;
    my ($key, $blog_id) = @_;
    return q() unless $blog_id;
    my $importer = $self->importer($key);

    my $blog_class = MT->model('blog');
    my $blog = $blog_class->load($blog_id)
        or return q();

    my $snip_tmpl = $self->_get_options_tmpl($key);
    return q() unless $snip_tmpl;

    require MT::Template;
    my $tmpl;
    if (ref $snip_tmpl ne 'MT::Template') {
        $tmpl = MT::Template->new(
            type => 'scalarref',
            source => ref $snip_tmpl ? $snip_tmpl : \$snip_tmpl,
        );
    } else {
        $tmpl = $snip_tmpl;
    }

    if (my $options_param = $importer->{options_param}) {
        if (ref($options_param) ne 'CODE') {
            if (my $c = $importer->{plugin}) {
                $options_param = MT->handler_to_coderef($options_param);
            }
        }
        my $param = $options_param->($blog_id)
            if ref $options_param eq 'CODE';

        $param->{blog_id} = $blog_id;
        $param->{missing_paths} =
          (      ( defined $blog->site_path || defined $blog->archive_path )
              && ( -d $blog->site_path || -d $blog->archive_path ) ) ? 0 : 1;

        $tmpl->param($param) if $param;
    }
    my $html = $tmpl->output();
    if ($html =~ m/<(_|MT)_TRANS /i) {
        if (my $c = $importer->{plugin}) {
            $html = $c->translate_templatized($html);
        } else {
            $html = MT->translate_templatized($html);
        }
    }
    return $html;
}

1;

__END__

=head1 NAME

MT::Import

=head1 METHODS

=head2 import_contents(%param)

This method is called from CMS when user decided to import files in a selected format.
This method calls selected importer's method to do actual imports.

=head2 register_importer($importer)

This method collects importer from a plugin which requests to add itself to
the importer list.  Collected information will be used to show the format
selection in the dropdown on the Import/Export screen, and actual call
to import data from uploaded files.

The information registered as an importer must have the following data
in a single hash:

    my $plugin = new Foo::Bar::Plugin({...});
    $plugin->register_importer({
        name => 'Name of the format which will appear in dropdown',
        key => 'importer_key_which_will_differentiate_it_from_others',
        code => \&Package::method_which_do_import,
        options => [ 'options', 'to', 'receive' ],
        options_template => 'template name' or \&template_code of options,
        options_param => \&Package::method_to_populate_param,
    });

The subroutine referenced by the "code" key takes the following parameters.

    {
        Iter => iterator of streams of files to be imported
        Blog => reference to the blog object which accepts imports
        Callback => subroutine reference used to report progress
        Encoding => specified encoding method for files
        ImportAs or ParentAuthor => reference to author object
        NewAuthorPassword => default password for newly created authors
        DefaultCategoryID => default category id to be used
        key => value, other options 
    }

The subroutine referenced by the "options_param" key takes the following parameters.

    blog_id: blog's id which is importing data.

The subroutine should return a reference to a hash which contains paramters passed to
template upon building html.

=head1 AUTHOR & COPYRIGHT

Please see L<MT/AUTHOR & COPYRIGHT>.

=cut
