# vim: set cindent expandtab ts=4 sw=4:
#
# Copyright (c) 1998-2005 Chi-Keung Ho. All rights reserved.
#
# This programe is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# Extmail - a high-performance webmail to maildir
# $Id$
package Ext::App::Pref;
use strict;
use Exporter;

use vars qw(@ISA @EXPORT);
@ISA = qw(Exporter Ext::App);
use Ext::App;
use Ext::Config;
use Ext::Utils;
use Fcntl qw(:flock);
use Ext::POP3;
use MIME::Base64;

use vars qw(%lang_pref $lang_charset); # load locale
use Ext::Lang; # locale handler

sub init {
    my $self = shift;
    $self->SUPER::init(@_);
    return unless($self->valid||$self->permit);

    $self->add_methods(pref_list => \&pref_list);
    $self->add_methods(pref_save => \&pref_save);
    $self->add_methods(pop3_list => \&pop3_list);
    $self->add_methods(pop3_save => \&pop3_save);

    $self->{default_mode} = 'pref_list';
    Ext::Storage::Maildir::init($self->get_working_path);

    $self->_initme;
    $self;
}

sub _initme {
    initlang($_[0]->userconfig->{lang}, __PACKAGE__);
    $_[0]->{tpl}->assign( lang_charset => $lang_charset );
    $_[0]->{tpl}->assign( \%lang_pref );
}

sub pref_list {
    my $self = shift;
    my $tpl = $self->{tpl};
    my $q = $self->{query};

    # 2005-09-07, bug fix
    # here we should use SUPER->userconfig to get user.cf,
    # if user.cf not exist, SUPER->userconfig will initialize
    # varibles, old code does not handle this procedure, local
    # $CFG to be compatible with other code, reduce typing :-)
    #
    # my $config = Ext::Config->new(file => $ENV{MAILDIR}.'/user.cf');
    my $CFG = $self->userconfig;

    # localize init the lang, because App level will asign the lang
    # undefined unless end user save preference the first time, so
    # we must initialize it, ouch XXX FIXME
    $CFG->{lang} = $CFG->{lang} ? $CFG->{lang} : guess_intl();

    $tpl->assign(
        #SID => $q->cgi('sid'),
        NICK_NAME => $CFG->{nick_name},
        FULL_HEADER => $CFG->{full_header},
        CCSENT => $CFG->{ccsent},
        TRYLOCAL => $CFG->{trylocal},
        SHOW_HTML => $CFG->{show_html},
        COMPOSE_HTML => $CFG->{compose_html},
        CONV_LINK => $CFG->{conv_link},
        ADDR2ABOOK => $CFG->{addr2abook},
        POP_ON => $CFG->{pop_on}, # XXX pop3
    );

    my $pref_page_size = pref_page_size();
    my $pref_sort = pref_sort();
    my $pref_lang = $self->pref_lang(); # method
    my $pref_theme = $self->pref_theme(); # method
    my $pref_poptimeout = $self->pref_poptimeout(); # method
    my $pref_popfiles = $self->pref_popfiles();

    # show prefer screen type
    foreach(@{$self->list_screen}) {
        my $selected = 0;
        $selected = 1 if ($CFG->{screen} eq $_);
        $tpl->assign(
            'LOOP_SCREEN',
            SCREEN_TYPE => $_,
            SCREEN_NAME => $self->get_screen($_)->[2],
            SCREEN_CHK => $selected,
        );
    }

    # show prefer page size
    foreach(@$pref_page_size ) {
        my $selected = 0;
        $selected = 1 if($CFG->{page_size} eq $_);
        $tpl->assign(
            'LOOP_PAGE_SIZE',
            PAGE_SIZE => $_,
            PAGE_SIZE_CHK => $selected,
        );
    }

    # show timezone
    my $pref_timezone = pref_timezone();
    foreach my $t (@$pref_timezone) {
        my $selected = 0;
        $selected = 1 if($CFG->{timezone} eq $t->{'value'});
        $tpl->assign(
            'LOOP_TIMEZONE',
            TIMEZONE_VALUE => $t->{value},
            TIMEZONE_NAME => $t->{key},
            TIMEZONE_CHK => $selected,
        );
    }

    # show sort order by default
    foreach (keys %$pref_sort) {
        my $selected = 0;
        $selected = 1 if($CFG->{sort} eq $pref_sort->{$_});
        $tpl->assign(
            'LOOP_SORT',
            SORT_NAME => $lang_pref{$_},
            SORT_VAL => $pref_sort->{$_},
            SORT_CHK => $selected,
        );
    }

    # show prefer language, for template language
    foreach (@$pref_lang) {
        my $selected = 0;
        $selected = 1 if ($CFG->{lang} eq $_->{lang});
        $tpl->assign(
            'LOOP_LANG',
            LANG => $_->{lang},
            LANG_DESC => $_->{desc},
            LANG_CHK => $selected,
        );
    }

    # show prefer template
    foreach (sort keys %$pref_theme) {
        my $selected = 0;
        my $desc = $pref_theme->{$_}->{$CFG->{lang}} || $_;
        $selected = 1 if ($CFG->{template} eq $_);
        $tpl->assign(
            'LOOP_THEME',
            THEME => $_,
            THEME_DESC => $desc,
            THEME_CHK => $selected,
        );
    }

    # show pop3 relate setting
    foreach (@$pref_poptimeout) {
        my $selected = 0;
        $selected = 1 if ($CFG->{pop_timeout} eq $_);
        $tpl->assign(
            'LOOP_POPTIMEOUT',
            TIMEOUT => $_,
            TIMEOUT_CHECK => $selected,
        );
    }

    foreach (@$pref_popfiles) {
        my $selected = 0;
        $selected = 1 if ($CFG->{pop_files} eq $_);
        $tpl->assign(
            'LOOP_POPFILES',
            POPFILES => $_,
            POPFILES_CHECK => $selected,
        );
    }

    $tpl->assign(
        SIGNATURE => read_signature()
    );
}

# pref_save must be called before any tpl was assign and output
# or some of tpl will have different value.
sub pref_save {
    my $self = shift;
    my $tpl = $self->{tpl};
    my $q = $self->{query};
    my $user_config = './user.cf';
    my $config = Ext::Config->new(file => $user_config);
    my $CFG = $config->dump || {};
    my $oldconfig = $self->userconfig;

    $CFG->{nick_name} = $q->cgi('nick_name');
    $CFG->{full_header} = $q->cgi('full_header')? 1:0;
    $CFG->{ccsent} = $q->cgi('ccsent')? 1:0;
    $CFG->{trylocal} = $q->cgi('trylocal') eq 1? 1:0;
    $CFG->{show_html} = $q->cgi('show_html') ? 1:0;
    $CFG->{compose_html} = $q->cgi('compose_html') ?1:0;
    $CFG->{conv_link} = $q->cgi('conv_link') ? 1:0;
    $CFG->{addr2abook} = $q->cgi('addr2abook') ? 1:0;
    $CFG->{pop_on} = $q->cgi('pop_on') ? 1:0;
    $CFG->{pop_timeout} = $q->cgi('pop_timeout');
    $CFG->{pop_files} = $q->cgi('pop_files');

    $CFG->{page_size} = $q->cgi('page_size');
    $CFG->{screen} = $q->cgi('screen_type');
    $CFG->{timezone} = $q->cgi('timezone');
    $CFG->{sort} = $q->cgi('sort') || 'by_time';
    $CFG->{lang} = $q->cgi('lang');
    $CFG->{template} = $q->cgi('template');
    $CFG->{charset} = 'UTF-8'; # XXX :-) now everything is UTF8

    $config->{cfg} = $CFG; # XXX without it, newly create
                           # user.cf would be save correctly!
    $config->save(file => $user_config);
    save_signature($q->cgi('signature'));
    undef $config;

    # new update style mechanism - 2005-09-26, assign a refresh
    # flag to notice template to force update the whole world
    if($oldconfig->{template} ne $q->cgi('template') or
       $oldconfig->{lang} ne $q->cgi('lang')) {
        $tpl->assign(REFRESH => 1);
    }

    # after save all, force userconfig() to update cache to
    # currently locale, XXX
    $self->userconfig(1);

    # re-initialize the world
    $self->global_tpl;
    $self->_initme;

    # call password after re-initialize
    $self->pref_change_passwd;
    $self->pref_list;

    # if you want to redirect, uncomment the following code
    #$self->{tpl}->{noprint} = 1;
    #$self->redirect("?__mode=pref_list&sid=".$q->cgi('sid'));
}

sub pref_change_passwd {
    my $self = shift;
    my $q = $self->{query};
    my $tpl = $self->{tpl};
    my $sys = $self->{sysconfig};

    return unless(my $oldpwd = $q->cgi('oldpw'));

    my $pass_fail = $lang_pref{'change_passwd_fail'};
    my $pass_ok = $lang_pref{'change_passwd_ok'};
    my $pass_short = $lang_pref{'change_passwd_short'};

    if($q->cgi('newpw1') eq $q->cgi('newpw2')) {
        my $newpwd = $q->cgi('newpw1');
        return unless($self->pre_auth); # prepare auth

        # check new password length
        if(length($newpwd) < $sys->{SYS_MIN_PASS_LEN}) {
            $pass_short = sprintf($pass_short, $sys->{SYS_MIN_PASS_LEN});
            $tpl->assign( CHGPWD_FAIL => $pass_short);
            return;
        }

        if ($ENV{OPTIONS} =~ /disablepwdchange/) {
            $tpl->assign( CHGPWD_FAIL => $lang_pref{'change_passwd_disabled'} );
            return;
        }

        my $auth = $self->{auth_handler};
        if($auth->change_passwd($ENV{USERNAME}, $oldpwd, $newpwd)) {
            $tpl->assign( CHGPWD_OK => $pass_ok );
        }else {
            $tpl->assign( CHGPWD_FAIL => $pass_fail );
        }
    }else {
        $tpl->assign( CHGPWD_FAIL => $pass_fail );
    }
}

sub pref_page_size {
    my @pref_page_size = (10,20,50,100);
    \@pref_page_size;
}

sub pref_sort {
    my %sort_order = (
        'by_time' => 'Ts',
        'by_date' => 'Dt',
        'by_size' => 'Sz',
        'by_from'=> 'Fr',
        'by_subject' => 'Sj',
        'by_status' => 'Fs');
    \%sort_order;
}

sub pref_lang {
    my $self = shift;
    my $ref = langlist(); # func in Ext::Lang
    $ref;
}

sub pref_theme {
    my $self = shift;
    my $html_dir = $self->{sysconfig}->{SYS_TEMPLDIR};
    my %theme;

    opendir(DIR, $html_dir) or die "Can't opendir $html_dir, $!\n";
    foreach my $d ( grep { !/^\./ } readdir DIR) {
        if(-d "$html_dir/$d" && -r "$html_dir/$d/README") {
            open(FD, "< $html_dir/$d/README"); # ignore error
            while(<FD>) {
                next if(/^#/); # ignore comments
                chomp;
                my($i18n, $desc) = (/([^:]+):(.*)/);
                $theme{$d}->{$i18n} = ($desc?$desc:$i18n);
            }
            close FD;
        }
    }
    close DIR;
    \%theme;
}

sub save_signature {
    my $s = shift;
    open (FD, "> signature.cf") or die "Can't write to signature.cf, $!\n";
    print FD $s;
    close FD;
}

sub read_signature {
    return "" if (!-r 'signature.cf');
    open (FD, "< signature.cf"); # ignore error
    local $/ = undef;
    my $buf = <FD>;
    close FD;
    return $buf;
}

sub pop3_list {
    my $self = shift;
    my $tpl = $self->{tpl};
    my $q = $self->{query};

    my $arr = parse_pop3config();
    if (scalar @$arr) {
        my $num = 1;
        for my $hash (@$arr) {
            $tpl->assign(
                'POP3LIST_LOOP',
                NUM => $num,
                POP3_UID => $hash->{uid},
                POP3_BACKUP => ($hash->{backup} eq 'on' ? 1 : 0),
                POP3_ACTIVE => ($hash->{active} eq 'on' ? 1 : 0),
                POP3_SERVER => "$hash->{host}:$hash->{port}",
            );
            $num ++;
        }
    } else {
        $tpl->assign( NOPOP3DEF => 'No pop3 accounts defined!');
    }

    if (my $uid = $q->cgi('edit')) {
        my $edit = '';
        for my $hash (@$arr) {
            next if (lc $hash->{uid} ne lc $uid);
            $edit = $hash;
            last;
        }

        if ($edit) {
            $tpl->assign(
                EDIT_ACCOUNT => 1,
                EDIT_UID => $edit->{uid},
                EDIT_HOST => $edit->{host},
                EDIT_PORT => $edit->{port},
                EDIT_BACKUP => $edit->{backup} eq 'on' ? 1 : 0,
                EDIT_ACTIVE => $edit->{active} eq 'on' ? 1 : 0,
            );
        } else {
            $tpl->assign( ERROR => "No such $uid" );
        }
    }

    $tpl->{template} = 'pref_pop3.html';
    1;
}

sub pop3_save {
    my $self = shift;
    my $tpl = $self->{tpl};
    my $q = $self->{query};
    my $arr = parse_pop3config();
    my $err = '';

    my $uid = $q->cgi('username');
    my $pass = $q->cgi('passwd');
    my $host = $q->cgi('host');
    my $port = $q->cgi('port');
    my $backup = $q->cgi('backup');
    my $active = $q->cgi('active');

    # fallback setting;
    if (!$host or !$host =~ /[^\.]/) {
        $uid =~ /@(.*)$/;
        if (my $domain = $1) {
            $host = "pop3.$domain";
        }
    }

    if (!$port =~ /^\d+$/) {
        $port = '110';
    }

    if ($q->cgi('doedit')) {
        if (scalar @$arr) {
            my $write = 0;

            if (!($uid =~ /^[^@]+@\S+$/) || !$pass) {
                $err = 'Username or password incomplete';
            }

            # check for modification to the specific entry
            for (my $i=0; $i < scalar @$arr; $i++) {
                my $h = $arr->[$i];
                if ( lc $h->{uid} eq lc $uid ) {
                    if (length $pass) {
                        $pass = encode_base64($pass);
                        $pass =~ s/[\r\n\s]+//g;
                        $h->{passwd} = $pass;
                    } else {
                        $h->{passwd} = encode_base64($h->{passwd});
                        $h->{passwd} =~ s/[\r\n\s]+//g;
                    }

                    $h->{uid} = $uid;
                    $h->{host} = $host;
                    $h->{port} = $port || '110';
                    $h->{backup} = $backup ? 'on' : 'off';
                    $h->{active} = $active ? 'on' : 'off';

                    $arr->[$i] = $h;
                    $write = 1;
                } else {
                    $h->{passwd} = encode_base64($h->{passwd});
                    $h->{passwd} =~ s/[\r\n\s]+//g;
                    $arr->[$i] = $h;
                }
            }

            if ($write) {
                open (FD, "> pop3config.cf")
                    or die "Can't write to pop3config.cf, $!\n";
                flock (FD, LOCK_EX);
                for my $h (@$arr) {
                    my $option ='';
                    $option .= 'backup='.($h->{backup} eq 'on' ? 'on' : 'off').',';
                    $option .= 'active='.($h->{active} eq 'on' ? 'on' : 'off').',';
                    $option .= 'color=cccccc'; # XXX FIXME

                    print FD "$h->{uid} $h->{passwd} $h->{host} $h->{port} $option\n";
                }
                flock (FD, LOCK_UN);
                close FD;
            } else {
                $err = 'Request not permited';
            }
        } else {
            $err = 'Empty pop3config, should add new entry first';
        }
    } elsif ($q->cgi('doadd')) {
        if (my $total = scalar @$arr) {
            if ($total >= 6) {
                $err = 'Can only add max 6 entries, abort';
            }elsif (!($uid=~/^[^@]+@\S+$/) || !$pass) {
                $err = 'Filled information incomplete';
            }

            for my $hash (@$arr) {
                if (lc $hash->{uid} eq lc $uid) {
                    $err = "$uid exists!";
                    last;
                }
            }

            if (!$err) {
                open (FD, ">> ./pop3config.cf") or die "$!\n";
                flock (FD, LOCK_EX);
                $pass = encode_base64($pass);
                $pass =~ s/[\r\n\s]+//g;
                my $option ='';

                $option .= 'backup='.($backup? 'on' : 'off').',';
                $option .= 'active='.($active? 'on' : 'off').',';
                $option .= 'color=cccccc'; # XXX FIXME

                print FD "$uid $pass $host $port $option\n";
                flock (FD, LOCK_UN);
                close FD;
            }
        } else {
            if (!($uid =~ /^[^@]+@\S+$/) || !$pass) {
                $err = 'Username or password incomplete';
            } else {
                # new add
                open (FD, " > ./pop3config.cf") or die "$!\n";
                flock (FD, LOCK_EX);
                $pass = encode_base64($pass);
                $pass =~ s/[\r\n\s]+//g;
                my $option = '';

                $option .= 'backup='.($backup? 'on' : 'off').',';
                $option .= 'active='.($active? 'on' : 'off').',';
                $option .= 'color=cccccc'; # XXX FIXME

                print FD "$uid $pass $host $port $option\n";
                flock (FD, LOCK_UN);
                close FD;
            }
        }
    } elsif ($q->cgi('dodelete')) {
        my $a = $q->cgi_full_names;
        my @del = grep { /^REMOVE-/ } @$a;

        open (FD, " > ./pop3config.cf") or die "$!\n";
        flock (FD, LOCK_EX);
        for my $h (@$arr) {
            my $remove = 0;
            for my $d (@del) {
                if (lc $h->{uid} eq lc $q->cgi($d)) {
                    $remove= 1;
                    last;
                }
            }
            if (!$remove) {
                $h->{passwd} = encode_base64($h->{passwd});
                $h->{passwd} =~ s/[\r\n\s]+//g;
                my $option = '';

                $option .= 'backup='.($h->{backup} eq 'on' ? 'on' : 'off').',';
                $option .= 'active='.($h->{active} eq 'on' ? 'on' : 'off').',';
                $option .= 'color=cccccc'; # XXX FIXME

                print FD "$h->{uid} $h->{passwd} $h->{host} $h->{port} $option\n";
            }
        }
        flock (FD, LOCK_UN);
        close FD;
    }
    if ($err) {
        $tpl->assign(ERRMSG => $err);
    }
    $self->pop3_list;
}

sub pref_timezone {
    my @t = (
        {key => '+13:00', value => '+1300'},
        {key => '+12:00', value => '+1200'},
        {key => '+11:00', value => '+1100'},
        {key => '+10:00', value => '+1000'},
        {key => '+09:00', value => '+0900'},
        {key => '+08:00', value => '+0800'},
        {key => '+07:00', value => '+0700'},
        {key => '+06:00', value => '+0600'},
        {key => '+05:00', value => '+0500'},
        {key => '+04:00', value => '+0400'},
        {key => '+03:00', value => '+0300'},
        {key => '+02:00', value => '+0200'},
        {key => '+01:00', value => '+0100'},
        {key => '00:00', value => '+0000'},
        {key => '-01:00', value => '-0100'},
        {key => '-02:00', value => '-0200'},
        {key => '-03:00', value => '-0300'},
        {key => '-04:00', value => '-0400'},
        {key => '-05:00', value => '-0500'},
        {key => '-06:00', value => '-0600'},
        {key => '-07:00', value => '-0700'},
        {key => '-08:00', value => '-0800'},
        {key => '-09:00', value => '-0900'},
        {key => '-10:00', value => '-1000'},
        {key => '-11:00', value => '-1100'},
    );
    \@t;
}

sub pref_poptimeout {
    shift;
    ['15','30'];
}

sub pref_popfiles {
    shift;
    ['15','30','50','100'];
}

sub pre_run { 1 }

sub post_run {
    my $template = $_[0]->{query}->cgi('screen') || $_[0]->{tpl}->{template} || 'pref.html';
    reset_working_path();
    $_[0]->{tpl}->process($template);
    $_[0]->{tpl}->print;
}

1;


syntax highlighted by Code2HTML, v. 0.9.1