Subversion Repositories VORC

Rev

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

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