diff --git a/src/UNL/Catalog/ACE.php b/src/UNL/Catalog/ACE.php new file mode 100644 index 0000000000000000000000000000000000000000..8f1360ab7be9b002bb4826dad7267531bf83a0ec --- /dev/null +++ b/src/UNL/Catalog/ACE.php @@ -0,0 +1,17 @@ +<?php +namespace UNL\Catalog; + +class ACE +{ + public static $descriptions = array( + 1 => 'Writing', + 2 => 'Communication Competence', + 3 => 'Math/Stat/Reasoning', + 4 => 'Science', + 5 => 'Humanities', + 6 => 'Social Science', + 7 => 'Arts', + 8 => 'Civics/Ethics/Stewardship', + 9 => 'Global/Diversity', + 10 => 'Integrated Product'); +} \ No newline at end of file diff --git a/src/UNL/Catalog/Course/Utilities.php b/src/UNL/Catalog/Course/Utilities.php new file mode 100644 index 0000000000000000000000000000000000000000..0e28713bc6aeb49cf570779397d2381ca245bafc --- /dev/null +++ b/src/UNL/Catalog/Course/Utilities.php @@ -0,0 +1,293 @@ +<?php + +namespace UNL\Catalog\Course; + +/** + * The EPUB files exported from InDesign contain simple markup with a few class names. + * + * This class contains simple methods for linking URLs within the text, courses, + * and other minor markup cleanup. + * + * @author bbieber + */ +class Utilities +{ + /** + * Overall method which applies all formatting + * + * @param string $html The markup + * + * @return string html + */ + public static function format($html) + { + $html = self::convertHeadings($html); + $html = self::addLeaders($html); + $html = self::linkURLs($html); + $html = self::addCourseLinks($html); + return $html; + } + + /** + * Finds www.unl.edu and http://../ within the text and wraps it in an + * anchor tag. + * + * @param string $html The markup + * + * @return string html + */ + public static function linkURLs($html) + { + $html = preg_replace('/\s(www\.unl\.edu.*)/', ' http://$1', $html); + return preg_replace_callback('/(http:\/\/[^<^\s]+)/', array('UNL_UndergraduateBulletin_EPUB_Utilities', 'linkHref'), $html); + } + + public static function convertHeadings($html) + { + $replacements = array( + // trim off pointless "generated-style" spans + '/<span class="generated-style">([^<]*)<\/span>/' => '$1', + '/<p class="content-box-h-1"[^>]*>([^<]*)<\/p>/' => '<h2 class="sec_header content-box-h-1">$1</h2>', + '/<p class="content-box-m-p"[^>]*>([^<]*)<\/p>/' => '<h2 class="sec_header content-box-m-p">$1</h2>', + '/<p class="section-1"[^>]*>([^<]*)<\/p>/' => '<h3 class="section-1">$1</h3>', + '/<p class="title-1"[^>]*>([^<]*)<\/p>/' => '<h3 class="title-1">$1</h3>', + '/<p class="title-2"[^>]*>([^<]*)<\/p>/' => '<h4 class="title-2">$1</h4>', + '/<p class="title-3"[^>]*>([^<]*)<\/p>/' => '<h5 class="title-3">$1</h5>', + '/<p class="section-2"[^>]*>([^<]*)<\/p>/' => '<h4 class="section-2">$1</h4>', + '/<p class="section-3"[^>]*>([^<]*)<\/p>/' => '<h5 class="section-3">$1</h5>', + '/([\s]+)?\(([\s]+)?CONTENT BOX HEADING([\s]+)?\)/i' => '', + ); + + $html = preg_replace(array_keys($replacements), array_values($replacements), $html); + $html = str_replace('<table>', '<table class="zentable cool">', $html); + return $html; + } + + /** + * Most majors contain simple tables in text with credits that add up to a total. + * + * These "leaders" are used as a series of dots (broder-bottom: dotted..), + * which leads the user to the credits at the end of the line. + * + * @param string $html Basic markup + * + * @return string html + */ + public static function addLeaders($html) + { + $html = preg_replace('/<br \/>/', ' ', $html); + $html = preg_replace('/<p class="(requirement-sec-[1-3])"[^>]*>(.*)\s([\d]{1,2})[\s]*<\/p>/', '<p class="$1"><span class="req_desc">$2</span><span class="leader"></span><span class="req_value">$3</span></p>', $html); + $html = preg_replace('/<p class="(requirement-sec-[1-4]\-ledr)"[^>]*>(.*)\s([\d]{1,2})[\s]*<\/p>/', '<p class="$1"><span class="req_desc">$2</span><span class="leader"></span><span class="req_value">$3</span></p>', $html); + $html = preg_replace('/<p class="(requirement-sec-[1-3]\-note)"[^>]*>(.*)\s([\d]{1,2})[\s]*<\/p>/', '<p class="$1"><span class="req_desc">$2</span><span class="leader"></span><span class="req_value">$3</span></p>', $html); + $html = preg_replace('/<p class="(requirement-sec-[1-3])"[^>]*>(.*)\s([\d]{1,2}\-[\d]{1,2})[\s]*<\/p>/', '<p class="$1"><span class="req_desc">$2</span><span class="leader"></span><span class="req_value">$3</span></p>', $html); + $html = preg_replace('/<p class="(requirement-sec-[1-4]\-ledr)"[^>]*>(.*)\s([\d]{1,2}\-[\d]{1,2})[\s]*<\/p>/', '<p class="$1"><span class="req_desc">$2</span><span class="leader"></span><span class="req_value">$3</span></p>', $html); + $html = preg_replace('/<p class="(requirement-sec-1)"[^>]*>(.*)\s([\d]{2,3})[\s]*<\/p>/', '<p class="$1"><span class="req_desc">$2</span><span class="leader"></span><span class="req_value">$3</span></p>', $html); + $html = preg_replace('/<p class="(abbreviations-list)"[^>]*>(((\sor\s)?[^\s]+)+)\s([^<]*)(<span.*>.*<\/span>)?<\/p>/', '<p class="$1"><span class="req_desc">$2</span><span class="leader"></span><span class="req_value">$5 $6</span></p>', $html); + return $html; + } + + + /** + * Find courses found within the text + * + * @param string $text Text to scan for links + * @param callback $callback Method to call with matches + * + * @return string + */ + protected static function courseScanCallback($text, $callback) + { + $text = preg_replace_callback('/' + . "([A-Z]{3,4}) # subject code, eg: CSCE \n" + . "(" + . "(" + . "(,?\s+) # eg: 340, 440 \n" + . "|(\/) # eg: 340\/440 \n" + . "|(,?\ or\ ) # eg: , 340 or 440 \n" + . "|(,?\ \&(amp\;)?\ ) # eg: , 340 & 440 \n" + . "|(,?\ and\ ) # eg: , 340 and 440 \n" + . "|(,?\ and\/or\ ) # eg: , 340 and\/or 440 \n" + . ")" + . "([0-9]{2,3}[A-Z]?) # course number, with optional letter \n" + . ")+" + . "([\.\s\<\,\;\/\)]|$) # characters which signal the end of a course sequence \n" + . "/x", + $callback, $text); + + return $text; + } + + /** + * Find courses within a block of raw text + * + * @param string $text Generic text with inline course data + * + * @return array Subject codes and course numbers, e.g. array('AGRO'=>array('153')) + */ + public static function findCourses($text) + { + $courses = array(); + $callback = function($matches) use (&$courses) { + if (!UNL_UndergraduateBulletin_EPUB_Utilities::isValidSubjectCode($matches[1])) { + return; + } + + preg_match_all('/0?([0-9]{2,4}[A-Z]?)/', $matches[0], $course_numbers); + + foreach ($course_numbers[0] as $course_number) { + if (!isset($courses[$matches[1]]) + || !in_array($course_number, $courses[$matches[1]])) { + $courses[$matches[1]][] = $course_number; + } + } + }; + + self::courseScanCallback($text, $callback); + return $courses; + } + + /** + * Find MISSING courses within a block of raw text + * + * @param string $text Generic text with inline course references + * + * @return array Subject codes and course numbers, e.g. array('AGRO'=>array('153')) + */ + public static function findUnknownCourses($text) + { + $missing_courses = array(); + + foreach (self::findCourses($text) as $subject_code => $courses) { + try { + $subject = new UNL_Services_CourseApproval_SubjectArea($subject_code); + foreach ($courses as $course) { + try { + // try and get the listing + $check_course = $subject->courses[$course]; + unset($check_course); + } catch (Exception $e) { + $missing_courses[$subject_code][] = $course; + } + } + } catch (Exception $e) { + // Missing subject code, all the courses are unknown + $missing_courses[$subject_code] = $courses; + } + unset($subject); + } + return $missing_courses; + } + + /** + * Link courses found within the text + * + * @param string $text Text to scan for links + * @param string $url Base URL for call links + */ + public static function addCourseLinks($text, $url = '') + { + return self::courseScanCallback($text, function ($matches) use ($url) { + return Utilities::linkCourse($matches, $url); + }); + } + + /** + * callback for the linkURLs preg match function + * + * @param array $matches + */ + public static function linkHref($matches) + { + $href = $matches[0]; + $link_end = ''; + $done = false; + while (!$done) { + $last_char = substr($href, -1); + switch ($last_char) { + case '.': + case ',': + case ')': + case ':': + $href = substr($href, 0, -1); + $link_end = $last_char . $link_end; + $done = false; + break; + default: + $done = true; + } + } + return '<a href="'.$href.'">'.$href.'</a>'.$link_end; + } + + /** + * callback for the course preg match function + * + * @param array $matches [0] is the full match, [1] is the subject code. + */ + public static function linkCourse($matches, $url = false) + { + + if (!self::isValidSubjectCode($matches[1])) { + return $matches[0]; + } + + if (!$url) { + $url = Controller::getURL(); + } + + $matches[0] = preg_replace( + array('/0?([0-9]{2,4}[A-Z]?)/', '/([A-Z]{3,4})\s+(\<a[^>]+\>)/'), + array('<a class="course" href="'.$url.'courses/'.$matches[1].'/$1">$0</a>', '$2$1 '), + $matches[0] + ); + + return $matches[0]; + } + + /** + * Check if a subject code is valid + * @todo Check against official subject codes + * + * @param string $code Subject code, e.g. CSCE + */ + public static function isValidSubjectCode($code) + { + switch ($code) { + case 'ACE': + case 'ACT': + case 'GPA': + case 'III': + case 'OEFL': // TOEFL + case 'SAT': + case 'PKI': // PKI 201E, Omaha building address + case 'CBA': + case 'DMIN': // PUB ADMIN (UNO courses) + case 'UNL': + case 'OURS': // HOURS + case 'OTAL': // TOTAL + case 'IMUM': // MINIMUM 15 HOURS + return false; + } + + return true; + } + + /** + * Updates a subject's course number in the supplied string. + * + * @param string $subject + * @param string $originalCourseNumeber + * @param string $newCourseNumber + * @return string + */ + public static function updateCourseNumber($text, $subject, $originalCourseNumeber, $newCourseNumber) + { + return self::courseScanCallback($text, function($matches) use ($subject, $originalCourseNumeber, $newCourseNumber) { + if (!self::isValidSubjectCode($matches[1]) || $matches[1] != $subject) { + return $matches[0]; + } + + return preg_replace('/(' . $originalCourseNumeber . ')([^0-9A-Z]|$)/', $newCourseNumber . '$2', $matches[0]); + }); + } +} diff --git a/www/templates/html/CourseFilters.tpl.php b/www/templates/html/CourseFilters.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..3b27b46fd2eb90309a2fc105a30ef52d4baed3ae --- /dev/null +++ b/www/templates/html/CourseFilters.tpl.php @@ -0,0 +1,65 @@ +<?php +$idPrefix = ''; +if (isset($context->subject)) { + $idPrefix = $context->subject; +} +?> +<div class="zenbox energetic wdn_filterset"> + <h3>Filter these Courses</h3> + <form method="post" action="#" id="<?php echo $idPrefix; ?>_filters" class="filters courseFilters"> + <?php if (isset($context->groups) + && count($context->groups)) : ?> + <fieldset class="groups"> + <legend><span>Groups</span></legend> + <ol> + <li> + <input type="checkbox" checked="checked" class="filterAll" id="<?php echo $idPrefix; ?>_filterAllGroups" name="all" value="all" /> + <label for="<?php echo $idPrefix; ?>_filterAllGroups">All groups</label></li> + <?php foreach ($context->groups as $group) : ?> + <li> + <input type="checkbox" id="<?php echo $idPrefix; ?>_filter_grp_<?php echo md5($group); ?>" value="grp_<?php echo md5($group); ?>" /> + + <label for="<?php echo $idPrefix; ?>_filter_grp_<?php echo md5($group); ?>"><?php echo $group; ?></label> + </li> + <?php endforeach; ?> + </ol> + </fieldset> + <?php endif; ?> + <fieldset class="formats"> + <legend><span>Course Formats</span></legend> + <ol> + <li><input type="checkbox" checked="checked" class="filterAll" id="<?php echo $idPrefix; ?>_filterAllFormats" name="all" value="all" /> + <label for="<?php echo $idPrefix; ?>_filterAllFormats">All formats</label></li> + <?php foreach (array( +'lec'=>'Lecture', +'lab'=>'Lab', +'quz'=>'Quiz', +'rct'=>'Recitation', +'stu'=>'Studio', +'fld'=>'Field', +'ind'=>'Independent Study', +'psi'=>'Personalized System of Instruction') as $key => $type) : ?> + <li> + <input type="checkbox" id="<?php echo $idPrefix; ?>_filter_format_<?php echo $key; ?>" value="<?php echo $key; ?>" /> + <label for="<?php echo $idPrefix; ?>_filter_format_<?php echo $key; ?>"><?php echo $type; ?></label> + </li> + <?php endforeach; ?> + </ol> + </fieldset> + <fieldset class="ace_outcomes"> + <legend><span>ACE Outcomes</span></legend> + <ol> + <li> + <input type="checkbox" id="<?php echo $idPrefix; ?>_filterAllACE" class="filterAll" checked="checked" name="allace" value="all" /> + <label for="<?php echo $idPrefix; ?>_filterAllACE">All ACE</label></li> + <?php for ($i=1;$i<=10;$i++) : ?> + <li> + <input type="checkbox" id="<?php echo $idPrefix; ?>_filter_ace_<?php echo $i; ?>" value="ace_<?php echo $i; ?>" /> + + <label for="<?php echo $idPrefix; ?>_filter_ace_<?php echo $i; ?>"><?php echo $i.' '.UNL\Catalog\ACE::$descriptions[$i]; ?></label> + </li> + <?php endfor; ?> + </ol> + </fieldset> + </form> +</div> \ No newline at end of file diff --git a/www/templates/html/UNL/Catalog/Course/Credits.tpl.php b/www/templates/html/UNL/Catalog/Course/Credits.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..0ef8b62b6c83336f5d2c167d1b5d988cec56b2f1 --- /dev/null +++ b/www/templates/html/UNL/Catalog/Course/Credits.tpl.php @@ -0,0 +1,39 @@ +<?php +/* @var $context UNL_Services_CourseApproval_Course */ + +// Set a default value +$credits = 'N/A'; + +if (isset($context->credits)) { + if (isset($context->credits['Single Value'])) { + $credits = $context->credits['Single Value']; + } elseif (isset($context->credits['Lower Range Limit'])) { + if (isset($context->credits['Lower Range Limit'])) { + $credits = $context->credits['Lower Range Limit'].'-'; + } + if (isset($context->credits['Upper Range Limit'])) { + $credits .= $context->credits['Upper Range Limit'].','; + } + } + $credits = trim($credits, ', '); + echo ' + <tr class="credits"> + <td class="label">Credit Hours:</td> + <td class="value">'.$credits.'</td> + </tr>'; + if (isset($context->credits['Per Semester Limit'])) { + echo ' + <tr class="credits limits"> + <td class="label">Max credits per semester:</td> + <td class="value">'.$context->credits['Per Semester Limit'].'</td> + </tr>'; + } + if (isset($context->credits['Per Career Limit'])) { + echo ' + <tr class="credits limits"> + <td class="label">Max credits per degree:</td> + <td class="value">'.$context->credits['Per Career Limit'].'</td> + </tr>'; + } + +} diff --git a/www/templates/html/UNL/Catalog/SubjectArea.tpl.php b/www/templates/html/UNL/Catalog/SubjectArea.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..16c0137992c4ce4adcb369fb0a7801537b2aaf68 --- /dev/null +++ b/www/templates/html/UNL/Catalog/SubjectArea.tpl.php @@ -0,0 +1,29 @@ +<?php +if ($controller->options['model'] == 'UNL\Catalog\SubjectArea') { + $url = $controller->getURL(); + $page->doctitle = '<title>'.$context->subject.' | Bulletin | University of Nebraska-Lincoln</title>'; + $page->pagetitle = '<h1>'.$context->subject.'</h1>'; + $page->breadcrumbs = ' + <ul> + <li><a href="http://www.unl.edu/">UNL</a></li> + <li><a href="'.$url.'">Undergraduate Bulletin</a></li> + <li>'.$context->subject.'</li> + </ul> + '; +} +?> +<h2 class="sec_main" id="<?php echo $context->subject; ?>"> Courses of Instruction (<?php echo $context->subject; ?>)</h2> +<div class="wdn-grid-set"> + <div class="bp2-wdn-col-one-fourth"> + <?php echo $savvy->render($context, 'CourseFilters.tpl.php'); ?> + </div> + <div class="bp2-wdn-col-three-fourths"> + <dl> + <?php + foreach ($context->courses as $course) { + echo $savvy->render($course); + } + ?> + </dl> + </div> +</div> diff --git a/www/templates/html/UNL/Services/CourseApproval/Course.tpl.php b/www/templates/html/UNL/Services/CourseApproval/Course.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..74f9ed13c0a3e80e48fbe49fbfa488ff5f9611c1 --- /dev/null +++ b/www/templates/html/UNL/Services/CourseApproval/Course.tpl.php @@ -0,0 +1,176 @@ +<?php + $url = $controller->getURL(); + /* example code for isArchvied and getNewestURL(); + if(UNL_UndergraduateBulletin_Controller::isArchived()){ + echo "This version may be out of date. ".UNL_UndergraduateBulletin_Controller::getNewestURL(); + } + */ + + $class = 'course'; + if (isset($parent->context->subjectArea)) { + $subject = $parent->context->subjectArea; + } elseif (isset($parent->context->subject)) { + $subject = $parent->context->subject; + } else { + $subject = $context->getHomeListing()->subjectArea; + } + $listings = array(); + $crosslistings = array(); + $groups = array(); + + foreach ($context->codes as $listing) { + if ((string)$listing->subjectArea == (string)$subject) { + + if (!isset($permalink)) { + $permalink = $url.'courses/'.$subject.'/'.$listing->courseNumber; + } + + $listings[] = $listing->courseNumber; + if ($listing->hasGroups()) { + foreach ($listing->groups as $group) { + $groups[] = (string)$group; + $class .= ' grp_'.md5($group); + } + } + } else { + if (!isset($crosslistings[(string)$listing->subjectArea])) { + $crosslistings[(string)$listing->subjectArea] = array(); + } + $crosslistings[(string)$listing->subjectArea][] = $listing->courseNumber; + } + } + $groups = implode(', ', array_unique($groups)); + $cltext = ''; + foreach ($crosslistings as $cl_subject => $cl_numbers) { + $cltext .= ', '.$cl_subject.' '.implode('/', $cl_numbers); + } + $number_class = 'l'.count($listings); + sort($listings); + $listings = implode('/', $listings); + if (!empty($cltext)) { + $crosslistings = '<span class="crosslisting">'.trim($cltext, ', ').'</span>'; + } + + $format = ''; + foreach ($context->activities as $type => $activity) { + $class .= ' '.$type; + $format .= UNL_Services_CourseApproval_Course_Activities::getFullDescription($type); + if (isset($activity->hours)) { + $format .= ' '.$activity->hours; + } + $format .= ', '; + } + $format = trim($format, ', '); + + if (!empty($context->aceOutcomes)) { + $class .= ' ace'; + foreach ($context->aceOutcomes as $outcome) { + $class .= ' ace_'.$outcome; + } + } + + if ($controller->options['model'] == 'UNL\Catalog\Course') { + Controller::setReplacementData('head', ' + <link rel="alternate" type="text/xml" href="'.$permalink.'?format=xml" /> + <link rel="alternate" type="text/javascript" href="'.$permalink.'?format=json" /> + <link rel="alternate" type="text/html" href="'.$permalink.'?format=partial" />'); + Controller::setReplacementData('doctitle', $subject.' '.$listings.': '.$context->title.' | Undergraduate Bulletin | University of Nebraska-Lincoln'); + Controller::setReplacementData('breadcrumbs', ' + <ul> + <li><a href="http://www.unl.edu/">UNL</a></li> + <li><a href="'.$url.'">Undergraduate Bulletin</a></li> + <li>'.$subject.' '.$listings.': '.$context->title.'</li> + </ul> + '); + echo '<dl>'; + } + + echo " + <dt class='$class'> + <div class='courseID'> + <span class='subjectCode'>".$subject."</span> + <span class='number $number_class'>$listings</span> + </div> + <span class='title'>" . $context->title . " <a href='" . $permalink . "' title='A permalink to " . $context->title . "'>LINK</a></span>"; + if (!empty($crosslistings)) { + echo '<span class="crosslistings">Crosslisted as '.$crosslistings.'</span>'; + } + echo "</dt> + <dd class='$class'> + <div class='wdn-grid-set'> + <div class='bp2-wdn-col-two-thirds bp3-wdn-col-three-fourths'>"; + + if (!empty($context->prerequisite)) { + echo "<div class='prereqs'>Prereqs: ".UNL\Catalog\Course\Utilities::addCourseLinks($context->getRaw('prerequisite'), $controller->getURL())."</div>\n"; + } + if (!empty($context->notes)) { + echo "<div class='notes'>".UNL\Catalog\Course\Utilities::addCourseLinks($context->getRaw('notes'), $controller->getURL())."</div>\n"; + } + if (!empty($context->description)) { + echo "<div class='description'>".UNL\Catalog\Course\Utilities::addCourseLinks($context->getRaw('description'), $controller->getURL())."</div>\n"; + } + $subsequent_courses = $context->getSubsequentCourses($course_search_driver->getRawObject()); + if (count($subsequent_courses)) { + echo "<div class='subsequent'>This course is a prerequisite for: "; + $sub_course_array = array(); + foreach ($subsequent_courses as $subsequent_courses) { + $sub_course_array[] = $subsequent_courses->getHomeListing()->subjectArea.' '.$subsequent_courses->getHomeListing()->courseNumber; + } + echo UNL\Catalog\Course\Utilities::addCourseLinks(implode(', ', $sub_course_array), $controller->getURL()); + echo "</div>\n"; + } + echo "</div>"; // Close the text content + echo '<div class="bp2-wdn-col-one-third bp3-wdn-col-one-fourth details">'; + echo '<table class="zentable cool details">'; + echo $savvy->render($context, 'UNL/Catalog/Course/Credits.tpl.php'); + if (!empty($format)) { + echo '<tr class="format"> + <td class="label">Course Format:</td> + <td class="value">'.$format.'</td> + </tr>'; + } + if (count($context->campuses) + && (count($context->campuses) > 1 + || $context->campuses[0] != 'UNL')) { + $campuses = ''; + foreach ($context->campuses as $campus) { + $campuses .= $campus . ','; + } + $campuses = trim($campuses, ','); + echo '<tr class="campus"> + <td class="label">Campus:</td> + <td class="value">'.$campus.'</td> + </tr>'; + } + $methods = ''; + foreach ($context->deliveryMethods as $method) { + $methods .= $method . ', '; + } + $methods = trim($methods, ', '); + echo '<tr class="deliveryMethods"> + <td class="label">Course Delivery:</td> + <td class="value">'.$methods.'</td> + </tr>'; + $ace = ''; + if (!empty($context->aceOutcomes)) { + $ace = ''; + foreach($context->aceOutcomes as $outcome) { + $ace .= '<abbr title="'.UNL_UndergraduateBulletin_ACE::$descriptions[$outcome].'">'.$outcome.'</abbr>, '; + } + $ace = trim($ace, ', '); + echo '<tr class="aceOutcomes"> + <td class="label">ACE Outcomes:</td> + <td class="value">'.$ace.'</td> + </tr>'; + } + if (!empty($groups)) { + echo '<tr class="groups"> + <td class="label">Groups:</td> + <td class="value">'.$groups.'</td> + </tr>'; + } + echo '</table></div>'.PHP_EOL; + echo "</div></dd>"; + if ($controller->options['model'] == 'UNL\Catalog\Course') { + echo '</dl>'; + }