#!/usr/bin/perl # # DW::Controller::Admin::UserHistory # # Admin pages for userlog and statushistory, converted from LJ. # # Authors: # Jen Griffin # # Copyright (c) 2020 by Dreamwidth Studios, LLC. # # This program is free software; you may redistribute it and/or modify it under # the same terms as Perl itself. For a copy of the license, please reference # 'perldoc perlartistic' or 'perldoc perlgpl'. # package DW::Controller::Admin::UserHistory; use strict; use DW::Controller; use DW::Controller::Admin; use DW::Routing; use DW::Template; use DW::FormErrors; my $statushistory_privs = [ 'historyview', sub { return ( $LJ::IS_DEV_SERVER, LJ::Lang::ml("/admin/index.tt.devserver") ); } ]; DW::Routing->register_string( "/admin/statushistory", \&statushistory_controller, app => 1 ); DW::Controller::Admin->register_admin_page( '/', path => 'statushistory', ml_scope => '/admin/statushistory.tt', privs => $statushistory_privs ); DW::Routing->register_string( "/admin/userlog", \&userlog_controller, app => 1 ); DW::Controller::Admin->register_admin_page( '/', path => 'userlog', ml_scope => '/admin/userlog.tt', privs => [ 'canview:userlog', 'canview:*' ] ); sub statushistory_controller { my ( $ok, $rv ) = controller( form_auth => 1, privcheck => $statushistory_privs ); return $rv unless $ok; my $scope = '/admin/statushistory.tt'; my $r = DW::Request->get; my $form_args = $r->did_post ? $r->post_args : $r->get_args; my $vars = {}; $vars->{formdata} = $form_args; $vars->{showtable} = ( $form_args->{'user'} || $form_args->{'admin'} || $form_args->{'type'} ) ? 1 : 0; return DW::Template->render_template( 'admin/statushistory.tt', $vars ) unless $vars->{showtable}; # build database query my $errors = DW::FormErrors->new; my $dbr = LJ::get_db_reader(); my @where; if ( $form_args->{'user'} ) { if ( my $userid = LJ::get_userid( $form_args->{'user'} ) ) { push @where, "s.userid=$userid"; } else { $errors->add( "user", ".error.nouser" ); } } if ( $form_args->{'admin'} ) { if ( my $userid = LJ::get_userid( $form_args->{'admin'} ) ) { push @where, "s.adminid=$userid"; } else { $errors->add( "admin", ".error.noadmin" ); } } if ( $form_args->{'type'} ) { my $qt = $dbr->quote( $form_args->{'type'} ); push @where, "s.shtype=$qt"; } if ( $errors->exist ) { $vars->{errors} = $errors; $vars->{showtable} = 0; return DW::Template->render_template( 'admin/statushistory.tt', $vars ); } my $where = ""; $where = "WHERE " . join( " AND ", @where ) . " " if @where; my $orderby = 's.shdate'; $orderby = { user => "u.user", admin => "admin", shdate => "s.shdate", shtype => "s.shtype", notes => "s.notes", }->{ $form_args->{'orderby'} } if $form_args->{'orderby'}; my $flow = 'DESC'; $flow = 'ASC' if $form_args->{'flow'} && $form_args->{'flow'} eq 'asc'; $vars->{rows} = $dbr->selectall_arrayref( "SELECT u.user, ua.user AS admin, s.shtype, s.shdate, s.notes " . "FROM statushistory s " . "LEFT JOIN useridmap ua ON s.adminid=ua.userid " . "LEFT JOIN useridmap u ON s.userid=u.userid " . $where . "ORDER BY $orderby $flow LIMIT 1000", { Slice => {} } ); return error_ml( "$scope.error.db", { err => $dbr->errstr } ) if $dbr->err; $vars->{canview} = sub { return 1 if $LJ::IS_DEV_SERVER; my $remote = $rv->{remote}; return 1 if $remote->has_priv( 'historyview', '' ); return $remote->has_priv( 'historyview', $_[0]->{shtype} ); }; # I dislike using ljuser instead of ljuser_display, # but this flow works better for this specific case $vars->{ljuser} = sub { LJ::ljuser( $_[0] ) }; $vars->{format_time} = sub { my $time = $_[0]; $time =~ s/(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/$1-$2-$3 $4:$5:$6/; return $time; }; $vars->{format_note} = sub { my $enotes = LJ::ehtml( $_[0] ); $enotes =~ s!\n!
\n!g; return $enotes; }; return DW::Template->render_template( 'admin/statushistory.tt', $vars ); } sub userlog_controller { my ( $ok, $rv ) = controller( form_auth => 1, privcheck => [ 'canview:userlog', 'canview:*' ] ); return $rv unless $ok; my $scope = '/admin/userlog.tt'; my $r = DW::Request->get; my $form_args = $r->did_post ? $r->post_args : $r->get_args; my $vars = {}; $vars->{user} = LJ::canonical_username( $form_args->{user} ); return DW::Template->render_template( 'admin/userlog.tt', $vars ) unless $vars->{user}; $vars->{u} = LJ::load_user( $vars->{user} ); return error_ml("$scope.error.nouser") unless $vars->{u}; return error_ml("$scope.error.purged") if $vars->{u}->is_expunged; my $dbcr = LJ::get_cluster_reader( $vars->{u} ); return error_ml("$scope.error.nodb") unless $dbcr; $vars->{rows} = $dbcr->selectall_arrayref( 'SELECT * FROM userlog WHERE userid = ? ORDER BY logtime DESC LIMIT 10000', { Slice => {} }, $vars->{u}->id ); $vars->{action_text} = sub { my ($row) = @_; my $extra = {}; LJ::decode_url_string( $row->{extra} // '', $extra ); my $action = $row->{action}; # we have a lot of possible actions and this used to be a long # chain of elsif conditionals - hopefully breaking it into chunks # of similar actions will be slightly easier to maintain. my $ml = sub { LJ::Lang::ml( "$scope$_[0]", $_[1] ) }; my %need_target_u = map { $_ => 1 } qw(ban_set ban_unset maintainer_add maintainer_remove impersonator screen_set screen_unset); if ( $need_target_u{$action} ) { my $u = LJ::load_userid( $row->{actiontarget} ); my $user = $u ? $u->ljuser_display : "userid \#$row->{actiontarget}"; return $ml->( ".action.$action", { user => $user, reason => LJ::ehtml( $extra->{reason} ) } ); } if ( $action eq 'redirect' ) { return $ml->( ".action.redirect.$extra->{action}", { to => $extra->{renamedto} } ); } if ( $action eq 'accountstatus' ) { my $path = "$extra->{old} -> $extra->{new}"; return $ml->(".action.accountstatus.V-to-D") if $path eq 'V -> D'; return $ml->(".action.accountstatus.D-to-V") if $path eq 'D -> V'; return $ml->( ".action.accountstatus.any", { old => $extra->{old}, new => $extra->{new} } ); } # at this point every other valid action is straightforward my %other_actions = ( account_create => {}, delete_entry => { target => $row->{actiontarget}, method => $extra->{method} }, delete_userpic => { picid => $extra->{picid} }, email_change => { new => $extra->{new} }, emailpost_auth => {}, emailpost => {}, friend_invite_sent => { whom => $extra->{extra} }, impersonated => { reason => LJ::ehtml( $extra->{reason} ) }, mass_privacy_change => { from => $extra->{s_security}, to => $extra->{e_security} }, password_change => {}, password_reset => {}, rename => { from => $extra->{from}, to => $extra->{to}, del => $extra->{del} ? "
Deleted: $extra->{del}" : '', redir => $extra->{redir} ? "
Redirected: $extra->{redir}" : '', }, siteadmin_email => { account => $extra->{account}, domain => $LJ::DOMAIN, msgid => $extra->{msgid} }, ); return $ml->( ".action.$action", $other_actions{$action} ) if exists $other_actions{$action}; return $ml->( ".action.unknown", { action => $action } ); }; $vars->{load_actor} = sub { LJ::load_userid( $_[0]->{remoteid} ) }; $vars->{mysql_time} = sub { $_[0] ? LJ::mysql_time( $_[0] ) : "" }; return DW::Template->render_template( 'admin/userlog.tt', $vars ); } 1;