view index.php @ 177:f8ae384b817e

Change fetching order again.
author Matti Hamalainen <ccr@tnsp.org>
date Tue, 25 Aug 2015 08:31:20 +0300
parents 113820219e8b
children 6a7ff339b4bb
line wrap: on
line source

<?php
//
// OAMK Lukkari v2.5
// (C) Copyright 2010 - 2015 Matti 'ccr' Hämäläinen <ccr@tnsp.org>
// Yes, this code is rather horrible. :|
//
// Include framework
require "msitegen.inc.php";

// Default settings
$pageName = "OAMK Lukkari";
$pageVersion = "2.5";
$mobileMode = FALSE;
$baseURI = "http://example.com/";

$pageLanguages = array("fi", "en");
$pageCSSData = array("cookie" => "lukcss", "prefix" => "luk");

$classDefaultID = "DEFAULT";
$classIDFile = array(FALSE => "classes.txt", TRUE => "classes_next.txt");
$courseCacheFile = "coursecache.txt";

if (file_exists("config.inc.php"))
  @require "config.inc.php";

//
// Hardcoded tables
//
$lukDayNames = array(
  "fi" => array("Maanantai", "Tiistai", "Keskiviikko", "Torstai", "Perjantai", "Lauantai", "Sunnuntai"),
  "en" => array("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"),
);

$pageTranslations = array(
  "Original" => array("fi" => "Alkuperäinen"),

  "Shown" => array("fi" => "Näkyvillä"),
  "Next period" => array("fi" => "Seuraava periodi"),
  "Current period" => array("fi" => "Nykyinen periodi"),
  "Switch" => array("fi" => "Vaihda"),

  "contact" => array(
    "en" => "Contact <b>%1</b> or <b>%2</b>. ".
    "The author can't be held responsible for any errors or inaccuracies in the data!",

    "fi" => "Yhteydenotot <b>%1</b> tai <b>%2</b>. ".
    "En vastaa mahdollisista virheistä tai epätarkkuuksista tiedoissa!",
  ),
  
  "updated" => array(
    "en" => "Last updated: <b>%1</b>.",
    "fi" => "Päivitetty: <b>%1</b>.",
  ),
  
  "beta" => array(
    "en" => " NOTICE! %2 v%1 is still under development. There may be bugs.",
    "fi" => " HUOM! %2 v%1 on vielä kehityksen alla. Bugeja voi löytyä.",
  ),

  "viikossa" => array(
    "en" => "Total of <b>%1</b> hours in the week.",
    "fi" => "Viikossa yhteensä <b>%1</b> tuntia.",
  ),

  "vuoroviikoin" => array(
    "en" => "Every other week",
    "fi" => "Vuoroviikoin",
  ),

  "classNotSet" => array(
    "en" => "Class not set, using default <b>%1</b>.",
    "fi" => "Luokkaa ei asetettu, käytetään vakioarvoa <b>%1</b>.",
  ),

  "classDataNotFound" => array(
    "fi" => "Luokan %1 tietoja ei löytynyt! Jos luokkakoodi on uusi, ".
    "ilmestyy se järjestelmään seuraavan päivityksen aikana. Luokkatiedot ".
    "päivitetään noin kerran viikossa. <b>On myös mahdollista, että luokkakoodi ".
    "on olemassa vain seuraavan periodin tiedoissa.</b>",

    "en" => "Data for class %1 was not found. If the class code is new, it ".
    "should appear in this system during the next update. The data is updated ".
    "approximately once per week. <b>It is also possible that the code only ".
    "exists for next period's data.</b>",
  ),
  
  "classListNotFound" => array(
    "fi" => "Luokkien listaa ei löytynyt. Kokeile ladata sivu uudelleen hetken kuluttua.",
    "en" => "Class list not found. An update may be in progress, try reloading in a minute.",
  ),
  
  "classFormatError" => array(
    "fi" => "Virhe! Luokan täytyy olla muotoa <b>XXXnXXX</b>, käytetään vakioarvoa <b>%1</b>.",
    "en" => "Error! Class code must be of format <b>XXXnXXX</b>, using default value of <b>%1</b>.",
  ),

  "Style" => array(
    "fi" => "Tyyli",
  ),

  "linkToggle" => array(
    "fi" => "Vaihda nykyisen ja seuraavan periodin (jos saatavilla) lukujärjestyksen välillä.",
    "en" => "Switch view between current and next period (if available) timetable.",
  ),

  "linkOrig" => array(
    "fi" => "Linkki alkuperäiseen lukujärjestysdataan.",
    "en" => "Link to the original time table data.",
  ),
  
  "linkMobile" => array(
    "fi" => "Yksinkertaisempi mobiililaiteversio lukujärjestyksestä.",
    "en" => "Simpler mobile device version of the timetable.",
  ),
  
  "linkCurrLuk" => array(
    "fi" => "Linkki tähän lukujärjestykseen nykyisillä asetuksilla (kieli, jne.)",
    "en" => "Link to the this timetable with current settings (language, etc.)",
  ),
//  "" => array("fi" => ),
);


function lukGetDayName($day)
{
  global $lukDayNames, $pageLang;

  if (isset($lukDayNames[$pageLang]) && isset($lukDayNames[$pageLang][$day]))
    return $lukDayNames[$pageLang][$day];
  else
    return $lukDayNames["en"][$day];
}


function lukCheckClassID(&$id)
{
  global $classDefaultID;
  if (preg_match("#^([A-Z]{3}\d[A-Za-z0-9_]{1,6}|ccr|Ryh_[A-Z]{3}\d[A-Za-z0-9_]{1,6})$#", $id, $m))
  {
    $id = $m[1];
    return TRUE;
  } else {
    stError(cmQM("classFormatError", $classDefaultID));
    $id = $classDefaultID;
    return FALSE;
  }
}


function lukGetWeekdayFromTimestamp($stamp)
{
  $info = getdate($stamp);
  $day = $info["wday"];
  return ($day > 0) ? $day - 1 : $day + 6;
}


function lukGetHourStamp($stamp)
{
  return date("H:i", mktime(0, 0, $stamp, 0, 0, 0));
}


function lukGetHourStr($hour)
{
  global $classHourTimes;
  if (isset($classHourTimes[$hour]))
  {
    return
      "<br />".
      lukGetHourStamp($classHourTimes[$hour]["start"]).
      " - ".
      lukGetHourStamp($classHourTimes[$hour]["end"]).
      "<br /><br />";
  }
  else
    return "ERROR";
}


function lukClassGetHourStr($start, $end)
{
  global $classHourTimes;
  return
    lukGetHourStamp($classHourTimes[$start]["start"]).
    " - ".
    lukGetHourStamp($classHourTimes[$end]["end"]);
}


function lukFetchCourseData($id, $uri, &$cache)
{
  global $pageCharset;

  if (($data = @file_get_contents($uri)) !== FALSE)
  {
    $data = @iconv("iso8859-15", $pageCharset, $data);

    // <td id="oj_nimi" class="smallheadercell"><strong>Korjausrakentamisen rakennussuunnittelu 3 op</strong></td>
    if (preg_match("#<td id=\"oj_nimi\" class=\"smallheadercell\"><strong>(.+?)\s+(\d+)\s*(op|ECTS\s+cr)\s*</strong></td>#", $data, $m))
    {
      $cache = array("desc" => trim($m[1]), "op" => intval($m[2]), "uri" => $uri);
      return TRUE;
    }
    else
    // <td><strong>... (N op)</strong></td>
    if (preg_match("#<td><strong>(.+?)\s+\((\d+)\s*(op|ECTS\s+cr|cr)\)\s*</strong></td>#i", $data, $m))
    {
      $cache = array("desc" => trim($m[1]), "op" => intval($m[2]), "uri" => $uri);
      return TRUE;
    }
  }
  return FALSE;
}


function lukMatchCourse($id)
{
  global $cache, $cacheDirty, $pageLang, $mobileMode;

  // Create the index
  if (!isset($cache[$id]))
    $cache[$id] = array();

  // Check if course exists in cache
  if (!isset($cache[$id][$pageLang]))
  {
    // Not cached, try to fetch data
    $uri = "http://www.oamk.fi/opinto-opas/opintojaksohaku/?sivu=oj_kuvaus&koodi1=".$id."&kieli=".strtoupper($pageLang);
    if (
        lukFetchCourseData($id, $uri."&opas=2014-2015", $cache[$id][$pageLang]) ||
        lukFetchCourseData($id, $uri."&opas=2015-2016", $cache[$id][$pageLang])
      )
      $cacheDirty = TRUE;
  }

  if (isset($cache[$id]) && isset($cache[$id][$pageLang]))
  {
    if ($mobileMode)
    {
      return "<b>".chentities($cache[$id][$pageLang]["desc"])."</b>";
    }
    else
    {
       return
         "<a target=\"_blank\" title=\"".chentities($id." - ".$cache[$id][$pageLang]["op"]." op").
         "\" href=\"".chentities($cache[$id][$pageLang]["uri"])."\">".chentities($cache[$id][$pageLang]["desc"])."</a>";
    }
  }
  else
    return chentities($id);
}


function lukGetClassInfo($class)
{
  $data = $class["data"];

  if ($class["grouped"])
  {
    // Grouped format
    $out = "<table><tr>";

    // Calculate max columns and add course titles
    $maxColumns = 0;
    foreach ($data as $col)
    {
      $out .= "<td>".lukMatchCourse($col[0])."</td>";
      if (count($col) > $maxColumns)
        $maxColumns = count($col);
    }
    $out .= "</tr>";

    // Print rest of the data
    for ($i = 1; $i < $maxColumns; $i++)
    {
      $out .= "<tr>";
      foreach ($data as $col)
      {
        $out .= "<td>";
        if (isset($col[$i]))
          $out .= chentities($col[$i]);
        $out .= "</td>";
      }
      $out .= "</tr>";
    }

    $out .= "</table>";
  }
  else
  {
    $out = lukMatchCourse($data[0][0])."<br />";

    for ($i = 1; $i < count($data[0]); $i++)
    {
      $out .= chentities($data[0][$i])."<br />";
    }
  }

  if ($class["turns"])
    $out .= cmQM("vuoroviikoin");

  return $out;
}


function lukFindClass($day, $hour)
{
  global $classHourDefs, $classDayTable;
  if (isset($classDayTable[$day]))
  {
    foreach ($classDayTable[$day] as $id)
    {
      if ($hour >= $classHourDefs[$id]["start"] && 
          $hour <  $classHourDefs[$id]["start"] + $classHourDefs[$id]["hours"])
        return $id;
    }
  }
  return 0;
}


function lukReadClassFile($filename, &$mtimestamp)
{
  // Attempt to open file for reading
  if (($fp = @fopen($filename, "rb")) === false)
    return false;

  $mtimestamp = filemtime($filename);
  $mclasses = FALSE;

  // Lock file so that we do not get clashes
  if (flock($fp, LOCK_SH))
  {
    $mclasses = array();
    // Read and parse data
    while (!feof($fp))
    {
      $str = trim(fgets($fp, 128));
      if (strlen($str) > 2 && $str[0] != "#")
        $mclasses[] = $str;
    }

    // Release lock
    flock($fp, LOCK_UN);
  }

  fclose($fp);
  
  sort($mclasses);
  return $mclasses;
}


function lukPrintTimeTable($mini)
{
  global $classInfo, $classHourDefs, $classHourTimes;
  
//  $currStamp = time() + ((3 * 60) + 45) * 60;
  $currStamp = time();
  $currTime = $currStamp - mktime(0, 0, 0);
  $currDay = lukGetWeekdayFromTimestamp($currStamp);

  $nextStamp = $currStamp + 30 * 60;
  $nextTime = $nextStamp - mktime(0, 0, 0);
  
  // Create the timetable table
  if ($mini)
  {
    $out =
    "<table id=\"timeTable\">\n".
    " <tr>\n".
    "  <th></th>".
    "  <th class=\"days\">".lukGetDayName($currDay)."</th>\n".
    " </tr>\n";

    $startDay = $currDay;
    $lastDay = $currDay + 1;
  }
  else
  {
    $out =
    "<p>".join("; ", $classInfo["general"]).
    //" [".cmQM("viikossa", $classInfo["totalHours"])."]".
    "</p>\n".
    "<table id=\"timeTable\">\n".
    " <tr>\n".
    "  <th></th>\n";
    
    $cellWidth = $classInfo["maxDays"] > 0 ? 100 / $classInfo["maxDays"] : 15;
    if ($cellWidth > 25) $cellWidth = 25;

    for ($day = 0; $day < $classInfo["maxDays"]; $day++)
    {
      $out .=  "  <th style=\"width: ".$cellWidth."%;\" class=\"days\">".lukGetDayName($day)."</th>\n";
    }
    $out .= " </tr>\n";
    $startDay = 0;
    $lastDay = $classInfo["maxDays"];
  }


  for ($hour = $classInfo["firstHour"]; $hour < $classInfo["lastHour"]; $hour++)
  {
    $out .= " <tr>\n".
    "  <th class=\"hours\">".lukGetHourStr($hour)."</th>\n";
    for ($day = $startDay; $day < $lastDay; $day++)
    {
      $id = lukFindClass($day, $hour);
      if ($id > 0)
      {
        $class = &$classHourDefs[$id];
        if (!isset($class["set"]))
        {
          $class["set"] = TRUE;

          $nextActive = $day == $currDay && 
                      $nextTime >= $classHourTimes[$class["start"]]["start"] &&
                      $nextTime <  $classHourTimes[$class["start"] + $class["hours"] - 1]["end"];

          $isActive = $day == $currDay && 
                      $currTime >= $classHourTimes[$class["start"]]["start"] &&
                      $currTime <  $classHourTimes[$class["start"] + $class["hours"] - 1]["end"];

          $out .= "  <td rowspan=\"".$class["hours"]."\" ".
          "title=\"".lukClassGetHourStr($hour, $hour + $class["hours"] - 1)."\" class=\"".
          ($isActive ? "clactive " : "").
          (!$isActive && $nextActive ? "clnext " : "").
          (($class["grouped"] || $class["turns"]) ? "clgrouped" : "clnormal")."\">".
          lukGetClassInfo($class).
          "<div class=\"nhours\"><span>".$class["hours"]."h</span></div></td>\n";
        }
      }
      else
      {
        $out .= "  <td class=\"clnothing\"></td>\n";
      }
    }
    $out .= " </tr>\n";
  }

  return $out."</table>\n";
}



//
// Main code begins
//
// Check given parameters:
// Language must be the first setting to be validated,
// so that the translation support works properly.
//

if (($tmp = stGetRequestItem("lang", FALSE, TRUE)) !== FALSE)
{
  $tmp = strtolower($tmp);
  if (in_array($tmp, $pageLanguages))
  {
    $pageLang = $tmp;
    setcookie("luklang", $tmp, time() + 365*24*60*60); // expire in a year
  }
}
else
if (isset($_COOKIE["luklang"]))
{
  $tmp = $_COOKIE["luklang"];
  if (in_array($tmp, $pageLanguages))
    $pageLang = $tmp;
}


// Check class setting
if (($classID = stGetRequestItem("luokka", FALSE, TRUE)) !== FALSE && lukCheckClassID($classID))
{
  setcookie("lukluokka", $classID, time() + 365*24*60*60); // expire in a year
}
else
if (isset($_COOKIE["lukluokka"]))
{
  $classID = $_COOKIE["lukluokka"];
  lukCheckClassID($classID);
}
else
{
  stError(cmQM("classNotSet", $classDefaultID));
  $classID = $classDefaultID;
}


// Check next period flag
if (isset($_REQUEST["next"]))
{
  $nextPeriod = TRUE;
  $cachePath = "cache-next/";
}
else
{
  $nextPeriod = FALSE;
  $cachePath = "cache/";
}

// Get original base URI data
$origBaseURI = "";
if (file_exists($cachePath."baseuri.data"))
  require $cachePath."baseuri.data";


// Global cache for course data
$cache = array();
$cacheDirty = FALSE;

// Try to read cachefile, if we can get file lock on it
$fp = @fopen($courseCacheFile, "rb");
if ($fp)
{
  if (flock($fp, LOCK_SH))
  {
    require($courseCacheFile);
    flock($fp, LOCK_UN);
  }
  fclose($fp);
}


// Read classfile
if (($classIDs = lukReadClassFile($classIDFile[$nextPeriod], $timestamp)) === false)
  stError(cmQM("classListNotFound"));


// Read class data
$dataFile = $cachePath.$classID.".data";
if (!file_exists($dataFile))
{
  stError(cmQM("classDataNotFound", chentities($classID)));
  $haveData = FALSE;
}
else
{
  require($dataFile);
  $haveData = isset($classInfo);
}


// Create references to mobile device (Apple touch) icons
$extra = "";
foreach (array(57 => FALSE, 76 => TRUE, 114 => TRUE, 120 => TRUE, 152 => TRUE) as $iconSize => $addSize)
{
  $extra .= "  <link rel=\"apple-touch-icon\" ".
    ($addSize ? "sizes=\"".$iconSize."x".$iconSize."\" " : "").
    "href=\"img/icon-".$iconSize."-precomposed.png\" />\n";
}


// Start printing the page
$pageTitle = $haveData ? $classID." / ".join("; ", $classInfo["info"]) : $classID;
cmPrintPageHeader($pageTitle." - ".$pageName, $extra);


// Info box
echo
  "<div id=\"infobox\">\n".
  " <div id=\"ctitle\">".$pageName." v".$pageVersion."</div>\n";

if (!$mobileMode)
{
  echo
    " <div id=\"csssel\">".cmQM("Style").": ";

  if (isset($pageCSSAlts))
  {
    foreach ($pageCSSAlts as $name => $id)
    {
      echo
        "<a ".(($pageCSSIndex == $id) ? "class=\"selected\" " : "").
        "href=\"".$baseURI."?css=".$id."\">".$name."</a>";
    }
  }

  echo
    "</div>\n";
}

echo
  " <div id=\"clang\">";

foreach ($pageLanguages as $id)
{
  echo
    "<a ".(($pageLang == $id) ? "class=\"selected\" " : "").
    "href=\"".$baseURI."?lang=".$id."\">".$id."</a>";
}

echo
  "</div>\n".
  "</div>\n";


// Additional controls
echo
  "<form id=\"controls\" action=\"".$baseURI."\" method=\"get\">\n".
  " <table>\n".
  "  <tr>\n".
  "   <th>\n".
  "    <select name=\"luokka\">\n";

if ($classIDs !== FALSE)
{
  foreach ($classIDs as $id)
  {
    echo
      "     <option ".($classID == $id ? "selected=\"selected\" " : "").
      "value=\"".$id."\">".chentities($id)."</option>\n";
  }
}

echo 
  "    </select>\n".
  "   </th>\n".
  "   <th><input class=\"submit\" type=\"submit\" value=\"".cmQM("Switch")."\" /></th>\n";

if (!$mobileMode)
{
  echo
    "   <th><a id=\"next\" class=\"textctrl\" href=\"".
    $baseURI.($nextPeriod ? "" : "?next")."\" title=\"".cmQM("linkToggle")."\">".
    cmQM("Shown").": ".($nextPeriod ? cmQM("Next period") : cmQM("Current period")).
    "</a></th>\n";
  
  if ($haveData)
  {
    echo
    "   <th><a id=\"orig\" class=\"textctrl\" href=\"".$origBaseURI.$classID.$origBaseExt."\" title=\"".cmQM("linkOrig")."\">".cmQM("Original")."</a></th>\n";
    //"   <th><a id=\"xml\" class=\"textctrl\" href=\"".$baseURI.$cachePath."/".$luokka.".xml\">XML</a></th>\n";
  }
  
  echo
    "   <th><a class=\"textctrl mobile\" href=\"http://tnsp.org/mluk/\" title=\"".cmQM("linkMobile")."\">Mobile</a></th>\n";
}

$currURL = $baseURI."?luokka=".$classID."&amp;lang=".$pageLang.
  (isset($pageCSSIndex) ? "&amp;css=".$pageCSSIndex : "").
  ($nextPeriod ? "&amp;next" : "");

echo
  "   <th><a href=\"".$currURL."\" class=\"textctrl\" title=\"".cmQM("linkCurrLuk")."\">Link</a></th>\n".
//  "   <th class=\"advert\"><a href=\"http://tnsp.org/u/G1X\"><img src=\"img/hdd_fi.gif\" alt=\"HDD.fi\" /></a></th>\n".
  "   <th class=\"advert\"><a href=\"http://tnsp.org/u/H9w\"><img src=\"img/batmud.png\" alt=\"BatMUD\" /></a></th>\n".
//  "   <th><a href=\"http://tnsp.org/testluk/\"><img src=\"img/test.png\" alt=\"Lukkari v3\" /></a></th>\n".
  "  </tr>\n".
  " </table>\n".
  "</form>\n".
  "<h1>".$pageTitle."</h1>\n";

// Show error messages
if ($errorSet)
{
  echo "<ul>\n";
  foreach ($errorMsgs as $msg)
    echo "<li>".$msg."</li>\n";
  echo "</ul>\n";
}

if ($haveData)
{
  echo lukPrintTimeTable($mobileMode);
}

echo
  "<div id=\"footer\">".
  cmQM("contact", "ccr @ IRCNet", "ccr (at) tnsp (dot) org").
  " / ".
  cmQM("updated", strftime("%d.%m.%Y, %H:%M", $timestamp)).
  //" <div style=\"color: red;\">".cmQM("beta", $pageVersion, $pageName)."</div>\n".
  "</div>\n";

cmPrintPageFooter();


// Dump the course data cache, but only if it has changed
if ($cacheDirty)
{
  $str = "<?\n\$cache = ".var_export($cache, TRUE)."\n?>";
  if (file_put_contents($courseCacheFile, $str, LOCK_EX) === FALSE)
  {
    // Can't do much anything here ..
  }
}

?>