# Copyright 1999-2006, Fred Steinberg, Brown Bear Software

# Add/Delete users, set passwords
package SysUsers;
use strict;
use CGI;

use Calendar::AdminPager;
use Calendar::GetHTML;
use Calendar::TableEditor;
use Calendar::User;

use vars ('@ISA');
@ISA = ('Operation');

sub perform {
    my $self = shift;
    my ($save, $cancel) = $self->getParams (qw (Save Cancel));

    if ($cancel or $self->calendarName) {
        print $self->redir ($self->makeURL({Op => 'SysAdminPage'}));
        return;
    }

    my $cgi  = CGI->new;
    my $i18n = $self->I18N;

    my @columns   = qw (Username Email DefaultCal TimeZone Password IsLocked);
    my %colLabels = (Username   => $i18n->get ("Username"),
                     Email      => $i18n->get ("Email Address"),
                     DefaultCal => $i18n->get ("Default Calendar"),
                     TimeZone   => $i18n->get ("Timezone Offset"),
                     Password   => $i18n->get ("Set New Password"),
                     IsLocked   => $i18n->get ("Locked"),
                     );
    my %colTypes = (DefaultCal => 'popupMenu',
                    TimeZone   => 'popupMenu',
                    IsLocked   => 'checkbox');
    my $numAddRows = 3;

    my @calendars = sort {lc($a) cmp lc($b)} MasterDB->getAllCalendars;

    my $stet = ' - - - ';

    my (@messages, @errors);

    if ($save) {
        $self->{audit_formsaved}++;
        my $ted = TableEditor::ParamParser->new (columns    => \@columns,
                                                 key        => 'Username',
                                                 numAddRows => $numAddRows,
                                                 params  => $self->rawParams);
        my @deletedKeys = $ted->getDeleted;
        my $rowHashes   = $ted->getRows;
        my $newRows     = $ted->getNewRows;

        my @deletedUsers;
        foreach my $username (@deletedKeys) {
            my $me = $self->getUsername || '';
            if ($username eq $me) {
                push @errors, $i18n->get ('You cannot delete yourself.');
                next;
            }
            push @deletedUsers, $username;
            MasterDB->removeUser ($username);
        }

        # And remove the deleted user permissions from each calendar
        if (@deletedUsers) {
            foreach (@calendars, undef) {
                my $perms = defined $_ ? Permissions->new ($_)
                                       : Permissions->new (MasterDB->new);
                my $uhash = $perms->getUserHash;
                foreach my $user (@deletedUsers) {
                    delete $uhash->{$user};
                }
                $perms->setUserHash ($uhash) if (@deletedUsers);
            }
            my $x = @deletedUsers > 1 ? 'Deleted Users' : 'Deleted User';
            push @messages, $i18n->get ($x) . ': ' .
                            join ', ', sort {lc $a cmp lc $b} @deletedUsers;
            $self->{audit_deletedUsers} = \@deletedUsers;
        }

        # Modify existing ones
        $self->{audit_modifiedUsers} = {};
        while (my ($name, $vals) = each %$rowHashes) {
            my $user = User->getUser ($name);
            next unless $user;
            my @mods;
            if (($vals->{Email} || '') ne ($user->email || '')) {
                $user->setEmail ($vals->{Email});
                push @mods, 'Email';
            }
            if (($vals->{TimeZone} || 0) != ($user->timezone || 0)) {
                $user->setTimezone ($vals->{TimeZone});
                push @mods, 'TimeZone';
            }
            my $defaultCal = $vals->{DefaultCal} eq $stet ? undef
                                                         : $vals->{DefaultCal};
            if (($defaultCal || '') ne ($user->defaultCalendar || '')) {
                $user->setDefaultCalendar ($defaultCal);
                push @mods, 'DefaultCal';
            }
            if ($vals->{Password} ne $stet) {
                $user->setPassword ($vals->{Password});
                push @mods, 'Password';
            }
            if ($vals->{IsLocked} xor $user->isLocked) {
                $user->setLocked ($vals->{IsLocked});
                push @mods, 'IsLocked';
            }
            if (@mods) {
                push @messages, $i18n->get ('Changed User') . ": $name; " .
                                       join (', ', map {$colLabels{$_}} @mods);
                $self->{audit_modifiedUsers}->{$name} = \@mods;
            }
        }

        # Add new ones
        my @newNames;
        foreach my $row (@$newRows) {
            my $name = $row->{Username};
            next if (!$name);
            if ($name =~ /\W/) {
                my $blah = $name;
                $blah =~ s/[-@\.\\]//g;
                if ($blah =~ /\W/) {
                    push @errors, "$name: " .
                                  $i18n->get ('Invalid Username') . '; ' .
                                  $i18n->get ('Only letters, digits, ' .
                                              'underscores, periods, @, and ' .
                                              'backslashes are allowed in ' .
                                              'user names.');
                    next;
                }
            }
            my $defaultCal = $row->{DefaultCal} eq $stet ? undef
                                                         : $row->{DefaultCal};
            my $password   = $row->{Password} eq $stet ? undef
                                                         : $row->{Password};
            my $newUser = User->create (name       => $name,
                                        email      => $row->{Email},
                                        defaultCal => $defaultCal,
                                        zoneOffset => $row->{TimeZone},
                                        isLocked   => $row->{IsLocked},
                                        password   => $password);

            if ($newUser->addUser) {
                push @newNames, $name;
            } else {
                push @errors, "<font color=red>" . $i18n->get ('Error') .
                              ":</font> " . $i18n->get ('User') .
                              " <b>$name</b> " . $i18n->get('already exists!');
            }
        }
        if (@newNames) {
            my $x = @newNames > 1 ? 'Added Users' : 'Added User';
            push @messages, $i18n->get ($x) . ': ' .
                            join ', ', sort {lc $a cmp lc $b} @newNames;
            $self->{audit_addedUsers} = \@newNames;
        }
    }

    unshift @calendars, $stet;

    my $h = $i18n->get ('hours');
    my %tzLabels;
    foreach (-23..23) {
        $tzLabels{$_} = "$_ $h";
    }

    my %colParams = (Email      => {size => 25},
                     DefaultCal => {values => \@calendars},
                     TimeZone   => {values  => [-23..23],
                                    default => 0,
                                    labels  => \%tzLabels});

    my $ted = TableEditor->new (columns       => \@columns,
                                key           => 'Username',
                                columnLabels  => \%colLabels,
                                types         => \%colTypes,
                                controlparams => \%colParams,
                                tableTitle    => $i18n->get ('Users'),
                                numAddRows    => $numAddRows,
                               );

    my ($all_ldap, $these_ldap) = $self->getParams (qw (AllLDAPUsers
                                                        TheseLDAPUsers));

    # only get INTERNAL users, unless LDAP is requested (since there might
    #  be say 200,000 LDAP ones)
    my %userMap = map {$_->name, $_} User->getUsers; # internal users
    my @ldap_users;
    if ($all_ldap) {
        @ldap_users = User->getUserNames; # includes external users
    }
    elsif ($these_ldap) {
        foreach my $name (split /\s+/, $these_ldap) {
            if (User->exists ($name)) {
                push @ldap_users, $name;
            }
            else {
                push @messages, $i18n->get ('User not found: ') . $name;
            }
        }
    }
    foreach (@ldap_users) {
        next if $userMap{$_};
        $userMap{$_} = $_;      # not a ref, external user
    }

    my @users;
    foreach my $name (sort {lc($a) cmp lc($b)} keys %userMap) {
        my $user = $userMap{$name};
        my ($email, $defaultCal, $tzone, $locked) = ('', '', '', '');
        if (ref $user) {
            $email      = $user->email;
            $defaultCal = $user->defaultCalendar;
            $tzone      = $user->timezone || 0;
            $locked     = $user->isLocked;
        }
        push @users, {name   => $name,
                      email  => $email,
                      defCal => $defaultCal,
                      tzone  => $tzone,
                      locked => $locked};
    }

    my $pager = AdminPager->new (op       => $self,
                                 contents => \@users,
                                 itemName => $i18n->get ('users'));
    my @displayThese = $pager->getDisplayList;

    foreach my $user (@displayThese) {
        next unless $user;
        my $row = $ted->addRow (Username   => $user->{name},
                                Email      => $user->{email},
                                DefaultCal => $user->{defCal},
                                TimeZone   => $user->{tzone} || 0,
                                Password   => $stet,
                                IsLocked   => $user->{locked});
    }

    print GetHTML->startHTML (title => $i18n->get ('User Administration'),
                              op    => $self);
    print GetHTML->SysAdminHeader ($i18n, 'Add, Modify, or Delete Users', 1);

    if (@errors) {
        print '<p>', join ('<br>', @errors), '</p>'
    }
    if (@messages) {
        print '<p>', join ('<br>', @messages), '</p>'
    }

    print '<br>';
    print $cgi->startform;
    print $ted->render;

    print $pager->controls;

    # Controls to specify particular LDAP users, or include all
    my $username_field = $cgi->textfield (-name => 'TheseLDAPUsers',
                                          -size => 60,
                                          -id   => 'UsernameField');
    my $all_ldap_cbox = $cgi->checkbox (-name  => 'AllLDAPUsers',
                                        -label => '');

    print '<p><center>';
    print $i18n->get ('Include only these users from LDAP:') . '<br/>';
    print $username_field;
    print '<br/>' . $i18n->get ('Or, include <b>all</b> LDAP users: ')
          . $all_ldap_cbox;
    print $cgi->submit (-name  => 'UpdateList',
                        -value => $i18n->get ('Refresh User List'));
    print '</center></p>';


    print $cgi->submit (-name => 'Save',   -value => $i18n->get ('Save'));
    print '&nbsp;';
    print $cgi->submit (-name => 'Cancel', -value => $i18n->get ('Done'));
    print '&nbsp;&nbsp;&nbsp;';
    print $cgi->reset  (-value => 'Reset');
    print $cgi->hidden (-name => 'Op', -value => __PACKAGE__);
    print $cgi->endform;

    print '<br><b>' . $i18n->get ('Notes') . ':</b>';

    my $note1 =<<FOO;
If Calcium is set up to use external authentication (e.g. LDAP), all users
are listed here - this includes all LDAP users, as well as those defined
only in Calcium (if any.) The settings here are used for Calcium only, so
modifications will <b>not</b> affect data in the LDAP directory. (In other
words, you can\'t change LDAP passwords or delete LDAP users from here.)
FOO
    my $note2 =<<FOO;
Even if Calcium is using external authentication, you can still log in
using a user's Calcium password - if one has been set for the user on this
page. To do so, prepend the keyword "internal" to the username in the
Calcium login. For example, to log in as the user 'admin' using the
password stored in Calcium instead of the external password, use the
username <nobr>"internal admin"</nobr>. This is an important feature, since
if you need to change the Calcium settings for the external server, you'll
still be able to login.
FOO

    print $cgi->ul (
            $cgi->li ($i18n->get (
               "The 'Default Calendar' is the calendar that is displayed " .
               "after the user logs in. It's also the calendar events are " .
               "added to for this user, via the 'Add Event' link in event " .
               "notification email.")),
            $cgi->li ($i18n->get ("'Locked' users cannot change any of " .
                                  'their own user settings.')),
            $cgi->li ($i18n->get ($note1)),
            $cgi->li ($i18n->get ($note2)));

    print $cgi->end_html;
}

sub cssDefaults {
    my $self = shift;
    my $css = $self->SUPER::cssDefaults;
    $css .= GetHTML->AdminCSS ($self);
    $css .= $self->cssString ('.PasswordInput',  {'text-align' => 'center'});
    return $css;
}

sub auditString {
    my ($self, $short) = @_;
    return unless $self->{audit_formsaved};
    my $line = $self->SUPER::auditString ($short);

    my $i18n = $self->I18N;

    my $space = $short ? '' : ' ';
    my $newl  = $short ? ' ' : "\n";

    $line .= $newl;

    if (my $x = $self->{audit_deletedUsers}) {
        $line .= $i18n->get ('Deleted') . ":$space" . join ",$space", @$x;
        $line .= $newl;
    }

    my $x = $self->{audit_modifiedUsers};
    if (keys %$x) {
        while (my ($name, $mods) = each %$x) {
            $line .= $i18n->get ('Modified') . ":$space$name$space-$space" .
                     join ",$space", @$mods;
            $line .= $newl;
        }
    }

    if (my $x = $self->{audit_addedUsers}) {
        $line .= $i18n->get ('Added') . ":$space" . join ",$space", @$x;
        $line .= $newl;
    }

    return $line;
}

1;
