Subversion Repositories VORC

Rev

Rev 177 | Rev 180 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
149 - 1
package RollerCon;
2
## RollerCon support functions...
3
 
4
use strict;
5
use cPanelUserConfig;
6
use Exporter 'import';
7
use CGI qw/param header start_html url/;
8
use CGI::Cookie;
9
use DBI;
10
use WebDB;
11
 
12
$SIG{__WARN__} = sub { warn sprintf("[%s] ", scalar localtime), @_ };
13
$SIG{__DIE__}  = sub { die  sprintf("[%s] ", scalar localtime), @_ };
14
 
15
our @EXPORT = qw( $ORCUSER $SYSTEM_EMAIL getRCDBH getAccessLevels authDB max authenticate canView getShiftDepartment getClassID getDepartments convertDepartments convertTime getSchedule getRCid getSetting getUser getUserEmail getUserDerbyName getYears printRCHeader changeShift modShiftTime signUpCount signUpEligible findConflict changeLeadShift sendNewUserEMail logit validate_emt);
16
 
162 - 17
checkQueue (100); # without a number here, the queue functionality is disabled / bypassed
149 - 18
 
153 - 19
my $dbh = WebDB::connect ("vorc");
149 - 20
sub getRCDBH {
21
  return $dbh;
22
}
23
our $ORCUSER;
24
our $SYSTEM_EMAIL = 'rollercon.vorc@gmail.com';
25
use constant {
26
    NOONE     => 0,
27
    USER      => 1,
28
    VOLUNTEER => 1,
29
    LEAD      => 2,
30
    MANAGER   => 3,
31
    DIRECTOR  => 4,
32
    SYSADMIN  => 5,
33
    ADMIN     => 5
34
  };
35
 
36
sub getAccessLevels {
37
  my %AccessLevels = (
38
    -1 => "Locked",
39
 
40
#    1 => "Volunteer",
41
    1 => "User",
42
    2 => "Lead",
43
    3 => "Manager",
44
    4 => "Director",
45
    5 => "SysAdmin"
46
  );
47
  return \%AccessLevels;
48
}
49
 
50
sub authDB {
51
	my $src = shift;
52
	my $id = shift;
53
	my $pass = shift;
54
	my $level = shift;
55
	my $activationcode = shift // "";
56
	my ($result, $encpass);
57
 
58
	my $sth = $dbh->prepare("select * from official where email = ?");
59
	$sth->execute($id);
60
	my $RCDBIDHASH = $sth->fetchrow_hashref();
61
 
62
	if ($src eq "form") {
63
		my $pwdhan = $dbh->prepare("select password(?)");
64
		$pwdhan->execute($pass);
65
		($encpass) = $pwdhan->fetchrow();
66
	} else {
67
		$encpass = $pass;
68
	}
69
 
70
	my $tempDepartments = convertDepartments ($RCDBIDHASH->{department});
71
	my $MAXACCESS = scalar keys %{ $tempDepartments } ? max ($RCDBIDHASH->{'access'}, values %{ $tempDepartments } ) : $RCDBIDHASH->{'access'};
72
 
73
	if (!$RCDBIDHASH->{'RCid'}) {
74
		$result->{ERRMSG} = "Email Address not found!";
75
		$result->{cookie_string} = '';
76
		$result->{RCid} = '';
77
		logit(0, "Account not found: $id");
78
		$result->{authenticated} = 'false';
79
		return $result;
80
	} elsif ($RCDBIDHASH->{'password'} ne $encpass) {
81
		$result->{ERRMSG} = "Incorrect Password!";
82
		$result->{cookie_string} = '';
83
		$result->{RCid} = $RCDBIDHASH->{'RCid'};
84
		logit($RCDBIDHASH->{'RCid'}, "Incorrect Password");
85
		$result->{authenticated} = 'false';
86
		return $result;
87
  } elsif ($RCDBIDHASH->{'activation'} ne "active") {
88
    # It's an inactive account...
89
    if ($activationcode eq "resend") {
90
      # warn "Resending activation code...";
91
      sendNewUserEMail ("New User", $RCDBIDHASH);
92
      $result->{ERRMSG} = "Activation code resent. Please check your email.";
93
  		$result->{cookie_string} = "${id}&${encpass}&0";
94
  		$result->{RCid} = $RCDBIDHASH->{'RCid'};
95
  		logit($RCDBIDHASH->{'RCid'}, "Activation code resent.");
96
  		$result->{authenticated} = 'inactive';
97
  		return $result;
98
    } elsif ($activationcode) {
99
      # They sent an activation code
100
      if ($activationcode eq $RCDBIDHASH->{'activation'}) {
101
        # ...and it was good.
102
        $dbh->do ("update official set activation = 'active', access = 1, last_login = now() where RCid = ? and activation = ?", undef, $RCDBIDHASH->{'RCid'}, $activationcode);
103
        logit($RCDBIDHASH->{'RCid'}, "Activated their account and logged In");
104
        # sendNewUserEMail ("Activate", $RCDBIDHASH);
105
        $RCDBIDHASH->{'access'} = 1;
106
        $RCDBIDHASH->{'activation'} = "active";
107
        $MAXACCESS = max ($MAXACCESS, 1);
108
      } else {
109
        # ...but it wasn't good.
110
        $result->{ERRMSG} = "Activation failed, invalid code submitted.";
111
    		$result->{cookie_string} = "${id}&${encpass}&0";;
112
    		$result->{RCid} = $RCDBIDHASH->{'RCid'};
113
        logit($RCDBIDHASH->{'RCid'}, "Activation failed, invalid code submitted.");
114
    		$result->{authenticated} = 'inactive';
115
  	  	return $result;
116
      }
117
    } else {
118
      # No activation code was submitted.
119
  		$result->{ERRMSG} = "Inactive account! Please check your email for activation link/code." unless $result->{ERRMSG};
120
  		$result->{cookie_string} = "${id}&${encpass}&0";
121
  		$result->{RCid} = $RCDBIDHASH->{'RCid'};
122
  		logit($RCDBIDHASH->{'RCid'}, "Login attempted without activation code.");
123
  		$result->{authenticated} = 'inactive';
124
  		return $result;
125
    }
126
	}
127
 
128
	if ($MAXACCESS < $level) {
129
	  if (getSetting ("MAINTENANCE")) {
130
	    $result->{ERRMSG} = "MAINTENANCE MODE: Logins are temporarily disabled.";
131
	  } else {
132
		  $result->{ERRMSG} = "Your account either needs to be activated, or doesn't have access to this page!";
133
  		logit($RCDBIDHASH->{'RCid'}, "Insufficient Privileges");
134
		}
135
		$result->{cookie_string} = "${id}&${encpass}&$RCDBIDHASH->{'access'}";
136
		$result->{RCid} = $RCDBIDHASH->{'RCid'};
137
		$result->{authenticated} = 'false';
138
	} else {
139
		$result->{ERRMSG} = '';
140
		$RCDBIDHASH->{department} = convertDepartments ($RCDBIDHASH->{department});
141
		$RCDBIDHASH->{'access'} = max ($RCDBIDHASH->{'access'}, values %{$RCDBIDHASH->{department}});
142
		$result->{cookie_string} = "${id}&${encpass}&$RCDBIDHASH->{'access'}";
143
		$result->{RCid} = $RCDBIDHASH->{'RCid'};
144
		logit($RCDBIDHASH->{'RCid'}, "Logged In") if $src eq "form";
145
		$dbh->do ("update official set last_login = now() where RCid = ?", undef, $RCDBIDHASH->{'RCid'}) if $src eq "form";
146
		$result->{authenticated} = 'true';
147
 
148
		$ORCUSER = $RCDBIDHASH;
149
		$ORCUSER->{MVPid} = getUser($ORCUSER->{RCid})->{MVPid};
150
		$ORCUSER->{emt_verified} = getUser($ORCUSER->{RCid})->{emt_verified};
151
	}
152
	return $result;
153
}
154
 
155
sub max {
156
    my ($max, $next, @vars) = @_;
157
    return $max if not $next;
158
    return max( $max > $next ? $max : $next, @vars );
159
}
160
 
161
sub inQueue {
162
	my $item = shift;
163
	my $array = shift;
164
	my $position = 1;
165
	foreach (@{$array})	{
166
	  if ($item eq $_) {
167
		  return $position;
168
		} else {
169
		  $position++;
170
		}
171
	}
172
	return 0;
173
}
174
 
175
 
176
sub authenticate {									# Verifies the user has logged in or puts up a log in screen
177
	my $MAINTMODE = getSetting ("MAINTENANCE");
178
	my $MINLEVEL = $MAINTMODE ? $MAINTMODE : shift // 1;
179
 
180
	my ($ERRMSG, $authenticated, %FORM);
181
	my $sth = $dbh->prepare("select * from official where email = '?'");
182
 
183
	my $query = new CGI;
184
# Check to see if the user has already logged in (there should be cookies with their authentication)?
185
	my $RCAUTH = $query->cookie('RCAUTH');
186
 	my $RCqueueID = CGI::cookie('RCQUEUEID') // WebDB::trim CGI::param('RCqueueID') // "";
187
	$FORM{'ID'} = WebDB::trim $query->param('userid') || '';
188
	$FORM{'PASS'} = WebDB::trim $query->param('pass') || '';
189
	$FORM{'SUB'} = $query->param('login') || '';
190
	$FORM{'activate'} = WebDB::trim $query->param('activate') // '';
191
 
192
	if ($RCAUTH) {
162 - 193
		# We have an authenication cookie.  Double-check it
149 - 194
		my ($RCID, $RCPASS, $RCLVL) = split /&/, $RCAUTH;
195
		$authenticated = authDB('cookie', $RCID, $RCPASS, $MINLEVEL, $FORM{'activate'});
196
	} elsif ($FORM{'SUB'}) {
162 - 197
		# a log in form was submited
149 - 198
		if ($FORM{'SUB'} eq "Submit") {
199
			$authenticated = authDB('form', $FORM{'ID'}, $FORM{'PASS'}, $MINLEVEL, $FORM{'activate'});
200
		} elsif ($FORM{'SUB'} eq "New User") {
201
			# Print the new user form and exit
202
		}
203
	} else {
204
		$authenticated->{authenticated} = 'false';
205
	}
206
 
207
	if ($authenticated->{authenticated} eq 'true') {
208
    use Digest::MD5 qw/md5_hex/;
209
    my $sessionid = md5_hex ($ORCUSER->{email});
210
 
211
    # Limit how long users are allowed to stay logged in at once.
212
    my ($session_length) = $dbh->selectrow_array ("select timestampdiff(MINUTE, last_login, now()) from official where RCid = ?", undef, $ORCUSER->{RCid});
213
    if ($session_length > getSetting ("MAX_SESSION_MINUTES")) {
214
      $ENV{'QUERY_STRING'} = "LOGOUT";
215
      $authenticated->{ERRMSG} = "Maximum session time exceeded.<br>";
216
    }
217
 
153 - 218
    my $qdbh = WebDB::connect ("session");
149 - 219
	  if ($ENV{'QUERY_STRING'} eq "LOGOUT") {
220
      # warn "logging $ORCUSER->{derby_name} out...";
221
      $authenticated->{ERRMSG} .= "Logged Out.<br>";
222
      $authenticated->{cookie_string} = "";
223
      $authenticated->{authenticated} = 'false';
224
      $ENV{REQUEST_URI} =~ s/LOGOUT//;
225
      logit ($ORCUSER->{RCid}, "Logged Out");
226
      $dbh->do ("update official set last_active = ? where RCid = ?", undef, undef, $ORCUSER->{RCid});
153 - 227
			$qdbh->do ("delete from session where sessionid = ?", undef, $sessionid);
149 - 228
      $ORCUSER = "";
229
    } else {
230
  		$dbh->do ("update official set last_active = now() where RCid = ?", undef, $ORCUSER->{RCid});
162 - 231
      $qdbh->do ("replace into session (RCid, sessionid, timestamp, email) values (?, ?, now(), ?)", undef, $ORCUSER->{RCid}, $sessionid, $ORCUSER->{email});
153 - 232
      $qdbh->do ("delete from queue where queueid = ?", undef, $RCqueueID) if $RCqueueID;
149 - 233
  		return $authenticated->{cookie_string};
234
  	}
153 - 235
    $qdbh->disconnect;
149 - 236
	}
237
 
238
 
239
# If we get here, the user has failed authentication; throw up the log-in screen and die.
240
 
241
	my $RCAUTH_cookie = CGI::Cookie->new(-name=>'RCAUTH',-value=>$authenticated->{cookie_string},-expires=>"+30m");
242
 
243
  if ($authenticated->{ERRMSG}) {
244
  	$authenticated->{ERRMSG} = "<TR><TD colspan=2 align=center><font color=red><b>".$authenticated->{ERRMSG}."</b></font>&nbsp</TD></TR>";
245
  	# Log the failed access attempt
246
  } else {
247
  	$authenticated->{ERRMSG} = "";
248
  	# Since there was no ERRMSG, no need to log anything.
249
  }
250
 
251
  if ($RCqueueID) {
252
   	my $RCQUEUE_cookie = CGI::Cookie->new(-name=>'RCQUEUEID',-value=>"",-expires=>"+0m");
253
 	  print header(-cookie=>[$RCAUTH_cookie,$RCQUEUE_cookie]);
254
  } else {
255
 	  print header(-cookie=>$RCAUTH_cookie);
256
 	}
162 - 257
 
149 - 258
	printRCHeader("Please Sign In");
259
	print<<authpage;
260
	<form action="$ENV{REQUEST_URI}" method=POST name=Req id=Req>
261
	<input type=hidden name=RCqueueID value=$RCqueueID>
262
		<TR><TD colspan=2 align=center><b><font size=+2>Please Sign In</font>
263
		<TABLE>
264
		</TD></TR>
265
		<TR><TD colspan=2>&nbsp</TD></TR>
266
		$authenticated->{ERRMSG}
267
authpage
268
 
269
  if ($ENV{'QUERY_STRING'} eq "LOGOUT") {
270
    print "<TR><TD colspan=2>&nbsp</TD></TR>";
271
    print "<TR><TD colspan=2><button onClick=\"location.href='';\">Log In</button></TD></TR>";
272
    print "</TABLE></BODY></HTML>";
273
    exit;
274
  }
275
 
276
  if ($authenticated->{authenticated} eq "inactive") {
277
 
278
    print<<activationpage;
279
      <TR><TD colspan=2 align=center>&nbsp;</TD></TR>
280
      <TR><TD align=right><B>Activation Code:</TD><TD><INPUT type=text id=activate name=activate></TD></TR>
281
      <TR><TD></TD><TD><INPUT type=submit name=login value=Submit></TD></TR>
282
      <TR><TD colspan=2 align=center>&nbsp;</TD></TR>
283
      <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>
284
      <TR><TD colspan=2 align=center><A HREF='' onClick="location.href='?LOGOUT';">[Log Out]</A></TD></TR>
285
      </TABLE></FORM>
286
activationpage
287
 
288
  } else {
289
 
290
    print<<authpage2;
291
  		<TR>
292
  			<TD align=right><B>Email Address:</TD><TD><INPUT type=text id=login name=userid></TD>
293
  		</TR>
294
  		<TR>
295
  			<TD align=right><B>Password:</TD><TD><INPUT type=password name=pass></TD>
296
  		</TR>
297
  		<TR><TD></TD><TD><input type=hidden name=activate id=activate value=$FORM{'activate'}><INPUT type=submit name=login value=Submit></TD></TR>
298
  		<TR><TD colspan=2 align=center>&nbsp;</TD></TR>
299
  		<TR><TD colspan=2 align=center><A HREF="/schedule/view_user.pl?submit=New%20User">[register as a new user]</A></TD></TR>
300
  		<TR><TD colspan=2 align=center><A HREF="/schedule/password_reset.pl">[reset your password]</A></TD></TR>
301
  	</TABLE>
302
  	</FORM>
303
 
304
  	<SCRIPT language="JavaScript">
305
  	<!--
306
  	document.getElementById("login").focus();
307
 
308
  	function Login () {
309
  		document.getElementById('Req').action = "$ENV{SCRIPT_NAME}";
310
  		document.getElementById('Req').submit.click();
311
  		return true;
312
  	}
313
 
314
  	//-->
315
  	</SCRIPT>
316
 
317
authpage2
318
  }
319
 
320
#foreach (keys %ENV) {
321
#	print "$_: $ENV{$_}<br>";
322
#}
323
#	&JScript;
324
	exit;
325
}
326
 
327
sub checkQueue {
328
  my $max_users = shift;
162 - 329
 
149 - 330
  return unless $max_users =~ /^\d+$/;
331
 
332
	return if $ENV{'QUERY_STRING'} eq "SKIPQUEUE";
333
 
334
	my $RCAUTH = CGI::cookie('RCAUTH') // "";
162 - 335
 
153 - 336
	my $qdbh = WebDB::connect ("session");
337
 
149 - 338
	if ($RCAUTH) {
339
	  # If the user is already logged in, bypass the queue check.
162 - 340
   	my ($email, $RCPASS, $RCLVL) = split /&/, $RCAUTH;
163 - 341
   	my ($active) = $qdbh->selectrow_array ("select count(*) from session where email = ? and timestampdiff(minute, timestamp, now()) < 30", undef, $email);
153 - 342
    return if $active;
149 - 343
	}
344
 
165 - 345
 	my ($active_users) = $qdbh->selectrow_array ("select count(*) from session where timestampdiff(minute, timestamp, now()) < 30");
163 - 346
	my ($current_wait) = $qdbh->selectrow_array ("select timestampdiff(minute, timestamp, now()) from queue where timestampdiff(minute, last_seen, now()) < 7 and (timestamp <> last_seen or timestampdiff(second, last_seen, now()) <= 60) limit 1");
153 - 347
	my @queued_users;
163 - 348
  push @queued_users, map { @{$_} } @{ $qdbh->selectall_arrayref ("select queueid from queue where timestampdiff(minute, last_seen, now()) < 7 and (timestamp <> last_seen or timestampdiff(second, last_seen, now()) <= 60) order by timestamp") };
153 - 349
 
149 - 350
 	my $RCqueueID = CGI::cookie('RCQUEUEID') // WebDB::trim CGI::param('RCqueueID') // "";
162 - 351
  $RCqueueID = "" unless inQueue ($RCqueueID, \@queued_users);
149 - 352
 
163 - 353
 	my $your_wait = 0;
149 - 354
  if ($active_users >= $max_users) {
355
    # We are at max users. People have to wait.
356
    if (!$RCqueueID) {
357
   	  use Digest::MD5 qw/md5_hex/;
358
   	  $RCqueueID = time () ."-". md5_hex (rand ());
359
   	  push @queued_users, $RCqueueID;
162 - 360
   	  $qdbh->do ("replace into queue (queueid, timestamp, last_seen) values (?, now(), now())", undef, $RCqueueID);
361
    } else {
163 - 362
    	($your_wait) = $qdbh->selectrow_array ("select timestampdiff(minute, timestamp, now()) from queue where queueid = ?", undef, $RCqueueID);
162 - 363
      $qdbh->do ("update queue set last_seen = now() where queueid = ?", undef, $RCqueueID);
149 - 364
    }
154 - 365
 
163 - 366
    printQueuePage ($RCqueueID, "(".inQueue ($RCqueueID, \@queued_users)." of ".scalar @queued_users." users)", $current_wait - $your_wait);
149 - 367
    exit;
368
 
369
  } elsif (scalar @queued_users) {
370
    # There are users in queue...
371
    if (!$RCqueueID) {
372
      # If you're not already in queue, get in line.
373
   	  use Digest::MD5 qw/md5_hex/;
374
   	  $RCqueueID = time () ."-". md5_hex (rand ());
375
   	  push @queued_users, $RCqueueID;
162 - 376
   	  $qdbh->do ("replace into queue (queueid, timestamp, last_seen) values (?, now(), now())", undef, $RCqueueID);
377
    } else {
163 - 378
    	($your_wait) = $qdbh->selectrow_array ("select timestampdiff(minute, timestamp, now()) from queue where queueid = ?", undef, $RCqueueID);
162 - 379
      $qdbh->do ("update queue set last_seen = now() where queueid = ?", undef, $RCqueueID);
149 - 380
    }
381
 
382
   	my $queue_position = inQueue ($RCqueueID, \@queued_users);
383
    if ($queue_position > ($max_users - $active_users)) {
384
      # If you're not at the head of the line, continue to wait.
163 - 385
      printQueuePage ($RCqueueID, "($queue_position of ".scalar @queued_users." users)", $current_wait - $your_wait);
149 - 386
      exit;
387
    }
388
  }
389
 
390
  return;
391
}
392
 
393
sub printQueuePage {
394
  my $RCqueueID = shift;
395
  my $queue_position = shift;
162 - 396
  my $wait_time = shift;
149 - 397
 
398
  print header(-cookie=>CGI::Cookie->new(-name=>'RCQUEUEID',-value=>$RCqueueID,-expires=>"+5m"));
399
  printRCHeader("is Busy");
400
  print<<busy;
401
    <P><b><font size=+2>Sorry, we are full right now.</font></P>
402
    <P>You are in queue $queue_position.</P>
403
    <div><ul>
162 - 404
	<li>Current wait time is about $wait_time minute(s).</li>
153 - 405
    <li>This page will refresh every 30 seconds.</li>
149 - 406
    <li>When it's your turn to log in, you'll see the username/password boxes.</li>
407
    <li>If you don't log in within five [5] minutes, or if you leave this page, you will likely lose your place in line.</li>
164 - 408
    <li>Please LOG OUT of VORC when you are done so that others can log in.</li>
149 - 409
    </ul></div>
410
    </BODY>
411
    <SCRIPT language="JavaScript">
412
   	<!--
153 - 413
    // Refresh the page after a delay
149 - 414
      setTimeout(function(){
415
        location.replace(location.href);
153 - 416
      }, 30000); // 30000 milliseconds = 30 seconds
149 - 417
    //-->
418
    </SCRIPT>
419
    </HTML>
420
busy
421
  return;
422
}
423
 
424
sub canView {
425
	my $A = shift // "";
426
	my $B = shift // "";
427
	# Is A a lead or higher of one of B's Depts? (or they're looking at themselves)
428
	# parameters should be a Hashref to the users' details
429
 
430
	return 1 if $A->{access} > 4 or $A->{RCid} == $B->{RCid}; # viewer and target are the same person or it's a SysAdmin.
431
 
432
	my $ADept = ref $A->{department} eq "HASH" ? $A->{department} : convertDepartments($A->{department});
433
	my $BDept = ref $B->{department} eq "HASH" ? $B->{department} : convertDepartments($B->{department});
434
 
435
	foreach (keys %{$BDept}) {
436
		if ($ADept->{$_} > 1) { # A is a Lead or higher of one of B's departments
437
			return 1;
438
		}
439
	}
440
 
441
	if ($ADept->{MVP} >= RollerCon::LEAD and $B->{MVPid}) {
442
	  # MVP Volunteers can see user details for people with MVP Passes
443
	  return 1;
444
	}
445
 
446
	return 0;
447
}
448
 
449
sub getShiftDepartment {
450
  my $shiftID = shift // "";
451
  my $dept;
452
 
453
  if ($shiftID =~ /^\d+$/) {
454
    ($dept) = $dbh->selectrow_array ("select dept from shift where id = ?", undef, $shiftID);
455
  } else {
456
    my ($id, $role) = split /-/, $shiftID;
457
    if ($role =~ /^CLA/) {
458
      $dept = "CLA";
459
    } else {
460
      ($dept) = $dbh->selectrow_array ("select distinct department from staff_template where role like ?", undef, $role.'%');
461
    }
462
  }
463
#  } elsif ($shiftID =~ /^\d+-ANN/) {
464
#    $dept = "ANN";
465
#  } else {
466
#    $dept = "OFF";
467
#  }
468
 
469
  return $dept;
470
}
471
 
472
sub getClassID {
473
  my $shift = shift // "";
474
  return unless $shift =~ /^\d+$/;
475
 
476
  my $shiftref = getShiftRef ($shift);
477
  my ($classid) = $dbh->selectrow_array ("select id from class where date = ? and start_time = ? and location = ?", undef, $shiftref->{date}, $shiftref->{start_time}, $shiftref->{location});
478
  return $classid unless !$classid;
479
 
480
  warn "ERROR: No class.id found for shift $shiftref->{id}";
481
  return "";
482
}
483
 
484
sub getShiftRef {
485
  my $shiftID = shift // "";
486
  return unless $shiftID =~ /^\d+$/;
487
 
488
  my ($shiftref) = $dbh->selectrow_hashref ("select * from shift where id = ?", undef, $shiftID);
489
  return $shiftref unless $shiftref->{id} != $shiftID;
490
 
491
  warn "ERROR: Couldn't find shift with ID [$shiftID]";
492
  return "";
493
}
494
 
495
sub getDepartments {
496
  my $RCid = shift // "";
497
  # If we get an RCid, return the list of departments and levels for that user.
498
  #   Otherwise (no parameter), return the list of departments with their display names.
499
 
500
	if ($RCid) {
501
  	my $sth = $dbh->prepare("select department from official where RCid = ?");
502
  	$sth->execute($RCid);
503
  	my ($dlist) = $sth->fetchrow;
504
  	return convertDepartments ($dlist);
505
	} else {
506
  	my %HASH;
507
  	my $sth = $dbh->prepare("select TLA, name from department");
508
  	$sth->execute();
509
  	while (my ($tla, $name) = $sth->fetchrow) {
510
  	  $HASH{$tla} = $name;
511
    }
512
    return \%HASH;
513
  }
514
 
515
}
516
 
517
sub convertDepartments {
518
  # For the department membership, converts the DB string back and forth to a hashref...
519
  my $input = shift // "";
520
  my $output;
521
 
522
  if (ref $input eq "HASH") {
523
    $output = join ":", map { $_."-".$input->{$_} } sort keys %{$input};
524
  } else {
525
  	foreach (split /:/, $input) {
526
  	  my ($tla, $level) = split /-/;
527
  	  $output->{$tla} = $level;
528
    }
529
    $output = {} unless ref $output eq "HASH";
530
  }
531
 
532
  return $output;
533
}
534
 
535
sub convertTime {
536
  my $time = shift || return;
537
 
538
  if ($time =~ / - /) {
539
    return join " - ", map { convertTime ($_) } split / - /, $time;
540
  }
541
 
542
  $time =~ s/^(\d{1,2}:\d{2}):\d{2}$/$1/;
543
  $time =~ s/^0//;
544
 
545
  if ($ORCUSER->{timeformat} eq "24hr") {
546
    if ($time =~ /^\d{1,2}:\d{2}$/) { return $time; }
547
  } else {
548
    my ($hr, $min) = split /:/, $time;
549
    my $ampm = " am";
550
    if ($hr >= 12) {
551
      $hr -= 12 unless $hr == 12;
552
      $ampm = " pm";
553
    } elsif ($hr == 0) {
554
      $hr = 12;
555
    }
556
    return $hr.":".$min.$ampm;
557
  }
558
}
559
 
560
sub getSchedule {
561
  my $RCid = shift // return "ERROR: No RCid provided to getSchedule";
562
  my $filter = shift // "";
563
  my $year = 1900 + (localtime)[5];
564
 
565
  my @whereclause;
566
  if ($filter eq "all") {
153 - 567
  	push @whereclause, "year(date) >= year(now())";
149 - 568
  } else {
569
  	push @whereclause, "date >= date(now())";
570
  }
571
#  if ($RCid ne $ORCUSER->{RCid}) {
572
#    push @whereclause, "dept != 'PER'";
573
#  }
574
 
575
  use DateTime;
576
  my $dt = DateTime->today (time_zone => 'America/Los_Angeles');
577
  $dt =~ s/T00\:00\:00$//;
578
  my $now = DateTime->now (time_zone => 'America/Los_Angeles');
579
 
580
 
581
  use HTML::Tiny;
582
  my $h = HTML::Tiny->new( mode => 'html' );
583
 
584
  my $where = scalar @whereclause ? "where ".join " and ", @whereclause : "";
585
  my @shifts;
586
  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
587
                                          select id, date, dayofweek, track as location, time, role, teams, signup, 'ANN' as dept, volhours from v_shift_announcer where RCid = ? union
588
                                          select id, date, dayofweek, location, time, role, '' as teams, type as signup, dept, volhours from v_shift where RCid = ? union
589
                                          select id, date, dayofweek, location, time, role, name as teams, 'mvpclass' as signup, 'CLA' as dept, 0 as volhours from v_class_signup where RCid = ?) temp
590
                           $where order by date, time");
591
  $sth->execute($RCid, $RCid, $RCid, $RCid);
592
  my $hours = 0;
593
  while (my $s = $sth->fetchrow_hashref) {
594
    my ($yyyy, $mm, $dd) = split /\-/, $s->{date};
595
	  my $cutoff = DateTime->new(
596
        year => $yyyy,
597
        month => $mm,
598
        day => $dd,
599
        hour => 5,
600
        minute => 0,
601
        second => 0,
602
        time_zone => 'America/Los_Angeles'
603
    );
604
 
605
 
606
  	if (!$s->{teams} or $s->{dept} eq "CLA") {
607
  	  # it's a time-based shift
608
  	  if ($s->{dept} eq "PER") {
609
        if ($RCid eq $ORCUSER->{RCid}) {
610
          # DROP
152 - 611
  	      $s->{buttons} = $h->button ({ onClick=>"event.stopPropagation(); if (confirm('Really? You want to delete this personal time?')==true) { location.href='personal_time.pl?choice=Delete&id=$s->{id}'; return false; }" }, "DEL")."&nbsp;".$h->button ({ onClick=>"event.stopPropagation(); location.href='personal_time.pl?choice=Update&id=$s->{id}'" }, "EDIT");
149 - 612
  	    } else {
613
  	      $s->{location} = "";
614
  	      $s->{role} = "";
615
  	    }
616
      } elsif (($RCid == $ORCUSER->{RCid} and $s->{signup} !~ /^selected/ and $now < $cutoff) or ($ORCUSER->{department}->{$s->{dept}} >= 2 or $ORCUSER->{access} >= 5)) {
617
        # DROP
618
        my ($shiftORclass, $linkargs) = ("shift", "");
619
        if ($s->{dept} eq "CLA") {
620
          $shiftORclass = "class";
621
          $linkargs = "&role=$s->{role}";
622
          $s->{role} = $s->{teams};
623
          $s->{teams} = "";
624
        }
625
	   		$s->{buttons} = $h->button ({ onClick=>"if (confirm('Really? You want to drop this $shiftORclass?')==true) { window.open('make_shift_change.pl?change=del&RCid=$RCid&id=$s->{id}$linkargs','Confirm Class Change','resizable,height=260,width=370'); return false; }" }, "DROP");
626
	   		if ($ORCUSER->{department}->{$s->{dept}} >= 2 or $ORCUSER->{access} >= 5) {
627
   		    # NO SHOW
628
 	  	    $s->{buttons} .= "&nbsp;".$h->button ({ onClick=>"if (confirm('Really? They were a no show?')==true) { window.open('make_shift_change.pl?noshow=true&change=del&RCid=$RCid&id=$s->{id}$linkargs','Confirm Shift Change','resizable,height=260,width=370'); return false; }" }, "NO SHOW");
629
 		    }
630
 
631
  		}
632
#  		$hours += $s->{volhours} unless $s->{dept} eq "PER" or $s->{dept} eq "CLA";
633
 
634
    } elsif (($RCid == $ORCUSER->{RCid} and $s->{signup} !~ /^selected/ and $now < $cutoff) or ($ORCUSER->{department}->{$s->{dept}} >= 2 or $ORCUSER->{access} >= 5)) {
635
      # it's a game shift
636
      #DROP
637
  		$s->{buttons} = $h->button ({ onClick=>"if (confirm('Really? You want to drop this shift?')==true) { window.open('make_shift_change.pl?change=del&RCid=$RCid&id=$s->{id}&role=$s->{role}','Confirm Shift Change','resizable,height=260,width=370'); return false; }" }, "DROP");
638
   		if ($ORCUSER->{department}->{$s->{dept}} >= 2 or $ORCUSER->{access} >= 5) {
639
 		    # NO SHOW
640
        $s->{buttons} .= "&nbsp;".$h->button ({ onClick=>"if (confirm('Really? They were a no show?')==true) { window.open('make_shift_change.pl?noshow=true&change=del&RCid=$RCid&id=$s->{id}&role=$s->{role}','Confirm Shift Change','resizable,height=260,width=370'); return false; }" }, "NO SHOW");
641
      }
642
#      $hours += $s->{volhours};
643
  	}
644
  	$s->{role} =~ s/\-\d+$//;
645
 
646
#  	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});
647
#  	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});
648
    $s->{time} = convertTime $s->{time};
152 - 649
    if ($s->{dept} eq "PER") {
650
  	  push @shifts, $h->li ({ onClick => "location.replace('personal_time.pl?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}) ]));
651
    } else {
652
  	  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}) ]));
653
  	}
149 - 654
    $hours += $s->{volhours} unless $s->{dept} eq "PER" or $s->{dept} eq "CLA";
655
  }
656
 
657
  if (scalar @shifts) {
658
    return $h->ul ([ @shifts, $h->h5 ("Currently showing $hours hours of Volunteer Time.") ]);
659
  } else {
660
    return $h->p ({ class=>"hint" }, "[nothing scheduled at the moment]");
661
  }
662
}
663
 
664
sub getRCid {
665
  my $derbyname = shift;
666
  ($derbyname) = $dbh->selectrow_array ("select RCid from official where derby_name = ?", undef, $derbyname);
667
  return $derbyname;
668
}
669
 
670
sub getSetting {
671
	my $k = shift;
672
 
673
	my ($value) = $dbh->selectrow_array ("select setting.value from setting where setting.key = ?", undef, $k);
674
  return defined $value ? $value : undef;
675
}
676
 
677
sub getUser {
678
	my $ID = shift;
679
 
680
	my $sth;
681
	if ($ID =~ /^\d+$/) {
682
	  $sth = $dbh->prepare("select * from v_official where RCid = ?");
683
	} else {
684
	  $sth = $dbh->prepare("select * from v_official where email = ?");
685
  }
686
	$sth->execute($ID);
687
 
688
	my $user = $sth->fetchrow_hashref;
689
	map { $user->{$_} = "" unless $user->{$_} } keys %{$user};
690
	return $user->{RCid} ? $user : "";
691
}
692
 
693
sub getUserEmail {
694
	my $RCid = shift;
695
	my $sth = $dbh->prepare("select email from official where RCid = ?");
696
	$sth->execute($RCid);
697
	my ($email) = $sth->fetchrow_array();
698
	return $email;
699
}
700
 
701
sub getUserDerbyName {
702
	my $RCid = shift;
703
	my $sth = $dbh->prepare("select derby_name from official where RCid = ?");
704
	$sth->execute($RCid);
705
	my ($dname) = $sth->fetchrow_array();
706
	return $dname;
707
}
708
 
709
sub getYears {
710
	my $sth = $dbh->prepare("select distinct year from (select distinct year(date) as year from v_shift_admin_view union select year(now()) as year) years order by year");
711
#	my $sth = $dbh->prepare("select distinct year(date) from v_shift_admin_view");
712
	$sth->execute();
713
	my @years;
714
	while (my ($y) =$sth->fetchrow_array()) { push @years, $y; }
715
	return \@years;
716
}
717
 
718
sub printRCHeader {
719
	my $PAGE_TITLE = shift;
720
#	use CGI qw/start_html/;
721
	use HTML::Tiny;
722
  my $h = HTML::Tiny->new( mode => 'html' );
723
 
724
#  my $logout = $h->a ({ href=>"index.pl", onClick=>"document.cookie = 'RCAUTH=; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/';return true;" }, "[Log Out]");
725
  my $referrer = param ("referrer") ? param ("referrer") : $ENV{HTTP_REFERER};
726
  my $logout = (!$referrer or $referrer eq url) ? "" : $h->button ({ onClick=>"window.location.href='$referrer';" }, "Back")."&nbsp;";
727
  $logout .= url =~ /\/(index.pl)?$/ ? "" : $h->button ({ onClick=>"window.location.href='/schedule/';" }, "Home")."&nbsp;";
728
#  $logout .= $h->button ({ onClick=>"document.cookie = 'RCAUTH=; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/'; location.href='/';" }, "Log Out");
729
  $logout .= $h->button ({ onClick=>"location.href='?LOGOUT';" }, "Log Out");
730
	my $loggedinas = $ORCUSER ? "Currently logged in as: ".$h->a ({ href=>"/schedule/view_user.pl?submit=View&RCid=$ORCUSER->{RCid}" }, $ORCUSER->{derby_name}).$h->br.$logout : "";
731
 
732
  print start_html (-title=>"vORC - $PAGE_TITLE", -style => {'src' => "/style.css"} );
733
 
734
#<html><head><title>Officials' RollerCon Schedule Manager - $PAGE_TITLE</title>
735
#<link rel="stylesheet" type="text/css" href="/style.css">
736
#</head>
737
#<body text="#000000" bgcolor="#FFFFFF" link="#0000EE" vlink="#551A8B" alink="#FF0000">
738
	print $h->div ({ class=>"sp0" }, [ $h->div ({ class=>"spLeft" },  $h->a ({ href=>"/schedule/" }, $h->img ({ src=>"/logo.jpg", width=>"75", height=>"75" }))),
739
	                                   $h->div ({ class=>"spRight" }, [ $h->h1 (["vORC $PAGE_TITLE", $h->br]),
740
	                                   $loggedinas,
741
	                                   ])
742
	                                 ]);
743
#print<<rcheader;
744
#  <TABLE>
745
#	<TR class="nostripe">
746
#		<TD align=right><img SRC="/logo.jpg"></TD>
747
#		<TD align=center valign=middle><b><font size=+3>Officials' RollerCon<br>Schedule Manager<br>$PAGE_TITLE</FONT></b>
748
#	<p align=right><font size=-2>$loggedinas <a href='index.pl' onClick="document.cookie = 'RCAUTH=; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/';return true;">[Log Out]</a></font></TD>
749
#	</TR>
750
 
751
#rcheader
752
}
753
 
754
sub changeShift {
755
	my ($change, $shift_id, $role, $user_id) = @_;
756
  if ($shift_id =~ /(am|pm)/) {
757
    my ($td, $st, $tl) = split /\|/, $shift_id;
758
    my ($hr, $min, $ampm) = split /:|\s/, $st;
759
    if ($ampm eq "pm") { $hr += 12; }
760
    elsif ($ampm eq "am" and $hr == 12) { $hr = "00" }
761
 
762
    $st = $hr.":".$min;
763
    $shift_id = join "|", ($td, $st, $tl);
764
  }
765
#warn join " - ", $change, $shift_id, $role, $user_id;
766
	my $leadership_change = 0;
767
#	my $department = getShiftDepartment ($role ? $shift_id."-".$role : $shift_id);
768
	my $department;
769
	if ($shift_id =~ /^\d+$/) {
770
		$department = getShiftDepartment ($role ? $shift_id."-".$role : $shift_id);
771
	} else {
772
		$department = "CLA";
773
		if ($change eq "del") {
774
		  ($shift_id, $role) = $dbh->selectrow_array ("select id, role from v_class_signup where date = ? and start_time = ? and location = ?", undef, split /\|/, $shift_id);
775
		} else {
776
		  ($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 where date = ? and start_time = ? and location = ? having capacity > count(role)", undef, split /\|/, $shift_id);
777
		}
778
    $role = "CLA-1" unless $role; # If no one has signed up for the class yet, the SQL above doesn't retrieve the first available
779
	}
780
#	my $game_based = $role ? "game" : "shift";
781
	my $game_based = $role =~ /^CLA-/ ? "class" : $role ? "game" : "shift";
782
	my $sth;
783
 
784
	if ($change eq "add" or $change eq "override") {
785
  	my $taken;
786
		if ($department eq "CLA") {
787
  	  ($taken) = $shift_id ? 0 : 1;
788
  	} elsif ($game_based eq "game") {
789
  	  ($taken) = $dbh->selectrow_array ("select count(*) from assignment where Gid = ? and role = ?", undef, $shift_id, $role);
790
  	} else {
791
  	  ($taken) = $dbh->selectrow_array ('select count(*) from shift where id = ? and (isnull(assignee_id) = 0 or assignee_id <> "")', undef, $shift_id);
792
  	}
793
  	if ($taken) {
794
  	    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";
795
  	}
796
  }
797
 
798
	if (lc ($user_id) ne lc ($ORCUSER->{RCid})) { # they're changing someone else's schedule...
799
	  if (($department eq "CLA" and $ORCUSER->{department}->{MVP} >= 2) or $ORCUSER->{department}->{$department} >= 2 or $ORCUSER->{access} >= 5 or $ORCUSER->{department}->{VCI} >= 2) {
800
	    # the user making the change is either a lead in the dept, a sysadmin, or a VCI lead
801
	    logit ($ORCUSER->{RCid}, "$ORCUSER->{derby_name} changed someone else's schedule. ($change, $shift_id, $role, $user_id)");
802
	    logit ($user_id, "Schedule was changed by $ORCUSER->{derby_name}. ($change, $shift_id, $role, $user_id)");
803
	    $leadership_change = 1;
804
	  } else {
805
	    logit ($ORCUSER->{RCid}, "Unauthorized attempt to change someone else's schedule. ($change, $shift_id, $role, $user_id)");
806
	    return "<br>Denied! You are not authorized to change someone else's schedule in this department ($department).<br>\n";
807
	  }
808
	} elsif ($ORCUSER->{department}->{$department} >= 3 or $ORCUSER->{access} >= 5) {
809
	  # Managers can sign up for as many shifts within their own department as they like...
810
	  $leadership_change = 1;
811
	}
812
 
813
  if ($change eq "add") {
814
    if ($department eq "CLA" and !getUser($user_id)->{MVPid}) {
815
      return "<br>Denied! User ($user_id) does not have an MVP Pass!<br>\n";
816
    } elsif ($department ne "CLA" and getUser($user_id)->{department} and convertDepartments(getUser($user_id)->{department})->{$department} < 1) {
817
      return "<br>Denied! User ($user_id) is not a member of Department ($department)!<br>\n" unless $department eq "CMP";
818
    }
819
  }
820
 
821
  my $conflict = findConflict ($user_id, $shift_id, $game_based);
822
  if ($change eq "add" and $conflict) {
823
		return "<br>Denied! There is a conflict ($conflict) with that shift's time!<br>\n";
824
  }
825
 
826
  my $game_type;
827
  if ($department ne "CLA") {
828
   	($game_type) = $dbh->selectrow_array ("select type from ".$game_based." where id = ?", undef, $shift_id);
829
 
830
   	if ($game_type =~ /^selected/ and !$leadership_change) {
831
   	  return "<br>Denied! Only leadership can make changes to 'selected staffing' shifts!<br>\n" unless $department eq "CMP";
832
   	}
833
 
834
   	if ($change eq "add" and $game_type eq "lead" and convertDepartments(getUser($user_id)->{department})->{$department} < 2 and $ORCUSER->{access} < 3) {
835
   	  return "<br>Denied! Shift reserved for leadership staff!<br>\n";
836
   	}
837
  } else {
838
    $game_type = "class";
839
  }
840
 
841
 
842
# 	my $MAXSHIFTS = getSetting ("MAX_SHIFT_SIGNUP_PER_DAY");
843
	my $MAXSHIFTS = getSetting ("MAX_SHIFT_SIGNUP_PER_DAY_".$department);
844
	$MAXSHIFTS = getSetting ("MAX_SHIFT_SIGNUP_PER_DAY") unless defined $MAXSHIFTS;
845
	if ($game_type eq "lead" and $department eq "OFF") { $MAXSHIFTS = 99; }
846
 
847
  my $daily_count;
848
  if ($department eq "CLA") {
849
    # MVP Class Sign-up
850
    $MAXSHIFTS = getSetting ("MAX_CLASS_SIGNUP");
153 - 851
	  ($daily_count) = $dbh->selectrow_array ("select count(*) from v_class_signup where RCid = ? and year(date) = year(now())", undef, $user_id);
149 - 852
#	  ($daily_count) = $dbh->selectrow_array ("select count(*) from v_shift where RCid = ? and dept = 'CLA'", undef, $user_id);
853
   	if ($change eq "add" and $daily_count >= $MAXSHIFTS and !$leadership_change) {
854
	    return "<br>Denied! You may only sign up for $MAXSHIFTS Classes!<br>\n";
855
	  }
856
  } else {
857
   	$daily_count = signUpCount ('get', $user_id, $department);
858
   	if ($change eq "add" and $daily_count >= $MAXSHIFTS and !$leadership_change) {
859
   		return "<br>Denied! You may only sign up for $MAXSHIFTS $game_type shifts in one day!<br>\n";
860
   	}
861
   	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) {
862
    	my $dept_table = $department eq 'OFF' ? "v_shift_officiating" : "v_shift_announcer";
863
    	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);
864
  		my $full_length_max = getSetting("MAX_FULL_LENGTH_SIGNUP_".$department);
865
  		if ($full_length_count >= $full_length_max) {
866
  		  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";
867
  			return $errormsg;
868
  		}
869
    }
870
  }
871
 
872
 	my @DBARGS;
873
  if ($game_based eq "game" or $game_based eq "class") {
874
  	if ($change eq "add" or $change eq "override") {
875
  		$sth = $dbh->prepare("insert into assignment (Gid, role, RCid) values (?, ?, ?)");
876
  	} elsif ($change eq "del") {
877
  		$sth = $dbh->prepare("delete from assignment where Gid = ? and role = ? and RCid= ?");
878
  	}
879
  	@DBARGS = ($shift_id, $role, $user_id);
880
  } else {
881
  	if ($change eq "add" or $change eq "override") {
882
  		$sth = $dbh->prepare("update shift set assignee_id = ? where id = ? and isnull(assignee_id) = 1");
883
  		@DBARGS = ($user_id, $shift_id);
884
  	} elsif ($change eq "del") {
885
  		$sth = $dbh->prepare("update shift set assignee_id = null where id = ?");
886
  		@DBARGS = ($shift_id);
887
  	}
888
  }
889
 
175 - 890
  my $wb_act_code;
891
  if ($change eq "del" and $department eq "CLA") {
892
    ($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-%');
893
  }
894
 
149 - 895
  print "<br>attempting to make DB changes...<br>";
896
  if ($sth->execute (@DBARGS)) {
897
  	$daily_count = signUpCount ($change, $user_id, $department) unless $leadership_change;
898
  	logit ($user_id, "Shift ".ucfirst($change).": $shift_id -> $role");
899
  	logit ($ORCUSER->{RCid}, "OVERRIDE: Shift ".ucfirst($change).": $shift_id -> $role") if $change eq "override";
900
  	if ($department eq "CLA") {
175 - 901
  	  print "Success!...<br>You've signed up for $daily_count class(es) (you're currently allowed to sign up for $MAXSHIFTS).<br>\n";
902
 	    updateWRSTBND ($change, $wb_act_code, $DBARGS[0], $DBARGS[2]);
149 - 903
  	} else {
904
  	  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";
905
  	}
906
  	return;
907
  } else {
908
  	if ($department eq "CLA") {
909
      return "<br><b>You did not get the class</b>, most likely because it filled up while you were looking.<br>\nERROR: ", $sth->errstr();
910
  	} else {
911
      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();
912
    }
913
  }
914
}
915
 
175 - 916
sub updateWRSTBND {
917
  my ($change, $wb_act_code, $shift_id, $user_id) = @_;
918
  use REST::Client;
919
  use JSON;
920
  my $headers = { Authorization => '601037851507c624' };
921
  my $client = REST::Client->new();
177 - 922
  $client->setHost('https://core.wrstbnd.io');
923
 
175 - 924
  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);
925
 
926
  if ($change eq "add" or $change eq "override") {
927
    my ($classid) = $dbh->selectrow_array ("select wrstbnd_id from class where id = ?", undef, $shift_id);
928
 
929
    my $body = {
930
      "eventId"      => "event_Y5567UWwS5",
931
      "activeStatus" => "active",
932
      "ticketTypeId" => $classid
933
    };
934
    my $json_body = encode_json $body;
935
 
936
    $client->POST(
937
      '/rest/core/v1/ticket',
938
      $json_body,
939
      $headers
940
    );
941
    my $response = from_json($client->responseContent());
942
 
943
    my $activationCode = $response->{activationCode};
944
 
178 - 945
    my @add_response = `/bin/curl --location --request POST 'https://core.wrstbnd.io/rest/core/v1/assign' --header 'Authorization: 601037851507c624' --form accountid=$accountid --form ticketactcode=$activationCode --output /dev/null --silent --write-out '%{http_code}\n'`;
946
    my $add_response = $add_response[$#add_response];
947
    chomp $add_response;
175 - 948
 
178 - 949
    $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";
175 - 950
 
951
    return;
952
  } elsif ($change eq "del") {
953
    my $activationCode = $wb_act_code;
178 - 954
    my $del_response = `/bin/curl --location --request DELETE 'https://core.wrstbnd.io/rest/core/v1/assign' --header 'Authorization: 601037851507c624' --form accountid=$accountid --form ticketactcode=$activationCode --output /dev/null --silent --write-out '%{http_code}\n'`;
175 - 955
  }
956
 
957
}
958
 
149 - 959
sub modShiftTime {
960
	my ($shift_id, $user_id, $diff) = @_;
961
	my $ORCUSER = getUser (1);
962
 
963
	use Scalar::Util qw(looks_like_number);
964
	if (!looks_like_number ($diff)) {
965
	  print "<br>ERROR! The time adjustment ($diff) doesn't look like a number.<br>\n";
966
  	return;
967
	}
968
 
969
  my ($validate_assignee) = $dbh->selectrow_array ("select count(*) from v_shift where id = ? and RCid = ?", undef, $shift_id, $user_id);
970
 	if (!$validate_assignee) {
971
	  print "<br>ERROR! This shift is assigned to someone else.<br>\n";
972
  	return;
973
 	}
974
 
975
	my $department = getShiftDepartment ($shift_id);
976
  if (convertDepartments ($ORCUSER->{department})->{$department} < 2 and $ORCUSER->{access} < 5) {
977
	  print "<br>ERROR! You're not authorized to modify this shift's time.<br>\n";
978
	  logit ($ORCUSER->{RCid}, "Unauthorized attempt to modify shift time. ($department, $shift_id)");
979
  	return;
980
 	}
981
 
982
  my $rows_changed;
983
  print "<br>attempting to make DB changes...<br>";
984
  if ($diff == 0) {
985
	  $rows_changed = $dbh->do ("update shift set mod_time = null where id = ? and assignee_id = ?", undef, $shift_id, $user_id);
986
  } else {
987
	  $rows_changed = $dbh->do ("update shift set mod_time = ? where id = ? and assignee_id = ?", undef, $diff, $shift_id, $user_id);
988
  }
989
 
990
 
991
  if (!$rows_changed or $dbh->errstr) {
992
  	print "ERROR: Nothing got updated".$dbh->errstr;
993
  	logit (0, "ERROR modifying a shift time ($diff, $shift_id, $user_id):".$dbh->errstr);
994
  } else {
995
  	print "SUCCESS: Shift $shift_id succesfully modified by $diff hour(s)";
996
  	logit ($ORCUSER->{RCid}, "SUCCESS: Shift $shift_id succesfully modified by $diff hour(s)");
997
 
998
  }
999
  return;
1000
}
1001
 
1002
sub signUpCount {
1003
	my $action = shift;
1004
	my $id = shift;
1005
	my $dept = shift // "";
1006
 
1007
	if ($id eq $ORCUSER->{RCid}) {
1008
		if ($action eq 'add') {
1009
			if (signUpCount ('get', $id, $dept)) {
1010
				$dbh->do("update sign_up_count set sign_ups = sign_ups + 1 where date = curdate() and RCid = ? and department = ?", undef, $id, $dept);
1011
			} else {
1012
				$dbh->do("replace into sign_up_count (date, RCid, department, sign_ups) values (curdate(), ?, ?, 1)", undef, $id, $dept);
1013
			}
1014
		} elsif ($action eq 'del') {
1015
			if (signUpCount ('get', $id, $dept)) {
1016
				$dbh->do("update sign_up_count set sign_ups = sign_ups - 1 where date = curdate() and RCid = ? and department = ?", undef, $id, $dept);
1017
			}
1018
		}
1019
	}
1020
 
1021
	my ($R) = $dbh->selectrow_array ("select sign_ups from sign_up_count where RCid = ? and department = ? and date = curdate()", undef, $id, $dept);
1022
 
1023
	return $R ? $R : '0';
1024
}
1025
 
1026
sub signUpEligible {
1027
	my $user = shift;
1028
	my $t = shift;
1029
	my $shifttype = shift // "game";
1030
	my $dept = $t->{dept} // "";
1031
  my $DEPTHASH = getDepartments ();
1032
  if ($dept and !exists $DEPTHASH->{$dept}) {
1033
    my %reverso = reverse %{$DEPTHASH};
1034
    $dept = $reverso{$dept};
1035
  }
1036
 
1037
	my $limit = getSetting ("MAX_SHIFT_SIGNUP_PER_DAY_".$dept);
1038
	$limit = getSetting ("MAX_SHIFT_SIGNUP_PER_DAY") unless defined $limit;
1039
 
1040
	if (lc $t->{type} eq "lead" and $dept eq "OFF") { $limit = 99; }
1041
 
1042
	return 0 unless $limit > 0;
1043
 
1044
	my $limitkey = $dept ? "sign_ups_today_".$dept : "sign_ups_today";
1045
 
1046
	if ($shifttype eq "class") {
1047
		($t->{id}) = $dbh->selectrow_array ("select id from v_class where date = ? and location = ? and start_time = ?", undef, $t->{date}, $t->{location}, $t->{start_time});
1048
		$t->{dept} = "CLA";
1049
		$dept = "CLA";
1050
		$t->{type} = "open";
1051
	}
1052
 
1053
	if (findConflict ($user->{RCid}, $t->{id}, $shifttype)) { return 0; }
1054
 
1055
	if (!exists $user->{$limitkey}) {
1056
		$user->{$limitkey} = signUpCount('get', $user->{RCid}, $dept);
1057
	}
1058
 
1059
	if ($shifttype eq "game") {
1060
#    if ($t->{gtype} !~ /^selected/ and $t->{gtype} ne "short track" and $user->{$limitkey} < $limit) {
1061
		if ($t->{gtype} eq "full length" and ($dept eq "OFF" or $dept eq "ANN")) {
1062
		  my $table = $dept eq "OFF" ? "v_shift_officiating" : "v_shift_announcer";
1063
			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});
1064
			if ($full_length_count >= getSetting ("MAX_FULL_LENGTH_SIGNUP_".$dept)) {
1065
				return 0;
1066
			}
1067
		}
1068
    if (lc $t->{signup} ne "selected" and $user->{$limitkey} < $limit) {
1069
			return 1;
1070
		} else {
1071
			return 0;
1072
		}
1073
	} else {
1074
    if ($dept eq "CLA") {
1075
      # MVP Class Sign-up
1076
			return 0 unless $user->{MVPid};
1077
      my $class_limit = getSetting ("MAX_CLASS_SIGNUP");
1078
			my ($class_count) = $dbh->selectrow_array ("select count(*) from v_class_signup where RCid = ? and year(date) = year(now())", undef, $user->{RCid});
1079
			return 0 unless $class_count < $class_limit;
1080
    } else {
1081
	    if ($user->{department}->{$dept} < 1) { return 0; }
1082
	  }
1083
	  if (lc $t->{type} eq "lead" and $user->{department}->{$dept} < 2) { return 0; }
1084
	  if (lc $t->{type} eq "manager" and $user->{department}->{$dept} < 3) { return 0; }
1085
	  if ($dept eq "EMT" and $user->{emt_verified} == 0) { return 0; }
1086
    if (lc $t->{type} !~ /^selected/ and $user->{$limitkey} < $limit) {
1087
			return 1;
1088
		} else {
1089
			return 0;
1090
		}
1091
	}
1092
}
1093
 
1094
sub findConflict {
1095
  my $rcid = shift;
1096
  my $gid = shift;
1097
  my $type = shift // "";
152 - 1098
  my ($date, $start, $end, $existing, $conflicts);
149 - 1099
 
1100
  if ($type eq "game") {
1101
  # Are they already signed up for this game? (It's faster to check the two views one at a time...)
1102
#    ($conflicts) = $dbh->selectrow_array ("select count(*) from v_shift_officiating where substring_index(id, '-', 1) = ? and RCid = ?", undef, $gid, $rcid);
1103
    ($conflicts) = $dbh->selectrow_array ("select count(*) from v_shift_officiating where id = ? and RCid = ?", undef, $gid, $rcid);
1104
  	if ($conflicts) { return "OFF-".$gid; } # no need to keep looking...
1105
    ($conflicts) = $dbh->selectrow_array ("select count(*) from v_shift_announcer where id = ? and RCid = ?", undef, $gid, $rcid);
1106
  	if ($conflicts) { return "ANN-".$gid; } # no need to keep looking...
1107
 
1108
    ($date, $start, $end) = $dbh->selectrow_array ("select distinct date, time, end_time from game where id = ?", undef, $gid);
1109
  } elsif ($type eq "class")  {
1110
    ($conflicts) = $dbh->selectrow_array ("select count(*) from v_class_signup where id = ? and RCid = ?", undef, $gid, $rcid);
1111
  	if ($conflicts) { return "CLA:".$gid; } # no need to keep looking...
1112
 
1113
    ($date, $start, $end) = $dbh->selectrow_array ("select distinct date, start_time, end_time from v_class where id = ?", undef, $gid);
1114
 
1115
  } elsif ($type eq "personal")  {
152 - 1116
    ($date, $start, $end, $existing) = @{ $gid };
149 - 1117
  } else {
1118
    ($date, $start, $end) = $dbh->selectrow_array ("select distinct date, start_time, end_time from shift where id = ?", undef, $gid);
1119
  }
1120
 
1121
  # Are they signed up for any games that would conflict with this one?
1122
#  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 = ?");
1123
#  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 = ?");
1124
 
1125
  ($conflicts) = $dbh->selectrow_array ("select * from (
152 - 1126
    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
1127
    select concat('CLA:', id) as conflict from v_class_signup      where date = ? and ((start_time <= ? and end_time > ?) or (start_time > ? and start_time < ?)) and RCid = ? union
1128
    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
1129
    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
1130
    where conflict <> ?",
1131
    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
149 - 1132
  );
1133
 
1134
  return $conflicts;
1135
}
1136
 
1137
sub changeLeadShift {
1138
	my ($change, $lshift, $user_id) = @_;
1139
	my $ERRMSG;
1140
 
1141
	my $sth = $dbh->prepare("update lead_shift set assignee_id = ? where id = ?");
1142
 
1143
	print "<br>attempting to make DB changes...<br>";
1144
	if ($change eq "add") {
1145
		$sth->execute($user_id, $lshift)
1146
    	or $ERRMSG = "ERROR: Can't execute SQL statement: ".$sth->errstr()."\n";
1147
	} elsif ($change eq "del") {
1148
		$sth->execute('', $lshift)
1149
    	or $ERRMSG = "ERROR: Can't execute SQL statement: ".$sth->errstr()."\n";
1150
	}
1151
	if ($ERRMSG) {
1152
		print $ERRMSG;
1153
	} else {
1154
		logit($user_id, "Lead Shift ".ucfirst($change).": $lshift");
1155
  	print "Success.<br>";
1156
  }
1157
}
1158
 
1159
sub logit {
1160
	my $RCid = shift;
1161
	my $msg = shift;
1162
	my $sth = $dbh->prepare("insert into log (RCid, event) values (?, ?)");
1163
	$sth->execute($RCid, $msg);
1164
}
1165
 
1166
sub sendNewUserEMail {
1167
	my $context = shift;
1168
	my $data = shift;
1169
	use RCMailer;
1170
  use HTML::Tiny;
1171
  my $h = HTML::Tiny->new( mode => 'html' );
1172
  my $depts = getDepartments (); # HashRef of the department TLAs -> Display Names...
1173
  my $AccessLevel = getAccessLevels;
1174
 
1175
	my $email = $data->{email};
1176
	my $subject = 'RollerCon VORC - New User';
1177
	my $body;
1178
	if ($context eq "New User") {
1179
    $subject .= " Request";
1180
    my $activationlink = url ()."?activate=".$data->{activation};
1181
	  $body = $h->p ("Greetings,");
1182
	  $body .= $h->p ("It appears as though you've registered a new account in RollerCon's VORC system with the following information:");
1183
	  $body .= $h->table ([
1184
	    $h->tr ([$h->td ("&nbsp;&nbsp;", "Derby Name:",    $data->{derby_name})]),
1185
	    $h->tr ([$h->td ("&nbsp;&nbsp;", "Full Name:",     $data->{real_name})]),
1186
	    $h->tr ([$h->td ("&nbsp;&nbsp;", "Pronouns:",      $data->{pronouns})]),
1187
	    $h->tr ([$h->td ("&nbsp;&nbsp;", "TShirt Size:",   $data->{tshirt})]),
1188
	    $h->tr ([$h->td ("&nbsp;&nbsp;", "Email Address:", $data->{email})]),
1189
	    $h->tr ([$h->td ("&nbsp;&nbsp;", "Phone:",         $data->{phone})])
1190
	  ]);
1191
    $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:",
1192
      $h->a ({ HREF=>$activationlink }, "Activate my VORC Account!"), $h->br,
1193
      "Or you can copy/paste this into the 'Activation Code' box: ".$data->{activation}, $h->br,
1194
      "Once activated, you'll be able to log in. If you're looking to volunteer, some departments are automatically enabled. Others need to be manually reviewed and approved.",
1195
      "If you're looking to sign up for MVP Classes, your MVP Ticket needs to be confirmed. Once that happens, you'll receive another email.",
1196
      "If you're new to using vORC, you may want to read this:",
1197
      $h->a ({ HREF=>"https://volunteers.rollercon.com/info.html" }, "VORC User Info"),
1198
      "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.",
1199
      $h->br,
1200
      "--RollerCon HQ".$h->br.'rollercon@gmail.com'.$h->br."rollercon.com");
1201
  } elsif ($context eq "Activate") {
1202
    $subject .= " Activated!";
1203
    my $tempDepartments = convertDepartments ($data->{department});
1204
    my $printableDepartments = join "\n", map { $depts->{$_}.": ".$AccessLevel->{$tempDepartments->{$_}} } sort keys %{$tempDepartments};
1205
    $body = "Greetings again,
1206
 
1207
You have been approved to volunteer at RollerCon in the following departments:
1208
 
1209
$printableDepartments
1210
 
1211
You may log into vORC and begin signing up for shifts.  Please be considerate of others and don't hogger all of the shifts.  If you do, we will find you and randomly drop your shifts.
1212
 
1213
https://volunteers.rollercon.com/schedule/
1214
 
1215
Please note that you are limited to signing up to a number of shifts per day.  (Meaning, once you sign up for X shifts, you'll have to wait until tomorrow to sign up for more.)  Please understand, while you are a nice, concientious, and good-looking person yourself, who knows how to share, there are others out there that will hogger up all of the shifts.  As time goes by and we get closer to the event, we may lift the limit.  Who knows?
1216
 
1217
If you've already signed up for your daily limit of shifts, and another shift REALLY strikes your fancy, try dropping one of your shifts.  That should allow you to pick up a different one.
1218
 
1219
We'll be adding shifts over time, again to throttle how fast some people (not you, mind you) gobble up the shifts.  Check back, maybe even daily.
1220
 
1221
If you're new to using vORC, you may want to read this:
1222
 
1223
https://volunteers.rollercon.com/info.html
1224
 
1225
If you didn't make this request, well, you're still the only one who received this email, and you now have an active account.  You should probably let us know that someone is messing with you.
1226
 
1227
-RollerCon Management
1228
";
1229
  } else {
1230
    return;
1231
  }
1232
	# send the message
1233
	EmailUser ($email, $subject, $body);
1234
 
1235
}
1236
 
1237
sub validate_emt {
1238
  my $target = shift // "";
1239
  my $change = shift // "";
1240
 
1241
  if (!$target or !$change) {
1242
    warn "ERROR: validate_emt() called without a required parameter! target: $target, change: $change";
1243
    return -1;
1244
  }
1245
 
1246
  my $uservalidate = getUser $target;
1247
  if (!exists $uservalidate->{RCid}) {
1248
    warn "ERROR: validate_emt() called on a non-existant user! target: $target, change: $change";
1249
    return -1;
1250
  }
1251
 
1252
  if ($change eq "add") {
1253
    if ($uservalidate->{emt_verified}) {
1254
      warn "ERROR: validate_emt() called to add on a user already verified: $target, change: $change";
1255
      return -1;
1256
    } else {
1257
      $dbh->do ("insert into emt_credential_verified (RCid, date, verified_by) values (?, date(now()), ?)", undef, $target, $ORCUSER->{RCid}) or warn $dbh->errstr;
1258
      logit ($target, "EMT Credentials Verified");
1259
      logit ($ORCUSER->{RCid}, "Verified EMT Credentials for $uservalidate->{derby_name} [$target]");
1260
    }
1261
  } elsif ($change eq "del") {
1262
    if (!$uservalidate->{emt_verified}) {
1263
      warn "ERROR: validate_emt() called to del on a user that isn't verified: $target, change: $change";
1264
      return -1;
1265
    } else {
1266
      $dbh->do ("delete from emt_credential_verified where date = date(now()) and RCid = ?", undef, $target) or warn $dbh->errstr;
1267
      logit ($target, "EMT Credential Verification removed");
1268
      logit ($ORCUSER->{RCid}, "Removed EMT Credential verification for $uservalidate->{derby_name} [$target]");
1269
    }
1270
  } else {
1271
    warn "ERROR: validate_emt() called with a bad parameter! target: $target, change: $change";
1272
    return -1;
1273
  }
1274
}
1275
 
1276
 
1277
1;