Subversion Repositories VORC

Rev

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