@@ -125,54 +125,55 @@ function school_cache($schools)
* want to run all crawlers ;-).
*/
if (!empty($school['crawled']) && !isset($school['crawled_notreally']))
{
$cache_auto_school_dir_name = $cache_auto_dir_name . $school['id'] . DIRECTORY_SEPARATOR;
if (!is_dir($cache_auto_school_dir_name))
if (!mkdir($cache_auto_school_dir_name, 0755, TRUE))
error_log('Unable to create needed directory: `' . $cache_auto_dir_name . '\'');
}
$semesters = array();
$semester_weights = 0;
/*
* Try to presort the semesters into the proper order based
* on time_start/time_end. We want the older semesters to be
* nearer to the end of the list. This way, the crawler
* doesn't have to decide how to sort the semesters itself:
usort($semesters, 'school_cache_semesters_sort');
foreach ($school['crawled_semesters'] as $semester)
$semesters[$semester->id()] = array(
'id' => $semester->id(),
'time_start' => $semester->time_start_get(),
'time_end' => $semester->time_end_get(),
'weight' => $semester_weights ++,
'name' => $semester->name_get(),
);
'popular_course_id' => $semester->popular_course_id_get(),
} /* foreach ( => $semester) */
* Store/cache the semester metadata:
$semesters_file = fopen($cache_auto_school_dir_name . '-semesters', 'wb');
fwrite($semesters_file, serialize($semesters));
fclose($semesters_file);
uasort($list_cache, 'school_cmp');
$cache = array('list' => $list_cache, 'domains' => $domain_cache);
$cache_file_name = $cache_dir_name . 'schools';
$cache_file = fopen($cache_file_name, 'wb');
if ($cache_file === FALSE)
fprintf(STDERR, "Unable to open `%s' for writing\n",
$cache_file_name);
return 1;
fwrite($cache_file, serialize($cache));
fclose($cache_file);
@@ -59,73 +59,82 @@ class Semester
* Semester::SEASON_FALL, Semester::SEASON_SPRING, or
* Semester::SEASON_SUMMER if possible.
* \param $time_start
* Specify a timestamp which roughly estimates when this semester
* starts to aid the algorithm for guessing the current
* semester. See Semester::time_start_set(), which may be used
* instead of this parameter
* \param $time_end
* This may be specified now or via Semester::time_end_set().
function __construct($year, $season, $time_start = 0, $time_end = 0)
$this->time_start = 0;
$this->time_end = 0;
$this->time_starts = array();
$this->time_ends = array();
$this->season = $season;
if (strlen($year) != 4 || !is_numeric($year))
throw new ErrorException('Attempt to construct a Semester with an invalid year. The given year is `' . $year . '\'');
$this->year = $year;
$this->departments = array();
$this->department_names = array();
* For $this->popular_course_get().
$this->course_num_sections_map = array();
/**
* \brief
* Add a class to this Semester.
*
* \param $class
* The class/course to add.
public function class_add(Course $class)
public function class_add(Course $course)
$class_parts = Course::parse($class->getName());
if (!isset($class_parts['course']))
throw new ErrorException('I was given a class with an invalid name: `' . $class->getName() . '\'');
$course_parts = Course::parse($course->getName());
if (!isset($course_parts['course']))
throw new ErrorException('I was given a class with an invalid name: `' . $course->getName() . '\'');
foreach ($class as $course_slot)
foreach ($course as $course_slot)
foreach ($course_slot as $section)
foreach ($section as $meeting)
$this->time_set_section_meeting($meeting);
if (!isset($this->departments[$class_parts['department']]))
$this->departments[$class_parts['department']] = array();
$department =& $this->departments[$class_parts['department']];
$this->_section_count($course, $section);
$department[$class_parts['course']] = $class;
if (!isset($this->departments[$course_parts['department']]))
$this->departments[$course_parts['department']] = array();
$department =& $this->departments[$course_parts['department']];
$department[$course_parts['course']] = $course;
* Retrieve a class.
* \param $dept
* The class's department. 'CS' for 'CS-262'.
* The course/class number. '262' for 'cs-262'.
* \return
* A Course or NULL if not found.
public function class_get($dept, $class)
if (!isset($this->departments[$dept][$class]))
return NULL;
return $this->departments[$dept][$class];
* Gets a list of departments available in this semester.
@@ -223,48 +232,50 @@ class Semester
* does not have the concept of course slots. Ask binki for help
* figuring this out. Course slots are a sort of
* inverse/complement to section_meetings.
public function section_add($dept, $class, Section $section, $title = NULL, $course_slot_id = 'default')
$dept = strtoupper($dept);
$class = strtoupper($class);
if (!isset($this->departments[$dept])
|| !isset($this->departments[$dept][$class]))
$classobj = new Course($dept . '-' . $class, $title);
$this->class_add($classobj);
else
$classobj = $this->departments[$dept][$class];
$classobj->section_add($section, $course_slot_id);
$this->_section_count($classobj, $section);
* Add a section_meeting, calling Semester::section_add() as
* necessary.
* To be used by crawlers when parsing data which only presents one
* section_meeting at a time. I.e., when they do tabular data right.
* The department this section_meeting's course belongs to.
* \param $course
* The course number this section_meeting's section belongs to.
* \param $title
* The course title of the given course the section_meeting or
* NULL. If the section has already been added, this parameter
* will be ignored.
* \param $section
* The letter or numbers which make up the section's name.
* \param $synonym
* The section synonym or NULL.
* \param $section_meeting
* The SectionMeeting to be added to a section which may or may
@@ -278,48 +289,88 @@ class Semester
public function section_meeting_add($dept, $course, $title, $section, $synonym, SectionMeeting $section_meeting, $course_slot_id = 'default', $credit_hours = -1.0)
$this->time_set_section_meeting($section_meeting);
$course = strtoupper($course);
if (empty($this->departments[$dept][$course]))
$course_obj = NULL;
$course_obj = $this->departments[$dept][$course];
$section_obj = $course_obj->section_get($section);
if (empty($course_obj) || empty($section_obj))
return $this->section_add($dept, $course, new Section($section, array($section_meeting), $synonym, $credit_hours), $title, $course_slot_id);
$section_obj->meeting_add($section_meeting);
return;
* Account for the addition of a new section to this Semester.
* The course this section is a part of.
* The section.
private function _section_count(Course $course, Section $section)
$fully_qualified_course_id = $course->getName();
$this->course_num_sections_map += array($fully_qualified_course_id => 0);
$this->course_num_sections_map[$fully_qualified_course_id] ++;
* Get the most popular course.
* The fully-qualified course_id of the most popular course in
* this Semester.
public function popular_course_id_get()
if (count($this->course_num_sections_map))
arsort($this->course_num_sections_map, SORT_NUMERIC);
reset($this->course_num_sections_map);
$this->_popular_course = key($this->course_num_sections_map);
if (!isset($this->_popular_course))
/* The default popular course */
$this->_popular_course = 'ENGL-101';
return $this->_popular_course;
* Update the time_end.
* The time_end is a unix timestamp roughly estimating the time at
* which a semester starts. It is used when guessing what semester a
* user is interested in.
* The new time_end.
public function time_end_set($time_end)
$this->time_end = $time_end;
* Set the time_end only if it would make the semester end later.
* Useful for crawler scripts incrementally guessing the endtime of
* a semester.
* The new time_end to consider.
@@ -506,26 +557,35 @@ class Semester
* Enumerate all valid seasons.
public static function seasons_get_all()
return array(self::SEASON_SPRING, self::SEASON_SUMMER, self::SEASON_FALL);
* Clean the semester of all sections, keeping metadata intact.
public function purge()
* Make sure that time_end is set to the proper end time before
* clearing out the pool in the time_ends array.
$this->time_end_get();
$this->time_start_get();
* A mapping which keeps track of how many sections any given
* course has, with course_id '-' section_id as the key and the
* count as the value. Used to calculate the most frequently-used
* course_id to use as the example course_id (bug #102).
$this->popular_course_id_get();
@@ -93,50 +93,52 @@ elseif (!empty($_REQUEST['e']))
$school = school_load($postData['school']);
if (!empty($school))
$inputPage_options['school'] = $school;
if (!empty($school) && !empty($postData['semester']))
$semesters = school_semesters($school);
if (!empty($semesters[$postData['semester']]))
$inputPage_options['semester'] = $semester;
$creating_new_schedule = FALSE;
* We cannot initialize the page object nor guess the school before
* figuring loading a saved schedule because we'll default to that
* saved_schedule's school/semester.
$scripts = array('jQuery', 'jQueryUI', 'qTip2', 'schedInput');
$inputPage = page::page_create('Enter Courses', $scripts, $inputPage_options);
$school = $inputPage->get_school();
$semester = $inputPage->semester_get();
if (empty($semesters))
$my_hc = 'var slate_permutate_example_course_id = ' . json_encode(school_example_course_id($school)) . ';
$my_hc = 'var slate_permutate_example_course_id = ' . json_encode(empty($semester) || empty($semester['popular_course_id']) ? school_example_course_id($school) : $semester['popular_course_id']) . ';
jQuery(document).ready(
function()
var class_last = 0;
';
if ($sch)
foreach ($sch->courses_get() as $course)
$my_hc .= input_course_js($course, ' ');
elseif ($errors_fix)
foreach ($_POST['postData'] as $course)
if (is_array($course))
$title = '';
if (!empty($course['title']))
$title = $course['title'];
if (empty($course['name']))
$my_hc .= ' class_last = add_class();' . PHP_EOL;
@@ -249,49 +251,49 @@ if (!empty($_REQUEST['selectsemester']))
$next_page = 'input.php?';
if (isset($_GET['s']))
$next_page .= 's=' . (int)$_GET['s'] . '&';
if (!empty($_GET['school']))
$next_page .= 'school=' . $_GET['school'] . '&';
$inputPage->showSemesters($next_page);
$inputPage->foot();
exit;
$inputPage->showSavedScheds($_SESSION);
?>
<?php if (!empty($input_warning_banner)): ?>
<div class="warning">
<?php echo $input_warning_banner; ?>
</div>
<?php endif; ?>
<p>
Welcome to SlatePermutate<?php $inputPage->addressStudent(', ', '', FALSE); ?>!
<?php if (school_has_auto($inputPage->get_school())): ?>
To get started, enter in a course identifier (e.g., <em>
<?php echo school_example_course_id($inputPage->get_school()); ?></em>)
<?php echo empty($semester) || empty($semester['popular_course_id']) ? school_example_course_id($inputPage->get_school()) : $semester['popular_course_id']; ?></em>)
and click the autosuggestion to automatically load available sections
for each class.
<?php else: ?>
To get started, enter a course number and add some sections to it.
Then specify each section's letter/number and what times it meets,
add more courses, and click “Find a Schedule”.
<!--'-->
</p>
<form method="post" action="process.php" id="scheduleForm">
<p class="nospace" style="border-left: 5px solid #999; padding-left: 5px!important; padding-top: 5px!important;"><label>Schedule Name</label><br />
<input
id="scheduleName"
style="margin-bottom: 1em;"
class="defText required input-submit-disable"
type="text"
size="25"
title="My <?php echo $semester['name']; ?> Schedule"
name="postData[name]"
<?php
echo 'value="' . htmlentities($sch->getName(), ENT_QUOTES) . '"';
Status change: