# 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.
#
# ExtMan - web interface to manage virtual accounts
# $Id$
use strict;
use DBI;

package Ext::Mgr::MySQL;
use Exporter;
use Ext::Mgr;
use POSIX qw(strftime);
use vars qw(@ISA @EXPORT);
@ISA = qw(Exporter Ext::Mgr);
@EXPORT = qw(auth);

sub new {
    my $this = shift;
    my $self = bless {@_}, ref $this || $this;
    $self->init(@_);
    $self;
}

sub init {
    my $self = shift;
    my %opt = @_;

    $opt{host} = '127.0.0.1' if not defined $opt{host};
    $opt{dbname} = 'extmail_db' if not defined $opt{dbname};
    $opt{dbuser} = 'root' if not defined $opt{dbuser};
    $opt{dbpw} = 'password' if not defined $opt{dbpw};

    $self->{opt}=\%opt;

    my $connect = "DBI:mysql:database=$opt{dbname};host=$opt{host}";
    if ($opt{socket}) {
        $connect .= ";mysql_socket=$opt{socket}";
    }
    my $dbh = DBI->connect(
        $connect,$opt{dbuser}, $opt{dbpw}, {'RaiseError' => 0}
    );

    $self->{dbh} = $dbh;
    $self->{psize} = $opt{psize} || 10;
    $self->{crypt_type} = $opt{crypt_type} || 'crypt'; # default type
}

sub search {
    my $self = shift;
    my $filter = $_[0];
    my %res = ();
    my $username = $self->{opt}->{'table_attr_username'};
    my $SQL = "SELECT * FROM $self->{opt}->{table} WHERE $username='$filter'";
    my $sth = $self->{dbh}->prepare($SQL);

    $sth->execute();
    while(my $r=$sth->fetchrow_hashref()) {
        $res{$r->{$username}} = $r; # feedback all rows
    };
    $sth->finish();
    \%res; # return a REF
}

sub auth {
    my $self = shift;
    my ($username, $password) = (@_);
    my $res = $self->search($username);

    if(scalar keys %$res) {
        my $pwd = $res->{$username}->{$self->{opt}->{'table_attr_passwd'}};

        # this step is a must, or null userpassword record will cause hole
        # that anonymous can step in the system
        return 0 unless($password && $pwd);

        if($self->verify($password, $pwd)) {
            $self->{INFO} = $self->_fill_user_info($res->{$username});
            return 1;
        }else {
            return 0;
        }
    }

    0; # default ?:)
}

sub change_passwd {
    my $self = shift;
    my ($username, $old, $new) = @_;

    # verify old password
    if($self->auth($username, $old)) {
        # encrypt new password and update it
        my $crypted_new = $self->encrypt($self->{crypt_type}, $new);
        my $table = $self->{opt}->{table};
        my $attr_pw = $self->{opt}->{table_attr_passwd};
        my $clearpw = $self->{opt}->{table_attr_clearpw};
        my $attr_un = $self->{opt}->{table_attr_username};

        my $SQL = "UPDATE $table set $attr_pw='$crypted_new'";
        if ($clearpw) {
            $SQL .=",$clearpw='$new'";
        }
        $SQL .= " WHERE $attr_un='$username'";

        my $sth = $self->{dbh}->prepare($SQL);
        $sth->execute;
        $sth->finish();
        return 1;
    }else {
        return 0;
    }
}

sub _fill_user_info {
    my $self = shift;
    my $opt = $self->{opt};
    my $entry = $_[0];
    my %info = ();

    # original infomation filling
    foreach my $key (keys %$entry) {
        $info{$key} = $entry->{$key};
    }

    $info{TYPE} = $info{'type'};

    \%info;
}

sub get_entry {
    my $self = shift;
    my $sth = $self->{dbh}->prepare($_[0]);
    $sth->execute();
    $sth->fetchrow_hashref(); # the first entry if multiplies return
}

sub get_entries {
    my $self = shift;
    my $sth = $self->{dbh}->prepare($_[0]);
    my $arr = [];

    $sth->execute();
    while (my $r=$sth->fetchrow_hashref()) {
        push @$arr, $r;
    }
    $arr;
}

#==========================#
# extmailUser land handler #
#==========================#

sub by_domain {
    lc $a->{domain} cmp lc $b->{domain};
}

sub by_username {
    lc $a->{username} cmp lc $b->{username};
}

sub by_alias {
    lc $a->{address} cmp lc $b->{address};
}

sub by_manager {
    lc $a->{username} cmp lc $b->{username};
}

sub get_users_list {
    my $self = shift;
    my $SQL = "SELECT * FROM mailbox WHERE domain='$_[0]'";
    my $rs = $self->get_entries($SQL);
    my $arr = []; # null ARRAY ref
    foreach my $ref (sort by_username @$rs) {
        push @$arr, {
            mail => $ref->{username},
            cn => $ref->{name},
            domain => $ref->{domain},
            uidnumber => $ref->{uidnumber},
            gidnumber => $ref->{gidnumber},
            uid => $ref->{uid},
            netdiskquota => $ref->{netdiskquota},
            active => $ref->{active} ? 1 : 0,
            quota => $ref->{quota},
            passwd => $ref->{password},
            clearpw => $ref->{clearpwd},
            mailhost => $ref->{mailhost},
            maildir => $ref->{maildir},
            homedir => $ref->{homedir},
            expire => $ref->{expiredate},
            create => $ref->{createdate},
            disablepwdchange => $ref->{disablepwdchange} ? 1 : 0,
            disablesmtpd => $ref->{disablesmtpd},
            disablesmtp => $ref->{disablesmtp},
            disablewebmail => $ref->{disablewebmail},
            disablenetdisk => $ref->{disablenetdisk},
            disableimap => $ref->{disableimap},
            disablepop3 => $ref->{disablepop3},
        }
    }
    scalar @$arr ? $arr : undef;
}

sub get_domains_list {
    my $self = shift;
    my $SQL = 'SELECT * FROM domain';
    my $rs = $self->get_entries($SQL);

    my $arr = [];
    foreach my $ref ( sort by_domain @$rs ) {
        push @$arr, {
            domain => $ref->{domain},
            create => $ref->{createdate},
            expire => $ref->{expiredate},
            description => $ref->{description},
            hashdirpath => $ref->{hashdirpath},
            maxalias => $ref->{maxalias},
            maxusers => $ref->{maxusers},
            maxquota => $ref->{maxquota},
            maxndquota => $ref->{maxnetdiskquota},
            transport => $ref->{transport},
            can_signup => $ref->{can_signup},
            default_quota => $ref->{default_quota},
            default_ndquota => $ref->{default_netdiskquota},
            default_expire => $ref->{default_expire},
            disablesmtpd => $ref->{disablesmtpd},
            disablesmtp => $ref->{disablesmtp},
            disablewebmail => $ref->{disablewebmail},
            disablenetdisk => $ref->{disablenetdisk},
            disableimap => $ref->{disableimap},
            disablepop3 => $ref->{disablepop3},
            active => $ref->{active} ? 1 : 0,
        }
    }
    scalar @$arr ? $arr : undef;
}

sub get_aliases_list {
    my $self = shift;
    my $SQL = "SELECT * FROM alias where domain='$_[0]'";
    my $rs = $self->get_entries($SQL);
    my $arr = [];
    foreach my $ref ( sort by_alias @$rs ) {
        my $goto = $ref->{goto}; # XXX
        push @$arr, {
            alias => $ref->{address},
            domain => $ref->{domain},
            goto => ($goto =~ m!,!) ? [split(/,/,$goto)] : $goto,
            active => $ref->{active} ? 1 : 0,
            create => $ref->{createdate},
            expire => $ref->{expiredate},
        }
    }
    scalar @$arr ? $arr : undef;
}

sub get_managers_list {
    my $self = shift;
    my $SQL = 'SELECT * FROM manager';
    my $rs = $self->get_entries($SQL);
    my $arr = [];

    foreach my $ref (sort by_manager @$rs) {
        push @$arr, {
            manager => $ref->{username},
            cn => $ref->{name},
            question => $ref->{question},
            answer => $ref->{answer},
            disablepwdchange => $ref->{disablepwdchange},
            create => $ref->{createdate},
            expire => $ref->{expiredate},
            type => $ref->{type},
            passwd => $ref->{password},
            active => $ref->{active} ? 1 : 0,
        }
    }
    scalar @$arr ? $arr : undef;
}

sub add_user {
    my $self = shift;
    my %opt = @_;
    my $db = $self->{dbh};

    my $ctype = $self->{crypt_type};
    my $passwd = $self->encrypt($ctype, $opt{passwd});
    my $clearpw = ($self->{opt}->{'table_attr_clearpw'} ? $opt{passwd} : '');
    my $active = $opt{active} ? 1 : 0;

    $db->do("INSERT into mailbox(
            username,
            uid,
            password,
            clearpwd,
            name,
            mailhost,
            maildir,
            homedir,
            quota,
            netdiskquota,
            domain,
            uidnumber,
            gidnumber,
            createdate,
            expiredate,
            active,
            disablepwdchange,
            disablesmtpd,
            disablesmtp,
            disablewebmail,
            disablenetdisk,
            disableimap,
            disablepop3) VALUES(
            '$opt{mail}',
            '$opt{uid}',
            '$passwd',
            '$clearpw',
            '$opt{cn}',
            '$opt{mailhost}',
            '$opt{maildir}',
            '$opt{homedir}',
            '$opt{quota}',
            '$opt{netdiskquota}',
            '$opt{domain}',
            '$opt{uidnumber}',
            '$opt{gidnumber}',
            '$opt{create}',
            '$opt{expire}',
            '$active',
            '$opt{disablepwdchange}',
            '$opt{disablesmtpd}',
            '$opt{disablesmtp}',
            '$opt{disablewebmail}',
            '$opt{disablenetdisk}',
            '$opt{disableimap}',
            '$opt{disablepop3}'
            )"
    );
    if ($db->err) {
        return $db->errstr;
    } else {
        return 0;
    }
}

sub add_domain {
    my $self = shift;
    my %opt = @_;
    my $db = $self->{dbh};

    my $active = $opt{active} ? 1 : 0;
    $db->do("INSERT into domain(
            domain,
            description,
            hashdirpath,
            maxalias,
            maxusers,
            maxquota,
            maxnetdiskquota,
            transport,
            can_signup,
            default_quota,
            default_netdiskquota,
            default_expire,
            disablesmtpd,
            disablesmtp,
            disablewebmail,
            disablenetdisk,
            disableimap,
            disablepop3,
            createdate,
            expiredate,
            active) VALUES(
            '$opt{domain}',
            '$opt{description}',
            '$opt{hashdirpath}',
            '$opt{maxalias}',
            '$opt{maxusers}',
            '$opt{maxquota}',
            '$opt{maxndquota}',
            '$opt{transport}',
            '$opt{can_signup}',
            '$opt{default_quota}',
            '$opt{default_ndquota}',
            '$opt{default_expire}',
            '$opt{disablesmtpd}',
            '$opt{disablesmtp}',
            '$opt{disablewebmail}',
            '$opt{disablenetdisk}',
            '$opt{disableimap}',
            '$opt{disablepop3}',
            '$opt{create}',
            '$opt{expire}',
            '$active'
            )"
    );
    if ($db->err) {
        return $db->errstr;
    } else {
        return 0;
    }
}

sub add_alias {
    my $self = shift;
    my %opt = @_;
    my $db = $self->{dbh};

    my $active = $opt{active} ? 1 : 0;
    my $goto = join(',', split(/\n/, $opt{goto}));

    $db->do("INSERT into alias(
            address,
            goto,
            domain,
            createdate,
            expiredate,
            active) VALUES(
            '$opt{alias}',
            '$goto',
            '$opt{domain}',
            '$opt{create}',
            '$opt{expire}',
            '$active'
            )"
    );
    if ($db->err) {
        return $db->errstr;
    } else {
        return 0;
    }
}

sub add_manager {
    my $self = shift;
    my %opt = @_;
    my $db = $self->{dbh};

    my $ctype = $self->{crypt_type};
    my $passwd = $self->encrypt($ctype, $opt{passwd});

    my $active = $opt{active} ? 1 : 0;
    $db->do("INSERT into manager(
                username,
                password,
                type,
                uid,
                name,
                question,
                answer,
                disablepwdchange,
                createdate,
                expiredate,
                active) VALUES(
                '$opt{manager}',
                '$passwd',
                '$opt{type}',
                '',
                '$opt{cn}',
                '$opt{question}',
                '$opt{answer}',
                '$opt{disablepwdchange}',
                '$opt{create}',
                '$opt{expire}',
                '$active'
                )"
    );
    return $db->errstr if ($db->err);

    foreach my $vd (split(/\s+/, $self->mshack($opt{domain}))) {
        $db->do("INSERT into domain_manager(
                username,
                domain,
                createdate,
                active) VALUES(
                '$opt{manager}',
                '$vd',
                '$opt{create}',
                '1'
                )"
        );
        return $db->errstr if ($db->err);
    }
    0; # success
}

sub delete_user {
    my $self = shift;
    my $user = $_[0];
    my $db = $self->{dbh};
    my ($domain) = ($user =~ m!.*@(.*)!);

    $db->do("DELETE FROM mailbox where username='$user'");
    if ($db->err) {
        return $db->errstr;
    } else {
        return 0;
    }
}

sub delete_alias {
    my $self = shift;
    my $db = $self->{dbh};

    $db->do("DELETE FROM alias where address='$_[0]'");
    if ($db->err) {
        return $db->errstr;
    } else {
        return 0;
    }
}

sub delete_domain {
    my $self = shift;
    my $db = $self->{dbh};

    $db->do("DELETE FROM domain where domain='$_[0]'");
    if ($db->err) {
        return $db->errstr;
    } else {
        return 0;
    }
}

sub delete_manager {
    my $self = shift;
    my $db = $self->{dbh};

    $db->do("DELETE FROM manager WHERE username='$_[0]'");
    return $db->errstr if ($db->err);

    $db->do("DELETE FROM domain_manager WHERE username='$_[0]'");
    if ($db->err) {
        return $db->errstr;
    } else {
        return 0;
    }
}

sub modify_user {
    my $self = shift;
    my %opt = @_;
    my $db = $self->{dbh};
    my $active = $opt{active} ? 1 : 0;

    if ($opt{passwd}) {
        my $ctype = $self->{crypt_type};
        my $passwd = $self->encrypt($ctype, $opt{passwd});
        my $SQL = "UPDATE mailbox set password='$passwd'";
        if ($self->{opt}->{'table_attr_clearpw'}) {
            $SQL .= ",clearpwd='$opt{passwd}'";
        }
        $SQL .=" WHERE username='$opt{user}'";

        $db->do($SQL);
        return $db->errstr if ($db->err);
    }

    $db->do("UPDATE mailbox set
            name='$opt{cn}',
            quota='$opt{quota}',
            netdiskquota='$opt{netdiskquota}',
            uidnumber='$opt{uidnumber}',
            gidnumber='$opt{gidnumber}',
            expiredate='$opt{expire}',
            active='$active',
            disablepwdchange='$opt{disablepwdchange}',
            disablesmtpd='$opt{disablesmtpd}',
            disablesmtp='$opt{disablesmtp}',
            disablewebmail='$opt{disablewebmail}',
            disablenetdisk='$opt{disablenetdisk}',
            disableimap='$opt{disableimap}',
            disablepop3='$opt{disablepop3}' WHERE username='$opt{user}'"
    );
    if ($db->err) {
        return $db->errstr;
    } else {
        return 0;
    }
}

sub modify_alias {
    my $self = shift;
    my %opt = @_;
    my $db = $self->{dbh};

    my $active = $opt{active} ? 1 : 0;
    my $goto = join(',', split(/\n/, $opt{goto}));

    $db->do("UPDATE alias set
            goto='$goto',
            expiredate='$opt{expire}',
            active='$active' WHERE address='$opt{alias}'"
    );
    if ($db->err) {
        return $db->errstr;
    } else {
        return 0;
    }
}

sub modify_domain {
    my $self = shift;
    my %opt = @_;
    my $db = $self->{dbh};

    my $active = $opt{active} ? 1 : 0;
    $db->do("UPDATE domain set
            maxusers='$opt{maxusers}',
            maxalias='$opt{maxalias}',
            maxquota='$opt{maxquota}',
            maxnetdiskquota='$opt{maxndquota}',
            transport='$opt{transport}',
            can_signup='$opt{can_signup}',
            default_quota='$opt{default_quota}',
            default_netdiskquota='$opt{default_ndquota}',
            default_expire='$opt{default_expire}',
            disablesmtpd='$opt{disablesmtpd}',
            disablesmtp='$opt{disablesmtp}',
            disablewebmail='$opt{disablewebmail}',
            disablenetdisk='$opt{disablenetdisk}',
            disableimap='$opt{disableimap}',
            disablepop3='$opt{disablepop3}',
            expiredate='$opt{expire}',
            active='$active',
            description='$opt{description}' WHERE domain='$opt{domain}'"
    );
    if ($db->err) {
        return $db->errstr;
    } else {
        return 0;
    }
}

sub modify_manager {
    my $self = shift;
    my %opt = @_;
    my $db = $self->{dbh};
    my $time = strftime("%Y-%m-%d %H:%M:%S", localtime);

    # update password if set
    if ($opt{passwd}) {
        my $ctype = $self->{crypt_type};
        my $passwd = $self->encrypt($ctype, $opt{passwd});
        $db->do("UPDATE manager set password='$passwd' WHERE username='$opt{manager}'");
        return $db->errstr if ($db->err);
    }

    # update main information
    my $active = $opt{active} ? 1 : 0;
    $db->do("UPDATE manager set
            name='$opt{cn}',
            question='$opt{question}',
            answer='$opt{answer}',
            disablepwdchange='$opt{disablepwdchange}',
            expiredate='$opt{expire}',
            active='$active' WHERE username='$opt{manager}'"
    );
    return $db->errstr if ($db->err);

    # delete old owndomain, to simplify procedure
    $db->do("DELETE FROM domain_manager where username='$opt{manager}'");
    return $db->errstr if ($db->err);

    # add new owndomain
    foreach my $vd (split(/\s+/, $self->mshack($opt{domain}))) {
        $db->do("INSERT into domain_manager
            (username,
            domain,
            createdate,
            active) VALUES(
            '$opt{manager}',
            '$vd',
            '$time',
            '1'
            )"
        );
        # ignore errstr even with some err, sucks
    }

    if ($db->err) {
        return $db->errstr;
    } else {
        return 0;
    }
}

sub get_user_info {
    my $self = shift;
    my $user = $_[0];
    my $domain = $user;

    $domain =~ s#^([^\@]+)@##;
    my $SQL = "SELECT * FROM mailbox where username='$user'";
    my $ref = $self->get_entry($SQL);
    return undef unless ($ref);
    return {
        mail => $ref->{username},
        cn => $ref->{name},
        domain => $ref->{domain},
        uidnumber => $ref->{uidnumber},
        gidnumber => $ref->{gidnumber},
        uid => $ref->{uid},
        netdiskquota => $ref->{netdiskquota},
        active => $ref->{active} ? 1 : 0,
        quota => $ref->{quota},
        passwd => $ref->{password},
        clearpw => $ref->{clearpwd},
        mailhost => $ref->{mailhost},
        maildir => $ref->{maildir},
        homedir => $ref->{homedir},
        expire => $ref->{expiredate},
        create => $ref->{createdate},
        disablepwdchange => $ref->{disablepwdchange} ? 1 : 0,
        disablesmtpd => $ref->{disablesmtpd},
        disablesmtp => $ref->{disablesmtp},
        disablewebmail => $ref->{disablewebmail},
        disablenetdisk => $ref->{disablenetdisk},
        disableimap => $ref->{disableimap},
        disablepop3 => $ref->{disablepop3},
    }
}

sub get_domain_info {
    my $self = shift;
    my $SQL = "SELECT * FROM domain WHERE domain='$_[0]'";
    my $ref = $self->get_entry($SQL);
    return undef unless ($ref);
    return {
        domain => $ref->{domain},
        create => $ref->{createdate},
        expire => $ref->{expiredate},
        description => $ref->{description},
        hashdirpath => $ref->{hashdirpath},
        maxalias => $ref->{maxalias},
        maxusers => $ref->{maxusers},
        maxquota => $ref->{maxquota},
        maxndquota => $ref->{maxnetdiskquota},
        transport => $ref->{transport},
        can_signup => $ref->{can_signup},
        default_quota => $ref->{default_quota},
        default_ndquota => $ref->{default_netdiskquota},
        default_expire => $ref->{default_expire},
        disablesmtpd => $ref->{disablesmtpd},
        disablesmtp => $ref->{disablesmtp},
        disablewebmail => $ref->{disablewebmail},
        disablenetdisk => $ref->{disablenetdisk},
        disableimap => $ref->{disableimap},
        disablepop3 => $ref->{disablepop3},
        active => $ref->{active} ? 1 : 0,
    }
}

sub get_alias_info {
    my $self = shift;
    my $SQL = "SELECT * FROM alias WHERE address='$_[0]'";
    my $ref = $self->get_entry($SQL);
    return undef unless ($ref);
    my $goto = $ref->{goto};

    return {
        alias => $ref->{address},
        domain => $ref->{domain},
        goto => ($goto =~ m!,!) ? [split(/,/,$goto)] : $goto,
        active => $ref->{active} ? 1 : 0,
        create => $ref->{createdate},
        expire => $ref->{expiredate},
    }
}

sub get_manager_info {
    my $self = shift;
    my $SQL = "SELECT * FROM manager where username='$_[0]'";
    my $ref = $self->get_entry($SQL);

    return undef unless ($ref);
    $SQL = "SELECT domain from domain_manager WHERE username='$_[0]'";
    my $ds = $self->get_entries($SQL);
    my $arr = []; # convert to array ref
    foreach (@$ds) {
        push @$arr, $_->{domain};
    }

    return {
        manager => $ref->{username},
        cn => $ref->{name},
        question => $ref->{question},
        answer => $ref->{answer},
        disablepwdchange => $ref->{disablepwdchange} ? 1 : 0,
        create => $ref->{createdate},
        expire => $ref->{expiredate},
        type => $ref->{type},
        passwd => $ref->{password},
        active => $ref->{active} ? 1 : 0,
        domain => $arr,
    }
}

#---------------------------------#
# search and sort, paging handler #
#---------------------------------#

# method and parameters
#
# $self, %opt => (
#   domain => $domain,
#   page => $page,
#   filter => $filter,        # NULL means retreive all
#   filter_type => $type      # mail or name(cn) is ok
#   );
sub user_paging {
    my $self = shift;
    my %opt = @_;

    my $domain = $opt{domain};
    my $page = $opt{page} || 0;
    my $filter = $opt{filter};
    my $filter_type = $opt{filter_type};

    my ($has_prev, $has_next) = (1, 0);
    my $psize = $self->{psize}; # page size
    my $begin = $page*$psize;

    # all un-filltered result
    my $all = $self->get_users_list($domain) || [];
    # array to contain filltered result
    my $arr = [];

    delete $self->{_ext_info};

    for(my $i=0; $i<scalar @$all; $i++) {
        my $e = $all->[$i];
        if ($filter) {
            next unless $e->{$filter_type} =~ /$filter/i;
        }
        push @$arr, $e;
    }

    # the result array
    my $res = [];
    for(my $i=$begin;$i<scalar @$arr;$i++) {
        push @$res, $arr->[$i];
        last if (scalar @$res>= $psize);
    }

    if (scalar @$res == $psize && $begin + $psize < scalar @$arr) {
        $has_next =1;
    }
    if ($page <= 0) { $has_prev = 0 };

    # XXX ext_info
    $self->{_ext_info} = { total => scalar @$all };

    return ($res, $has_prev, $has_next);
}

# 
# $self, %opt => (
#   domain => $domain,
#   page => $page,
#   filter => $filter,    # NULL means all
# )
sub alias_paging {
    my $self = shift;
    my %opt = @_;

    my $domain = $opt{domain};
    my $page = $opt{page} || 0;
    my $filter = $opt{filter};
    my ($has_prev, $has_next) = (1, 0);

    my $psize = $self->{psize}; # page size
    my $begin = $page*$psize;

    # all un-filltered result
    my $all = $self->get_aliases_list($domain) || [];
    # array to contain filltered result
    my $arr = [];

    delete $self->{_ext_info};

    for(my $i=0; $i<scalar @$all; $i++) {
        my $e = $all->[$i];
        if ($filter) {
            next unless ($e->{alias} =~ /$filter/i ||
               $e->{goto} =~ /$filter/i);
        }
        push @$arr, $e;
    }

    my $res = [];
    for(my $i=$begin; $i<scalar @$arr; $i++) {
        push @$res, $arr->[$i];
        last if (scalar @$res>= $psize);
    }

    if (scalar @$res == $psize && $begin + $psize < scalar @$arr) {
        $has_next =1;
    }
    if ($page <= 0) { $has_prev = 0 };

    # XXX ext_info
    $self->{_ext_info} = { total => scalar @$all };

    return ($res, $has_prev, $has_next);
}

#
# $self, %opt => (
#   filter => $filter,          # NULL means all
#   filter_type => $filter_type # admin or postmaster
# )
#
# $self->ext_info() - return extend info for counting
sub manager_paging {
    my $self = shift;
    my %opt = @_;

    my $page = $opt{page} || 0;
    my $filter = $opt{filter};
    my $filter_type = $opt{filter_type};
    my ($has_prev, $has_next) = (1, 0);

    my $psize = $self->{psize}; # page size
    my $begin = $page*$psize;

    # all un-filltered result
    my $all = $self->get_managers_list || [];
    # array to contain filltered result
    my $arr = [];

    delete $self->{_ext_info};

    for(my $i=0; $i<scalar @$all; $i++) {
        my $e = $all->[$i];
        if ($filter) {
            next unless ($e->{manager} =~ /$filter/i ||
                $e->{cn} =~ /$filter/i) and 
                $e->{type} eq $filter_type;
        }
        push @$arr, $e;
    }

    my $res = [];
    for(my $i=$begin; $i<scalar @$arr;$i++) {
        push @$res, $arr->[$i];
        last if (scalar @$res>= $psize);
    }

    if (scalar @$res == $psize && $begin + $psize < scalar @$arr) {
        $has_next =1;
    }
    if ($page <= 0) { $has_prev = 0 };

    # XXX ext_info
    $self->{_ext_info} = { total => scalar @$all };

    return ($res, $has_prev, $has_next);
}

sub ext_info {
    my $self = shift;
    return $self->{_ext_info};
}

sub domain_paging {
    die "use Ext::MgrApp::domain_paging() instead\n";
}

sub DESTORY {
    my $self = shift;
    $self->{dbh}->disconnect();
    undef $self;
}
1;


syntax highlighted by Code2HTML, v. 0.9.1