From e869071626395ae699959521bfc30257a05e5b7e Mon Sep 17 00:00:00 2001 From: Jeremy Voros Date: Thu, 30 Apr 2015 11:14:29 -0600 Subject: [PATCH] MVC architecture for client --- TODO.txt | 1 + composer.json | 1 + dbase.sqlite | Bin 19456 -> 19456 bytes htaccess.txt | 10 ++ index.php | 59 ++++--- lib/controllers/admin/home.php | 19 +++ lib/controllers/admin/location-new.php | 41 +++++ lib/controllers/admin/location-update.php | 33 ++++ lib/controllers/conference-control.php | 73 -------- lib/controllers/home.php | 60 ------- lib/controllers/login.php | 53 ------ lib/routes/admin.php | 50 ++++++ lib/routes/client.php | 118 +++++++------ lib/routes/login.php | 17 +- lib/src/Auth.php | 151 +++++++++++++++++ lib/src/CheckinService.php | 120 ++++++++++++++ lib/src/ConfService.php | 52 ++++++ lib/src/ReportService.php | 83 ++++++++++ lib/src/UserService.php | 32 ++++ lib/utilityFunctions.php | 34 ---- migrations/init.php | 39 +++-- templates/_conference-checkin.html | 11 ++ templates/_conference-control.html | 86 ---------- templates/admin/_base.html | 42 +++++ templates/admin/home.html | 18 ++ templates/admin/location.html | 193 ++++++++++++++++++++++ templates/admin/locations.html | 45 +++++ templates/admin/test.html | 9 + templates/checkin-done.html | 23 +++ templates/checkin-in.html | 17 ++ templates/checkin-out.html | 29 ++++ templates/checkin-wrongtl.html | 16 ++ templates/conference.html | 6 +- templates/home.html | 16 +- vendor/composer/autoload_classmap.php | 79 +++++---- vendor/composer/autoload_psr4.php | 1 + 36 files changed, 1194 insertions(+), 443 deletions(-) create mode 100644 TODO.txt create mode 100644 htaccess.txt create mode 100644 lib/controllers/admin/home.php create mode 100644 lib/controllers/admin/location-new.php create mode 100644 lib/controllers/admin/location-update.php delete mode 100644 lib/controllers/conference-control.php delete mode 100644 lib/controllers/home.php delete mode 100644 lib/controllers/login.php create mode 100644 lib/routes/admin.php create mode 100644 lib/src/Auth.php create mode 100644 lib/src/CheckinService.php create mode 100644 lib/src/ConfService.php create mode 100644 lib/src/ReportService.php create mode 100644 lib/src/UserService.php delete mode 100644 lib/utilityFunctions.php create mode 100644 templates/_conference-checkin.html delete mode 100644 templates/_conference-control.html create mode 100644 templates/admin/_base.html create mode 100644 templates/admin/home.html create mode 100644 templates/admin/location.html create mode 100644 templates/admin/locations.html create mode 100644 templates/admin/test.html create mode 100644 templates/checkin-done.html create mode 100644 templates/checkin-in.html create mode 100644 templates/checkin-out.html create mode 100644 templates/checkin-wrongtl.html diff --git a/TODO.txt b/TODO.txt new file mode 100644 index 0000000..233403e --- /dev/null +++ b/TODO.txt @@ -0,0 +1 @@ +[] checkout time check, if checkout-checkin > conf.duration only log conf.duration \ No newline at end of file diff --git a/composer.json b/composer.json index 55325e5..a6244cc 100644 --- a/composer.json +++ b/composer.json @@ -7,6 +7,7 @@ "google/apiclient": "1.0.*@beta" }, "autoload": { + "psr-4": { "JV\\" : "lib/src/" }, "classmap": ["vendor/redbean/"] } } \ No newline at end of file diff --git a/dbase.sqlite b/dbase.sqlite index 2561a62243bc4acc070fb2142153e1fac5f8aa9e..5e50ae55a47841904e23da83dfff58134d9e032d 100644 GIT binary patch delta 1611 zcmb_cU1%It6u#%4*_oZ)G;F(V6C2~sQmSd1pUrN5w`r?gHX(^gYuut$N_MlIBx{o$ z%}x^^6q}eLms8S7TyRek0orr7-hLd}c1&kTlW*`ly~x>V3mTGGV5S zv|22ozhFSl{^0&naS@BUmT-N2xIxT&PZo5ue|epfYo6g1Jjx&Nc~WtiySpTng}M}l zB#Q6$9d~!3%J#o*_xF|^cfZ0_7J1q3$IE)$gP^jOQ;Nc&R@^A-tbh%O4e45cYET<8 zGH<5L#Bhz}m(1H3eul5%9{xT5h_}&dLH=I5>?F;HffbsTr`x#QG)qBaOwX8FbXZTC zsWF{CGWz%s*#J_+VF85-fIyx|suTl{SdumCaFo_XK9Ln$dMgNyH)iGJGdAw796 zmCiR@ly^oe`pnb_{S5NIkgB+G-h47;8gz~Y?)Qu58>X`H1M{3z5UGcxbluiU^j2`0 zR9v&E$vJ8=Q9E2i3P|!j61tcZd^785okx!%kphs_T+69+=~%PJ@=?My+s8HBAjG?v za}Q*(-d^e)VelJH!wB7=J`m#_E#MUk9Vg2Ff1x6NU_eZF1z{&G@T~Z$>*?Gs=h@|y s6Io6~z5+4b6a@aLjzmDvvVE#SsGX<(t;HV*smUlUI5(KvY@UnoH$-ERJOBUy delta 1325 zcmbVMO>7fa5PtJ!*KfT97so_43Bk6gv6GP2e~BTaAz&;>qM|_g6$eNoS4x$}D43rc z5W)!wf#y(-s6`0n5Y#LOR8>_Eh~`KSb$dvwD5~0uN)eGzRh95FZ)1h@z@e-(&-!L| z#@~E1Z!g?{3pe0eeQo`xLWlrc_3_`mzBygb-qzm@lYNK_IugKV_%|-#;`JSYH50*T z6voz-2Rg`vnvHdWP2cVg)ALvz?b{SM z%x=4e53iRG=~>OE326ao7>D2FA{K?8U7FN8RnxENj8$xQ)hzz?v3b}uQy=+_$8Vql zO;grAY{sC88PJrAqs5WI;_>0q;6Qn(G@^V4Mppz(=hs!=C_9ynq}n<%xsD{=3iPjl z&WnI$*bf9w;t}-82aeykC#^k(M`ZexqPz7wtv%=w8;>zqWN<)wME)ZNJq>%Ue(;E` z2N`_P&|B*^FQ){4jb%*ACvu8w-IV7$O(im!I6yj?YHLkqTbWBb<8oGKH=Cb{)~@sn%|g0nts2N zV->;v3eMpSS9pr^9QzKbl4)M2w-Iq(S7yJcWys5W(e%;DLOXrcm!n?_A$u&g#C<&a zf4S34c3ei9D4DC{zk-BT;O(l=nZ8j?hJNTt)9pf>ntSbp764J7Al(ymLvF)vLs}|s zx>9X;+j~nw&Em8xk}FtMsO?*P?%eQ@!0+)Y9&)052~@k@q?Dpj758_``}_X7yx~4V z&Rr=*U#f(^Ti;-QK<3=tQuL!rgu3-NKzm(ARf&0cj(_be(tJK-ofYsBufSQpFhf9} i6}EzpKQo)?W}i(z6~f-P-WS&VFaddGlobal('session', $_SESSION); $twig->addExtension(new \Twig_Extension_Debug()); -// Google PHP Library -$client = new Google_Client(); -$client->setApplicationName('Attend'); -$client->setClientId('459801839286-j103ceme00kacpbrk19nihn7r3l2icme.apps.googleusercontent.com'); -$client->setClientSecret('C95vXMePXkVXKgtuQr8lWCqk'); -$client->setRedirectUri(BASE_URL . '/login'); -$client->setScopes(array( - 'https://www.googleapis.com/auth/userinfo.email', - 'https://www.googleapis.com/auth/userinfo.profile', -)); -$app->client = $client; +// Model services +$app->userService = new \JV\UserService(); +$app->confService = new \JV\ConfService(); +$app->checkinService = new \JV\CheckinService($app->userService, $app->confService); +$app->reportService = new \JV\ReportService($app->userService, $app->confService, $app->checkinService); + +// Auth handling +$googleClientParams = array( + 'client_id' => '459801839286-j103ceme00kacpbrk19nihn7r3l2icme.apps.googleusercontent.com', + 'client_secret' => 'C95vXMePXkVXKgtuQr8lWCqk', + 'redirect_uri' => BASE_URL . '/login', +); -/********************************* - UTILITY FUNCTIONS -*********************************/ +$app->auth = new \JV\Auth($app, $app->userService, $googleClientParams); -require 'lib/utilityFunctions.php'; /********************************* ROUTES *********************************/ +// Routes act as views, managing data from Model services +// include all route files $routeFiles = (array) glob('lib/routes/*.php'); foreach($routeFiles as $routeFile) { require_once $routeFile; @@ -74,11 +74,30 @@ // TEST ROUTES -$app->get('/getsession', function() use ($app) { - header("Content-Type: application/json"); - echo json_encode($_SESSION); +$app->get('/test', function() use($app) { + $confs = $app->confService->getConferencesByDateRange('2015-04-01', '2016-01-01'); + foreach ($confs as $conf) { + $start = date('Y-m-d', strtotime($conf->start)); + echo "

". $conf->name ." on ". $start ."

"; + } + + $checkins = $app->checkinService->getCheckinsForUserByDateRange(1, '2015-01-01', '2016-01-01'); + echo "Checkins"; + foreach($checkins as $checkin) { + $in = date('Y-m-d', strtotime($checkin->in_time)); + echo "

User_id: ". $checkin->user_id .". In: ". $in ."

"; + } + + $report = $app->reportService->userAttendanceByDate(1, '2015-01-01', '2016-01-01'); + echo "Report
"; + echo "Required hours: ".$report['required_hours']."
"; + echo "Checkins:
";
+  echo print_r($report['checkins'], true);
+  echo "
"; + echo "Electives:
";
+  echo print_r($report['user_electives'], true);
+  echo "
"; }); - /********************************* RUN *********************************/ diff --git a/lib/controllers/admin/home.php b/lib/controllers/admin/home.php new file mode 100644 index 0000000..f50e7c0 --- /dev/null +++ b/lib/controllers/admin/home.php @@ -0,0 +1,19 @@ +finish) - time() > -3600) { $conferences[] = getConferenceAsArray($bean, $checkins); } + } + + return $conferences; +} + +// UPCOMING CONFERENCES X3 +$future_conferences = R::find('conference', ' start >= ? ORDER BY start ASC ', array(date("Y-m-d"))); + +$data = array( + 'future_conferences' => $future_conferences, + 'upcoming_conferences' => array_slice($future_conferences, 0, 3), +); \ No newline at end of file diff --git a/lib/controllers/admin/location-new.php b/lib/controllers/admin/location-new.php new file mode 100644 index 0000000..dd7c47c --- /dev/null +++ b/lib/controllers/admin/location-new.php @@ -0,0 +1,41 @@ +request->post(); +$formErrors = array(); + +if ($postVars["name"] == '') { + $formErrors[] = "Name is required."; +} + +if ($postVars['address'] == '') { + $formErrors[] = "Address is required."; +} + +$nameTest = R::findOne('location', ' name = ? ', array($postVars["name"])); +if (isset($nameTest)) { + $formErrors[] = "That name is already in use."; +} + +$addressTest = R::findOne('location', ' address = ? ', array($postVars["address"])); +if (isset($addressTest)){ + $formErrors[] = "That address is already in use."; +} + +if (empty($formErrors)) { + $location = R::dispense('location'); + $location->name = $postVars["name"]; + $location->address = $postVars["address"]; + $location->lat = $postVars["lat"]; + $location->lng = $postVars["lng"]; + $location->radius = $postVars["radius"]; + $location->favorite = $postVars["favorite"]; + $loc_id = R::store($location); +} + +if (isset($loc_id)) { + $app->flash('message', 'Added location: '.$location->name); + $app->redirect(BASE_URL . '/admin/locations'); +} else { + $app->flash('formErrors', $formErrors); + $app->render('admin/location.html'); +} + diff --git a/lib/controllers/admin/location-update.php b/lib/controllers/admin/location-update.php new file mode 100644 index 0000000..8e70b8c --- /dev/null +++ b/lib/controllers/admin/location-update.php @@ -0,0 +1,33 @@ +request->post(); + +$location = R::load('location', $id); +if ($location->id == 0) { + echo ajaxRespond('error', 'Location not found'); + $app->stop(); +} + +if ($postVars["name"] == '') { + echo ajaxRespond('error', 'Location name is required'); + $app->stop(); +} + +if ($postVars['address'] == '') { + echo ajaxRespond('error', 'Location address is required'); + $app->stop(); +} + +$location->name = $postVars["name"]; +$location->address = $postVars["address"]; +$location->lat = $postVars["lat"]; +$location->lng = $postVars["lng"]; +$location->radius = $postVars["radius"]; +$location->favorite = $postVars["favorite"]; +$loc_id = R::store($location); + +if (isset($loc_id)) { + echo ajaxRespond('success', 'Location updated'); +} else { + echo ajaxRespond('error', 'Unable to save updates'); +} + diff --git a/lib/controllers/conference-control.php b/lib/controllers/conference-control.php deleted file mode 100644 index 3731c67..0000000 --- a/lib/controllers/conference-control.php +++ /dev/null @@ -1,73 +0,0 @@ -withCondition(' conference_id = ? ', [$conf_id])->ownCheckinList); // gets first and only item out of ownList array - -// Confirm time and location -if((strtotime($conf->start) - time()) < 3600 && (time() - strtotime($conf->finish)) < 3600) { - $timeframe = 'true'; -} else { - $timeframe = 'false'; -} - -// Handle check-in -if($_POST['checkin'] == 'in') { - if (isset($checkin->in)) { - $error = "You have already checked in for this conference."; - } elseif ($timeframe == 'false') { - $error = "You must be within one hour of the start or finish time for checkins"; - } elseif ($_SESSION['user']['at_loc'] == 'false') { - $error = "You must be at the conference location or a remote location for checkins"; - } else { - $new_check = R::dispense('checkin'); - $new_check->user = $user; - $new_check->conference = $conf; - $new_check->in = date('Y-m-d H:i:s'); - $ncid = R::store($new_check); - $checkin = $new_check; - } -} - -// Handle cancel check-in -if($_POST['cancel'] == 'in') { - if(!isset($checkin->in)) { - $error = "You have not checked in yet."; - } else { - R::trash($checkin); - $checkin = ''; - } -} - -// Handle check-out -if($_POST['checkin'] == 'out') { - if (isset($checkin->out)) { - $error = "You have already checked out for this conference."; - } elseif (!isset($checkin->in)) { - $error = "You must check in before you check out."; - } elseif ($timeframe == 'false') { - $error = "You must be within one hour of the start or finish time for checkins"; - } elseif ($_SESSION['user']['at_loc'] == 'false') { - $error = "You must be at the conference location or a remote location for checkins"; - } else { - $checkin->out = date('Y-m-d H:i:s'); - $checkin_id = R::store($checkin); - } -} - -// Handle cancel check-out -if($_POST['cancel'] == 'out') { - if(!isset($checkin->out)) { - $error = "You have not checked out yet."; - } else { - $checkin->out = ''; - $cancel_out_id = R::store($checkin); - } -} - -$data = array( - 'conf' => $conf_array, - 'checkin' => $checkin, - 'timeframe' => $timeframe, - 'at_loc' => $_POST['at_location'] -); \ No newline at end of file diff --git a/lib/controllers/home.php b/lib/controllers/home.php deleted file mode 100644 index e254aa7..0000000 --- a/lib/controllers/home.php +++ /dev/null @@ -1,60 +0,0 @@ -finish) - time() > -3600) { $conferences[] = getConferenceAsArray($bean, $checkins); } - } - - return $conferences; -} - -// CONTROLLER LOGIC -// Get user and then index checkins by conference for easy querying -$user = R::load('user', $_SESSION['user']['id']); -$checkins_by_conf = getCheckinsByConf($user->ownCheckinList); - -// initialize variables -$all_conferences = []; -$attended_electives = []; -$total_conference_hours = ''; -$logged_user_hours = ''; - -// Get a list of all conferences with corresponding user's attendance at each -// Total conference hours and user's attendance hours -// Don't include today's conferences -$conf_beans = R::findAll('conference', ' elective = 0 ORDER BY start DESC ' ); -foreach ($conf_beans as $bean) { - if(date('Ymd', strtotime($bean->start)) < date('Ymd')) { - $conf = getConferenceAsArray($bean, $checkins_by_conf); - $all_conferences[] = $conf; - $total_conference_hours += $conf['duration']; - if (isset($conf['checkin'])) { - $logged_user_hours += $conf['checkin']['total']; - } - } -} - -// Get a list of all electives user has attended and add those hours to user's total hours -$elective_beans = R::findAll('conference', ' elective = 1 ORDER BY start DESC ' ); -foreach ($elective_beans as $bean) { - $conf = getConferenceAsArray($bean, $checkins_by_conf); - if (isset($conf['checkin'])) { - $attended_electives[] = $conf; - $logged_user_hours += $conf['checkin']['total']; - } -} - -// Return the data to the template -$data = array( - 'all_conferences' => $all_conferences, - 'attended_electives' => $attended_electives, - 'total_conference_hours' => $total_conference_hours, - 'logged_user_hours' => $logged_user_hours, - 'percent_attended' => round($logged_user_hours/$total_conference_hours, 2) * 100, - 'todays_conferences' => getTodaysConferences($checkins_by_conf), -); diff --git a/lib/controllers/login.php b/lib/controllers/login.php deleted file mode 100644 index 07f5916..0000000 --- a/lib/controllers/login.php +++ /dev/null @@ -1,53 +0,0 @@ -flash('error', 'You must use your @denverem.org email address.'); - $app->redirect(BASE_URL . '/logout'); - exit; - } - - // query database - $user = R::findOne('user', ' email = ? ', [$guser['email']]); - - // check if new user or new name - if (is_null($user)) { - $user->fname = $guser['givenName']; - $user->lname = $guser['familyName']; - $user->email = $guser['email']; - $user->role = '2'; - } elseif ($user->fname != $guser['givenName'] || $user->lname != $guser['familyName']) { - $user->fname = $guser['givenName']; - $user->lname = $guser['familyName']; - } - - // update last visit, store - $user->last = date("Y-m-d H:i:s"); - $user_id = R::store($user); - - // save in user in session - $_SESSION['user'] = $user->export(); - $_SESSION['user']['role_name'] = $user->role->name; - -} - -// handle redirect from Google with code as URL parameter -if (isset($_GET['code'])) { - $app->client->authenticate($_GET['code']); - $app->client->getAccessToken(); - $service = new Google_Service_Oauth2($app->client); - $user = $service->userinfo->get(); - processGoogleUser($user); - $app->redirect(BASE_URL . '/user'); -} else { - $authUrl = $app->client->createAuthUrl(); - $app->redirect($authUrl); -} \ No newline at end of file diff --git a/lib/routes/admin.php b/lib/routes/admin.php new file mode 100644 index 0000000..d56de95 --- /dev/null +++ b/lib/routes/admin.php @@ -0,0 +1,50 @@ +group('/admin', $app->auth->authorizedRole('admin'), function() use($app) { + + $app->get('/', function() use($app) { + require_once 'lib/controllers/admin/home.php'; + $app->render('admin/home.html', array('data' => $data)); + }); + + $app->get('/locations', function() use($app) { + $locations = R::findAll('location', ' ORDER BY name ASC '); + $faves = array(); + foreach($locations as $loc) { + if ($loc->favorite == "on") { $faves[] = $loc; } + } + $app->render('admin/locations.html', array('locations' => $locations, 'faves' => $faves)); + }); + + $app->get('/location', function() use($app) { + $app->render('admin/location.html'); + }); + + $app->post('/location', function() use($app) { + require 'lib/controllers/admin/location-new.php'; + }); + + $app->get('/location/:id', function($id) use($app) { + $location = R::load('location', $id); + if($location->id == 0) { + $app->flash('error', 'No location found.'); + } + $app->render('admin/location.html', array('location' => $location)); + }); + + $app->put('/location/:id', function($id) use($app) { + require 'lib/controllers/admin/location-update.php'; + }); + + $app->get('/location/delete/:id', function($id) use($app) { + $location = R::load('location', $id); + if($location->id == 0) { + $app->flash('error', 'No location found.'); + } else { + R::trash($location); + $app->flash('message', 'Deleted location: '.$location->name); + } + $app->redirect(BASE_URL . '/admin/locations'); + }); + +}); \ No newline at end of file diff --git a/lib/routes/client.php b/lib/routes/client.php index 665ef5a..739ae7d 100644 --- a/lib/routes/client.php +++ b/lib/routes/client.php @@ -1,54 +1,78 @@ redirect(BASE_URL . '/loginform'); - } - }; -}; - -// verify user logged in for ajax request -$verifyAjax = function() { - return function() { - if (!isset($_SESSION['user'])) { - echo "Please log in for access"; - $app = \Slim\Slim::getInstance(); - $app->stop(403, 'You shall not pass!'); - } - }; -}; - // USER CLIENT ROUTES -$app->group('/user', $verifyLogin(), function() use($app) { - - // HOME - $app->get('/', function() use($app) { - require_once 'lib/controllers/home.php'; - $app->render('home.html', array('data' => $data)); - }); - - // CONFERENCE - $app->get('/conference/:id', function($id) use($app) { - $conf = R::load('conference', $id); - $conf = getConferenceAsArray($conf); - $data = array( - 'conf' => $conf - ); - $app->render('conference.html', array('data' => $data)); - }); +// HOME +$app->get('/', $app->auth->verifyLogin(), function() use($app) { + $todaysConferences = $app->confService->getConferencesByDate(date("Y-m-d")); + $report = $app->reportService->userAttendanceByDate($app->auth->getUserID(), '2015-01-01', '2016-01-01'); + $data = array( + 'todays_conferences' => $todaysConferences, + 'report' => $report + ); + $app->render('home.html', array('data' => $data)); +}); + + +// CONFERENCE +$app->get('/conference/:id', $app->auth->verifyLogin(), function($id) use($app) { + $conf = $app->confService->getConferenceByID($id); + $data = array( + 'conf' => $conf + ); + $app->render('conference.html', array('data' => $data)); }); -// AJAX CLIENT ROUTES -$app->group('/ajax', $verifyAjax(), function() use($app) { - // CONFERENCE DETAILS - $app->post('/conference/:conf_id', function($conf_id) use($app) { - require 'lib/controllers/conference-control.php'; - $app->render('_conference-control.html', array('data' => $data)); - }); - +// CHECKIN +$app->post('/checkin/:conf_id/:user_id', $app->auth->verifyAjax(), function($conf_id, $user_id) use($app) { + + $checkin = $app->checkinService->getCheckinForConfUser($conf_id, $user_id); + + $data = array('checkin' => $checkin); + + // Confirm time and location + $tandl = $app->checkinService->checkTimeAndLocation($conf_id, $app->request->params('at_location')); + if ($tandl == 'false') { + $app->render('checkin-wrongtl.html', array('data' => $data)); + $app->stop(); + } + + // Handle client-side controller reqeuests + switch($app->request->params('action')) { + case "checkin": + $response = $app->checkinService->processCheckin($conf_id, $user_id); + break; + case "checkout": + $response = $app->checkinService->processCheckout($conf_id, $user_id); + break; + case "cancel-checkin": + $response = $app->checkinService->cancelCheckin($conf_id, $user_id); + break; + case "cancel-checkout": + $response = $app->checkinService->cancelCheckout($conf_id, $user_id); + break; + } + + // Update template variables + $error = $response['error']; + $checkin = $app->checkinService->getCheckinForConfUser($conf_id, $user_id); + $data['checkin'] = $checkin; + $data['error'] = $error; + + // Decide which template to render + if (!isset($checkin->in_time)) { + $app->render('checkin-in.html', array('data' => $data)); + $app->stop(); + } + + if(!isset($checkin->out_time)) { + $app->render('checkin-out.html', array('data' => $data)); + $app->stop(); + } + + if(isset($checkin->out_time)) { + $app->render('checkin-done.html', array('data' => $data)); + $app->stop(); + } + }); diff --git a/lib/routes/login.php b/lib/routes/login.php index ea35244..110e168 100644 --- a/lib/routes/login.php +++ b/lib/routes/login.php @@ -1,7 +1,15 @@ get('/login', function() use($app){ - require_once 'lib/controllers/login.php'; +// LOGIN HANDLING ROUTES + +$app->get('/login', function() use($app){ + // handle redirect from Google with code as URL parameter + if (isset($_GET['code'])) { + $app->auth->loginUserByCode($_GET['code']); + $app->redirect(BASE_URL); + } else { + $authUrl = $app->auth->createAuthUrl(); + $app->redirect($authUrl); + } }); $app->get('/loginform', function() use ($app) { @@ -10,7 +18,6 @@ $app->get('/logout', function () use ($app) { $app->flashKeep(); - session_unset(); - $app->client->revokeToken(); + $app->auth->logout(); $app->redirect(BASE_URL . '/loginform'); }); \ No newline at end of file diff --git a/lib/src/Auth.php b/lib/src/Auth.php new file mode 100644 index 0000000..aae205b --- /dev/null +++ b/lib/src/Auth.php @@ -0,0 +1,151 @@ +app = $app; + $this->userService = $userService; + $this->client = $this->initGoogleClient($params); + } + + // Route Middleware + public function verifyLogin() + { + return function() { + if (!isset($_SESSION['user'])) { + $this->app->redirect(BASE_URL . '/loginform'); + } + }; + } + + public function verifyAjax() + { + return function() { + if (!isset($_SESSION['user'])) { + echo "Please log in for access"; + $this->app->stop(403, 'You shall not pass!'); + } + }; + } + + public function authorizedRole($role_name = 'user') + { + return function() use ($role_name) { + if ($_SESSION['user']['role_name'] != $role_name) { + $this->app->flash('error', 'You do not have access to that resource.'); + $this->app->redirect(BASE_URL); + } + }; + } + + // Google PHP Library + // http://www.ibm.com/developerworks/library/mo-php-todolist-app/ + // https://developers.google.com/api-client-library/php/guide/aaa_oauth2_web + // http://phppot.com/php/php-google-oauth-login/ + + private function initGoogleClient($params) + { + $client = new Google_Client(); + $client->setApplicationName('Attend'); + $client->setClientId($params['client_id']); + $client->setClientSecret($params['client_secret']); + $client->setRedirectUri($params['redirect_uri']); + $client->setScopes(array( + 'https://www.googleapis.com/auth/userinfo.email', + 'https://www.googleapis.com/auth/userinfo.profile', + )); + return $client; + } + + public function createAuthUrl() + { + return $this->client->createAuthUrl(); + } + + private function authenticateCodeReturnGoogleUser($code) + { + $this->client->authenticate($code); + $this->client->getAccessToken(); + $service = new Google_Service_Oauth2($this->client); + $user = $service->userinfo->get(); + return $user; + } + + // Handle login + private function processGoogleUser($guser) + { + + // check domain + if ($guser['hd'] != 'denverem.org') { + $this->app->flash('error', 'You must use your @denverem.org email address.'); + $this->app->redirect(BASE_URL . '/logout'); + $this->app->stop(); + } + + // query database + $user = $this->userService->findUserByEmail($guser['email']); + + // check if new user or new name + if (is_null($user)) { + $user->fname = $guser['givenName']; + $user->lname = $guser['familyName']; + $user->email = $guser['email']; + $user->role_id = '2'; + } elseif ($user->fname != $guser['givenName'] || $user->lname != $guser['familyName']) { + $user->fname = $guser['givenName']; + $user->lname = $guser['familyName']; + } + + // update last visit, store + $user->last = date("Y-m-d H:i:s"); + $user_id = $this->userService->saveUser($user); + + // save in user in Service and session + $this->user = $user; + $_SESSION['user'] = $user->export(); + $_SESSION['user']['role_name'] = $user->role->name; + + } + + public function loginUserByCode($code) + { + $user = $this->authenticateCodeReturnGoogleUser($code); + $this->processGoogleUser($user); + } + + // User Functions + public function getUser() + { + $user = R::load('user', $_SESSION['user']['id']); + return $user; + } + + public function getUserID() + { + $user_id = $_SESSION['user']['id']; + return $user_id; + } + + // Logout + public function logout() + { + session_unset(); + $this->client->revokeToken(); + } + +} diff --git a/lib/src/CheckinService.php b/lib/src/CheckinService.php new file mode 100644 index 0000000..8a46316 --- /dev/null +++ b/lib/src/CheckinService.php @@ -0,0 +1,120 @@ +userService = $userService; + $this->confService = $confService; + } + + public function checkTimeAndLocation($conf_id, $loc) + { + $conf = $this->confService->getConferenceByID($conf_id); + if ((strtotime($conf->start) - time()) > 3600 || (time() - strtotime($conf->finish)) > 3600) { + $response = 'false'; + }; + if ($loc == 'false') { + $response = 'false'; + }; + return $response; + } + + public function getCheckinForConfUser($conf_id, $user_id) + { + $checkin = R::findOne('checkin', ' conference_id = :conf_id AND user_id = :user_id ', array(':conf_id' => $conf_id, ':user_id' => $user_id)); + return $checkin; + } + + public function getCheckinsForUserByDateRange($user_id, $start, $end) { + $checkins = R::find('checkin', ' user_id = :user_id AND in_time >= :start AND in_time <= :end ', + array(':user_id' => $user_id, ':start' => $start, ':end' => $end)); + return $checkins; + } + + public function indexCheckinsByConf($checkins) + { + foreach($checkins as $checkin) { + $checkins_by_conf[$checkin->conference_id] = $checkin; + } + return $checkins_by_conf; + } + + // Checkin processing + public function processCheckin($conf_id, $user_id) + { + $response = array(); + $checkin = $this->getCheckinForConfUser($conf_id, $user_id); + if(isset($checkin->in_time)) { + $response['error'] = "You have already checked in for this conference."; + } else { + $checkin = R::dispense('checkin'); + $checkin->conference_id = $conf_id; + $checkin->user_id = $user_id; + $checkin->in_time = date('Y-m-d H:i:s'); + $checkin_id = R::store($checkin); + if($checkin_id) { + $response['checkin'] = $checkin; + } else { + $response['error'] = "Unable to save checkin."; + } + } + return $response; + } + + public function processCheckout($conf_id, $user_id) + { + $response = array(); + $checkin = $this->getCheckinForConfUser($conf_id, $user_id); + if(isset($checkin->out_time)) { + $response['error'] = "You have already checked out for this conference."; + } elseif (!isset($checkin->in_time)) { + $resopnse['error'] = "You must check in before you check out."; + } else { + $checkin->out_time = date('Y-m-d H:i:s'); + $checkin_id = R::store($checkin); + if($checkin_id) { + $response['checkin'] = $checkin; + } else { + $response['error'] = "Unable to save checkin."; + } + } + return $response; + } + + public function cancelCheckin($conf_id, $user_id) { + $response = array(); + $checkin = $this->getCheckinForConfUser($conf_id, $user_id); + R::trash($checkin); + $response['checkin'] = null; + return $response; + } + + public function cancelCheckout($conf_id, $user_id) { + $response = array(); + $checkin = $this->getCheckinForConfUser($conf_id, $user_id); + $checkin->out_time = null; + $checkin_id = R::store($checkin); + if($checkin_id) { + $response['checkin'] = $checkin; + } else { + $response['error'] = "Unable to save checkin."; + } + return $response; + } + +} + + diff --git a/lib/src/ConfService.php b/lib/src/ConfService.php new file mode 100644 index 0000000..dc77c0e --- /dev/null +++ b/lib/src/ConfService.php @@ -0,0 +1,52 @@ +duration = (strtotime($conf->finish) - strtotime($conf->start)) / 3600; + $conf->location = $conf->fetchAs('location')->primary_loc; + $conf->remotes = $conf->sharedLocationList; + return $conf; + } + + public function getConferenceByID($id) + { + $conf = R::load('conference', $id); + $conf = $this->getAdditionalConferenceDetails($conf); + return $conf; + } + + // Y-m-d format + public function getConferencesByDate($date) + { + $conferences = R::find('conference', ' start LIKE ? ORDER BY start ASC ', array($date.'%')); + foreach ($conferences as $conf) { + $conf = $this->getAdditionalConferenceDetails($conf); + } + return $conferences; + } + + // Y-m-d format + public function getConferencesByDateRange($start, $end) + { + $conferences = R::find('conference', ' start >= :start AND start <= :end ORDER BY start ASC ', array(':start' => $start, ':end' => $end)); + foreach ($conferences as $conf) { + $conf = $this->getAdditionalConferenceDetails($conf); + $conf = $conf->export(); + } + return $conferences; + } + +} + diff --git a/lib/src/ReportService.php b/lib/src/ReportService.php new file mode 100644 index 0000000..c4a4eb4 --- /dev/null +++ b/lib/src/ReportService.php @@ -0,0 +1,83 @@ +userService = $userService; + $this->confService = $confService; + $this->checkinService = $checkinService; + } + + public function userAttendanceByDate($user_id, $start, $end) + { + // variables to be returned + $required_conferences = array(); + $required_hours = 0; + $required_attended = 0; + $user_electives = array(); + $user_electives_attended = 0; + $total_attended = 0; + $percent_attended = 0; + + $checkins = $this->checkinService->getCheckinsForUserByDateRange($user_id, $start, $end); + $checkins = $this->checkinService->indexCheckinsByConf($checkins); + $conferences = $this->confService->getConferencesByDateRange($start, $end); + + foreach($conferences as $conf) { + // all required conferences and total time + if ($conf->elective == false) { + $required_conferences[] = $conf; + $required_hours += round((strtotime($conf->finish) - strtotime($conf->start))/3600, 2); + } + // all user-attended elective conferences and user's attended time + if ($conf->elective == true && !empty($checkins[$conf->id])) { + $time_logged = round((strtotime($checkins[$conf->id]['out_time']) - strtotime($checkins[$conf->id]['in_time']))/3600, 2); + $time_logged = ($time_logged > $conf->duration ? $conf->duration : $time_logged); + $checkins[$conf->id]['total'] = $time_logged; + $user_electives_attended += $time_logged; + $conf->checkin = $checkins[$conf->id]; + $user_electives[] = $conf; + } + } + + // match checkins to required conferences and tally user's attended time + foreach($required_conferences as $conf) { + $conf->checkin = $checkins[$conf->id]; + $time_logged = round((strtotime($checkins[$conf->id]['out_time']) - strtotime($checkins[$conf->id]['in_time']))/3600, 2); + $time_logged = ($time_logged > $conf->duration ? $conf->duration : $time_logged); + if (isset($checkins[$conf->id])) { $checkins[$conf->id]['total'] = $time_logged; } + $required_attended += $time_logged; + } + + $total_attended = $required_attended + $user_electives_attended; + $percent_attended = round($total_attended/$required_hours, 2) * 100; + + $report = array( + 'required_conferences' => $required_conferences, + 'required_hours' => $required_hours, + 'required_attended' => $required_attended, + 'user_electives' => $user_electives, + 'user_electives_attended' => $user_electives_attended, + 'percent_attended' => $percent_attended, + 'total_attended' => $total_attended + ); + + return $report; + } + +} + diff --git a/lib/src/UserService.php b/lib/src/UserService.php new file mode 100644 index 0000000..6e5ef6b --- /dev/null +++ b/lib/src/UserService.php @@ -0,0 +1,32 @@ +flash('error', 'You do not have access to that resource.'); - $app->redirect(BASE_URL); - } - }; -}; - -// USERS -function getCheckinsByConf($checkins) { - foreach ($checkins as $checkin) { - $checkins_by_conf[$checkin->conference_id] = $checkin; - } - return $checkins_by_conf; -} - -// CONFERENCES -// get necessary conference data as array -// if $checkins (indexed by conference) are provided, also get user's checkin for that conference -function getConferenceAsArray($conf, $checkins = NULL) { - $conf->duration = (strtotime($conf->finish) - strtotime($conf->start)) / 3600; - $conf->location = $conf->fetchAs('location')->primary_loc; - $conf->remotes = $conf->sharedLocationList; - if(isset($checkins[$conf->id])) { - $conf->checkin = $checkins[$conf->id]; - } - $conference = $conf->export(); - return $conference; -} diff --git a/migrations/init.php b/migrations/init.php index 5c272d4..3e1006f 100644 --- a/migrations/init.php +++ b/migrations/init.php @@ -47,6 +47,9 @@ $loc->address = "12605 East 16th Ave, Aurora CO 80045"; $loc->lat = "39.7423359"; $loc->lng = "-104.8415582"; +$loc->radius = 500; +$loc->favorite = 1; +$loc->comments = "The big hospital."; $loc_id = R::store($loc); $loc2 = R::dispense('location'); @@ -75,16 +78,17 @@ $conf->start = "2015-01-04 07:30:00"; $conf->finish = "2015-01-04 12:30:00"; $conf->primary_loc = $loc; -$conf->sharedLocationList = [$loc3]; +$conf->sharedLocationList = array($loc3); $conf->name = "Morbity and Mortality"; $conf->elective = FALSE; +$conf->comments = "This will be a good one."; $conf_id = R::store($conf); $conf2 = R::dispense('conference'); $conf2->start = "2015-02-04 07:30:00"; $conf2->finish = "2015-02-04 12:30:00"; $conf2->primary_loc = $loc2; -$conf2->sharedLocationList = [$loc3]; +$conf2->sharedLocationList = array($loc3); $conf2->name = "Morbity and Mortality"; $conf2->elective = FALSE; $conf2_id = R::store($conf2); @@ -93,7 +97,7 @@ $conf3->start = "2015-03-06 18:30:00"; $conf3->finish = "2015-03-06 20:30:00"; $conf3->primary_loc = $loc; -$conf3->sharedLocationList = [$loc3]; +$conf3->sharedLocationList = array($loc3); $conf3->name = "Asynchronous Module"; $conf3->elective = TRUE; $conf3_id = R::store($conf3); @@ -102,7 +106,7 @@ $conf4->start = date("Y-m-d H:i:s"); $conf4->finish = date("Y-m-d H:i:s", time()+7200); $conf4->primary_loc = $loc3; -$conf4->sharedLocationList = [$loc, $loc2]; +$conf4->sharedLocationList = array($loc, $loc2); $conf4->name = "Test Conference"; $conf4->elective = FALSE; $conf4_id = R::store($conf4); @@ -110,36 +114,45 @@ $conf5 = R::dispense('conference'); $conf5->start = date("Y-m-d H:i:s", time()+28800); $conf5->finish = date("Y-m-d H:i:s", time()+36000); -$conf5->primary_loc = $loc5; -$conf5->sharedLocationList = [$loc2, $loc]; -$conf5->name = "Test Elective"; +$conf5->primary_loc = $loc3; +$conf5->sharedLocationList = array($loc2, $loc); +$conf5->name = "Test Elective Later"; $conf5->elective = TRUE; $conf5_id = R::store($conf5); +$conf6 = R::dispense('conference'); +$conf6->start = date("Y-m-d H:i:s"); +$conf6->finish = date("Y-m-d H:i:s", time()+7200); +$conf6->primary_loc = $loc5; +$conf6->sharedLocationList = array($loc2, $loc); +$conf6->name = "Test Elective Elsewhere"; +$conf6->elective = TRUE; +$conf6_id = R::store($conf6); + echo "Created conferences
"; // CHECK INS $check = R::dispense('checkin'); $check->conference = $conf; $check->user = $user; -$check->in = '2015-01-04 07:32:12'; -$check->out = '2015-01-04 11:47:12'; +$check->in_time = '2015-01-04 07:32:12'; +$check->out_time = '2015-01-04 11:47:12'; $check->total = round((strtotime($check->out) - strtotime($check->in)) / 3600, 2); $check_id = R::store($check); $check2 = R::dispense('checkin'); $check2->conference = $conf; $check2->user = $admin; -$check2->in = '2015-01-04 07:30:12'; -$check2->out = '2015-01-04 10:15:12'; +$check2->in_time = '2015-01-04 07:30:12'; +$check2->out_time = '2015-01-04 10:15:12'; $check2->total = round((strtotime($check2->out) - strtotime($check2->in)) / 3600, 2); $check2_id = R::store($check2); $check3 = R::dispense('checkin'); $check3->conference = $conf3; $check3->user = $user; -$check3->in = '2015-03-06 07:32:12'; -$check3->out = '2015-03-06 9:52:47'; +$check3->in_time = '2015-03-06 07:32:12'; +$check3->out_time = '2015-03-06 9:52:47'; $check3->total = round((strtotime($check3->out) - strtotime($check3->in)) / 3600, 2); $check3_id = R::store($check3); diff --git a/templates/_conference-checkin.html b/templates/_conference-checkin.html new file mode 100644 index 0000000..2617d7d --- /dev/null +++ b/templates/_conference-checkin.html @@ -0,0 +1,11 @@ +{% if data.checkin %} +
+

Check In: {{ data.checkin.in_time | date('h:i a') }}

+ {% if data.checkin.out_time %} +

Check Out: {{ data.checkin.out_time | date('h:i a') }}

+ {% endif %} +
+{% endif %} + +{% block checkin_controls %} +{% endblock %} \ No newline at end of file diff --git a/templates/_conference-control.html b/templates/_conference-control.html deleted file mode 100644 index c5e3874..0000000 --- a/templates/_conference-control.html +++ /dev/null @@ -1,86 +0,0 @@ - -{% if data.error %} -
{{ data.error }}
-{% endif %} - - -{% if session.user.at_loc == 'false' or data.timeframe == 'false' %} -
-

Checkin not available at this time or location.

- -
-{% endif %} - - -{% if data.checkin.in is not empty %} -
-

Your Attendance

-
-

- Check In: {{ data.checkin.in | date('h:i a') }} - {% if data.checkin.out is empty %} - Cancel Check In - {% endif %} -

- -

- Check Out: - {% if data.checkin.out is not empty %} - {{ data.checkin.out | date('h:i a') }} - Cancel Check Out - {% endif %} -

-
-
-{% endif %} - - -{% if data.at_loc == 'true' and data.timeframe == 'true' %} - -{% if data.checkin.in is empty %} - -{% endif %} - -{% if data.checkin.in is not empty and data.checkin.out is empty %} - -{% endif %} - -{% endif %} - - - - \ No newline at end of file diff --git a/templates/admin/_base.html b/templates/admin/_base.html new file mode 100644 index 0000000..29a67b8 --- /dev/null +++ b/templates/admin/_base.html @@ -0,0 +1,42 @@ + + + + + Attend::Admin + + + + + + + + + + + + + + + + + +
+ + {% if flash.error %}
{{ flash.error }}
{% endif %} + {% if flash.message %}
{{ flash.message }}
{% endif %} + + {% block main %}{% endblock %} + +
+ + + \ No newline at end of file diff --git a/templates/admin/home.html b/templates/admin/home.html new file mode 100644 index 0000000..86c7c03 --- /dev/null +++ b/templates/admin/home.html @@ -0,0 +1,18 @@ +{% extends 'admin/_base.html' %} + +{% block main %} + +

Home block, baby.

+ +

Future

+{% for conf in data.future_conferences %} +

{{ conf.name }}

+{% endfor %} + +

Upcoming

+{% for conf in data.upcoming_conferences %} +

{{ conf.name }}

+{% endfor %} + + +{% endblock %} \ No newline at end of file diff --git a/templates/admin/location.html b/templates/admin/location.html new file mode 100644 index 0000000..63fdd67 --- /dev/null +++ b/templates/admin/location.html @@ -0,0 +1,193 @@ +{% extends 'admin/_base.html' %} + +{% block main %} + + + + +
+ +
+

+ +
Error
+ + +
+ +
+ +
Message
+ {% for error in flash.formErrors %} +
{{ error }}
+ {% endfor %} + +
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+ +
+ +
+ +
+ +
+ + +
+ + + + + + + +{% endblock %} \ No newline at end of file diff --git a/templates/admin/locations.html b/templates/admin/locations.html new file mode 100644 index 0000000..cea6d39 --- /dev/null +++ b/templates/admin/locations.html @@ -0,0 +1,45 @@ +{% extends 'admin/_base.html' %} + +{% block main %} + +

LOCATIONS

+ ++ Add New + +

Favorites

+
    +{% for loc in faves %} +
  • +

    {{ loc.name }}
    {{ loc.address }}

    +
    + Edit + Delete +
    +
  • +{% endfor %} +
+ +

Locations

+
    +{% for loc in locations %} +
  • +

    {{ loc.name }}
    {{ loc.address }}

    +
    + Edit + Delete +
    +
  • +{% endfor %} +
+ + + +{% endblock %} \ No newline at end of file diff --git a/templates/admin/test.html b/templates/admin/test.html new file mode 100644 index 0000000..e377fde --- /dev/null +++ b/templates/admin/test.html @@ -0,0 +1,9 @@ +{% extends 'admin/_base.html' %} + +{% block main %} + +

Testing

+ +Test equals: {{ test }} + +{% endblock %} \ No newline at end of file diff --git a/templates/checkin-done.html b/templates/checkin-done.html new file mode 100644 index 0000000..f0b1935 --- /dev/null +++ b/templates/checkin-done.html @@ -0,0 +1,23 @@ +{% extends '_conference-checkin.html' %} + +{% block checkin_controls %} + +{% if data.error %} +
{{ data.error }}
+{% endif %} + +

Cancel Check Out

+ + + +{% endblock %} \ No newline at end of file diff --git a/templates/checkin-in.html b/templates/checkin-in.html new file mode 100644 index 0000000..d45ddfa --- /dev/null +++ b/templates/checkin-in.html @@ -0,0 +1,17 @@ +{% extends '_conference-checkin.html' %} + +{% block checkin_controls %} + +{% if data.error %} +
{{ data.error }}
+{% endif %} + + + + + +{% endblock %} \ No newline at end of file diff --git a/templates/checkin-out.html b/templates/checkin-out.html new file mode 100644 index 0000000..352c710 --- /dev/null +++ b/templates/checkin-out.html @@ -0,0 +1,29 @@ +{% extends '_conference-checkin.html' %} + +{% block checkin_controls %} + +{% if data.error %} +
{{ data.error }}
+{% endif %} + +

Cancel Check In

+ + + + + +{% endblock %} \ No newline at end of file diff --git a/templates/checkin-wrongtl.html b/templates/checkin-wrongtl.html new file mode 100644 index 0000000..ac058a6 --- /dev/null +++ b/templates/checkin-wrongtl.html @@ -0,0 +1,16 @@ +{% extends '_conference-checkin.html' %} + +{% block checkin_controls %} + +
+

Checkin not available at this time or location.

+ +
+ + + +{% endblock %} \ No newline at end of file diff --git a/templates/conference.html b/templates/conference.html index 34c2bca..b88c379 100644 --- a/templates/conference.html +++ b/templates/conference.html @@ -37,7 +37,7 @@ var atLocation; $.each(allCircles, function(k, v) { if (v.getBounds().contains(currentLocation)) { - atLocation = true; + atLocation = 'true'; return false; } else { atLocation = 'false'; @@ -50,9 +50,11 @@ var ajax_request = $.ajax({ method: "POST", - url: "{{ base_url }}/ajax/conference/{{ data.conf.id }}", + url: "{{ base_url }}/checkin/{{ data.conf.id }}/{{ session.user.id }}", data: data, beforeSend: function (){ + console.log("Sending data..."); + console.log(data); $.mobile.loading('show', {theme:"a", text:"Loading", textonly:false, textVisible: true}); } }); diff --git a/templates/home.html b/templates/home.html index 6bd9b73..beb3c35 100644 --- a/templates/home.html +++ b/templates/home.html @@ -28,7 +28,7 @@

Today's Conferences