* * This file is a part of slate_permutate. * * slate_permutate 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. * * slate_permutate 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 slate_permutate. If not, see . */ /** * \file * * Provide a method of storing and retrieving school-specific * information. Identifying schools is intended to be useful for * obtaining and storing preknowledge of the sections a school offers * to allow easier input. * * Anything code specific to a particular school should be placed in a * file in the school.d directory. The filename shall be the short, * alphanumeric, machine-usable school identifier followed by * ``.inc''. This allows optimized loading of school-specific routines * when the identifier is already known. */ /** * \brief * Load a school profile based on its identifier. * * This function loads the school's description file and asks for info * from a callback called $school_id . '_info' which must return an * array with the following keys: * - name: a friendly name for the school. Must be a valid XHTML attribute string. * - url: the school's website URL as a valid XHTML attribute string. (i.e., escape ampersands). * - example_course_id: An example course identifier representative of a school's course IDs. (e.g., CS-101 for Calvin). * - id: The school's ID. * * \param $school_id * The school's alphanumeric identifier (which determines the name * of the school's *.inc file). * \param $load_all_inc * Asks for a school's extraneous .inc files to be loaded * to. Intended for use by rehash.php only. * \return * A school_profile handle or NULL on error. */ function school_load($school_id, $load_all_inc = FALSE) { $school = array('id' => $school_id); /* guard against cracking attempts (protects against '../' and friends) */ if (!preg_match('/^[0-9a-z]+$/', $school_id)) return NULL; $school_file_name_base = dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'school.d' . DIRECTORY_SEPARATOR; $school_file_name = $school_file_name_base . $school_id . '.inc'; if (!file_exists($school_file_name)) return NULL; require_once($school_file_name); if ($load_all_inc) { $school_crawl_file_name = $school_file_name_base . $school_id . '.crawl.inc'; if (file_exists($school_crawl_file_name)) require_once($school_crawl_file_name); } $school_info = $school_id . '_info'; $school += $school_info(); /* * append small amount of info from the cache entry for this school: * whether or not it was crawled. * * Perhaps this stuff should be just moved into the _info function * for efficiency. */ $cache = _school_cache_load(); if ($cache && count($cache['list']) && isset($cache['list'][$school['id']])) { $school['crawled'] = $cache['list'][$school['id']]['crawled']; $school_semesters_filename = dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR . 'auto' . DIRECTORY_SEPARATOR . $school['id'] . DIRECTORY_SEPARATOR . '-semesters'; if (file_exists($school_semesters_filename)) $school['semesters'] = unserialize(file_get_contents($school_semesters_filename)); else $school['semesters'] = array(); } return $school; } /** * \brief * Tries to guess what school a connection comes from. * * This function checks if $_REQUEST['school'] is set to a valid * school, so that the user can manually choose his school. Then it * chcecks if the user's session specifies what school profile to * use. Then it tries to make a best guess as to the school he's from * using the rDNS information provided by the httpd. * * \return * A school profile or NULL if the school isn't in the session and * can't be guessed. */ function school_load_guess() { if (isset($_REQUEST['school'])) { $school = school_load($_REQUEST['school']); if ($school) { $_SESSION['school'] = $school['id']; return $school; } } /* assume that we stored a valid school in the $_SESSION */ if (isset($_SESSION['school'])) return school_load($_SESSION['school']); if (isset($_SERVER['REMOTE_HOST']) || isset($_SERVER['REMOTE_ADDR'])) { $addr = NULL; if (!isset($_SERVER['REMOTE_HOST'])) $addr = gethostbyaddr($_SERVER['REMOTE_ADDR']); $cache = _school_cache_load(); if ($addr && $cache && count($cache['domains'])) { $domain_parts = array_reverse(explode('.', $addr)); $domain_school = $cache['domains']; while (is_array($domain_school)) { $domain_part = array_shift($domain_parts); if (isset($domain_school[$domain_part])) $domain_school = $domain_school[$domain_part]; else $domain_school = NULL; } /* * by now, $domain_school is either NULL or the school_id of * the school we want. */ if ($domain_school) { $school = school_load($domain_school); if ($school) { $_SESSION['school'] = $domain_school; return school_load($domain_school); } } } } /* * set something in $_SESSION so that the gethostbyaddr() call * doesn't have to be done too often. (the isset() call above should * detect even the empty string). */ $_SESSION['school'] = 'default'; /* loading the school_id of 'default' MUST always work */ return school_load($_SESSION['school']); } /** * \brief * Render a list of school profile choices. * * Loads the list of schools and transforms the list into HTML, * optionally highlighting a specified school. * * The list of schools includes links to the specified destination, * appending a &school= to the query string. This is intended to work * in conjunction with school_load_guess() to allow the user to * manually choose his school. * * \param $highlight * The school_id of the school whose list entry should be * highlighted or NULL to avoid highlighting any entry. * \param $linkto * Each school entry shall be a link for the user to switch which * school profile he's using. This is to specify the URL or page * these links should point to (the rest is handled by * school_load_guess()). We will call htmlentities() for you. * \return * An HTML formatted list of school profile choices where each entry * is a link setting the client's choice to the specified school. */ function school_list_html($highlight = NULL, $linkto = NULL) { $cache = _school_cache_load(); if (!$cache || !count($cache['list'])) return NULL; $school_list = $cache['list']; /* form the query string for the links */ if (!$linkto) $linkto = '?'; elseif (strpos($linkto, '?') === FALSE) $linkto .= '?'; else $linkto .= '&'; $linkto .= 'school='; $linkto = htmlentities($linkto); $html = "\n"; return $html; } /** * \brief * Get a school-specific information page. * * Each school may define a function called * _instructions_html(). This is the wrapper which retrieves a * specific school's info HTML. It is recommended that instructions * about using the school's registration system in conjunction with * slate_permutate be placed in the instructions_html. * * \param $school * A school handle obtained from school_load() or * school_load_guess(). * \return * An HTML fragment of the school's information or NULL if the * school either doesn't have any such information or if the school * handle is invalid. */ function school_instructions_html($school) { global $school_default_school; if (empty($school) || empty($school['id'])) /* * Invalid param deserves a NULL :-p. Really, this invalid param * handling shouldn't be needed... */ return NULL; $school_instructions_html = $school['id'] . '_instructions_html'; if (!function_exists($school_instructions_html)) { /* load the default school's _instructions_html() function */ if ($school_default_school === NULL) $school_default_school = school_load('default'); /* ``hacky'', but preferable to recursion: */ $school_instructions_html = 'default' . '_instructions_html'; /* be 503-safe */ if (!function_exists($school_instructions_html)) return NULL; } return $school_instructions_html(); } /** * \brief * Return information about available semesters. * * \param $school * The school. * \return * An array with keys being semester IDs ordered by weights with * lowest first and keys of 'id' (the semester's ID), 'name' (the * friendly name), and 'weight' (lower numbers mean these semesters * should be earlier, may be positive or negative). 'time_start', * 'time_end' are unix timestamps estimating the begin and end point * of each semester. */ function school_semesters(array $school) { if (empty($school['crawled'])) return array(); return $school['semesters']; } /** * \brief * Return the semester which either the user has selected or which * makes the most sense. * * \param $school * The school for which a semester should be guessed. * \return * An array with the keys 'id', 'name', and 'weight' corresponding * to the same keys in the arrays returned by school_semesters() or * NULL if no semester can be found. */ function school_semester_guess(array $school) { $semesters = school_semesters($school); if (!empty($_REQUEST['semester']) && isset($semesters[$_REQUEST['semester']])) { $semester = $semesters[$_REQUEST['semester']]; $_SESSION['semester'] = $semester['id']; return $semester; } if (!empty($_SESSION['semester']) && isset($semesters[$_SESSION['semester']])) return $semesters[$_SESSION['semester']]; /* * The following is the most _common_ scenario: * * A student is looking ahead in the last half of March (3) to * register for a semester starting in September (9) and ending in * December. Thus, looking 6 months into the future may put us right * in the middle of the desired semester, also considering that * during the summer (6) one is looking to register for a fall * semester which ends in December (12). */ $time_target = time() + 60*60*24*365.25 * 0.5; $semester = NULL; /* guessed semester */ $best_semester = NULL; /* * The absolute value of the difference between the $time_target and * the middle of the guessed semester. Smaller is better. */ $best_score = -1; foreach ($semesters as $semester) { $my_score = abs(($semester['time_end'] + $semester['time_start']) / 2 - $time_target); if ($best_score == -1 || $my_score < $best_score) { $best_semester = $semester; $best_score = $my_score; } error_log($semester['name'] . ': ' . $my_score); } if (!empty($best_semester)) return $best_semester; return $semester; } /** * \brief * Return an array of default classes for a particular school. * * \param $school * The school's handle. */ function school_default_courses($school) { $school_default_courses = $school['id'] . '_default_courses'; if (function_exists($school_default_courses)) { require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'class.course.inc'); return $school_default_courses(); } return array(); } /** * \brief * Return an example course id for the school. * * Each school may specify an example course ID by placing a key * called 'example_course_id' into the array returned by its * _info() function. See school_load(). * * \param $school * The school's handle. * \return * A string containing a representative example of a course ID for * the given school. */ function school_example_course_id(array $school) { return $school['example_course_id']; } /** * \brief * Determine if a school has crawler data stored. * * \param $school * The which should be checked. */ function school_has_auto(array $school) { return isset($school['crawled']) && $school['crawled']; } /** * \brief * Used to load the school cache. * * \return * The cache array or NULL if the cache couldn't be loaded. */ function _school_cache_load() { static $cache = NULL; if ($cache != NULL) return $cache; $cache_file_name = dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR . 'schools'; $cache_serialized = @file_get_contents($cache_file_name); if (!empty($cache_serialized)) $cache = unserialize($cache_serialized); return $cache; }