#/*
# *  Copyright 2007-2008 hkrn <hikarin@users.sourceforge.jp>
# *
# *  Licensed under the Apache License, Version 2.0 (the "License");
# *  you may not use this file except in compliance with the License.
# *  You may obtain a copy of the License at
# *
# *      http://www.apache.org/licenses/LICENSE-2.0
# *
# *  Unless required by applicable law or agreed to in writing, software
# *  distributed under the License is distributed on an "AS IS" BASIS,
# *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# *  See the License for the specific language governing permissions and
# *  limitations under the License.
# */
#
# $Id: UFM.pm 1688 2008-04-27 13:58:44Z hikarin $
#

package Img0ch::App::UFM;

use strict;
use Img0ch::BBS qw();
use Img0ch::Template qw();
use Img0ch::Upload qw();

my $TAG = 'BAD';

sub new {
    my ( $iClass, $iKernel, @args ) = @_;
    bless {
        __bbs     => undef,
        __config  => $iKernel->get_config(),
        __filter  => undef,
        __kernel  => $iKernel,
        __key     => 0,
        __param   => {},
        __resno   => 0,
        __request => Img0ch::Request->new(@args),
        __setting => undef,
        __upload  => undef,
    }, $iClass;
}

sub run {
    my ($iApp) = @_;
    $iApp->init() or return $iApp->redirect_error('init');

    my $key   = $iApp->{__key};
    my $resno = $iApp->{__resno};
    my ( $result, $iTemplate, $ctype, $body );
    if ( $key and $resno ) {
        ( $result, $iTemplate, $ctype, $body )
            = $iApp->process( $key, $resno );
    }
    else {
        ( $result, $iTemplate, $ctype, $body ) = $iApp->render();
    }

    $result or return $iApp->redirect_error('remove');
    my $iRequest = $iApp->{__request};
    $iRequest->send_http_header( $ctype || 'text/html' );
    $iTemplate
        ? $iTemplate->flush( $iApp->{__filter} )
        : $iRequest->print( $body, $iApp->{__filter} );
    return 1;
}

sub init {
    my ($iApp) = @_;
    my $iRequest = $iApp->{__request};
    $iRequest->init( $iApp->{__config} );

    my $bbs   = $iRequest->bbs();
    my $key   = $iRequest->param_int('key');
    my $resno = $iRequest->param_int('resno');
    if ( my $path_info = $iRequest->path_info() ) {
        my @path = split '/', $path_info;
        ( $path[1] || 'test' ) =~ /([\w\-]+)/xms;
        $bbs = $1;
        ( $path[2] || '0000000000' ) =~ /(\d{9,10})/xms;
        $key = $1 eq '0000000000' ? $key : $1;
        ( $path[3] || '0' ) =~ /(\d+)/xms;
        $resno = $1 || $resno;
        if ( scalar @path > 5 ) {
            splice @path, 0, 4, ();
            %{ $iApp->{__param} } = @path;
        }
    }

    my $iBBS = Img0ch::BBS->new( $iApp->{__kernel}, { bbs => $bbs, } );
    if ( !$iBBS->get_id() ) {
        $iApp->{__error} = 'INVALID_POST';
        return 0;
    }

    $iApp->{__bbs}     = $iBBS;
    $iApp->{__key}     = $key;
    $iApp->{__resno}   = $resno;
    $iApp->{__setting} = $iBBS->get_setting_instance();

    return 1;
}

sub get_param {
    my ( $iApp, $key ) = @_;
    my $value = $iApp->{__param}->{$key} || '';
    $value =~ tr/+/ /;
    $value =~ s/%([0-9A-Fa-f][0-9A-Fa-f])/pack('H2', $1)/egxms;
    return Img0ch::Kernel::escape_html_entities($value);
}

sub get_param_int {
    my ( $iApp, $key ) = @_;
    return Img0ch::Kernel::intval( $iApp->{__param}->{$key} || '' );
}

sub process {
    my ( $iApp, $key, $resno ) = @_;
    my $iBBS     = $iApp->{__bbs};
    my $iRequest = $iApp->{__request};
    my $iUpload  = $iApp->{__upload} = $iBBS->get_upload_instance($key);
    $iUpload->get($resno)->[0] or return ( 0, undef, '', \'' );

    my $pass = $iApp->get_param('pass') || $iRequest->param('pass');
    my $type = $iApp->get_param('type') || $iRequest->param('type');
    my $process = {};

    if ($pass) {
        my $js = {};
        if (    $type eq 'remove'
            and $iUpload->can_remove( $resno, $pass ) )
        {
            $iUpload->remove($resno);
            $iUpload->save();
            $process->{removed} = 1;
            $js->{ok}           = 1;
        }
        elsif ( $type eq 'download'
            and $iUpload->can_download( $resno, $pass ) )
        {
            my $iKernel = $iApp->{__kernel};
            my ($ext)   = @{ $iUpload->get($resno) };
            my $path    = $iUpload->path( $resno, $ext );
            my $size    = -s $path;
            my $agent   = $iRequest->agent();

            if ( $agent == 3 ) {
                my $email = $iRequest->param('email');
                my $id    = $iRequest->get_device_id($agent);

                if ( $id and $email and $email =~ /ezweb\.ne\.jp\z/xms ) {

                    open my $fh, '<', $path
                        or $iKernel->throw_io_exception($path);
                    my $body = do { local $/ = undef; <$fh> };
                    close $fh or $iKernel->throw_io_exception($path);

                    my $iConfig = $iKernel->get_config();
                    my $from    = $iConfig->get('MailRawAddress');

                    my $message =~ s{<br\s?/?>}{\n}gxms;
                    my $message_jis
                        = $iKernel->get_encoded_str( $message, 'jis' );

                    defined $Email::Send::VERSION
                        or require Email::Send;

                    my $sender;
                    if ( my $sendmail = $iConfig->get('SendmailPath') ) {
                        $sender
                            = Email::Send->new( { mailer => 'Sendmail' } );
                        $Email::Send::Sendmail::SENDMAIL = $sendmail;
                    }
                    elsif ( my $qmail = $iConfig->get('QmailPath') ) {
                        $sender = Email::Send->new( { mailer => 'Qmail' } );
                        $Email::Send::Qmail::QMAIL = $qmail;
                    }
                    else {
                        my $host = $iConfig->get('smtp.host');
                        my $port = $iConfig->get_int('smtp.port');
                        $port and $host = join ':', $host, $port;
                        $sender = Email::Send->new( { mailer => 'SMTP' } );
                        $sender->mailer_args(
                            [   Host => $host,
                                ssl  => $iConfig->get('smtp.ssl'),
                                tls  => $iConfig->get('smtp.tls'),
                            ]
                        );
                    }

                    defined $Email::MIME::Creator::VERSION
                        or require Email::MIME::Creator;
                    defined &{'Img0ch::Util::Ezweb::ext2type'}
                        or require Img0ch::Util::Ezweb;

                    my $filename   = $key . '-' . $resno . '.' . $ext;
                    my $mail_parts = [
                        Email::MIME->create(
                            attributes => {
                                filename => $filename,
                                content_type =>
                                    Img0ch::Util::Ezweb::ext2mime($ext),
                                encoding => 'base64',
                                name     => $filename,
                            },
                            body => $body,
                        )
                    ];
                    my $mail_body = Email::MIME->create(
                        header => [
                            From    => $from,
                            To      => $email,
                            Subject => $filename
                        ],
                        parts => $mail_parts,
                    );

                    my $result = $sender->send( $mail_body->as_string() );
                    $process->{address_is_invalid} = 0;
                    if ($result) {
                        $process->{send_ok}     = 1;
                        $process->{send_errstr} = '';
                    }
                    else {
                        $process->{send_ok}     = 0;
                        $process->{send_errstr} = $result;
                    }
                }
                else {
                    $process->{send_ok}            = 0;
                    $process->{address_is_invalid} = 1;
                }
            }
            else {
                my $fh = $iKernel->get_read_file_handle($path);

                binmode $fh;
                my $buffer = do { local $/ = undef; <$fh> };
                close $fh or $iKernel->throw_io_exception($path);

                $iRequest->enable_binary();
                $iRequest->set_header( 'content-disposition',
                    'attachment; filename="' . "${resno}.${ext}" . '"' );
                $iRequest->set_header( 'content-length', $size );

                require MIME::Types;
                return ( 1, undef, MIME::Types->new()->mimeTypeOf($ext),
                    \$buffer, );
            }
        }
        elsif ( $type eq 'confirm' ) {
            $js->{remove} = $iUpload->can_remove( $resno, $pass );
            $js->{download} = $iUpload->can_download( $resno, $pass );
            $process->{ok} = 1;
        }
        if ( $iRequest->param('js') ) {
            return ( ( $process->{ok} || $process->{removed} || 0 ),
                undef, 'text/javascript', $iRequest->get_json($js) );
        }
    }
    elsif ( $type eq 'poll' ) {
        defined &{'Img0ch::Upload::Poll::new'}
            or require Img0ch::Upload::Poll;
        my $iSetting = $iApp->{__setting};
        my $iUPoll   = Img0ch::Upload::Poll->new( $iBBS, $key );
        my $agent    = $iRequest->agent();
        my $comment  = $iRequest->param('comment');
        my $ip       = $iRequest->ip();
        my $maxlen   = $iSetting->get_int('BBS_IMG_POLL_COMMENT_COUNT');
        if ($iApp->validate_poll( $ip, $agent, $comment, $maxlen )
            and $iUPoll->can_poll(
                $TAG,
                $iRequest->ip(),
                $iSetting->get_int('BBS_IMG_POLL_TIME_TO_POLLABLE'),
                $iSetting->get_int('BBS_IMG_POLL_POLLABLE_COUNT')
            )
            and $iUPoll->add( $resno, $TAG, $ip, $comment )
            )
        {
            $iUPoll->save();
            $process->{polled} = 1;
            if ( $iUPoll->count( $resno, $TAG )
                >= $iSetting->get('BBS_IMG_POLL_REQUIRE_TO_STOP') )
            {
                $iUpload->freeze($resno);
                $process->{freezed} = 1;
            }
        }
        else {
            $process->{polled} = 0;
        }
    }
    elsif ( $type eq 'rate' ) {
        defined &{'Img0ch::Upload::Poll::new'}
            or require Img0ch::Upload::Poll;
        my $iSetting = $iApp->{__setting};
        my $iUPoll   = Img0ch::Upload::Poll->new( $iBBS, $key );
        my $agent    = $iRequest->agent();
        my $comment  = $iRequest->param('comment');
        my $rate     = $iRequest->param_int('rate');
        my $ip       = $iRequest->ip();
        my $maxlen = $iSetting->get_int('BBS_IMG_POLL_COMMENT_COUNT') || 255;

        if ( $iApp->validate_poll( $ip, $agent, $comment, $maxlen ) ) {
            $rate < 1 or $rate > 5 and $rate = 3;
            $comment = join "\n", $rate, $comment;
            if ( $iUPoll->add( $resno, 'STAR', $ip, $comment, undef, 1 ) ) {
                $iUPoll->save();
                $process->{rated} = 1;
            }
            else {
                $process->{rate} = 0;
            }
        }
        else {
            $process->{rated} = 0;
        }
    }
    elsif ( $type eq 'tagging' ) {
        defined &{'Img0ch::Upload::Tag::new'}
            or require Img0ch::Upload::Tag;
        my $iConfig = $iApp->{__config};
        if ( !$iConfig->get('search.disable_upload_tag') ) {
            my $iUTag  = Img0ch::Upload::Tag->new($iBBS);
            my $maxcnt = $iConfig->get_int('ufm.tag_max_count') || 10;
            my $maxlen = $iConfig->get_int('ufm.tag_max_length') || 32;
            my $ip     = $iRequest->ip();
            my $tags   = [
                grep { length($_) <= $maxlen } split /\s+/xms,
                $iRequest->param('tag')
            ];
            scalar @$tags > $maxcnt and @$tags = splice @$tags, 0, $maxcnt;
            if ( $iApp->can_post($ip) ) {
                $iUTag->set( $key, $resno, $ip, $tags );
                $iUTag->save();
                $iApp->save_tag_json($iUTag);
                $process->{tagged} = 1;
            }
            else {
                $process->{tagged} = 0;
            }
        }
        else {
            $process->{tagged} = 0;
        }
    }

    return $iApp->render( $process, $key, $resno, $pass, );
}

sub load_poll {
    my ( $iApp, $key, $resno ) = @_;

    defined &{'Img0ch::Upload::Poll::new'} or require Img0ch::Upload::Poll;
    my $iUPoll = Img0ch::Upload::Poll->new( $iApp->{__bbs}, $key );
    my $ret = $iUPoll->get( $resno, $TAG );
    my $i = 1;
    for my $one (@$ret) {
        my @d = localtime( $one->{polled} );
        $d[5] += 1900;
        $d[4] += 1;
        $one->{resno} = $i;
        $one->{polled} = sprintf '%02d/%02d/%02d %02d:%02d:%02d', $d[5],
            $d[4], $d[3], $d[2], $d[1], $d[0];
        $i++;
    }

    return {
        PollForBad      => $ret,
        PollForBadCount => scalar(@$ret),
        PollableCount   => $iUPoll->get_pollable_count(
            $TAG,
            $iApp->{__request}->ip(),
            $iApp->{__setting}->get_int('BBS_IMG_POLL_POLLABLE_COUNT')
        ),
    };
}

sub load_review {
    my ( $iApp, $key, $resno ) = @_;

    defined &{'Img0ch::Upload::Poll::new'} or require Img0ch::Upload::Poll;
    my $iUPoll = Img0ch::Upload::Poll->new( $iApp->{__bbs}, $key );
    my $ret = $iUPoll->get( $resno, 'STAR' );
    my $i = 1;
    for my $one (@$ret) {
        my ( $rate, $comment ) = split "\n", $one->{comment};
        my @d = localtime( $one->{polled} );
        $d[5] += 1900;
        $d[4] += 1;
        $one->{resno}    = $i;
        $one->{comment}  = $comment;
        $one->{rate}     = $rate;
        $one->{reviewed} = sprintf '%02d/%02d/%02d %02d:%02d:%02d', $d[5],
            $d[4], $d[3], $d[2], $d[1], $d[0];
        $i++;
    }

    defined $List::Util::VERSION or require List::Util;
    my $count = scalar @$ret || 1;
    my $average = List::Util::sum( map { $_->{rate} } @$ret ) / $count;
    return {
        Reviews     => $ret,
        ReviewCount => $count,
        RateAverage => sprintf( '%01.1f', $average ),
    };
}

sub validate_poll {
    my ( $iApp, $ip, $agent, $comment, $maxlen ) = @_;
    !$comment                 and return 0;
    length $comment > $maxlen and return 0;
    $agent                    and return 0;
    $iApp->can_post($ip) or return 0;
    return 1;
}

sub save_tag_json {
    my ( $iApp, $iUTag ) = @_;
    my $iKernel = $iApp->{__kernel};
    my ( $fh, $path );

    $path = $iApp->{__bbs}->path( 'img', 'tag.json' );
    open $fh, '>', $path or $iKernel->throw_io_exception($path);
    print $fh ${ $iUTag->to_json() }
        or $iKernel->throw_io_exception($path);
    close $fh or $iKernel->throw_io_exception($path);

    if ( my $key = $iApp->{__key} ) {
        $path = join '/', $iApp->{__upload}->path(), 'tag.json';
        open $fh, '>', $path or $iKernel->throw_io_exception($path);
        print $fh ${ $iUTag->to_json($key) }
            or $iKernel->throw_io_exception($path);
        close $fh or $iKernel->throw_io_exception($path);
    }

    return 1;
}

sub can_post {
    my ( $iApp, $ip ) = @_;
    my $iConfig = $iApp->{__config};

    if ( !$iConfig->get('ufm.skip_check_dnsbl') ) {
        defined &{'Img0ch::Plugin::Module::DNSBL::is_in_dnsbl'}
            or require Img0ch::Plugin::Module::DNSBL;
        my $dnsbl = $iConfig->get('ufm.dnsbl_list') || 'dsbl,rbl,bbq,bbx';
        my @dnsbl_list = split ',', $dnsbl;
        map {
            Img0ch::Plugin::Module::DNSBL->is_in_dnsbl( $_, undef, $ip )
                and return 0
        } @dnsbl_list;
    }

    if ( !$iConfig->get('ufm.skip_check_ip') ) {
        defined &{'Img0ch::Filter::IP::new'} or require Img0ch::Filter::IP;
        my $iFIP = Img0ch::Filter::IP->new( $iApp->{__bbs} );
        $iFIP->load();
        $iFIP->write_ok($ip) or return 0;
    }

    return 1;
}

sub render {
    my ( $iApp, $process, $key_param, $resno_param, $pass_param ) = @_;
    my $iBBS     = $iApp->{__bbs};
    my $iConfig  = $iApp->{__config};
    my $iRequest = $iApp->{__request};
    $key_param   ||= $iApp->{__key};
    $resno_param ||= $iApp->{__resno};

    my $iSubject = $iBBS->get_subject_instance();
    my $i        = 1;
    my $stack    = [];
    for my $key ( @{ $iSubject->to_array() } ) {
        my $subj = $iSubject->get($key);
        push @{$stack},
            {
            res      => $subj->[1],
            key      => $key,
            subject  => $subj->[0],
            count    => $i,
            index    => $i,
            selected => ( $key eq $key_param ),
            };
        $i++;
    }

    my $template_file = 'ufm';
    if ( $iRequest->param('m') ) {
        $template_file = 'ufm_mobile';
        $iApp->{__filter} = sub { ${ $_[0] } =~ tr/\n//d; ${ $_[0] } };
    }

    my $iMeta     = $iBBS->get_metadata_instance();
    my $iTemplate = $iBBS->get_template_instance(
        {   file    => $template_file,
            request => $iRequest,
            setting => $iApp->{__setting},
        }
    );

    my $removed = $process->{removed};
    my $status  = {};
    for my $key (qw(polled rated tagged)) {
        my $really_did   = exists $process->{$key};
        my $result       = $process->{$key};
        my $template_key = ucfirst $key;
        $status->{"${template_key}OK"} = ( $really_did and $result );
        $status->{"${template_key}NG"} = ( $really_did and !$result );
    }

    my $poll_rate = {};
    my $poll_bad  = {};
    if ( $key_param and $resno_param ) {
        %$poll_rate = %{ $iApp->load_review( $key_param, $resno_param ) };
        %$poll_bad = %{ $iApp->load_poll( $key_param, $resno_param ) };
    }

    defined $HTML::TagClound::VERSION or require HTML::TagCloud;
    defined &{'Img0ch::Upload::Tag::new'} or require Img0ch::Upload::Tag;
    my $my_tags_stack   = [];
    my $tag_cloud_stack = [];
    my $level           = $iConfig->get_int('tagcloud.level') || 24;
    my $max             = $iConfig->get_int('tagcloud.max') || 50;
    my $ip              = $iRequest->ip();
    my $cloud           = HTML::TagCloud->new( levels => $level );
    my $iUTag           = Img0ch::Upload::Tag->new($iBBS);
    my $tags_data       = $iUTag->get( $key_param, $resno_param );
    my $uri             = join '/', $iRequest->get_app_uri(), 'img0ch-sf.cgi',
        $iBBS->get_name();

    while ( my ( $name, $info ) = each %$tags_data ) {
        $cloud->add( $name, join( '/', $uri, $iRequest->escape_uri($name) ),
            $info->[0] );
        scalar grep { $_ eq $ip } @{ $info->[1] }
            and push @$my_tags_stack, $name;
    }
    for my $tag ( $cloud->tags($max) ) {
        push @$tag_cloud_stack,
            {
            count => $tag->{count},
            level => $tag->{level},
            name  => $tag->{name},
            url   => $tag->{url},
            };
    }

    $iTemplate->param(
        {   AddressIsInvalid => $process->{address_is_invalid},
            Banner           => $iMeta->main_banner(),
            DisplayPolledIP  => ( $iConfig->get('ufm.no_polled_ip') ? 0 : 1 ),
            Freezed          => $process->{freezed},
            KEY              => $key_param,
            META             => $iMeta->meta(),
            MyTags   => join( ' ', sort @$my_tags_stack ),
            Password => $pass_param,
            Removed  => $removed,
            Failed =>
                ( $pass_param and !exists $process->{send_ok} and !$removed ),
            Resno         => $resno_param,
            SendOK        => $process->{send_ok},
            SendErrstr    => $process->{send_errstr},
            TagCloud      => $tag_cloud_stack,
            TagCloudHTML  => $cloud->html($max),
            TagCloudCSS   => $cloud->css(),
            TagCloudLevel => [ map {$_} ( 1 .. $level ) ],
            TagMaxCount  => ( $iConfig->get('ufm.tag_max_count')  || 10 ),
            TagMaxLength => ( $iConfig->get('ufm.tag_max_length') || 32 ),
            Threads      => $stack,
            %$poll_rate,
            %$poll_bad,
            %$status,
        }
    );

    return ( 1, $iTemplate, 'text/html', undef, );
}

sub redirect_error {
    my ( $iApp, $reason ) = @_;
    my $iKernel  = $iApp->{__kernel};
    my $iRequest = $iApp->{__request};
    my $key      = $iRequest->key();
    my $ip       = $iRequest->ip_int();

    defined &{'Img0ch::Error::new'} or require Img0ch::Error;
    my $iError = Img0ch::Error->new( $iKernel, $key, $ip );
    my $long = $iError->get( $iApp->{__error} || 'INVALID_POST' );

    if ( $iRequest->param('js') ) {
        $iRequest->send_http_header( 'text/javascript', 'UTF-8' );
        $iRequest->print(
            $iRequest->get_json( { ok => 0, error => $reason } ) );
    }
    else {
        my $iTemplate = Img0ch::Template->new(
            ( $iApp->{__bbs} || $iKernel ),
            {   file    => ('error'),
                request => $iRequest,
                setting => $iApp->{__setting},
            }
        );
        $iTemplate->param(
            {   Banner       => '',
                Flag         => 0,
                LongErrorStr => $long,
            }
        );
        my $iConfig = $iApp->{__config};
        my $charset = $iConfig->get('DefaultCharset');
        $iRequest->send_http_header( 'text/html', $charset );
        $iTemplate->flush();
    }
}

1;
__END__
