From 4acba20b922fb6473b603bbfa5b082662a7262d8 Mon Sep 17 00:00:00 2001
From: Brett Bieber <brett.bieber@gmail.com>
Date: Tue, 24 Sep 2013 13:27:52 -0500
Subject: [PATCH] Install UNL_Services_CourseApproval lib

---
 lib/.pear2registry                            | Bin 382976 -> 382976 bytes
 .../0.4.0-info.xml                            |  96 ++++
 .../examples/Courses_by_Subject_Code.php      | 185 ++++++++
 lib/php/Savvy.php                             |   2 +-
 lib/php/UNL/Services/CourseApproval.php       |  76 +++
 .../CourseApproval/CachingService.php         |   6 +
 .../CachingService/CacheLite.php              |  28 ++
 .../CourseApproval/CachingService/Null.php    |  15 +
 .../UNL/Services/CourseApproval/College.php   |   5 +
 .../UNL/Services/CourseApproval/Course.php    | 262 +++++++++++
 .../CourseApproval/Course/Activities.php      |  55 +++
 .../Services/CourseApproval/Course/Codes.php  |  46 ++
 .../CourseApproval/Course/Credits.php         |  76 +++
 .../UNL/Services/CourseApproval/Courses.php   |  20 +
 .../Filter/ExcludeGraduateCourses.php         |  15 +
 .../Filter/ExcludeUndergraduateCourses.php    |  15 +
 .../UNL/Services/CourseApproval/Listing.php   |  57 +++
 .../UNL/Services/CourseApproval/Search.php    |  93 ++++
 .../CourseApproval/Search/Results.php         |  25 +
 .../CourseApproval/SearchInterface.php        | 137 ++++++
 .../CourseApproval/SearchInterface/XPath.php  | 338 ++++++++++++++
 .../Services/CourseApproval/SubjectArea.php   |  31 ++
 .../CourseApproval/SubjectArea/Courses.php    |  73 +++
 .../CourseApproval/SubjectArea/Groups.php     |  48 ++
 .../Services/CourseApproval/XCRIService.php   |   6 +
 .../XCRIService/MockService.php               | 440 ++++++++++++++++++
 .../CourseApproval/XCRIService/creq.php       |  79 ++++
 .../activities.phpt                           |  12 +
 .../array_details.phpt                        |  14 +
 .../UNL_Services_CourseApproval/credits.phpt  |  26 ++
 .../dfremoval.phpt                            |  19 +
 .../UNL_Services_CourseApproval/isset.phpt    |  13 +
 .../UNL_Services_CourseApproval/listing.phpt  |  28 ++
 .../UNL_Services_CourseApproval/sample.phpt   |   9 +
 .../UNL_Services_CourseApproval/search.phpt   |  43 ++
 .../UNL_Services_CourseApproval/subjects.phpt |  14 +
 .../subsequent_courses.phpt                   |  18 +
 .../test_framework.php                        | 228 +++++++++
 38 files changed, 2652 insertions(+), 1 deletion(-)
 create mode 100644 lib/.xmlregistry/packages/pear.unl.edu/UNL_Services_CourseApproval/0.4.0-info.xml
 create mode 100644 lib/docs/pear.unl.edu/UNL_Services_CourseApproval/examples/Courses_by_Subject_Code.php
 create mode 100644 lib/php/UNL/Services/CourseApproval.php
 create mode 100644 lib/php/UNL/Services/CourseApproval/CachingService.php
 create mode 100644 lib/php/UNL/Services/CourseApproval/CachingService/CacheLite.php
 create mode 100644 lib/php/UNL/Services/CourseApproval/CachingService/Null.php
 create mode 100644 lib/php/UNL/Services/CourseApproval/College.php
 create mode 100644 lib/php/UNL/Services/CourseApproval/Course.php
 create mode 100644 lib/php/UNL/Services/CourseApproval/Course/Activities.php
 create mode 100644 lib/php/UNL/Services/CourseApproval/Course/Codes.php
 create mode 100644 lib/php/UNL/Services/CourseApproval/Course/Credits.php
 create mode 100644 lib/php/UNL/Services/CourseApproval/Courses.php
 create mode 100644 lib/php/UNL/Services/CourseApproval/Filter/ExcludeGraduateCourses.php
 create mode 100644 lib/php/UNL/Services/CourseApproval/Filter/ExcludeUndergraduateCourses.php
 create mode 100644 lib/php/UNL/Services/CourseApproval/Listing.php
 create mode 100644 lib/php/UNL/Services/CourseApproval/Search.php
 create mode 100644 lib/php/UNL/Services/CourseApproval/Search/Results.php
 create mode 100644 lib/php/UNL/Services/CourseApproval/SearchInterface.php
 create mode 100644 lib/php/UNL/Services/CourseApproval/SearchInterface/XPath.php
 create mode 100644 lib/php/UNL/Services/CourseApproval/SubjectArea.php
 create mode 100644 lib/php/UNL/Services/CourseApproval/SubjectArea/Courses.php
 create mode 100644 lib/php/UNL/Services/CourseApproval/SubjectArea/Groups.php
 create mode 100644 lib/php/UNL/Services/CourseApproval/XCRIService.php
 create mode 100644 lib/php/UNL/Services/CourseApproval/XCRIService/MockService.php
 create mode 100644 lib/php/UNL/Services/CourseApproval/XCRIService/creq.php
 create mode 100644 lib/tests/pear.unl.edu/UNL_Services_CourseApproval/activities.phpt
 create mode 100644 lib/tests/pear.unl.edu/UNL_Services_CourseApproval/array_details.phpt
 create mode 100644 lib/tests/pear.unl.edu/UNL_Services_CourseApproval/credits.phpt
 create mode 100644 lib/tests/pear.unl.edu/UNL_Services_CourseApproval/dfremoval.phpt
 create mode 100644 lib/tests/pear.unl.edu/UNL_Services_CourseApproval/isset.phpt
 create mode 100644 lib/tests/pear.unl.edu/UNL_Services_CourseApproval/listing.phpt
 create mode 100644 lib/tests/pear.unl.edu/UNL_Services_CourseApproval/sample.phpt
 create mode 100644 lib/tests/pear.unl.edu/UNL_Services_CourseApproval/search.phpt
 create mode 100644 lib/tests/pear.unl.edu/UNL_Services_CourseApproval/subjects.phpt
 create mode 100644 lib/tests/pear.unl.edu/UNL_Services_CourseApproval/subsequent_courses.phpt
 create mode 100644 lib/tests/pear.unl.edu/UNL_Services_CourseApproval/test_framework.php

diff --git a/lib/.pear2registry b/lib/.pear2registry
index fae52034d512c482b1b4fa280fb21f50e4afe04a..a421eaecf4abcdef93299b11da5698da5eb6f63b 100644
GIT binary patch
delta 18348
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
AN&o-=

delta 9798
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
NW^~?u;}ENcC;(R`mqq{p

diff --git a/lib/.xmlregistry/packages/pear.unl.edu/UNL_Services_CourseApproval/0.4.0-info.xml b/lib/.xmlregistry/packages/pear.unl.edu/UNL_Services_CourseApproval/0.4.0-info.xml
new file mode 100644
index 0000000..7d33401
--- /dev/null
+++ b/lib/.xmlregistry/packages/pear.unl.edu/UNL_Services_CourseApproval/0.4.0-info.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<package xmlns="http://pear.php.net/dtd/package-2.1" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.1" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0     http://pear.php.net/dtd/tasks-1.0.xsd     http://pear.php.net/dtd/package-2.1     http://pear.php.net/dtd/package-2.1.xsd">
+ <name>UNL_Services_CourseApproval</name>
+ <channel>pear.unl.edu</channel>
+ <summary>Client API for the curriculum request system at creq.unl.edu
+</summary>
+ <description>
+This project provides a simple API for the course data within the creq system
+built by Tim Steiner.
+
+This project requires the UNL_Autoload package, and optionally Cache_Lite for
+caching data from the creq system.
+
+Currently data is cached on the local system in /tmp/cache_* files and stored
+for one week.
+
+See the docs/examples/ directory for examples.
+
+For information on the XML format, see the XSD -
+http://courseapproval.unl.edu/schema/courses.xsd</description>
+ <lead>
+  <name>Brett Bieber</name>
+  <user>saltybeagle</user>
+  <email>brett.bieber@gmail.com</email>
+  <active>yes</active>
+ </lead>
+ <date>2013-09-24</date>
+ <time>10:29:31</time>
+ <version>
+  <release>0.4.0</release>
+  <api>0.1.0</api>
+ </version>
+ <stability>
+  <release>alpha</release>
+  <api>alpha</api>
+ </stability>
+ <license uri="http://www.opensource.org/licenses/bsd-license.php">New BSD License</license>
+ <notes>Feature Release!
+
+New tools for filtering out courses
+
+Features:
+
+* Add support for searching by course number suffix. E.g. 41 for 141, 241, 341
+</notes>
+ <contents>
+  <dir name="/">
+   <file role="test" name="tests/test_framework.php" md5sum="e4f9bc1757951877325604c19e3ab217"/>
+   <file role="test" name="tests/subsequent_courses.phpt" md5sum="f64a2e44fbeb29244d1f513dc9345614"/>
+   <file role="test" name="tests/subjects.phpt" md5sum="2cad4c38c985bf9ad5447cd125f722e4"/>
+   <file role="test" name="tests/search.phpt" md5sum="96138fa9c28c562d58cfc34a015df468"/>
+   <file role="test" name="tests/sample.phpt" md5sum="3aa1605f14db215adbecb5aea6fb9e7e"/>
+   <file role="test" name="tests/listing.phpt" md5sum="09c4d745744c0d1868940c784c818ec1"/>
+   <file role="test" name="tests/isset.phpt" md5sum="894be932fa8e4a53fe5ab6a6a09cf108"/>
+   <file role="test" name="tests/dfremoval.phpt" md5sum="1d7731d25166be2846dca42482d0e9d8"/>
+   <file role="test" name="tests/credits.phpt" md5sum="d8a226bcd8df76eba52d2be3fcd1f232"/>
+   <file role="test" name="tests/array_details.phpt" md5sum="a594d847a39c1e4f252ab6f224a4f7f1"/>
+   <file role="test" name="tests/activities.phpt" md5sum="8fc02ef556eebeb64845686c5329a915"/>
+   <file role="php" name="src/UNL/Services/CourseApproval/XCRIService/MockService.php" md5sum="6ce3e7960cc96859324b01d3689e483c"/>
+   <file role="php" name="src/UNL/Services/CourseApproval/XCRIService/creq.php" md5sum="fa8f0b57588220b7b1d27d9c310ea438"/>
+   <file role="php" name="src/UNL/Services/CourseApproval/XCRIService.php" md5sum="43510f6da18f84d84906db84e85c12ec"/>
+   <file role="php" name="src/UNL/Services/CourseApproval/SubjectArea/Groups.php" md5sum="1366c9b03a537a89738761649f6f9192"/>
+   <file role="php" name="src/UNL/Services/CourseApproval/SubjectArea/Courses.php" md5sum="5d8f10bdb2abd05623f2d17c4e03ead0"/>
+   <file role="php" name="src/UNL/Services/CourseApproval/SubjectArea.php" md5sum="37a06f138ab00f42acca4ba2fa4cedbc"/>
+   <file role="php" name="src/UNL/Services/CourseApproval/SearchInterface/XPath.php" md5sum="b3b89c725efffcc1dd0f5224a337b291"/>
+   <file role="php" name="src/UNL/Services/CourseApproval/SearchInterface.php" md5sum="51d01d912970c154eaf804e98ca541c3"/>
+   <file role="php" name="src/UNL/Services/CourseApproval/Search/Results.php" md5sum="fec9d8c559335a6f7e7b894a85056772"/>
+   <file role="php" name="src/UNL/Services/CourseApproval/Search.php" md5sum="6c17f58d0f4a7f4e83a441a75e064716"/>
+   <file role="php" name="src/UNL/Services/CourseApproval/Listing.php" md5sum="0d2b6c93325511b5d4a06db12d709200"/>
+   <file role="php" name="src/UNL/Services/CourseApproval/Filter/ExcludeUndergraduateCourses.php" md5sum="17a5e0f582d8f8d086108df6e24a6959"/>
+   <file role="php" name="src/UNL/Services/CourseApproval/Filter/ExcludeGraduateCourses.php" md5sum="099d7af04e732e1851840901c5be3a41"/>
+   <file role="php" name="src/UNL/Services/CourseApproval/Courses.php" md5sum="867d1cf75bf8af88b4eb273c9384e417"/>
+   <file role="php" name="src/UNL/Services/CourseApproval/Course/Credits.php" md5sum="32be56ea6645167ce0a5a7cc62c4ba96"/>
+   <file role="php" name="src/UNL/Services/CourseApproval/Course/Codes.php" md5sum="fae6d972149685f84d70dd7273552661"/>
+   <file role="php" name="src/UNL/Services/CourseApproval/Course/Activities.php" md5sum="05600adcb178cb7630337b52969633a9"/>
+   <file role="php" name="src/UNL/Services/CourseApproval/Course.php" md5sum="d3da6078336cae6b2b794ed2c40e8e47"/>
+   <file role="php" name="src/UNL/Services/CourseApproval/College.php" md5sum="90b76d7bbac0738920d6439b37f49d97"/>
+   <file role="php" name="src/UNL/Services/CourseApproval/CachingService/Null.php" md5sum="c830c9993bfa2e7367f52dacb2857ef5"/>
+   <file role="php" name="src/UNL/Services/CourseApproval/CachingService/CacheLite.php" md5sum="45e0a57bdcdb743c006c9a9b754260d4"/>
+   <file role="php" name="src/UNL/Services/CourseApproval/CachingService.php" md5sum="9ff7d4e53553663a7b12f8f89da48fd5"/>
+   <file role="php" name="src/UNL/Services/CourseApproval.php" md5sum="9aa460c3b7225ab2987762fd526a36c7"/>
+   <file role="doc" name="docs/examples/Courses_by_Subject_Code.php" md5sum="7c2172b207b574d0fa0ed31013993fd3"/>
+  </dir>
+ </contents>
+ <dependencies>
+  <required>
+   <php>
+    <min>5.2.0</min>
+   </php>
+   <pearinstaller>
+    <min>2.0.0a1</min>
+   </pearinstaller>
+  </required>
+ </dependencies>
+ <phprelease/>
+</package>
diff --git a/lib/docs/pear.unl.edu/UNL_Services_CourseApproval/examples/Courses_by_Subject_Code.php b/lib/docs/pear.unl.edu/UNL_Services_CourseApproval/examples/Courses_by_Subject_Code.php
new file mode 100644
index 0000000..76ea630
--- /dev/null
+++ b/lib/docs/pear.unl.edu/UNL_Services_CourseApproval/examples/Courses_by_Subject_Code.php
@@ -0,0 +1,185 @@
+<?php 
+chdir(dirname(dirname(dirname(__FILE__))).'/src');
+
+require_once 'UNL/Autoload.php';
+UNL_Templates::$options['version'] = 3;
+$page = UNL_Templates::factory('Fixed');
+
+$page->addStyleDeclaration('
+.course .subjectCode {background-color:#E7F0F9;margin-bottom:-1px;color:#818489;display:block;float:left;min-width:85px;text-align:center;}
+.course .number {font-size:2.5em;padding:7px 0px;margin:0 5px 0 0;background-color:#E7F0F9;display:block;clear:left;float:left;font-weight:bold;min-width:85px;text-align:center;}
+.course .title {font-size:1.5em; display:block; border-bottom:1px solid #C8C8C8;font-style:normal;font-weight:bold;margin-left:95px;}
+.course .crosslistings {margin:4px 0 4px 95px;display:block;}
+.course .crosslistings .crosslisting {font-size:1em;color:#C60203;background:none;}
+
+.course .prereqs,
+.course .notes,
+.course .description {margin:4px 0;float:left;clear:left;width:450px;}
+
+.course .prereqs {color:#0F900A;font-weight:bold;}
+.course .notes {font-style:italic;}
+.course .description {border-left:3px solid #C8C8C8;padding-left:5px;}
+
+.course .details {width:220px;border-collapse:collapse;right:0px;float:right;}
+.course .details tr.alt td {border:1px solid #C9E2F6;border-right:none;border-left:none;background-color:#E3F0FF;}
+.course .details td {}
+.course .details .label {font-weight:bold;}
+.course .details .value {text-align:right;}
+dd {margin:0 0 3em 0;padding-left:0 !important;position:relative;overflow:hidden;}
+dt {padding:3em 0 0 0 !important;}
+.course {clear:both;}
+');
+
+$page->titlegraphic = '<h1>Undergraduate Bulletin</h1>
+                       <h2>Your Academic Guide</h2>';
+$page->doctitle = '<title>UNL | Undergraduate Bulletin</title>';
+$page->breadcrumbs = '<ul>
+    <li><a href="http://www.unl.edu/">UNL</a></li>
+    <li>Undergraduate Bulletin</li></ul>';
+$page->navlinks = '
+<ul>
+    <li><a href="#">Academic Policies</a></li>
+    <li><a href="#">Achievement-Centered Education (ACE)</a></li>
+    <li><a href="#">Academic Colleges</a></li>
+    <li><a href="#">Areas of Study</a></li>
+    <li><a href="#">Courses</a></li>
+</ul>
+';
+$page->leftRandomPromo = '';
+$page->maincontentarea = '';
+if (!isset($_GET['subject'])) {
+    echo 'Enter a subject code';
+    exit();
+}
+
+
+$subject = new UNL_Services_CourseApproval_SubjectArea($_GET['subject']);
+
+$page->maincontentarea .= '<h1>There are '.count($subject->courses).' courses for '.htmlentities($subject).'</h1>';
+
+$page->maincontentarea .= implode(', ', $subject->groups);
+
+$page->maincontentarea .=  '<dl>';
+
+foreach ($subject->courses as $course) {
+    $listings = '';
+    $crosslistings = '';
+    $groups = '';
+    foreach ($course->codes as $listing) {
+        if ($listing->subjectArea == $subject->subject) {
+            if ($listing->hasGroups()) {
+                $groups = implode(', ', $listing->groups);
+            }
+            $listings .= $listing->courseNumber.'/';
+        } else {
+            $crosslistings .= '<span class="crosslisting">'.$listing->subjectArea.' '.$listing->courseNumber.'</span>, ';
+        }
+    }
+    $listings = trim($listings, '/');
+    $crosslistings = trim($crosslistings, ', ');
+    
+    $credits = '';
+    if (isset($course->credits['Single Value'])) {
+        $credits = $course->credits['Single Value'];
+    }
+    
+    $format = '';
+    foreach ($course->activities as $type=>$activity) {
+        switch ($type) {
+            case 'lec':
+                $format .= 'Lecture';
+                break;
+            case 'lab':
+                $format .= 'Lab';
+                break;
+            case 'quz':
+                $format .= 'Quiz';
+                break;
+            case 'rct':
+                $format .= 'Recitation';
+                break;
+            case 'stu':
+                $format .= 'Studio';
+                break;
+            case 'fld':
+                $format .= 'Field';
+                break;
+            case 'ind':
+                $format .= 'Independent Study';
+                break;
+            case 'psi':
+                $format .= 'Personalized System of Instruction';
+                break;
+            default:
+                throw new Exception('Unknown activity type! '.$type);
+                break;
+        }
+        $format .= ' '.$activity->hours.', ';
+    }
+    $format = trim($format, ', ');
+    
+    $page->maincontentarea .= "
+        <dt class='course'>
+            <span class='subjectCode'>".htmlentities($subject->subject)."</span>
+            <span class='number'>$listings</span>
+            <span class='title'>".htmlentities($course->title)."</span>";
+        if (!empty($crosslistings)) {
+            $page->maincontentarea .= '<span class="crosslistings">Crosslisted as '.$crosslistings.'</span>';
+        }
+        $page->maincontentarea .= "</dt>
+        <dd class='course'>";
+        $page->maincontentarea .= '<table class="zentable cool details">';
+        $page->maincontentarea .= '<tr class="credits">
+                                    <td class="label">Credit Hours:</td>
+                                    <td class="value">'.$credits.'</td>
+                                    </tr>';
+        if (!empty($format)) {
+            $page->maincontentarea .= '<tr class="format">
+                                        <td class="label">Course Format:</td>
+                                        <td class="value">'.$format.'</td>
+                                        </tr>';
+        }
+        if (count($course->campuses) == 1
+            && $course->campuses[0] != 'UNL') {
+            $page->maincontentarea .= '<tr class="campus">
+                                        <td class="label">Campus:</td>
+                                        <td class="value">'.implode(', ', $course->campuses).'</td>
+                                        </tr>';
+        }
+//        $page->maincontentarea .= '<tr class="termsOffered alt">
+//                                    <td class="label">Terms Offered:</td>
+//                                    <td class="value">'.implode(', ', $course->termsOffered).'</td>
+//                                    </tr>';
+        $page->maincontentarea .= '<tr class="deliveryMethods">
+                                    <td class="label">Course Delivery:</td>
+                                    <td class="value">'.implode(', ', $course->deliveryMethods).'</td>
+                                    </tr>';
+        $ace = '';
+        if (!empty($course->aceOutcomes)) {
+            $ace = implode(', ', $course->aceOutcomes);
+            $page->maincontentarea .= '<tr class="aceOutcomes">
+                                        <td class="label">ACE Outcomes:</td>
+                                        <td class="value">'.$ace.'</td>
+                                        </tr>';
+        }
+        if (!empty($groups)) {
+            $page->maincontentarea .= '<tr class="groups">
+                                        <td class="label">Groups:</td>
+                                        <td class="value">'.$groups.'</td>
+                                       </tr>';
+        }
+        $page->maincontentarea .= '</table>';
+
+        if (!empty($course->prerequisite)) {
+            $page->maincontentarea .= "<p class='prereqs'>Prereqs: ".htmlentities($course->prerequisite)."</p>";
+        }
+        if (!empty($course->notes)) {
+            $page->maincontentarea .= "<p class='notes'>".htmlentities($course->notes)."</p>";
+        }
+        $page->maincontentarea .= "<p class='description'>".htmlentities($course->description)."</p>";
+        
+    $page->maincontentarea .= "</dd>";
+}
+$page->maincontentarea .= '</dl>';
+
+echo $page;
\ No newline at end of file
diff --git a/lib/php/Savvy.php b/lib/php/Savvy.php
index 484bdc8..00110a1 100644
--- a/lib/php/Savvy.php
+++ b/lib/php/Savvy.php
@@ -836,7 +836,7 @@ class Savvy
         }
     }
     
-    protected function fetch($mixed, $template = null)
+    public function fetch($mixed, $template = null)
     {
         if ($template) {
             $this->template = $template;
diff --git a/lib/php/UNL/Services/CourseApproval.php b/lib/php/UNL/Services/CourseApproval.php
new file mode 100644
index 0000000..37fec84
--- /dev/null
+++ b/lib/php/UNL/Services/CourseApproval.php
@@ -0,0 +1,76 @@
+<?php
+class UNL_Services_CourseApproval
+{
+    /**
+     * The caching service used.
+     * 
+     * @var UNL_Services_CourseApproval_CachingService
+     */
+    protected static $_cache;
+    
+    /**
+     * The XCRI service used.
+     * 
+     * @var UNL_Services_CourseApproval_XCRIService
+     */
+    protected static $_xcri;
+    
+    /**
+     * Get the static caching service
+     * 
+     * @return UNL_Services_CourseApproval_CachingService
+     */
+    public static function getCachingService()
+    {
+        if (!isset(self::$_cache)) {
+            try {
+                self::setCachingService(new UNL_Services_CourseApproval_CachingService_CacheLite());
+            } catch(Exception $e) {
+                self::setCachingService(new UNL_Services_CourseApproval_CachingService_Null());
+            }
+        }
+        
+        return self::$_cache;
+    }
+    
+    /**
+     * Set the static caching service
+     * 
+     * @param UNL_Services_CourseApproval_CachingService $service The caching service to use
+     * 
+     * @return UNL_Services_CourseApproval_CachingService
+     */
+    public static function setCachingService(UNL_Services_CourseApproval_CachingService $service)
+    {
+        self::$_cache = $service;
+        
+        return self::$_cache;
+    }
+    
+    /**
+     * Gets the XCRI service we're subscribed to.
+     * 
+     * @return UNL_Services_CourseApproval_XCRIService
+     */
+    public static function getXCRIService()
+    {
+        if (!isset(self::$_xcri)) {
+            self::setXCRIService(new UNL_Services_CourseApproval_XCRIService_creq());
+        }
+        
+        return self::$_xcri;
+    }
+    
+    /**
+     * Set the static XCRI service
+     * 
+     * @param UNL_Services_CourseApproval_XCRIService $xcri The XCRI service object
+     * 
+     * @return UNL_Services_CourseApproval_XCRIService
+     */
+    public static function setXCRIService(UNL_Services_CourseApproval_XCRIService $xcri)
+    {
+        self::$_xcri = $xcri;
+        return self::$_xcri;
+    }
+}
diff --git a/lib/php/UNL/Services/CourseApproval/CachingService.php b/lib/php/UNL/Services/CourseApproval/CachingService.php
new file mode 100644
index 0000000..e16cee4
--- /dev/null
+++ b/lib/php/UNL/Services/CourseApproval/CachingService.php
@@ -0,0 +1,6 @@
+<?php 
+interface UNL_Services_CourseApproval_CachingService
+{
+    function save($key, $data);
+    function get($key);
+}
diff --git a/lib/php/UNL/Services/CourseApproval/CachingService/CacheLite.php b/lib/php/UNL/Services/CourseApproval/CachingService/CacheLite.php
new file mode 100644
index 0000000..e5ae58a
--- /dev/null
+++ b/lib/php/UNL/Services/CourseApproval/CachingService/CacheLite.php
@@ -0,0 +1,28 @@
+<?php 
+class UNL_Services_CourseApproval_CachingService_CacheLite implements UNL_Services_CourseApproval_CachingService
+{
+    protected $cache;
+    
+    function __construct()
+    {
+        @include_once 'Cache/Lite.php';
+        if (!class_exists('Cache_Lite')) {
+            throw new Exception('Unable to include Cache_Lite, is it installed?');
+        }
+        $options = array('lifeTime'=>604800); //one week lifetime
+        $this->cache = new Cache_Lite();
+    }
+    
+    function save($key, $data)
+    {
+        return $this->cache->save($data, $key, 'ugbulletin');
+    }
+    
+    function get($key)
+    {
+        if ($data = $this->cache->get($key, 'ugbulletin')) {
+            return $data;
+        }
+        return false;
+    }
+}
diff --git a/lib/php/UNL/Services/CourseApproval/CachingService/Null.php b/lib/php/UNL/Services/CourseApproval/CachingService/Null.php
new file mode 100644
index 0000000..d0339eb
--- /dev/null
+++ b/lib/php/UNL/Services/CourseApproval/CachingService/Null.php
@@ -0,0 +1,15 @@
+<?php
+class UNL_Services_CourseApproval_CachingService_Null implements UNL_Services_CourseApproval_CachingService
+{
+    function get($key)
+    {
+        // Expired cache always.
+        return false;
+    }
+    
+    function save($key, $data)
+    {
+        // Make it appear as though it was saved.
+        return true;
+    }
+}
diff --git a/lib/php/UNL/Services/CourseApproval/College.php b/lib/php/UNL/Services/CourseApproval/College.php
new file mode 100644
index 0000000..7e646d7
--- /dev/null
+++ b/lib/php/UNL/Services/CourseApproval/College.php
@@ -0,0 +1,5 @@
+<?php
+class UNL_Services_CourseApproval_College
+{
+    public $areas_of_study;
+}
diff --git a/lib/php/UNL/Services/CourseApproval/Course.php b/lib/php/UNL/Services/CourseApproval/Course.php
new file mode 100644
index 0000000..36d4e7a
--- /dev/null
+++ b/lib/php/UNL/Services/CourseApproval/Course.php
@@ -0,0 +1,262 @@
+<?php 
+class UNL_Services_CourseApproval_Course
+{
+    
+    /**
+     * The internal object
+     * 
+     * @var SimpleXMLElement
+     */
+    protected $_internal;
+
+    /**
+     * Collection of course codes
+     * 
+     * @var UNL_Services_CourseApproval_Course_Codes
+     */
+    public $codes;
+    
+    protected $_getMap = array('credits'         => 'getCredits',
+                               'dfRemoval'       => 'getDFRemoval',
+                               'campuses'        => 'getCampuses',
+                               'deliveryMethods' => 'getDeliveryMethods',
+                               'termsOffered'    => 'getTermsOffered',
+                               'activities'      => 'getActivities',
+                               'aceOutcomes'     => 'getACEOutcomes',
+                               );
+
+    protected $ns_prefix = '';
+
+    function __construct(SimpleXMLElement $xml)
+    {
+        $this->_internal = $xml;
+        //Fetch all namespaces
+        $namespaces = $this->_internal->getNamespaces(true);
+        if (isset($namespaces[''])
+            && $namespaces[''] == 'http://courseapproval.unl.edu/courses') {
+            $this->_internal->registerXPathNamespace('default', $namespaces['']);
+            $this->ns_prefix = 'default:';
+
+            //Register the rest with their prefixes
+            foreach ($namespaces as $prefix => $ns) {
+                $this->_internal->registerXPathNamespace($prefix, $ns);
+            }
+        }
+        $this->codes = new UNL_Services_CourseApproval_Course_Codes($this->_internal->courseCodes->children());
+    }
+    
+    function __get($var)
+    {
+        if (array_key_exists($var, $this->_getMap)) {
+            return $this->{$this->_getMap[$var]}();
+        }
+
+        if (isset($this->_internal->$var)
+            && count($this->_internal->$var->children())) {
+            if (isset($this->_internal->$var->div)) {
+                return str_replace(' xmlns="http://www.w3.org/1999/xhtml"', 
+                                   '',
+                                   html_entity_decode($this->_internal->$var->div->asXML()));
+            }
+        }
+
+        return (string)$this->_internal->$var;
+    }
+    
+    function __isset($var)
+    {
+        $elements = $this->_internal->xpath($this->ns_prefix.$var);
+        if (count($elements)) {
+            return true;
+        }
+        return false;
+    }
+    
+    function getCampuses()
+    {
+        return $this->getArray('campuses');
+    }
+    
+    function getTermsOffered()
+    {
+        return $this->getArray('termsOffered');
+    }
+    
+    function getDeliveryMethods()
+    {
+        return $this->getArray('deliveryMethods');
+    }
+    
+    function getActivities()
+    {
+        return new UNL_Services_CourseApproval_Course_Activities($this->_internal->activities->children());
+    }
+    
+    function getACEOutcomes()
+    {
+        return $this->getArray('aceOutcomes');
+    }
+    
+    function getArray($var)
+    {
+        $results = array();
+        foreach ($this->_internal->$var->children() as $el) {
+            $results[] = (string)$el;
+        }
+        return $results;
+    }
+    
+    /**
+     * Gets the types of credits offered for this course.
+     * 
+     * @return UNL_Services_CourseApproval_Course_Credits
+     */
+    function getCredits()
+    {
+        return new UNL_Services_CourseApproval_Course_Credits($this->_internal->credits->children());
+    }
+    
+    /**
+     * Checks whether this course can remove a previous grade of D or F for the same course.
+     * 
+     * @return bool
+     */
+    function getDFRemoval()
+    {
+        if ($this->_internal->dfRemoval == 'true') {
+            return true;
+        }
+        
+        return false;
+    }
+    
+    /**
+     * Verifies that the course number is in the correct format.
+     * 
+     * @param $number The course number eg 201H, 4004I
+     * @param $parts  Array of matched parts
+     * 
+     * @return bool
+     */
+    public static function validCourseNumber($number, &$parts = null)
+    {
+        $matches = array();
+        if (preg_match('/^([\d]?[\d]{2,3})([A-Z])?$/i', $number, $matches)) {
+            $parts['courseNumber'] = $matches[1];
+            if (isset($matches[2])) {
+                $parts['courseLetter'] = $matches[2];
+            }
+            return true;
+        }
+        
+        return false;
+    }
+    
+    public static function courseNumberFromCourseCode(SimpleXMLElement $xml)
+    {
+        $number = (string)$xml->courseNumber;
+        if (isset($xml->courseLetter)) {
+            $number .= (string)$xml->courseLetter;
+        }
+        return $number;
+    }
+
+    public static function getListingGroups(SimpleXMLElement $xml)
+    {
+        $groups = array();
+        if (isset($xml->courseGroup)) {
+            foreach ($xml->courseGroup as $group) {
+                $groups[] = $group;
+            }
+        }
+        return $groups;
+    }
+    
+    function getHomeListing()
+    {
+        $home_listing = $this->_internal->xpath($this->ns_prefix.'courseCodes/'.$this->ns_prefix.'courseCode[@type="home listing"]');
+        if ($home_listing === false
+            || count($home_listing) < 1) {
+            return false;
+        }
+        $number = UNL_Services_CourseApproval_Course::courseNumberFromCourseCode($home_listing[0]);
+        return new UNL_Services_CourseApproval_Listing($home_listing[0]->subject,
+                                                     $number,
+                                                     UNL_Services_CourseApproval_Course::getListingGroups($home_listing[0]));
+    }
+
+    /**
+     * Search for subsequent courses
+     * 
+     * (reverse prereqs)
+     *
+     * @param UNL_Services_CourseApproval_Search $search_driver
+     *
+     * @return UNL_Services_CourseApproval_Courses
+     */
+    function getSubsequentCourses($search_driver = null)
+    {
+        $searcher = new UNL_Services_CourseApproval_Search($search_driver);
+
+        $query = $this->getHomeListing()->subjectArea.' '.$this->getHomeListing()->courseNumber;
+        return $searcher->byPrerequisite($query);
+    }
+    
+    function asXML()
+    {
+        return $this->_internal->asXML();
+    }
+
+    public static function getPossibleActivities()
+    {
+        //Value=>Description
+        return array('lec' => 'Lecture',
+                     'lab' => 'Lab',
+                     'stu' => 'Studio',
+                     'fld' => 'Field',
+                     'quz' => 'Quiz',
+                     'rct' => 'Recitation',
+                     'ind' => 'Independent Study',
+                     'psi' => 'Personalized System of Instruction');
+    }
+
+    public static function getPossibleAceOutcomes()
+    {
+        //Value=>Description
+        return array(1  => 'ACE 1',
+                     2  => 'ACE 2',
+                     3  => 'ACE 3',
+                     4  => 'ACE 4',
+                     5  => 'ACE 5',
+                     6  => 'ACE 6',
+                     7  => 'ACE 7',
+                     8  => 'ACE 8',
+                     9  => 'ACE 9',
+                     10 => 'ACE 10');
+    }
+
+    public static function getPossibleCampuses()
+    {
+        //Value=>Description
+        return array('UNL'  => 'University of Nebraska Lincoln',
+                     'UNO'  => 'University of Nebraska Omaha',
+                     'UNMC' => 'University of Nebraska Medical University',
+                     'UNK'  => 'University of Nebraska Kearney');
+    }
+
+    public static function getPossibleDeliveryMethods()
+    {
+        //Value=>Description
+        return array('Classroom'      => 'Classroom',
+                     'Web'            => 'Online',
+                     'Correspondence' => 'Correspondence');
+    }
+
+    public static function getPossibleTermsOffered()
+    {
+        //Value=>Description
+        return array('Fall'   => 'Fall',
+                     'Spring' => 'Spring',
+                     'Summer' => 'Summer');
+    }
+}
diff --git a/lib/php/UNL/Services/CourseApproval/Course/Activities.php b/lib/php/UNL/Services/CourseApproval/Course/Activities.php
new file mode 100644
index 0000000..335972c
--- /dev/null
+++ b/lib/php/UNL/Services/CourseApproval/Course/Activities.php
@@ -0,0 +1,55 @@
+<?php 
+class UNL_Services_CourseApproval_Course_Activities implements Countable, Iterator
+{
+    protected $_xmlActivities;
+    
+    protected $_currentActivity = 0;
+    
+    function __construct(SimpleXMLElement $xml)
+    {
+        $this->_xmlActivities = $xml;
+    }
+    
+    function current()
+    {
+        return $this->_xmlActivities[$this->_currentActivity];
+    }
+    
+    function next()
+    {
+        ++$this->_currentActivity;
+    }
+    
+    function rewind()
+    {
+        $this->_currentActivity = 0;
+    }
+    
+    function valid()
+    {
+        if ($this->_currentActivity >= $this->count()) {
+            return false;
+        }
+        return true;
+    }
+    
+    function key()
+    {
+        return (string)$this->current()->type;
+    }
+    
+    function count()
+    {
+        return count($this->_xmlActivities);
+    }
+
+    public static function getFullDescription($activity)
+    {
+        $activities = UNL_Services_CourseApproval_Course::getPossibleActivities();
+        if (!isset($activities[$activity])) {
+            throw new Exception('Unknown activity type! '.$activity);
+        }
+
+        return $activities[$activity];
+    }
+}
diff --git a/lib/php/UNL/Services/CourseApproval/Course/Codes.php b/lib/php/UNL/Services/CourseApproval/Course/Codes.php
new file mode 100644
index 0000000..15e59e1
--- /dev/null
+++ b/lib/php/UNL/Services/CourseApproval/Course/Codes.php
@@ -0,0 +1,46 @@
+<?php
+/**
+ * Collection of course codes for this course
+ *
+ * @author Brett Bieber <brett.bieber@gmail.com>
+ */
+class UNL_Services_CourseApproval_Course_Codes extends ArrayIterator
+{
+
+	/**
+	 * Array of results, usually from an xpath query
+	 * 
+	 * @param array $courseCodes
+	 */
+    function __construct($courseCodes)
+    {
+        $codes = array();
+        foreach ($courseCodes as $code) {
+            $codes[] = $code;
+        }
+        parent::__construct($codes);
+    }
+
+    /**
+     * Get the listing
+     *
+     * @return UNL_Services_CourseApproval_Listing
+     */
+    function current()
+    {
+        $number = UNL_Services_CourseApproval_Course::courseNumberFromCourseCode(parent::current());
+        return new UNL_Services_CourseApproval_Listing(parent::current()->subject,
+                                                     $number,
+                                                     UNL_Services_CourseApproval_Course::getListingGroups(parent::current()));
+    }
+
+    /**
+     * Get the course number
+     * 
+     * @return string course number
+     */
+    function key()
+    {
+        return $this->current()->courseNumber;
+    }
+}
diff --git a/lib/php/UNL/Services/CourseApproval/Course/Credits.php b/lib/php/UNL/Services/CourseApproval/Course/Credits.php
new file mode 100644
index 0000000..341f131
--- /dev/null
+++ b/lib/php/UNL/Services/CourseApproval/Course/Credits.php
@@ -0,0 +1,76 @@
+<?php 
+
+class UNL_Services_CourseApproval_Course_Credits implements Countable, ArrayAccess
+{
+    protected $_xmlCredits;
+    
+    protected $_currentCredit = 0;
+    
+    function __construct(SimpleXMLElement $xml)
+    {
+        $this->_xmlCredits = $xml;
+    }
+    
+    function current()
+    {
+        return $this->_xmlCredits[$this->_currentCredit];
+    }
+    
+    function next()
+    {
+        ++$this->_currentCredit;
+    }
+    
+    function rewind()
+    {
+        $this->_currentCredit = 0;
+    }
+    
+    function valid()
+    {
+        if ($this->_currentCredit >= $this->count()) {
+            return false;
+        }
+        return true;
+    }
+    
+    function count()
+    {
+        return count($this->_xmlCredits);
+    }
+    
+    function key()
+    {
+        $credit = $this->current();
+        return $credit['creditType'];
+    }
+    
+    function offsetExists($type)
+    {
+        foreach ($this->_xmlCredits as $credit) {
+            if ($credit['type'] == $type) {
+                return true;
+            }
+        }
+        return false;
+    }
+    
+    function offsetGet($type)
+    {
+        foreach ($this->_xmlCredits as $credit) {
+            if ($credit['type'] == $type) {
+                return (int)$credit;
+            }
+        }
+    }
+    
+    function offsetSet($type, $var)
+    {
+        throw new Exception('Not available.');
+    }
+    
+    function offsetUnset($type)
+    {
+        throw new Exception('Not available.');
+    }
+}
diff --git a/lib/php/UNL/Services/CourseApproval/Courses.php b/lib/php/UNL/Services/CourseApproval/Courses.php
new file mode 100644
index 0000000..f8d8845
--- /dev/null
+++ b/lib/php/UNL/Services/CourseApproval/Courses.php
@@ -0,0 +1,20 @@
+<?php
+
+class UNL_Services_CourseApproval_Courses extends ArrayIterator
+{
+    
+    function __construct($courses)
+    {
+        parent::__construct($courses);
+    }
+    
+    /**
+     * Get the current course
+     * 
+     * @return UNL_Services_CourseApproval_Course
+     */
+    function current()
+    {
+        return new UNL_Services_CourseApproval_Course(parent::current());
+    }
+}
\ No newline at end of file
diff --git a/lib/php/UNL/Services/CourseApproval/Filter/ExcludeGraduateCourses.php b/lib/php/UNL/Services/CourseApproval/Filter/ExcludeGraduateCourses.php
new file mode 100644
index 0000000..0d256a4
--- /dev/null
+++ b/lib/php/UNL/Services/CourseApproval/Filter/ExcludeGraduateCourses.php
@@ -0,0 +1,15 @@
+<?php
+class UNL_Services_CourseApproval_Filter_ExcludeGraduateCourses extends FilterIterator
+{
+    function accept()
+    {
+        $course = $this->getInnerIterator()->current();
+        foreach ($course->codes as $listing) {
+            if ($listing->courseNumber < 500) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/lib/php/UNL/Services/CourseApproval/Filter/ExcludeUndergraduateCourses.php b/lib/php/UNL/Services/CourseApproval/Filter/ExcludeUndergraduateCourses.php
new file mode 100644
index 0000000..dfb6872
--- /dev/null
+++ b/lib/php/UNL/Services/CourseApproval/Filter/ExcludeUndergraduateCourses.php
@@ -0,0 +1,15 @@
+<?php
+class UNL_Services_CourseApproval_Filter_ExcludeUndergraduateCourses extends FilterIterator
+{
+    function accept()
+    {
+        $course = $this->getInnerIterator()->current();
+        foreach ($course->codes as $listing) {
+            if ($listing->courseNumber >= 500) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/lib/php/UNL/Services/CourseApproval/Listing.php b/lib/php/UNL/Services/CourseApproval/Listing.php
new file mode 100644
index 0000000..874ce3e
--- /dev/null
+++ b/lib/php/UNL/Services/CourseApproval/Listing.php
@@ -0,0 +1,57 @@
+<?php 
+class UNL_Services_CourseApproval_Listing
+{
+    
+    /**
+     * Internal subject area object
+     * 
+     * @var UNL_Services_CourseApproval_SubjectArea
+     */
+    protected $_subjectArea;
+    
+    /**
+     * The subject area for this listing eg ACCT
+     * 
+     * @var string
+     */
+    public $subjectArea;
+    
+    /**
+     * The course number eg 201
+     * 
+     * @var string|int
+     */
+    public $courseNumber;
+    
+    public $groups = array();
+    
+    function __construct($subject, $number, $groups = array())
+    {
+        $this->subjectArea  = $subject;
+        $this->courseNumber = $number;
+        $this->groups       = $groups;
+    }
+    
+    function __get($var)
+    {
+        if ($var == 'course') {
+            if (!isset($this->_subjectArea)) {
+                $this->_subjectArea = new UNL_Services_CourseApproval_SubjectArea($this->subjectArea);
+            }
+            return $this->_subjectArea->courses[$this->courseNumber];
+        }
+        // Delegate to the course
+        return $this->course->$var;
+    }
+    
+    function __isset($var)
+    {
+        // Delegate to the course
+        return isset($this->course->$var);
+    }
+
+    function hasGroups()
+    {
+        return count($this->groups)? true : false;
+    }
+}
diff --git a/lib/php/UNL/Services/CourseApproval/Search.php b/lib/php/UNL/Services/CourseApproval/Search.php
new file mode 100644
index 0000000..4c0918d
--- /dev/null
+++ b/lib/php/UNL/Services/CourseApproval/Search.php
@@ -0,0 +1,93 @@
+<?php
+class UNL_Services_CourseApproval_Search extends UNL_Services_CourseApproval_SearchInterface
+{
+    /**
+     * The driver that performs the searches
+     * @var UNL_Services_CourseApproval_SearchInterface
+     */
+    public $driver;
+
+    function __construct(UNL_Services_CourseApproval_SearchInterface $driver = null)
+    {
+        if (!isset($driver)) {
+            $this->driver = new UNL_Services_CourseApproval_SearchInterface_XPath();
+        } else {
+            $this->driver = $driver;
+        }
+    }
+
+    /**
+     * Combine two queries into one which will return the intersect
+     *
+     * @return string
+     */
+    function intersectQuery($query1, $query2)
+    {
+        return $this->driver->intersectQuery($query1, $query2);
+    }
+
+    function aceQuery($ace)
+    {
+        return $this->driver->aceQuery($ace);
+    }
+    function aceAndNumberPrefixQuery($number)
+    {
+        return $this->driver->aceAndNumberPrefixQuery($number);
+    }
+    function subjectAndNumberQuery($subject, $number, $letter = null)
+    {
+        return $this->driver->subjectAndNumberQuery($subject, $number, $letter);
+    }
+    function subjectAndNumberPrefixQuery($subject, $number)
+    {
+        return $this->driver->subjectAndNumberPrefixQuery($subject, $number);
+    }
+    function subjectAndNumberSuffixQuery($subject, $number)
+    {
+        return $this->driver->subjectAndNumberSuffixQuery($subject, $number);
+    }
+    function numberPrefixQuery($number)
+    {
+        return $this->driver->numberPrefixQuery($number);
+    }
+    function numberSuffixQuery($number)
+    {
+        return $this->driver->numberSuffixQuery($number);
+    }
+    function honorsQuery()
+    {
+        return $this->driver->honorsQuery();
+    }
+    function titleQuery($title)
+    {
+        return $this->driver->titleQuery($title);
+    }
+    function subjectAreaQuery($subject)
+    {
+        return $this->driver->subjectAreaQuery($subject);
+    }
+    function getQueryResult($query, $offset = 0, $limit = -1)
+    {
+        return $this->driver->getQueryResult($query, $offset, $limit);
+    }
+    function numberQuery($number, $letter = null)
+    {
+        return $this->driver->numberQuery($number, $letter);
+    }
+    function creditQuery($credits)
+    {
+        return $this->driver->creditQuery($credits);
+    }
+    function prerequisiteQuery($prereq)
+    {
+        return $this->driver->prerequisiteQuery($prereq);
+    }
+    function undergraduateQuery()
+    {
+        return $this->driver->undergraduateQuery();
+    }
+    function graduateQuery()
+    {
+        return $this->driver->graduateQuery();
+    }
+}
\ No newline at end of file
diff --git a/lib/php/UNL/Services/CourseApproval/Search/Results.php b/lib/php/UNL/Services/CourseApproval/Search/Results.php
new file mode 100644
index 0000000..a08406d
--- /dev/null
+++ b/lib/php/UNL/Services/CourseApproval/Search/Results.php
@@ -0,0 +1,25 @@
+<?php
+class UNL_Services_CourseApproval_Search_Results extends UNL_Services_CourseApproval_Courses implements Countable
+{
+    protected $total;
+    
+    function __construct($results, $offset = 0, $limit = -1)
+    {
+        $this->total = count($results);
+
+        if (
+                $limit > 0
+            &&
+                $this->total < $offset + $limit
+            ) {
+            $results = array_slice($results, $offset, $limit);
+        }
+
+        parent::__construct($results);
+    }
+    
+    function count()
+    {
+        return $this->total;
+    }
+}
\ No newline at end of file
diff --git a/lib/php/UNL/Services/CourseApproval/SearchInterface.php b/lib/php/UNL/Services/CourseApproval/SearchInterface.php
new file mode 100644
index 0000000..bd1db3c
--- /dev/null
+++ b/lib/php/UNL/Services/CourseApproval/SearchInterface.php
@@ -0,0 +1,137 @@
+<?php
+abstract class UNL_Services_CourseApproval_SearchInterface
+{
+    abstract function aceQuery($ace);
+    abstract function subjectAndNumberQuery($subject, $number, $letter = null);
+    abstract function subjectAndNumberPrefixQuery($subject, $number);
+    abstract function subjectAndNumberSuffixQuery($subject, $number);
+    abstract function numberPrefixQuery($number);
+    abstract function numberSuffixQuery($number);
+    abstract function honorsQuery();
+    abstract function titleQuery($title);
+    abstract function subjectAreaQuery($subject);
+    abstract function numberQuery($number, $letter = null);
+    abstract function creditQuery($credits);
+    abstract function prerequisiteQuery($prereq);
+    abstract function intersectQuery($query1, $query2);
+    abstract function graduateQuery();
+    abstract function undergraduateQuery();
+
+    function filterQuery($query)
+    {
+        return trim($query);
+    }
+
+    public function byTitle($query, $offset = 0, $limit = -1)
+    {
+        $query = $this->titleQuery($this->filterQuery($query));
+
+        return $this->getQueryResult($query, $offset, $limit);
+    }
+
+    public function byNumber($query, $offset = 0, $limit = -1)
+    {
+        $query = $this->numberQuery($this->filterQuery($query));
+
+        return $this->getQueryResult($query, $offset, $limit);
+    }
+
+    public function bySubject($query, $offset = 0, $limit = -1)
+    {
+        $query = $this->subjectAreaQuery($this->filterQuery($query));
+
+        return $this->getQueryResult($query, $offset, $limit);
+    }
+
+    public function byPrerequisite($query, $offset = 0, $limit = -1)
+    {
+        $query = $this->prerequisiteQuery($query);
+
+        return $this->getQueryResult($query, $offset, $limit);
+    }
+
+    public function graduateCourses($offset = 0, $limit = -1)
+    {
+        $query = $this->graduateQuery();
+
+        return $this->getQueryResult($query, $offset, $limit);
+    }
+
+    public function undergraduateCourses($offset = 0, $limit = -1)
+    {
+        $query = $this->undergraduateQuery();
+
+        return $this->getQueryResult($query, $offset, $limit);
+    }
+
+    public function byAny($query, $offset = 0, $limit = -1)
+    {
+        $query = $this->filterQuery($query);
+
+        switch (true) {
+            case preg_match('/([\d]+)\scredits?/i', $query, $match):
+                // Credit search
+                $query = $this->creditQuery($match[1]);
+                break;
+            case preg_match('/^ace\s*:?\s*([0-9])(X+|\*+)/i', $query, $matches):
+                // ACE course, and number range, eg: ACE 2XX
+                $query = $this->aceAndNumberPrefixQuery($matches[1]);
+                break;
+            case preg_match('/^ace\s*:?\s*(10|[1-9])$/i', $query, $match):
+                // ACE outcome number
+                $query = $this->aceQuery($match[1]);
+                break;
+            case preg_match('/^([A-Z]{3,4})\s+([0-9])(X+|\*+)?$/i', $query, $matches):
+                // Course subject and number range, eg: MRKT 3XX
+                $subject = strtoupper($matches[1]);
+
+                $query = $this->subjectAndNumberPrefixQuery($subject, $matches[2]);
+                break;
+            case preg_match('/^([A-Z]{3,4})\s+(X+|\*+)([0-9]+)$/i', $query, $matches):
+                // Course subject and number suffix, eg: MUDC *41
+                $subject = strtoupper($matches[1]);
+
+                $query = $this->subjectAndNumberSuffixQuery($subject, $matches[3]);
+                break;
+            case preg_match('/^([A-Z]{3,4})\s+([\d]?[\d]{2,3})([A-Z])?:?.*$/i', $query, $matches):
+                // Course subject code and number
+                $subject = strtoupper($matches[1]);
+                $letter = null;
+                if (isset($matches[3])) {
+                    $letter = $matches[3];
+                }
+                $query = $this->subjectAndNumberQuery($subject, $matches[2], $letter);
+                break;
+            case preg_match('/^([0-9])(X+|\*+)?$/i', $query, $match):
+                // Course number range
+                $query = $this->numberPrefixQuery($match[1]);
+                break;
+            case preg_match('/^(X+|\*+)([0-9]+)?$/i', $query, $match):
+                // Course number suffix
+                $query = $this->numberSuffixQuery($match[1]);
+                break;
+            case preg_match('/^([\d]?[\d]{2,3})([A-Z])?(\*+)?$/i', $query, $matches):
+
+                $letter = null;
+                if (isset($matches[2])) {
+                    $letter = $matches[2];
+                }
+                $query = $this->numberQuery($matches[1], $letter);
+                break;
+            case preg_match('/^([A-Z]{3,4})(\s*:\s*.*)?(\s[Xx]+|\s\*+)?$/', $query, $matches):
+                // Subject code search
+                $query = $this->subjectAreaQuery($matches[1]);
+                break;
+            case preg_match('/^honors$/i', $query):
+                $query = $this->honorsQuery();
+                break;
+            default:
+                // Do a title text search
+                $query = $this->titleQuery($query);
+        }
+
+        return $this->getQueryResult($query, $offset, $limit);
+    }
+
+    abstract function getQueryResult($query, $offset = 0, $limit = -1);
+}
\ No newline at end of file
diff --git a/lib/php/UNL/Services/CourseApproval/SearchInterface/XPath.php b/lib/php/UNL/Services/CourseApproval/SearchInterface/XPath.php
new file mode 100644
index 0000000..3119bfb
--- /dev/null
+++ b/lib/php/UNL/Services/CourseApproval/SearchInterface/XPath.php
@@ -0,0 +1,338 @@
+<?php
+/**
+ * 
+ * Course search driver which uses XPath queries on the course XML data
+ * 
+ * @author Brett Bieber <brett.bieber@gmail.com>
+ *
+ */
+class UNL_Services_CourseApproval_SearchInterface_XPath extends UNL_Services_CourseApproval_SearchInterface
+{
+    /**
+     * SimpleXMLElement for all courses
+     * 
+     * @var SimpleXMLElement
+     */
+    protected static $all_courses;
+
+    protected static $courses = array();
+
+    const XML_BASE = '/default:courses/default:course/';
+
+    /**
+     * Get all courses in a SimpleXMLElement
+     * 
+     * @return SimpleXMLElement
+     */
+    protected static function getCourses()
+    {
+        if (!isset(self::$all_courses)) {
+            $xml = UNL_Services_CourseApproval::getXCRIService()->getAllCourses();
+            self::$all_courses = new SimpleXMLElement($xml);
+
+            //Fetch all namespaces
+            $namespaces = self::$all_courses->getNamespaces(true);
+            self::$all_courses->registerXPathNamespace('default', $namespaces['']);
+
+            //Register the rest with their prefixes
+            foreach ($namespaces as $prefix => $ns) {
+                self::$all_courses->registerXPathNamespace($prefix, $ns);
+            }
+        }
+
+        return self::$all_courses;
+    }
+
+    /**
+     * Get the XML for a specific subject area as a SimpleXMLElement
+     * 
+     * @param string $subjectarea Course subject area e.g. CSCE
+     * 
+     * @return SimpleXMLElement
+     */
+    protected static function getSubjectAreaCourses($subjectarea)
+    {
+        if (!isset(self::$courses[$subjectarea])) {
+            $xml = UNL_Services_CourseApproval::getXCRIService()->getSubjectArea($subjectarea);
+            self::$courses[$subjectarea] = new SimpleXMLElement($xml);
+
+            //Fetch all namespaces
+            $namespaces = self::$courses[$subjectarea]->getNamespaces(true);
+            self::$courses[$subjectarea]->registerXPathNamespace('default', $namespaces['']);
+
+            //Register the rest with their prefixes
+            foreach ($namespaces as $prefix => $ns) {
+                self::$courses[$subjectarea]->registerXPathNamespace($prefix, $ns);
+            }
+        }
+
+        return self::$courses[$subjectarea];
+    }
+
+    /**
+     * Utility method to trim out characters which aren't safe for XPath queries
+     * 
+     * @param string $query Search string
+     * 
+     * @return string
+     */
+    function filterQuery($query)
+    {
+        $query = trim($query);
+
+        $query = str_replace(array('/', '"', '\'', '*'), ' ', $query);
+        return $query;
+    }
+
+    /**
+     * Set the courses data to perform searches on
+     * 
+     * @param SimpleXMLElement $courses Set of courses to search
+     */
+    public function setCourses(SimpleXMLElement $courses)
+    {
+        self::$courses = $courses;
+    }
+
+    /**
+     * Construct a query for courses matching an Achievement Centered Education (ACE) number
+     * 
+     * @param string|int $ace Achievement Centered Education (ACE) number, e.g. 1-10
+     * 
+     * @return string XPath query
+     */
+    function aceQuery($ace)
+    {
+        return "default:aceOutcomes[default:slo='$ace']/parent::*";
+    }
+
+    /**
+     * Construct a query for Achievement Centered Education (ACE) courses which
+     * have a course number prefix
+     * 
+     * @param string|int $number Number prefix, e.g. 1 for 100 level ACE courses
+     * 
+     * @return string XPath query
+     */
+    function aceAndNumberPrefixQuery($number)
+    {
+        return "default:courseCodes/default:courseCode/default:courseNumber[starts-with(., '$number')]/parent::*/parent::*/parent::*/default:aceOutcomes/parent::*";
+    }
+
+    /**
+     * Construct a query for courses matching a subject and number prefix
+     * 
+     * @param string     $subject Subject code, e.g. CSCE
+     * @param string|int $number  Course number prefix, e.g. 2 for 200 level courses
+     * 
+     * @return string XPath query
+     */
+    function subjectAndNumberPrefixQuery($subject, $number)
+    {
+        return "default:courseCodes/default:courseCode[starts-with(default:courseNumber, '$number') and default:subject='$subject']/parent::*/parent::*";
+    }
+
+    /**
+     * Construct a query for courses matching a subject and number suffix
+     * 
+     * @param string     $subject Subject code, e.g. MUDC
+     * @param string|int $number  Course number prefix, e.g. 41 for 241, 341, 441
+     * 
+     * @return string XPath query
+     */
+    function subjectAndNumberSuffixQuery($subject, $number)
+    {
+        return "default:courseCodes/default:courseCode[('$number' = substring(default:courseNumber,string-length(default:courseNumber)-string-length('$number')+1)) and default:subject='$subject']/parent::*/parent::*";
+    }
+
+    /**
+     * Construct a query for courses matching a number prefix
+     * 
+     * @param string|int $number  Course number prefix, e.g. 2 for 200 level courses
+     * 
+     * @return string XPath query
+     */
+    function numberPrefixQuery($number)
+    {
+        return "default:courseCodes/default:courseCode/default:courseNumber[starts-with(., '$number')]/parent::*/parent::*/parent::*";
+    }
+
+    /**
+     * Construct a query for courses matching a number suffix
+     * 
+     * @param string|int $number  Course number suffix, e.g. 41 for 141, 241, 341 etc
+     * 
+     * @return string XPath query
+     */
+    function numberSuffixQuery($number)
+    {
+        return "default:courseCodes/default:courseCode/default:courseNumber['$number' = substring(., string-length(.)-string-length('$number')+1)]/parent::*/parent::*/parent::*";
+    }
+
+    /**
+     * Construct a query for honors courses
+     * 
+     * @return string XPath query
+     */
+    function honorsQuery()
+    {
+        return "default:courseCodes/default:courseCode[default:courseLetter='H']/parent::*/parent::*";
+    }
+
+    /**
+     * Construct a query for courses with a title matching the query
+     * 
+     * @param string $title Portion of the title of the course
+     * 
+     * @return string XPath query
+     */
+    function titleQuery($title)
+    {
+        return 'default:title['.$this->caseInsensitiveXPath($title).']/parent::*';
+    }
+
+    /**
+     * Construct a query for courses matching a subject area
+     * 
+     * @param string $subject Subject code, e.g. CSCE
+     * 
+     * @return string XPath query
+     */
+    function subjectAreaQuery($subject)
+    {
+        return "default:courseCodes/default:courseCode[default:subject='$subject']/parent::*/parent::*";
+    }
+
+    /**
+     * Construct a query for courses matching a subject and number
+     * 
+     * @param string     $subject Subject code, e.g. CSCE
+     * @param string|int $number  Course number, e.g. 201
+     * @param string     $letter  Optional course letter, e.g. H
+     * 
+     * @return string XPath query
+     */
+    function subjectAndNumberQuery($subject, $number, $letter = null)
+    {
+        return "default:courseCodes/default:courseCode[default:courseNumber='$number'{$this->courseLetterCheck($letter)} and default:subject='$subject']/parent::*/parent::*";
+    }
+
+    /**
+     * Construct a query for courses matching a number
+     * 
+     * @param string|int $number Course number, e.g. 201
+     * @param string     $letter Optional course letter, e.g. H
+     * 
+     * @return string XPath query
+     */
+    function numberQuery($number, $letter = null)
+    {
+        return "default:courseCodes/default:courseCode[default:courseNumber='$number'{$this->courseLetterCheck($letter)}]/parent::*/parent::*";
+    }
+
+    /**
+     * Construct a query for undergraduate courses
+     * 
+     * @return string XPath query
+     */
+    function undergraduateQuery()
+    {
+        return "default:courseCodes/default:courseCode[default:courseNumber<'500']/parent::*/parent::*";
+    }
+
+    /**
+     * Construct a query for graduate courses
+     * 
+     * @return string XPath query
+     */
+    function graduateQuery()
+    {
+        return "default:courseCodes/default:courseCode[default:courseNumber>='500']/parent::*/parent::*";
+    }
+
+    /**
+     * Construct part of an XPath query for matching a course letter
+     *
+     * @param string $letter Letter, e.g. H
+     *
+     * @return string
+     */
+    protected function courseLetterCheck($letter = null)
+    {
+        $letter_check = '';
+        if (!empty($letter)) {
+            $letter_check = " and (default:courseLetter='".strtoupper($letter)."' or default:courseLetter='".strtolower($letter)."')";
+        }
+        return $letter_check;
+    }
+
+    /**
+     * Construct a query for courses with the required number of credits
+     * 
+     * @param string|int $credits Course credits
+     * 
+     * @return string XPath query
+     */
+    function creditQuery($credits)
+    {
+        return "default:courseCredits[default:credit='$credits']/parent::*/parent::*";
+    }
+
+    /**
+     * Construct a query for courses with prerequisites matching the query
+     * 
+     * @param string $prereq Query to search prereqs for
+     * 
+     * @return string XPath query
+     */
+    function prerequisiteQuery($prereq)
+    {
+        return 'default:prerequisite['.$this->caseInsensitiveXPath($prereq).']/parent::*';
+    }
+
+    /**
+     * Convert a query to a case-insensitive XPath contains query
+     *
+     * @param string $query The query to search for
+     * 
+     * @return string
+     */
+    protected function caseInsensitiveXPath($query)
+    {
+        $query = strtolower($query);
+        return 'contains(translate(.,"ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz"),"'.$query.'")';
+    }
+
+    /**
+     * Combine two XPath queries into one which will return the intersect
+     *
+     * @return string
+     */
+    public function intersectQuery($query1, $query2)
+    {
+        return $query1 . '/' . $query2;
+    }
+
+    /**
+     * Execute the supplied query and return matching results
+     * 
+     * @param string $query  XPath compatible query
+     * @param int    $offset Offset for pagination of search results
+     * @param int    $limit  Limit for the number of results returned
+     * 
+     * @return UNL_Services_CourseApproval_Search_Results
+     */
+    function getQueryResult($query, $offset = 0, $limit = -1)
+    {
+        // prepend XPath XML Base
+        $query = self::XML_BASE . $query;
+
+        $result = self::getCourses()->xpath($query);
+
+        if ($result === false) {
+            $result = array();
+        }
+
+        return new UNL_Services_CourseApproval_Search_Results($result, $offset, $limit);
+    }
+}
\ No newline at end of file
diff --git a/lib/php/UNL/Services/CourseApproval/SubjectArea.php b/lib/php/UNL/Services/CourseApproval/SubjectArea.php
new file mode 100644
index 0000000..1f87a61
--- /dev/null
+++ b/lib/php/UNL/Services/CourseApproval/SubjectArea.php
@@ -0,0 +1,31 @@
+<?php 
+class UNL_Services_CourseApproval_SubjectArea
+{
+    public $subject;
+
+    /**
+     * Collection of courses
+     * 
+     * @var UNL_Services_CourseApproval_SubjectArea_Courses
+     */
+    public $courses;
+    
+    /**
+     * array of groups if any
+     * @var UNL_Services_CourseApproval_SubjectArea_Groups
+     */
+    public $groups;
+    
+    function __construct($subject)
+    {
+        $this->subject = $subject;
+        $this->courses = new UNL_Services_CourseApproval_SubjectArea_Courses($this);
+        $groups = new UNL_Services_CourseApproval_SubjectArea_Groups($this);
+        $this->groups = $groups->groups;
+    }
+    
+    function __toString()
+    {
+        return $this->subject;
+    }
+}
diff --git a/lib/php/UNL/Services/CourseApproval/SubjectArea/Courses.php b/lib/php/UNL/Services/CourseApproval/SubjectArea/Courses.php
new file mode 100644
index 0000000..cc83fb2
--- /dev/null
+++ b/lib/php/UNL/Services/CourseApproval/SubjectArea/Courses.php
@@ -0,0 +1,73 @@
+<?php 
+class UNL_Services_CourseApproval_SubjectArea_Courses extends ArrayIterator implements ArrayAccess
+{
+    protected $_subjectArea;
+    
+    protected $_xml;
+    
+    function __construct(UNL_Services_CourseApproval_SubjectArea $subjectarea)
+    {
+        $this->_subjectArea = $subjectarea;
+        $this->_xml = new SimpleXMLElement(UNL_Services_CourseApproval::getXCRIService()->getSubjectArea($subjectarea->subject));
+        //Fetch all namespaces
+        $namespaces = $this->_xml->getNamespaces(true);
+        $this->_xml->registerXPathNamespace('default', $namespaces['']);
+        
+        //Register the rest with their prefixes
+        foreach ($namespaces as $prefix => $ns) {
+            $this->_xml->registerXPathNamespace($prefix, $ns);
+        }
+
+        parent::__construct($this->_xml->xpath('//default:courses/default:course'));
+    }
+    
+    function current()
+    {
+        return new UNL_Services_CourseApproval_Course(parent::current());
+    }
+    
+    function offsetSet($number, $value)
+    {
+        throw new Exception('Not implemented yet');
+    }
+    
+    function offsetUnset($number)
+    {
+        throw new Exception('Not implemented yet');
+    }
+    
+    function offsetExists($number)
+    {
+        throw new Exception('Not implemented yet');
+    }
+    
+    function offsetGet($number)
+    {
+        $parts = array();
+        if (!UNL_Services_CourseApproval_Course::validCourseNumber($number, $parts)) {
+            throw new Exception('Invalid course number format '.$number);
+        }
+        
+        if (!empty($parts['courseLetter'])) {
+            $letter_check = "default:courseLetter='{$parts['courseLetter']}'";
+        } else {
+            $letter_check = 'not(default:courseLetter)';
+        }
+        
+        $xpath = "//default:courses/default:course/default:courseCodes/default:courseCode[default:subject='{$this->_subjectArea->subject}' and default:courseNumber='{$parts['courseNumber']}' and $letter_check]/parent::*/parent::*";
+        $courses = $this->_xml->xpath($xpath);
+
+        if (false === $courses
+            || !isset($courses[0])) {
+            throw new Exception('No course was found matching '.$this->_subjectArea->subject.' '.$number, 404);
+        }
+
+        if (count($courses) > 1) {
+            // Whoah whoah whoah, more than one course?
+            throw new Exception('More than one course was found matching '.$this->_subjectArea->subject.' '.$number, 500);
+        }
+
+        return new UNL_Services_CourseApproval_Course($courses[0]);
+    }
+}
+
diff --git a/lib/php/UNL/Services/CourseApproval/SubjectArea/Groups.php b/lib/php/UNL/Services/CourseApproval/SubjectArea/Groups.php
new file mode 100644
index 0000000..6b6035d
--- /dev/null
+++ b/lib/php/UNL/Services/CourseApproval/SubjectArea/Groups.php
@@ -0,0 +1,48 @@
+<?php
+class UNL_Services_CourseApproval_SubjectArea_Groups implements Countable
+{
+    /**
+     * The XCRI as a SimpleXMLElement
+     * 
+     * @var SimpleXMLElement
+     */
+    public $groups = array();
+    
+    /**
+     * subject area
+     * 
+     * @var UNL_Services_CourseApproval_SubjectArea
+     */
+    protected $_subjectArea;
+    
+    function __construct(UNL_Services_CourseApproval_SubjectArea $subjectarea)
+    {
+        $this->_subjectArea = $subjectarea;
+        $this->_xcri = new SimpleXMLElement(UNL_Services_CourseApproval::getXCRIService()->getSubjectArea($subjectarea->subject));
+        
+        //Fetch all namespaces
+        $namespaces = $this->_xcri->getNamespaces(true);
+        $this->_xcri->registerXPathNamespace('default', $namespaces['']);
+        
+        //Register the rest with their prefixes
+        foreach ($namespaces as $prefix => $ns) {
+            $this->_xcri->registerXPathNamespace($prefix, $ns);
+        }
+        
+        $xpath = "//default:subject[.='{$subjectarea->subject}']/../default:courseGroup";
+        $groups = $this->_xcri->xpath($xpath);
+        if ($groups) {
+            foreach ($groups as $group) {
+                $this->groups[] = (string)$group;
+            }
+            
+            $this->groups = array_unique($this->groups);
+            asort($this->groups);
+        }
+    }
+    
+    function count()
+    {
+        return count($this->groups);
+    }
+}
diff --git a/lib/php/UNL/Services/CourseApproval/XCRIService.php b/lib/php/UNL/Services/CourseApproval/XCRIService.php
new file mode 100644
index 0000000..8a31505
--- /dev/null
+++ b/lib/php/UNL/Services/CourseApproval/XCRIService.php
@@ -0,0 +1,6 @@
+<?php 
+interface UNL_Services_CourseApproval_XCRIService
+{
+    function getAllCourses();
+    function getSubjectArea($subjectarea);
+}
diff --git a/lib/php/UNL/Services/CourseApproval/XCRIService/MockService.php b/lib/php/UNL/Services/CourseApproval/XCRIService/MockService.php
new file mode 100644
index 0000000..325fe51
--- /dev/null
+++ b/lib/php/UNL/Services/CourseApproval/XCRIService/MockService.php
@@ -0,0 +1,440 @@
+<?php 
+class UNL_Services_CourseApproval_XCRIService_MockService implements UNL_Services_CourseApproval_XCRIService
+{    
+    public $xml_header = '<?xml version="1.0" encoding="UTF-8"?>
+<courses xmlns="http://courseapproval.unl.edu/courses" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://courseapproval.unl.edu/courses /schema/courses.xsd">';
+    
+    public $xml_footer = '</courses>';
+    
+    public $mock_data = array();
+    
+    function __construct()
+    {
+
+		$this->mock_data['MATH'] = <<<MATH
+  <course>
+    <title>Calculus for Managerial and Social Sciences</title>
+    <courseCodes>
+      <courseCode type="home listing">
+        <subject>MATH</subject>
+        <courseNumber>104</courseNumber>
+        <courseGroup>Introductory Mathematics Courses</courseGroup>
+      </courseCode>
+      <courseCode type="crosslisting">
+        <subject>MATH</subject>
+        <courseNumber>104</courseNumber>
+        <courseLetter>X</courseLetter>
+      </courseCode>
+    </courseCodes>
+    <gradingType>unrestricted</gradingType>
+    <dfRemoval>false</dfRemoval>
+    <effectiveSemester>1108</effectiveSemester>
+    <prerequisite>
+      <div xmlns="http://www.w3.org/1999/xhtml">Appropriate placement exam score or a grade of P (pass), or C or better in MATH 101.</div>
+    </prerequisite>
+    <notes>
+      <div xmlns="http://www.w3.org/1999/xhtml">Credit for both MATH 104 and 106 is not allowed.</div>
+    </notes>
+    <description>
+      <div xmlns="http://www.w3.org/1999/xhtml">Rudiments of differential and integral calculus with applications to problems from business, economics, and social sciences.</div>
+    </description>
+    <campuses>
+      <campus>UNL</campus>
+    </campuses>
+    <deliveryMethods>
+      <deliveryMethod>Classroom</deliveryMethod>
+      <deliveryMethod>Web</deliveryMethod>
+    </deliveryMethods>
+    <termsOffered>
+      <term>Fall</term>
+      <term>Spring</term>
+      <term>Summer</term>
+    </termsOffered>
+    <activities/>
+    <credits>
+      <credit type="Single Value">3</credit>
+    </credits>
+    <aceOutcomes>
+      <slo>3</slo>
+    </aceOutcomes>
+  </course>
+MATH;
+    	
+        $this->mock_data['ENSC'] = <<<ENSC
+  <course>
+    <title>Energy in Perspective</title>
+    <courseCodes>
+      <courseCode type="home listing">
+        <subject>ENSC</subject>
+        <courseNumber>110</courseNumber>
+
+      </courseCode>
+    </courseCodes>
+    <gradingType>letter grade only</gradingType>
+    <dfRemoval>false</dfRemoval>
+    <effectiveSemester>20082</effectiveSemester>
+    <description>
+      <div xmlns="http://www.w3.org/1999/xhtml">Scientific principles and historical interpretation to place energy use in the context of pressing societal, environmental and climate issues.</div>
+
+    </description>
+    <campuses>
+      <campus>UNL</campus>
+    </campuses>
+    <deliveryMethods>
+      <deliveryMethod>Classroom</deliveryMethod>
+    </deliveryMethods>
+    <termsOffered>
+
+      <term>Fall</term>
+    </termsOffered>
+    <activities>
+      <activity>
+        <type>lec</type>
+        <hours>3</hours>
+      </activity>
+
+    </activities>
+    <credits>
+      <credit type="Single Value">3</credit>
+    </credits>
+  </course>
+ENSC;
+        
+        $this->mock_data['ACCT'] = <<<ACCT
+<course>
+    <title>Introductory Accounting I</title>
+    <courseCodes>
+      <courseCode type="home listing">
+        <subject>ACCT</subject>
+        <courseNumber>201</courseNumber>
+
+      </courseCode>
+    </courseCodes>
+    <gradingType>letter grade only</gradingType>
+    <dfRemoval>false</dfRemoval>
+    <effectiveSemester>20101</effectiveSemester>
+    <prerequisite>
+      <div xmlns="http://www.w3.org/1999/xhtml">Math 104 with a grade of 'C' or better;  14 cr hrs at UNL with a 2.5 GPA.</div>
+
+    </prerequisite>
+    <notes>
+      <div xmlns="http://www.w3.org/1999/xhtml">ACCT 201 is 'Letter grade only'.</div>
+    </notes>
+    <description>
+      <div xmlns="http://www.w3.org/1999/xhtml">Fundamentals of accounting, reporting, and analysis to understand financial, managerial, and business concepts and practices. Provides foundation for advanced courses.</div>
+    </description>
+    <campuses>
+
+      <campus>UNL</campus>
+    </campuses>
+    <deliveryMethods>
+      <deliveryMethod>Classroom</deliveryMethod>
+    </deliveryMethods>
+    <termsOffered>
+      <term>Fall</term>
+
+      <term>Spring</term>
+      <term>Summer</term>
+    </termsOffered>
+    <activities>
+      <activity>
+        <type>lec</type>
+        <hours>3</hours>
+
+      </activity>
+    </activities>
+    <credits>
+      <credit type="Single Value">3</credit>
+    </credits>
+  </course>
+  <course>
+    <title>Honors: Introductory Accounting I</title>
+
+    <courseCodes>
+      <courseCode type="home listing">
+        <subject>ACCT</subject>
+        <courseNumber>201</courseNumber>
+        <courseLetter>H</courseLetter>
+      </courseCode>
+    </courseCodes>
+
+    <gradingType>unrestricted</gradingType>
+    <dfRemoval>false</dfRemoval>
+    <effectiveSemester>20081</effectiveSemester>
+    <prerequisite>
+      <div xmlns="http://www.w3.org/1999/xhtml">Good standing in the University Honors Program or by invitation; freshman standing; 3.5 GPA over at least 14 credit hours earned at UNL.</div>
+    </prerequisite>
+    <description>
+
+      <div xmlns="http://www.w3.org/1999/xhtml">For course description, see ACCT 201.</div>
+    </description>
+    <campuses>
+      <campus>UNL</campus>
+    </campuses>
+    <deliveryMethods>
+      <deliveryMethod>Classroom</deliveryMethod>
+
+    </deliveryMethods>
+    <termsOffered>
+      <term>Fall</term>
+      <term>Spring</term>
+      <term>Summer</term>
+    </termsOffered>
+    <activities>
+
+      <activity>
+        <type>lec</type>
+      </activity>
+    </activities>
+    <credits>
+      <credit type="Single Value">3</credit>
+    </credits>
+  </course>
+ACCT;
+        $this->mock_data['AECN'] = <<<AECN
+  <course>
+    <title>Agricultural Marketing in a Multinational Environment</title>
+    <courseCodes>
+      <courseCode type="home listing">
+        <subject>AECN</subject>
+        <courseNumber>425</courseNumber>
+      </courseCode>
+    </courseCodes>
+    <gradingType>unrestricted</gradingType>
+    <dfRemoval>true</dfRemoval>
+    <effectiveSemester>20091</effectiveSemester>
+    <prerequisite>
+      <div xmlns="http://www.w3.org/1999/xhtml">9 hrs agricultural economics and/or economics or permission.</div>
+    </prerequisite>
+    <notes>
+      <div xmlns="http://www.w3.org/1999/xhtml">Capstone course.</div>
+    </notes>
+    <description>
+      <div xmlns="http://www.w3.org/1999/xhtml">Systems approach to evaulating the effects of current domestic and international political and economic events on agricultural markets.</div>
+    </description>
+    <campuses>
+      <campus>UNL</campus>
+    </campuses>
+    <deliveryMethods>
+      <deliveryMethod>Classroom</deliveryMethod>
+    </deliveryMethods>
+    <termsOffered>
+      <term>Fall</term>
+    </termsOffered>
+    <activities/>
+    <credits>
+      <credit type="Single Value">3</credit>
+    </credits>
+    <aceOutcomes>
+      <slo>9</slo>
+      <slo>10</slo>
+    </aceOutcomes>
+  </course>
+  <course>
+    <title>Agricultural and Natural Resource Policy Analysis</title>
+    <courseCodes>
+      <courseCode type="home listing">
+        <subject>AECN</subject>
+        <courseNumber>445</courseNumber>
+      </courseCode>
+      <courseCode type="crosslisting">
+        <subject>NREE</subject>
+        <courseNumber>445</courseNumber>
+      </courseCode>
+    </courseCodes>
+    <gradingType>unrestricted</gradingType>
+    <dfRemoval>false</dfRemoval>
+    <effectiveSemester>20091</effectiveSemester>
+    <prerequisite>
+      <div xmlns="http://www.w3.org/1999/xhtml">ECON 211; ECON 212 or AECN 141. ECON 311 and 312 recommended.</div>
+    </prerequisite>
+    <notes>
+      <div xmlns="http://www.w3.org/1999/xhtml">Capstone course. <br/></div>
+    </notes>
+    <description>
+      <div xmlns="http://www.w3.org/1999/xhtml">Introduction to the application of economic concepts and tools to the analysis and evaluation of public policies. Economic approaches to policy evaluation derived from welfare economics. Social benefit-cost analysis described and illustrated through applications to current agricultural and natural resource policy issues.</div>
+    </description>
+    <campuses>
+      <campus>UNL</campus>
+    </campuses>
+    <deliveryMethods>
+      <deliveryMethod>Classroom</deliveryMethod>
+    </deliveryMethods>
+    <termsOffered>
+      <term>Spring</term>
+    </termsOffered>
+    <activities>
+      <activity>
+        <type>lec</type>
+        <hours>3</hours>
+      </activity>
+    </activities>
+    <credits>
+      <credit type="Single Value">3</credit>
+    </credits>
+    <aceOutcomes>
+      <slo>8</slo>
+      <slo>10</slo>
+    </aceOutcomes>
+  </course>
+AECN;
+        $this->mock_data['CSCE'] = <<<CSCE
+  <course>
+    <title>Introduction to Problem Solving with Computers</title>
+    <courseCodes>
+      <courseCode type="home listing">
+        <subject>CSCE</subject>
+        <courseNumber>150</courseNumber>
+        <courseLetter>A</courseLetter>
+
+      </courseCode>
+    </courseCodes>
+    <gradingType>unrestricted</gradingType>
+    <dfRemoval>true</dfRemoval>
+    <effectiveSemester>20083</effectiveSemester>
+    <prerequisite>
+      <div xmlns="http://www.w3.org/1999/xhtml">Four years high school mathematics.</div>
+
+    </prerequisite>
+    <notes>
+      <div xmlns="http://www.w3.org/1999/xhtml">
+        <em>CSCE 150A is designed to develop skills in programming and problem solving to prepare for CSCE 155.</em>
+        <em>CSCE 150A will not count toward the requirements for the major in computer science and computer engineering. </em>
+        <em>
+          <em>Credit towards the degree may be earned in only one of: CSCE 150A or CSCE 150E or CSCE 150M or CSCE 252A.</em>
+
+        </em>
+      </div>
+    </notes>
+    <description>
+      <div xmlns="http://www.w3.org/1999/xhtml">Problem solving with a computer and programming fundamentals using a popular high-level language. Logic and functions that apply to computer science; elementary programming constructs, type, and algorithmic techniques.</div>
+    </description>
+    <campuses>
+      <campus>UNL</campus>
+
+    </campuses>
+    <deliveryMethods>
+      <deliveryMethod>Classroom</deliveryMethod>
+    </deliveryMethods>
+    <termsOffered>
+      <term>Fall</term>
+      <term>Spring</term>
+
+      <term>Summer</term>
+    </termsOffered>
+    <activities>
+      <activity>
+        <type>lec</type>
+        <hours>3</hours>
+      </activity>
+
+    </activities>
+    <credits>
+      <credit type="Single Value">3</credit>
+    </credits>
+  </course>
+  <course>
+    <title>Special Topics in Computer Science</title>
+    <courseCodes>
+      <courseCode type="home listing">
+        <subject>CSCE</subject>
+        <courseNumber>196</courseNumber>
+
+      </courseCode>
+    </courseCodes>
+    <gradingType>unrestricted</gradingType>
+    <dfRemoval>false</dfRemoval>
+    <effectiveSemester>20081</effectiveSemester>
+    <prerequisite>
+      <div xmlns="http://www.w3.org/1999/xhtml">Permission.</div>
+
+    </prerequisite>
+    <description>
+      <div xmlns="http://www.w3.org/1999/xhtml">Aspects of computers and computing for computer science and computer engineering majors and minors. Topics vary.</div>
+    </description>
+    <campuses>
+      <campus>UNL</campus>
+    </campuses>
+    <deliveryMethods>
+
+      <deliveryMethod>Classroom</deliveryMethod>
+    </deliveryMethods>
+    <termsOffered>
+      <term>Fall</term>
+      <term>Spring</term>
+      <term>Summer</term>
+    </termsOffered>
+
+    <activities/>
+    <credits>
+      <credit type="Lower Range Limit">1</credit>
+      <credit type="Upper Range Limit">3</credit>
+      <credit type="Per Semester Limit">6</credit>
+    </credits>
+  </course>
+CSCE;
+        $this->mock_data['NREE'] = <<<NREE
+<course>
+    <title>Agricultural and Natural Resource Policy Analysis</title>
+    <courseCodes>
+      <courseCode type="home listing">
+        <subject>NREE</subject>
+        <courseNumber>445</courseNumber>
+      </courseCode>
+      <courseCode type="crosslisting">
+        <subject>NREE</subject>
+        <courseNumber>845</courseNumber>
+      </courseCode>
+    </courseCodes>
+    <gradingType>unrestricted</gradingType>
+    <dfRemoval>false</dfRemoval>
+    <effectiveSemester>20091</effectiveSemester>
+    <prerequisite>
+      <div xmlns="http://www.w3.org/1999/xhtml">ECON 211; ECON 212 or AECN 141. ECON 311 and 312 recommended.</div>
+    </prerequisite>
+    <notes>
+      <div xmlns="http://www.w3.org/1999/xhtml">Capstone course. <br/></div>
+    </notes>
+    <description>
+      <div xmlns="http://www.w3.org/1999/xhtml">Introduction to the application of economic concepts and tools to the analysis and evaluation of public policies. Economic approaches to policy evaluation derived from welfare economics. Social benefit-cost analysis described and illustrated through applications to current agricultural and natural resource policy issues.</div>
+    </description>
+    <campuses>
+      <campus>UNL</campus>
+    </campuses>
+    <deliveryMethods>
+      <deliveryMethod>Classroom</deliveryMethod>
+    </deliveryMethods>
+    <termsOffered>
+      <term>Spring</term>
+    </termsOffered>
+    <activities>
+      <activity>
+        <type>lec</type>
+        <hours>3</hours>
+      </activity>
+    </activities>
+    <credits>
+      <credit type="Single Value">3</credit>
+    </credits>
+    <aceOutcomes>
+      <slo>8</slo>
+      <slo>10</slo>
+    </aceOutcomes>
+  </course>
+NREE;
+    }
+    
+    function getAllCourses()
+    {
+        return $this->xml_header.implode($this->mock_data).$this->xml_footer;
+    }
+    
+    function getSubjectArea($subjectarea)
+    {
+        if (!isset($this->mock_data[$subjectarea])) {
+            throw new Exception('Could not get data.', 500);
+        }
+        
+        return $this->xml_header.$this->mock_data[$subjectarea].$this->xml_footer;
+    }
+}
diff --git a/lib/php/UNL/Services/CourseApproval/XCRIService/creq.php b/lib/php/UNL/Services/CourseApproval/XCRIService/creq.php
new file mode 100644
index 0000000..584ad0c
--- /dev/null
+++ b/lib/php/UNL/Services/CourseApproval/XCRIService/creq.php
@@ -0,0 +1,79 @@
+<?php
+/**
+ * Course data driver for the Course Requisition system at UNL (CREQ)
+ * 
+ * @author Brett Bieber <brett.bieber@gmail.com>
+ */
+class UNL_Services_CourseApproval_XCRIService_creq implements UNL_Services_CourseApproval_XCRIService
+{
+
+    /**
+     * URL to the public creq XML data service endpoint
+     * 
+     * @var string
+     */
+    const URL = 'http://creq.unl.edu/courses/public-view/all-courses';
+
+    /**
+     * The caching service.
+     * 
+     * @var UNL_Services_CourseApproval_CachingService
+     */
+    protected $_cache;
+
+    /**
+     * Constructor for the creq service
+     */
+    function __construct()
+    {
+        $this->_cache = UNL_Services_CourseApproval::getCachingService();
+    }
+
+    /**
+     * Get all course data
+     * 
+     * @return string XML course data
+     */
+    function getAllCourses()
+    {
+        return $this->_getData('creq_allcourses', self::URL);
+    }
+
+    /**
+     * Get the XML for a specific subject area, e.g. CSCE
+     * 
+     * @param string $subjectarea Subject area/code to retrieve courses for e.g. CSCE
+     * 
+     * @return string XML data
+     */
+    function getSubjectArea($subjectarea)
+    {
+        return $this->_getData('creq_subject_'.$subjectarea, self::URL.'/subject/'.$subjectarea);
+    }
+
+    /**
+     * Generic data retrieval method which grabs a URL and caches the data
+     * 
+     * @param string $key A unique key for this piece of data
+     * @param string $url The URL to retrieve data from
+     * 
+     * @return string The data from the URL
+     * 
+     * @throws Exception
+     */
+    protected function _getData($key, $url)
+    {
+        if ($data = $this->_cache->get($key)) {
+            return $data;
+        }
+        
+        if ($data = file_get_contents($url)) {
+            if ($this->_cache->save($key, $data)) {
+                return $data;
+            }
+            throw new Exception('Could not save data for '.$url);
+        }
+        
+        throw new Exception('Could not get data from '.$url);
+    }
+}
diff --git a/lib/tests/pear.unl.edu/UNL_Services_CourseApproval/activities.phpt b/lib/tests/pear.unl.edu/UNL_Services_CourseApproval/activities.phpt
new file mode 100644
index 0000000..3d41011
--- /dev/null
+++ b/lib/tests/pear.unl.edu/UNL_Services_CourseApproval/activities.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Course activities test 
+--FILE--
+<?php
+require_once 'test_framework.php';
+$listing = new UNL_Services_CourseApproval_Listing('ACCT', 201);
+$test->assertTrue($listing->activities instanceof Iterator, 'Activities returned is an iterator');
+$test->assertEquals(1, count($listing->activities), 'Count the number of activities');
+?>
+===DONE===
+--EXPECT--
+===DONE===
\ No newline at end of file
diff --git a/lib/tests/pear.unl.edu/UNL_Services_CourseApproval/array_details.phpt b/lib/tests/pear.unl.edu/UNL_Services_CourseApproval/array_details.phpt
new file mode 100644
index 0000000..c905b4b
--- /dev/null
+++ b/lib/tests/pear.unl.edu/UNL_Services_CourseApproval/array_details.phpt
@@ -0,0 +1,14 @@
+--TEST--
+Course details returned as arrays
+--FILE--
+<?php
+require_once 'test_framework.php';
+$listing = new UNL_Services_CourseApproval_Listing('ACCT', 201);
+
+$test->assertTrue(is_array($listing->campuses), 'Campuses');
+$test->assertTrue(is_array($listing->deliveryMethods), 'Delivery methods');
+$test->assertTrue(is_array($listing->termsOffered), 'Terms offered');
+?>
+===DONE===
+--EXPECT--
+===DONE===
\ No newline at end of file
diff --git a/lib/tests/pear.unl.edu/UNL_Services_CourseApproval/credits.phpt b/lib/tests/pear.unl.edu/UNL_Services_CourseApproval/credits.phpt
new file mode 100644
index 0000000..966bfe3
--- /dev/null
+++ b/lib/tests/pear.unl.edu/UNL_Services_CourseApproval/credits.phpt
@@ -0,0 +1,26 @@
+--TEST--
+Test course credit information
+--FILE--
+<?php
+require_once 'test_framework.php';
+$listing = new UNL_Services_CourseApproval_Listing('CSCE', 196);
+$test->assertTrue($listing->credits instanceof Countable, 'Credits is a countable object.');
+$test->assertEquals(3, count($listing->credits), 'Three types of credits for this course.');
+
+$test->assertEquals(1, $listing->credits['Lower Range Limit'], 'Array access by type.');
+$test->assertEquals(3, $listing->credits['Upper Range Limit'], 'Array access by type 2.');
+$test->assertEquals(6, $listing->credits['Per Semester Limit'], 'Array access by type 3.');
+$test->assertFalse(isset($listing->credits['Single Value']), 'Course has no credit of this type.');
+$test->assertTrue(isset($listing->credits['Lower Range Limit']), 'Course has credit of this type.');
+
+$listing = new UNL_Services_CourseApproval_Listing('ACCT', 201);
+$test->assertTrue($listing->credits instanceof Countable, 'Credits is a countable object.');
+$test->assertEquals(1, count($listing->credits), 'Three types of credits for this course.');
+
+$test->assertTrue(isset($listing->credits['Single Value']), 'Course has credit of this type.');
+$test->assertEquals(3, $listing->credits['Single Value'], 'Array access by type.');
+
+?>
+===DONE===
+--EXPECT--
+===DONE===
\ No newline at end of file
diff --git a/lib/tests/pear.unl.edu/UNL_Services_CourseApproval/dfremoval.phpt b/lib/tests/pear.unl.edu/UNL_Services_CourseApproval/dfremoval.phpt
new file mode 100644
index 0000000..e237e37
--- /dev/null
+++ b/lib/tests/pear.unl.edu/UNL_Services_CourseApproval/dfremoval.phpt
@@ -0,0 +1,19 @@
+--TEST--
+Sample Test
+--FILE--
+<?php
+require_once 'test_framework.php';
+$listing = new UNL_Services_CourseApproval_Listing('ACCT', 201);
+$test->assertFalse($listing->dfRemoval, 'D or F removal');
+
+$listing = new UNL_Services_CourseApproval_Listing('ENSC', 110);
+$test->assertFalse($listing->dfRemoval, 'D or F removal');
+
+$listing = new UNL_Services_CourseApproval_Listing('CSCE', '150A');
+$test->assertTrue($listing->dfRemoval, 'D or F removal');
+
+
+?>
+===DONE===
+--EXPECT--
+===DONE===
\ No newline at end of file
diff --git a/lib/tests/pear.unl.edu/UNL_Services_CourseApproval/isset.phpt b/lib/tests/pear.unl.edu/UNL_Services_CourseApproval/isset.phpt
new file mode 100644
index 0000000..3bc4200
--- /dev/null
+++ b/lib/tests/pear.unl.edu/UNL_Services_CourseApproval/isset.phpt
@@ -0,0 +1,13 @@
+--TEST--
+Sample Test
+--FILE--
+<?php
+require_once 'test_framework.php';
+$listing = new UNL_Services_CourseApproval_Listing('ACCT', 201);
+$test->assertTrue(isset($listing->notes), 'Course has notes.');
+$test->assertTrue(isset($listing->description), 'Course has description.');
+$test->assertFalse(isset($listing->aceOutcomes), 'Course does NOT have ACE outcomes.');
+?>
+===DONE===
+--EXPECT--
+===DONE===
\ No newline at end of file
diff --git a/lib/tests/pear.unl.edu/UNL_Services_CourseApproval/listing.phpt b/lib/tests/pear.unl.edu/UNL_Services_CourseApproval/listing.phpt
new file mode 100644
index 0000000..5905f63
--- /dev/null
+++ b/lib/tests/pear.unl.edu/UNL_Services_CourseApproval/listing.phpt
@@ -0,0 +1,28 @@
+--TEST--
+Sample Test
+--FILE--
+<?php
+require_once 'test_framework.php';
+$listing = new UNL_Services_CourseApproval_Listing('ACCT', 201);
+
+$test->assertEquals('ACCT', $listing->subjectArea, 'Subject area');
+$test->assertEquals(201, $listing->courseNumber, 'Course number');
+$test->assertEquals('Introductory Accounting I', $listing->title, 'Course title');
+$test->assertEquals('<div>Fundamentals of accounting, reporting, and analysis to understand financial, managerial, and business concepts and practices. Provides foundation for advanced courses.</div>', $listing->description, 'Course description');
+$test->assertEquals('<div>Math 104 with a grade of \'C\' or better;  14 cr hrs at UNL with a 2.5 GPA.</div>', $listing->prerequisite, 'Prerequisite');
+$test->assertEquals('<div>ACCT 201 is \'Letter grade only\'.</div>', $listing->notes, 'Notes');
+$test->assertEquals('letter grade only', $listing->gradingType, 'Grading type.');
+$test->assertEquals('20101', $listing->effectiveSemester, 'Effective semester');
+
+
+// Now let's test getting an honors course, with H courseLetter.
+$listing = new UNL_Services_CourseApproval_Listing('ACCT', '201H');
+
+$test->assertEquals('ACCT', $listing->subjectArea, 'Subject area');
+$test->assertEquals('201H', $listing->courseNumber, 'Course number');
+$test->assertEquals('Honors: Introductory Accounting I', $listing->title, 'Course title');
+
+?>
+===DONE===
+--EXPECT--
+===DONE===
\ No newline at end of file
diff --git a/lib/tests/pear.unl.edu/UNL_Services_CourseApproval/sample.phpt b/lib/tests/pear.unl.edu/UNL_Services_CourseApproval/sample.phpt
new file mode 100644
index 0000000..716939a
--- /dev/null
+++ b/lib/tests/pear.unl.edu/UNL_Services_CourseApproval/sample.phpt
@@ -0,0 +1,9 @@
+--TEST--
+Sample Test
+--FILE--
+<?php
+require_once 'test_framework.php';
+?>
+===DONE===
+--EXPECT--
+===DONE===
\ No newline at end of file
diff --git a/lib/tests/pear.unl.edu/UNL_Services_CourseApproval/search.phpt b/lib/tests/pear.unl.edu/UNL_Services_CourseApproval/search.phpt
new file mode 100644
index 0000000..439de2e
--- /dev/null
+++ b/lib/tests/pear.unl.edu/UNL_Services_CourseApproval/search.phpt
@@ -0,0 +1,43 @@
+--TEST--
+Search test
+--FILE--
+<?php
+require_once 'test_framework.php';
+$search = new UNL_Services_CourseApproval_Search();
+
+$courses = $search->byNumber('201');
+$test->assertEquals(2, count($courses), 'Two results returned');
+
+$courses = $search->byTitle('Accounting');
+$test->assertEquals(2, count($courses), 'Two results returned');
+
+foreach ($courses as $course) {
+    $test->assertNotFalse(
+        strpos($course->title, 'Accounting'),
+        'Course title contains the word Accounting'
+    );
+}
+
+$courses = $search->numberSuffixQuery('04');
+$test->assertEquals(1, count($courses), 'One *04 result returned');
+
+$query1 = $search->subjectAreaQuery('NREE');
+$courses = $search->driver->getQueryResult($query1);
+$test->assertEquals(2, count($courses), 'Two results returned for NREE');
+
+$query2 = $search->subjectAreaQuery('AECN');
+$courses = $search->driver->getQueryResult($query2);
+$test->assertEquals(2, count($courses), 'Two results returned for AECN');
+
+$query = $search->intersectQuery($query1, $query2);
+$courses = $search->driver->getQueryResult($query);
+$test->assertEquals(1, count($courses), 'Intersection of two queries');
+
+$courses = $search->graduateCourses();
+$test->assertEquals(1, count($courses), 'One graduate course returned');
+
+
+?>
+===DONE===
+--EXPECT--
+===DONE===
\ No newline at end of file
diff --git a/lib/tests/pear.unl.edu/UNL_Services_CourseApproval/subjects.phpt b/lib/tests/pear.unl.edu/UNL_Services_CourseApproval/subjects.phpt
new file mode 100644
index 0000000..e0281e5
--- /dev/null
+++ b/lib/tests/pear.unl.edu/UNL_Services_CourseApproval/subjects.phpt
@@ -0,0 +1,14 @@
+--TEST--
+Test Subjects
+--FILE--
+<?php
+require_once 'test_framework.php';
+$subject = new UNL_Services_CourseApproval_SubjectArea('ACCT');
+$test->assertEquals('ACCT', $subject->subject, 'Returns correct subject code');
+$test->assertEquals('ACCT', $subject->__toString(), '__tostring() Returns correct subject code');
+$test->assertTrue($subject->courses instanceof ArrayAccess, 'Listings is an array');
+$test->assertEquals(2, count($subject->courses), 'Count the number of courses.');
+?>
+===DONE===
+--EXPECT--
+===DONE===
\ No newline at end of file
diff --git a/lib/tests/pear.unl.edu/UNL_Services_CourseApproval/subsequent_courses.phpt b/lib/tests/pear.unl.edu/UNL_Services_CourseApproval/subsequent_courses.phpt
new file mode 100644
index 0000000..e2add55
--- /dev/null
+++ b/lib/tests/pear.unl.edu/UNL_Services_CourseApproval/subsequent_courses.phpt
@@ -0,0 +1,18 @@
+--TEST--
+Subsequent course test
+--FILE--
+<?php
+require_once 'test_framework.php';
+$listing = new UNL_Services_CourseApproval_Listing('MATH', '104');
+
+$courses = $listing->course->getSubsequentCourses();
+
+$test->assertEquals(1, count($courses), 'One subsequent course returned');
+foreach ($courses as $course) {
+    $test->assertEquals('Introductory Accounting I', $course->title, 'Course title');
+}
+
+?>
+===DONE===
+--EXPECT--
+===DONE===
\ No newline at end of file
diff --git a/lib/tests/pear.unl.edu/UNL_Services_CourseApproval/test_framework.php b/lib/tests/pear.unl.edu/UNL_Services_CourseApproval/test_framework.php
new file mode 100644
index 0000000..0041c98
--- /dev/null
+++ b/lib/tests/pear.unl.edu/UNL_Services_CourseApproval/test_framework.php
@@ -0,0 +1,228 @@
+<?php
+$__e = error_reporting();
+error_reporting(E_ERROR|E_NOTICE|E_WARNING);
+
+ini_set('display_errors', true);
+error_reporting(E_ALL|E_STRICT);
+
+function autoload($class)
+{
+    $class = str_replace('_', '/', $class);
+    include $class . '.php';
+}
+    
+spl_autoload_register("autoload");
+
+set_include_path(dirname(__DIR__).'/src/');
+
+@include_once 'Text/Diff.php';
+@include_once 'Text/Diff/Renderer.php';
+@include_once 'Text/Diff/Renderer/unified.php';
+chdir(dirname(dirname(__FILE__)));
+
+UNL_Services_CourseApproval::setCachingService(new UNL_Services_CourseApproval_CachingService_Null());
+UNL_Services_CourseApproval::setXCRIService(new UNL_Services_CourseApproval_XCRIService_MockService());
+
+error_reporting($__e);
+class PEAR2_PHPT
+{
+    var $_diffonly;
+    function __construct($diffonly = false)
+    {
+        $this->_diffonly = $diffonly;
+        $this->_errors = array();
+    }
+
+    function assertTrue($test, $message)
+    {
+        if ($test === true) {
+            return true;
+        }
+        $this->_failTest(debug_backtrace(), $message);
+        echo "Unexpected non-true value: \n";
+        var_export($test);
+        echo "\n'$message'\n";
+        return false;
+    }
+
+    function assertIsa($control, $test, $message)
+    {
+        if (is_a($test, $control)) {
+            return true;
+        }
+        $this->_failTest(debug_backtrace(), $message);
+        echo "Unexpected non-$control object: \n";
+        var_export($test);
+        echo "\n'$message'\n";
+        return false;
+    }
+
+    function assertNull($test, $message)
+    {
+        if ($test === null) {
+            return true;
+        }
+        $this->_failTest(debug_backtrace(), $message);
+        echo "Unexpected non-null value: \n";
+        var_export($test);
+        echo "\n'$message'\n";
+        return false;
+    }
+
+    function assertNotNull($test, $message)
+    {
+        if ($test !== null) {
+            return true;
+        }
+        $this->_failTest(debug_backtrace(), $message);
+        echo "Unexpected null: \n";
+        var_export($test);
+        echo "\n'$message'\n";
+        return false;
+    }
+
+    function assertSame($test, $test1, $message)
+    {
+        if ($test === $test1) {
+            return true;
+        }
+        $this->_failTest(debug_backtrace(), $message);
+        echo "Unexpectedly two vars are not the same thing: \n";
+        echo "\n'$message'\n";
+        return false;
+    }
+
+    function assertNotSame($test, $test1, $message)
+    {
+        if ($test !== $test1) {
+            return true;
+        }
+        $this->_failTest(debug_backtrace(), $message);
+        echo "Unexpectedly two vars are the same thing: \n";
+        echo "\n'$message'\n";
+        return false;
+    }
+
+    function assertFalse($test, $message)
+    {
+        if ($test === false) {
+            return true;
+        }
+        $this->_failTest(debug_backtrace(), $message);
+        echo "Unexpected non-false value: \n";
+        var_export($test);
+        echo "\n'$message'\n";
+        return false;
+    }
+
+    function assertNotTrue($test, $message)
+    {
+        if (!$test) {
+            return true;
+        }
+        $this->_failTest(debug_backtrace(), $message);
+        echo "Unexpected loose true value: \n";
+        var_export($test);
+        echo "\n'$message'\n";
+        return false;
+    }
+
+    function assertNotFalse($test, $message)
+    {
+        if ($test) {
+            return true;
+        }
+        $this->_failTest(debug_backtrace(), $message);
+        echo "Unexpected loose false value: \n";
+        var_export($test);
+        echo "\n'$message'\n";
+        return false;
+    }
+
+    function assertRegex($regex, $test, $message)
+    {
+        if (!preg_match($regex, $test)) {
+            $this->_failTest(debug_backtrace(), $message);
+            echo "Expecting:\nText Matching Regular Expression $regex\n";
+            echo "\nReceived:\n";
+            var_export($test);
+            echo "\n";
+            return false;
+        }
+        return true;
+    }
+
+    function assertException($exception, $class, $emessage, $message)
+    {
+        if (!($exception instanceof $class)) {
+            $this->_failTest(debug_backtrace(), $message);
+            echo "Expecting class $class, got ", get_class($exception);
+        }
+        $this->assertEquals($emessage, $exception->getMessage(), $message, debug_backtrace());
+    }
+
+    function assertEquals($control, $test, $message, $trace = null)
+    {
+        if (!$trace) {
+            $trace = debug_backtrace();
+        }
+        if (str_replace(array("\r", "\n"), array('', ''),
+            var_export($control, true)) != str_replace(array("\r", "\n"), array('', ''),
+            var_export($test, true))) {
+            $this->_failTest($trace, $message);
+            if (class_exists('Text_Diff')) {
+                echo "Diff of expecting/received:\n";
+                $diff = new Text_Diff(
+                    explode("\n", var_export($control, true)),
+                    explode("\n", var_export($test, true)));
+
+                // Output the diff in unified format.
+                $renderer = new Text_Diff_Renderer_unified();
+                echo $renderer->render($diff);
+                if ($this->_diffonly) {
+                    return false;
+                }
+            }
+            echo "Expecting:\n";
+            var_export($control);
+            echo "\nReceived:\n";
+            var_export($test);
+            echo "\n";
+            return false;
+        }
+        return true;
+    }
+
+    function assertFileExists($fname, $message)
+    {
+        if (!@file_exists($fname)) {
+            $this->_failTest(debug_backtrace(), $message);
+            echo "File '$fname' does not exist, and should\n";
+            return false;
+        }
+        return true;
+    }
+
+    function assertFileNotExists($fname, $message)
+    {
+        if (@file_exists($fname)) {
+            $this->_failTest(debug_backtrace(), $message);
+            echo "File '$fname' exists, and should not\n";
+            return false;
+        }
+        return true;
+    }
+
+    function _failTest($trace, $message)
+    {
+        echo 'Test Failure: "' . $message  . "\"\n in " . $trace[0]['file'] . ' line ' .
+            $trace[0]['line'] . "\n";
+    }
+
+    function showAll()
+    {
+        $this->_diffonly = false;
+    }
+}
+$test = new PEAR2_PHPT();
+?>
-- 
GitLab