diff --git a/.htaccess.sample b/.htaccess.sample index a8be392fe45b19e0781027d31ea8a5585d5a5e4b..e948a57c95350fd358c3e3136a1e60f9f92e6e7d 100644 --- a/.htaccess.sample +++ b/.htaccess.sample @@ -4,8 +4,9 @@ RewriteEngine On RewriteBase /~tsteiner/sojourn RewriteRule ^public - [L] -RewriteCond %{REQUEST_FILENAME} -s [OR] -RewriteCond %{REQUEST_FILENAME} -l [OR] -RewriteCond %{REQUEST_FILENAME} -d -RewriteRule ^(.*$) public/$1 [NC,L] +RewriteRule ^(.*$) public/$1 [NC,NS] +RewriteCond %{REQUEST_FILENAME} !-s +RewriteCond %{REQUEST_FILENAME} !-l +RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^.*$ public/index.php [NC,L] + diff --git a/application/configs/application.ini.sample b/application/configs/application.ini.sample index c65ea90f69143734cb8375cb5c8f05e2927d10da..f622a21fa61c0634a08e94f1a5946b78774116e3 100644 --- a/application/configs/application.ini.sample +++ b/application/configs/application.ini.sample @@ -5,18 +5,25 @@ includePaths.library = APPLICATION_PATH "/../library" bootstrap.path = APPLICATION_PATH "/Bootstrap.php" bootstrap.class = "Bootstrap" resources.frontController.moduleDirectory = APPLICATION_PATH "/modules" -resources.frontController.baseUrl = "/~tsteiner/sojourn" +resources.frontController.baseUrl = "/" resources.db.adapter = "mysqli" resources.db.params.host = "localhost" -resources.db.params.username = "sojourn" -resources.db.params.password = "6VDj4W53vGHVEEVp" -resources.db.params.dbname = "sojourn" +resources.db.params.username = "username" +resources.db.params.password = "password" +resources.db.params.dbname = "dbname" resources.db.isDefaultTableAdapter = true -autoloaderNamespaces.amz = "Unl_" +autoloaderNamespaces.unl = "Unl_" +autoloaderNamespaces.sojourn = "Sojourn_" resources.modules = "" +timeclock.crypto.key = "key" +timeclock.crypto.iv = "ldap" +timeclock.ldap.uri = "ldap://example.com" +timeclock.ldap.dn = "uid=example,dc=example,dc=com" +timeclock.ldap.password = "password" + [staging : production] [testing : production] @@ -25,4 +32,4 @@ phpSettings.display_errors = 1 [development : production] phpSettings.display_startup_errors = 1 -phpSettings.display_errors = 1 \ No newline at end of file +phpSettings.display_errors = 1 diff --git a/application/modules/auth/Bootstrap.php b/application/modules/auth/Bootstrap.php new file mode 100644 index 0000000000000000000000000000000000000000..3d4edf871a9391dea1f98f74c19e771b8d814dda --- /dev/null +++ b/application/modules/auth/Bootstrap.php @@ -0,0 +1,6 @@ +<?php + +class Auth_Bootstrap extends Zend_Application_Module_Bootstrap +{ + +} diff --git a/application/modules/auth/models/Users.php b/application/modules/auth/models/Users.php new file mode 100644 index 0000000000000000000000000000000000000000..760265634ddc721e0995e4d8545537478450e924 --- /dev/null +++ b/application/modules/auth/models/Users.php @@ -0,0 +1,30 @@ +<?php + +class Auth_Model_Users extends Zend_Db_Table +{ + protected $_name = 'users'; + protected $_primary = 'userId'; + protected $_dependentTables = array('Timeclock_Model_Events', 'Timeclock_Model_Readers'); + + public function fetchCurrentUser() + { + $auth = Zend_Auth::getInstance(); + + if (!$auth->hasIdentity()) { + return NULL; + } + + $username = $auth->getIdentity(); + + $users = $this->fetchAll($this->getDefaultAdapter()->quoteInto('username = ?', $username)); + if (count($users) > 0) { + return $users[0]; + } + + $user = $this->fetchNew(); + $user->username = $username; + $user->save(); + + return $user; + } +} diff --git a/application/modules/default/controllers/IndexController.php b/application/modules/default/controllers/IndexController.php index 98e6ab7c0a0203e0a293c5a235288db620dd595e..3396a5aeab7d1b09448e44f9c2ed52cbadf81578 100644 --- a/application/modules/default/controllers/IndexController.php +++ b/application/modules/default/controllers/IndexController.php @@ -4,7 +4,6 @@ class IndexController extends Unl_Controller_Action { public function indexAction() { - } } diff --git a/application/modules/default/views/scripts/layout.phtml b/application/modules/default/views/scripts/layout.phtml index 16afdcc42eaa70bcff4b37b5f38fdcc478b4ca41..98c5f03c78ad6a1bd6f91abe20fd2567b056d874 100644 --- a/application/modules/default/views/scripts/layout.phtml +++ b/application/modules/default/views/scripts/layout.phtml @@ -3,15 +3,22 @@ $this->headLink()->appendStylesheet($this->baseUrl() . '/css/index.css', 'all'); $this->headLink()->appendStylesheet($this->baseUrl() . '/css/print.css', 'print'); $this->headScript()->prependFile($this->baseUrl() . '/javascript/index.js'); -$this->headScript()->prependFile($this->baseUrl() . '/yui/yuiloader/yuiloader-min.js'); + +// JQuery Includes +$this->headLink()->appendStylesheet('http://jqueryui.com/latest/themes/base/ui.all.css', 'all'); +$this->headScript()->prependFile('http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/jquery-ui.min.js'); +$this->headScript()->prependFile('http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js'); if (!$this->layout()->template) { $this->layout()->template = 'Fixed'; } -$this->layout()->title = 'My Application'; +$this->layout()->title = 'Sojourn'; if (!$this->layout()->tagline) { - $this->layout()->tagline = 'Tagline'; + $controller = Zend_Controller_Front::getInstance()->getRequest()->getControllerName(); + $controller = strtr($controller, array('-' => ' ')); + $controller = ucwords($controller); + $this->layout()->tagline = $controller; } require_once 'UNL/Templates.php'; @@ -28,10 +35,11 @@ $template->maincontentarea .= $this->layout()->content . "\n" . '</div>' . "\n"; $template->doctitle = '<title>' . $this->layout()->title . ': ' . $this->layout()->tagline . '</title>'; -$template->head .= "\n" . $this->headLink()->__toString() +$template->head .= "\n" . $this->headLink()->__toString() . "\n" . $this->headMeta()->__toString() . "\n" . $this->headScript()->__toString() - . "\n" . $this->headStyle()->__toString(); + . "\n" . $this->headStyle()->__toString() + . "\n" . '<script type="text/javascript">$().data("baseUrl", "' . $this->baseUrl() . '");</script>'; $template->loadSharedCodeFiles(); $defaultBreadcrumbs = array( @@ -69,5 +77,4 @@ if ($this->layout()->footercontent) { echo "<?xml version='1.0' encoding='UTF-8'?>\n"; $html = $template->toHtml(); -$html = strtr($html, array('<body' => '<body onload="handleOnLoad(\'' . $this->baseUrl() . '\');"')); echo $html; diff --git a/application/modules/default/views/scripts/menu.phtml b/application/modules/default/views/scripts/menu.phtml index 97a04a0fe70903d8e18d59446078b8fcf464a69e..984467c9d27161dfbbf89b3787eda8ad3fe42aae 100644 --- a/application/modules/default/views/scripts/menu.phtml +++ b/application/modules/default/views/scripts/menu.phtml @@ -11,6 +11,9 @@ User: <?php echo Zend_Auth::getInstance()->getIdentity(); ?><a></a> <ul> <li><a href="<?php echo $this->url(array('module' => 'auth', 'action' => 'logout'), null, true); ?>">Logout</a></li> + <li><a href="<?php echo $this->url(array('module' => 'timeclock', 'controller' => 'reader-manager'), null, true); ?>">Reader Manager</a></li> + <li><a href="<?php echo $this->url(array('module' => 'timeclock', 'controller' => 'event-manager'), null, true); ?>">Event Manager</a></li> + </ul> </li> <?php } ?> diff --git a/application/modules/timeclock/Bootstrap.php b/application/modules/timeclock/Bootstrap.php new file mode 100644 index 0000000000000000000000000000000000000000..c836ee45742e3aa81bdd2d559a75c24fcb7ed3be --- /dev/null +++ b/application/modules/timeclock/Bootstrap.php @@ -0,0 +1,6 @@ +<?php + +class Timeclock_Bootstrap extends Zend_Application_Module_Bootstrap +{ + +} \ No newline at end of file diff --git a/application/modules/timeclock/controllers/EventManagerController.php b/application/modules/timeclock/controllers/EventManagerController.php new file mode 100644 index 0000000000000000000000000000000000000000..effcf7c9687abf2701eae070f83818502d0e2d99 --- /dev/null +++ b/application/modules/timeclock/controllers/EventManagerController.php @@ -0,0 +1,82 @@ +<?php + +class Timeclock_EventManagerController extends Unl_Controller_Action +{ + public function indexAction() + { + $userTable = new Auth_Model_Users(); + $events = $userTable->fetchCurrentUser()->findDependentRowset('Timeclock_Model_Events'); + + $this->view->events = $events; + } + + public function addPostAction() + { + $in = $this->_getAllParams(); + + $eventTable = new Timeclock_Model_Events(); + $event = $eventTable->fetchNew(); + + $event->name = $in['name']; + $event->anonymous = $in['anonymous']; + + $userTable = new Auth_Model_Users(); + $event->owner = $userTable->fetchCurrentUser()->userId; + + $event->save(); + + $eventAttendeeTable = new Timeclock_Model_EventAttendees(); + foreach ($in['attendees'] as $nuid) { + $attendee = $eventAttendeeTable->fetchNew(); + $attendee->event = $event->eventId; + $attendee->nuid = $nuid; + $attendee->save(); + } + + + $this->_redirect('/timeclock/event-manager'); + } + + public function editPostAction() + { + $in = $this->_getAllParams(); + + $eventTable = new Timeclock_Model_Events(); + $eventAttendeeTable = new Timeclock_Model_EventAttendees(); + + foreach ($in['edit'] as $id => $data) { + $event = $eventTable->find($id)->current(); + if (!$event) { + continue; + } + if ($data['delete']) { + $event->delete(); + continue; + } + + $event->name = $data['name']; + $event->anonymous = $data['anonymous']; + $event->save(); + + if (!array_key_exists('attendees', $data)) { + continue; + } + foreach ($data['attendees'] as $attendeeId => $attendeeData) { + $attendee = $eventAttendeeTable->find($attendeeId)->current(); + if (!$attendee) { + $attendee = $eventAttendeeTable->fetchNew(); + $attendee->event = $event->eventId; + } + if (array_key_exists('delete', $attendeeData) && $attendeeData['delete']) { + $attendee->delete(); + continue; + } + + $attendee->nuid = $attendeeData['nuid']; + $attendee->save(); + } + } + + $this->_redirect('/timeclock/event-manager'); + } +} diff --git a/application/modules/timeclock/controllers/ReaderManagerController.php b/application/modules/timeclock/controllers/ReaderManagerController.php new file mode 100644 index 0000000000000000000000000000000000000000..5934de10256cd775ce7509e4d3a5fcd887c016a4 --- /dev/null +++ b/application/modules/timeclock/controllers/ReaderManagerController.php @@ -0,0 +1,88 @@ +<?php + +class Timeclock_ReaderManagerController extends Unl_Controller_Action +{ + public function indexAction() + { + $userTable = new Auth_Model_Users(); + $readers = $userTable->fetchCurrentUser()->findDependentRowset('Timeclock_Model_Readers'); + $events = $userTable->fetchCurrentUser()->findDependentRowset('Timeclock_Model_Events'); + + $readersEvents = array(); + foreach ($readers as $reader) { + $readerEvents = $reader->findDependentRowset('Timeclock_Model_ReaderEvents'); + foreach ($readerEvents as $readerEvent) { + $readersEvents[$reader->readerId][$readerEvent->event] = $readerEvent->readerEventId; + } + } + + $this->view->readers = $readers; + $this->view->events = $events; + $this->view->readersEvents = $readersEvents; + } + + public function addPostAction() + { + $in = $this->_getAllParams(); + + $readerTable = new Timeclock_Model_Readers(); + $reader = $readerTable->fetchNew(); + + $reader->name = $in['name']; + if (!$in['virtual']) { + $reader->macAddress = $in['macAddress']; + } + + $userTable = new Auth_Model_Users(); + $reader->owner = $userTable->fetchCurrentUser()->userId; + + $reader->save(); + + $this->_redirect('/timeclock/reader-manager'); + } + + public function editPostAction() + { + $in = $this->_getAllParams(); + + $readerTable = new Timeclock_Model_Readers(); + $readerEventsTable = new Timeclock_Model_ReaderEvents(); + + foreach ($in['edit'] as $id => $data) { + $reader = $readerTable->find($id)->current(); + if (!$reader) { + continue; + } + if ($data['delete']) { + $reader->delete(); + continue; + } + + $reader->name = $data['name']; + if ($data['virtual']) { + $reader->macAddress = NULL; + } else { + $reader->macAddress = $data['macAddress']; + } + $reader->save(); + + $readerEvents = $reader->findDependentRowset('Timeclock_Model_ReaderEvents'); + foreach ($readerEvents as $readerEvent) { + $index = array_search($readerEvent->event, (array) $data['events']); + if ($index !== false) { + unset($data['events'][$index]); + } else { + $readerEvent->delete(); + } + } + foreach ($data['events'] as $eventId) { + $readerEvent = $readerEventsTable->fetchNew(); + $readerEvent->reader = $reader->readerId; + $readerEvent->event = $eventId; + $readerEvent->save(); + } + } + + $this->_redirect('/timeclock/reader-manager'); + } +} diff --git a/application/modules/timeclock/controllers/ReportsController.php b/application/modules/timeclock/controllers/ReportsController.php new file mode 100644 index 0000000000000000000000000000000000000000..175b232b90e5f2701e6f86c9acfabd05899ba544 --- /dev/null +++ b/application/modules/timeclock/controllers/ReportsController.php @@ -0,0 +1,67 @@ +<?php + +class Timeclock_ReportsController extends Unl_Controller_Action +{ + protected $_tableNameTransform = array('count' => 'count', + 'building_name' => 'Location', + 'time' => 'time', + 'sex' => 'Gender', + 'college' => 'College', + 'major_1' => 'Major', + 'major_2' => 'Second-Major', + 'minor_1' => 'Minor', + 'minor_2' => 'Second-Minor', + 'degree' => 'Degree', + 'class_level' => 'Class', + 'honors_program' => 'Honors', + 'full_part' => 'Full-time', + 'birth_dt' => 'Birthdate', + 'ethnic_origin' => 'Ethnicity', + 'advisor_name' => 'Advisor', + 'pre_prof' => 'Pre-Prof', + 'ug_cum_gpa' => 'GPA'); + + public function indexAction() + { + $userTable = new Auth_Model_Users(); + $events = $userTable->fetchCurrentUser()->findDependentRowset('Timeclock_Model_Events'); + $eventsArray = array(); + foreach ($events as $event) { + $eventsArray[$event->eventId] = $event->name; + } + $this->view->events = $eventsArray; + $this->view->fields = $this->_tableNameTransform; + } + + public function getDataAction() + { + + $eventId = $this->_getParam('eventId'); + $fields = $this->_getParam('fields'); + + $eventId = intval($eventId); + + $peopleTable = new Timeclock_Model_People(); + $demographics = $peopleTable->fetchCountBy($eventId, $fields); + + + $dom = new DOMDocument(); + $root = $dom->createElement('result'); + $dom->appendChild($root); + + foreach ($demographics as $row) { + $recordNode = $dom->createElement('record'); + $root->appendChild($recordNode); + foreach ($row as $key => $value) { + $node = $dom->createElement($this->_tableNameTransform[$key]); + $node->appendChild($dom->createTextNode($value)); + $recordNode->appendChild($node); + } + } + + header('Content-Type: text/xml'); + echo $dom->saveXML(); + + exit; + } +} diff --git a/application/modules/timeclock/controllers/TakeAttendanceController.php b/application/modules/timeclock/controllers/TakeAttendanceController.php new file mode 100644 index 0000000000000000000000000000000000000000..4bf8d62470943d8697c3fd2af65ed06928d7fe8d --- /dev/null +++ b/application/modules/timeclock/controllers/TakeAttendanceController.php @@ -0,0 +1,128 @@ +<?php + +class Timeclock_TakeAttendanceController extends Unl_Controller_Action +{ + public function indexAction() + { + $session = new Zend_Session_Namespace(__CLASS__); + + $readers = $this->_getReaders(); + $readerArray = array(-1 => '--Select--'); + foreach ($readers as $reader) { + $readerArray[$reader->readerId] = $reader->name; + } + $this->view->readers = $readerArray; + } + + public function indexPostAction() + { + throw new Exception('You must have javascript enabled.'); + } + + public function swipePostAction() + { + $errors = array(); + + $config = $this->getInvokeArg('bootstrap')->getOptions(); + $config = $config['timeclock']; + + $nuid = $this->_getParam('nuid'); + $iso = $this->_getParam('iso'); + $readerId = $this->_getParam('readerId'); + $encryptedMacAddress = $this->_getParam('macAddress'); + $reader = NULL; + + if ($encryptedMacAddress) { + + $key = hash('sha256', $config['crypto']['key'], true); + $iv = hash('sha256', $config['crypto']['iv'], true); + $data = unserialize(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, base64_decode($encryptedMacAddress), MCRYPT_MODE_CBC, $iv)); + if ($data['time'] < time() - 5*60 && $data['time'] > time() + 5*60) { + $errors[] = 'Reader authentication failed!'; + } + $macAddress = $data['macAddress']; + $readersTable = new Timeclock_Model_Readers(); + $reader = $readersTable->fetchWithMacAddress($macAddress); + } else { + $readers = $this->_getReaders(); + foreach ($readers as $readerModel) { + if ($readerModel->readerId == $readerId) { + $reader = $readerModel; + break; + } + } + + if (!$readerId || !$reader) { + $errors[] = 'Reader authentication failed!'; + } + } + + if ($nuid) { + if ($nuid < 1 || $nuid > 99999999) { + $errors[] = 'The NUID is out of range.'; + } + $filter = 'unluncwid=' . $nuid; + } else if ($iso) { + $iso = trim($iso); + if (strlen($iso) != 16 || substr($iso, 0, 6) != '627139') { + $errors[] = 'The ISO Number is out of range.'; + } + $filter = 'unlIdCardISO=' . $iso; + } else { + $errors[] = 'No NCard ID was provided.'; + } + + if (count($errors) == 0) { + $ldap = new Unl_Ldap($config['ldap']['uri']); + $ldap->bind($config['ldap']['dn'], $config['ldap']['password']); + $data = $ldap->search('ou=people,dc=unl,dc=edu', $filter); + if (count($data) < 1) { + $errors[] = 'User not found'; + } else { + $nuid = $data[0]['unluncwid'][0]; + switch (Sojourn_Attendance::recordSwipe($nuid, $reader)) + { + case Sojourn_Attendance::NO_EVENTS: + $errors[] = 'No events found.'; + break; + + case Sojourn_Attendance::SELECT_EVENT: + $errors[] = 'Multiple events found.'; + break; + } + } + } + + + $dom = new DOMDocument(); + $root = $dom->createElement('result'); + $root->setAttribute('status', 'fail'); + $dom->appendChild($root); + + if (count($errors) > 0) { + foreach ($errors as $message) { + $errorNode = $dom->createElement('message'); + $errorNode->appendChild($dom->createTextNode($message)); + $root->appendChild($errorNode); + } + + echo $dom->saveXML(); + exit; + } + + $messageNode = $dom->createElement('message'); + $messageNode->appendChild($dom->createTextNode('Greetings ' . $data[0]['cn'][0])); + $root->appendChild($messageNode); + + $root->setAttribute('status', 'success'); + + echo $dom->saveXML(); + exit; + } + + protected function _getReaders() + { + $userTable = new Auth_Model_Users(); + return $userTable->fetchCurrentUser()->findDependentRowset('Timeclock_Model_Readers'); + } +} \ No newline at end of file diff --git a/application/modules/timeclock/models/Attendances.php b/application/modules/timeclock/models/Attendances.php new file mode 100644 index 0000000000000000000000000000000000000000..43f34717de14eb7199bad918452643028fd4e1fb --- /dev/null +++ b/application/modules/timeclock/models/Attendances.php @@ -0,0 +1,52 @@ +<?php + +class Timeclock_Model_Attendances extends Zend_Db_Table +{ + protected $_name = 'attendances'; + protected $_primary = 'attendanceId'; + protected $_referenceMap = array( + 'Event' => array( + 'columns' => 'event', + 'refTableClass' => 'Timeclock_Model_Events', + 'refColumns' => 'eventId' + ) + ); + + public function swipe($nuid, $eventId) + { + $db = $this->getAdapter(); + + $where = array(); + $where[] = $db->quoteInto('nuid = ?', $nuid); + $where[] = $db->quoteInto('event = ?', $eventId); + $where[] = 'timeOut IS NULL'; + $where = implode(' AND ', $where); + + $rows = $this->fetchAll($where); + if (count($rows) == 0) { + // insert new row with timeIn + $row = $this->fetchNew(); + $row->nuid = $nuid; + $row->event = $eventId; + $row->timeIn = time(); + } else if (count($rows) == 1) { + // update row with timeOut + $row = $rows[0]; + if ($row->timeIn < time() - 60*60*12) { + $row->timeOut = $row->timeIn; + $row->save(); + + $row = $this->fetchNew(); + $row->nuid = $nuid; + $row->event = $eventId; + $row->timeIn = time(); + } else { + $row->timeOut = time(); + } + } else { + throw new Zend_Exception('Orphaned Attendance Record.'); + } + $row->save(); + } + +} diff --git a/application/modules/timeclock/models/EventAttendees.php b/application/modules/timeclock/models/EventAttendees.php new file mode 100644 index 0000000000000000000000000000000000000000..a075866b971db49e906de47651e6007b0c149fba --- /dev/null +++ b/application/modules/timeclock/models/EventAttendees.php @@ -0,0 +1,14 @@ +<?php + +class Timeclock_Model_EventAttendees extends Zend_Db_Table +{ + protected $_name = 'eventAttendees'; + protected $_primary = 'eventAttendeeId'; + protected $_referenceMap = array( + 'Event' => array( + 'columns' => 'event', + 'refTableClass' => 'Timeclock_Model_Events', + 'refColumns' => 'eventId' + ) + ); +} diff --git a/application/modules/timeclock/models/Events.php b/application/modules/timeclock/models/Events.php new file mode 100644 index 0000000000000000000000000000000000000000..7a5e68f741d69a0597ab791ac47065b2e34b5696 --- /dev/null +++ b/application/modules/timeclock/models/Events.php @@ -0,0 +1,15 @@ +<?php + +class Timeclock_Model_Events extends Zend_Db_Table +{ + protected $_name = 'events'; + protected $_primary = 'eventId'; + protected $_dependentTables = array('Timeclock_Model_Attendances', 'Timeclock_Model_EventAttendees', 'Timeclock_Model_ReaderEvents'); + protected $_referenceMap = array( + 'Owner' => array( + 'columns' => 'owner', + 'refTableClass' => 'Auth_Model_Users', + 'refColumns' => 'userId' + ) + ); +} diff --git a/application/modules/timeclock/models/People.php b/application/modules/timeclock/models/People.php new file mode 100644 index 0000000000000000000000000000000000000000..9cb1a3751787ee1137a63b12084fc7fcc48fbeac --- /dev/null +++ b/application/modules/timeclock/models/People.php @@ -0,0 +1,37 @@ +<?php + +class Timeclock_Model_People extends Zend_Db_Table +{ + protected $_name = 'people'; + protected $_primary = 'nuid'; + + public function fetchCountBy($eventId, $fields) + { + if (!is_array($fields)) { + return array(); + } + if (count($fields) == 0) { + return array(); + } + + + $selectedFields = array(); + foreach ($fields as $field) { + if (!in_array($field, $this->_getCols())) { + continue; + } + $selectedFields[$field] = $field; + } + + $db = $this->getAdapter(); + $select = new Zend_Db_Select($db); + $select->from(array('p' => $this->_name), array('count' => 'COUNT(DISTINCT a.nuid)') + $selectedFields); + $select->join(array('a' => 'attendances'), 'a.nuid = p.nuid', array()); + $select->group(array_keys($selectedFields)); + $select->order('count'); + $select->where('event = ?', $eventId); + + $result = $select->query()->fetchAll(); + return $result; + } +} diff --git a/application/modules/timeclock/models/ReaderEvents.php b/application/modules/timeclock/models/ReaderEvents.php new file mode 100644 index 0000000000000000000000000000000000000000..3e12ec84d9ef73216ba3cafc89e8c7912cf77c0b --- /dev/null +++ b/application/modules/timeclock/models/ReaderEvents.php @@ -0,0 +1,19 @@ +<?php + +class Timeclock_Model_ReaderEvents extends Zend_Db_Table +{ + protected $_name = 'readerEvents'; + protected $_primary = 'readerEventId'; + protected $_referenceMap = array( + 'Reader' => array( + 'columns' => 'reader', + 'refTableClass' => 'Timeclock_Model_Readers', + 'refColumns' => 'readerId' + ), + 'Event' => array( + 'columns' => 'event', + 'refTableClass' => 'Timeclock_Model_Events', + 'refColumns' => 'eventId' + ) + ); +} diff --git a/application/modules/timeclock/models/Readers.php b/application/modules/timeclock/models/Readers.php new file mode 100644 index 0000000000000000000000000000000000000000..1ceb3fd66b57b21dcf27eaa3268a0963076fccbb --- /dev/null +++ b/application/modules/timeclock/models/Readers.php @@ -0,0 +1,23 @@ +<?php + +class Timeclock_Model_Readers extends Zend_Db_Table +{ + protected $_name = 'readers'; + protected $_primary = 'readerId'; + protected $_dependentTables = array('Timeclock_Model_ReaderEvents', 'Timeclock_Model_Swipes'); + protected $_referenceMap = array( + 'Owner' => array( + 'columns' => 'owner', + 'refTableClass' => 'Auth_Model_Users', + 'refColumns' => 'userId' + ) + ); + + public function fetchWithMacAddress($macAddress) + { + $db = $this->getAdapter(); + $where = $db->quoteInto('macAddress = ?', $macAddress); + $row = $this->fetchRow($where); + return $row; + } +} diff --git a/application/modules/timeclock/models/Swipes.php b/application/modules/timeclock/models/Swipes.php new file mode 100644 index 0000000000000000000000000000000000000000..ccb4b95c917d2b70e01d3b4e4dc07523d0cde25b --- /dev/null +++ b/application/modules/timeclock/models/Swipes.php @@ -0,0 +1,14 @@ +<?php + +class Timeclock_Model_Swipes extends Zend_Db_Table +{ + protected $_name = 'swipes'; + protected $_primary = 'swipeId'; + protected $_referenceMap = array( + 'Reader' => array( + 'columns' => 'reader', + 'refTableClass' => 'Timeclock_Model_Readers', + 'refColumns' => 'readerId' + ) + ); +} diff --git a/application/modules/timeclock/views/scripts/event-manager/index.phtml b/application/modules/timeclock/views/scripts/event-manager/index.phtml new file mode 100644 index 0000000000000000000000000000000000000000..4cc43025094bb15939a3fd6f32ba7315ca515f45 --- /dev/null +++ b/application/modules/timeclock/views/scripts/event-manager/index.phtml @@ -0,0 +1,36 @@ +<?php $this->layout()->tagline = 'Event Manager'; ?> +<?php $this->headScript()->appendFile($this->baseUrl() . '/javascript/timeclock/event-manager.js'); ?> +<?php $this->headLink()->appendStylesheet($this->baseUrl() . '/css/timeclock/event-manager.css', 'all'); ?> + +<h4>Set up new event</h4> +<form method="post" action="<?php echo $this->baseUrl(); ?>/timeclock/event-manager/add.post"> + Name: <?php echo $this->formText('name'); ?> + Anonymous Statistics: <?php echo $this->formCheckbox('anonymous', null, array('class' => 'virtualInput')); ?> + <div class="eventAttendees"> + <a href="#" class="addAttendeeLink" rel="-1">Add registered attendee.</a> + </div> + <?php echo $this->formSubmit('submit', 'Register'); ?> +</form> + +<h4>Edit existing events</h4> +<form method="post" action="<?php echo $this->baseUrl(); ?>/timeclock/event-manager/edit.post"> + <?php + foreach ($this->events as $event) { + ?> + <div> + Name: <?php echo $this->formText("edit[$event->eventId][name]", $event->name); ?> + Anonymous Statistics: <?php echo $this->formCheckbox("edit[$event->eventId][anonymous]", null, array('class' => 'virtualInput') + ($event->anonymous ? array('checked' => 'checked') : array())); ?> + Delete: <?php echo $this->formCheckbox("edit[$event->eventId][delete]"); ?> + <div class="eventAttendees"> + <?php foreach ($event->findDependentRowset('Timeclock_Model_EventAttendees') as $eventAttendee) { ?> + <div> + NUID: <?php echo $this->formText("edit[$event->eventId][attendees][$eventAttendee->eventAttendeeId][nuid]", $eventAttendee->nuid); ?> + Delete: <?php echo $this->formCheckbox("edit[$event->eventId][attendees][$eventAttendee->eventAttendeeId][delete]"); ?> + </div> + <?php } ?> + <a href="#" class="addAttendeeLink" rel="<?php echo $event->eventId; ?>">Add registered attendee.</a> + </div> + </div> + <?php } ?> + <?php echo $this->formSubmit('submit', 'Update'); ?> +</form> diff --git a/application/modules/timeclock/views/scripts/reader-manager/index.phtml b/application/modules/timeclock/views/scripts/reader-manager/index.phtml new file mode 100644 index 0000000000000000000000000000000000000000..6a2f7b3fd40d486e94544b8c08a305c1e12603b4 --- /dev/null +++ b/application/modules/timeclock/views/scripts/reader-manager/index.phtml @@ -0,0 +1,48 @@ +<?php $this->layout()->tagline = 'Reader Manager'; ?> +<?php $this->headScript()->appendFile($this->baseUrl() . '/javascript/timeclock/reader-manager.js'); ?> + +<h4>Register new reader</h4> +<form method="post" action="<?php echo $this->baseUrl(); ?>/timeclock/reader-manager/add.post"> + Name: <?php echo $this->formText('name'); ?> + Web Based: <?php echo $this->formCheckbox('virtual', null, array('class' => 'virtualInput')); ?> + MAC Address: <?php echo $this->formText('macAddress', null, array('class' => 'macAddressInput')); ?> + <div class="readerEvents"> + <?php foreach ($this->events as $event) { ?> + <div><input type="checkbox" name="events[] value="<?php echo $event->eventId; ?>" /><?php echo $event->name; ?></div> + <?php } ?> + </div> + <?php echo $this->formSubmit('submit', 'Register'); ?> +</form> + +<h4>Edit existing readers</h4> +<form method="post" action="<?php echo $this->baseUrl(); ?>/timeclock/reader-manager/edit.post"> + <?php + foreach ($this->readers as $reader) { + $virtualAttribs = array('class' => 'virtualInput'); + $macAddressAttribs = array('class' => 'macAddressInput'); + if (!$reader->macAddress) { + $virtualAttribs['checked'] = 'checked'; + $macAddressAttribs['disabled'] = 'disabled'; + } + ?> + <div> + Name: <?php echo $this->formText("edit[$reader->readerId][name]", $reader->name); ?> + Web Based: <?php echo $this->formCheckbox("edit[$reader->readerId][virtual]", null, $virtualAttribs); ?> + MAC Address: <?php echo $this->formText("edit[$reader->readerId][macAddress]", $reader->macAddress, $macAddressAttribs); ?> + Delete: <?php echo $this->formCheckbox("edit[$reader->readerId][delete]"); ?> + <div class="readerEvents"> + <?php foreach ($this->events as $event) { ?> + <div> + <input type="checkbox" + name="<?php echo "edit[$reader->readerId][events][]"; ?>" + value="<?php echo $event->eventId; ?>" + <?php if (@$this->readersEvents[$reader->readerId][$event->eventId]) {?>checked="checked"<?php } ?> + /> + <?php echo $event->name; ?> + </div> + <?php } ?> + </div> + </div> + <?php } ?> + <?php echo $this->formSubmit('submit', 'Update'); ?> +</form> \ No newline at end of file diff --git a/application/modules/timeclock/views/scripts/reports/index.phtml b/application/modules/timeclock/views/scripts/reports/index.phtml new file mode 100644 index 0000000000000000000000000000000000000000..d28f13cf4c2d6ae16a42d56821dc8c3cec1d0e76 --- /dev/null +++ b/application/modules/timeclock/views/scripts/reports/index.phtml @@ -0,0 +1,28 @@ +<?php $this->headScript()->appendFile($this->baseUrl() . '/javascript/timeclock/reports.js'); ?> +<?php $this->headLink()->appendStylesheet($this->baseUrl() . '/css/timeclock/reports.css', 'all'); ?> + +<form id="reportsForm"> + <h4>Select an event:</h4> + <div> + <?php echo $this->formSelect('eventId', NULL, NULL, $this->events); ?> + </div> + + <h4>Select Fields to Report:</h4> + <div> + <label>Gender: <?php echo $this->formCheckbox('sex'); ?></label> + <!-- label>Location: <?php echo $this->formCheckbox('building_name'); ?></label --> + <label>College: <?php echo $this->formCheckbox('college'); ?></label> + <label>Major: <?php echo $this->formCheckbox('major_1'); ?></label> + <label>Minor: <?php echo $this->formCheckbox('minor_1'); ?></label> + <label>Degree: <?php echo $this->formCheckbox('degree'); ?></label> + <label>Class: <?php echo $this->formCheckbox('class_level'); ?></label> + <label>Honors Program: <?php echo $this->formCheckbox('honors_program'); ?></label> + <label>Full/Part Time: <?php echo $this->formCheckbox('full_part'); ?></label> + <!-- label>Advisor: <?php echo $this->formCheckbox('advisor_name'); ?></label --> + <label>Ethnicity: <?php echo $this->formCheckbox('ethnic_origin'); ?></label> + </div> + + <?php echo $this->formSubmit('submit', 'Update'); ?> +</form> + +<div id="report"></div> \ No newline at end of file diff --git a/application/modules/timeclock/views/scripts/take-attendance/index.phtml b/application/modules/timeclock/views/scripts/take-attendance/index.phtml new file mode 100644 index 0000000000000000000000000000000000000000..c0418cc16a46597f3977904e9ffd7571da28b8a0 --- /dev/null +++ b/application/modules/timeclock/views/scripts/take-attendance/index.phtml @@ -0,0 +1,11 @@ +<?php $this->headScript()->appendFile($this->baseUrl() . '/javascript/timeclock/take-attendance.js'); ?> + +<form id="attendanceForm" method="post" action="<?php echo $this->baseUrl(); ?>/timeclock/take-attendance/index.post"> + Please select a reader: + <?php echo $this->formSelect('readerId', NULL, NULL, $this->readers); ?><br/> + <br /> + <div id=""> + Please enter your NUID: <?php echo $this->formText('nuid'); ?> + <?php echo $this->formSubmit('submit', 'Submit'); ?> + </div> +</form> diff --git a/library/Sojourn/Attendance.php b/library/Sojourn/Attendance.php new file mode 100644 index 0000000000000000000000000000000000000000..6f5157f59e026499a42b7eba4d9dd87598849500 --- /dev/null +++ b/library/Sojourn/Attendance.php @@ -0,0 +1,35 @@ +<?php + +class Sojourn_Attendance +{ + const SUCCESS = 0; + const SELECT_EVENT = -1; + const NO_EVENTS = -2; + + static public function recordSwipe($nuid, Zend_Db_Table_Row $reader, $eventName = NULL) + { + $readerEvents = $reader->findDependentRowset('Timeclock_Model_ReaderEvents'); + $eventIds = array(); + foreach ($readerEvents as $readerEvent) { + $eventIds[] = $readerEvent->event; + } + + $eventsTable = new Timeclock_Model_Events(); + $events = $eventsTable->find($eventIds); + + if (count($events) > 1) { + return self::SELECT_EVENT; + } + + if (count($events) < 1) { + return self::NO_EVENTS; + } + + $event = $events[0]; + + $attendancesTable = new Timeclock_Model_Attendances(); + $attendancesTable->swipe($nuid, $event->eventId); + + return self::SUCCESS; + } +} \ No newline at end of file diff --git a/public/css/index.css b/public/css/index.css new file mode 100644 index 0000000000000000000000000000000000000000..bf67b66d07cfa85b0628cb720c2fa7be1a85b66b --- /dev/null +++ b/public/css/index.css @@ -0,0 +1 @@ +@CHARSET "UTF-8"; \ No newline at end of file diff --git a/public/css/print.css b/public/css/print.css new file mode 100644 index 0000000000000000000000000000000000000000..bf67b66d07cfa85b0628cb720c2fa7be1a85b66b --- /dev/null +++ b/public/css/print.css @@ -0,0 +1 @@ +@CHARSET "UTF-8"; \ No newline at end of file diff --git a/public/css/timeclock/event-manager.css b/public/css/timeclock/event-manager.css new file mode 100644 index 0000000000000000000000000000000000000000..46969df92ace81d0c9d1392aa278d6f9a7eedbc9 --- /dev/null +++ b/public/css/timeclock/event-manager.css @@ -0,0 +1,3 @@ +@CHARSET "UTF-8"; + +.eventAttendees {margin-left: 2em;} \ No newline at end of file diff --git a/public/css/timeclock/reports.css b/public/css/timeclock/reports.css new file mode 100644 index 0000000000000000000000000000000000000000..b1f95706b0c2540994f475e9b8f742633073edba --- /dev/null +++ b/public/css/timeclock/reports.css @@ -0,0 +1,4 @@ +@CHARSET "UTF-8"; + +#maincontent {width: 940px;} +#report td {text-align: center;} \ No newline at end of file diff --git a/public/javascript/index.js b/public/javascript/index.js new file mode 100644 index 0000000000000000000000000000000000000000..cfa374bdb67616955ddecabe673b51c266aeddb9 --- /dev/null +++ b/public/javascript/index.js @@ -0,0 +1 @@ +// empty \ No newline at end of file diff --git a/public/javascript/timeclock/event-manager.js b/public/javascript/timeclock/event-manager.js new file mode 100644 index 0000000000000000000000000000000000000000..4845a999d6ebf0a5951d0f84e2e68d079b84e674 --- /dev/null +++ b/public/javascript/timeclock/event-manager.js @@ -0,0 +1,42 @@ +var eventManager = {}; + + +$(document).ready( function() { + $('.addAttendeeLink').click(eventManager.handleAddAttendee); + $('.removeAttendeeLink').click(eventManager.handleRemoveAttendeeLink); +}); + + + +eventManager.handleAddAttendee = function() +{ + var nextId = $(this).data('nextId'); + if (!nextId) { + nextId = -1; + } + + var eventId = $(this).attr('rel'); + + var name = ''; + if (eventId > 0) { + name = 'edit[' + eventId + '][attendees][' + nextId + '][nuid]'; + } else { + name = 'attendees[' + nextId + ']'; + } + + $(this).before('<div>NUID: <input name="' + name + '" type="text" /><a href="#" class="removeAttendeeLink"> Remove </a></div>'); + + nextId--; + $(this).data('nextId', nextId); + + $('.removeAttendeeLink').click(eventManager.handleRemoveAttendeeLink); + + return false; +} + +eventManager.handleRemoveAttendeeLink = function() +{ + $(this).parent().remove(); + + return false; +} \ No newline at end of file diff --git a/public/javascript/timeclock/reader-manager.js b/public/javascript/timeclock/reader-manager.js new file mode 100644 index 0000000000000000000000000000000000000000..5ff6fd446ba76ed1fe3890190e3c023d6cc2034a --- /dev/null +++ b/public/javascript/timeclock/reader-manager.js @@ -0,0 +1,13 @@ +var readerManager = {}; + + +$(document).ready( function() { + $('.virtualInput').change(readerManager.handleVirtualChange); +}); + + + +readerManager.handleVirtualChange = function() +{ + $(this).parent().find('.macAddressInput').attr('disabled', this.checked); +} \ No newline at end of file diff --git a/public/javascript/timeclock/reports.js b/public/javascript/timeclock/reports.js new file mode 100644 index 0000000000000000000000000000000000000000..cde61a4796535bd280d27d858b6a0faa6f938e0b --- /dev/null +++ b/public/javascript/timeclock/reports.js @@ -0,0 +1,71 @@ +var reports = {}; + +$(document).ready( function() { + $('#reportsForm').submit(reports.handleUpdate); +}); + +reports.handleUpdate = function() +{ + var params = 'eventId=' + $('#eventId').val(); + for (var i = 0; i < $('#reportsForm').attr('elements').length; i++) { + var input = $('#reportsForm').attr('elements').item(i); + if (input.type != 'checkbox') { + continue; + } + if (!input.checked) { + continue; + } + + params += '&fields[]=' + input.id; + } + + $.ajax({ + type: "POST", + url: $().data('baseUrl') + "/timeclock/reports/get-data", + data: params, + success: reports.handleUpdatePost + }); + + return false; +} + +reports.handleUpdatePost = function(data) +{ + while(document.getElementById('report').hasChildNodes()) { + document.getElementById('report').removeChild(document.getElementById('report').firstChild); + } + + if (data.childNodes.length == 0) { + return; + } + + var table = document.createElement('table'); + var thead = document.createElement('thead'); + table.appendChild(thead); + table.className = 'zentable cool' + var header = document.createElement('tr'); + thead.appendChild(header); + document.getElementById('report').appendChild(table); + + for (var i = 0; i < data.childNodes[0].childNodes[0].childNodes.length; i++) { + var node = document.createElement('th'); + node.appendChild(document.createTextNode(data.childNodes[0].childNodes[0].childNodes[i].nodeName)); + header.appendChild(node); + } + + var tbody = document.createElement('tbody'); + table.appendChild(tbody); + + for (var i = 0; i < data.childNodes[0].childNodes.length; i++) { + var row = document.createElement('tr'); + tbody.appendChild(row); + for (var j = 0; j < data.childNodes[0].childNodes[0].childNodes.length; j++) { + var node = document.createElement('td'); + if (data.childNodes[0].childNodes[i].childNodes[j].childNodes[0]) { + node.appendChild(document.createTextNode(data.childNodes[0].childNodes[i].childNodes[j].childNodes[0].data)); + } + row.appendChild(node); + } + } +} + diff --git a/public/javascript/timeclock/take-attendance.js b/public/javascript/timeclock/take-attendance.js new file mode 100644 index 0000000000000000000000000000000000000000..bcb4f2d8f3cd98ccf5f30abb1ede7680b3e50228 --- /dev/null +++ b/public/javascript/timeclock/take-attendance.js @@ -0,0 +1,28 @@ +var takeAttendance = {}; + + +$(document).ready( function() { + $('#attendanceForm').submit(takeAttendance.handleSwipe); + $('#readerId').change(takeAttendance.handleReaderChange); +}); + +takeAttendance.handleReaderChange = function() +{ + this.form.submit(); +} + +takeAttendance.handleSwipe = function() +{ + $.ajax({ + type: "POST", + url: $().data('baseUrl') + "/timeclock/take-attendance/swipe.post", + data: "nuid=" + $('#nuid').val() + "&readerId=" + $('#readerId').val(), + success: takeAttendance.handleSwipePost + }); + + return false; +} + +takeAttendance.handleSwipePost = function(msg) { + alert( "Data Saved: " + msg ); +}