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