The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
package Silki::Controller::User;
{
  $Silki::Controller::User::VERSION = '0.29';
}

use strict;
use warnings;
use namespace::autoclean;

use File::MimeInfo qw( mimetype );
use Silki::I18N qw( loc );
use Silki::Schema::TimeZone;
use Silki::Schema::User;
use Silki::Schema::UserImage;
use Silki::Util qw( string_is_empty );

use Moose;

BEGIN { extends 'Silki::Controller::Base' }

with qw(
    Silki::Role::Controller::Pager
    Silki::Role::Controller::User
);

sub _set_user : Chained('/') : PathPart('user') : CaptureArgs(1) {
}

sub _make_user_uri {
    my $self = shift;
    my $c    = shift;
    my $user = shift;
    my $view = shift || q{};

    return $user->uri( view => $view );
}

sub wikis : Chained('_set_user') : PathPart('wikis') : Args(0) : ActionClass('+Silki::Action::REST') {
}

sub wikis_GET {
    my $self = shift;
    my $c    = shift;

    my $user = $c->stash()->{user};

    my $wikis = $user->all_wikis();

    my @entity = map {
        {
            wiki_id    => $_->wiki_id(),
            title      => $_->title(),
            short_name => $_->short_name(),
        }
    } $wikis->all();

    return $self->status_ok( $c, entity => \@entity );
}

sub _set_confirmation : Chained('_set_user') : PathPart('confirmation') : CaptureArgs(1) {
    my $self = shift;
    my $c    = shift;
    my $key  = shift;

    my $user = Silki::Schema::User->new( confirmation_key => $key );

    $c->redirect_and_detach( $c->domain()->uri( with_host => 1 ) )
        unless $user && $user->user_id() == $c->stash()->{user}->user_id();

    return;
}

sub pending_confirmation : Chained('_set_confirmation') : PathPart('status') : Args(0)  {
    my $self = shift;
    my $c    = shift;

    $c->stash()->{template} = '/user/pending-confirmation';
}

sub confirmation_form : Chained('_set_confirmation') : PathPart('preferences_form') : Args(0)  {
    my $self = shift;
    my $c    = shift;

    $c->stash()->{template} = '/user/confirmation-form';
}

sub login_form : Local {
    my $self = shift;
    my $c    = shift;

    $c->stash()->{template} = '/user/login-form';
}

sub authentication : Local : ActionClass('+Silki::Action::REST') {
}

sub authentication_GET_html {
    my $self = shift;
    my $c    = shift;

    my $method = $c->request()->param('x-tunneled-method');

    if ( $method && $method eq 'DELETE' ) {
        $self->authentication_DELETE($c);
        return;
    }
    else {
        $c->redirect_and_detach(
            $c->domain()->application_uri( path => '/user/login_form' ) );
    }
}

sub authentication_POST {
    my $self = shift;
    my $c    = shift;

    my $username = $c->request()->params->{username};
    my $pw       = $c->request()->params->{password};

    my @errors;

    push @errors, {
        field   => 'password',
        message => loc('You must provide a password.')
        }
        if string_is_empty($pw);

    my $user;
    unless (@errors) {
        $user = Silki::Schema::User->new(
            username => $username,
        );

        if ($user) {
            if ( $user->is_disabled() ) {
                undef $user;

                push @errors,
                    loc(
                    'This user account has been disabled by a site admin.');
            }
            else {
                undef $user unless $user->check_password($pw);

                if ($user) {
                    $c->redirect_and_detach(
                        $user->confirmation_uri(
                            view      => 'status',
                            host      => $c->domain()->web_hostname(),
                            with_host => 1,
                        )
                    ) if $user->requires_activation();
                }

                push @errors,
                    loc(
                    'The username or password you provided was not valid.')
                    unless $user;
            }
        }
    }

    unless ($user) {
        $c->redirect_with_error(
            error => \@errors,
            uri   => $c->domain()->application_uri(
                path      => '/user/login_form',
                with_host => 1
            ),
            form_data => $c->request()->params(),
        );
    }

    $self->_login_user( $c, $user );
}

sub _login_user {
    my $self = shift;
    my $c    = shift;
    my $user = shift;

    my %expires
        = $c->request()->param('remember') ? ( expires => '+1y' ) : ();

    $c->set_authen_cookie(
        value => { user_id => $user->user_id() },
        %expires,
    );

    $c->session_object()
        ->add_message( 'Welcome to the site, ' . $user->best_name() );

    my $redirect_to = $c->request()->params()->{return_to}
        || $c->domain()->application_uri( path => q{} );

    $c->redirect_and_detach($redirect_to);
}

sub authentication_DELETE {
    my $self = shift;
    my $c    = shift;

    $c->unset_authen_cookie();

    $c->session_object()->add_message('You have been logged out.');

    my $redirect = $c->request()->params()->{return_to}
        || $c->domain()->application_uri( path => q{} );
    $c->redirect_and_detach($redirect);
}

sub forgot_password_form : Local {
    my $self = shift;
    my $c    = shift;

    $c->stash()->{template} = '/user/forgot-password-form';
}

sub password_reminder : Local : ActionClass('+Catalyst::Action::REST') {
}

sub password_reminder_POST {
    my $self = shift;
    my $c    = shift;

    my $username = $c->request()->params()->{username};

    my $user;

    my @errors;
    if ( string_is_empty($username) ) {
        push @errors, {
            field   => 'username',
            message => loc('You must provide an email address.'),
            };
    }
    else {
        $user = Silki::Schema::User->new( username => $username );

        if ($user) {
            push @errors,
                loc('This user account has been disabled by a site admin.')
                if $user->is_disabled();
        }
        else {
            push @errors, {
                field => 'username',
                message =>
                    loc( "There is no user with the email address %1.", $username ),
                };
        }
    }

    if (@errors) {
        $c->redirect_with_error(
            error => \@errors,
            uri   => $c->domain()->application_uri(
                path      => '/user/forgot_password_form',
                with_host => 1,
            ),
            form_data => {
                username  => $username,
                return_to => $c->request()->params()->{return_to},
            },
        );
    }

    $user->forgot_password( domain => $c->domain() );

    $c->session_object()->add_message(
        loc(
            'A message telling you how to change your password has been sent to your email address.'
        )
    );

    $c->redirect_and_detach(
        $c->domain()->application_uri(
            path      => '/user/forgot_password_form',
            with_host => 1,
            query =>
                { return_to => $c->request()->parameters()->{return_to} },
            with_host => 1,
        )
    );
}

sub purge_confirmation : Chained('_set_user') : PathPart('purge_confirmation') : Args(0) {
    my $self = shift;
    my $c    = shift;

    $self->_require_site_admin($c);

    $c->stash()->{template} = '/user/purge-confirmation';
}

sub user_DELETE {
    my $self = shift;
    my $c    = shift;

    $self->_require_site_admin($c);

    my $user = $c->stash()->{user};

    my $msg = loc(
        'Deleted the user %1 - %2',
        $user->best_name(),
        $user->email_address()
    );

    $user->delete( user => $c->user() );

    $c->session_object()->add_message($msg);

    $c->redirect_and_detach(
        $c->domain()->uri( view => 'users', with_host => 1 ) );
}

sub new_user_form : Local {
    my $self = shift;
    my $c    = shift;

    $c->stash()->{template} = '/user/new-user-form';
}

sub users_collection : Path('/users') : ActionClass('+Silki::Action::REST') {
}

sub users_collection_GET_html {
    my $self = shift;
    my $c    = shift;

    $self->_require_site_admin($c);

    my $include_disabled = $c->request()->params()->{include_disabled};

    my $count_meth = $include_disabled ? 'Count' : 'ActiveUserCount';

    my ( $limit, $offset ) = $self->_make_pager( $c, Silki::Schema::User->$count_meth() );

    my $meth = $include_disabled ? 'All' : 'ActiveUsers';

    $c->stash()->{include_disabled} = $include_disabled;

    $c->stash()->{users} = Silki::Schema::User->$meth(
        limit  => $limit,
        offset => $offset,
    );

    $c->stash()->{template} = '/user/users';
}

sub users_collection_POST {
    my $self = shift;
    my $c    = shift;

    my %insert = $c->request()->user_params();

    my $upload = $c->request()->upload('image');

    my @errors = $self->_check_passwords_match(\%insert);

    $insert{requires_activation} = 1;

    my $user;
    unless (@errors) {
        eval {
            Silki::Schema->RunInTransaction(
                sub {
                    $user = Silki::Schema::User->insert(
                        %insert,
                        user => $c->user(),
                    );

                    if ($upload) {
                        Silki::Schema::UserImage->insert(
                            user_id   => $user->user_id(),
                            mime_type => mimetype( $upload->tempname() ),
                            file_size => $upload->size(),
                            contents =>
                                do { my $fh = $upload->fh(); local $/; <$fh> },
                        );
                    }
                }
            );
        };

        my $e = $@;
        die $e if $e && ! ref $e;

        push @errors, @{ $e->errors() } if $e;
    }

    if (@errors) {
        $c->redirect_with_error(
            error => \@errors,
            uri   => $c->domain()->application_uri(
                path      => '/user/new_user_form',
                with_host => 1
            ),
            form_data => \%insert,
        );
    }

    $user->send_activation_email(
        sender => Silki::Schema::User->SystemUser(),
        domain => $c->domain(),
    );

    $c->redirect_and_detach(
        $user->confirmation_uri(
            view      => 'status',
            host      => $c->domain()->web_hostname(),
            with_host => 1,
        )
    );
}

__PACKAGE__->meta()->make_immutable();

1;

# ABSTRACT: Controller class for users

__END__
=pod

=head1 NAME

Silki::Controller::User - Controller class for users

=head1 VERSION

version 0.29

=head1 AUTHOR

Dave Rolsky <autarch@urth.org>

=head1 COPYRIGHT AND LICENSE

This software is Copyright (c) 2011 by Dave Rolsky.

This is free software, licensed under:

  The GNU Affero General Public License, Version 3, November 2007

=cut