Changeset - 146ce01816f1
[Not reviewed]
default
0 12 0
Nathan Brink (binki) - 14 years ago 2012-03-28 21:52:09
ohnobinki@ohnopublishing.net
Store the start and end dates of each section meeting. (Phase 1 in bug #122).
12 files changed with 157 insertions and 63 deletions:
0 comments (0 inline, 0 general)
inc/class.schedule.php
Show inline comments
 
@@ -168,78 +168,87 @@ class Schedule
 
   *   Adds a new class to the schedule.
 
   *
 
   * \param $slot
 
   *   Currently, the Schedule class is not smart enough to understand
 
   *   CourseSlots. At a lower level, we split Courses with multiple
 
   *   CourseSlots into multiple Course objects with redundant
 
   *   information.
 
   */
 
  function addCourse($course_id, $title)
 
  {
 
    $this->courses[] = new Course($course_id, $title);
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Adds a section to this semester after finding the class.
 
   *
 
   * \param $instructor
 
   *   The instructor of this section/section_meeting.
 
   *
 
   * \return
 
   *   NULL on success, a string on error which is a message for the
 
   *   user and a valid XHTML fragment.
 
   */
 
  function addSection($course_name, $letter, $time_start, $time_end, $days, $synonym = NULL, $instructor = NULL, $location = NULL, $type = 'lecture', $slot = 'default', $credit_hours = -1.0)
 
  function addSection($course_name, $letter, $time_start, $time_end, $days, $synonym = NULL, $instructor = NULL, $location = NULL, $type = 'lecture', $slot = 'default', $credit_hours = -1.0, $date_start = NULL, $date_end = NULL)
 
  {
 
    if (empty($letter) && (empty($time_start) || !strcmp($time_start, 'none')) && (empty($time_end) || !strcmp($time_end, 'none')) && empty($days)
 
	&& empty($synonym) && empty($instructor) && empty($location) && (empty($type) || !strcmp($type, 'lecture'))
 
	&& (empty($slot) || !strcmp($slot, 'default')))
 
      return;
 

	
 
    /* reject invalid times */
 
    if (!strcmp($time_start, 'none') || !strcmp($time_end, 'none')
 
	|| $time_start > $time_end)
 
      {
 
	return 'Invalid time specifications for ' . htmlentities($course_name) . '-' . htmlentities($letter)
 
	  . '. Start time: <tt>' . htmlentities($time_start) . '</tt>. End time: <tt>' . htmlentities($time_end) . '</tt>.';
 
      }
 

	
 
    if (!empty($credit_hours) && !is_numeric($credit_hours))
 
      {
 
        return 'Invalid credit-hour specification of <tt>' . htmlentities($credit_hours) . '</tt> for ' . htmlentities($course_name) . '-' . htmlentities($letter) . '. Please use a floating point number or do not enter anything if the number of credit hours is not known.';
 
      }
 

	
 
    if (empty($date_start) != empty($date_end)
 
        || !empty($date_start) && !is_numeric($date_start)
 
        || !empty($date_end) && !is_numeric($date_end))
 
      {
 
        return 'Invalid date range specification of <tt>' . htmlentities($date_start, ENT_QUOTES)
 
          . '</tt> through <tt>' . htmlentities($date_end, ENT_QUOTES)
 
          . '</tt>. Was expecting two valid unix timestamps or two empty values.';
 
      }
 

	
 
    foreach ($this->courses as $course)
 
      if (!strcmp($course_name, $course->getName()))
 
	{
 
	  $section = $course->section_get($letter);
 
	  if (!$section)
 
	    {
 
              $section = new Section($letter, array(), $synonym, $credit_hours);
 
	      $course->section_add($section, $slot);
 
	    }
 
	  $section->meeting_add(new SectionMeeting($days, $time_start, $time_end, $location, $type, $instructor));
 
	  $section->meeting_add(new SectionMeeting($days, $time_start, $time_end, $location, $type, $instructor, $date_start, $date_end));
 

	
 
	  return;
 
	}
 

	
 
    error_log('Could not find class when parsing schedule from postData: ' . $course_name);
 
    echo 'Could not find class: ' . $course_name . "<br />\n";
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Get the school associated with this schedule.
 
   *
 
   * \return
 
   *   The school associated with this schedule or some fallback.
 
   */
 
  public function school_get()
 
  {
 
    $school = NULL;
 

	
 
    if (!empty($this->school_id))
 
      /*
 
       * May return NULL, so we don't just return this value right
 
       * away -- we fall through.
 
       */
inc/class.section.php
Show inline comments
 
@@ -2,95 +2,106 @@
 
/*
 
 * Copyright 2010 Nathan Gelderloos, Ethan Zonca, Nathan Phillip Brink
 
 *
 
 * This file is part of SlatePermutate.
 
 *
 
 * SlatePermutate is free software: you can redistribute it and/or modify
 
 * it under the terms of the GNU Affero General Public License as published by
 
 * the Free Software Foundation, either version 3 of the License, or
 
 * (at your option) any later version.
 
 *
 
 * SlatePermutate is distributed in the hope that it will be useful,
 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
 * GNU Affero General Public License for more details.
 
 *
 
 * You should have received a copy of the GNU Affero General Public License
 
 * along with SlatePermutate.  If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
require_once dirname(__FILE__) . DIRECTORY_SEPARATOR . 'class.section_meeting.inc';
 
   
 
/**
 
 * \brief
 
 *   Represent a Section associated with a Course.
 
 *
 
 * Iterating over a Section yields section_SectionMeeting objects.
 
 */
 
class Section
 
class Section implements IteratorAggregate
 
{
 

	
 
  private $letter;	// Section letter
 
  private $prof;	// Professor, preserved for Section::__wakeup()
 

	
 
  /* meeting times, array of SectionMeeting */
 
  private $meetings;
 

	
 
  /* the section synonym which uniquely identifies this section/course combination */
 
  private $synonym;
 
  /**
 
   * \brief
 
   *   The number of credit hours this course has.
 
   */
 
  private $credit_hours;
 

	
 
  /**
 
   * \brief
 
   *   Construct a Section.
 
   *
 
   * \param $letter
 
   *   The identifier (often a letter or numeral) of this section. For
 
   *   CS-262-A, this would be 'a'.
 
   * \param $section_meetings
 
   *   An array of SectionMeeting objects which describe all the
 
   *   different types of meetings this particular section has. It
 
   *   will be very uncommon for a course to have more than one such
 
   *   meeting time for a section. For example, Calvin doesn't have
 
   *   this. Another example, Cedarville lists different meeting times
 
   *   inside of a single section. Cedarville also lists all lectures
 
   *   and lab meeting times directly in a section's listing.
 
   * \param $synonym
 
   *   Some schools have a unique number for each section. This field
 
   *   is for that number.
 
   * \param $credit_hours
 
   *   The number of credit hours this course is worth.
 
   */
 
  function __construct ($letter, array $section_meetings = array(), $synonym = NULL, $credit_hours = -1.0)
 
  {
 
    $this->letter = $letter;
 
    $this->meetings = $section_meetings;
 
    $this->synonym = $synonym;
 
    $this->credit_hours = (float)$credit_hours;
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Implements the IteratorAggregate interface.
 
   */
 
  public function getIterator()
 
  {
 
    return new ArrayIterator($this->meetings);
 
  }
 

	
 
  public function getLetter()
 
  {
 
    return $this->letter;
 
  }
 

	
 
  /**
 
   * \return
 
   *   This section's synonym -- a unique numeric identifier for this
 
   *   course. NULL if undefined.
 
   */
 
  public function getSynonym()
 
  {
 
    return $this->synonym;
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Get an array of section meetings for this section.
 
   *
 
   * \return
 
   *   An array of SectionMeeting objects.
 
   */
 
  public function getMeetings()
 
  {
inc/class.section_meeting.inc
Show inline comments
 
@@ -14,83 +14,94 @@
 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
 * GNU Affero General Public License for more details.
 
 *
 
 * You should have received a copy of the GNU Affero General Public License
 
 * along with SlatePermutate.  If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/**
 
 * \brief
 
 *   Represent a time/location that a Section meets during/at.
 
 *
 
 * A Calvin student might ask ``Why is there a need for a
 
 * SectionMeeting class? Doesn't every Section have a unique
 
 * prof/time/location?'' A Cedarville student would retort ``Huh?
 
 * Don't some of your classes have labs in addition to lecture? How do
 
 * you know that you have to go to both a lecture and lab -- and how
 
 * do you handle that some classes have different lecture times for
 
 * different days of the week?''. A Calvin section _is_ a unique
 
 * prof/time/location. At Cedarville, a Section refers to a prof and a
 
 * _set_ of time/location pairs. The generalization is to make each
 
 * Section support a list of meeting times/locations.
 
 */
 
class SectionMeeting
 
{
 
  private $date_start;
 
  private $time_start;
 
  private $date_end;
 
  private $time_end;
 
  private $days;
 
  private $location;
 
  private $instructor;
 

	
 
  /**
 
   * \brief
 
   *   Construct a SectionMeeting.
 
   *
 
   * \param $days
 
   *   A string of single-char day upon which a section meets. Sunday
 
   *   is represented with 'u', Monday with 'm', Tuesday with 't',
 
   *   Wednesday with 'w', Thursday with 'h', Friday with 'f', and
 
   *   Saturday with 's'.
 
   * \param $time_start
 
   *   The time of day when the section meeting starts. Use
 
   *   school_crawl_time_format() or ensure that the time is formatted
 
   *   in 24-hour, 0-padded, 4-digit form (HHMM).
 
   * \param $time_end
 
   *   The time of day when the section meeting ends.
 
   * \param $location
 
   *   Where the meeting occurs. Often a room number of some sort.
 
   * \param $type
 
   *   The type of meeting this is. For lectures, please use
 
   *   'lecture'. For labs, please use 'lab'. For others, use the
 
   *   school's notation.
 
   * \param $instructor
 
   *   The instructor for this section meeting.
 
   * \param $date_start
 
   *   A timestamp marking some time prior to the first occurence of
 
   *   the section_meeting.
 
   * \param $date_end
 
   *   A timestamp marking some time after the end of the last
 
   *   occurence of this section_meeting.
 
   */
 
  public function __construct($days, $time_start, $time_end, $location = NULL, $type = 'lecture', $instructor = NULL)
 
  public function __construct($days, $time_start, $time_end, $location = NULL, $type = 'lecture', $instructor = NULL, $date_start = NULL, $date_end = NULL)
 
  {
 
    $this->days_set($days);
 

	
 

	
 
    $this->date_start = empty($date_start) ? NULL : (int)$date_start;
 
    $this->time_start = $time_start;
 
    $this->date_end = empty($date_end) ? NULL : (int)$date_end;
 
    $this->time_end = $time_end;
 

	
 
    $this->location = $location;
 

	
 
    $this->type = $type;
 
    $this->instructor = $instructor;
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Take a days of week string and store it into our $days of week array.
 
   *
 
   * \param $days_str
 
   *   The days of the week in a string format. One char per
 
   *   day. Sun-Sat is represented with 'u', 'm', 't', 'w', 'h', 'f',
 
   *   's'.
 
   */
 
  private function days_set($days_str)
 
  {
 
    $this->days = array(0 => FALSE, 1 => FALSE, 2 => FALSE, 3 => FALSE, 4 => FALSE, 5 => FALSE, 6 => FALSE);
 

	
 
    $days_str_strlen = strlen($days_str);
 
    for ($i = 0; $i < $days_str_strlen; $i ++)
 
      $this->days[self::day_atoi($days_str[$i])] = TRUE;
 
@@ -176,48 +187,68 @@ class SectionMeeting
 
  {
 
    return $this->time_end;
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Get the type of section meeting this is.
 
   *
 
   * Examples of Section meeting types include 'lecture' and
 
   * 'lab'. Currently, any string may be used as a Section meeting
 
   * type; the possibilities for this field are controlled by the
 
   * crawl scripts authors' choices about what they pass to
 
   * SectionMeeting().
 
   *
 
   * \return
 
   *   A string indicating the type of section meeting.
 
   */
 
  public function type_get()
 
  {
 
    return $this->type;
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Return the unix timestamp of a time prior to the first section
 
   *   meeting or NULL if unknown.
 
   */
 
  public function date_start_get()
 
  {
 
    return empty($this->date_start) ? NULL : $this->date_start;
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Return the unix timestamp of a time after the last section
 
   *   meeting or NULL if unknown.
 
   */
 
  public function date_end_get()
 
  {
 
    return empty($this->date_end) ? NULL : $this->date_end;
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Check if this section conflicts with the given section.
 
   *
 
   * \param $that
 
   *   The other section for which I should check for conflicts.
 
   * \return
 
   *   TRUE if there is a conflict, FALSE otherwise.
 
   */
 
  public function conflictsWith(SectionMeeting $that)
 
  {
 
    /*
 
     * The two sections meetings can't conflict if the start/end times
 
     * don't overlap. Also, use >= or <= here so that one can say ``I
 
     * have gym from 10 through 11 and then latin from 11 though 12''.
 
     */	
 
    if ($this->getStartTime() >= $that->getEndTime()
 
	|| $this->getEndTime() <= $that->getStartTime())
 
      {
 
	return FALSE;
 
      }
 

	
 
    /*
 
     * Now we know that the sections meetings overlap in start/end
 
     * times. But if they don't both meet on the same day at least
 
     * once, they don't conflict.
 
@@ -228,56 +259,61 @@ class SectionMeeting
 
	  return TRUE;
 
      }
 

	
 
    /*
 
     * The sections meetings don't both share a day of the week.
 
     */
 
    return FALSE;
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Return an array of JSON keys specific to this section meeting
 
   *   time.
 
   *
 
   * Currently, the AJAX UI doesn't recognize that a given section may
 
   * have multiple meeting times. Thus, we simulate this by having
 
   * multiple instances of the same section but just with different
 
   * times in the UI.
 
   */
 
  public function to_json_array()
 
  {
 
    static $daymap = array(0 => 'm', 1 => 't', 2 => 'w', 3 => 'h', 4 => 'f', 5 => 's', 6 => 'u');
 

	
 
    $json_array = array(
 
      'date_start' => empty($this->date_start) ? NULL : $this->date_start,
 
			'time_start' => $this->time_start,
 
      'date_end' => empty($this->date_end) ? NULL : $this->date_end,
 
			'time_end' => $this->time_end,
 
			'days' => array(),
 
			'location' => $this->location,
 
			'instructor' => $this->instructor,
 
			'type' => $this->type,
 
			);
 

	
 
    for ($day = 0; $day < 7; $day ++)
 
      $json_array['days'][$daymap[$day]] = $this->getDay($day);
 

	
 
    return $json_array;
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Parse a JSON array into a SectionMeeting.
 
   *
 
   * \param $json_array
 
   *   The JSON array to parse.
 
   * \return
 
   *   A shiny, new SectionMeeting.
 
   */
 
  public static function from_json_array(array $json_array)
 
  {
 
    $json_array += array('date_start' => NULL, 'date_end' => NULL);
 
    $days = '';
 
    foreach ($json_array['days'] as $day => $meets)
 
      if ($meets)
 
	$days .= $day;
 
    return new SectionMeeting($days, $json_array['time_start'], $json_array['time_end'], $json_array['location'], $json_array['type'], $json_array['instructor']);
 
    return new SectionMeeting($days, $json_array['time_start'], $json_array['time_end'],
 
			      $json_array['location'], $json_array['type'], $json_array['instructor'],
 
			      $json_array['date_start'], $json_array['date_end']);
 
  }
 
}
inc/class.semester.inc
Show inline comments
 
@@ -74,48 +74,53 @@ class Semester
 
    $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();
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Add a class to this Semester.
 
   *
 
   * \param $class
 
   *   The class/course to add.
 
   */
 
  public function class_add(Course $class)
 
  {
 
    $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() . '\'');
 

	
 
    foreach ($class 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']];
 

	
 
    $department[$class_parts['course']] = $class;
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Retrieve a class.
 
   *
 
   * \param $dept
 
   *   The class's department. 'CS' for 'CS-262'.
 
   * \param $class
 
   *   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];
 
@@ -200,48 +205,51 @@ class Semester
 
   * \brief
 
   *   Utility function to add a section to the semester,
 
   *   automatically creating classes as necessary.
 
   *
 
   * Crawler functions should generally use this instead of
 
   * Semester::class_add().
 
   *
 
   * \param $dept
 
   *   The department this section belongs to.
 
   * \param $class
 
   *   The class this section belongs to.
 
   * \param $section
 
   *   The section itself.
 
   * \param $title
 
   *   The course human-friendly title.
 
   * \param $course_slot_id
 
   *   The slot of the course which this section should be added
 
   *   to. Use 'default' (or don't pass this parameter) if your school
 
   *   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')
 
  {
 
    foreach ($section as $meeting)
 
      $this->time_set_section_meeting($meeting);
 

	
 
    $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);
 
  }
 

	
 
  /**
 
   * \brief
 
   *   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.
 
@@ -249,48 +257,50 @@ class Semester
 
   * \param $dept
 
   *   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
 
   *   not already be in this Semester.
 
   * \param $course_slot_id
 
   *   The name of the new CourseSlot to create if the given section
 
   *   does not yet exist.
 
   * \param $credit_hours
 
   *   The number of credit hours of the associated course or a
 
   *   negative value if unknown.
 
   */
 
  public function section_meeting_add($dept, $course, $title, $section, $synonym, $section_meeting, $course_slot_id = 'default', $credit_hours = -1.0)
 
  {
 
    $this->time_set_section_meeting($section_meeting);
 

	
 
    $dept = strtoupper($dept);
 
    $course = strtoupper($course);
 

	
 
    if (empty($this->departments[$dept][$course]))
 
      $course_obj = NULL;
 
    else
 
      {
 
	$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;
 
  }
 

	
 
  /**
 
   * \brief
 
   *   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.
 
@@ -381,48 +391,65 @@ class Semester
 
   * The idea is that there might be erroneous entries in a school's
 
   * database (
 
   * http://www.facebook.com/CalvinRegistrar/posts/299438720070457 )
 
   * which would skew the detected start time. Use statistics to
 
   * detect and kill outliers by using a pool of endtimes :-D.
 
   */
 
  public function time_start_pool_add($time_start)
 
  {
 
    $this->time_starts[] = $time_start;
 
  }
 

	
 
  public function time_start_get()
 
  {
 
    if (count($this->time_starts))
 
      {
 
        $times = filter_outliers($this->time_starts);
 
        $this->time_end = min($times);
 
      }
 

	
 
    return $this->time_start;
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Consider a section_meeting's start and end dates and make
 
   *   appropriate time_start_set_test() and time_end_set_test()
 
   *   calls.
 
   */
 
  public function time_set_section_meeting(SectionMeeting $meeting)
 
  {
 
    $date_start = $meeting->date_start_get();
 
    if (!empty($date_start))
 
      $this->time_start_set_test($date_start);
 

	
 
    $date_end = $meeting->date_end_get();
 
    if (!empty($date_end))
 
      $this->time_end_set_test($date_end);
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Get a semester's year.
 
   */
 
  public function year_get()
 
  {
 
    return $this->year;
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Get a semester's season.
 
   */
 
  public function season_get()
 
  {
 
    return $this->season;
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Get a semester's friendly name:
 
   *
 
   * \return
 
   *   A string, the semester's friendly name.
 
   */
 
  public function name_get()
input.php
Show inline comments
 
@@ -133,49 +133,51 @@ if ($sch)
 
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;
 
	  else
 
	    $my_hc .= '    class_last = add_class_n(' . json_encode($course['name']) . ', ' . json_encode($title) . ');' . PHP_EOL;
 
	  foreach ($course as $section)
 
	    if (is_array($section))
 
	      $my_hc .= '    add_section_n(class_last, ' . json_encode($section['letter']) . ', '
 
		. json_encode($section['synonym']) . ', ' . json_encode($section['start']) . ', '
 
		. json_encode($section['end']) . ', '
 
		. json_encode(array('m' => !empty($section['days'][0]), 't' => !empty($section['days'][1]), 'w' => !empty($section['days'][2]),
 
				    'h' => !empty($section['days'][3]), 'f' => !empty($section['days'][4]),
 
				    's' => !empty($section['days'][5])))
 
		. ', ' . json_encode($section['professor']) . ', '
 
		. json_encode($section['location']) . ', '
 
		. json_encode($section['type']) . ', '
 
		. json_encode($section['slot']) . ', '
 
		. json_encode(isset($section['credit_hours']) ? $section['credit_hours'] : -1) . ');' . PHP_EOL;
 
		. json_encode(isset($section['credit_hours']) ? $section['credit_hours'] : -1) . ', '
 
		. json_encode(empty($section['date_start']) ? NULL : $section['date_start']) . ', '
 
		. json_encode(empty($section['date_end']) ? NULL : $section['date_end']) . ');' . PHP_EOL;
 
	  $my_hc .= PHP_EOL;
 
	}
 
  }
 
else
 
  {
 
    $default_courses = school_default_courses($school);
 
    foreach ($default_courses as $default_class)
 
      $my_hc .= input_course_js($default_class, '    ');
 
  }
 
$my_hc .= '    class_last = add_class();' . PHP_EOL;
 
if ($qtips_always || !isset($_SESSION['saw_qtips']))
 
  {
 
    $my_hc .= '    addTips();' . PHP_EOL;
 
    $_SESSION['saw_qtips'] = TRUE;
 
  }
 
$my_hc .= '  });
 
';
 

	
 
$inputPage->headcode_add('scheduleInput', $inputPage->script_wrap($my_hc), TRUE);
 

	
 
if ($school['id'] != 'default'
 
    && empty($_REQUEST['selectschool'])
 
    && empty($_REQUEST['selectsemester']))
 
  {
process.php
Show inline comments
 
@@ -197,52 +197,55 @@ if(!$DEBUG)
 
	$allClasses = new Schedule($name, $parent_schedule_id, $school, $semester);
 

	
 
	$errors = array();
 
	foreach($postData as $course)
 
	  {
 
	    /*
 
	     * Only add classes if the user added at least one
 
	     * section to the class. We know that $course['name']
 
	     * is not a section, so count() needs to be > 1 and
 
	     * we need to skip over 'name' in our loop.
 
	     */
 
	    if(is_array($course) && count($course) > 1)
 
	      {
 
		if (empty($course['title']))
 
		  $course['title'] = '';
 

	
 
		if (empty($course['credit_hours']))
 
		  $course['credit_hours'] = -1;
 
		$allClasses->addCourse($course['name'], $course['title']);
 

	
 
				foreach($course as $section)
 
				  /* Skip the section name, which isn't a section */
 
					if(is_array($section))
 
					  {
 
					    if (empty($section['slot']))
 
					      $section['slot'] = 'default';
 
					    $section += array(
 
					      'slot' => 'default',
 
					      'date_start' => NULL,
 
					      'date_end' => NULL,
 
					    );
 

	
 
					    $error_string = $allClasses->addSection($course['name'], $section['letter'], $section['start'], $section['end'], arrayToDays(empty($section['days']) ? array() : $section['days'], 'alpha'), $section['synonym'], $section['professor'], $section['location'], $section['type'], $section['slot'], $section['credit_hours']);
 
					    $error_string = $allClasses->addSection($course['name'], $section['letter'], $section['start'], $section['end'], arrayToDays(empty($section['days']) ? array() : $section['days'], 'alpha'), $section['synonym'], $section['professor'], $section['location'], $section['type'], $section['slot'], $section['credit_hours'], $section['date_start'], $section['date_end']);
 
					    if ($error_string !== NULL)
 
					      $errors[] = $error_string;
 
					  }
 
			}
 
		}
 

	
 
		/*
 
		 * Tell the user that his input is erroneous and
 
		 * require him to fix it.
 
		 */
 
		if (count($errors))
 
		  {
 
		    $error_page = page::page_create('Process Schedule — Errors', array('qTip2'), $page_create_options);
 
		    $error_page->head();
 

	
 
		    echo '        <p>' . PHP_EOL
 
		      . '          You have the following errors in your input:' . PHP_EOL
 
		      . '        </p>' . PHP_EOL
 
		      . '        <ul>' . PHP_EOL;
 
		    foreach ($errors as $error)
 
		      echo '          <li>' . $error . '</li>' . PHP_EOL;
 
		    echo '        </ul>' . PHP_EOL
 
		      . '        <h3>Solving Errors</h3>' . PHP_EOL
 
		      . '        <ul>' . PHP_EOL
school.d/calvin.crawl.inc
Show inline comments
 
@@ -363,68 +363,62 @@ function calvin_crawl_semester(array $sc
 
	    {
 
	      school_crawl_logf($school_crawl_log, 8, 'Unable to parse calvin section meeting info string into start/end/days information for '
 
				. implode('-', $section_id) . ': ``' . $sec_meeting_info . '\'\'');
 
	      $skipped_sections['invalid meeting info format'] ++;
 
	      /*
 
	       * Still add at least the course to the semester so that
 
	       * it shows up in autocmoplete.
 
	       */
 
	      calvin_crawl_course_add($semester, $section_id['department'], $section_id['course'], $title);
 
	      continue;
 
	    }
 
	  $date_start = $meeting_info_matches[1];
 
	  $date_end = $meeting_info_matches[2];
 
	  /* e.g., 'Lecture', 'Practicum' */
 
	  $meeting_type = school_crawl_meeting_type($meeting_info_matches[3]);
 

	
 
	  $days = school_crawl_days_format($school_crawl_log, explode(', ', $meeting_info_matches[5]));
 
	  $time_start = school_crawl_time_format(strptime($meeting_info_matches[6], '%I:%M%p'));
 
	  $time_end = school_crawl_time_format(strptime($meeting_info_matches[7], '%I:%M%p'));
 
	  $meeting_place = $meeting_info_matches[8];
 

	
 
	  foreach (array('date_start', 'date_end', 'meeting_type', 'days', 'time_start', 'time_end', 'meeting_place', 'meeting_type') as $var)
 
	    school_crawl_logf($school_crawl_log, 10, "%s:%s", $var, ${$var});
 

	
 
	  $semester->section_meeting_add($section_id['department'], $section_id['course'], $title, $section_id['section'], $synonym,
 
					 new SectionMeeting($days, $time_start, $time_end, $meeting_place, $meeting_type, $faculty_name), 'default', $credits);
 

	
 
	  /*
 
	   * Try to update semester's longetivity stats to help the
 
	   * school_semester_guess() function:
 
	   */
 
	  $date_start_time = strptime($date_start, '%m/%d/%Y');
 
	  $date_end_time = strptime($date_end, '%m/%d/%Y');
 

	
 
	  if ($date_start_time !== FALSE)
 
	    {
 
	      $date_start_time = school_crawl_gmmktime($date_start_time, -5 * 60*60);
 
	      $semester->time_start_pool_add($date_start_time);
 
	    }
 
	  else
 
	    $date_start_time = NULL;
 
	  if ($date_end_time !== FALSE)
 
	    {
 
	      $date_end_time = school_crawl_gmmktime($date_end_time, -5 * 60*60);
 
	      $semester->time_end_pool_add($date_end_time);
 
	    }
 
	    $date_end_time = school_crawl_gmmktime($date_end_time, -5 * 60*60) + 24*60*60;
 
	  else
 
	    $date_end_time = NULL;
 

	
 
	  $semester->section_meeting_add($section_id['department'], $section_id['course'], $title, $section_id['section'], $synonym,
 
					 new SectionMeeting($days, $time_start, $time_end, $meeting_place, $meeting_type, $faculty_name, $date_start_time, $date_end_time), 'default', $credits);
 

	
 
	}
 
	}
 

	
 
      if (!preg_match(';Page ([0-9]+) of ([0-9]+)\</td\>$;m', $html, $pages))
 
	{
 
	  school_crawl_logf($school_crawl_log, 0, 'Unable to determine the number of pages in this Calvin resultset');
 
	  break;
 
	}
 

	
 
      school_crawl_logf($school_crawl_log, 8, "calvin_crawl(): finished page %d of %d with %d courses.", $pages[1], $pages[2], $list_row - 1);
 

	
 
      $form = array(
 
		    'ACTION*Grp:WSS.COURSE.SECTIONS' => 'NEXT',
 
		    );
 
    }
 

	
 
  $has_stat = FALSE;
 
  foreach ($skipped_sections as $reason => $num)
 
    {
 
      if (!$num)
 
	continue;
 
      if (!$has_stat)
 
	school_crawl_logf($school_crawl_log, 7, 'Skipped some sections for <reason>: <number skipped>:');
 
      school_crawl_logf($school_crawl_log, 7, "%s: %d", $reason, $num);
school.d/ccbcmd.crawl.inc
Show inline comments
 
@@ -271,55 +271,55 @@ function ccbcmd_crawl_semester($school, 
 
	 * data integrity. I.e., make sure that the college doesn't
 
	 * suddenly support multiple meeting times in one field
 
	 * without our anticipating that and then cause us to have
 
	 * invalid data. ;-). The college does support multiple
 
	 * section meetings, it does this by having multiple rows per
 
	 * section. The extra rows _only_ have the days, time, prof,
 
	 * and dates columns. --binki
 
	 */
 
	if (strpos($time_end_text, '-') !== FALSE)
 
	  {
 
	    school_crawl_logf($school_crawl_log, 4, "Entry seems to have invalid date column data: ``%s'' time_end_text: ``%s''.",
 
		    $time_range_text, $time_end_text);
 
	    continue;
 
	  }
 
	$time_end = strptime($time_end_text, '%I:%M %p');
 
	if ($time_end === FALSE || $time_start === FALSE)
 
	  {
 
	    school_crawl_logf($school_crawl_log, 4, "Error parsing start or end time: start: ``%s'' end: ``%s''.",
 
		    $time_start_text, $time_end_text);
 
	    continue;
 
	  }
 

	
 
	$days = school_crawl_days_str_format($school_crawl_log, $children->item($section_offsets['days'])->textContent);
 

	
 
	$section_dates = $children->item($section_offsets['dates'])->textContent;
 
	$date_start = $date_end = NULL;
 
	if (preg_match(';^([0-9]+)/([0-9]+)-([0-9]+)/([0-9]+)$;', $section_dates, $section_dates_matches))
 
	  {
 
	    $date_start = gmmktime(0, 0, 0, $section_dates_matches[1], $section_dates_matches[2], $semester->year_get());
 
	    $date_end = gmmktime(0, 0, 0, $section_dates_matches[3], $section_dates_matches[4], $semester->year_get());
 
	  }
 

	
 
	$section->meeting_add(new SectionMeeting($days, school_crawl_time_format($time_start), school_crawl_time_format($time_end),
 
						 $children->item($section_offsets['location'])->textContent,
 
						 'lecture',
 
						 $instructor));
 

	
 
	/* check if a semester's date range should be increased */
 
	$section_dates = $children->item($section_offsets['dates'])->textContent;
 
	if (preg_match(';^([0-9]+)/([0-9]+)-([0-9]+)/([0-9]+)$;', $section_dates, $section_dates_matches))
 
	  {
 
	    $semester->time_start_set_test(gmmktime(0, 0, 0, $section_dates_matches[1], $section_dates_matches[2], $semester->year_get()));
 
	    $semester->time_end_set_test(gmmktime(0, 0, 0, $section_dates_matches[3], $section_dates_matches[4], $semester->year_get()));
 
	  }
 
						 $instructor, $date_start, $date_end));
 
      }
 
    }
 

	
 
  return 0;
 
}
 

	
 
function ccbcmd_crawl_curlhook(&$curl)
 
{
 
  /*
 
   * OK, so this must be set to SSLv2 or SSLv3 because of how the
 
   * server's SSL junk is messed up. When curl is built against
 
   * gnutls, though, we can't use SSL2 since it doesn't support that
 
   * old of a protocol. So, we use 3 which works. Apparently, the
 
   * server can't handle gnutls's attempt to use TLS. Even openssl's
 
   * s_client command fails without manually specifying --ssl2 or
 
   * --ssl3. So, this must be a _really_ weird server setup...
 
   */
 
  curl_setopt($curl, CURLOPT_SSLVERSION, 3);
 
}
school.d/cedarville.crawl.inc
Show inline comments
 
@@ -270,70 +270,67 @@ function cedarville_crawl_semester(array
 
		  if (preg_match($meeting_start_regex . $meeting_date_regex . $meeting_end_regex,
 
				 $meetings_str, $meeting_matches))
 
		    {
 

	
 
		      school_crawl_logf($school_crawl_log, 8, "Skipping some meeting data for %s because it is a date range: `%s'.",
 
					implode('-', $section_parts), $meeting_matches[0]);
 
		      $meetings_str = substr($meetings_str, strlen($meeting_matches[0]));
 
		      continue;
 
		    }
 

	
 
		  school_crawl_logf($school_crawl_log, 6, "Error parsing meeting time. Given `%s'. Skipping %s.", $meetings_str, implode('-', $section_parts));
 
		  break;
 
		}
 
	      /* prepare for parsing the next meeting time */
 
	      $meetings_str = substr($meetings_str, strlen($meeting_matches[0]));
 

	
 
	      $days = school_crawl_days_str_format($school_crawl_log, $meeting_matches[3]);
 
	      $time_start = school_crawl_time_format(strptime($meeting_matches[4] . 'M', '%I:%M%p'));
 
	      $time_end = school_crawl_time_format(strptime($meeting_matches[5] . 'M', '%I:%M%p'));
 
	      $room = $meeting_matches[2];
 

	
 
	      $type = school_crawl_meeting_type($meeting_matches[1]);
 

	
 
	      /* check for daterange information -- i.e., if the first regex successfully matched: */
 
	      $date_start = $date_end = NULL;
 
	      if (count($meeting_matches) > 7)
 
		{
 
		  $date_start = school_crawl_gmmktime(strptime($meeting_matches[6], '%m/%d/%y'), CEDARVILLE_TIMEZONE_OFFSET);
 
		  $date_end = school_crawl_gmmktime(strptime($meeting_matches[7], '%m/%d/%y'), CEDARVILLE_TIMEZONE_OFFSET);
 
		  if (!empty($date_start) && !empty($date_end))
 
		    {
 
		      $semester->time_start_set_test($date_start);
 
		      $semester->time_end_set_test($date_end);
 
		    }
 
		}
 

	
 
	      /*
 
	       * The tables are made for humans, not computers. If
 
	       * there aren't enough instructors for the number of
 
	       * section meetings, just reuse the first listed
 
	       * instructor:
 
	       */
 
	      if ($meeting_i >= count($instructors))
 
		$instructors[$meeting_i] = $instructors[0];
 

	
 
	      $meetings[] = new SectionMeeting($days, $time_start, $time_end,
 
					       $room, $type, $instructors[$meeting_i]);
 
					       $room, $type, $instructors[$meeting_i],
 
					       $date_start, $date_end);
 

	
 
	      $meeting_i ++;
 
	    }
 

	
 
	  $semester->section_add($section_parts['department'], $section_parts['course'],
 
				 new Section($section_parts['section'], $meetings,
 
					     $synonym, $credit_hours), $title);
 

	
 
	  /*
 
	   * Get the full subject's name from the course's page if we
 
	   * don't have it already.
 
	   */
 
	  if (!$semester->department_name_has($section_parts['department']))
 
	    {
 
	      foreach ($course_table[1]->childNodes as $course_a)
 
		if ($course_a instanceof DOMElement
 
		&& $course_a->tagName == 'a')
 
		  break;
 
	      if ($course_a instanceof DOMElement
 
		  && $course_a->tagName == 'a'
 
		  && strlen($course_href = $course_a->getAttribute('href')))
 
		{
 
		  $course_uri = school_crawl_url($uri, $course_href);
 
		  $course_html = school_crawl_geturi($course_uri, $cookies, $school_crawl_log);
school.d/hope.crawl.inc
Show inline comments
 
@@ -257,72 +257,74 @@ function hope_crawl_semester(array $scho
 
       * line. Therefore, we must propagate these values.
 
       */
 
      foreach (array(
 
	'subject_id' => 'Subject',
 
	'course_id' => 'Course Number',
 
	'title' => 'Title',
 
	'section_id' => 'Section Number',
 
	'synonym' => 'CRN',
 
	'instructor' => 'Instructor',
 
	'location' => 'Location',
 
      ) as $var => $field)
 
	if (strlen(trim($section_csv[$fields[$field]])))
 
	  ${$var} = trim($section_csv[$fields[$field]]);
 

	
 
      if ($section_csv[$fields['M']] == 'TBA'
 
	  || $section_csv[$fields['Times']] == 'TBA')
 
	{
 
	  $semester->class_add(new Course($subject_id . '-' . $course_id,
 
					  $section_csv[$fields['Title']]));
 
	  school_crawl_logf($school_crawl_log, 8, "Course %s-%s-%s has a section meeting with a TBA time, adding dummy course.",
 
			    $subject_id, $course_id, $section_id);
 
	  continue;
 
	}
 

	
 
      $date_start = $date_end = NULL;
 
      if (preg_match(',(\\d\\d)/(\\d\\d)-(\\d\\d)/(\\d\\d),', $section_csv[$fields['Date']], $matches))
 
	{
 
	  list(, $m_start, $d_start, $m_end, $d_end) = $matches;
 
	  if ($m_start && $d_start && $m_end && $d_end)
 
	    {
 
	      $y_start = $y_end = $semester->year_get();
 
	      if ($m_end < $m_start)
 
		$y_end ++;
 
	      $semester->time_start_set_test(gmmktime(0, 0, 0, $m_start, $d_start, $y_start));
 
	      $semester->time_end_set_test(gmmktime(0, 0, 0, $m_end, $d_end, $y_end));
 
	      $date_start = gmmktime(0, 0, 0, $m_start, $d_start, $y_start);
 
	      $date_end = gmmktime(0, 0, 0, $m_end, $d_end, $y_end);
 
	    }
 
	}
 

	
 
      if (trim($section_csv[$fields['U']]))
 
	school_crawl_logf($school_crawl_log, 0, "Section %d has sunday.", $synonym);
 
      $days = school_crawl_days_format($school_crawl_log, array_filter(array_slice($section_csv, $fields['M'], 7), '_hope_crawl_days_filter'));
 
      list($time_start, $time_end) = explode('-', $section_csv[$fields['Times']]);
 
      if (strlen($time_start) != 4 || strlen($time_end) != 4)
 
	{
 
	  school_crawl_logf($school_crawl_log, 4, "Section meeting (synonym=%s) has invalidly-formatted start time (%s) or end time (%s). Skipping.",
 
			    $synonym, $time_start, $time_end);
 
	  continue;
 
	}
 

	
 
      /*
 
       * Guessing the type of section_meeting: `attribute' of NSL
 
       * seems to be associated with labs. Matches `lab', `lab.', `
 
       * lab', ` labo'..., etc.
 
       */
 
      $type = 'lecture';
 
      if (preg_match('/(^|[^a-z])lab($|o|[^a-z])/i', $title))
 
	$type = 'lab';
 

	
 
      $section_meeting = new SectionMeeting($days, $time_start, $time_end,
 
					    $location,
 
					    $type,
 
					    $instructor);
 
					    $instructor,
 
					    $date_start, $date_end);
 
      $semester->section_meeting_add($subject_id,
 
				     $course_id,
 
				     $title,
 
				     $section_id,
 
				     $synonym,
 
				     $section_meeting,
 
				     $type,
 
				     $section_csv[$fields['Cred']]);
 
    }
 
  return 0;
 
}
school.d/umich.crawl.inc
Show inline comments
 
@@ -253,74 +253,73 @@ function umich_crawl_semester(array $sch
 
	  if ($semester->class_get($dept, $course_id) === NULL)
 
	    /**
 
	     * \todo
 
	     *   SP does credit hours by section, what about Courses
 
	     *   with no sections because they're these weird limbo
 
	     *   `ARR' courses but these limbo courses still have a
 
	     *   number of credit hours?
 
	     */
 
	    $semester->class_add(new Course($dept . '-' . $course_id, $row[$fields['Course Title']]));
 
	  continue;
 
	}
 
      $time_end = umich_crawl_time($matches[2], $matches[3]);
 
      $time_start = umich_crawl_time($matches[1], FALSE, $time_end);
 
      /* umich defines course_slots by meeting_type. */
 
      $meeting_type = school_crawl_meeting_type(trim($row[$fields['Component']]));
 

	
 
      /*
 
       * Some information is only presented in the first row in a
 
       * listing of courses. Perform some accumulation here.
 
       */
 
      foreach (array('Instructor') as $key)
 
	if (strlen($curr_value = trim($row[$fields[$key]])))
 
	  $row_accumulation[$key] = $curr_value;
 

	
 
      $semester->section_meeting_add($dept, $course_id, trim($row[$fields['Course Title']]),
 
				     trim($row[$fields['Section']]), $synonym,
 
				     new SectionMeeting($days, $time_start, $time_end,
 
							trim($row[$fields['Location']]),
 
							$meeting_type,
 
							$row_accumulation['Instructor']),
 
				     $meeting_type,
 
				     $credit_hours);
 

	
 
      /*
 
       * If the section so far passed as being a normal section, use
 
       * its start and end dates to help determine the semester's
 
       * respective start and end dates.
 
       * Grab start/stop dates.
 
       */
 
      $date_start = $date_end = NULL;
 
      $date_start_tm = strptime(trim($row[$fields['Start Date']]), '%m/%d/%Y');
 
      $date_end_tm = strptime(trim($row[$fields['End Date']]), '%m/%d/%Y');
 
      if (!empty($date_start_tm) && !empty($date_end_tm))
 
	{
 
	  $date_start = school_crawl_gmmktime($date_start_tm);
 
	  $date_end = school_crawl_gmmktime($date_end_tm);
 
	  if ($date_start > 1000000 && $date_end > 1000000)
 
	  if ($date_start < 1000000 || $date_end < 1000000)
 
	    {
 
	      $semester->time_start_set_test($date_start);
 
	      $semester->time_end_set_test($date_end);
 
	      $date_start = $date_end = NULL;
 
	    }
 
	}
 

	
 
      $semester->section_meeting_add($dept, $course_id, trim($row[$fields['Course Title']]),
 
				     trim($row[$fields['Section']]), $synonym,
 
				     new SectionMeeting($days, $time_start, $time_end,
 
							trim($row[$fields['Location']]),
 
							$meeting_type,
 
							$row_accumulation['Instructor'],
 
							$date_start, $date_end),
 
				     $meeting_type,
 
				     $credit_hours);
 
    }
 
}
 

	
 
/**
 
 * \brief
 
 *   Try to turn a umich-formatted time into something usable.
 
 *
 
 * \param $raw
 
 *   The raw input.
 
 * \param $xm
 
 *   FALSE or, if PM or AM was specified, 'P' for PM and 'A' for AM.
 
 * \param $before
 
 *   A time of day before which this time must be. Used generally for
 
 *   the start time of a class. The end time of a class must be parsed
 
 *   first so that the result of that calculation may be passed as the
 
 *   $before value.
 
 */
 
function umich_crawl_time($raw, $xm = FALSE, $before = '2400')
 
{
 
  $h = $raw;
 
  $m = '00';
 
  if (strlen($raw) > 2)
 
    {
 
      $h = substr($raw, 0, strlen($raw) - 2);
scripts/scheduleInput.js
Show inline comments
 
/* -*- tab-width: 4; -*-
 
 * Copyright 2010 Nathan Gelderloos, Ethan Zonca, Nathan Phillip Brink
 
 *
 
 * This file is part of SlatePermutate.
 
 *
 
 * SlatePermutate is free software: you can redistribute it and/or modify
 
 * it under the terms of the GNU Affero General Public License as published by
 
 * the Free Software Foundation, either version 3 of the License, or
 
 * (at your option) any later version.
 
 *
 
 * SlatePermutate is distributed in the hope that it will be useful,
 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
 * GNU Affero General Public License for more details.
 
 *
 
 * You should have received a copy of the GNU Affero General Public License
 
 * along with SlatePermutate.  If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
    //--------------------------------------------------
 
    // General Notes
 
    //--------------------------------------------------
 
/**
 
 * \file
 
 *
 
 * If you are reading this file, you may be interested in contributing
 
 * to slate_permutate. Please see http://ohnopub.net/w/SlatePermutate .
 
 */
 

	
 
/**
 
 * \brief
 
 *   The next course_i value that will be produced when add_class() is
 
 *   called.
 
 *
 
 * If iterating through all of the possible courses, use (classNum -
 
 * 1) as the upper bound.
 
 */
 
var classNum = 0;
 

	
 
/**
 
 * \brief
 
 *   The number of section entries for a given course.
 
 *
 
 * Key is course_i, value is the current number of sections.
 
 */
 
var sectionsOfClass = new Array();
 

	
 
/**
 
 * \brief
 
 *   Help to generate a unique section identifier for each section
 
 *   added to a given course.
 
 *
 
@@ -106,49 +109,49 @@ function addTips()
 
 /* slate_permutate_example_course_id is set globally in input.php. */
 
 jQuery('td:first', td).qtip(
 
   {
 
      content: 'Start typing your class ID (such as ' + slate_permutate_example_course_id + ') and click a suggestion to add sections',
 
      style: {
 
        tip: true,
 
        classes: "ui-tooltip-dark ui-tooltip-shadow ui-tooltip-rounded"
 
      },
 
      show: {
 
        ready: true
 
      },
 
      position:{
 
        my: 'top left', 
 
        at: 'bottom right'
 
      }
 
    }
 
  );
 

	
 
}
 

	
 
/**
 
 * \brief
 
 *   Add a section to a class.
 
 */
 
function add_section_n(cnum, name, synonym, stime, etime, days, instructor, location, type, slot, credit_hours)
 
function add_section_n(cnum, name, synonym, stime, etime, days, instructor, location, type, slot, credit_hours, date_start, date_end)
 
{
 
    var snum = last_section_i ++;
 
    var cssclasses = 'section class' + cnum + ' ' + safe_css_class('slot-' + slot);
 
    var last_tr;
 

	
 
	/*
 
	 * Add the type of the course to the CSS if it's a valid (and
 
	 * _clean-looking_ CSS class). Supports things like Cedarville's
 
	 * coloration of labs/ILB.
 
	 */
 
	if (/[a-z-]+/.exec(type) != null)
 
		cssclasses += ' type-' + type;
 

	
 
    var section_html = '<tr id="tr-section-' + String(snum) + '" class="' + cssclasses + '"><td class="none"></td>' +
 
	'<td class="sectionIdentifier center"><input type="text" size="1" class="required section-letter-entry" name="postData[' + cnum + '][' + snum + '][letter]" />' + 
 
	'  <input class="section-synonym-entry" type="hidden" name="postData[' + cnum + '][' + snum + '][synonym]" />' +
 
	'  <input class="section-slot-entry" type="hidden" name="postData[' + cnum + '][' + snum + '][slot]" />' +
 
	'</td>' +
 
	'<td class="professor center"><input type="text" size="10" class="profName" name="postData[' + cnum + ']['+ snum + '][professor]" /></td>' +
 
	'<td><select class="selectRequired" name="postData[' + cnum + '][' + snum + '][start]"><option value="none"></option>' +
 
	genOptionHtml("0700", "7:00 am", stime) + genOptionHtml("0730", "7:30 am", stime) +
 
	genOptionHtml("0800", "8:00 am", stime) + genOptionHtml("0830", "8:30 am", stime) +
 
	genOptionHtml("0900", "9:00 am", stime) + genOptionHtml("0930", "9:30 am", stime) +
 
	genOptionHtml("1000", "10:00 am", stime) + genOptionHtml("1030", "10:30 am", stime) +
 
@@ -191,130 +194,141 @@ function add_section_n(cnum, name, synon
 
	genOptionHtml("2020", "8:20 pm", etime) + genOptionHtml("2050", "8:50 pm", etime) +
 
	genOptionHtml("2120", "9:20 pm", etime) + genOptionHtml('2150', '9:50 pm', etime);
 

	
 
    if (etime.length > 0)
 
    {
 
	var etime_end = etime.substr(2);
 
	var etime_begin = etime.substr(0, 2);
 
	if (etime_end != '50' && etime_end != '20'
 
	   || etime_begin < 7 || etime_begin > 21)
 
	    section_html = section_html + genOptionHtml(etime, prettyTime(etime), etime);
 
    }
 

	
 
    section_html = section_html + '</select></td>\n\
 
<td class="cbrow"><input type="checkbox" title="Sunday" class="daysRequired" name="postData[' + cnum +'][' + snum + '][days][6]" value="1" ' + (days.u ? 'checked="checked"' : '') + ' /></td>\
 
<td class="cbrow"><input type="checkbox" class="daysRequired" name="postData[' + cnum + '][' + snum + '][days][0]" value="1" ' + (days.m ? 'checked="checked"' : '') + ' /></td>\
 
<td class="cbrow"><input type="checkbox" class="daysRequired" name="postData[' + cnum + '][' + snum + '][days][1]" value="1" ' + (days.t ? 'checked="checked"' : '') + ' /></td>\
 
<td class="cbrow"><input type="checkbox" class="daysRequired" name="postData[' + cnum + '][' + snum + '][days][2]" value="1" ' + (days.w ? 'checked="checked"' : '') + ' /></td>\
 
<td class="cbrow"><input type="checkbox" class="daysRequired" name="postData[' + cnum + '][' + snum + '][days][3]" value="1" ' + (days.h ? 'checked="checked"' : '') + ' /></td>\
 
<td class="cbrow"><input type="checkbox" class="daysRequired" name="postData[' + cnum + '][' + snum + '][days][4]" value="1" ' + (days.f ? 'checked="checked"' : '') + ' /></td>\
 
<td class="cbrow"><input type="checkbox" class="daysRequired" name="postData[' + cnum + '][' + snum + '][days][5]" value="1" ' + (days.s ? 'checked="checked"' : '') + ' /></td>' +
 
	'<td class="removeCell"><div><button class="gray section-meeting-delete">x</button></div></td><td class="emptyCell">' +
 
	'<input class="section-location-entry" type="hidden" name="postData[' + cnum + '][' + snum + '][location]" />' +
 
	'<input class="section-type-entry" type="hidden" name="postData[' + cnum + '][' + snum + '][type]" />' +
 
		'<input class="section-credit-hours-entry" type="hidden" name="postData[' + cnum + '][' + snum + '][credit_hours]" />' +
 
		'<input class="section-date-start-entry" type="hidden" name="postData[' + cnum + '][' + snum + '][date_start]" />' +
 
		'<input class="section-date-end-entry" type="hidden" name="postData[' + cnum + '][' + snum + '][date_end]" />' +
 
	'</td></tr>';
 

	
 
    /*
 
     * Try to append this section to the last section in its
 
     * associated CourseSlot...
 
     */
 
    last_tr = jQuery('tr.class' + cnum + '.' + safe_css_class('slot-' + slot) + ':last');
 
    if (!last_tr.length)
 
    {
 
	/* Also append a a new ``we are this slot'' row... */
 
	course_add_slot_row(cnum, slot);
 
	last_tr = jQuery('tr.class' + cnum + ':last');
 
    }
 
    last_tr.after(section_html);
 
    sectionsOfClass[cnum] ++;
 

	
 
    var section_tr = jQuery('#tr-section-' + String(snum));
 
    /* store course_i in a place the newly added section will look for it */
 
    section_tr.data({course_i: cnum});
 

	
 
    /*
 
     * Store data into the newly created HTML. With this method we
 
     * have to _avoid_ escaping entities in the text we're setting as
 
     * values because the DOM stuff will escape it for us.
 
     */
 
    section_tr.find('.section-letter-entry').val(name);
 
    section_tr.find('.section-synonym-entry').val(synonym);
 
    section_tr.find('.section-slot-entry').val(slot);
 
    section_tr.find('.profName').val(instructor);
 
    section_tr.find('.section-location-entry').val(location);
 
    section_tr.find('.section-type-entry').val(type);
 
	section_tr.find('.section-credit-hours-entry').val(credit_hours);
 
	section_tr.find('.section-date-start-entry').val(date_start);
 
	section_tr.find('.section-date-end-entry').val(date_end);
 

	
 
    /* unhide the saturday and sunday columns if they're used by autocomplete data */
 
	if (days.u)
 
		jQuery('#jsrows col.sunday').removeClass('collapsed');
 
    if (days.s)
 
	jQuery('#jsrows col.saturday').removeClass('collapsed');
 

	
 
	credit_hours_change(cnum);
 

	
 
    return last_section_i - 1;
 
}
 
function add_section(cnum)
 
{
 
    var section_i = add_section_n(cnum, '', '', '', '', {}, '', '', '', 'default', -1);
 
    var section_i = add_section_n(cnum, '', '', '', '', {}, '', '', '', 'default', -1, null, null);
 
    if (cnum == slate_permutate_course_free)
 
	course_free_check(cnum);
 
    return section_i;
 
}
 

	
 
/**
 
 * Add a list of sections gotten via an AJAX call.
 
 */
 
function add_sections(cnum, data)
 
{
 
    var i;
 

	
 
    if (data.title)
 
	jQuery('.pclass' + cnum + ' .course-title-entry').val(data.title);
 

	
 
    /*
 
     * If the user enterred something iffy, correct him. Or do so
 
     * regardless ;-).
 
     */
 
    /* this data['class'] stuff is for the old JSON format we used... */
 
    if (data['class'])
 
	data.course = data['class'];
 
    if (data.course)
 
	jQuery('.className' + cnum).val(data.course);
 

	
 
    if (!data.sections)
 
	return;
 

	
 
    jQuery.each(data.sections, function(i, section)
 
		{
 
		    if (!section.slot)
 
			section.slot = 'default';
 
			if (section.credit_hours === undefined)
 
				section.credit_hours = -1;
 
			if (section.date_start === undefined)
 
			{
 
				section.date_start = null;
 
				section.date_end = null;
 
			}
 

	
 
		    add_section_n(cnum, section.section, section.synonym, section.time_start, section.time_end, section.days, section.instructor, section.location, section.type, section.slot, section.credit_hours);
 
		    add_section_n(cnum, section.section, section.synonym, section.time_start, section.time_end,
 
						  section.days, section.instructor, section.location, section.type, section.slot,
 
						  section.credit_hours, section.date_start, section.date_end);
 
		});
 

	
 
    /*
 
     * Handle course-level interdependencies.
 
     */
 
    if (data.dependencies)
 
	jQuery.each(data.dependencies, function(i, dep) {
 
			/* Gracefully deprecate the old crawler's JSON format. */
 
			if (dep['class'])
 
			    dep.course = dep['class'];
 

	
 
		var new_course_num = add_class_n(dep.course, dep['title'] ? dep['title'] : '');
 
			add_sections(new_course_num, dep);
 
		    });
 
}
 

	
 
/**
 
 * \brief
 
 *   Adds an identifier for a course slot.
 
 *
 
 * \param course_i
 
 *   The javascript index of the course to which a slot is being
 
 *   added.
 
 * \param slot_id
0 comments (0 inline, 0 general)