diff --git a/admin.php b/admin.php new file mode 100644 index 0000000..c05e201 --- /dev/null +++ b/admin.php @@ -0,0 +1,322 @@ +getConfig(); +$qtrs = $points_center->getQuarters(); +$qtr_info = $points_center->getQuarterInfo(); +$slivkans = $points_center->getSlivkans(); +$fellows = $points_center->getFellows(); + +$date = \DateTime::createFromFormat('Y-m-d', $qtr_info['start_date']); +$start_date = $date->format('m/d/Y'); + +$date = \DateTime::createFromFormat('Y-m-d', $qtr_info['end_date']); +$end_date = $date->format('m/d/Y'); +?> + + + + + Admin - Slivka Points Center + counter-increment: + + +
+
+ + Admin Dashboard +
+
+
+
+
+

Quarter

+
+ + + + + + + + + + + + + + + + + + + + + + + +
Current Quarter + + + + Edit + +
Start DateEdit
End DateEdit
IM Teams + ', $qtr_info['im_teams']) ?> + + + Edit + +
+
+
+
+

Configuration

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Housing SelectionToggle
VP NameEdit
VP EmailEdit
VP Copies EmailEdit
Point Submission
Notifications
Toggle
Mailbot EmailEdit
Mailbot PasswordEdit
+
+
+
+
+
+
+

+ Add/Update Photo +

+
+
+ +
+ + Successfully uploaded photo! +
+ +
+
+ + +
+
+
+ + +
+
+ + +
+
+ +
+
+
+
+

Committee

+
+
+
+
+ +
+
+ Edit +
+
+
+
+
+
+

Suite

+
+
+
+
+ +
+
+ Edit +
+
+
+
+
+
+
+
+
+ + + + diff --git a/ajax/PointsCenter.php b/ajax/PointsCenter.php index 628a607..1ce5495 100644 --- a/ajax/PointsCenter.php +++ b/ajax/PointsCenter.php @@ -1,9 +1,6 @@ prepare( + "INSERT INTO config (name, value) + VALUES (?, ?) + ON DUPLICATE KEY UPDATE value=VALUES(value)" + ); + $statement->execute(array($name, $value)); + } catch (PDOException $e) { + echo "Error: " . $e->getMessage(); + die(); + } + + // If we're changing the quarter make sure a row exists in the `quarters` table + if ($name == 'qtr') { + $qtr = (int) $value; + $quarter_info = self::getQuarterInfo($qtr); + if (!$quarter_info) { + $y = round($qtr, -2); + $q = $qtr - $y; + + $year = 2000 + $y / 100; + + $season = ''; + switch ($q) { + case 1: + $season = 'Winter'; + break; + case 2: + $season = 'Spring'; + break; + default: + $season = 'Fall'; + } + + // get im teams from last year, same season + $last_year_quarter_info = self::getQuarterInfo($qtr - 100); + + if ($last_year_quarter_info) { + $im_teams = json_encode($last_year_quarter_info['im_teams']); + } else { + $im_teams = ''; + } + + try { + $statement = self::$dbConn->prepare( + "INSERT INTO quarters (qtr, quarter, im_teams) + VALUES (?, ?, ?)" + ); + $statement->execute(array($value, $season . ' ' . $year, $im_teams)); + } catch (PDOException $e) { + echo "Error: " . $e->getMessage(); + die(); + } + } + } + + return true; + } + public function getQuarter() { return self::$qtr; @@ -86,8 +145,12 @@ public function getQuarters() return self::fetchAllQuery("SELECT qtr,quarter FROM quarters WHERE 1301 < qtr ORDER BY qtr DESC"); } - public function getQuarterInfo() + public function getQuarterInfo($qtr = false) { + if (!$qtr) { + $qtr = self::$qtr; + } + $quarter_info; try { $statement = self::$dbConn->prepare( @@ -95,7 +158,7 @@ public function getQuarterInfo() FROM quarters WHERE qtr=:qtr" ); - $statement->bindValue(":qtr", self::$qtr); + $statement->bindValue(":qtr", $qtr); $statement->execute(); $quarter_info = $statement->fetch(\PDO::FETCH_ASSOC); } catch (PDOException $e) { @@ -103,11 +166,32 @@ public function getQuarterInfo() die(); } - $quarter_info['im_teams'] = json_decode($quarter_info['im_teams']); + if ($quarter_info) { + $quarter_info['im_teams'] = json_decode($quarter_info['im_teams']); + } return $quarter_info; } + public function updateQuarterInfo($name, $value) + { + try { + $statement = self::$dbConn->prepare( + "UPDATE quarters + SET ".$name."=:value + WHERE qtr=:qtr" + ); + $statement->bindValue(":value", $value); + $statement->bindValue(":qtr", self::$qtr); + $statement->execute(); + } catch (PDOException $e) { + echo "Error: " . $e->getMessage(); + die(); + } + + return true; + } + public function getDirectory() { return self::fetchAllQuery( @@ -155,6 +239,25 @@ public function getFellows() ); } + public function updateFellowPhoto($fellow, $photo) + { + try { + $statement = self::$dbConn->prepare( + "UPDATE fellows + SET photo=:photo + WHERE full_name=:full_name" + ); + $statement->bindValue(":full_name", $fellow); + $statement->bindValue(":photo", $photo); + $statement->execute(); + } catch (PDOException $e) { + echo "Error: " . $e->getMessage(); + die(); + } + + return true; + } + public function getEvents($count = 20, $offset = 0) { $events = array(); @@ -407,9 +510,15 @@ public function getSlivkanBonusPoints($nu_email) $other_breakdown = array( array($bonus['other1_name'] | '', $bonus['other1'] | 0), array($bonus['other2_name'] | '', $bonus['other2'] | 0), - array($bonus['other3_name'] | '', $bonus['other3'] | 0)); + array($bonus['other3_name'] | '', $bonus['other3'] | 0) + ); - return array("helper" => $helper_points, "committee" => $committee_points | 0, "other" => $other_points, "other_breakdown" => $other_breakdown); + return array( + "helper" => $helper_points, + "committee" => $committee_points | 0, + "other" => $other_points, + "other_breakdown" => $other_breakdown + ); } public function getBonusPoints() @@ -548,7 +657,12 @@ public function getPointsTable($showall = false) $by_suite[] = array($s, round(array_sum($totals)/count($totals), 2)); } - return array('points_table' => array_values($points_table), 'events' => $events, 'by_year' => $by_year, 'by_suite' => $by_suite); + return array( + 'points_table' => array_values($points_table), + 'events' => $events, + 'by_year' => $by_year, + 'by_suite' => $by_suite + ); } public function getCommitteePointsTable($committee) @@ -564,13 +678,19 @@ public function getCommitteePointsTable($committee) $events_count = count($events); $total_ind = $events_count + 1; - for ($s=0; $s 0.0, 'filled_by' => false, 'attended' => false, 'contributions' => '', 'comments' => '') + $events_count + 2, + array( + 'points' => 0.0, + 'filled_by' => false, + 'attended' => false, + 'contributions' => '', + 'comments' => '' + ) ); } @@ -579,7 +699,8 @@ public function getCommitteePointsTable($committee) $committee_points_table[$nu_email][$events_count]['points'] = (float) $committee_bonus_points[$i]['bonus']; $committee_points_table[$nu_email][$events_count]['comments'] = $committee_bonus_points[$i]['comments']; - $committee_points_table[$nu_email][$total_ind]['points'] = (float) $committee_bonus_points[$i]['bonus']; # total column + # total column + $committee_points_table[$nu_email][$total_ind]['points'] = (float) $committee_bonus_points[$i]['bonus']; } for ($e=0; $e<$events_count; $e++) { @@ -642,11 +763,13 @@ public function getMultipliers() $q_total = $q_acc + 3 * $y_acc - $slivkans[$s]['qtrs_away']; // give multiplier for current qtr if it isnt housing - if (!$is_housing) { - $q_total += 1; - } + // (commented because it makes more sense that multiplier increases at end of qtr) + // if (!$is_housing) { + // $q_total += 1; + // } // figure out if they are a non res freshman + // NOTE: If not completing a 4-year program, this code mistakes the person for being a sophomore if ($slivkans[$s]['suite'] == 'NonRes') { $y_grad = (int) $y_this / 100 + 3; // four years later, but three if its Fall if ($q_this == 3) { @@ -730,7 +853,8 @@ public function getRankings() $rankings[$i]['total'] = $sum; $rankings[$i]['total_w_mult'] = $sum * $rankings[$i]['mult']; - $rankings[$i]['abstains'] = in_array($rankings[$i]['nu_email'], $abstentions) || $rankings[$i]['total'] < $house_meetings; + $rankings[$i]['abstains'] = in_array($rankings[$i]['nu_email'], $abstentions) || + $rankings[$i]['total'] < $house_meetings; } return array('rankings' => $rankings, 'qtrs' => $qtrs, 'is_housing' => $is_housing); @@ -751,7 +875,7 @@ public function updateTotals() ON DUPLICATE KEY UPDATE total=VALUES(total)" ); - for ($s=0; $s < count($slivkans); $s++) { + for ($s = 0; $s < count($slivkans); $s++) { $nu_email = $slivkans[$s]['nu_email']; $total = $event_totals[$nu_email] + $im_points[$nu_email]; @@ -791,6 +915,25 @@ public function getAbstentions() ); } + public function updateSlivkanPhoto($nu_email, $photo) + { + try { + $statement = self::$dbConn->prepare( + "UPDATE slivkans + SET photo=:photo + WHERE nu_email=:nu_email" + ); + $statement->bindValue(":nu_email", $nu_email); + $statement->bindValue(":photo", $photo); + $statement->execute(); + } catch (PDOException $e) { + echo "Error: " . $e->getMessage(); + die(); + } + + return true; + } + public function getCommittee($committee) { $slivkans = array(); @@ -1294,6 +1437,8 @@ public function pointsCorrectionReply($get) private function sendEmail($to_email, $subject, $body) { + include_once __DIR__ . "/swift/swift_required.php"; + $from = array(self::$config['mailbot_email'] => "Slivka Points Center"); if ($to_email) { diff --git a/ajax/submitConfigOrQuarterInfo.php b/ajax/submitConfigOrQuarterInfo.php new file mode 100644 index 0000000..bab148f --- /dev/null +++ b/ajax/submitConfigOrQuarterInfo.php @@ -0,0 +1,14 @@ +getQuarterInfo()); + +if ($_POST['name'] != 'qtr' && in_array($_POST['name'], $quarter_table_properties)) { + $status = $points_center->updateQuarterInfo($_POST['name'], $_POST['value']); +} else { + $status = $points_center->updateConfig($_POST['name'], $_POST['value']); +} + +echo $status; diff --git a/ajax/submitPhoto.php b/ajax/submitPhoto.php new file mode 100644 index 0000000..ec9d5b7 --- /dev/null +++ b/ajax/submitPhoto.php @@ -0,0 +1,134 @@ + 'php.ini max file size exceeded', + 2 => 'html form max file size exceeded', + 3 => 'file upload was only partial', + 4 => 'no file was attached'); + +//check file size +if ($_FILES['file']['size'] > 20000) { + error('file is too large'); +} + +// check the upload form was actually submitted else print the form +isset($_POST['nu_email']) or isset($_POST['fellow']) + or error('the upload form is neaded'); + +// check for PHP's built-in uploading errors +($_FILES['file']['error'] == 0) + or error($errors[$_FILES['file']['error']]); + +// check that the file we are working on really was the subject of an HTTP upload +@is_uploaded_file($_FILES['file']['tmp_name']) + or error('not an HTTP upload'); + +// validation... since this is an image upload script we should run a check +// to make sure the uploaded file is in fact an image. Here is a simple check: +// getimagesize() returns false if the file tested is not an image. +@getimagesize($_FILES['file']['tmp_name']) + or error('only image uploads are allowed'); + +if ($_POST['type'] == 'slivkan-photo') { + $name = $_POST['nu_email']; +} else { + $name = $_POST['fellow']; +} + +$now = time(); +while (file_exists($uploadFilename = __DIR__.'/../img/slivkans/'.$name.'-'.$now)) { + $now++; +} + +$images = $_FILES['file']['tmp_name']; +$ext = resize(200, $uploadFilename, $_FILES['file']['tmp_name']); + +if ($_POST['type'] == 'slivkan-photo') { + $points_center->updateSlivkanPhoto($name, $name.'-'.$now.'.'.$ext); +} else { + $points_center->updateFellowPhoto($name, $name.'-'.$now.'.'.$ext); +} + +// If you got this far, everything has worked and the file has been successfully saved. +// We are now going to redirect the client to a success page. +header('Location: ' . $uploadSuccess); + +function resize($newWidth, $targetFile, $originalFile) +{ + $info = getimagesize($originalFile); + $mime = $info['mime']; + + switch ($mime) { + case 'image/jpeg': + $image_create_func = 'imagecreatefromjpeg'; + $image_save_func = 'imagejpeg'; + $new_image_ext = 'jpg'; + break; + + case 'image/png': + $image_create_func = 'imagecreatefrompng'; + $image_save_func = 'imagepng'; + $new_image_ext = 'png'; + break; + + case 'image/gif': + $image_create_func = 'imagecreatefromgif'; + $image_save_func = 'imagegif'; + $new_image_ext = 'gif'; + break; + + default: + throw Exception('Unknown image type.'); + } + + $img = $image_create_func($originalFile); + list($width, $height) = getimagesize($originalFile); + + $newHeight = ($height / $width) * $newWidth; + $tmp = imagecreatetruecolor($newWidth, $newHeight); + imagecopyresampled($tmp, $img, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height); + + if (file_exists($targetFile)) { + unlink($targetFile); + } + $image_save_func($tmp, $targetFile.'.'.$new_image_ext); + + //dumb: returning the extension + return $new_image_ext; +} + +// The following function is an error handler which is used +// to output an HTML error page if the file upload fails +function error($error) +{ + header("Refresh: 5; URL=".$uploadForm.""); + echo ''."\n". + ''."\n". + ' '."\n". + ' '."\n". + ' Upload error'."\n". + ' '."\n". + ' '."\n". + '
'."\n". + '

Upload failure

'."\n". + '

An error has occurred: '."\n". + ' ' . $error . '...'."\n". + ' The upload form is reloading

'."\n". + '
'."\n". + ''; + exit; +} // end error handler diff --git a/fellowsTable.php b/fellowsTable.php index 7ddea49..bda3e11 100644 --- a/fellowsTable.php +++ b/fellowsTable.php @@ -1,7 +1,7 @@ getFellows(); ?> @@ -17,15 +17,15 @@ +for ($ii = 0; $ii < count($fellows); $ii++) { + ?> - .jpg" width="100px" /> + " width="100px" /> - diff --git a/js/directory.js b/js/directory.js index e79216c..0f228d7 100644 --- a/js/directory.js +++ b/js/directory.js @@ -1,10 +1,10 @@ -jQuery(document).ready(function($) { +$(function() { 'use strict'; $.getJSON('http://slivka.northwestern.edu/points/ajax/getDirectory.php',function(data){ var path = 'http://slivka.northwestern.edu/points/img/slivkans/'; for (var i=0; i' ].join(''); @@ -36,8 +36,6 @@ jQuery(document).ready(function($) { } }); - //$('td').css('height','auto'); - if(localStorage.directorypass){ $('#directorypass').val(localStorage.directorypass); $('#submitdirectorypass').click(); diff --git a/js/pointsCenter.js b/js/pointsCenter.js index b7f98e6..7727ecf 100644 --- a/js/pointsCenter.js +++ b/js/pointsCenter.js @@ -27,7 +27,7 @@ define(['jquery', 'moment', 'hogan'], function($, moment, Hogan) { valueKey: 'full_name', local: slivkans, template: ['
{{full_name}}', - '{{#photo}}{{/photo}}
'].join(''), + '{{#photo}}{{/photo}}'].join(''), engine: Hogan }; }, @@ -1367,177 +1367,6 @@ define(['jquery', 'moment', 'hogan'], function($, moment, Hogan) { } }, - updateSlivkans = { - init: function() { - $.getJSON('./ajax/getSlivkans.php', function(data) { - slivkans = data.slivkans; - nicknames = data.nicknames; - - //tack on nicknames to slivkans - for(var i=0; i 0){ - $.getJSON('./ajax/getCommitteeOrSuite.php', {committee: event.target.value}, updateSlivkans.addSlivkans); - } - }); - - $('#suite').on('change', function(event){ - $('#committee').val(''); - $('.committee-points').hide(); - - if(event.target.value.length > 0){ - $.getJSON('./ajax/getCommitteeOrSuite.php', {suite: event.target.value}, updateSlivkans.addSlivkans); - } - }); - - $('#submit').on('click', function(){ - var name, pts, - entries = $('#slivkan-entry-tab').find('.slivkan-entry'), - committeePoints = $('.committee-points'), - committee = $('#committee').val(), - suite = $('#suite').val(), - nuEmailArray = [], - committeePointsArray = []; - - for(var i=0; i 0){ - nuEmailArray.push(slivkans[slivkans.indexOfKey('full_name', name)].nu_email); - } - - if(committee.length > 0){ - pts = committeePoints.eq(i).val(); - committeePointsArray.push(pts); - } - } - - if(committee.length > 0){ - $.post( - './ajax/submitCommitteeOrSuite.php', - { - committee: committee, - slivkans: nuEmailArray, - points: committeePointsArray - }, - function(data){ - window.alert(data); - } - ); - }else if(suite.length > 0){ - $.post( - './ajax/submitCommitteeOrSuite.php', - { - suite: suite, - slivkans: nuEmailArray - }, - function(data){ - window.alert(data); - } - ); - } - }); - }, - slivkanTypeahead: function() { - var target = $(this); - - if(target.closest('.slivkan-entry-control').addClass('has-warning').is(':last-child')){ - var num_inputs = $('#slivkan-entry-tab').find('.slivkan-entry').length; - if(num_inputs < 20){ - submission.appendSlivkanInputs(1); - } - } - if(!target.hasClass('tt-query')){ - target.typeahead(typeaheadOpts('slivkans', slivkans)).focus(); - } - }, - validateSlivkanName: function(entry) { - var valid = true, - slivkan_entry = entry.find('.slivkan-entry'), - name = slivkan_entry.val(), - nickname_ind = nicknames.indexOfKey('nickname', name); - - if(nickname_ind != -1){ - name = slivkans[slivkans.indexOfKey('nu_email', nicknames[nickname_ind].nu_email)].full_name; - slivkan_entry.val(name); - } - - var nameArray = []; - - //clear duplicates - $('#slivkan-entry-tab').find('.slivkan-entry').each(function() { - var self = $(this); - if(self.val().length > 0){ - if(nameArray.indexOf(self.val()) == -1){ - nameArray.push(self.val()); - }else{ - self.val(''); - $('#duplicate-alert').show(); - submission.validateSlivkanName(self.parent(), true); - } - } - }); - - //no names = invalid - if(nameArray.length === 0){ valid = false; } - - //update name in case it changed - name = slivkan_entry.val(); - - entry.removeClass('has-warning'); - - if(name.length > 0){ - if(slivkans.indexOfKey('full_name', name) == -1){ valid=false; } - updateValidity(entry, valid); - }else{ - entry.removeClass('has-success has-error'); - } - - return valid; - }, - addSlivkans: function(data) { - var entries = $('#slivkan-entry-tab').find('.slivkan-entry-control'), - len = data.length; - - entries.find('.slivkan-entry').val(''); - - if(entries.length <= len){ - submission.appendSlivkanInputs(len - entries.length + 1); - entries = $('#slivkan-entry-tab').find('.slivkan-entry-control'); - } - - for(var i=0; i', + 'Save
', + 'Cancel', + ''].join('')).appendTo(el.find('td:eq(2)')); + + el.find('td:eq(1)').html(''); + inputEl = el.find('input'); + inputEl.val(thisEl.data('value') || original); + + el.find('[data-save]').on('click', function() { + var val = inputEl.val(); + + if (val == original) { + el.find('[data-cancel]').click(); + } else { + admin.submitConfigOrQuarterInfo(field, val, 'Set ' + field + ' = "' + val + '" for ' + quarter + '?'); + } + + return false; + }); + + el.find('[data-cancel]').on('click', function() { + el.find('.edit').remove(); + el.find('td:eq(1)').html(original); + thisEl.show(); + + return false; + }); + + return false; + }); + + $.getJSON('./ajax/getSlivkans.php', function(data) { + slivkans = data.slivkans; + nicknames = data.nicknames; + + //tack on nicknames to slivkans + for(var i=0; i 0){ + nuEmailArray.push(slivkans[slivkans.indexOfKey('full_name', name)].nu_email); + } + + if(isCommittee){ + pts = committeePoints.eq(i).val(); + committeePointsArray.push(pts); + } + } + + if(isCommittee){ + formData = { + committee: committee, + slivkans: nuEmailArray, + points: committeePointsArray + }; + } else { + formData = { + suite: suite, + slivkans: nuEmailArray + }; + } + $.post( + './ajax/submitCommitteeOrSuite.php', + formData, + function(response){ + if (response == '1') { + $('#editCommitteeOrSuite').modal('hide'); + } else { + window.alert(response); + } + } + ); + + return false; + }); + }); + }, + submitConfigOrQuarterInfo: function(name, value, confirmMessage) { + if (window.confirm(confirmMessage)) { + $.post('./ajax/submitConfigOrQuarterInfo.php', { name: name, value: value }, function(status) { + if (status == '1') { + window.location.reload(); + } else { + window.alert(status); + } + }); + } + }, + slivkanTypeahead: function() { + var target = $(this); + + if(target.closest('.slivkan-entry-control').addClass('has-warning').is(':last-child')){ + var num_inputs = $('#slivkan-entry-tab').find('.slivkan-entry').length; + if(num_inputs < 20){ + submission.appendSlivkanInputs(1); + } + } + if(!target.hasClass('tt-query')){ + target.typeahead(typeaheadOpts('slivkans', slivkans)).focus(); + } + + return false; + }, + validateSlivkanName: function(entry) { + var valid = true, + slivkan_entry = entry.find('.slivkan-entry'), + name = slivkan_entry.val(); + + var nameArray = []; + + //clear duplicates + $('#slivkan-entry-tab').find('.slivkan-entry').each(function() { + var self = $(this); + if(self.val().length > 0){ + if(nameArray.indexOf(self.val()) == -1){ + nameArray.push(self.val()); + }else{ + self.val(''); + $('#duplicate-alert').show(); + submission.validateSlivkanName(self.parent(), true); + } + } + }); + + //no names = invalid + if(nameArray.length === 0){ valid = false; } + + if(name.length > 0){ + if(slivkans.indexOfKey('full_name', name) == -1){ valid=false; } + updateValidity(entry, valid); + }else{ + updateValidity(entry, null); + } + + return valid; + }, + addSlivkans: function(data) { + var entries = $('.slivkan-entry-control', '#slivkan-entry-tab'), + len = data.length; + + entries.find('.slivkan-entry').val(''); + + if(entries.length <= len){ + submission.appendSlivkanInputs(len - entries.length + 1); + entries = $('.slivkan-entry-control', '#slivkan-entry-tab'); + } + + for(var i=0; i