# HG changeset patch
# User Nathan Phillip Brink
# Date 2010-10-09 01:54:03
# Node ID d903bd3d579e5c9ba361d6cad8048535d9da2eb7
# Parent 9032b77926d8f8f3d6e978c3fdd5818b072c9337
Add school profiles support. Currently, the only significant thing school profiles does is provide a different blog of HTML at the bottom of the input.php page so that users may receive school-specific instructions on how to use slate_permutate. School profiles _will_ be extended to provide autocompletion of sections for a specific course based on previously crawling a college's registration website. All-in-all, it maintains a simple school_id string which can be used by other components to uniquely identify a school for any arbitrary purpose.
diff --git a/.hgignore b/.hgignore
--- a/.hgignore
+++ b/.hgignore
@@ -2,6 +2,8 @@ style: regex
# ignore all saved schedules, but keep track of the .keep file.
^saved_schedules/[^.]
+# ignore all of cache except for the .keep file
+^cache/[^.]
# ignore common unwanted suffixes
(~|\.orig|\.rej)$
diff --git a/admin/rehash.php b/admin/rehash.php
new file mode 100755
--- /dev/null
+++ b/admin/rehash.php
@@ -0,0 +1,153 @@
+#!/usr/bin/env php
+
+ *
+ * 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
+ *
+ * Runs through schools.d grabbing and caching data, such as the
+ * school listing used for the ``choose your school list''.
+ */
+
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'inc' . DIRECTORY_SEPARATOR . 'school.inc');
+return main($argc, $argv);
+
+function main($argc, $argv)
+{
+ $school_id_list = school_list();
+ if (!$school_id_list)
+ return 1;
+
+ $schools = array();
+ foreach ($school_id_list as $school_id)
+ {
+ $school = school_load($school_id);
+ if (!$school)
+ {
+ fprintf(STDERR, "Error loading school with school_id=%s\n",
+ $school_id);
+ return 1;
+ }
+ $schools[] = $school;
+ }
+
+ if (school_cache($schools))
+ {
+ fprintf(STDERR, "Error writing out school cache\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ * functions which are only needed when recreating the cache.
+ */
+
+/**
+ * \brief
+ * Returns the list of available school IDs or NULL on error.
+ */
+function school_list()
+{
+ $schoold_dir_name = dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'school.d';
+ $schoold_dir = opendir($schoold_dir_name);
+ if ($schoold_dir === FALSE)
+ {
+ fprintf(STDERR, "Unable to open school.d directory. Was using path: `%s'\n",
+ $schoold_dir_Name);
+ return NULL;
+ }
+
+ $school_id_list = array();
+ while ($filename = readdir($schoold_dir))
+ {
+ if (!preg_match('/^([a-z0-9]+)\.inc$/', $filename, $matches))
+ continue;
+
+ $school_id_list[] = $matches[1];
+ }
+
+ closedir($schoold_dir);
+
+ return $school_id_list;
+}
+
+/**
+ * \brief
+ * Write out the cache file which remembers the list of available
+ * schools.
+ *
+ * \todo
+ * If the list of displayed schools is to be sorted, this is the
+ * place to do it.
+ *
+ * \param $schools
+ * An array of school handles.
+ */
+function school_cache($schools)
+{
+ $list_cache = array();
+ $domain_cache = array();
+ foreach ($schools as $school)
+ {
+ $list_cache[$school['id']] = array(
+ 'name' => $school['name'],
+ 'url' => $school['url'],
+ );
+ foreach ($school['domains'] as $school_domain)
+ {
+ $domain_cache_ptr =& $domain_cache;
+
+ $domain_parts = array_reverse(explode('.', $school_domain));
+ while (count($domain_parts) > 1)
+ {
+ $domain_part = array_shift($domain_parts);
+ if (!isset($domain_cache_ptr[$domain_part])
+ || !is_array($domain_cache_ptr[$domain_part]))
+ $domain_cache_ptr[$domain_part] = array();
+ $domain_cache_ptr =& $domain_cache_ptr[$domain_part];
+ }
+ /*
+ * get the last part which is unambiguously identifies this
+ * school combined with the previous parts
+ */
+ $domain_part = array_shift($domain_parts);
+ $domain_cache_ptr[$domain_part] = $school['id'];
+ }
+ }
+
+ $cache = array('list' => $list_cache, 'domains' => $domain_cache);
+
+
+ $cache_file_name = dirname(__FILE__) . DIRECTORY_SEPARATOR . '..'
+ . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR . 'schools';
+ $cache_file = fopen($cache_file_name, 'wb');
+ if ($cache_file === FALSE)
+ {
+ fprintf(STDERR, "Unable to open `%s' for writing\n",
+ $cache_file_name);
+ return 1;
+ }
+ fwrite($cache_file, serialize($cache));
+ fclose($cache_file);
+
+ return 0;
+}
diff --git a/cache/.keep b/cache/.keep
new file mode 100644
diff --git a/inc/class.page.php b/inc/class.page.php
--- a/inc/class.page.php
+++ b/inc/class.page.php
@@ -29,8 +29,13 @@ class page
private $pagetitle = ''; // Title of page
private $scripts = array(); // Scripts to include on page
+ /* the current school. See get_school(). */
+ private $school;
+
public function __construct($ntitle, $nscripts = array(), $immediate = TRUE)
{
+ require_once('school.inc');
+
// Scripts and styles available to include
$this->headCode['jQuery'] = '';
$this->headCode['jQueryUI'] = '';
@@ -62,6 +67,10 @@ class page
if($immediate
&& $ntitle != "NOHEAD")
$this->head();
+
+ /* everything that needs sessions started to work: */
+
+ $this->school = school_load_guess();
}
/**
@@ -162,6 +171,65 @@ class page
/**
* \brief
+ * Display a list of schools the user might be from.
+ * \param $linkto
+ * The to which a &school= or ?school= query string should be
+ * appended.
+ */
+ public function showSchools($linkto)
+ {
+ echo "
\n";
+ }
+
+ /**
+ * \brief
+ * Print out a vocative form of a student's identity. For example,
+ * Dearborn Christin Schoolers are called ``Knights'' as are
+ * Calvin College students.
+ *
+ * The third argument is used to determine whether or not this
+ * address _needs_ to be printed out. For example, in some sentences
+ * when addressing generic students, it makes no sense to say the
+ * standard ``Welcome, student'' or ``Dear generic person, how do
+ * you do today?''. If the third argument is false, we'll refrain
+ * from outputting anything at all.
+ *
+ * \param $prefix
+ * If the address is to be printed, output this beforehand. Useful
+ * if this prefix shouldn't be printed if the address itself isn't
+ * to be printed. See $necessary.
+ * \param $postfix
+ * Text to print after the address if it's printed.
+ * \param $necessary
+ * Whether or not we might ignore the request that an address be
+ * printed in certain cases. We default to always printing the
+ * address.
+ */
+ public function addressStudent($prefix = '', $postfix = '', $necessary = TRUE)
+ {
+ if (!$necessary && $this->school['id'] == 'default')
+ return;
+
+ echo $prefix . $this->school['student_address'] . $postfix;
+ }
+
+ /**
+ * \brief
* Display a 404 page and halt the PHP interpreter.
*
* This function does not return. It handles the creation of a Page
@@ -185,4 +253,13 @@ class page
exit();
}
+
+ /**
+ * \brief
+ * Get the current school profile handle.
+ */
+ public function get_school()
+ {
+ return $this->school;
+ }
}
diff --git a/inc/school.inc b/inc/school.inc
new file mode 100644
--- /dev/null
+++ b/inc/school.inc
@@ -0,0 +1,253 @@
+
+ *
+ * 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).
+ *
+ * \param $school_id
+ * The school's alphanumeric identifier (which determines the name
+ * of the school's *.inc file).
+ * \return
+ * A school_profile handle or NULL on error.
+ */
+function school_load($school_id)
+{
+ $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 = dirname(__FILE__) . DIRECTORY_SEPARATOR
+ . '..' . DIRECTORY_SEPARATOR . 'school.d' . DIRECTORY_SEPARATOR . $school_id . '.inc';
+
+ if (!file_exists($school_file_name))
+ return NULL;
+
+ require_once($school_file_name);
+
+ $school_info = $school_id . '_info';
+ $school += $school_info();
+
+ 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)
+{
+ if (!$school || !$school['id']
+ || !function_exists($school['id'] . '_instructions_html'))
+ return NULL;
+
+ $school_instructions_html = $school['id'] . '_instructions_html';
+ return $school_instructions_html();
+}
+
+/**
+ * \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 (isset($cache_serialized))
+ $cache = unserialize($cache_serialized);
+
+ return $cache;
+}
diff --git a/input.php b/input.php
--- a/input.php
+++ b/input.php
@@ -39,9 +39,39 @@ var sectionsOfClass = Array();
// ', TRUE);
$inputPage->head();
+
+/*
+ * Force a student to choose a school or declare he's a generic
+ * student before displaying the input form.
+ */
+$school = $inputPage->get_school();
+if ($_REQUEST['setted'] == 1 || $school['id'] != 'default')
+ $_SESSION['school_setted'] = TRUE;
+if ($_REQUEST['selectschool'] == 1
+ || $school['id'] == 'default' && !isset($_SESSION['school_setted']))
+ {
+?>
+
School Selection
+
+ Choose the school you attend from the list below. If you cannot
+ find your school, you may proceed using
+ the generic
+ settings.
+
Welcome to SlatePermutate! To get started, enter in some of your classes, and add available sections for each class.
+
+ Welcome to SlatePermutateaddressStudent(', ', '',
+ FALSE); ?>! (Not from ?) To get started, enter in some of your
+ classes, and add available sections for each class.
+