Changeset - acc2b5f0aae2
[Not reviewed]
default
0 9 1
Nathan Brink (binki) - 14 years ago 2011-04-18 23:45:54
ohnobinki@ohnopublishing.net
Support umich's concept of a student having to sign up for multiple sections within one course. In slate_permutate, we call these course slots.
10 files changed with 522 insertions and 95 deletions:
0 comments (0 inline, 0 general)
inc/class.course.inc
Show inline comments
 
<?php
 
/*
 
 * Copyright 2010 Nathan Gelderloos, Ethan Zonca, Nathan Phillip Brink
 
 * Copyright 2011 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
 
@@ -15,123 +15,141 @@
 
 * 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/>.
 
 */
 

	
 
include_once 'class.section.php';
 
require_once 'class.section.php';
 
require_once 'class.course_slot.inc';
 

	
 
/**
 
 * \brief
 
 *   Represents a Course containing multiple Sections.
 
 *
 
 * A course is associated with a certain subject material. For each
 
 * course, a student has to choose a particular Section to
 
 * take. Courses are not associated with professors or meeting times,
 
 * those are in the realm of the Section and SectionMeeting.
 
 *
 
 * Iterating over this object will return CourseSlot objects, which
 
 * act exactly like most universities' idea of a course. However, some
 
 * universities have one Course and require students to take one
 
 * section from each of different categories of sections within in a
 
 * course. For example, at umich for any course which has a listing of
 
 * Sections of the type 'discussion' the student _must_ take one of
 
 * these 'discussion' sections in addition to, for example, a
 
 * 'lecture' Section.
 
 */
 
class Course implements IteratorAggregate
 
{
 
  private $name;	// String
 
  private $title;       // String
 
  private $sections;	// Array of sections
 
  private $nsections;	// int
 
  private $sections;	// Array of sections, for __wakeup() to convert to CourseSlots.
 
  private $nsections;	// int, for __wakeup() to convert to CourseSlots.
 
  /**
 
   * \brief
 
   *   Other courses that this course depends on.
 
   *
 
   * Example: Many calvin courses depend on lab courses.
 
   */
 
  private $dependencies;
 

	
 
  /**
 
   * \brief
 
   *    Creates a class with the given name.
 
   *
 
   * When updating this function, update the call to ``new Course()''
 
   * in Schedule::findPossibilities(), Schedule::writeoutTables(), and
 
   * Schedule::courses_get().
 
   *
 
   * \param $course_id
 
   *    The identifier of the class. Ex., MATH-101 in
 
   *    MATH-101-A. Retrieved with Course::getName().
 
   * \param $title
 
   *    The human-friendly course title, such as 'Introduction to
 
   *    Algebra', or NULL.
 
   */
 
  function __construct($course_id, $title = NULL)
 
  public function __construct($course_id, $title = NULL)
 
  {
 
    $this->sections = array();
 
    $this->slots = array();
 
    $this->name = $course_id;
 
    $this->title = $title;
 
    $this->nsections = 0;
 
    $this->dependencies = array();
 
  }
 
	
 
  /**
 
   * \brief
 
   *   Adds an already-instantiated section to this class.
 
   *   Adds an already-instantiated Section to this class.
 
   *
 
   * \param $section
 
   *   The Section to append to this Course.
 
   * \param $course_slot_id
 
   *   The string identifer of the CourseSlot to place the given
 
   *   Section into. Most schools will not specify this.
 
   */
 
  public function section_add(Section $section)
 
  public function section_add(Section $section, $course_slot_id = 'default')
 
  {
 
    $this->sections[$this->nsections] = $section;
 
    $this->nsections++;
 
    if (empty($this->slots[$course_slot_id]))
 
      $this->slots[$course_slot_id] = new CourseSlot($course_slot_id);
 
    $this->slots[$course_slot_id]->section_add($section);
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Append a CourseSlot to this Course.
 
   *
 
   * If this course already contains a CourseSlot with the same
 
   * CourseSlot identifier as $course_slot, then the new CourseSlot
 
   * will replace the old one.
 
   *
 
   * \param $course_slot
 
   *   The CourseSlot to append.
 
   */
 
  public function course_slot_add(CourseSlot $course_slot)
 
  {
 
    $this->slots[$course_slot->id_get()] = $course_slot;
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Required function to implement the IteratorAggregate interface.
 
   */
 
  public function getIterator()
 
  {
 
    return new ArrayIterator($this->sections);
 
  }
 

	
 
  /**
 
   * \brief
 
   *    Returns the number of sections in the class.
 
   */
 
  function getnsections()
 
  {
 
    return $this->nsections;
 
    return new ArrayIterator($this->slots);
 
  }
 
	
 
  /**
 
   * \brief
 
   *    Returns the desired section for analysis.
 
   * \return
 
   *    The selected section of the course.
 
   */
 
  function getSection($i)
 
  {
 
    $result = $this->sections[$i];
 
    return $result;
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Retrieve a section of this class based on its letter.
 
   *   Retrieve a section of this Course based on its letter.
 
   *
 
   * \todo Make this function replace completely the getSection()
 
   * function, have $this->sections be keyed by letter, and have a
 
   * __wakup() convert the old $this->sections format to the new one.
 
   * This function will automatically search CourseSlots for this
 
   * Section. Why? Because even though we support multiple
 
   * CourseSlots, the section_id must _still_ be unique -- no two
 
   * CourseSlots should share a fully-qualified section_id.
 
   *
 
   * \return
 
   *   The requested section or NULL if that section does not yet
 
   *   exist for this class.
 
   */
 
  public function section_get($letter)
 
  {
 
    foreach ($this->sections as $section) {
 
      if ($section->getLetter() == $letter) {
 
    foreach ($this->slots as $slot)
 
      {
 
	$section = $slot->section_get($letter);
 
	if (!empty($section))
 
	return $section;
 
      }
 
    }
 
    return NULL;
 
  }
 

	
 
  /**
 
   * \brief
 
   *    Returns the name of the class.
 
   *    Returns the name of the Course.
 
   * \return
 
   *    The name of the class.
 
   *    The name of the Course.
 
   */
 
  public function getName()
 
  {
 
    return $this->name;
 
  }
 

	
 
@@ -177,22 +195,21 @@ class Course implements IteratorAggregat
 
   *   'course'. If the user's input has less than these two keys of
 
   *   information, the returned array may have zero or one elements.
 
   */
 
  public static function parse($course_spec)
 
  {
 
    $section_parts = Section::parse($course_spec);
 
    if (isset($section_parts['section'])) {
 
    if (isset($section_parts['section']))
 
      unset($section_parts['section']);
 
    }
 

	
 
    return $section_parts;
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Represent this class as a string.
 
   *   Represent this Course as a string.
 
   */
 
  public function __toString()
 
  {
 
    return $this->getName();
 
  }
 

	
 
@@ -212,18 +229,15 @@ class Course implements IteratorAggregat
 
    $json_array = array(
 
			'course' => $this->getName(),
 
			'title' => $this->title_get(),
 
			'sections' => array(),
 
			'dependencies' => array(),
 
			);
 
    foreach ($this->sections as $section)
 
      {
 
	$section_json_arrays = $section->to_json_arrays();
 
	foreach ($section_json_arrays as $section_json_array)
 
	  $json_array['sections'][] = $section_json_array;
 
      }
 
    foreach ($this->slots as $slot)
 
      foreach ($slot->to_json_arrays() as $slot_json_section_array)
 
        $json_array['sections'][] = $slot_json_section_array;
 

	
 
    foreach ($this->dependencies as $dependency)
 
      {
 
	$dependency_json = $dependency->to_json_array($recursion_trace);
 
	if (!empty($dependency_json))
 
	  $json_array['dependencies'][] = $dependency_json;
 
@@ -252,13 +266,28 @@ class Course implements IteratorAggregat
 
	$json['course'] = $json['class'];
 
	unset($json['class']);
 
      }
 
    $course = new Course($json['course'], $title);
 

	
 
    if (!empty($json['sections']))
 
      $course->section_add(Section::from_json_arrays($json['sections']));
 
      {
 
	$json_course_slot_sections = array();
 
	foreach ($json['sections'] as $json_section)
 
	  {
 
	    $slot_id = 'default';
 
	    if (!empty($json_section['slot']))
 
	      $slot_id = $json_section['slot'];
 

	
 
	    if (empty($json_course_slot_sections[$slot_id]))
 
	      $json_course_slot_sections[$slot_id] = array();
 
	    $json_course_slot_sections[$slot_id][] = $json_section;
 
	  }
 

	
 
	foreach ($json_course_slot_sections as $slot_id => $json_course_slot_section)
 
	  $course->section_add(Section::from_json_arrays($json_course_slot_section), $slot_id);
 
      }
 
    
 
    if (!empty($json['dependencies']))
 
      foreach ($json['dependencies'] as $dependency)
 
	$course->dependency_add(Course::from_json_array($dependency));
 

	
 
    return $course;
 
@@ -272,8 +301,19 @@ class Course implements IteratorAggregat
 
  {
 
    if (!isset($this->dependencies))
 
      $this->dependencies = array();
 

	
 
    if (!isset($this->title))
 
      $this->title = NULL;
 

	
 
    if (empty($this->slots) && !empty($this->sections))
 
      {
 
	$this->slots = array();
 

	
 
	foreach ($this->sections as $section)
 
	  $this->section_add($section);
 

	
 
	unset($this->sections);
 
	unset($this->nsections);
 
  }
 
}
 
}
inc/class.course_slot.inc
Show inline comments
 
new file 100644
 
<?php
 
/*
 
 * Copyright 2011 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 'class.section.php';
 

	
 
/**
 
 * \brief
 
 *   A package of Section objects of which one must be taken to sign
 
 *   up for a particular Course.
 
 *
 
 * For example, some schools like umich have a single Course where one
 
 * must sign up for one Section for every meeting_type of the
 
 * following: 'lecture', 'discussion', and 'lab'. This way they avoid
 
 * creating separate Course objects for labs (which is calvin's
 
 * solution to the problem).
 
 *
 
 * Many schools do not have the CourseSlot paradigm. These will work
 
 * just fine using one default CourseSlot.
 
 *
 
 * Iterating over this object will yield Section objects.
 
 */
 
class CourseSlot implements IteratorAggregate
 
{
 
  /**
 
   * \brief
 
   *   An array of Section objects associated with this CourseSlot.
 
   */
 
  private $sections;
 

	
 
  /**
 
   * \brief
 
   *   An identifier for this slot. Used during crawling when sorting
 
   *   Sections into CourseSlot objects.
 
   */
 
  private $id;
 

	
 
  /**
 
   * \brief
 
   *   Creates a CourseSlot with the given identifier.
 
   */
 
  public function __construct($id)
 
  {
 
    $this->id = $id;
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Required function to implement the IteratorAggregate interface.
 
   */
 
  public function getIterator()
 
  {
 
    return new ArrayIterator($this->sections);
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Get the identifier of this slot.
 
   *
 
   * \return
 
   *   The slot's identifier string.
 
   */
 
  public function id_get()
 
  {
 
    return $this->id;
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Appends a Section to this CourseSlot.
 
   *
 
   * \param $section
 
   *   The Section to append.
 
   */
 
  public function section_add(Section $section)
 
  {
 
    /*
 
     * This behavior of the Schedule class requires this manner of
 
     * indexing sections because it iterates using for ($count = 0;
 
     * $count < ...) -style loops. Thus we allow PHP's natural
 
     * indexing mechanism to do its job...
 
     */
 
    $this->sections[] = $section;
 
  }
 

	
 
  /**
 
   * \brief
 
   *    Returns the number of sections in the class.
 
   */
 
  function sections_count()
 
  {
 
    return count($this->sections);
 
  }
 

	
 
  /**
 
   * \brief
 
   *    Returns the desired section for analysis.
 
   * \return
 
   *    The selected section of the course.
 
   */
 
  function section_get_i($i)
 
  {
 
    $result = $this->sections[$i];
 
    return $result;
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Retrieve a section of this class based on its letter.
 
   *
 
   * \todo Make this function replace completely the getSection()
 
   * function, have $this->sections be keyed by letter, and have a
 
   * __wakup() convert the old $this->sections format to the new one.
 
   *
 
   * \return
 
   *   The requested section or NULL if that section does not yet
 
   *   exist for this class.
 
   */
 
  public function section_get($letter)
 
  {
 
    foreach ($this->sections as $section) {
 
      if ($section->getLetter() == $letter) {
 
	return $section;
 
      }
 
    }
 
    return NULL;
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Get the JSON arrays of data specific to each Section, adding
 
   *   slight metadata for this SLOT.
 
   *
 
   * There is no corresponding from_json_arrays() function for this
 
   * class. See Course::from_json_array() which manages the conversion
 
   * of JSON slots to CourseSlot objects.
 
   */
 
  public function to_json_arrays()
 
  {
 
    $slot_section_json_arrays = array();
 
    foreach ($this->sections as $section)
 
      {
 
	$section_json_arrays = $section->to_json_arrays();
 
	foreach ($section_json_arrays as $section_json_array)
 
	  $slot_section_json_arrays[] = $section_json_array + array('slot' => $this->id);
 
      }
 
    return $slot_section_json_arrays;
 
  }
 
}
inc/class.schedule.php
Show inline comments
 
@@ -44,12 +44,27 @@ class Schedule
 
   * Variables for upgrading from saved schedules created when there
 
   * was a class called Classes.
 
   */
 
  private $classStorage;			// array of courses
 
  private $nclasses;				// Integer number of classes
 

	
 
  /**
 
   * \brief
 
   *   Provides a mapping to regain the user's original input.
 
   *
 
   * Currently, the Schedule object cannot natively handle CourseSlot
 
   * objects properly. It assumes that each Course has one and only
 
   * one CourseSlot. This array maps each Course object stored in
 
   * $classStorage onto the index of the course it was originally
 
   * from. I.e., if the Course at index 0 had two CourseSlot objects,
 
   * array(0 => 0, 1 => 0, 2 => 1) would map these two CourseSlot
 
   * objects onto the same Course object and have the next CourseSlot
 
   * be mapped into a separate Course object.
 
   */
 
  private $course_slot_mappings;
 

	
 
  /* My member variables. */
 
  private $courses;
 
  private $nPermutations = 0;		// Integer number of real permutations
 
  private $possiblePermutations;	// Integer number of possible permutations
 
  private $scheduleName;			// String name of schedule
 
  private $storage;				// Integer array of valid schedules
 
@@ -108,12 +123,13 @@ class Schedule
 
   * \param $semester
 
   *   The semester used for this schedule.
 
   */
 
  function __construct($name, $parent = NULL, array $school = NULL, array $semester = NULL)
 
  {
 
    $this->courses = array();
 
    $this->course_slot_mappings = array();
 
    $this->scheduleName = $name;
 
    $this->storage = array();
 
    $this->title = "SlatePermutate - Scheduler";
 
    $this->parent_id = $parent;
 

	
 
    if (empty($school))
 
@@ -138,12 +154,18 @@ class Schedule
 
    return $this->scheduleName;
 
  }    
 

	
 
  /**
 
   * \brief
 
   *   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);
 
  }
 

	
 
@@ -155,16 +177,17 @@ class Schedule
 
   *   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')
 
  function addSection($course_name, $letter, $time_start, $time_end, $days, $synonym = NULL, $instructor = NULL, $location = NULL, $type = 'lecture', $slot = 'default')
 
  {
 
    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($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)
 
      {
 
@@ -176,13 +199,13 @@ class Schedule
 
      if (!strcmp($course_name, $course->getName()))
 
	{
 
	  $section = $course->section_get($letter);
 
	  if (!$section)
 
	    {
 
	      $section = new Section($letter, array(), $synonym);
 
	      $course->section_add($section);
 
	      $course->section_add($section, $slot);
 
	    }
 
	  $section->meeting_add(new SectionMeeting($days, $time_start, $time_end, $location, $type, $instructor));
 

	
 
	  return;
 
	}
 

	
 
@@ -233,23 +256,48 @@ class Schedule
 
  // Finds all of the possible permutations and stores
 
  // the results in the storage array.
 
  //--------------------------------------------------
 
	function findPossibilities()
 
	{
 
	  /*
 
	   * Split out any Course objects with multiple CourseSlots
 
	   * into multiple Course objects...
 
	   */
 
	  $new_courses = array();
 
	  foreach ($this->courses as $i => $course)
 
	    foreach ($course as $course_slot)
 
	      {
 
		$new_course = new Course($course->getName(), $course->title_get());
 
		$new_course->course_slot_add($course_slot);
 
		$new_courses[] = $new_course;
 

	
 
		$this->course_slot_mappings[count($new_courses) - 1] = $i;
 
	      }
 
	  $this->courses = $new_courses;
 
	  unset($new_courses);
 

	
 
	  /*
 
	   * Clean crud (completely empty courses) out of the
 
	   * schedule. For some crud, it's much easier to detect that
 
	   * it's crud now than during parsing of postData[].
 
	   *
 
	   * Now we may assume that each Course only has one
 
	   * CourseSlot...
 
	   */
 
	  foreach ($this->courses as $i => $course)
 
	    if (!$course->getnsections())
 
	    {
 
	      $course_slot = NULL;
 
	      foreach ($course as $course_slot)
 
		break;
 
	      if (empty($course_slot) || !$course_slot->sections_count())
 
	      {
 
		unset($this->courses[$i]);
 
		$this->courses = array_values($this->courses);
 
		return $this->findPossibilities();
 
	      }
 
	    }
 

	
 
		$this->possiblePermutations = 1;
 
		/* special case: there is nothing entered into the schedule and thus there is one, NULL permutation */
 
		if (!count($this->courses))
 
		{
 
			/* have an empty schedule */
 
@@ -260,13 +308,21 @@ class Schedule
 
		$position = 0;
 
		$counter = 0;
 

	
 
		$i = 0;
 
		foreach ($this->courses as $course)
 
		{
 
			$this->possiblePermutations = $this->possiblePermutations * $course->getnsections();
 
		  /*
 
		   * Kludge for until we support course_slots natively
 
		   * or find a better solution.
 
		   */
 
		  unset($course_slot);
 
		  foreach ($course as $course_slot)
 
		    break;
 

	
 
			$this->possiblePermutations = $this->possiblePermutations * $course_slot->sections_count();
 
			$cs[$i] = 0;	// Sets the counter array to all zeroes.
 
			$i ++;
 
		}
 
        
 
		// Checks for conflicts in given classes, stores if none found
 
		do
 
@@ -276,14 +332,21 @@ class Schedule
 
			// Get first class to compare
 
			for ($upCounter = 0; $upCounter < count($this->courses) && !$conflict; $upCounter ++)
 
			{
 
	    
 
			  for ($downCounter = count($this->courses) - 1; $downCounter > $upCounter && !$conflict; $downCounter --)
 
				{
 
				  if ($this->courses[$upCounter]->getSection($cs[$upCounter])
 
				      ->conflictsWith($this->courses[$downCounter]->getSection($cs[$downCounter])))
 
				  unset($course_slot_up);
 
				  foreach ($this->courses[$upCounter] as $course_slot_up)
 
				    break;
 

	
 
				  unset($course_slot_down);
 
				  foreach ($this->courses[$downCounter] as $course_slot_down)
 
				    break;
 

	
 
				  if ($course_slot_up->section_get_i($cs[$upCounter])->conflictsWith($course_slot_down->section_get_i($cs[$downCounter])))
 
				    {
 
				      $conflict = TRUE;
 
				      break;
 
				    }
 
				}
 
			}
 
@@ -302,13 +365,17 @@ class Schedule
 
	$cs[$position] = $cs[$position] + 1;
 
			
 
	// Check to make sure the counter is still valid.
 
	$valid = false;
 
	while(!$valid)
 
	  {
 
	    if($cs[$position] == $this->courses[$position]->getnsections())
 
	    unset($course_slot);
 
	    foreach ($this->courses[$position] as $course_slot)
 
	      break;
 

	
 
	    if($cs[$position] == $course_slot->sections_count())
 
	      {
 
		$cs[$position] = 0;
 

	
 
		$position++;
 
					
 
		// This is for the very last permutation. Even 
 
@@ -455,15 +522,16 @@ class Schedule
 
	$have_saturday = FALSE;
 

	
 
	$max_time = (int)max($time);
 
	$min_time = (int)min($time);
 
	$sort_time = FALSE;
 
	foreach ($this->courses as $course)
 
	  foreach ($course as $course_slot)
 
	  {
 
	    for ($si = 0; $si < $course->getnsections(); $si ++)
 
	      foreach ($course->getSection($si)->getMeetings() as $meeting)
 
	    for ($si = 0; $si < $course_slot->sections_count(); $si ++)
 
	      foreach ($course_slot->section_get_i($si)->getMeetings() as $meeting)
 
		{
 
		  /* Saturdayness */
 
		  if ($meeting->getDay(5))
 
		    {
 
		      $max_day_plusone = 6;
 
		      $have_saturday = TRUE;
 
@@ -586,14 +654,17 @@ class Schedule
 
		  /* Makes sure there is not a class already in progress */
 
		  if($rowspan[$dayLoop] <= 0)
 
		    {
 
		      for($j = 0; $j < count($this->courses); $j++)
 
			{
 
			  $course = $this->courses[$j];
 
			  foreach ($course as $course_slot)
 
			    {
 
			  $section_index = $this->storage[$i][$j];
 
			  $section = $course->getSection($section_index);
 
			      $section = $course_slot->section_get_i($section_index);
 

	
 
				  /* iterate through all of a class's meeting times */
 
				  $meetings = $section->getMeetings();
 

	
 
				  /* find any meeting which are going on at this time */
 
				  $current_meeting = NULL;
 
				  foreach ($meetings as $meeting)
 
@@ -641,18 +712,19 @@ class Schedule
 
					. "</td>\n";
 

	
 
				      /* for the ``Registration Codes'' dialogue: */
 
				      if (empty($permutations_courses[$j]))
 
					{
 
					  $singleton_course = new Course($course->getName(), $course->title_get());
 
					  $singleton_course->section_add($section);
 
					  $singleton_course->section_add($section, $course_slot->id_get());
 
					  $permutation_courses[$j] = $singleton_course->to_json_array();
 
					}
 

	
 
				      $filled = TRUE;
 
				    }
 
			    } /* $course_slot */
 
			}
 
		    }
 

	
 
		  if ($rowspan[$dayLoop] > 0)
 
		    {
 
		      $filled = TRUE;
 
@@ -720,21 +792,52 @@ class Schedule
 
  {
 
    return count($this->courses);
 
  }
 

	
 
  /**
 
   * \brief
 
   *   fetch a specified class by its key
 
   *   Fetch a specified class by its key.
 
   *
 
   * Use Schedule::courses_get() instead of this function if the code
 
   * you're writing understand CourseSlot objects.
 
   *
 
   * \see Schedule::courses_get().
 
   */
 
  function class_get($class_key)
 
  public function class_get($class_key)
 
  {
 
    return $this->courses[$class_key];
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Get an array of Course objects as originally inputted by the
 
   *   user.
 
   */
 
  public function courses_get()
 
  {
 
    /*
 
     * As Mr. Westra would say, just map them courses back into their
 
     * original forms.
 
     */
 

	
 
    $courses = array();
 
    foreach ($this->courses as $course_i => $course)
 
      {
 
	$mapping = $this->course_slot_mappings[$course_i];
 
	if (empty($courses[$mapping]))
 
	  $courses[$mapping] = new Course($course->getName(), $course->title_get());
 

	
 
	foreach ($course as $course_slot)
 
	  $courses[$mapping]->course_slot_add($course_slot);
 
      }
 

	
 
    return $courses;
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Set my global ID.
 
   *
 
   * Only to be called by schedule_store_store().
 
   */
 
  function id_set($id)
 
  { 
 
@@ -801,22 +904,21 @@ class Schedule
 
   * \brief
 
   *   A magic function which tries to upgrade old serialized sections
 
   *   to the new format.
 
   */
 
  function __wakeup()
 
  {
 
    if ($this->nclasses == -1)
 
      /* this Schedule doesn't need to be upgraded from Classes to Course */
 
      return;
 
    if ($this->nclasses != -1)
 
      {
 
	/* this Schedule needs to be upgraded from Classes to Course */
 

	
 
    $this->courses = array();
 
    foreach ($this->classStorage as $classes)
 
      {
 
	$this->courses[] = $classes->to_course();
 
	$this->nclasses = -1;
 
      }
 
    $this->nclasses = -1;
 

	
 
    if (empty($this->parent_id))
 
      $this->parent_id = NULL;
 

	
 
    if (empty($this->school_id))
 
      {
 
@@ -833,8 +935,15 @@ class Schedule
 
	    page::session_start();
 

	
 
	    $school = school_load($this->school_id);
 
	    $this->semester = school_semester_guess($school);
 
	  }
 
      }
 

	
 
    if (empty($this->course_slot_mappings))
 
      {
 
	$this->course_slot_mappings = array();
 
	foreach ($this->courses as $course_i => $course)
 
	  $this->course_slot_mappings[$course_i] = count($this->course_slot_mappings);
 
  }
 
}
 
}
inc/class.semester.inc
Show inline comments
 
@@ -152,14 +152,20 @@ class Semester
 
   * \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)
 
  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]))
 
@@ -169,13 +175,13 @@ class Semester
 
      }
 
    else
 
      {
 
	$classobj = $this->departments[$dept][$class];
 
      }
 

	
 
    $classobj->section_add($section);
 
    $classobj->section_add($section, $course_slot_id);
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Add a section_meeting, calling Semester::section_add() as
 
   *   necessary.
 
@@ -195,27 +201,30 @@ class Semester
 
   *   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.
 
   */
 
  public function section_meeting_add($dept, $course, $title, $section, $synonym, $section_meeting)
 
  public function section_meeting_add($dept, $course, $title, $section, $synonym, $section_meeting, $course_slot_id = 'default')
 
  {
 
    $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), $title);
 
      return $this->section_add($dept, $course, new Section($section, array($section_meeting), $synonym), $title, $course_slot_id);
 

	
 
    $section_obj->meeting_add($section_meeting);
 
    return;
 
  }
 

	
 
  /**
input.php
Show inline comments
 
@@ -122,16 +122,15 @@ jQuery(document).ready(
 
  {
 
    var class_last = 0;
 

	
 
';
 
if ($sch)
 
{
 
  $nclasses = $sch->nclasses_get();
 
  for ($class_key = 0; $class_key < $nclasses; $class_key ++)
 
  foreach ($sch->courses_get() as $course)
 
    {
 
      $my_hc .= input_class_js($sch->class_get($class_key), '    ');
 
      $my_hc .= input_course_js($course, '    ');
 
    }
 
}
 
elseif ($errors_fix)
 
  {
 
    foreach ($_POST['postData'] as $course)
 
      if (is_array($course))
 
@@ -150,21 +149,22 @@ elseif ($errors_fix)
 
		. 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']) . ');' . PHP_EOL;
 
		. json_encode($section['type']) . ', '
 
		. json_encode($section['slot']) . ');' . PHP_EOL;
 
	  $my_hc .= PHP_EOL;
 
	}
 
  }
 
else
 
  {
 
    $default_courses = school_default_courses($school);
 
    foreach ($default_courses as $default_class)
 
      $my_hc .= input_class_js($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;
 
@@ -327,35 +327,35 @@ if (!empty($_REQUEST['selectsemester']))
 
<div id="showInstructions" style="width: 100%; text-align: center;"><a href="#">Detailed Instructions...</a></div>
 

	
 
<?php
 
$inputPage->showSchoolInstructions();
 
$inputPage->foot();
 

	
 
function input_class_js(Course $course, $whitespace = '  ')
 
function input_course_js(Course $course, $whitespace = '  ')
 
{
 
  $title = $course->title_get();
 
  if (empty($title))
 
    $title = '';
 
  $js = $whitespace . 'class_last = add_class_n(' . json_encode($course->getName()) . ', '
 
    . json_encode($title) . ');' . PHP_EOL;
 

	
 
  $nsections  = $course->getnsections();
 
  for ($section_key = $nsections - 1; $section_key >= 0; $section_key --)
 
  foreach ($course as $course_slot)
 
    foreach ($course_slot as $section)
 
    {
 
      $section = $course->getSection($section_key);
 
      $meetings = $section->getMeetings();
 
      foreach ($meetings as $meeting)
 
	{
 
	  $js .= $whitespace . 'add_section_n(class_last, ' . json_encode($section->getLetter()) . ', '
 
	    . json_encode($section->getSynonym()) . ', '
 
	    . json_encode($meeting->getStartTime()) . ', '
 
	    . json_encode($meeting->getEndTime()) . ', '
 
	    . json_encode(array('m' => $meeting->getDay(0), 't' => $meeting->getDay(1), 'w' => $meeting->getDay(2), 'h' => $meeting->getDay(3), 'f' => $meeting->getDay(4),
 
				's' => $meeting->getDay(5))) . ', '
 
	    . json_encode($meeting->instructor_get()) . ', '
 
	    . json_encode($meeting->getLocation()) . ','
 
	    . json_encode($meeting->type_get()) . ');' . PHP_EOL;
 
	    . json_encode($meeting->type_get()) . ', '
 
	    . json_encode($course_slot->id_get()) . ');' . PHP_EOL;
 
	}
 
    }
 

	
 
  return $js;
 
}
process.php
Show inline comments
 
@@ -205,13 +205,16 @@ if(!$DEBUG)
 
		$allClasses->addCourse($course['name'], $course['title']);
 

	
 
				foreach($course as $section)
 
				  /* Skip the section name, which isn't a section */
 
					if(is_array($section))
 
					  {
 
					    $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']);
 
					    if (empty($section['slot']))
 
					      $section['slot'] = 'default';
 

	
 
					    $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']);
 
					    if ($error_string !== NULL)
 
					      $errors[] = $error_string;
 
					  }
 
			}
 
		}
 

	
school.d/default.inc
Show inline comments
 
@@ -79,11 +79,12 @@ function default_registration_html(Page 
 
    . '    Enter these codes into ' . htmlentities($school['name']) . '\'s online course registration' . PHP_EOL
 
    . '    system (<a href="' . htmlentities($link_url) . '" target="_blank">' . htmlentities($link_text) . '</a>)' . PHP_EOL
 
    . '    to register for classes:' . PHP_EOL
 
    . '  </p>' . PHP_EOL
 
    . '  <ul class="synonym-list">' . PHP_EOL;
 
  foreach ($courses as $course)
 
    foreach ($course as $section)
 
    foreach ($course as $course_slot)
 
      foreach ($course_slot as $section)
 
      $html .= '    <li>' . htmlentities($section->getSynonym()) . '</li>' . PHP_EOL;
 
  $html .= '  </ul>' . PHP_EOL;
 
  return $html;
 
}
school.d/umich.crawl.inc
Show inline comments
 
@@ -225,19 +225,22 @@ function umich_crawl_csv($school_crawl_l
 
	  if ($semester->class_get($dept, $course_id) === NULL)
 
	    $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']]));
 

	
 
      $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']]),
 
							school_crawl_meeting_type(trim($row[$fields['Component']])),
 
							trim($row[$fields['Instructor']])));
 
							$meeting_type,
 
							trim($row[$fields['Instructor']])),
 
				     $meeting_type);
 
    }
 
}
 

	
 
/**
 
 * \brief
 
 *   Try to turn a umich-formatted time into something usable.
scripts/scheduleInput.js
Show inline comments
 
@@ -59,12 +59,22 @@ var course_ajax_requests = [];
 
 * keep a course added at the end of the list of courses. If this
 
 * variable is -1, it indicates that no such free course exists. If it
 
 * is zero or greater, that number is the class which is the free one.
 
 */
 
var slate_permutate_course_free = -1;
 

	
 
/**
 
 * \brief
 
 *   Whether or not we should divulge the existence of course slots to
 
 *   the user.
 
 *
 
 * This will automatically be set to true as soon as a course
 
 * utilizing multiple slots is added using autocomplete.
 
 */
 
var show_course_slots = false;
 

	
 
/*
 
 * General Input Functions
 
 */
 

	
 
/**
 
 * Outputs an <option/> element. It will inlcude selected="selected"
 
@@ -106,22 +116,26 @@ function addTips()
 
}
 

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

	
 
    if(type == 'lab')
 
	cssclasses += ' lab';
 

	
 
    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]" /></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) +
 
@@ -182,13 +196,24 @@ function add_section_n(cnum, name, synon
 
<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 class="deleteSection"><input type="button" value="x" class="gray" /></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]" />' +
 
	'</td></tr>';
 

	
 
    jQuery('tr.class' + cnum + ':last').after(section_html);
 
    /*
 
     * 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});
 

	
 
@@ -196,25 +221,26 @@ function add_section_n(cnum, name, synon
 
     * 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);
 

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

	
 
    return last_section_i - 1;
 
}
 
function add_section(cnum)
 
{
 
    var section_i = add_section_n(cnum, '', '', '', '', {m: false, t: false, w: false, h: false, f: false, s: false}, '', '', '');
 
    var section_i = add_section_n(cnum, '', '', '', '', {m: false, t: false, w: false, h: false, f: false, s: false}, '', '', '', 'default');
 
    if (cnum == slate_permutate_course_free)
 
	course_free_check(cnum);
 
    return section_i;
 
}
 

	
 
/**
 
@@ -239,13 +265,16 @@ function add_sections(cnum, data)
 

	
 
    if (!data.sections)
 
	return;
 

	
 
    jQuery.each(data.sections, function(i, section)
 
		{
 
		    add_section_n(cnum, section.section, section.synonym, section.time_start, section.time_end, section.days, section.instructor, section.location, section.type);
 
		    if (!section.slot)
 
			section.slot = 'default';
 

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

	
 
    /*
 
     * Handle course-level interdependencies.
 
     */
 
    if (data.dependencies)
 
@@ -259,12 +288,63 @@ function add_sections(cnum, data)
 
			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
 
 *   The idenfifier of the slot.
 
 */
 
function course_add_slot_row(course_i, slot_id)
 
{
 
    var extra_classes = '';
 

	
 
    if (!show_course_slots)
 
    {
 
	var aclass;
 
	/*
 
	 * Then check if this course has multiple slots and we should
 
	 * enable displaying them to the user.
 
	 */
 
	aclass = jQuery('.' + safe_css_class('class' + course_i) + '.section .section-slot-entry');
 
	if (aclass.length && aclass.val() != slot_id)
 
	{
 
	    enable_course_slots();
 
	}
 
    }
 
    if (!show_course_slots)
 
	extra_classes += ' tr-slot-id-hidden';
 

	
 
    jQuery('tr.class' + course_i + ':last').after(
 
	'<tr class="class' + course_i + ' tr-slot-id ' + safe_css_class('slot-' + slot_id) + extra_classes + '">\n' +
 
	    '  <td><span /></td>\n' +
 
	    '  <td colspan="10"><span class="slot-id-text" /></td>\n' +
 
	    '  <td colspan="2"><span /></td>\n' +
 
	    '</tr>\n'
 
    );
 
    jQuery('tr.class' + course_i + ':last .slot-id-text').text('The following are ' + slot_id + ' sections and will be scheduled as a group.');
 
}
 

	
 
/**
 
 * \brief
 
 * Dynamically enable the displification of course slots to the user.
 
 */
 
function enable_course_slots()
 
{
 
    show_course_slots = true;
 

	
 
    jQuery('.tr-slot-id-hidden').removeClass('tr-slot-id-hidden');
 
}
 

	
 
/**
 
 * \brief
 
 *   Adds a new class to the input.
 
 *
 
 * \param course_id
 
 *   The course_id.
 
 * \param title
 
 *   The human-friendly course title.
 
@@ -551,12 +631,24 @@ function prettyTime(time_str)
 
	hour_str = '0' + hour_str;
 
    */
 

	
 
    return hour_str + ':' + time_str.substr(2) + ' ' + m + 'm';
 
}
 

	
 
/**
 
 * \brief
 
 *   Takes any value classname and tries to smooth it out to a valid
 
 *   CSS class name.
 
 *
 
 * \todo STUB
 
 */
 
function safe_css_class(classname)
 
{
 
    return classname;
 
}
 

	
 
//--------------------------------------------------
 
// Items bound to pageload/events
 
//--------------------------------------------------
 
jQuery(document).ready(function() {
 

	
 
	//--------------------------------------------------
styles/general.css
Show inline comments
 
@@ -383,12 +383,17 @@ a:hover {
 

	
 
.course-title-entry
 
{
 
    width: 80%;
 
}
 

	
 
.tr-slot-id-hidden
 
{
 
  display: none;
 
}
 

	
 
.inPlace {
 
  color: #000;
 
  border: none;
 
  background: transparent;
 
}
 

	
0 comments (0 inline, 0 general)