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 );
+}