diff --git a/lib/.pear2registry b/lib/.pear2registry index fae52034d512c482b1b4fa280fb21f50e4afe04a..a421eaecf4abcdef93299b11da5698da5eb6f63b 100644 Binary files a/lib/.pear2registry and b/lib/.pear2registry differ 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 0000000000000000000000000000000000000000..7d33401158bdc82ddbc8d718f4d12dd382a616a9 --- /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 0000000000000000000000000000000000000000..76ea63035ee5487a934909317855bb2adcc29513 --- /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 484bdc884b30d68a6897d05911eac040e2ca741f..00110a1202b09cfb4b686a5b26bd2d419f0f4700 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 0000000000000000000000000000000000000000..37fec840fcc2968400b181d537259c9a9bfb3bb3 --- /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 0000000000000000000000000000000000000000..e16cee45df11e5864786e955661141732d20305c --- /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 0000000000000000000000000000000000000000..e5ae58adcafa4628c1b10782ddfc96e953833d27 --- /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 0000000000000000000000000000000000000000..d0339ebc1366cba46d9e966de0bb417af525328e --- /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 0000000000000000000000000000000000000000..7e646d79694c4aaf15b226298dbd68c0eb6a0e93 --- /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 0000000000000000000000000000000000000000..36d4e7a38f05ce294f6a871868f19e65baa584a5 --- /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 0000000000000000000000000000000000000000..335972cd65c58fbd77a1fe48f45bfd5c8312b60b --- /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 0000000000000000000000000000000000000000..15e59e1f36d20d28cc3e67298db1af5adf0a6487 --- /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 0000000000000000000000000000000000000000..341f131872cccd43e994edbe57ca19868b498f9c --- /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 0000000000000000000000000000000000000000..f8d8845a4b9fd2b24b14ca8e837de643c72839c5 --- /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 0000000000000000000000000000000000000000..0d256a4eb5ad77c087cdee573f695f11ebe60b38 --- /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 0000000000000000000000000000000000000000..dfb68724b6089fcc2f59a68382070ec9469f8f17 --- /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 0000000000000000000000000000000000000000..874ce3e983f38fb8a297f8bc507197ea21f2900a --- /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 0000000000000000000000000000000000000000..4c0918d71a6db08eb768cdff42bd8b161a07f95a --- /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 0000000000000000000000000000000000000000..a08406d606c131bd035d55551fda04e78629faef --- /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 0000000000000000000000000000000000000000..bd1db3c001919094b6aeb21b46c83d982273cab1 --- /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 0000000000000000000000000000000000000000..3119bfb4c225a47f80ea7c2827493080c6d66eb9 --- /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 0000000000000000000000000000000000000000..1f87a61c224a15d36b8516d20c1e73b75fc165d8 --- /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 0000000000000000000000000000000000000000..cc83fb24cf9dd83df26fed3e781161067614f474 --- /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 0000000000000000000000000000000000000000..6b6035d1079279bf7489eb62c65ea64b618f4348 --- /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 0000000000000000000000000000000000000000..8a3150545c3cd86fe5555795e0ac469f694a1521 --- /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 0000000000000000000000000000000000000000..325fe51f6ef73a1a24808dab3ce0bf777a71bfb9 --- /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 0000000000000000000000000000000000000000..584ad0c57a2cbceb2152c69f172564c4339d0f70 --- /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 0000000000000000000000000000000000000000..3d410119b5456dc05a2cfada470be02dcda15692 --- /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 0000000000000000000000000000000000000000..c905b4bb7af83d8f69bdedff40ad573a1189ab93 --- /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 0000000000000000000000000000000000000000..966bfe3080a3b993bbe0e6e8ea6f72e703049db2 --- /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 0000000000000000000000000000000000000000..e237e37038fba2af7f045ea3b420867aaa04c5ae --- /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 0000000000000000000000000000000000000000..3bc42005c1905bdd473c20011ec383a4fb594f51 --- /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 0000000000000000000000000000000000000000..5905f634f6d28c55fe8105eff1d6849187fb0e20 --- /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 0000000000000000000000000000000000000000..716939a795014a2b00a196cb00baabd90c7d4691 --- /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 0000000000000000000000000000000000000000..439de2e0aa9b20cc4c87b0e809489311d156d231 --- /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 0000000000000000000000000000000000000000..e0281e53ff8eeb20995bcbc50240555a31365b0f --- /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 0000000000000000000000000000000000000000..e2add55ed7a472f86723fb662051acbbb90db734 --- /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 0000000000000000000000000000000000000000..0041c98a39977f8eb9f6961be2d4eb6f46a45e53 --- /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(); +?>