Subversion Repositories VORC

Rev

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