Subversion Repositories VORC

Rev

Rev 162 | Rev 164 | 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
 
163 - 345
 	my ($active_users) = $qdbh->selectrow_array ("select count(*) from session where timestampdiff(minute, timestamp, now()) < 30)");
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>
408
    </ul></div>
409
    </BODY>
410
    <SCRIPT language="JavaScript">
411
   	<!--
153 - 412
    // Refresh the page after a delay
149 - 413
      setTimeout(function(){
414
        location.replace(location.href);
153 - 415
      }, 30000); // 30000 milliseconds = 30 seconds
149 - 416
    //-->
417
    </SCRIPT>
418
    </HTML>
419
busy
420
  return;
421
}
422
 
423
sub canView {
424
	my $A = shift // "";
425
	my $B = shift // "";
426
	# Is A a lead or higher of one of B's Depts? (or they're looking at themselves)
427
	# parameters should be a Hashref to the users' details
428
 
429
	return 1 if $A->{access} > 4 or $A->{RCid} == $B->{RCid}; # viewer and target are the same person or it's a SysAdmin.
430
 
431
	my $ADept = ref $A->{department} eq "HASH" ? $A->{department} : convertDepartments($A->{department});
432
	my $BDept = ref $B->{department} eq "HASH" ? $B->{department} : convertDepartments($B->{department});
433
 
434
	foreach (keys %{$BDept}) {
435
		if ($ADept->{$_} > 1) { # A is a Lead or higher of one of B's departments
436
			return 1;
437
		}
438
	}
439
 
440
	if ($ADept->{MVP} >= RollerCon::LEAD and $B->{MVPid}) {
441
	  # MVP Volunteers can see user details for people with MVP Passes
442
	  return 1;
443
	}
444
 
445
	return 0;
446
}
447
 
448
sub getShiftDepartment {
449
  my $shiftID = shift // "";
450
  my $dept;
451
 
452
  if ($shiftID =~ /^\d+$/) {
453
    ($dept) = $dbh->selectrow_array ("select dept from shift where id = ?", undef, $shiftID);
454
  } else {
455
    my ($id, $role) = split /-/, $shiftID;
456
    if ($role =~ /^CLA/) {
457
      $dept = "CLA";
458
    } else {
459
      ($dept) = $dbh->selectrow_array ("select distinct department from staff_template where role like ?", undef, $role.'%');
460
    }
461
  }
462
#  } elsif ($shiftID =~ /^\d+-ANN/) {
463
#    $dept = "ANN";
464
#  } else {
465
#    $dept = "OFF";
466
#  }
467
 
468
  return $dept;
469
}
470
 
471
sub getClassID {
472
  my $shift = shift // "";
473
  return unless $shift =~ /^\d+$/;
474
 
475
  my $shiftref = getShiftRef ($shift);
476
  my ($classid) = $dbh->selectrow_array ("select id from class where date = ? and start_time = ? and location = ?", undef, $shiftref->{date}, $shiftref->{start_time}, $shiftref->{location});
477
  return $classid unless !$classid;
478
 
479
  warn "ERROR: No class.id found for shift $shiftref->{id}";
480
  return "";
481
}
482
 
483
sub getShiftRef {
484
  my $shiftID = shift // "";
485
  return unless $shiftID =~ /^\d+$/;
486
 
487
  my ($shiftref) = $dbh->selectrow_hashref ("select * from shift where id = ?", undef, $shiftID);
488
  return $shiftref unless $shiftref->{id} != $shiftID;
489
 
490
  warn "ERROR: Couldn't find shift with ID [$shiftID]";
491
  return "";
492
}
493
 
494
sub getDepartments {
495
  my $RCid = shift // "";
496
  # If we get an RCid, return the list of departments and levels for that user.
497
  #   Otherwise (no parameter), return the list of departments with their display names.
498
 
499
	if ($RCid) {
500
  	my $sth = $dbh->prepare("select department from official where RCid = ?");
501
  	$sth->execute($RCid);
502
  	my ($dlist) = $sth->fetchrow;
503
  	return convertDepartments ($dlist);
504
	} else {
505
  	my %HASH;
506
  	my $sth = $dbh->prepare("select TLA, name from department");
507
  	$sth->execute();
508
  	while (my ($tla, $name) = $sth->fetchrow) {
509
  	  $HASH{$tla} = $name;
510
    }
511
    return \%HASH;
512
  }
513
 
514
}
515
 
516
sub convertDepartments {
517
  # For the department membership, converts the DB string back and forth to a hashref...
518
  my $input = shift // "";
519
  my $output;
520
 
521
  if (ref $input eq "HASH") {
522
    $output = join ":", map { $_."-".$input->{$_} } sort keys %{$input};
523
  } else {
524
  	foreach (split /:/, $input) {
525
  	  my ($tla, $level) = split /-/;
526
  	  $output->{$tla} = $level;
527
    }
528
    $output = {} unless ref $output eq "HASH";
529
  }
530
 
531
  return $output;
532
}
533
 
534
sub convertTime {
535
  my $time = shift || return;
536
 
537
  if ($time =~ / - /) {
538
    return join " - ", map { convertTime ($_) } split / - /, $time;
539
  }
540
 
541
  $time =~ s/^(\d{1,2}:\d{2}):\d{2}$/$1/;
542
  $time =~ s/^0//;
543
 
544
  if ($ORCUSER->{timeformat} eq "24hr") {
545
    if ($time =~ /^\d{1,2}:\d{2}$/) { return $time; }
546
  } else {
547
    my ($hr, $min) = split /:/, $time;
548
    my $ampm = " am";
549
    if ($hr >= 12) {
550
      $hr -= 12 unless $hr == 12;
551
      $ampm = " pm";
552
    } elsif ($hr == 0) {
553
      $hr = 12;
554
    }
555
    return $hr.":".$min.$ampm;
556
  }
557
}
558
 
559
sub getSchedule {
560
  my $RCid = shift // return "ERROR: No RCid provided to getSchedule";
561
  my $filter = shift // "";
562
  my $year = 1900 + (localtime)[5];
563
 
564
  my @whereclause;
565
  if ($filter eq "all") {
153 - 566
  	push @whereclause, "year(date) >= year(now())";
149 - 567
  } else {
568
  	push @whereclause, "date >= date(now())";
569
  }
570
#  if ($RCid ne $ORCUSER->{RCid}) {
571
#    push @whereclause, "dept != 'PER'";
572
#  }
573
 
574
  use DateTime;
575
  my $dt = DateTime->today (time_zone => 'America/Los_Angeles');
576
  $dt =~ s/T00\:00\:00$//;
577
  my $now = DateTime->now (time_zone => 'America/Los_Angeles');
578
 
579
 
580
  use HTML::Tiny;
581
  my $h = HTML::Tiny->new( mode => 'html' );
582
 
583
  my $where = scalar @whereclause ? "where ".join " and ", @whereclause : "";
584
  my @shifts;
585
  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
586
                                          select id, date, dayofweek, track as location, time, role, teams, signup, 'ANN' as dept, volhours from v_shift_announcer where RCid = ? union
587
                                          select id, date, dayofweek, location, time, role, '' as teams, type as signup, dept, volhours from v_shift where RCid = ? union
588
                                          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
589
                           $where order by date, time");
590
  $sth->execute($RCid, $RCid, $RCid, $RCid);
591
  my $hours = 0;
592
  while (my $s = $sth->fetchrow_hashref) {
593
    my ($yyyy, $mm, $dd) = split /\-/, $s->{date};
594
	  my $cutoff = DateTime->new(
595
        year => $yyyy,
596
        month => $mm,
597
        day => $dd,
598
        hour => 5,
599
        minute => 0,
600
        second => 0,
601
        time_zone => 'America/Los_Angeles'
602
    );
603
 
604
 
605
  	if (!$s->{teams} or $s->{dept} eq "CLA") {
606
  	  # it's a time-based shift
607
  	  if ($s->{dept} eq "PER") {
608
        if ($RCid eq $ORCUSER->{RCid}) {
609
          # DROP
152 - 610
  	      $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 - 611
  	    } else {
612
  	      $s->{location} = "";
613
  	      $s->{role} = "";
614
  	    }
615
      } elsif (($RCid == $ORCUSER->{RCid} and $s->{signup} !~ /^selected/ and $now < $cutoff) or ($ORCUSER->{department}->{$s->{dept}} >= 2 or $ORCUSER->{access} >= 5)) {
616
        # DROP
617
        my ($shiftORclass, $linkargs) = ("shift", "");
618
        if ($s->{dept} eq "CLA") {
619
          $shiftORclass = "class";
620
          $linkargs = "&role=$s->{role}";
621
          $s->{role} = $s->{teams};
622
          $s->{teams} = "";
623
        }
624
	   		$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");
625
	   		if ($ORCUSER->{department}->{$s->{dept}} >= 2 or $ORCUSER->{access} >= 5) {
626
   		    # NO SHOW
627
 	  	    $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");
628
 		    }
629
 
630
  		}
631
#  		$hours += $s->{volhours} unless $s->{dept} eq "PER" or $s->{dept} eq "CLA";
632
 
633
    } elsif (($RCid == $ORCUSER->{RCid} and $s->{signup} !~ /^selected/ and $now < $cutoff) or ($ORCUSER->{department}->{$s->{dept}} >= 2 or $ORCUSER->{access} >= 5)) {
634
      # it's a game shift
635
      #DROP
636
  		$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");
637
   		if ($ORCUSER->{department}->{$s->{dept}} >= 2 or $ORCUSER->{access} >= 5) {
638
 		    # NO SHOW
639
        $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");
640
      }
641
#      $hours += $s->{volhours};
642
  	}
643
  	$s->{role} =~ s/\-\d+$//;
644
 
645
#  	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});
646
#  	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});
647
    $s->{time} = convertTime $s->{time};
152 - 648
    if ($s->{dept} eq "PER") {
649
  	  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}) ]));
650
    } else {
651
  	  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}) ]));
652
  	}
149 - 653
    $hours += $s->{volhours} unless $s->{dept} eq "PER" or $s->{dept} eq "CLA";
654
  }
655
 
656
  if (scalar @shifts) {
657
    return $h->ul ([ @shifts, $h->h5 ("Currently showing $hours hours of Volunteer Time.") ]);
658
  } else {
659
    return $h->p ({ class=>"hint" }, "[nothing scheduled at the moment]");
660
  }
661
}
662
 
663
sub getRCid {
664
  my $derbyname = shift;
665
  ($derbyname) = $dbh->selectrow_array ("select RCid from official where derby_name = ?", undef, $derbyname);
666
  return $derbyname;
667
}
668
 
669
sub getSetting {
670
	my $k = shift;
671
 
672
	my ($value) = $dbh->selectrow_array ("select setting.value from setting where setting.key = ?", undef, $k);
673
  return defined $value ? $value : undef;
674
}
675
 
676
sub getUser {
677
	my $ID = shift;
678
 
679
	my $sth;
680
	if ($ID =~ /^\d+$/) {
681
	  $sth = $dbh->prepare("select * from v_official where RCid = ?");
682
	} else {
683
	  $sth = $dbh->prepare("select * from v_official where email = ?");
684
  }
685
	$sth->execute($ID);
686
 
687
	my $user = $sth->fetchrow_hashref;
688
	map { $user->{$_} = "" unless $user->{$_} } keys %{$user};
689
	return $user->{RCid} ? $user : "";
690
}
691
 
692
sub getUserEmail {
693
	my $RCid = shift;
694
	my $sth = $dbh->prepare("select email from official where RCid = ?");
695
	$sth->execute($RCid);
696
	my ($email) = $sth->fetchrow_array();
697
	return $email;
698
}
699
 
700
sub getUserDerbyName {
701
	my $RCid = shift;
702
	my $sth = $dbh->prepare("select derby_name from official where RCid = ?");
703
	$sth->execute($RCid);
704
	my ($dname) = $sth->fetchrow_array();
705
	return $dname;
706
}
707
 
708
sub getYears {
709
	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");
710
#	my $sth = $dbh->prepare("select distinct year(date) from v_shift_admin_view");
711
	$sth->execute();
712
	my @years;
713
	while (my ($y) =$sth->fetchrow_array()) { push @years, $y; }
714
	return \@years;
715
}
716
 
717
sub printRCHeader {
718
	my $PAGE_TITLE = shift;
719
#	use CGI qw/start_html/;
720
	use HTML::Tiny;
721
  my $h = HTML::Tiny->new( mode => 'html' );
722
 
723
#  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]");
724
  my $referrer = param ("referrer") ? param ("referrer") : $ENV{HTTP_REFERER};
725
  my $logout = (!$referrer or $referrer eq url) ? "" : $h->button ({ onClick=>"window.location.href='$referrer';" }, "Back")."&nbsp;";
726
  $logout .= url =~ /\/(index.pl)?$/ ? "" : $h->button ({ onClick=>"window.location.href='/schedule/';" }, "Home")."&nbsp;";
727
#  $logout .= $h->button ({ onClick=>"document.cookie = 'RCAUTH=; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/'; location.href='/';" }, "Log Out");
728
  $logout .= $h->button ({ onClick=>"location.href='?LOGOUT';" }, "Log Out");
729
	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 : "";
730
 
731
  print start_html (-title=>"vORC - $PAGE_TITLE", -style => {'src' => "/style.css"} );
732
 
733
#<html><head><title>Officials' RollerCon Schedule Manager - $PAGE_TITLE</title>
734
#<link rel="stylesheet" type="text/css" href="/style.css">
735
#</head>
736
#<body text="#000000" bgcolor="#FFFFFF" link="#0000EE" vlink="#551A8B" alink="#FF0000">
737
	print $h->div ({ class=>"sp0" }, [ $h->div ({ class=>"spLeft" },  $h->a ({ href=>"/schedule/" }, $h->img ({ src=>"/logo.jpg", width=>"75", height=>"75" }))),
738
	                                   $h->div ({ class=>"spRight" }, [ $h->h1 (["vORC $PAGE_TITLE", $h->br]),
739
	                                   $loggedinas,
740
	                                   ])
741
	                                 ]);
742
#print<<rcheader;
743
#  <TABLE>
744
#	<TR class="nostripe">
745
#		<TD align=right><img SRC="/logo.jpg"></TD>
746
#		<TD align=center valign=middle><b><font size=+3>Officials' RollerCon<br>Schedule Manager<br>$PAGE_TITLE</FONT></b>
747
#	<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>
748
#	</TR>
749
 
750
#rcheader
751
}
752
 
753
sub changeShift {
754
	my ($change, $shift_id, $role, $user_id) = @_;
755
  if ($shift_id =~ /(am|pm)/) {
756
    my ($td, $st, $tl) = split /\|/, $shift_id;
757
    my ($hr, $min, $ampm) = split /:|\s/, $st;
758
    if ($ampm eq "pm") { $hr += 12; }
759
    elsif ($ampm eq "am" and $hr == 12) { $hr = "00" }
760
 
761
    $st = $hr.":".$min;
762
    $shift_id = join "|", ($td, $st, $tl);
763
  }
764
#warn join " - ", $change, $shift_id, $role, $user_id;
765
	my $leadership_change = 0;
766
#	my $department = getShiftDepartment ($role ? $shift_id."-".$role : $shift_id);
767
	my $department;
768
	if ($shift_id =~ /^\d+$/) {
769
		$department = getShiftDepartment ($role ? $shift_id."-".$role : $shift_id);
770
	} else {
771
		$department = "CLA";
772
		if ($change eq "del") {
773
		  ($shift_id, $role) = $dbh->selectrow_array ("select id, role from v_class_signup where date = ? and start_time = ? and location = ?", undef, split /\|/, $shift_id);
774
		} else {
775
		  ($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);
776
		}
777
    $role = "CLA-1" unless $role; # If no one has signed up for the class yet, the SQL above doesn't retrieve the first available
778
	}
779
#	my $game_based = $role ? "game" : "shift";
780
	my $game_based = $role =~ /^CLA-/ ? "class" : $role ? "game" : "shift";
781
	my $sth;
782
 
783
	if ($change eq "add" or $change eq "override") {
784
  	my $taken;
785
		if ($department eq "CLA") {
786
  	  ($taken) = $shift_id ? 0 : 1;
787
  	} elsif ($game_based eq "game") {
788
  	  ($taken) = $dbh->selectrow_array ("select count(*) from assignment where Gid = ? and role = ?", undef, $shift_id, $role);
789
  	} else {
790
  	  ($taken) = $dbh->selectrow_array ('select count(*) from shift where id = ? and (isnull(assignee_id) = 0 or assignee_id <> "")', undef, $shift_id);
791
  	}
792
  	if ($taken) {
793
  	    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";
794
  	}
795
  }
796
 
797
	if (lc ($user_id) ne lc ($ORCUSER->{RCid})) { # they're changing someone else's schedule...
798
	  if (($department eq "CLA" and $ORCUSER->{department}->{MVP} >= 2) or $ORCUSER->{department}->{$department} >= 2 or $ORCUSER->{access} >= 5 or $ORCUSER->{department}->{VCI} >= 2) {
799
	    # the user making the change is either a lead in the dept, a sysadmin, or a VCI lead
800
	    logit ($ORCUSER->{RCid}, "$ORCUSER->{derby_name} changed someone else's schedule. ($change, $shift_id, $role, $user_id)");
801
	    logit ($user_id, "Schedule was changed by $ORCUSER->{derby_name}. ($change, $shift_id, $role, $user_id)");
802
	    $leadership_change = 1;
803
	  } else {
804
	    logit ($ORCUSER->{RCid}, "Unauthorized attempt to change someone else's schedule. ($change, $shift_id, $role, $user_id)");
805
	    return "<br>Denied! You are not authorized to change someone else's schedule in this department ($department).<br>\n";
806
	  }
807
	} elsif ($ORCUSER->{department}->{$department} >= 3 or $ORCUSER->{access} >= 5) {
808
	  # Managers can sign up for as many shifts within their own department as they like...
809
	  $leadership_change = 1;
810
	}
811
 
812
  if ($change eq "add") {
813
    if ($department eq "CLA" and !getUser($user_id)->{MVPid}) {
814
      return "<br>Denied! User ($user_id) does not have an MVP Pass!<br>\n";
815
    } elsif ($department ne "CLA" and getUser($user_id)->{department} and convertDepartments(getUser($user_id)->{department})->{$department} < 1) {
816
      return "<br>Denied! User ($user_id) is not a member of Department ($department)!<br>\n" unless $department eq "CMP";
817
    }
818
  }
819
 
820
  my $conflict = findConflict ($user_id, $shift_id, $game_based);
821
  if ($change eq "add" and $conflict) {
822
		return "<br>Denied! There is a conflict ($conflict) with that shift's time!<br>\n";
823
  }
824
 
825
  my $game_type;
826
  if ($department ne "CLA") {
827
   	($game_type) = $dbh->selectrow_array ("select type from ".$game_based." where id = ?", undef, $shift_id);
828
 
829
   	if ($game_type =~ /^selected/ and !$leadership_change) {
830
   	  return "<br>Denied! Only leadership can make changes to 'selected staffing' shifts!<br>\n" unless $department eq "CMP";
831
   	}
832
 
833
   	if ($change eq "add" and $game_type eq "lead" and convertDepartments(getUser($user_id)->{department})->{$department} < 2 and $ORCUSER->{access} < 3) {
834
   	  return "<br>Denied! Shift reserved for leadership staff!<br>\n";
835
   	}
836
  } else {
837
    $game_type = "class";
838
  }
839
 
840
 
841
# 	my $MAXSHIFTS = getSetting ("MAX_SHIFT_SIGNUP_PER_DAY");
842
	my $MAXSHIFTS = getSetting ("MAX_SHIFT_SIGNUP_PER_DAY_".$department);
843
	$MAXSHIFTS = getSetting ("MAX_SHIFT_SIGNUP_PER_DAY") unless defined $MAXSHIFTS;
844
	if ($game_type eq "lead" and $department eq "OFF") { $MAXSHIFTS = 99; }
845
 
846
  my $daily_count;
847
  if ($department eq "CLA") {
848
    # MVP Class Sign-up
849
    $MAXSHIFTS = getSetting ("MAX_CLASS_SIGNUP");
153 - 850
	  ($daily_count) = $dbh->selectrow_array ("select count(*) from v_class_signup where RCid = ? and year(date) = year(now())", undef, $user_id);
149 - 851
#	  ($daily_count) = $dbh->selectrow_array ("select count(*) from v_shift where RCid = ? and dept = 'CLA'", undef, $user_id);
852
   	if ($change eq "add" and $daily_count >= $MAXSHIFTS and !$leadership_change) {
853
	    return "<br>Denied! You may only sign up for $MAXSHIFTS Classes!<br>\n";
854
	  }
855
  } else {
856
   	$daily_count = signUpCount ('get', $user_id, $department);
857
   	if ($change eq "add" and $daily_count >= $MAXSHIFTS and !$leadership_change) {
858
   		return "<br>Denied! You may only sign up for $MAXSHIFTS $game_type shifts in one day!<br>\n";
859
   	}
860
   	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) {
861
    	my $dept_table = $department eq 'OFF' ? "v_shift_officiating" : "v_shift_announcer";
862
    	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);
863
  		my $full_length_max = getSetting("MAX_FULL_LENGTH_SIGNUP_".$department);
864
  		if ($full_length_count >= $full_length_max) {
865
  		  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";
866
  			return $errormsg;
867
  		}
868
    }
869
  }
870
 
871
 	my @DBARGS;
872
  if ($game_based eq "game" or $game_based eq "class") {
873
  	if ($change eq "add" or $change eq "override") {
874
  		$sth = $dbh->prepare("insert into assignment (Gid, role, RCid) values (?, ?, ?)");
875
  	} elsif ($change eq "del") {
876
  		$sth = $dbh->prepare("delete from assignment where Gid = ? and role = ? and RCid= ?");
877
  	}
878
  	@DBARGS = ($shift_id, $role, $user_id);
879
  } else {
880
  	if ($change eq "add" or $change eq "override") {
881
  		$sth = $dbh->prepare("update shift set assignee_id = ? where id = ? and isnull(assignee_id) = 1");
882
  		@DBARGS = ($user_id, $shift_id);
883
  	} elsif ($change eq "del") {
884
  		$sth = $dbh->prepare("update shift set assignee_id = null where id = ?");
885
  		@DBARGS = ($shift_id);
886
  	}
887
  }
888
 
889
  print "<br>attempting to make DB changes...<br>";
890
  if ($sth->execute (@DBARGS)) {
891
  	$daily_count = signUpCount ($change, $user_id, $department) unless $leadership_change;
892
  	logit ($user_id, "Shift ".ucfirst($change).": $shift_id -> $role");
893
  	logit ($ORCUSER->{RCid}, "OVERRIDE: Shift ".ucfirst($change).": $shift_id -> $role") if $change eq "override";
894
  	if ($department eq "CLA") {
895
  	  print "Success!...<br>You've signed up for $daily_count class(es) (you're currently allowed to sign up for $MAXSHIFTS).<br>\n";
896
  	} else {
897
  	  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";
898
  	}
899
  	return;
900
  } else {
901
  	if ($department eq "CLA") {
902
      return "<br><b>You did not get the class</b>, most likely because it filled up while you were looking.<br>\nERROR: ", $sth->errstr();
903
  	} else {
904
      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();
905
    }
906
  }
907
}
908
 
909
sub modShiftTime {
910
	my ($shift_id, $user_id, $diff) = @_;
911
	my $ORCUSER = getUser (1);
912
 
913
	use Scalar::Util qw(looks_like_number);
914
	if (!looks_like_number ($diff)) {
915
	  print "<br>ERROR! The time adjustment ($diff) doesn't look like a number.<br>\n";
916
  	return;
917
	}
918
 
919
  my ($validate_assignee) = $dbh->selectrow_array ("select count(*) from v_shift where id = ? and RCid = ?", undef, $shift_id, $user_id);
920
 	if (!$validate_assignee) {
921
	  print "<br>ERROR! This shift is assigned to someone else.<br>\n";
922
  	return;
923
 	}
924
 
925
	my $department = getShiftDepartment ($shift_id);
926
  if (convertDepartments ($ORCUSER->{department})->{$department} < 2 and $ORCUSER->{access} < 5) {
927
	  print "<br>ERROR! You're not authorized to modify this shift's time.<br>\n";
928
	  logit ($ORCUSER->{RCid}, "Unauthorized attempt to modify shift time. ($department, $shift_id)");
929
  	return;
930
 	}
931
 
932
  my $rows_changed;
933
  print "<br>attempting to make DB changes...<br>";
934
  if ($diff == 0) {
935
	  $rows_changed = $dbh->do ("update shift set mod_time = null where id = ? and assignee_id = ?", undef, $shift_id, $user_id);
936
  } else {
937
	  $rows_changed = $dbh->do ("update shift set mod_time = ? where id = ? and assignee_id = ?", undef, $diff, $shift_id, $user_id);
938
  }
939
 
940
 
941
  if (!$rows_changed or $dbh->errstr) {
942
  	print "ERROR: Nothing got updated".$dbh->errstr;
943
  	logit (0, "ERROR modifying a shift time ($diff, $shift_id, $user_id):".$dbh->errstr);
944
  } else {
945
  	print "SUCCESS: Shift $shift_id succesfully modified by $diff hour(s)";
946
  	logit ($ORCUSER->{RCid}, "SUCCESS: Shift $shift_id succesfully modified by $diff hour(s)");
947
 
948
  }
949
  return;
950
}
951
 
952
sub signUpCount {
953
	my $action = shift;
954
	my $id = shift;
955
	my $dept = shift // "";
956
 
957
	if ($id eq $ORCUSER->{RCid}) {
958
		if ($action eq 'add') {
959
			if (signUpCount ('get', $id, $dept)) {
960
				$dbh->do("update sign_up_count set sign_ups = sign_ups + 1 where date = curdate() and RCid = ? and department = ?", undef, $id, $dept);
961
			} else {
962
				$dbh->do("replace into sign_up_count (date, RCid, department, sign_ups) values (curdate(), ?, ?, 1)", undef, $id, $dept);
963
			}
964
		} elsif ($action eq 'del') {
965
			if (signUpCount ('get', $id, $dept)) {
966
				$dbh->do("update sign_up_count set sign_ups = sign_ups - 1 where date = curdate() and RCid = ? and department = ?", undef, $id, $dept);
967
			}
968
		}
969
	}
970
 
971
	my ($R) = $dbh->selectrow_array ("select sign_ups from sign_up_count where RCid = ? and department = ? and date = curdate()", undef, $id, $dept);
972
 
973
	return $R ? $R : '0';
974
}
975
 
976
sub signUpEligible {
977
	my $user = shift;
978
	my $t = shift;
979
	my $shifttype = shift // "game";
980
	my $dept = $t->{dept} // "";
981
  my $DEPTHASH = getDepartments ();
982
  if ($dept and !exists $DEPTHASH->{$dept}) {
983
    my %reverso = reverse %{$DEPTHASH};
984
    $dept = $reverso{$dept};
985
  }
986
 
987
	my $limit = getSetting ("MAX_SHIFT_SIGNUP_PER_DAY_".$dept);
988
	$limit = getSetting ("MAX_SHIFT_SIGNUP_PER_DAY") unless defined $limit;
989
 
990
	if (lc $t->{type} eq "lead" and $dept eq "OFF") { $limit = 99; }
991
 
992
	return 0 unless $limit > 0;
993
 
994
	my $limitkey = $dept ? "sign_ups_today_".$dept : "sign_ups_today";
995
 
996
	if ($shifttype eq "class") {
997
		($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});
998
		$t->{dept} = "CLA";
999
		$dept = "CLA";
1000
		$t->{type} = "open";
1001
	}
1002
 
1003
	if (findConflict ($user->{RCid}, $t->{id}, $shifttype)) { return 0; }
1004
 
1005
	if (!exists $user->{$limitkey}) {
1006
		$user->{$limitkey} = signUpCount('get', $user->{RCid}, $dept);
1007
	}
1008
 
1009
	if ($shifttype eq "game") {
1010
#    if ($t->{gtype} !~ /^selected/ and $t->{gtype} ne "short track" and $user->{$limitkey} < $limit) {
1011
		if ($t->{gtype} eq "full length" and ($dept eq "OFF" or $dept eq "ANN")) {
1012
		  my $table = $dept eq "OFF" ? "v_shift_officiating" : "v_shift_announcer";
1013
			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});
1014
			if ($full_length_count >= getSetting ("MAX_FULL_LENGTH_SIGNUP_".$dept)) {
1015
				return 0;
1016
			}
1017
		}
1018
    if (lc $t->{signup} ne "selected" and $user->{$limitkey} < $limit) {
1019
			return 1;
1020
		} else {
1021
			return 0;
1022
		}
1023
	} else {
1024
    if ($dept eq "CLA") {
1025
      # MVP Class Sign-up
1026
			return 0 unless $user->{MVPid};
1027
      my $class_limit = getSetting ("MAX_CLASS_SIGNUP");
1028
			my ($class_count) = $dbh->selectrow_array ("select count(*) from v_class_signup where RCid = ? and year(date) = year(now())", undef, $user->{RCid});
1029
			return 0 unless $class_count < $class_limit;
1030
    } else {
1031
	    if ($user->{department}->{$dept} < 1) { return 0; }
1032
	  }
1033
	  if (lc $t->{type} eq "lead" and $user->{department}->{$dept} < 2) { return 0; }
1034
	  if (lc $t->{type} eq "manager" and $user->{department}->{$dept} < 3) { return 0; }
1035
	  if ($dept eq "EMT" and $user->{emt_verified} == 0) { return 0; }
1036
    if (lc $t->{type} !~ /^selected/ and $user->{$limitkey} < $limit) {
1037
			return 1;
1038
		} else {
1039
			return 0;
1040
		}
1041
	}
1042
}
1043
 
1044
sub findConflict {
1045
  my $rcid = shift;
1046
  my $gid = shift;
1047
  my $type = shift // "";
152 - 1048
  my ($date, $start, $end, $existing, $conflicts);
149 - 1049
 
1050
  if ($type eq "game") {
1051
  # Are they already signed up for this game? (It's faster to check the two views one at a time...)
1052
#    ($conflicts) = $dbh->selectrow_array ("select count(*) from v_shift_officiating where substring_index(id, '-', 1) = ? and RCid = ?", undef, $gid, $rcid);
1053
    ($conflicts) = $dbh->selectrow_array ("select count(*) from v_shift_officiating where id = ? and RCid = ?", undef, $gid, $rcid);
1054
  	if ($conflicts) { return "OFF-".$gid; } # no need to keep looking...
1055
    ($conflicts) = $dbh->selectrow_array ("select count(*) from v_shift_announcer where id = ? and RCid = ?", undef, $gid, $rcid);
1056
  	if ($conflicts) { return "ANN-".$gid; } # no need to keep looking...
1057
 
1058
    ($date, $start, $end) = $dbh->selectrow_array ("select distinct date, time, end_time from game where id = ?", undef, $gid);
1059
  } elsif ($type eq "class")  {
1060
    ($conflicts) = $dbh->selectrow_array ("select count(*) from v_class_signup where id = ? and RCid = ?", undef, $gid, $rcid);
1061
  	if ($conflicts) { return "CLA:".$gid; } # no need to keep looking...
1062
 
1063
    ($date, $start, $end) = $dbh->selectrow_array ("select distinct date, start_time, end_time from v_class where id = ?", undef, $gid);
1064
 
1065
  } elsif ($type eq "personal")  {
152 - 1066
    ($date, $start, $end, $existing) = @{ $gid };
149 - 1067
  } else {
1068
    ($date, $start, $end) = $dbh->selectrow_array ("select distinct date, start_time, end_time from shift where id = ?", undef, $gid);
1069
  }
1070
 
1071
  # Are they signed up for any games that would conflict with this one?
1072
#  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 = ?");
1073
#  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 = ?");
1074
 
1075
  ($conflicts) = $dbh->selectrow_array ("select * from (
152 - 1076
    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
1077
    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
1078
    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
1079
    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
1080
    where conflict <> ?",
1081
    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 - 1082
  );
1083
 
1084
  return $conflicts;
1085
}
1086
 
1087
sub changeLeadShift {
1088
	my ($change, $lshift, $user_id) = @_;
1089
	my $ERRMSG;
1090
 
1091
	my $sth = $dbh->prepare("update lead_shift set assignee_id = ? where id = ?");
1092
 
1093
	print "<br>attempting to make DB changes...<br>";
1094
	if ($change eq "add") {
1095
		$sth->execute($user_id, $lshift)
1096
    	or $ERRMSG = "ERROR: Can't execute SQL statement: ".$sth->errstr()."\n";
1097
	} elsif ($change eq "del") {
1098
		$sth->execute('', $lshift)
1099
    	or $ERRMSG = "ERROR: Can't execute SQL statement: ".$sth->errstr()."\n";
1100
	}
1101
	if ($ERRMSG) {
1102
		print $ERRMSG;
1103
	} else {
1104
		logit($user_id, "Lead Shift ".ucfirst($change).": $lshift");
1105
  	print "Success.<br>";
1106
  }
1107
}
1108
 
1109
sub logit {
1110
	my $RCid = shift;
1111
	my $msg = shift;
1112
	my $sth = $dbh->prepare("insert into log (RCid, event) values (?, ?)");
1113
	$sth->execute($RCid, $msg);
1114
}
1115
 
1116
sub sendNewUserEMail {
1117
	my $context = shift;
1118
	my $data = shift;
1119
	use RCMailer;
1120
  use HTML::Tiny;
1121
  my $h = HTML::Tiny->new( mode => 'html' );
1122
  my $depts = getDepartments (); # HashRef of the department TLAs -> Display Names...
1123
  my $AccessLevel = getAccessLevels;
1124
 
1125
	my $email = $data->{email};
1126
	my $subject = 'RollerCon VORC - New User';
1127
	my $body;
1128
	if ($context eq "New User") {
1129
    $subject .= " Request";
1130
    my $activationlink = url ()."?activate=".$data->{activation};
1131
	  $body = $h->p ("Greetings,");
1132
	  $body .= $h->p ("It appears as though you've registered a new account in RollerCon's VORC system with the following information:");
1133
	  $body .= $h->table ([
1134
	    $h->tr ([$h->td ("&nbsp;&nbsp;", "Derby Name:",    $data->{derby_name})]),
1135
	    $h->tr ([$h->td ("&nbsp;&nbsp;", "Full Name:",     $data->{real_name})]),
1136
	    $h->tr ([$h->td ("&nbsp;&nbsp;", "Pronouns:",      $data->{pronouns})]),
1137
	    $h->tr ([$h->td ("&nbsp;&nbsp;", "TShirt Size:",   $data->{tshirt})]),
1138
	    $h->tr ([$h->td ("&nbsp;&nbsp;", "Email Address:", $data->{email})]),
1139
	    $h->tr ([$h->td ("&nbsp;&nbsp;", "Phone:",         $data->{phone})])
1140
	  ]);
1141
    $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:",
1142
      $h->a ({ HREF=>$activationlink }, "Activate my VORC Account!"), $h->br,
1143
      "Or you can copy/paste this into the 'Activation Code' box: ".$data->{activation}, $h->br,
1144
      "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.",
1145
      "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.",
1146
      "If you're new to using vORC, you may want to read this:",
1147
      $h->a ({ HREF=>"https://volunteers.rollercon.com/info.html" }, "VORC User Info"),
1148
      "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.",
1149
      $h->br,
1150
      "--RollerCon HQ".$h->br.'rollercon@gmail.com'.$h->br."rollercon.com");
1151
  } elsif ($context eq "Activate") {
1152
    $subject .= " Activated!";
1153
    my $tempDepartments = convertDepartments ($data->{department});
1154
    my $printableDepartments = join "\n", map { $depts->{$_}.": ".$AccessLevel->{$tempDepartments->{$_}} } sort keys %{$tempDepartments};
1155
    $body = "Greetings again,
1156
 
1157
You have been approved to volunteer at RollerCon in the following departments:
1158
 
1159
$printableDepartments
1160
 
1161
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.
1162
 
1163
https://volunteers.rollercon.com/schedule/
1164
 
1165
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?
1166
 
1167
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.
1168
 
1169
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.
1170
 
1171
If you're new to using vORC, you may want to read this:
1172
 
1173
https://volunteers.rollercon.com/info.html
1174
 
1175
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.
1176
 
1177
-RollerCon Management
1178
";
1179
  } else {
1180
    return;
1181
  }
1182
	# send the message
1183
	EmailUser ($email, $subject, $body);
1184
 
1185
}
1186
 
1187
sub validate_emt {
1188
  my $target = shift // "";
1189
  my $change = shift // "";
1190
 
1191
  if (!$target or !$change) {
1192
    warn "ERROR: validate_emt() called without a required parameter! target: $target, change: $change";
1193
    return -1;
1194
  }
1195
 
1196
  my $uservalidate = getUser $target;
1197
  if (!exists $uservalidate->{RCid}) {
1198
    warn "ERROR: validate_emt() called on a non-existant user! target: $target, change: $change";
1199
    return -1;
1200
  }
1201
 
1202
  if ($change eq "add") {
1203
    if ($uservalidate->{emt_verified}) {
1204
      warn "ERROR: validate_emt() called to add on a user already verified: $target, change: $change";
1205
      return -1;
1206
    } else {
1207
      $dbh->do ("insert into emt_credential_verified (RCid, date, verified_by) values (?, date(now()), ?)", undef, $target, $ORCUSER->{RCid}) or warn $dbh->errstr;
1208
      logit ($target, "EMT Credentials Verified");
1209
      logit ($ORCUSER->{RCid}, "Verified EMT Credentials for $uservalidate->{derby_name} [$target]");
1210
    }
1211
  } elsif ($change eq "del") {
1212
    if (!$uservalidate->{emt_verified}) {
1213
      warn "ERROR: validate_emt() called to del on a user that isn't verified: $target, change: $change";
1214
      return -1;
1215
    } else {
1216
      $dbh->do ("delete from emt_credential_verified where date = date(now()) and RCid = ?", undef, $target) or warn $dbh->errstr;
1217
      logit ($target, "EMT Credential Verification removed");
1218
      logit ($ORCUSER->{RCid}, "Removed EMT Credential verification for $uservalidate->{derby_name} [$target]");
1219
    }
1220
  } else {
1221
    warn "ERROR: validate_emt() called with a bad parameter! target: $target, change: $change";
1222
    return -1;
1223
  }
1224
}
1225
 
1226
 
1227
1;