Subversion Repositories PEEPS

Rev

Rev 3 | Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 - 1
package PEEPS;
2
## PEEPS support functions...
3
 
4
use strict;
5
use Exporter 'import';
6
use CGI qw/param header start_html url/;
7
use CGI::Cookie;
8
use DBI;
9
use WebDB;
10
 
11
$SIG{__WARN__} = sub { warn sprintf("[%s] ", scalar localtime), @_ };
12
$SIG{__DIE__}  = sub { die  sprintf("[%s] ", scalar localtime), @_ };
13
 
14
our @EXPORT = qw( $ORCUSER $SYSTEM_EMAIL getRCDBH getAccessLevels authDB max authenticate canView getLeagueAffiliation getLeagueName getLeagues getShiftRef getShiftDepartment getClassID getDepartments convertDepartments convertTime getSchedule getRCid getSetting getUser getUserEmail getUserDerbyName getYears printRCHeader changeShift modShiftTime signUpCount signUpEligible findConflict changeLeadShift sendNewUserEMail sendUserMFAEMail logit orglogit isLeagueAdmin isPersonCovered isLeagueCovered isWFTDAMember remainingPolicyDays remainingOrgPolicyDays getPolicyByID getCoverageByID);
15
 
16
my $dbh = WebDB::connect ("peeps");
17
sub getRCDBH {
18
  return $dbh;
19
}
20
our $ORCUSER;
21
our $SYSTEM_EMAIL = 'rollercon.vorc@gmail.com';
22
use constant {
23
    NOONE     => 0,
24
    USER      => 1,
25
    VOLUNTEER => 1,
26
    LEAD      => 2,
27
    MANAGER   => 3,
28
    DIRECTOR  => 4,
29
    SYSADMIN  => 5,
30
    ADMIN     => 5
31
  };
32
 
33
sub getAccessLevels {
34
  my %AccessLevels = (
35
    -1 => "Locked",
36
 
37
#    1 => "Volunteer",
38
    1 => "User",
39
    2 => "Lead",
40
    3 => "Manager",
41
    4 => "Director",
42
    5 => "SysAdmin"
43
  );
44
  return \%AccessLevels;
45
}
46
 
47
sub authDB {
48
  my $src = shift;
49
  my $id = shift;
50
  my $pass = shift;
51
  my $level = shift;
52
  my $activationcode = shift // "";
53
  my $authentication = shift // "";
54
  my ($result, $authMatch, $sessionid);
55
 
56
  use CGI::Session;
57
 
58
  my $IDHASH;
59
 
60
  if ($src eq "form") {
61
    # check the username and password against the DB and return sessionid (if one exists) if valid.
62
    ($authMatch, $sessionid) = $dbh->selectrow_array ("select 1, sessionid from authentication where username = ? and password = password(?)", undef, $id, $pass);
63
    if ($authMatch) {
64
 
65
      my $session = CGI::Session->new ("driver:mysql", $sessionid ? $sessionid : undef, { DataSource => WebDB::SessionDSN });
66
      $session->param  ('is_logged_in', 1);
67
      $session->expire ('is_logged_in', '+30m');
68
      $session->flush;
69
      $sessionid = $session->id;
70
      $dbh->do ("update authentication set sessionid = ? where username = ?", undef, $sessionid, $id);
71
 
72
      %{$IDHASH} = (%{$dbh->selectrow_hashref ("select * from authentication where username = ?", undef, $id)},
73
                    %{$dbh->selectrow_hashref ("select * from person where id = (select person_id from authentication where username = ?)", undef, $id)});
74
    } else {
75
      $result->{ERRMSG} = "Incorrect Password!";
76
    }
77
  } else {
78
    # check the sessionid against the DB to make sure it's the same user.
79
    ($authMatch, $sessionid) = $dbh->selectrow_array ("select 1, sessionid from authentication where username = ? and sessionid = ?", undef, $id, $pass);
80
    if ($authMatch) {
81
      # the sessionid matches their DB entry, but we need to see if it's expired.
82
      my $session = CGI::Session->load ("driver:mysql", $sessionid, { DataSource => WebDB::SessionDSN });
83
      $sessionid = $session->id;
84
      if ($session->is_empty) {
85
        $result->{ERRMSG} = "Session Not Found!";
86
        $authMatch = 0;
87
      } elsif ($session->is_expired) {
88
        $result->{ERRMSG} = "Session Expired!";
89
        $authMatch = 0;
90
      } elsif (!$session->param  ('is_logged_in')) {
91
        $result->{ERRMSG} = "Session Timed Out (>30 minutes idle)!";
92
        $authMatch = 0;
93
      } else {
94
        $session->expire ('is_logged_in', '+30m');
95
        $session->flush;
96
      }
97
      %{$IDHASH} = (%{$dbh->selectrow_hashref ("select * from authentication where username = ?", undef, $id)},
98
                    %{$dbh->selectrow_hashref ("select * from person where id = (select person_id from authentication where username = ?)", undef, $id)});
99
    } else {
100
      $result->{ERRMSG} = "SECURITY ALERT: Bogus Session!";
101
    }
102
  }
103
 
104
  if ($authMatch) {
105
    # good login, but have we seen this browser before?
106
    my $query = new CGI;
107
    my $PEEPSMFACOOKIE = $query->cookie('PEEPS_MFA_UUID');
108
    my ($MFACHECK) = $dbh->selectrow_array ("select 1 from MFA where person_id = (select person_id from authentication where username = ?) and MFA_UUID = ?", undef, $id, $PEEPSMFACOOKIE);
109
 
110
    if (!$MFACHECK) {
111
      $result->{ERRMSG} = "MFA Check Required.";
112
    }
113
  }
114
 
115
 
116
 
117
 
118
#  my $tempDepartments = convertDepartments ($IDHASH->{department});
119
#  my $MAXACCESS = scalar keys %{ $tempDepartments } ? max ($IDHASH->{'access'}, values %{ $tempDepartments } ) : $IDHASH->{'access'};
120
  my $MAXACCESS = 1;
121
  my ($failed_attempts) = $dbh->selectrow_array ("select count(*) from log where person_id = ? and event = ? and timestampdiff(MINUTE, timestamp, now()) < ? and timestamp > (select timestamp from log where person_id = ? and event = ? order by timestamp desc limit 1)", undef, $IDHASH->{person_id}, "Incorrect Password", 30, $IDHASH->{person_id}, "Logged In");
122
 
123
  if (!$IDHASH->{'person_id'}) {
124
    $result->{ERRMSG} = "Username not found!";
125
    $result->{cookie_string} = '';
126
    $result->{person_id} = '';
127
    logit(0, "Account not found: $id");
128
    $result->{authenticated} = 'false';
129
    return $result;
130
  } elsif ($failed_attempts >= 3) {
131
    $result->{ERRMSG} = "Too Many Failed Login Attempts!<br>(Please wait 30 minutes before trying again.)";
132
    $result->{cookie_string} = '';
133
    $result->{person_id} = $IDHASH->{'person_id'};
134
    logit($IDHASH->{'person_id'}, "User Login Timeout");
135
    $result->{authenticated} = 'false';
136
    return $result;
137
  } elsif (!$authMatch) {
138
    $result->{cookie_string} = '';
139
    $result->{person_id} = $IDHASH->{'person_id'};
140
    logit($IDHASH->{'person_id'}, $result->{ERRMSG});
141
    ($failed_attempts) = $dbh->selectrow_array ("select count(*) from log where person_id = ? and event = ? and timestampdiff(MINUTE, timestamp, now()) < ? and timestamp > (select timestamp from log where person_id = ? and event = ? order by timestamp desc limit 1)", undef, $IDHASH->{person_id}, "Incorrect Password", 30, $IDHASH->{person_id}, "Logged In");
142
    if ($failed_attempts >=3) {
143
      $result->{ERRMSG} .= "<br>Too Many Failed Login Attempts!<br>(Please wait 30 minutes before trying again.)";
144
      logit($IDHASH->{'person_id'}, "Excessive Login Failures, 30 Minute Timeout");
145
    }
146
    $result->{authenticated} = 'false';
147
    return $result;
148
  } elsif ($IDHASH->{'locked'}) {
149
    $result->{ERRMSG} = "Account Locked!";
150
    $result->{cookie_string} = '';
151
    $result->{person_id} = $IDHASH->{'person_id'};
152
    logit($IDHASH->{'person_id'}, "Login attempted for Locked account.");
153
    $result->{authenticated} = 'false';
154
    return $result;
155
  } elsif ($IDHASH->{'activation'} ne "active") {
156
    # It's an inactive account...
157
    if ($activationcode eq "resend") {
158
      # warn "Resending activation code...";
159
      sendNewUserEMail ("New User", $IDHASH);
160
      $result->{ERRMSG} = "Activation code resent. Please check your email.";
161
      $result->{cookie_string} = "${id}&${sessionid}&0";
162
      $result->{person_id} = $IDHASH->{'person_id'};
163
      logit($IDHASH->{'person_id'}, "Activation code resent.");
164
      $result->{authenticated} = 'inactive';
165
      return $result;
166
    } elsif ($activationcode) {
167
      # They submitted an activation code
168
      if ($activationcode eq $IDHASH->{'activation'}) {
169
        # ...and it was good.
170
        $dbh->do ("update authentication set activation = 'active', locked = 0, last_login = now() where person_id = ? and activation = ?", undef, $IDHASH->{'person_id'}, $activationcode);
171
        logit($IDHASH->{'person_id'}, "Activated their account and logged In");
172
        # sendNewUserEMail ("Activate", $IDHASH);
173
        $IDHASH->{'access'} = 1;
174
        $IDHASH->{'activation'} = "active";
175
        $MAXACCESS = max ($MAXACCESS, 1);
176
      } else {
177
        # ...but it wasn't good.
178
        $result->{ERRMSG} = "Activation failed, invalid code submitted.";
179
        $result->{cookie_string} = "${id}&${sessionid}&0";;
180
        $result->{person_id} = $IDHASH->{'person_id'};
181
        logit($IDHASH->{'person_id'}, "Activation failed, invalid code submitted.");
182
        $result->{authenticated} = 'inactive';
183
        return $result;
184
      }
185
    } else {
186
      # No activation code was submitted.
187
      $result->{ERRMSG} = "Inactive account! Please check your email for activation link/code." unless $result->{ERRMSG};
188
      $result->{cookie_string} = "${id}&${sessionid}&0";
189
      $result->{person_id} = $IDHASH->{'person_id'};
190
      logit($IDHASH->{'person_id'}, "Login attempted without activation code.");
191
      $result->{authenticated} = 'inactive';
192
      return $result;
193
    }
194
  } elsif ($result->{ERRMSG} eq "MFA Check Required.") {
195
 
196
    # Need to check MFA...
197
    if ($authentication eq "resend") {
198
      sendUserMFAEMail ($IDHASH);
199
      $result->{ERRMSG} .= "<br>Activation code resent. Please check your email.";
200
      $result->{cookie_string} = "${id}&${sessionid}&0";
201
      $result->{person_id} = $IDHASH->{'person_id'};
202
      logit($IDHASH->{'person_id'}, "MFA code resent.");
203
      $result->{authenticated} = 'needsMFA';
204
      return $result;
205
    } elsif ($authentication) {
206
      # They submitted an authentication code
207
      if ($authentication =~ /^\d{6}$/ and $authentication eq $IDHASH->{'mfa'}) {
208
        # check to see how old it is...
209
        my ($code_age) = $dbh->selectrow_array ("select timestampdiff(MINUTE, mfa_timestamp, now()) from authentication where person_id = ? and mfa = ?", undef, $IDHASH->{'person_id'}, $IDHASH->{'mfa'}) // 99;
210
        if ($code_age > 10) {
211
          # ...but it was too old.
212
          $result->{ERRMSG} = "MFA Authentication failed, code is too old. Resending...";
213
          sendUserMFAEMail ($IDHASH);
214
          $result->{cookie_string} = "${id}&${sessionid}&0";
215
          $result->{person_id} = $IDHASH->{'person_id'};
216
          logit($IDHASH->{'person_id'}, "MFA Authentication failed, code is too old.");
217
          $result->{authenticated} = 'needsMFA';
218
          return $result;
219
        } else {
220
          # ...and it was good.
221
 
222
          use UUID::Tiny qw(create_UUID_as_string UUID_V4);
223
          $result->{MFA_UUID} = create_UUID_as_string(UUID_V4);
224
          $dbh->do ("insert into MFA (person_id, MFA_UUID) values (?, ?)", undef, $IDHASH->{'person_id'}, $result->{MFA_UUID});
225
 
226
          logit($IDHASH->{'person_id'}, "Authenticated with an MFA code.");
227
          $IDHASH->{'access'} = 1;
228
          $MAXACCESS = max ($MAXACCESS, 1);
229
          $result->{cookie_string} = "${id}&${sessionid}&".$MAXACCESS;
230
          $result->{authenticated} = 'confirmedMFA';
231
          return $result;
232
        }
233
      } else {
234
        # ...but it wasn't good.
235
        $result->{ERRMSG} = "MFA Authentication failed, invalid code submitted.";
236
        $result->{cookie_string} = "${id}&${sessionid}&0";
237
        $result->{person_id} = $IDHASH->{'person_id'};
238
        logit($IDHASH->{'person_id'}, "MFA Authentication failed, invalid code submitted.");
239
        $result->{authenticated} = 'needsMFA';
240
        return $result;
241
      }
242
    } else {
243
      # No activation code was submitted.
244
      sendUserMFAEMail ($IDHASH);
245
      $result->{ERRMSG} .= " Please check your email for activation code." unless $result->{ERRMSG};
246
      $result->{cookie_string} = "${id}&${sessionid}&0";
247
      $result->{person_id} = $IDHASH->{'person_id'};
248
      logit($IDHASH->{'person_id'}, "Login attempted from unrecognized location, MFA needed.");
249
      $result->{authenticated} = 'needsMFA';
250
      return $result;
251
    }
252
 
253
 
254
 
255
  }
256
 
257
  if ($MAXACCESS < $level) {
258
    if (getSetting ("MAINTENANCE")) {
259
      $result->{ERRMSG} = "MAINTENANCE MODE: Logins are temporarily disabled.";
260
    } else {
261
      $result->{ERRMSG} = "Your account either needs to be activated, or doesn't have access to this page!";
262
      logit($IDHASH->{'person_id'}, "Insufficient Privileges");
263
    }
264
    $result->{cookie_string} = "${id}&${sessionid}&$IDHASH->{'access'}";
265
    $result->{person_id} = $IDHASH->{'person_id'};
266
    $result->{authenticated} = 'false';
267
  } else {
268
    $result->{ERRMSG} = '';
269
#    $IDHASH->{department} = convertDepartments ($IDHASH->{department});
270
#    $IDHASH->{'access'} = max ($IDHASH->{'access'}, values %{$IDHASH->{department}});
271
    ($IDHASH->{'SYSADMIN'}) = $dbh->selectrow_array ("select 1 from role where person_id = ? and member_org_id = (select id from organization where league_name = ?) and role = ?", undef, $IDHASH->{person_id}, "WFTDA Leadership", "System Admin");
272
    $IDHASH->{'access'} = $IDHASH->{'SYSADMIN'} ? 5 : 1;
273
    $result->{cookie_string} = "${id}&${sessionid}&$IDHASH->{'access'}";
274
    $result->{person_id} = $IDHASH->{'person_id'};
275
    logit($IDHASH->{'person_id'}, "Logged In") if $src eq "form";
276
    $dbh->do ("update authentication set last_login = now() where person_id = ?", undef, $IDHASH->{'person_id'}) if $src eq "form";
277
    $result->{authenticated} = 'true';
278
 
279
    $ORCUSER = $IDHASH;
280
  }
281
  return $result;
282
}
283
 
284
sub max {
285
    my ($max, $next, @vars) = @_;
286
    return $max if not $next;
287
    return max( $max > $next ? $max : $next, @vars );
288
}
289
 
290
 
291
sub authenticate {                  # Verifies the user has logged in or puts up a log in screen
292
  my $MAINTMODE = getSetting ("MAINTENANCE");
293
  my $MINLEVEL = $MAINTMODE ? $MAINTMODE : shift // 1;
294
 
295
  my ($ERRMSG, $authenticated, %FORM);
296
  my $sth = $dbh->prepare("select * from authentication where username = '?'");
297
 
298
  my $query = new CGI;
299
# Check to see if the user has already logged in (there should be cookies with their authentication)?
300
  my $PEEPSAUTH = $query->cookie('PEEPSAUTH');
301
  $FORM{'ID'} = WebDB::trim $query->param('userid') || '';
302
  $FORM{'PASS'} = WebDB::trim $query->param('pass') || '';
303
  $FORM{'SUB'} = $query->param('login') || '';
304
  $FORM{'activate'} = WebDB::trim $query->param('activate') // '';
305
  $FORM{'authenticate'} = WebDB::trim $query->param('authenticate') // '';
306
  $FORM{'saveMFA'} = WebDB::trim $query->param('saveMFA') // '';
307
 
308
  if ($PEEPSAUTH) {
309
    # We have an authenication cookie.  Double-check it
310
    my ($PEEPSID, $SESSID, $LVL) = split /&/, $PEEPSAUTH;
311
    $authenticated = authDB('cookie', $PEEPSID, $SESSID, $MINLEVEL, $FORM{'activate'}, $FORM{'authenticate'});
312
  } elsif ($FORM{'SUB'}) {
313
    # a log in form was submited
314
    if ($FORM{'SUB'} eq "Submit") {
315
      $authenticated = authDB('form', $FORM{'ID'}, $FORM{'PASS'}, $MINLEVEL, $FORM{'activate'}, $FORM{'authenticate'});
316
    } elsif ($FORM{'SUB'} eq "New User") {
317
      # Print the new user form and exit
318
    }
319
  } else {
320
    $authenticated->{authenticated} = 'false';
321
  }
322
 
323
  if ($authenticated->{authenticated} eq 'true') {
324
    use CGI::Session;
325
    my $session = CGI::Session->new ("driver:mysql", $ORCUSER->{sessionid}, { DataSource => WebDB::SessionDSN });
326
#    $session->expire ('~logged_in', '30m');
327
#    $session->flush;
328
    my $sessionid = $session->id ();
329
 
330
    # Limit how long users are allowed to stay logged in at once.
331
    #  [there's no reason to limit PEEPS session length]
332
    # my ($session_length) = $dbh->selectrow_array ("select timestampdiff(MINUTE, last_login, now()) from authentication where person_id = ?", undef, $ORCUSER->{person_id});
333
    # if ($session_length > getSetting ("MAX_SESSION_MINUTES")) {
334
    #   $ENV{'QUERY_STRING'} = "LOGOUT";
335
    #   $authenticated->{ERRMSG} = "Maximum session time exceeded.<br>";
336
    # }
337
 
338
    if ($ENV{'QUERY_STRING'} eq "LOGOUT") {
339
      # warn "logging $ORCUSER->{derby_name} out...";
340
      $authenticated->{ERRMSG} .= "Logged Out.<br>";
341
      $authenticated->{cookie_string} = "";
342
      #$session->clear ("is_logged_in");
343
      #$session->flush;
344
      $authenticated->{authenticated} = 'false';
345
      $ENV{REQUEST_URI} =~ s/LOGOUT//;
346
      logit ($ORCUSER->{person_id}, "Logged Out");
347
      $dbh->do ("update authentication set last_active = now(), sessionid = null where person_id = ?", undef, $ORCUSER->{person_id});
348
      $dbh->do ("delete from sessions where sessions.id = ?", undef, $sessionid);
349
      $ORCUSER = "";
350
    } else {
351
      $dbh->do ("update authentication set last_active = now(), sessionid = ? where person_id = ?", undef, $sessionid, $ORCUSER->{person_id});
352
      return $authenticated->{cookie_string};
353
    }
354
  } elsif ($authenticated->{authenticated} eq "confirmedMFA") {
355
    # Set the MFA cookie and redirect the user to where they were going.
356
 
357
    my $PEEPSAUTH_cookie = CGI::Cookie->new(-name=>'PEEPSAUTH',-value=>$authenticated->{cookie_string});
358
    my $PEEPSMFA_cookie = $FORM{saveMFA} ?
359
      CGI::Cookie->new(-name=>'PEEPS_MFA_UUID',-value=>$authenticated->{MFA_UUID},-expires=>'+5y') :
360
      CGI::Cookie->new(-name=>'PEEPS_MFA_UUID',-value=>$authenticated->{MFA_UUID});
361
 
362
    my $destination = url ();
363
    print header(-cookie=>[$PEEPSAUTH_cookie, $PEEPSMFA_cookie]);
364
    printRCHeader("MFA Confirmed", $destination);
365
    print<<goforth;
366
      Your MFA Code has been confirmed.  You may continue on your way.<br><br>
367
 
368
      <a href="$destination">Continue.</a>
369
goforth
370
    exit;
371
  }
372
 
373
 
374
# If we get here, the user has failed authentication; throw up the log-in screen and die.
375
 
376
  my $PEEPSAUTH_cookie = CGI::Cookie->new(-name=>'PEEPSAUTH',-value=>$authenticated->{cookie_string});
377
 
378
  if ($authenticated->{ERRMSG}) {
379
    $authenticated->{ERRMSG} = "<TR><TD colspan=2 align=center><font color=red><b>".$authenticated->{ERRMSG}."</b></font>&nbsp</TD></TR>";
380
  } else {
381
    $authenticated->{ERRMSG} = "";
382
  }
383
 
384
  print header(-cookie=>$PEEPSAUTH_cookie);
385
 
386
  printRCHeader("Please Sign In");
387
  print<<authpage;
388
  <form action="$ENV{REQUEST_URI}" method=POST name=Req id=Req>
389
    <TR><TD colspan=2 align=center><b><font size=+2>Please Sign In</font>
390
    <TABLE>
391
    </TD></TR>
392
    <TR><TD colspan=2>&nbsp</TD></TR>
393
    $authenticated->{ERRMSG}
394
authpage
395
 
396
  if ($ENV{'QUERY_STRING'} eq "LOGOUT") {
397
    print "<TR><TD colspan=2>&nbsp</TD></TR>";
398
    print "<TR><TD colspan=2><button onClick=\"location.href='';\">Log In</button></TD></TR>";
399
    print "</TABLE></BODY></HTML>";
400
    exit;
401
  }
402
 
403
  if ($authenticated->{authenticated} eq "inactive") {
404
 
405
    print<<activationpage;
406
      <TR><TD colspan=2 align=center>&nbsp;</TD></TR>
407
      <TR><TD align=right><B>Activation Code:</TD><TD><INPUT type=text id=activate name=activate></TD></TR>
408
      <TR><TD></TD><TD><INPUT type=submit name=login value=Submit></TD></TR>
409
      <TR><TD colspan=2 align=center>&nbsp;</TD></TR>
410
      <TR><TD colspan=2 align=center><A HREF='' onClick='document.getElementById("activate").value="resend"; Req.submit(); return false;'>[Resend your activation email]</A></TD></TR>
411
      <TR><TD colspan=2 align=center><A HREF='' onClick="location.href='?LOGOUT';">[Log Out]</A></TD></TR>
412
      </TABLE></FORM>
413
activationpage
414
 
415
  } elsif ($authenticated->{authenticated} eq "needsMFA") {
416
 
417
    print<<MFApage;
418
      <TR><TD colspan=2 align=center>&nbsp;</TD></TR>
419
      <TR><TD align=right><B>Email Authentication Code:</TD><TD><INPUT type=text id=authenticate name=authenticate></TD></TR>
420
      <TR><TD></TD><TD><INPUT type=submit name=login value=Submit></TD></TR>
421
      <TR><TD colspan=2 align=center>&nbsp;</TD></TR>
422
      <TR><TD colspan=2 align=center>Save this browser: <label class="switch"><input name="saveMFA" type="checkbox" value="1"><span class="slider round"></span></label></TD></TR>
423
      <TR><TD colspan=2 align=center><A HREF='' onClick='document.getElementById("authenticate").value="resend"; Req.submit(); return false;'>[Send new authentication code]</A></TD></TR>
424
      <TR><TD colspan=2 align=center><A HREF='' onClick="location.href='?LOGOUT';">[Log Out]</A></TD></TR>
425
      </TABLE></FORM>
426
MFApage
427
 
428
  } else {
429
 
430
    print<<authpage2;
431
      <TR><TD colspan=2>&nbsp</TD></TR>
432
      <TR>
433
        <TD align=right><B>Username:</TD><TD><INPUT type=text id=login name=userid></TD>
434
      </TR>
435
      <TR>
436
        <TD align=right><B>Password:</TD><TD><INPUT type=password name=pass></TD>
437
      </TR>
438
      <TR><TD></TD><TD><input type=hidden name=activate id=activate value=$FORM{'activate'}><input type=hidden name=authenticate id=authenticate value=$FORM{'authenticate'}><INPUT type=submit name=login value=Submit></TD></TR>
439
      <TR><TD colspan=2 align=center><A HREF="view_user?submit=New%20User">[register as a new user]</A></TD></TR>
440
      <TR><TD colspan=2 align=center><A HREF="recoverAccount">[recover your account]</A></TD></TR>
441
    </TABLE>
442
    </FORM>
443
 
444
    <SCRIPT language="JavaScript">
445
    <!--
446
    document.getElementById("login").focus();
447
 
448
    function Login () {
449
      document.getElementById('Req').action = "$ENV{SCRIPT_NAME}";
450
      document.getElementById('Req').submit.click();
451
      return true;
452
    }
453
 
454
    //-->
455
    </SCRIPT>
456
 
457
authpage2
458
  }
459
 
460
#foreach (keys %ENV) {
461
# print "$_: $ENV{$_}<br>";
462
#}
463
# &JScript;
464
  exit;
465
}
466
 
467
sub canView {
468
  my $A = shift // "";
469
  my $B = shift // "";
470
  # Is A a lead or higher of one of B's Depts? (or they're looking at themselves)
471
  # parameters should be a Hashref to the users' details
472
 
473
  return 1 if $A->{person_id} == $B->{person_id}; # It's the same person.
474
 
475
  my ($admin_check) = $dbh->selectrow_array ("select 1 from role where person_id = ? and member_org_id = ? and role = ?", undef, $A->{person_id}, 4276, "System Admin"); # 4276 is "WFTDA Leadership"
476
 
477
  return 1 if $admin_check or $A->{access} > 4; # viewer and target are the same person or it's a SysAdmin.
478
 
479
  my ($league_admin) = $dbh->selectrow_array ("select * from role where person_id = ? and member_org_id in (select distinct member_org_id from role where person_id = ?) and role = ?", undef, $A->{person_id}, $B->{person_id}, "League Admin");
480
 
481
  return 1 if $league_admin;
482
 
483
 
484
  return 0;
485
}
486
 
487
sub getShiftDepartment {
488
  my $shiftID = shift // "";
489
  my $dept;
490
 
491
  if ($shiftID =~ /^\d+$/) {
492
    ($dept) = $dbh->selectrow_array ("select dept from shift where id = ?", undef, $shiftID);
493
  } else {
494
    my ($id, $role) = split /-/, $shiftID;
495
    if ($role =~ /^CLA/) {
496
      $dept = "CLA";
497
    } else {
498
      ($dept) = $dbh->selectrow_array ("select distinct department from staff_template where role like ?", undef, $role.'%');
499
    }
500
  }
501
#  } elsif ($shiftID =~ /^\d+-ANN/) {
502
#    $dept = "ANN";
503
#  } else {
504
#    $dept = "OFF";
505
#  }
506
 
507
  return $dept;
508
}
509
 
510
sub getClassID {
511
  my $shift = shift // "";
512
  return unless $shift =~ /^\d+$/;
513
 
514
  my $shiftref = getShiftRef ($shift);
515
  my ($classid) = $dbh->selectrow_array ("select id from class where date = ? and start_time = ? and location = ?", undef, $shiftref->{date}, $shiftref->{start_time}, $shiftref->{location});
516
  return $classid unless !$classid;
517
 
518
  warn "ERROR: No class.id found for shift $shiftref->{id}";
519
  return "";
520
}
521
 
522
sub getShiftRef {
523
  my $shiftID = shift // "";
524
  return unless $shiftID =~ /^\d+$/;
525
 
526
  my ($shiftref) = $dbh->selectrow_hashref ("select * from shift where id = ?", undef, $shiftID);
527
  return $shiftref unless $shiftref->{id} != $shiftID;
528
 
529
  warn "ERROR: Couldn't find shift with ID [$shiftID]";
530
  return "";
531
}
532
 
533
sub getLeagueAffiliation {
534
  my $PEEPSid = shift // "";
535
 
536
  my $results;
537
 
538
  my $sth = $dbh->prepare ("select member_org_id, league_name, role, type from role join organization on member_org_id = organization.id left join person on person_id = person.id where person_id = ?");
539
  if ($PEEPSid =~ /^\d+$/) {
540
    $sth->execute ($PEEPSid);
541
    while (my ($orgid, $orgname, $role, $orgtype) = $sth->fetchrow_array ()) {
542
      push (@{$results->{$orgid}}, $role);
543
    }
544
  }
545
  return $results;
546
}
547
 
548
sub getLeagueName {
549
  my $id = shift // "";
550
  my ($name) = $dbh->selectrow_array ("select league_name from organization where id = ?", undef, $id);
551
  return $name;
552
}
553
 
554
sub getLeagues {
555
  my $exclude_id = shift // 0;
556
  return $dbh->selectall_arrayref ("select id, concat_ws(' - ', league_name, type, status) as league from organization where status in ('Active', 'Voluntary Inactive')  and id not in (select member_org_id from role where person_id = ?) order by league_name", undef, $exclude_id);
557
}
558
 
559
sub getDepartments {
560
  my $RCid = shift // "";
561
  # If we get an RCid, return the list of departments and levels for that user.
562
  #   Otherwise (no parameter), return the list of departments with their display names.
563
 
564
  if ($RCid) {
565
    my $sth = $dbh->prepare("select department from official where RCid = ?");
566
    $sth->execute($RCid);
567
    my ($dlist) = $sth->fetchrow;
568
    return convertDepartments ($dlist);
569
  } else {
570
    my %HASH;
571
    my $sth = $dbh->prepare("select TLA, name from department");
572
    $sth->execute();
573
    while (my ($tla, $name) = $sth->fetchrow) {
574
      $HASH{$tla} = $name;
575
    }
576
    return \%HASH;
577
  }
578
 
579
}
580
 
581
sub convertDepartments {
582
  # For the department membership, converts the DB string back and forth to a hashref...
583
  my $input = shift // "";
584
  my $output;
585
 
586
  if (ref $input eq "HASH") {
587
    $output = join ":", map { $_."-".$input->{$_} } sort keys %{$input};
588
  } else {
589
    foreach (split /:/, $input) {
590
      my ($tla, $level) = split /-/;
591
      $output->{$tla} = $level;
592
    }
593
    $output = {} unless ref $output eq "HASH";
594
  }
595
 
596
  return $output;
597
}
598
 
599
sub getPolicyByID {
600
  my $pid = shift // "";
601
 
602
  (warn "ERROR: No PolicyID passed to getPolicyByID()" and return) unless $pid =~ /^\d+$/;
603
 
604
  my $policy = $dbh->selectrow_hashref ("select * from policy where id = ?", undef, $pid);
605
 
606
  (warn "ERROR: No Policy found with id: $pid" and return) unless $policy->{id} =~ /^\d+$/;
607
 
608
  return $policy;
609
}
610
 
611
sub getCoverageByID {
612
  my $pid = shift // "";
613
  my $person = shift // "";
614
 
615
  (warn "ERROR: No PolicyID passed to getCoverageByID()" and return) unless $pid    =~ /^\d+$/;
616
  (warn "ERROR: No PersonID passed to getCoverageByID()" and return) unless $person =~ /^\d+$/;
617
 
618
  my $policy = $dbh->selectrow_hashref ("select * from coverage where id = ? and person_id = ?", undef, $pid, $person);
619
 
620
  (warn "ERROR: No Coverage found with id: $pid and person_id: $person" and return) unless $policy->{id} =~ /^\d+$/;
621
 
622
  return $policy;
623
}
624
 
625
sub convertTime {
626
  my $time = shift || return;
627
 
628
  if ($time =~ / - /) {
629
    return join " - ", map { convertTime ($_) } split / - /, $time;
630
  }
631
 
632
  $time =~ s/^(\d{1,2}:\d{2}):\d{2}$/$1/;
633
  $time =~ s/^0//;
634
 
635
  if ($ORCUSER->{timeformat} eq "24hr") {
636
    if ($time =~ /^\d{1,2}:\d{2}$/) { return $time; }
637
  } else {
638
    my ($hr, $min) = split /:/, $time;
639
    my $ampm = " am";
640
    if ($hr >= 12) {
641
      $hr -= 12 unless $hr == 12;
642
      $ampm = " pm";
643
    } elsif ($hr == 0) {
644
      $hr = 12;
645
    }
646
    return $hr.":".$min.$ampm;
647
  }
648
}
649
 
650
sub getSchedule {
651
  my $RCid = shift // return "ERROR: No RCid provided to getSchedule";
652
  my $filter = shift // "";
653
  my $output = shift // "";
654
  my $year = 1900 + (localtime)[5];
655
 
656
  my @whereclause;
657
  if ($filter eq "all") {
658
    push @whereclause, "year(date) >= year(now())";
659
  } elsif ($filter eq "prior") {
660
    push @whereclause, "year(date) < year(now())";
661
  } else {
662
    push @whereclause, "date >= date(now())";
663
  }
664
#  if ($RCid ne $ORCUSER->{RCid}) {
665
#    push @whereclause, "dept != 'PER'";
666
#  }
667
 
668
  use DateTime;
669
  my $dt = DateTime->today (time_zone => 'America/Los_Angeles');
670
  $dt =~ s/T00\:00\:00$//;
671
  my $now = DateTime->now (time_zone => 'America/Los_Angeles');
672
 
673
 
674
  use HTML::Tiny;
675
  my $h = HTML::Tiny->new( mode => 'html' );
676
 
677
  my $where = scalar @whereclause ? "where ".join " and ", @whereclause : "";
678
  my @shifts;
679
  my $sth = $dbh->prepare("select * from (select id, date, dayofweek, track as location, time, role, teams, signup, 'OFF' as dept, volhours from v_shift_officiating where RCid = ? union
680
                                          select id, date, dayofweek, track as location, time, role, teams, signup, 'ANN' as dept, volhours from v_shift_announcer where RCid = ? union
681
                                          select id, date, dayofweek, location, time, role, '' as teams, type as signup, dept, volhours from v_shift where RCid = ? union
682
                                          select id, date, dayofweek, location, time, role, name as teams, 'mvpclass' as signup, 'CLA' as dept, 0 as volhours from v_class_signup_new where RCid = ?) temp
683
                           $where order by date, time");
684
  $sth->execute($RCid, $RCid, $RCid, $RCid);
685
  my $hours = 0;
686
  while (my $s = $sth->fetchrow_hashref) {
687
    my ($yyyy, $mm, $dd) = split /\-/, $s->{date};
688
    my $cutoff = DateTime->new(
689
        year => $yyyy,
690
        month => $mm,
691
        day => $dd,
692
        hour => 5,
693
        minute => 0,
694
        second => 0,
695
        time_zone => 'America/Los_Angeles'
696
    );
697
 
698
 
699
    if (!$s->{teams} or $s->{dept} eq "CLA") {
700
      # it's a time-based shift
701
      if ($s->{dept} eq "PER") {
702
        if ($RCid eq $ORCUSER->{RCid}) {
703
          # DROP
704
          $s->{buttons} = $h->button ({ onClick=>"event.stopPropagation(); if (confirm('Really? You want to delete this personal time?')==true) { location.href='personal_time?choice=Delete&id=$s->{id}'; return false; }" }, "DEL")."&nbsp;".$h->button ({ onClick=>"event.stopPropagation(); location.href='personal_time?choice=Update&id=$s->{id}'" }, "EDIT");
705
        } else {
706
          $s->{location} = "";
707
          $s->{role} = "";
708
        }
709
      } elsif (($RCid == $ORCUSER->{RCid} and $s->{signup} !~ /^selected/ and $now < $cutoff) or ($ORCUSER->{department}->{$s->{dept}} >= 2 or $ORCUSER->{access} >= 5)) {
710
        # DROP
711
        my ($shiftORclass, $linkargs) = ("shift", "");
712
        if ($s->{dept} eq "CLA") {
713
          $shiftORclass = "class";
714
          $linkargs = "&role=$s->{role}";
715
          $s->{role} = $s->{teams};
716
          $s->{teams} = "";
717
        }
718
        $s->{buttons} = $h->button ({ onClick=>"if (confirm('Really? You want to drop this $shiftORclass?')==true) { window.open('make_shift_change?change=del&RCid=$RCid&id=$s->{id}$linkargs','Confirm Class Change','resizable,height=260,width=370'); return false; }" }, "DROP");
719
        if ($ORCUSER->{department}->{$s->{dept}} >= 2 or $ORCUSER->{access} >= 5) {
720
          # NO SHOW
721
          $s->{buttons} .= "&nbsp;".$h->button ({ onClick=>"if (confirm('Really? They were a no show?')==true) { window.open('make_shift_change?noshow=true&change=del&RCid=$RCid&id=$s->{id}$linkargs','Confirm Shift Change','resizable,height=260,width=370'); return false; }" }, "NO SHOW");
722
        }
723
 
724
      }
725
#     $hours += $s->{volhours} unless $s->{dept} eq "PER" or $s->{dept} eq "CLA";
726
 
727
    } elsif (($RCid == $ORCUSER->{RCid} and $s->{signup} !~ /^selected/ and $now < $cutoff) or ($ORCUSER->{department}->{$s->{dept}} >= 2 or $ORCUSER->{access} >= 5)) {
728
      # it's a game shift
729
      #DROP
730
      $s->{buttons} = $h->button ({ onClick=>"if (confirm('Really? You want to drop this shift?')==true) { window.open('make_shift_change?change=del&RCid=$RCid&id=$s->{id}&role=$s->{role}','Confirm Shift Change','resizable,height=260,width=370'); return false; }" }, "DROP");
731
      if ($ORCUSER->{department}->{$s->{dept}} >= 2 or $ORCUSER->{access} >= 5) {
732
        # NO SHOW
733
        $s->{buttons} .= "&nbsp;".$h->button ({ onClick=>"if (confirm('Really? They were a no show?')==true) { window.open('make_shift_change?noshow=true&change=del&RCid=$RCid&id=$s->{id}&role=$s->{role}','Confirm Shift Change','resizable,height=260,width=370'); return false; }" }, "NO SHOW");
734
      }
735
#      $hours += $s->{volhours};
736
    }
737
    $s->{role} =~ s/\-\d+$//;
738
 
739
#   push @shifts, $h->li ({ class=> $s->{date} eq $dt ? "nowrap highlighted" : "nowrap shaded" }, join '&nbsp;&nbsp;', $s->{date}, $s->{dayofweek}, $s->{time}, $s->{location}, getDepartments()->{$s->{dept}}, $s->{role}, $s->{teams}, $s->{buttons});
740
#   push @shifts, $h->li ({ class=> $s->{date} eq $dt ? "highlighted" : "shaded" }, join '&nbsp;&nbsp;', $s->{date}, $s->{dayofweek}, $s->{time}, $s->{location}, getDepartments()->{$s->{dept}}, $s->{role}, $s->{teams}, $s->{buttons});
741
    $s->{time} = convertTime $s->{time};
742
    if ($s->{dept} eq "PER") {
743
      push @shifts, $h->li ({ onClick => "location.replace('personal_time?id=$s->{id}');", class=> $s->{date} eq $dt ? "highlighted" : "shaded" }, $h->div ({ class=>"lisp0" }, [ $h->div ({ class=>"liLeft" }, join '&nbsp;&nbsp;', ($s->{date}, $s->{dayofweek}, $s->{time}, $s->{location}, $s->{dept} eq "CLA" ? "MVP Class:" : getDepartments()->{$s->{dept}}, $s->{role}, $s->{teams})), $h->div ({ class=>"liRight" }, $s->{buttons}) ]));
744
    } else {
745
      push @shifts, $h->li ({ class=> $s->{date} eq $dt ? "highlighted" : "shaded" }, $h->div ({ class=>"lisp0" }, [ $h->div ({ class=>"liLeft" }, join '&nbsp;&nbsp;', ($s->{date}, $s->{dayofweek}, $s->{time}, $s->{location}, $s->{dept} eq "CLA" ? "MVP Class:" : getDepartments()->{$s->{dept}}, $s->{role}, $s->{teams})), $h->div ({ class=>"liRight" }, $s->{buttons}) ]));
746
    }
747
    $hours += $s->{volhours} unless $s->{dept} eq "PER" or $s->{dept} eq "CLA";
748
  }
749
 
750
  if ($output eq "hours") {
751
    return $hours;
752
  }
753
 
754
  if (scalar @shifts) {
755
    return $h->ul ([ @shifts, $h->h5 ("Currently showing $hours hours of Volunteer Time.") ]);
756
  } elsif ($filter eq "prior") {
757
    return $h->p ({ class=>"hint" }, "[nothing to see here]");
758
  } else {
759
    return $h->p ({ class=>"hint" }, "[nothing scheduled at the moment]");
760
  }
761
}
762
 
763
sub getRCid {
764
  my $derbyname = shift;
765
  ($derbyname) = $dbh->selectrow_array ("select RCid from official where derby_name = ?", undef, $derbyname);
766
  return $derbyname;
767
}
768
 
769
sub getSetting {
770
  my $k = shift;
771
 
772
  my ($value) = $dbh->selectrow_array ("select setting.value from setting where setting.key = ?", undef, $k);
773
  return defined $value ? $value : undef;
774
}
775
 
776
sub getUser {
777
  my $ID = shift;
778
 
779
  my $sth;
780
  if ($ID =~ /^\d+$/) {
781
    $sth = $dbh->prepare("select * from person where id = ?");
782
  } elsif ($ID =~ /@/) {
783
    $sth = $dbh->prepare("select * from person where email = ?");
784
  } else {
785
    $sth = $dbh->prepare("select * from person where id = (select person_id from authentication where username = ?)");
786
  }
787
  $sth->execute($ID);
788
  my $user = $sth->fetchrow_hashref;
789
 
790
  my $auth = $dbh->selectrow_hashref ("select * from authentication where person_id = ?", undef, $user->{id});
791
 
792
  map { $user->{$_} = "" unless $user->{$_} } keys %{$user};
793
  map { $user->{$_} = $auth->{$_} ? $auth->{$_} : "" } keys %{$auth};
794
  $user->{person_id} = $user->{id};
795
  return $user->{id} ? $user : "";
796
}
797
 
798
sub getUserEmail {
799
  my $RCid = shift;
800
  my $sth = $dbh->prepare("select email from official where RCid = ?");
801
  $sth->execute($RCid);
802
  my ($email) = $sth->fetchrow_array();
803
  return $email;
804
}
805
 
806
sub getUserDerbyName {
807
  my $RCid = shift;
808
  my $sth = $dbh->prepare("select derby_name from official where RCid = ?");
809
  $sth->execute($RCid);
810
  my ($dname) = $sth->fetchrow_array();
811
  return $dname;
812
}
813
 
814
sub getYears {
815
  my $sth = $dbh->prepare("select distinct year from (select distinct year(date) as year from shift union select distinct year(date) as year from game union select distinct year(date) as year from class union select year(now()) as year) years order by year");
816
# my $sth = $dbh->prepare("select distinct year(date) from v_shift_admin_view");
817
  $sth->execute();
818
  my @years;
819
  while (my ($y) =$sth->fetchrow_array()) { push @years, $y; }
820
  return \@years;
821
}
822
 
823
sub printRCHeader {
824
  my $PAGE_TITLE = shift;
825
  my $redirect = shift // "";
826
# use CGI qw/start_html/;
827
  use HTML::Tiny;
828
  my $h = HTML::Tiny->new( mode => 'html' );
829
 
830
#  my $logout = $h->a ({ href=>"index", onClick=>"document.cookie = 'PEEPSAUTH=; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/';return true;" }, "[Log Out]");
831
  my $referrer = param ("referrer") ? param ("referrer") : $ENV{HTTP_REFERER};
832
  my $logout = (!$referrer or $referrer eq url) ? "" : $h->button ({ onClick=>"window.location.href='$referrer';" }, "Back")."&nbsp;";
833
  $logout .= url =~ /\/(index)?$/ ? "" : $h->button ({ onClick=>"window.location.href='/';" }, "Home")."&nbsp;";
834
#  $logout .= $h->button ({ onClick=>"document.cookie = 'PEEPSAUTH=; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/'; location.href='/';" }, "Log Out");
835
  $logout .= $h->button ({ onClick=>"location.href='?LOGOUT';" }, "Log Out");
836
 
837
  my $loggedinas = $ORCUSER ? "Currently logged in as: ".$h->a ({ href=>"/view_user?submit=View&person_id=$ORCUSER->{person_id}" }, $ORCUSER->{derby_name}).$h->br.$logout : "";
838
 
839
#  print start_html (-title=>"vORC - $PAGE_TITLE", -style => {'src' => "/style.css"} );
840
 
841
  my $ANALYTICS = <<MATOMO;
842
  var _mtm = window._mtm = window._mtm || [];
843
  _mtm.push({'mtm.startTime': (new Date().getTime()), 'event': 'mtm.Start'});
844
  (function() {
845
    var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
846
    g.async=true; g.src='https://analytics.whump.org/js/container_to4NCtvM.js'; s.parentNode.insertBefore(g,s);
847
  })();
848
MATOMO
849
 
850
  print $h->open ("html");
851
  print $h->head ([$h->title ("PEEPS - $PAGE_TITLE"),
852
                   $h->link  ({ rel  => "stylesheet",
853
                                type => "text/css",
854
                                href => "/style.css" }),
855
                   $redirect ? $h->meta ({ 'http-equiv'=>"refresh", content=>"0; URL=".$redirect }) : "",
856
#                   $h->script ($ANALYTICS)
857
                  ]);
858
  print $h->open ("body");
859
#  print $h->img ({referrerpolicy=>"no-referrer-when-downgrade", src=>"https://analytics.whump.org/matomo.php?idsite=2&amp;rec=1", style=>"border:0", alt=>""});
860
#<html><head><title>Officials' RollerCon Schedule Manager - $PAGE_TITLE</title>
861
#<link rel="stylesheet" type="text/css" href="/style.css">
862
#</head>
863
#<body text="#000000" bgcolor="#FFFFFF" link="#0000EE" vlink="#551A8B" alink="#FF0000">
864
  print $h->div ({ class=>"sp0" }, [ $h->div ({ class=>"spLeft" },  $h->a ({ href=>"/" }, $h->img ({ src=>"/images/wftda-insurance-logo.svg", width=>"250", height=>"75" }))),
865
                                     $h->div ({ class=>"spRight" }, [ $h->h1 (["PEEPS $PAGE_TITLE", $h->br]),
866
                                     $loggedinas,
867
                                     ])
868
                                   ]);
869
#print<<rcheader;
870
#  <TABLE>
871
# <TR class="nostripe">
872
#   <TD align=right><img SRC="/logo.jpg"></TD>
873
#   <TD align=center valign=middle><b><font size=+3>Officials' RollerCon<br>Schedule Manager<br>$PAGE_TITLE</FONT></b>
874
# <p align=right><font size=-2>$loggedinas <a href='index' onClick="document.cookie = 'PEEPSAUTH=; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/';return true;">[Log Out]</a></font></TD>
875
# </TR>
876
 
877
#rcheader
878
}
879
 
880
sub changeShift {
881
  my ($change, $shift_id, $role, $user_id) = @_;
882
  if ($shift_id =~ /(am|pm)/) {
883
    my ($td, $st, $tl) = split /\|/, $shift_id;
884
    my ($hr, $min, $ampm) = split /:|\s/, $st;
885
    if ($ampm eq "pm") { $hr += 12; }
886
    elsif ($ampm eq "am" and $hr == 12) { $hr = "00" }
887
 
888
    $st = $hr.":".$min;
889
    $shift_id = join "|", ($td, $st, $tl);
890
  } else {
891
    $shift_id =~ s/(\d+:\d+):00/$1/;
892
  }
893
#warn join " - ", $change, $shift_id, $role, $user_id;
894
  my $leadership_change = 0;
895
# my $department = getShiftDepartment ($role ? $shift_id."-".$role : $shift_id);
896
  my $department;
897
  if ($shift_id =~ /^\d+$/) {
898
    $department = getShiftDepartment ($role ? $shift_id."-".$role : $shift_id);
899
  } else {
900
    $department = "CLA";
901
    if ($change eq "del") {
902
      ($shift_id, $role) = $dbh->selectrow_array ("select id, role from v_class_signup_new where date = ? and start_time = ? and location = ?", undef, split /\|/, $shift_id);
903
    } else {
904
      if ($change eq "override") {
905
        ($shift_id, $role) = $dbh->selectrow_array ("select id, concat('CLA-', max(cast(substring_index(role, '-', -1) as UNSIGNED)) +1) as role from v_class_signup_new where date = ? and start_time = ? and location = ?", undef, split /\|/, $shift_id) unless $change ne "override";
906
      } else {
907
        ($shift_id, $role) = $dbh->selectrow_array ("select id, concat('CLA-', max(cast(substring_index(role, '-', -1) as UNSIGNED)) +1) as role, count(role), capacity from v_class_signup_new where date = ? and start_time = ? and location = ? having capacity > count(role)", undef, split /\|/, $shift_id);
908
      }
909
    }
910
    $role = "CLA-1" unless $role; # If no one has signed up for the class yet, the SQL above doesn't retrieve the first available
911
  }
912
# my $game_based = $role ? "game" : "shift";
913
  my $game_based = $role =~ /^CLA-/ ? "class" : $role ? "game" : "shift";
914
  my $sth;
915
 
916
  if ($change eq "add" or $change eq "override") {
917
    my $taken;
918
    if ($department eq "CLA") {
919
      ($taken) = $shift_id ? 0 : 1;
920
    } elsif ($game_based eq "game") {
921
      ($taken) = $dbh->selectrow_array ("select count(*) from assignment where Gid = ? and role = ?", undef, $shift_id, $role);
922
    } else {
923
      ($taken) = $dbh->selectrow_array ('select count(*) from shift where id = ? and (isnull(assignee_id) = 0 or assignee_id <> "")', undef, $shift_id);
924
    }
925
    if ($taken) {
926
      return ($department eq "CLA") ? "<br>Denied! This class is already full ($shift_id).<br>\n" : "<br>Denied! This shift is already taken ($shift_id).<br>\n";
927
    }
928
  }
929
 
930
  if (lc ($user_id) ne lc ($ORCUSER->{RCid})) { # they're changing someone else's schedule...
931
    if (($department eq "CLA" and $ORCUSER->{department}->{MVP} >= 2) or $ORCUSER->{department}->{$department} >= 2 or $ORCUSER->{access} >= 5 or $ORCUSER->{department}->{VCI} >= 2) {
932
      # the user making the change is either a lead in the dept, a sysadmin, or a VCI lead
933
      logit ($ORCUSER->{RCid}, "$ORCUSER->{derby_name} changed someone else's schedule. ($change, $shift_id, $role, $user_id)");
934
      logit ($user_id, "Schedule was changed by $ORCUSER->{derby_name}. ($change, $shift_id, $role, $user_id)");
935
      $leadership_change = 1;
936
    } else {
937
      logit ($ORCUSER->{RCid}, "Unauthorized attempt to change someone else's schedule. ($change, $shift_id, $role, $user_id)");
938
      return "<br>Denied! You are not authorized to change someone else's schedule in this department ($department).<br>\n";
939
    }
940
  } elsif ($ORCUSER->{department}->{$department} >= 3 or $ORCUSER->{access} >= 5) {
941
    # Managers can sign up for as many shifts within their own department as they like...
942
    $leadership_change = 1;
943
  }
944
 
945
  if ($change eq "add") {
946
    if ($department eq "CLA" and !getUser($user_id)->{MVPid}) {
947
      return "<br>Denied! User ($user_id) does not have an MVP Pass!<br>\n";
948
    } elsif ($department ne "CLA" and getUser($user_id)->{department} and convertDepartments(getUser($user_id)->{department})->{$department} < 1) {
949
      return "<br>Denied! User ($user_id) is not a member of Department ($department)!<br>\n" unless $department eq "CMP";
950
    } elsif ($department eq "EMT" and getUser($user_id)->{emt_verified} == 0) {
951
      return "<br>Denied! User ($user_id) has not had their EMT status verified!<br>\n";
952
    }
953
  }
954
 
955
  my $conflict = findConflict ($user_id, $shift_id, $game_based);
956
  if ($change eq "add" and $conflict) {
957
    return "<br>Denied! There is a conflict ($conflict) with that shift's time!<br>\n";
958
  }
959
 
960
  my $game_type;
961
  if ($department ne "CLA") {
962
    ($game_type) = $dbh->selectrow_array ("select type from ".$game_based." where id = ?", undef, $shift_id);
963
 
964
    if ($game_type =~ /^selected/ and !$leadership_change) {
965
      return "<br>Denied! Only leadership can make changes to 'selected staffing' shifts!<br>\n" unless $department eq "CMP";
966
    }
967
 
968
    if ($change eq "add" and $game_type eq "lead" and convertDepartments(getUser($user_id)->{department})->{$department} < 2 and $ORCUSER->{access} < 3) {
969
      return "<br>Denied! Shift reserved for leadership staff!<br>\n";
970
    }
971
  } else {
972
    $game_type = "class";
973
  }
974
 
975
 
976
#   my $MAXSHIFTS = getSetting ("MAX_SHIFT_SIGNUP_PER_DAY");
977
  my $MAXSHIFTS = getSetting ("MAX_SHIFT_SIGNUP_PER_DAY_".$department);
978
  $MAXSHIFTS = getSetting ("MAX_SHIFT_SIGNUP_PER_DAY") unless defined $MAXSHIFTS;
979
  if ($game_type eq "lead" and $department eq "OFF") { $MAXSHIFTS = 99; }
980
 
981
  my $daily_count;
982
  if ($department eq "CLA") {
983
    # MVP Class Sign-up
984
    $MAXSHIFTS = getSetting ("MAX_CLASS_SIGNUP");
985
    ($daily_count) = $dbh->selectrow_array ("select count(*) from v_class_signup_new where RCid = ? and year(date) = year(now())", undef, $user_id);
986
#   ($daily_count) = $dbh->selectrow_array ("select count(*) from v_shift where RCid = ? and dept = 'CLA'", undef, $user_id);
987
    if ($change eq "add" and $daily_count >= $MAXSHIFTS and !$leadership_change) {
988
      return "<br>Denied! You may only sign up for $MAXSHIFTS Classes!<br>\n";
989
    }
990
  } else {
991
    $daily_count = signUpCount ('get', $user_id, $department);
992
    if ($change eq "add" and $daily_count >= $MAXSHIFTS and !$leadership_change) {
993
      return "<br>Denied! You may only sign up for $MAXSHIFTS $game_type shifts in one day!<br>\n";
994
    }
995
    if ($change eq "add" and $game_based eq "game" and ($department eq "OFF" or $department eq "ANN") and $game_type eq "full length" and !$leadership_change) {
996
      my $dept_table = $department eq 'OFF' ? "v_shift_officiating" : "v_shift_announcer";
997
      my ($full_length_count) = $dbh->selectrow_array ("select count(*) from $dept_table where RCid = ? and gtype = 'full length' and year(date) = year(now())", undef, $user_id);
998
      my $full_length_max = getSetting("MAX_FULL_LENGTH_SIGNUP_".$department);
999
      if ($full_length_count >= $full_length_max) {
1000
        my $errormsg = "<br>Denied! You may only sign up to ".($department eq 'OFF' ? "officiate" : "announce")." $full_length_max $game_type game(s) (total)!<br>\n";
1001
        return $errormsg;
1002
      }
1003
    }
1004
  }
1005
 
1006
  my @DBARGS;
1007
  if ($game_based eq "game" or $game_based eq "class") {
1008
    if ($change eq "add" or $change eq "override") {
1009
      $sth = $dbh->prepare("insert into assignment (Gid, role, RCid) values (?, ?, ?)");
1010
    } elsif ($change eq "del") {
1011
      $sth = $dbh->prepare("delete from assignment where Gid = ? and role = ? and RCid= ?");
1012
    }
1013
    @DBARGS = ($shift_id, $role, $user_id);
1014
  } else {
1015
    if ($change eq "add" or $change eq "override") {
1016
      $sth = $dbh->prepare("update shift set assignee_id = ? where id = ? and isnull(assignee_id) = 1");
1017
      @DBARGS = ($user_id, $shift_id);
1018
    } elsif ($change eq "del") {
1019
      $sth = $dbh->prepare("update shift set assignee_id = null where id = ?");
1020
      @DBARGS = ($shift_id);
1021
    }
1022
  }
1023
 
1024
  my $wb_act_code;
1025
  if ($change eq "del" and $department eq "CLA") {
1026
    ($wb_act_code) = $dbh->selectrow_array ("select wb_ticket_act from assignment where Gid = ? and RCid = ? and role like ?", undef, $DBARGS[0], $DBARGS[2], 'CLA-%');
1027
  }
1028
 
1029
  print "<br>attempting to make DB changes...<br>";
1030
  if ($sth->execute (@DBARGS)) {
1031
    $daily_count = signUpCount ($change, $user_id, $department) unless $leadership_change;
1032
    logit ($user_id, "Shift ".ucfirst($change).": $shift_id -> $role");
1033
    logit ($ORCUSER->{RCid}, "OVERRIDE: Shift ".ucfirst($change).": $shift_id -> $role") if $change eq "override";
1034
    if ($department eq "CLA") {
1035
      print "Success!...<br>You've signed up for $daily_count class(es) (you're currently allowed to sign up for $MAXSHIFTS).<br>\n";
1036
      updateWRSTBND ($change, $wb_act_code, $DBARGS[0], $DBARGS[2]);
1037
    } else {
1038
      print "Success!...<br>You've signed up for $daily_count shifts today (you're currently allowed to sign up for $MAXSHIFTS per day).<br>\n";
1039
    }
1040
    return;
1041
  } else {
1042
    if ($department eq "CLA") {
1043
      return "<br><b>You did not get the class</b>, most likely because it filled up while you were looking.<br>\nERROR: ", $sth->errstr();
1044
    } else {
1045
      return "<br><b>You did not get the shift</b>, most likely because someone else took it while you were looking.<br>\nERROR: ", $sth->errstr();
1046
    }
1047
  }
1048
}
1049
 
1050
sub updateWRSTBND {
1051
  my ($change, $wb_act_code, $shift_id, $user_id) = @_;
1052
  use REST::Client;
1053
  use JSON;
1054
  my $headers = { Authorization => getSetting ("WRSTBND_API_KEY") };
1055
  my $client = REST::Client->new();
1056
  $client->setHost('https://core.wrstbnd.io');
1057
 
1058
  my ($accountid) = $dbh->selectrow_array ("select wrstbnd_accountid from RCid_ticket_link left join ticket on MVPid = id where RCid = ? and year = year(now())", undef, $user_id);
1059
 
1060
  if ($change eq "add" or $change eq "override") {
1061
    my ($classid) = $dbh->selectrow_array ("select wrstbnd_id from class where id = ?", undef, $shift_id);
1062
 
1063
    my $body = {
1064
      "eventId"      => "event_893C6u5olU",
1065
      "activeStatus" => "active",
1066
      "ticketTypeId" => $classid
1067
    };
1068
    my $json_body = encode_json $body;
1069
 
1070
    $client->POST(
1071
      '/rest/core/v1/ticket',
1072
      $json_body,
1073
      $headers
1074
    );
1075
    my $response = from_json($client->responseContent());
1076
 
1077
    my $activationCode = $response->{activationCode};
1078
 
1079
    my $api_key = getSetting ("WRSTBND_API_KEY");
1080
    my @add_response = `/bin/curl --location --request POST 'https://core.wrstbnd.io/rest/core/v1/assign' --header 'Authorization: $api_key' --form accountid=$accountid --form ticketactcode=$activationCode --output /dev/null --silent --write-out '%{http_code}\n'`;
1081
    my $add_response = $add_response[$#add_response];
1082
    chomp $add_response;
1083
 
1084
    $dbh->do ("update assignment set wb_ticket_act = ? where Gid = ? and RCid = ? and role like ?", undef, $activationCode, $shift_id, $user_id, 'CLA-%') unless $add_response ne "200";
1085
 
1086
    return;
1087
  } elsif ($change eq "del") {
1088
    my $activationCode = $wb_act_code;
1089
    my $api_key = getSetting ("WRSTBND_API_KEY");
1090
    my $del_response = `/bin/curl --location --request DELETE 'https://core.wrstbnd.io/rest/core/v1/assign' --header 'Authorization: $api_key' --form accountid=$accountid --form ticketactcode=$activationCode --output /dev/null --silent --write-out '%{http_code}\n'`;
1091
  }
1092
 
1093
}
1094
 
1095
sub modShiftTime {
1096
  my ($shift_id, $user_id, $diff) = @_;
1097
  my $ORCUSER = getUser (1);
1098
 
1099
  use Scalar::Util qw(looks_like_number);
1100
  if (!looks_like_number ($diff)) {
1101
    print "<br>ERROR! The time adjustment ($diff) doesn't look like a number.<br>\n";
1102
    return;
1103
  }
1104
 
1105
  my ($validate_assignee) = $dbh->selectrow_array ("select count(*) from v_shift where id = ? and RCid = ?", undef, $shift_id, $user_id);
1106
  if (!$validate_assignee) {
1107
    print "<br>ERROR! This shift is assigned to someone else.<br>\n";
1108
    return;
1109
  }
1110
 
1111
  my $department = getShiftDepartment ($shift_id);
1112
  if (convertDepartments ($ORCUSER->{department})->{$department} < 2 and $ORCUSER->{access} < 5) {
1113
    print "<br>ERROR! You're not authorized to modify this shift's time.<br>\n";
1114
    logit ($ORCUSER->{RCid}, "Unauthorized attempt to modify shift time. ($department, $shift_id)");
1115
    return;
1116
  }
1117
 
1118
  my $rows_changed;
1119
  print "<br>attempting to make DB changes...<br>";
1120
  if ($diff == 0) {
1121
    $rows_changed = $dbh->do ("update shift set mod_time = null where id = ? and assignee_id = ?", undef, $shift_id, $user_id);
1122
  } else {
1123
    $rows_changed = $dbh->do ("update shift set mod_time = ? where id = ? and assignee_id = ?", undef, $diff, $shift_id, $user_id);
1124
  }
1125
 
1126
 
1127
  if (!$rows_changed or $dbh->errstr) {
1128
    print "ERROR: Nothing got updated".$dbh->errstr;
1129
    logit (0, "ERROR modifying a shift time ($diff, $shift_id, $user_id):".$dbh->errstr);
1130
  } else {
1131
    print "SUCCESS: Shift $shift_id succesfully modified by $diff hour(s)";
1132
    logit ($ORCUSER->{RCid}, "SUCCESS: Shift $shift_id succesfully modified by $diff hour(s)");
1133
 
1134
  }
1135
  return;
1136
}
1137
 
1138
sub signUpCount {
1139
  my $action = shift;
1140
  my $id = shift;
1141
  my $dept = shift // "";
1142
 
1143
  if ($id eq $ORCUSER->{RCid}) {
1144
    if ($action eq 'add') {
1145
      if (signUpCount ('get', $id, $dept)) {
1146
        $dbh->do("update sign_up_count set sign_ups = sign_ups + 1 where date = curdate() and RCid = ? and department = ?", undef, $id, $dept);
1147
      } else {
1148
        $dbh->do("replace into sign_up_count (date, RCid, department, sign_ups) values (curdate(), ?, ?, 1)", undef, $id, $dept);
1149
      }
1150
    } elsif ($action eq 'del') {
1151
      if (signUpCount ('get', $id, $dept)) {
1152
        $dbh->do("update sign_up_count set sign_ups = sign_ups - 1 where date = curdate() and RCid = ? and department = ?", undef, $id, $dept);
1153
      }
1154
    }
1155
  }
1156
 
1157
  my ($R) = $dbh->selectrow_array ("select sign_ups from sign_up_count where RCid = ? and department = ? and date = curdate()", undef, $id, $dept);
1158
 
1159
  return $R ? $R : '0';
1160
}
1161
 
1162
sub signUpEligible {
1163
  my $user = shift;
1164
  my $t = shift;
1165
  my $shifttype = shift // "game";
1166
  my $dept = $t->{dept} // "";
1167
  my $DEPTHASH = getDepartments ();
1168
  if ($dept and !exists $DEPTHASH->{$dept}) {
1169
    my %reverso = reverse %{$DEPTHASH};
1170
    $dept = $reverso{$dept};
1171
  }
1172
 
1173
  my $limit = getSetting ("MAX_SHIFT_SIGNUP_PER_DAY_".$dept);
1174
  $limit = getSetting ("MAX_SHIFT_SIGNUP_PER_DAY") unless defined $limit;
1175
 
1176
  if (lc $t->{type} eq "lead" and $dept eq "OFF") { $limit = 99; }
1177
 
1178
  return 0 unless $limit > 0;
1179
 
1180
  my $limitkey = $dept ? "sign_ups_today_".$dept : "sign_ups_today";
1181
 
1182
  if ($shifttype eq "class") {
1183
    my $classid = $t->{id};
1184
    $t->{start_time} =~ s/^(\d+:\d+):00$/$1/;
1185
    ($t->{id}) = $dbh->selectrow_array ("select id from v_class_new where date = ? and location = ? and start_time = ?", undef, $t->{date}, $t->{location}, $t->{start_time});
1186
    $t->{dept} = "CLA";
1187
    $dept = "CLA";
1188
    $t->{type} = "open";
1189
  }
1190
 
1191
  if (findConflict ($user->{RCid}, $t->{id}, $shifttype)) { return 0; }
1192
 
1193
  if (!exists $user->{$limitkey}) {
1194
    $user->{$limitkey} = signUpCount('get', $user->{RCid}, $dept);
1195
  }
1196
 
1197
  if ($shifttype eq "game") {
1198
#    if ($t->{gtype} !~ /^selected/ and $t->{gtype} ne "short track" and $user->{$limitkey} < $limit) {
1199
    if ($t->{gtype} eq "full length" and ($dept eq "OFF" or $dept eq "ANN")) {
1200
      my $table = $dept eq "OFF" ? "v_shift_officiating" : "v_shift_announcer";
1201
      my ($full_length_count) = $dbh->selectrow_array ("select count(*) from $table where RCid = ? and gtype = 'full length' and year(date) = year(now())", undef, $user->{RCid});
1202
      if ($full_length_count >= getSetting ("MAX_FULL_LENGTH_SIGNUP_".$dept)) {
1203
        return 0;
1204
      }
1205
    }
1206
    if (lc $t->{signup} ne "selected" and $user->{$limitkey} < $limit) {
1207
      return 1;
1208
    } else {
1209
      return 0;
1210
    }
1211
  } else {
1212
    if ($dept eq "CLA") {
1213
      # MVP Class Sign-up
1214
      return 0 unless $user->{MVPid};
1215
      my $class_limit = getSetting ("MAX_CLASS_SIGNUP");
1216
      my ($class_count) = $dbh->selectrow_array ("select count(*) from v_class_signup_new where RCid = ? and year(date) = year(now())", undef, $user->{RCid});
1217
      return 0 unless $class_count < $class_limit;
1218
    } else {
1219
      if ($user->{department}->{$dept} < 1) { return 0; }
1220
    }
1221
    if (lc $t->{type} eq "lead" and $user->{department}->{$dept} < 2) { return 0; }
1222
    if (lc $t->{type} eq "manager" and $user->{department}->{$dept} < 3) { return 0; }
1223
    if ($dept eq "EMT" and $user->{emt_verified} == 0) { return 0; }
1224
    if (lc $t->{type} !~ /^selected/ and $user->{$limitkey} < $limit) {
1225
      return 1;
1226
    } else {
1227
      return 0;
1228
    }
1229
  }
1230
}
1231
 
1232
sub findConflict {
1233
  my $rcid = shift;
1234
  my $gid = shift;
1235
  my $type = shift // "";
1236
  my ($date, $start, $end, $existing, $conflicts);
1237
 
1238
  if ($type eq "game") {
1239
  # Are they already signed up for this game? (It's faster to check the two views one at a time...)
1240
#    ($conflicts) = $dbh->selectrow_array ("select count(*) from v_shift_officiating where substring_index(id, '-', 1) = ? and RCid = ?", undef, $gid, $rcid);
1241
    ($conflicts) = $dbh->selectrow_array ("select count(*) from v_shift_officiating where id = ? and RCid = ?", undef, $gid, $rcid);
1242
    if ($conflicts) { return "OFF-".$gid; } # no need to keep looking...
1243
    ($conflicts) = $dbh->selectrow_array ("select count(*) from v_shift_announcer where id = ? and RCid = ?", undef, $gid, $rcid);
1244
    if ($conflicts) { return "ANN-".$gid; } # no need to keep looking...
1245
 
1246
    ($date, $start, $end) = $dbh->selectrow_array ("select distinct date, time, end_time from game where id = ?", undef, $gid);
1247
  } elsif ($type eq "class")  {
1248
    ($conflicts) = $dbh->selectrow_array ("select count(*) from v_class_signup_new where id = ? and RCid = ?", undef, $gid, $rcid);
1249
    if ($conflicts) { return "CLA:".$gid; } # no need to keep looking...
1250
 
1251
    ($date, $start, $end) = $dbh->selectrow_array ("select distinct date, start_time, end_time from v_class_new where id = ?", undef, $gid);
1252
 
1253
  } elsif ($type eq "personal")  {
1254
    ($date, $start, $end, $existing) = @{ $gid };
1255
  } else {
1256
    ($date, $start, $end) = $dbh->selectrow_array ("select distinct date, start_time, end_time from shift where id = ?", undef, $gid);
1257
  }
1258
 
1259
  # Are they signed up for any games that would conflict with this one?
1260
#  my $sth = $dbh->prepare("select count(*) from v_shift_admin_view where id in (select id from game where date = (select date from game where id = ?) and ((time <= (select time from game where id = ?) and end_time > (select time from game where id = ?)) or (time > (select time from game where id = ?) and time < (select end_time from game where id = ?)))) and RCid = ?");
1261
#  my $sth = $dbh->prepare("select count(*) from v_shift_all where id in (select id from v_shift_all where date = (select date from v_shift_all where id = ?) and ((start_time <= (select start_time from v_shift_all where id = ?) and end_time > (select start_time from v_shift_all where id = ?)) or (start_time > (select start_time from v_shift_all where id = ?) and start_time < (select end_time from v_shift_all where id = ?)))) and RCid = ?");
1262
 
1263
  ($conflicts) = $dbh->selectrow_array ("select * from (
1264
    select concat(dept, '-', id) as conflict from v_shift          where date = ? and ((start_time <= ? and end_time > ?) or (start_time > ? and start_time < ?)) and RCid = ? union
1265
    select concat('CLA:', id) as conflict from v_class_signup_new  where date = ? and ((start_time <= ? and end_time > ?) or (start_time > ? and start_time < ?)) and RCid = ? union
1266
    select concat('ANN-', id) as conflict from v_shift_announcer   where date = ? and ((start_time <= ? and end_time > ?) or (start_time > ? and start_time < ?)) and RCid = ? union
1267
    select concat('OFF-', id) as conflict from v_shift_officiating where date = ? and ((start_time <= ? and end_time > ?) or (start_time > ? and start_time < ?)) and RCid = ? ) alltables
1268
    where conflict <> ?",
1269
    undef, $date, $start, $start, $start, $end, $rcid, $date, $start, $start, $start, $end, $rcid, $date, $start, $start, $start, $end, $rcid, $date, $start, $start, $start, $end, $rcid, "PER-".$existing
1270
  );
1271
 
1272
  return $conflicts;
1273
}
1274
 
1275
sub changeLeadShift {
1276
  my ($change, $lshift, $user_id) = @_;
1277
  my $ERRMSG;
1278
 
1279
  my $sth = $dbh->prepare("update lead_shift set assignee_id = ? where id = ?");
1280
 
1281
  print "<br>attempting to make DB changes...<br>";
1282
  if ($change eq "add") {
1283
    $sth->execute($user_id, $lshift)
1284
      or $ERRMSG = "ERROR: Can't execute SQL statement: ".$sth->errstr()."\n";
1285
  } elsif ($change eq "del") {
1286
    $sth->execute('', $lshift)
1287
      or $ERRMSG = "ERROR: Can't execute SQL statement: ".$sth->errstr()."\n";
1288
  }
1289
  if ($ERRMSG) {
1290
    print $ERRMSG;
1291
  } else {
1292
    logit($user_id, "Lead Shift ".ucfirst($change).": $lshift");
1293
    print "Success.<br>";
1294
  }
1295
}
1296
 
1297
sub logit {
1298
  my $RCid = shift;
1299
  my $msg = shift;
1300
  my $sth = $dbh->prepare("insert into log (person_id, ip_address, event) values (?, ?, ?)");
1301
  $sth->execute($RCid, $ENV{REMOTE_ADDR}, $msg);
1302
}
1303
 
1304
sub orglogit {
1305
  my $RCid = shift;
1306
  my $org = shift;
1307
  my $msg = shift;
1308
  $dbh->do ("insert into organization_log (person_id, organization_id, ip_address, event) values (?, ?, ?, ?)", undef, $RCid, $org, $ENV{REMOTE_ADDR}, $msg);
1309
}
1310
 
1311
sub sendUserMFAEMail {
1312
  my $user = shift // return "ERROR [sendUserMFAEMail]: No user data sent to function.";
1313
  use PEEPSMailer;
1314
  use HTML::Tiny;
1315
  my $h = HTML::Tiny->new( mode => 'html' );
1316
 
1317
  return "ERROR [sendUserMFAEMail]: No email address found for user" unless $user->{email};
1318
 
1319
  my $subject = 'WFTDI PEEPS - MFA Verification Code';
1320
  my $body;
1321
  $body = $h->p ("Greetings,");
1322
  $body .= $h->p ("It appears you are trying to log into PEEPS from somewhere new. Here's a code to enter:");
1323
 
1324
  my $random_six_digit_number = 100000 + int(rand(900000));
1325
  my $string_number = sprintf ("%06d", $random_six_digit_number);
1326
  $dbh->do ("update authentication set mfa = ?, mfa_timestamp = now() where person_id = ?", undef, $string_number, $user->{person_id});
1327
 
1328
  $body .= $h->p ($string_number);
1329
  $body .= $h->p ("Or click ".$h->a ({ href => url ()."?authenticate=".$string_number }, "this link"));
1330
  $body .= $h->br, $h->p ("--PEEPS Automated Emailer");
1331
 
1332
  EmailUser ($user->{email}, $subject, $body);
1333
}
1334
 
1335
sub sendNewUserEMail {
1336
  my $context = shift;
1337
  my $data = shift;
1338
  use PEEPSMailer;
1339
  use HTML::Tiny;
1340
  my $h = HTML::Tiny->new( mode => 'html' );
1341
  my $depts = getDepartments (); # HashRef of the department TLAs -> Display Names...
1342
  my $AccessLevel = getAccessLevels;
1343
 
1344
  my $email = $data->{email};
1345
  my $subject = 'WFTDI PEEPS - New User';
1346
  my $body;
1347
  if ($context eq "New User") {
1348
    $subject .= " Request";
1349
    my $activationlink = url ()."?activate=".$data->{activation};
1350
    $body = $h->p ("Greetings,");
1351
    $body .= $h->p ("It appears as though you've registered a new account in WFTDI's PEEPS system with the following information:");
1352
    $body .= $h->table ([
1353
      $h->tr ([$h->td ("&nbsp;&nbsp;", "Derby Name:",    $data->{derby_name})]),
1354
      $h->tr ([$h->td ("&nbsp;&nbsp;", "Civil Name:",    join (" ", $data->{name_first}, $data->{name_middle}, $data->{name_last}))]),
1355
#      $h->tr ([$h->td ("&nbsp;&nbsp;", "Pronouns:",      $data->{pronouns})]),
1356
#      $h->tr ([$h->td ("&nbsp;&nbsp;", "TShirt Size:",   $data->{tshirt})]),
1357
      $h->tr ([$h->td ("&nbsp;&nbsp;", "Email Address:", $data->{email})]),
1358
#      $h->tr ([$h->td ("&nbsp;&nbsp;", "Phone:",         $data->{phone})])
1359
    ]);
1360
    $body .= $h->p ("To validate that you've entered a real (and correct) email address (and that you're not a spam-bot), please click the following link:",
1361
      $h->a ({ HREF=>$activationlink }, "Activate my PEEPS Account!"), $h->br,
1362
      "Or you can copy/paste this into the 'Activation Code' box: ".$data->{activation}, $h->br,
1363
      "Once activated, you'll be able to log in.",
1364
      "If you didn't make this request, well, you're still the only one who received this email, and you now have an account request.  You should probably let us know that someone is messing with you.",
1365
      $h->br,
1366
      "--PEEPS Automated Emailer");
1367
  } elsif ($context eq "Activate") {
1368
    $subject .= " Activated!";
1369
    $body = "Greetings again,
1370
 
1371
Your PEEPS account has been actived.
1372
 
1373
--PEEPS Automated Emailer
1374
";
1375
  } else {
1376
    return;
1377
  }
1378
  # send the message
1379
  EmailUser ($email, $subject, $body);
1380
 
1381
}
1382
 
1383
sub isPersonCovered {
1384
  my $pid = shift // "";
1385
  my $date = shift // "";
1386
 
1387
  return "" unless $pid =~ /^\d+$/;
1388
  return "" unless !$date or $date =~ /^\d{4}-\d{2}-\d{2}$/;
1389
 
1390
  my $policy_id;
1391
  if ($date) {
1392
    ($policy_id) = $dbh->selectrow_array ("select id from coverage where person_id = ? and datediff(start, ?) <= 1 and datediff(end, ?) >= 0 and isnull(coverage.terminated) = 1", undef, $pid, $date, $date);
1393
  } else {
1394
    ($policy_id) = $dbh->selectrow_array ("select id from coverage where person_id = ? and datediff(start, now()) < 1 and datediff(end, now()) >= 0 and isnull(coverage.terminated) = 1", undef, $pid);
1395
  }
1396
 
1397
  return $policy_id;
1398
}
1399
 
1400
sub isLeagueCovered {
1401
  my $pid = shift // "";
1402
  my $date = shift // "";
1403
  my $type = shift // "WFTDA General Liability Insurance";
1404
 
1405
  return "" unless $pid =~ /^\d+$/;
1406
  return "" unless !$date or $date =~ /^\d{4}-\d{2}-\d{2}$/;
1407
 
1408
  my $policy_id;
1409
  if ($date) {
1410
    ($policy_id) = $dbh->selectrow_array ("select id from org_coverage where policy_name = ? and organization_id = ? and datediff(start, ?) <= 1 and datediff(end, ?) >= 0 and isnull(org_coverage.terminated) = 1", undef, $type, $pid, $date, $date);
1411
  } else {
1412
    ($policy_id) = $dbh->selectrow_array ("select id from org_coverage where policy_name = ? and organization_id = ? and datediff(start, now()) < 1 and datediff(end, now()) >= 0 and isnull(org_coverage.terminated) = 1", undef, $type, $pid);
1413
  }
1414
 
1415
  return $policy_id;
1416
}
1417
 
1418
sub isLeagueAdmin {
1419
  my $person = shift // "";
1420
 
1421
  if (ref $person eq "HASH") {
1422
    $person = $person->{person_id};
1423
  }
1424
 
1425
  die "ERROR: function isLeagueAdmin(person_id) didn't receive proper argument" unless $person =~ /^\d+$/;
1426
 
1427
  my @array_of_leagues = map { $_->[0] } @{ $dbh->selectall_arrayref ("select member_org_id from role where person_id = ? and role = ?", undef, $person, "League Admin") };
1428
 
1429
  return scalar @array_of_leagues ? \@array_of_leagues : [];
1430
}
1431
 
1432
sub isWFTDAMember {
1433
  my $pid = shift // "";
1434
  return "" unless $pid =~ /^\d+$/;
1435
 
1436
  my ($membership) = $dbh->selectrow_array ("select count(*) from organization where type = ? and status = ? and id in (select member_org_id from role where person_id = ?)", undef, "member league", "Active", $pid);
1437
 
1438
  return $membership;
1439
}
1440
 
1441
sub remainingPolicyDays {
1442
  my $person = shift // "";
1443
  my $policy = shift // "";
1444
 
1445
  return "" unless $person =~ /^\d+$/;
1446
  return "" unless $policy =~ /^\d+$/;
1447
 
1448
  my ($days_remaining) = $dbh->selectrow_array ("select datediff(end, now()) from coverage where id = ? and person_id = ?", undef, $policy, $person);
1449
 
1450
  return defined $days_remaining ? $days_remaining : "ERROR: Policy Not Found";
1451
}
1452
 
1453
sub remainingOrgPolicyDays {
1454
  my $league = shift // "";
1455
  my $policy = shift // "";
1456
 
1457
  return "" unless $league =~ /^\d+$/;
1458
  return "" unless $policy =~ /^\d+$/;
1459
 
1460
  my ($days_remaining) = $dbh->selectrow_array ("select datediff(end, now()) from org_coverage where id = ? and organization_id = ?", undef, $policy, $league);
1461
 
1462
  return defined $days_remaining ? $days_remaining : "ERROR: Policy Not Found";
1463
}
1464
 
1465
1;