Diff -- /var/www/vhosts/3dshawn.com/ptmatrix.3dshawn.com/activity.cgi
Diff
/var/www/vhosts/3dshawn.com/ptmatrix.3dshawn.com/activity.cgi
added on local at 2026-07-01 12:33:52
Added
+175
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 19ad1e935730
to 19ad1e935730
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.| 1 | #!/usr/bin/perl | |
| 2 | ||
| 3 | ################################################################################################################################## | |
| 4 | # ______ ____ ____ ______ ______ ____ ___ __ ___ ______ _ __ ____ ____ __ __ _ __ _____ ____ | |
| 5 | # / ____// __ \ / __ \ / ____/ / ____// __ \ / | / |/ // ____/| | / // __ \ / __ \ / //_/ | | / /|__ / / __ \ | |
| 6 | # / / / / / // / / // __/ / /_ / /_/ // /| | / /|_/ // __/ | | /| / // / / // /_/ // ,< | | / / /_ < / / / / | |
| 7 | # / /___ / /_/ // /_/ // /___ / __/ / _, _// ___ | / / / // /___ | |/ |/ // /_/ // _, _// /| | | |/ / ___/ /_ / /_/ / | |
| 8 | # \____/ \____//_____//_____/ /_/ /_/ |_|/_/ |_|/_/ /_//_____/ |__/|__/ \____//_/ |_|/_/ |_| |___/ /____/(_)\____/ | |
| 9 | # | |
| 10 | # - Written by: Shawn Pickering | |
| 11 | # - shawn@shawnpickering.com | |
| 12 | ################################################################################################################################## | |
| 13 | use strict; | |
| 14 | use lib '/var/www/vhosts/3dshawn.com/ptmatrix.3dshawn.com'; | |
| 15 | ||
| 16 | ||
| 17 | ############################################################# | |
| 18 | ## Variables | |
| 19 | ############################################################# | |
| 20 | ||
| 21 | ||
| 22 | ||
| 23 | ############################################################ | |
| 24 | # Modules | |
| 25 | ############################################################ | |
| 26 | use CGI; my $query = new CGI; my $form = $query->Vars; | |
| 27 | use MODS::Login; my $login = new MODS::Login; | |
| 28 | use MODS::PTMatrix::Urls; my $getlist = new MODS::PTMatrix::Urls; my $url = $getlist->urls(); | |
| 29 | use MODS::Template; my $tfile = new MODS::Template; | |
| 30 | use MODS::PTMatrix::Wrapper; my $load = new MODS::PTMatrix::Wrapper; | |
| 31 | use MODS::DBConnect; my $db = new MODS::DBConnect; | |
| 32 | use MODS::PTMatrix::Config; my $config = new MODS::PTMatrix::Config; my $database_name = $config->settings('database_name'); | |
| 33 | use MODS::PTMatrix::PM; | |
| 34 | ||
| 35 | ||
| 36 | ||
| 37 | ############################################################# | |
| 38 | ## Get form data | |
| 39 | ############################################################# | |
| 40 | foreach my $line(keys %$form){ | |
| 41 | $form->{$line} =~ s/[^!-~\s]//g; | |
| 42 | $form->{$line} = quotemeta("$form->{$line}"); | |
| 43 | } | |
| 44 | ||
| 45 | ||
| 46 | ||
| 47 | ############################################################ | |
| 48 | # Program Controls | |
| 49 | ############################################################ | |
| 50 | $|=1; | |
| 51 | my $userinfo = $login->login_verify(); | |
| 52 | if(!$userinfo){print qq~Content-type:text/html; Cache-Control:no-cache;\n\n<script>window.location="$url->{login}";</script>~;} | |
| 53 | elsif(($form->{format} || '') eq 'csv'){&export_csv;} | |
| 54 | else{&activity_page;} | |
| 55 | ||
| 56 | ||
| 57 | ############################################################ | |
| 58 | # Subroutines | |
| 59 | ############################################################ | |
| 60 | sub range_dates{ | |
| 61 | #Range defaults to this month, matches reports.cgi / cycle_time.cgi / portfolio.cgi | |
| 62 | my @lt = localtime; | |
| 63 | my $today_iso = sprintf("%04d-%02d-%02d", $lt[5]+1900, $lt[4]+1, $lt[3]); | |
| 64 | my $month_iso = sprintf("%04d-%02d-01", $lt[5]+1900, $lt[4]+1); | |
| 65 | my $rs = $form->{start} || $month_iso; | |
| 66 | my $re = $form->{end} || $today_iso; | |
| 67 | $rs = $month_iso unless $rs =~ /^\d{4}-\d{2}-\d{2}$/; | |
| 68 | $re = $today_iso unless $re =~ /^\d{4}-\d{2}-\d{2}$/; | |
| 69 | return ($rs, $re); | |
| 70 | } | |
| 71 | ||
| 72 | sub activity_page{ | |
| 73 | my $cid = MODS::PTMatrix::PM::get_company_id($userinfo); | |
| 74 | my ($range_start, $range_end) = &range_dates; | |
| 75 | my $range_end_eod = "$range_end 23:59:59"; | |
| 76 | ||
| 77 | my $dbh = $db->db_connect(); | |
| 78 | ||
| 79 | #------------------------------------------------------------------------------------------------------ | |
| 80 | #Last 200 audit rows in the range, joined to the actor user | |
| 81 | my $sql = qq~SELECT a.id, a.action, a.target_type, a.target_id, a.occurred_at, a.user_id, u.display_name AS actor_name, u.avatar_color FROM `${database_name}`.audit_log a LEFT JOIN `${database_name}`.users u ON u.id = a.user_id WHERE a.company_id='$cid' AND a.occurred_at BETWEEN '$range_start' AND '$range_end_eod' ORDER BY a.occurred_at DESC LIMIT 200~; | |
| 82 | my @rows = $db->db_readwrite_multiple($dbh, $sql, $ENV{SCRIPT_NAME}, __LINE__); | |
| 83 | #------------------------------------------------------------------------------------------------------ | |
| 84 | ||
| 85 | $db->db_disconnect($dbh); | |
| 86 | ||
| 87 | foreach my $r(@rows){ | |
| 88 | $r->{actor_initials} = $r->{actor_name} ? MODS::PTMatrix::PM::initials($r->{actor_name}) : '?'; | |
| 89 | $r->{avatar_color} ||= '#5aa9ff'; | |
| 90 | $r->{action_label} = &action_label($r->{action}); | |
| 91 | if ($r->{target_type} eq 'task') {$r->{target_url} = "/task.cgi?id=$r->{target_id}";} | |
| 92 | elsif($r->{target_type} eq 'project') {$r->{target_url} = "/project.cgi?id=$r->{target_id}";} | |
| 93 | else {$r->{target_url} = '';} | |
| 94 | } | |
| 95 | ||
| 96 | print qq~Content-type:text/html; Cache-Control:no-cache;\n\n~; | |
| 97 | ||
| 98 | my $is_mgr = MODS::PTMatrix::PM::is_manager_or_admin($userinfo) ? 1 : 0; | |
| 99 | ||
| 100 | #Create all template variables | |
| 101 | my $tvars; | |
| 102 | $tvars->{range_start} = $range_start; | |
| 103 | $tvars->{range_end} = $range_end; | |
| 104 | $tvars->{rows} = \@rows; | |
| 105 | $tvars->{has_rows} = scalar(@rows) ? 1 : 0; | |
| 106 | $tvars->{is_mgr} = $is_mgr; | |
| 107 | ||
| 108 | my $body = join('', $tfile->template('tf_activity.html', $tvars, $userinfo)); | |
| 109 | ||
| 110 | $load->render({ | |
| 111 | userinfo => $userinfo, | |
| 112 | page_key => 'activity', | |
| 113 | title => 'Activity Feed', | |
| 114 | body => $body, | |
| 115 | }); | |
| 116 | } | |
| 117 | ||
| 118 | sub export_csv{ | |
| 119 | #CSV export - Business+ plan gate, admin/manager only | |
| 120 | if(!MODS::PTMatrix::PM::is_manager_or_admin($userinfo)){ | |
| 121 | print qq~Status: 403 Forbidden\nContent-Type: text/plain\n\nManagers only\n~; | |
| 122 | return; | |
| 123 | } | |
| 124 | ||
| 125 | my $cid = MODS::PTMatrix::PM::get_company_id($userinfo); | |
| 126 | my $dbh = $db->db_connect(); | |
| 127 | ||
| 128 | my $plan = $db->db_readwrite($dbh, qq~SELECT plan_tier FROM `${database_name}`.companies WHERE id='$cid'~, $ENV{SCRIPT_NAME}, __LINE__); | |
| 129 | my $tier = $plan ? ($plan->{plan_tier} || 'trial') : 'trial'; | |
| 130 | if(!($tier eq 'business' || $tier eq 'enterprise' || $tier eq 'trial' || $userinfo->{is_super_admin})){ | |
| 131 | $db->db_disconnect($dbh); | |
| 132 | print qq~Status: 402 Payment Required\nContent-Type: text/plain\n\nCSV export is on the Business plan and up. <a href="$url->{billing}">Upgrade</a>\n~; | |
| 133 | return; | |
| 134 | } | |
| 135 | ||
| 136 | my ($range_start, $range_end) = &range_dates; | |
| 137 | my $range_end_eod = "$range_end 23:59:59"; | |
| 138 | ||
| 139 | my $sql = qq~SELECT a.id, a.action, a.target_type, a.target_id, a.occurred_at, a.ip_addr, u.email AS actor_email, u.display_name AS actor_name FROM `${database_name}`.audit_log a LEFT JOIN `${database_name}`.users u ON u.id = a.user_id WHERE a.company_id='$cid' AND a.occurred_at BETWEEN '$range_start' AND '$range_end_eod' ORDER BY a.occurred_at DESC LIMIT 50000~; | |
| 140 | my @rows_csv = $db->db_readwrite_multiple($dbh, $sql, $ENV{SCRIPT_NAME}, __LINE__); | |
| 141 | $db->db_disconnect($dbh); | |
| 142 | ||
| 143 | print qq~Content-Type: text/csv; charset=utf-8\n~; | |
| 144 | print qq~Content-Disposition: attachment; filename="audit_log_${cid}.csv"\n\n~; | |
| 145 | print "id,occurred_at,actor_email,actor_name,action,target_type,target_id,ip\n"; | |
| 146 | foreach my $r(@rows_csv){ | |
| 147 | my @cols = ($r->{id}, $r->{occurred_at}, $r->{actor_email}||'', $r->{actor_name}||'', $r->{action}, $r->{target_type}||'', $r->{target_id}||'', $r->{ip_addr}||''); | |
| 148 | my @esc = map { | |
| 149 | my $s = defined $_ ? $_ : ''; | |
| 150 | if($s =~ /[",\n\r]/){$s =~ s/"/""/g; $s = qq("$s");} | |
| 151 | $s; | |
| 152 | } @cols; | |
| 153 | print join(',', @esc), "\n"; | |
| 154 | } | |
| 155 | } | |
| 156 | ||
| 157 | #Friendly label for the action code | |
| 158 | sub action_label{ | |
| 159 | my($a) = @_; | |
| 160 | my %M = ( | |
| 161 | 'task.create' => 'created task', | |
| 162 | 'task.update' => 'updated task', | |
| 163 | 'task.move' => 'moved task', | |
| 164 | 'task.assign' => 'assigned task', | |
| 165 | 'task.comment' => 'commented on task', | |
| 166 | 'task.delete' => 'deleted task', | |
| 167 | 'project.create' => 'created project', | |
| 168 | 'project.update' => 'updated project', | |
| 169 | 'project.archive'=> 'archived project', | |
| 170 | 'project.delete' => 'deleted project', | |
| 171 | 'user.invite' => 'invited user', | |
| 172 | 'user.remove' => 'removed user', | |
| 173 | ); | |
| 174 | return $M{$a} || $a; | |
| 175 | } |