Subversion Repositories VORC

Rev

Rev 180 | Rev 200 | 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 // "";
180 - 563
  my $output = shift // "";
149 - 564
  my $year = 1900 + (localtime)[5];
565
 
566
  my @whereclause;
567
  if ($filter eq "all") {
153 - 568
  	push @whereclause, "year(date) >= year(now())";
149 - 569
  } else {
570
  	push @whereclause, "date >= date(now())";
571
  }
572
#  if ($RCid ne $ORCUSER->{RCid}) {
573
#    push @whereclause, "dept != 'PER'";
574
#  }
575
 
576
  use DateTime;
577
  my $dt = DateTime->today (time_zone => 'America/Los_Angeles');
578
  $dt =~ s/T00\:00\:00$//;
579
  my $now = DateTime->now (time_zone => 'America/Los_Angeles');
580
 
581
 
582
  use HTML::Tiny;
583
  my $h = HTML::Tiny->new( mode => 'html' );
584
 
585
  my $where = scalar @whereclause ? "where ".join " and ", @whereclause : "";
586
  my @shifts;
587
  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
588
                                          select id, date, dayofweek, track as location, time, role, teams, signup, 'ANN' as dept, volhours from v_shift_announcer where RCid = ? union
589
                                          select id, date, dayofweek, location, time, role, '' as teams, type as signup, dept, volhours from v_shift where RCid = ? union
590
                                          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
591
                           $where order by date, time");
592
  $sth->execute($RCid, $RCid, $RCid, $RCid);
593
  my $hours = 0;
594
  while (my $s = $sth->fetchrow_hashref) {
595
    my ($yyyy, $mm, $dd) = split /\-/, $s->{date};
596
	  my $cutoff = DateTime->new(
597
        year => $yyyy,
598
        month => $mm,
599
        day => $dd,
600
        hour => 5,
601
        minute => 0,
602
        second => 0,
603
        time_zone => 'America/Los_Angeles'
604
    );
605
 
606
 
607
  	if (!$s->{teams} or $s->{dept} eq "CLA") {
608
  	  # it's a time-based shift
609
  	  if ($s->{dept} eq "PER") {
610
        if ($RCid eq $ORCUSER->{RCid}) {
611
          # DROP
152 - 612
  	      $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 - 613
  	    } else {
614
  	      $s->{location} = "";
615
  	      $s->{role} = "";
616
  	    }
617
      } elsif (($RCid == $ORCUSER->{RCid} and $s->{signup} !~ /^selected/ and $now < $cutoff) or ($ORCUSER->{department}->{$s->{dept}} >= 2 or $ORCUSER->{access} >= 5)) {
618
        # DROP
619
        my ($shiftORclass, $linkargs) = ("shift", "");
620
        if ($s->{dept} eq "CLA") {
621
          $shiftORclass = "class";
622
          $linkargs = "&role=$s->{role}";
623
          $s->{role} = $s->{teams};
624
          $s->{teams} = "";
625
        }
626
	   		$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");
627
	   		if ($ORCUSER->{department}->{$s->{dept}} >= 2 or $ORCUSER->{access} >= 5) {
628
   		    # NO SHOW
629
 	  	    $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");
630
 		    }
631
 
632
  		}
633
#  		$hours += $s->{volhours} unless $s->{dept} eq "PER" or $s->{dept} eq "CLA";
634
 
635
    } elsif (($RCid == $ORCUSER->{RCid} and $s->{signup} !~ /^selected/ and $now < $cutoff) or ($ORCUSER->{department}->{$s->{dept}} >= 2 or $ORCUSER->{access} >= 5)) {
636
      # it's a game shift
637
      #DROP
638
  		$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");
639
   		if ($ORCUSER->{department}->{$s->{dept}} >= 2 or $ORCUSER->{access} >= 5) {
640
 		    # NO SHOW
641
        $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");
642
      }
643
#      $hours += $s->{volhours};
644
  	}
645
  	$s->{role} =~ s/\-\d+$//;
646
 
647
#  	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});
648
#  	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});
649
    $s->{time} = convertTime $s->{time};
152 - 650
    if ($s->{dept} eq "PER") {
651
  	  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}) ]));
652
    } else {
653
  	  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}) ]));
654
  	}
149 - 655
    $hours += $s->{volhours} unless $s->{dept} eq "PER" or $s->{dept} eq "CLA";
656
  }
180 - 657
 
658
  if ($output eq "hours") {
659
    return $hours;
660
  }
149 - 661
 
662
  if (scalar @shifts) {
663
    return $h->ul ([ @shifts, $h->h5 ("Currently showing $hours hours of Volunteer Time.") ]);
664
  } else {
665
    return $h->p ({ class=>"hint" }, "[nothing scheduled at the moment]");
666
  }
667
}
668
 
669
sub getRCid {
670
  my $derbyname = shift;
671
  ($derbyname) = $dbh->selectrow_array ("select RCid from official where derby_name = ?", undef, $derbyname);
672
  return $derbyname;
673
}
674
 
675
sub getSetting {
676
	my $k = shift;
677
 
678
	my ($value) = $dbh->selectrow_array ("select setting.value from setting where setting.key = ?", undef, $k);
679
  return defined $value ? $value : undef;
680
}
681
 
682
sub getUser {
683
	my $ID = shift;
684
 
685
	my $sth;
686
	if ($ID =~ /^\d+$/) {
687
	  $sth = $dbh->prepare("select * from v_official where RCid = ?");
688
	} else {
689
	  $sth = $dbh->prepare("select * from v_official where email = ?");
690
  }
691
	$sth->execute($ID);
692
 
693
	my $user = $sth->fetchrow_hashref;
694
	map { $user->{$_} = "" unless $user->{$_} } keys %{$user};
695
	return $user->{RCid} ? $user : "";
696
}
697
 
698
sub getUserEmail {
699
	my $RCid = shift;
700
	my $sth = $dbh->prepare("select email from official where RCid = ?");
701
	$sth->execute($RCid);
702
	my ($email) = $sth->fetchrow_array();
703
	return $email;
704
}
705
 
706
sub getUserDerbyName {
707
	my $RCid = shift;
708
	my $sth = $dbh->prepare("select derby_name from official where RCid = ?");
709
	$sth->execute($RCid);
710
	my ($dname) = $sth->fetchrow_array();
711
	return $dname;
712
}
713
 
714
sub getYears {
190 - 715
	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");
149 - 716
#	my $sth = $dbh->prepare("select distinct year(date) from v_shift_admin_view");
717
	$sth->execute();
718
	my @years;
719
	while (my ($y) =$sth->fetchrow_array()) { push @years, $y; }
720
	return \@years;
721
}
722
 
723
sub printRCHeader {
724
	my $PAGE_TITLE = shift;
725
#	use CGI qw/start_html/;
726
	use HTML::Tiny;
727
  my $h = HTML::Tiny->new( mode => 'html' );
728
 
729
#  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]");
730
  my $referrer = param ("referrer") ? param ("referrer") : $ENV{HTTP_REFERER};
731
  my $logout = (!$referrer or $referrer eq url) ? "" : $h->button ({ onClick=>"window.location.href='$referrer';" }, "Back")."&nbsp;";
732
  $logout .= url =~ /\/(index.pl)?$/ ? "" : $h->button ({ onClick=>"window.location.href='/schedule/';" }, "Home")."&nbsp;";
733
#  $logout .= $h->button ({ onClick=>"document.cookie = 'RCAUTH=; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/'; location.href='/';" }, "Log Out");
734
  $logout .= $h->button ({ onClick=>"location.href='?LOGOUT';" }, "Log Out");
735
	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 : "";
736
 
737
  print start_html (-title=>"vORC - $PAGE_TITLE", -style => {'src' => "/style.css"} );
738
 
739
#<html><head><title>Officials' RollerCon Schedule Manager - $PAGE_TITLE</title>
740
#<link rel="stylesheet" type="text/css" href="/style.css">
741
#</head>
742
#<body text="#000000" bgcolor="#FFFFFF" link="#0000EE" vlink="#551A8B" alink="#FF0000">
743
	print $h->div ({ class=>"sp0" }, [ $h->div ({ class=>"spLeft" },  $h->a ({ href=>"/schedule/" }, $h->img ({ src=>"/logo.jpg", width=>"75", height=>"75" }))),
744
	                                   $h->div ({ class=>"spRight" }, [ $h->h1 (["vORC $PAGE_TITLE", $h->br]),
745
	                                   $loggedinas,
746
	                                   ])
747
	                                 ]);
748
#print<<rcheader;
749
#  <TABLE>
750
#	<TR class="nostripe">
751
#		<TD align=right><img SRC="/logo.jpg"></TD>
752
#		<TD align=center valign=middle><b><font size=+3>Officials' RollerCon<br>Schedule Manager<br>$PAGE_TITLE</FONT></b>
753
#	<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>
754
#	</TR>
755
 
756
#rcheader
757
}
758
 
759
sub changeShift {
760
	my ($change, $shift_id, $role, $user_id) = @_;
761
  if ($shift_id =~ /(am|pm)/) {
762
    my ($td, $st, $tl) = split /\|/, $shift_id;
763
    my ($hr, $min, $ampm) = split /:|\s/, $st;
764
    if ($ampm eq "pm") { $hr += 12; }
765
    elsif ($ampm eq "am" and $hr == 12) { $hr = "00" }
766
 
767
    $st = $hr.":".$min;
768
    $shift_id = join "|", ($td, $st, $tl);
769
  }
770
#warn join " - ", $change, $shift_id, $role, $user_id;
771
	my $leadership_change = 0;
772
#	my $department = getShiftDepartment ($role ? $shift_id."-".$role : $shift_id);
773
	my $department;
774
	if ($shift_id =~ /^\d+$/) {
775
		$department = getShiftDepartment ($role ? $shift_id."-".$role : $shift_id);
776
	} else {
777
		$department = "CLA";
778
		if ($change eq "del") {
779
		  ($shift_id, $role) = $dbh->selectrow_array ("select id, role from v_class_signup where date = ? and start_time = ? and location = ?", undef, split /\|/, $shift_id);
780
		} else {
781
		  ($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);
782
		}
783
    $role = "CLA-1" unless $role; # If no one has signed up for the class yet, the SQL above doesn't retrieve the first available
784
	}
785
#	my $game_based = $role ? "game" : "shift";
786
	my $game_based = $role =~ /^CLA-/ ? "class" : $role ? "game" : "shift";
787
	my $sth;
788
 
789
	if ($change eq "add" or $change eq "override") {
790
  	my $taken;
791
		if ($department eq "CLA") {
792
  	  ($taken) = $shift_id ? 0 : 1;
793
  	} elsif ($game_based eq "game") {
794
  	  ($taken) = $dbh->selectrow_array ("select count(*) from assignment where Gid = ? and role = ?", undef, $shift_id, $role);
795
  	} else {
796
  	  ($taken) = $dbh->selectrow_array ('select count(*) from shift where id = ? and (isnull(assignee_id) = 0 or assignee_id <> "")', undef, $shift_id);
797
  	}
798
  	if ($taken) {
799
  	    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";
800
  	}
801
  }
802
 
803
	if (lc ($user_id) ne lc ($ORCUSER->{RCid})) { # they're changing someone else's schedule...
804
	  if (($department eq "CLA" and $ORCUSER->{department}->{MVP} >= 2) or $ORCUSER->{department}->{$department} >= 2 or $ORCUSER->{access} >= 5 or $ORCUSER->{department}->{VCI} >= 2) {
805
	    # the user making the change is either a lead in the dept, a sysadmin, or a VCI lead
806
	    logit ($ORCUSER->{RCid}, "$ORCUSER->{derby_name} changed someone else's schedule. ($change, $shift_id, $role, $user_id)");
807
	    logit ($user_id, "Schedule was changed by $ORCUSER->{derby_name}. ($change, $shift_id, $role, $user_id)");
808
	    $leadership_change = 1;
809
	  } else {
810
	    logit ($ORCUSER->{RCid}, "Unauthorized attempt to change someone else's schedule. ($change, $shift_id, $role, $user_id)");
811
	    return "<br>Denied! You are not authorized to change someone else's schedule in this department ($department).<br>\n";
812
	  }
813
	} elsif ($ORCUSER->{department}->{$department} >= 3 or $ORCUSER->{access} >= 5) {
814
	  # Managers can sign up for as many shifts within their own department as they like...
815
	  $leadership_change = 1;
816
	}
817
 
818
  if ($change eq "add") {
819
    if ($department eq "CLA" and !getUser($user_id)->{MVPid}) {
820
      return "<br>Denied! User ($user_id) does not have an MVP Pass!<br>\n";
821
    } elsif ($department ne "CLA" and getUser($user_id)->{department} and convertDepartments(getUser($user_id)->{department})->{$department} < 1) {
822
      return "<br>Denied! User ($user_id) is not a member of Department ($department)!<br>\n" unless $department eq "CMP";
823
    }
824
  }
825
 
826
  my $conflict = findConflict ($user_id, $shift_id, $game_based);
827
  if ($change eq "add" and $conflict) {
828
		return "<br>Denied! There is a conflict ($conflict) with that shift's time!<br>\n";
829
  }
830
 
831
  my $game_type;
832
  if ($department ne "CLA") {
833
   	($game_type) = $dbh->selectrow_array ("select type from ".$game_based." where id = ?", undef, $shift_id);
834
 
835
   	if ($game_type =~ /^selected/ and !$leadership_change) {
836
   	  return "<br>Denied! Only leadership can make changes to 'selected staffing' shifts!<br>\n" unless $department eq "CMP";
837
   	}
838
 
839
   	if ($change eq "add" and $game_type eq "lead" and convertDepartments(getUser($user_id)->{department})->{$department} < 2 and $ORCUSER->{access} < 3) {
840
   	  return "<br>Denied! Shift reserved for leadership staff!<br>\n";
841
   	}
842
  } else {
843
    $game_type = "class";
844
  }
845
 
846
 
847
# 	my $MAXSHIFTS = getSetting ("MAX_SHIFT_SIGNUP_PER_DAY");
848
	my $MAXSHIFTS = getSetting ("MAX_SHIFT_SIGNUP_PER_DAY_".$department);
849
	$MAXSHIFTS = getSetting ("MAX_SHIFT_SIGNUP_PER_DAY") unless defined $MAXSHIFTS;
850
	if ($game_type eq "lead" and $department eq "OFF") { $MAXSHIFTS = 99; }
851
 
852
  my $daily_count;
853
  if ($department eq "CLA") {
854
    # MVP Class Sign-up
855
    $MAXSHIFTS = getSetting ("MAX_CLASS_SIGNUP");
153 - 856
	  ($daily_count) = $dbh->selectrow_array ("select count(*) from v_class_signup where RCid = ? and year(date) = year(now())", undef, $user_id);
149 - 857
#	  ($daily_count) = $dbh->selectrow_array ("select count(*) from v_shift where RCid = ? and dept = 'CLA'", undef, $user_id);
858
   	if ($change eq "add" and $daily_count >= $MAXSHIFTS and !$leadership_change) {
859
	    return "<br>Denied! You may only sign up for $MAXSHIFTS Classes!<br>\n";
860
	  }
861
  } else {
862
   	$daily_count = signUpCount ('get', $user_id, $department);
863
   	if ($change eq "add" and $daily_count >= $MAXSHIFTS and !$leadership_change) {
864
   		return "<br>Denied! You may only sign up for $MAXSHIFTS $game_type shifts in one day!<br>\n";
865
   	}
866
   	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) {
867
    	my $dept_table = $department eq 'OFF' ? "v_shift_officiating" : "v_shift_announcer";
868
    	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);
869
  		my $full_length_max = getSetting("MAX_FULL_LENGTH_SIGNUP_".$department);
870
  		if ($full_length_count >= $full_length_max) {
871
  		  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";
872
  			return $errormsg;
873
  		}
874
    }
875
  }
876
 
877
 	my @DBARGS;
878
  if ($game_based eq "game" or $game_based eq "class") {
879
  	if ($change eq "add" or $change eq "override") {
880
  		$sth = $dbh->prepare("insert into assignment (Gid, role, RCid) values (?, ?, ?)");
881
  	} elsif ($change eq "del") {
882
  		$sth = $dbh->prepare("delete from assignment where Gid = ? and role = ? and RCid= ?");
883
  	}
884
  	@DBARGS = ($shift_id, $role, $user_id);
885
  } else {
886
  	if ($change eq "add" or $change eq "override") {
887
  		$sth = $dbh->prepare("update shift set assignee_id = ? where id = ? and isnull(assignee_id) = 1");
888
  		@DBARGS = ($user_id, $shift_id);
889
  	} elsif ($change eq "del") {
890
  		$sth = $dbh->prepare("update shift set assignee_id = null where id = ?");
891
  		@DBARGS = ($shift_id);
892
  	}
893
  }
894
 
175 - 895
  my $wb_act_code;
896
  if ($change eq "del" and $department eq "CLA") {
897
    ($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-%');
898
  }
899
 
149 - 900
  print "<br>attempting to make DB changes...<br>";
901
  if ($sth->execute (@DBARGS)) {
902
  	$daily_count = signUpCount ($change, $user_id, $department) unless $leadership_change;
903
  	logit ($user_id, "Shift ".ucfirst($change).": $shift_id -> $role");
904
  	logit ($ORCUSER->{RCid}, "OVERRIDE: Shift ".ucfirst($change).": $shift_id -> $role") if $change eq "override";
905
  	if ($department eq "CLA") {
175 - 906
  	  print "Success!...<br>You've signed up for $daily_count class(es) (you're currently allowed to sign up for $MAXSHIFTS).<br>\n";
907
 	    updateWRSTBND ($change, $wb_act_code, $DBARGS[0], $DBARGS[2]);
149 - 908
  	} else {
909
  	  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";
910
  	}
911
  	return;
912
  } else {
913
  	if ($department eq "CLA") {
914
      return "<br><b>You did not get the class</b>, most likely because it filled up while you were looking.<br>\nERROR: ", $sth->errstr();
915
  	} else {
916
      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();
917
    }
918
  }
919
}
920
 
175 - 921
sub updateWRSTBND {
922
  my ($change, $wb_act_code, $shift_id, $user_id) = @_;
923
  use REST::Client;
924
  use JSON;
925
  my $headers = { Authorization => '601037851507c624' };
926
  my $client = REST::Client->new();
177 - 927
  $client->setHost('https://core.wrstbnd.io');
928
 
175 - 929
  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);
930
 
931
  if ($change eq "add" or $change eq "override") {
932
    my ($classid) = $dbh->selectrow_array ("select wrstbnd_id from class where id = ?", undef, $shift_id);
933
 
934
    my $body = {
935
      "eventId"      => "event_Y5567UWwS5",
936
      "activeStatus" => "active",
937
      "ticketTypeId" => $classid
938
    };
939
    my $json_body = encode_json $body;
940
 
941
    $client->POST(
942
      '/rest/core/v1/ticket',
943
      $json_body,
944
      $headers
945
    );
946
    my $response = from_json($client->responseContent());
947
 
948
    my $activationCode = $response->{activationCode};
949
 
180 - 950
#    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'`;
951
#    my $add_response = $add_response[$#add_response];
952
#    chomp $add_response;
175 - 953
 
180 - 954
#    $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 - 955
 
956
    return;
957
  } elsif ($change eq "del") {
958
    my $activationCode = $wb_act_code;
178 - 959
    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 - 960
  }
961
 
962
}
963
 
149 - 964
sub modShiftTime {
965
	my ($shift_id, $user_id, $diff) = @_;
966
	my $ORCUSER = getUser (1);
967
 
968
	use Scalar::Util qw(looks_like_number);
969
	if (!looks_like_number ($diff)) {
970
	  print "<br>ERROR! The time adjustment ($diff) doesn't look like a number.<br>\n";
971
  	return;
972
	}
973
 
974
  my ($validate_assignee) = $dbh->selectrow_array ("select count(*) from v_shift where id = ? and RCid = ?", undef, $shift_id, $user_id);
975
 	if (!$validate_assignee) {
976
	  print "<br>ERROR! This shift is assigned to someone else.<br>\n";
977
  	return;
978
 	}
979
 
980
	my $department = getShiftDepartment ($shift_id);
981
  if (convertDepartments ($ORCUSER->{department})->{$department} < 2 and $ORCUSER->{access} < 5) {
982
	  print "<br>ERROR! You're not authorized to modify this shift's time.<br>\n";
983
	  logit ($ORCUSER->{RCid}, "Unauthorized attempt to modify shift time. ($department, $shift_id)");
984
  	return;
985
 	}
986
 
987
  my $rows_changed;
988
  print "<br>attempting to make DB changes...<br>";
989
  if ($diff == 0) {
990
	  $rows_changed = $dbh->do ("update shift set mod_time = null where id = ? and assignee_id = ?", undef, $shift_id, $user_id);
991
  } else {
992
	  $rows_changed = $dbh->do ("update shift set mod_time = ? where id = ? and assignee_id = ?", undef, $diff, $shift_id, $user_id);
993
  }
994
 
995
 
996
  if (!$rows_changed or $dbh->errstr) {
997
  	print "ERROR: Nothing got updated".$dbh->errstr;
998
  	logit (0, "ERROR modifying a shift time ($diff, $shift_id, $user_id):".$dbh->errstr);
999
  } else {
1000
  	print "SUCCESS: Shift $shift_id succesfully modified by $diff hour(s)";
1001
  	logit ($ORCUSER->{RCid}, "SUCCESS: Shift $shift_id succesfully modified by $diff hour(s)");
1002
 
1003
  }
1004
  return;
1005
}
1006
 
1007
sub signUpCount {
1008
	my $action = shift;
1009
	my $id = shift;
1010
	my $dept = shift // "";
1011
 
1012
	if ($id eq $ORCUSER->{RCid}) {
1013
		if ($action eq 'add') {
1014
			if (signUpCount ('get', $id, $dept)) {
1015
				$dbh->do("update sign_up_count set sign_ups = sign_ups + 1 where date = curdate() and RCid = ? and department = ?", undef, $id, $dept);
1016
			} else {
1017
				$dbh->do("replace into sign_up_count (date, RCid, department, sign_ups) values (curdate(), ?, ?, 1)", undef, $id, $dept);
1018
			}
1019
		} elsif ($action eq 'del') {
1020
			if (signUpCount ('get', $id, $dept)) {
1021
				$dbh->do("update sign_up_count set sign_ups = sign_ups - 1 where date = curdate() and RCid = ? and department = ?", undef, $id, $dept);
1022
			}
1023
		}
1024
	}
1025
 
1026
	my ($R) = $dbh->selectrow_array ("select sign_ups from sign_up_count where RCid = ? and department = ? and date = curdate()", undef, $id, $dept);
1027
 
1028
	return $R ? $R : '0';
1029
}
1030
 
1031
sub signUpEligible {
1032
	my $user = shift;
1033
	my $t = shift;
1034
	my $shifttype = shift // "game";
1035
	my $dept = $t->{dept} // "";
1036
  my $DEPTHASH = getDepartments ();
1037
  if ($dept and !exists $DEPTHASH->{$dept}) {
1038
    my %reverso = reverse %{$DEPTHASH};
1039
    $dept = $reverso{$dept};
1040
  }
1041
 
1042
	my $limit = getSetting ("MAX_SHIFT_SIGNUP_PER_DAY_".$dept);
1043
	$limit = getSetting ("MAX_SHIFT_SIGNUP_PER_DAY") unless defined $limit;
1044
 
1045
	if (lc $t->{type} eq "lead" and $dept eq "OFF") { $limit = 99; }
1046
 
1047
	return 0 unless $limit > 0;
1048
 
1049
	my $limitkey = $dept ? "sign_ups_today_".$dept : "sign_ups_today";
1050
 
1051
	if ($shifttype eq "class") {
1052
		($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});
1053
		$t->{dept} = "CLA";
1054
		$dept = "CLA";
1055
		$t->{type} = "open";
1056
	}
1057
 
1058
	if (findConflict ($user->{RCid}, $t->{id}, $shifttype)) { return 0; }
1059
 
1060
	if (!exists $user->{$limitkey}) {
1061
		$user->{$limitkey} = signUpCount('get', $user->{RCid}, $dept);
1062
	}
1063
 
1064
	if ($shifttype eq "game") {
1065
#    if ($t->{gtype} !~ /^selected/ and $t->{gtype} ne "short track" and $user->{$limitkey} < $limit) {
1066
		if ($t->{gtype} eq "full length" and ($dept eq "OFF" or $dept eq "ANN")) {
1067
		  my $table = $dept eq "OFF" ? "v_shift_officiating" : "v_shift_announcer";
1068
			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});
1069
			if ($full_length_count >= getSetting ("MAX_FULL_LENGTH_SIGNUP_".$dept)) {
1070
				return 0;
1071
			}
1072
		}
1073
    if (lc $t->{signup} ne "selected" and $user->{$limitkey} < $limit) {
1074
			return 1;
1075
		} else {
1076
			return 0;
1077
		}
1078
	} else {
1079
    if ($dept eq "CLA") {
1080
      # MVP Class Sign-up
1081
			return 0 unless $user->{MVPid};
1082
      my $class_limit = getSetting ("MAX_CLASS_SIGNUP");
1083
			my ($class_count) = $dbh->selectrow_array ("select count(*) from v_class_signup where RCid = ? and year(date) = year(now())", undef, $user->{RCid});
1084
			return 0 unless $class_count < $class_limit;
1085
    } else {
1086
	    if ($user->{department}->{$dept} < 1) { return 0; }
1087
	  }
1088
	  if (lc $t->{type} eq "lead" and $user->{department}->{$dept} < 2) { return 0; }
1089
	  if (lc $t->{type} eq "manager" and $user->{department}->{$dept} < 3) { return 0; }
1090
	  if ($dept eq "EMT" and $user->{emt_verified} == 0) { return 0; }
1091
    if (lc $t->{type} !~ /^selected/ and $user->{$limitkey} < $limit) {
1092
			return 1;
1093
		} else {
1094
			return 0;
1095
		}
1096
	}
1097
}
1098
 
1099
sub findConflict {
1100
  my $rcid = shift;
1101
  my $gid = shift;
1102
  my $type = shift // "";
152 - 1103
  my ($date, $start, $end, $existing, $conflicts);
149 - 1104
 
1105
  if ($type eq "game") {
1106
  # Are they already signed up for this game? (It's faster to check the two views one at a time...)
1107
#    ($conflicts) = $dbh->selectrow_array ("select count(*) from v_shift_officiating where substring_index(id, '-', 1) = ? and RCid = ?", undef, $gid, $rcid);
1108
    ($conflicts) = $dbh->selectrow_array ("select count(*) from v_shift_officiating where id = ? and RCid = ?", undef, $gid, $rcid);
1109
  	if ($conflicts) { return "OFF-".$gid; } # no need to keep looking...
1110
    ($conflicts) = $dbh->selectrow_array ("select count(*) from v_shift_announcer where id = ? and RCid = ?", undef, $gid, $rcid);
1111
  	if ($conflicts) { return "ANN-".$gid; } # no need to keep looking...
1112
 
1113
    ($date, $start, $end) = $dbh->selectrow_array ("select distinct date, time, end_time from game where id = ?", undef, $gid);
1114
  } elsif ($type eq "class")  {
1115
    ($conflicts) = $dbh->selectrow_array ("select count(*) from v_class_signup where id = ? and RCid = ?", undef, $gid, $rcid);
1116
  	if ($conflicts) { return "CLA:".$gid; } # no need to keep looking...
1117
 
1118
    ($date, $start, $end) = $dbh->selectrow_array ("select distinct date, start_time, end_time from v_class where id = ?", undef, $gid);
1119
 
1120
  } elsif ($type eq "personal")  {
152 - 1121
    ($date, $start, $end, $existing) = @{ $gid };
149 - 1122
  } else {
1123
    ($date, $start, $end) = $dbh->selectrow_array ("select distinct date, start_time, end_time from shift where id = ?", undef, $gid);
1124
  }
1125
 
1126
  # Are they signed up for any games that would conflict with this one?
1127
#  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 = ?");
1128
#  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 = ?");
1129
 
1130
  ($conflicts) = $dbh->selectrow_array ("select * from (
152 - 1131
    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
1132
    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
1133
    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
1134
    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
1135
    where conflict <> ?",
1136
    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 - 1137
  );
1138
 
1139
  return $conflicts;
1140
}
1141
 
1142
sub changeLeadShift {
1143
	my ($change, $lshift, $user_id) = @_;
1144
	my $ERRMSG;
1145
 
1146
	my $sth = $dbh->prepare("update lead_shift set assignee_id = ? where id = ?");
1147
 
1148
	print "<br>attempting to make DB changes...<br>";
1149
	if ($change eq "add") {
1150
		$sth->execute($user_id, $lshift)
1151
    	or $ERRMSG = "ERROR: Can't execute SQL statement: ".$sth->errstr()."\n";
1152
	} elsif ($change eq "del") {
1153
		$sth->execute('', $lshift)
1154
    	or $ERRMSG = "ERROR: Can't execute SQL statement: ".$sth->errstr()."\n";
1155
	}
1156
	if ($ERRMSG) {
1157
		print $ERRMSG;
1158
	} else {
1159
		logit($user_id, "Lead Shift ".ucfirst($change).": $lshift");
1160
  	print "Success.<br>";
1161
  }
1162
}
1163
 
1164
sub logit {
1165
	my $RCid = shift;
1166
	my $msg = shift;
1167
	my $sth = $dbh->prepare("insert into log (RCid, event) values (?, ?)");
1168
	$sth->execute($RCid, $msg);
1169
}
1170
 
1171
sub sendNewUserEMail {
1172
	my $context = shift;
1173
	my $data = shift;
1174
	use RCMailer;
1175
  use HTML::Tiny;
1176
  my $h = HTML::Tiny->new( mode => 'html' );
1177
  my $depts = getDepartments (); # HashRef of the department TLAs -> Display Names...
1178
  my $AccessLevel = getAccessLevels;
1179
 
1180
	my $email = $data->{email};
1181
	my $subject = 'RollerCon VORC - New User';
1182
	my $body;
1183
	if ($context eq "New User") {
1184
    $subject .= " Request";
1185
    my $activationlink = url ()."?activate=".$data->{activation};
1186
	  $body = $h->p ("Greetings,");
1187
	  $body .= $h->p ("It appears as though you've registered a new account in RollerCon's VORC system with the following information:");
1188
	  $body .= $h->table ([
1189
	    $h->tr ([$h->td ("&nbsp;&nbsp;", "Derby Name:",    $data->{derby_name})]),
1190
	    $h->tr ([$h->td ("&nbsp;&nbsp;", "Full Name:",     $data->{real_name})]),
1191
	    $h->tr ([$h->td ("&nbsp;&nbsp;", "Pronouns:",      $data->{pronouns})]),
1192
	    $h->tr ([$h->td ("&nbsp;&nbsp;", "TShirt Size:",   $data->{tshirt})]),
1193
	    $h->tr ([$h->td ("&nbsp;&nbsp;", "Email Address:", $data->{email})]),
1194
	    $h->tr ([$h->td ("&nbsp;&nbsp;", "Phone:",         $data->{phone})])
1195
	  ]);
1196
    $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:",
1197
      $h->a ({ HREF=>$activationlink }, "Activate my VORC Account!"), $h->br,
1198
      "Or you can copy/paste this into the 'Activation Code' box: ".$data->{activation}, $h->br,
1199
      "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.",
1200
      "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.",
1201
      "If you're new to using vORC, you may want to read this:",
1202
      $h->a ({ HREF=>"https://volunteers.rollercon.com/info.html" }, "VORC User Info"),
1203
      "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.",
1204
      $h->br,
1205
      "--RollerCon HQ".$h->br.'rollercon@gmail.com'.$h->br."rollercon.com");
1206
  } elsif ($context eq "Activate") {
1207
    $subject .= " Activated!";
1208
    my $tempDepartments = convertDepartments ($data->{department});
1209
    my $printableDepartments = join "\n", map { $depts->{$_}.": ".$AccessLevel->{$tempDepartments->{$_}} } sort keys %{$tempDepartments};
1210
    $body = "Greetings again,
1211
 
1212
You have been approved to volunteer at RollerCon in the following departments:
1213
 
1214
$printableDepartments
1215
 
1216
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.
1217
 
1218
https://volunteers.rollercon.com/schedule/
1219
 
1220
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?
1221
 
1222
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.
1223
 
1224
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.
1225
 
1226
If you're new to using vORC, you may want to read this:
1227
 
1228
https://volunteers.rollercon.com/info.html
1229
 
1230
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.
1231
 
1232
-RollerCon Management
1233
";
1234
  } else {
1235
    return;
1236
  }
1237
	# send the message
1238
	EmailUser ($email, $subject, $body);
1239
 
1240
}
1241
 
1242
sub validate_emt {
1243
  my $target = shift // "";
1244
  my $change = shift // "";
1245
 
1246
  if (!$target or !$change) {
1247
    warn "ERROR: validate_emt() called without a required parameter! target: $target, change: $change";
1248
    return -1;
1249
  }
1250
 
1251
  my $uservalidate = getUser $target;
1252
  if (!exists $uservalidate->{RCid}) {
1253
    warn "ERROR: validate_emt() called on a non-existant user! target: $target, change: $change";
1254
    return -1;
1255
  }
1256
 
1257
  if ($change eq "add") {
1258
    if ($uservalidate->{emt_verified}) {
1259
      warn "ERROR: validate_emt() called to add on a user already verified: $target, change: $change";
1260
      return -1;
1261
    } else {
1262
      $dbh->do ("insert into emt_credential_verified (RCid, date, verified_by) values (?, date(now()), ?)", undef, $target, $ORCUSER->{RCid}) or warn $dbh->errstr;
1263
      logit ($target, "EMT Credentials Verified");
1264
      logit ($ORCUSER->{RCid}, "Verified EMT Credentials for $uservalidate->{derby_name} [$target]");
1265
    }
1266
  } elsif ($change eq "del") {
1267
    if (!$uservalidate->{emt_verified}) {
1268
      warn "ERROR: validate_emt() called to del on a user that isn't verified: $target, change: $change";
1269
      return -1;
1270
    } else {
1271
      $dbh->do ("delete from emt_credential_verified where date = date(now()) and RCid = ?", undef, $target) or warn $dbh->errstr;
1272
      logit ($target, "EMT Credential Verification removed");
1273
      logit ($ORCUSER->{RCid}, "Removed EMT Credential verification for $uservalidate->{derby_name} [$target]");
1274
    }
1275
  } else {
1276
    warn "ERROR: validate_emt() called with a bad parameter! target: $target, change: $change";
1277
    return -1;
1278
  }
1279
}
1280
 
1281
 
1282
1;