Mercurial > hg > lukkari
view index.php @ 193:16ce445c499a v3 tip
Import v3 branch.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Sun, 22 Jan 2017 02:31:10 +0200 |
parents | f2adb44ea251 |
children |
line wrap: on
line source
<?php // // OAMK Lukkari v3.0 // (C) Copyright 2010 - 2015 Matti 'ccr' Hämäläinen <ccr@tnsp.org> // Yes, this code is rather horrible. :| // // Include framework require "mgeneric.inc.php"; // Default settings $pageName = "OAMK Lukkari"; $pageVersion = "3.0alpha"; $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( "change" => array( "fi" => "Vaihda", "en" => "Change", ), "period_shown" => array( "fi" => "Näkyvillä", "en" => "Shown", ), "next_period" => array( "fi" => "Seuraava periodi", "en" => "Next period", ), "current_period" => array( "fi" => "Nykyinen periodi", "en" => "Current period", ), "expl_toggle_period" => array( "fi" => "Vaihda nykyisen ja seuraavan periodin (jos saatavilla) lukujärjestyksen välillä.", "en" => "Switch view between current and next period (if available) timetable.", ), "viikossa" => array( "fi" => "Viikossa yhteensä <b>%1</b> tuntia.", "en" => "Total of <b>%1</b> hours in the week.", ), "vuoroviikoin" => array( "fi" => "Vuoroviikoin", "en" => "Every other week", ), "class_not_set" => array( "fi" => "Luokkaa ei asetettu, käytetään vakioarvoa <b>%1</b>.", "en" => "Class not set, using default <b>%1</b>.", ), "class_data_not_found" => 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>", ), "class_list_not_found" => 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.", ), "class_format_error" => 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>.", ), "change_style" => array( "fi" => "Tyyli", "en" => "Style", ), "link_original_data" => array( "fi" => "Alkuperäinen", "en" => "Original", ), "expl_link_original_data" => array( "fi" => "Linkki alkuperäiseen lukujärjestysdataan.", "en" => "Link to the original time table data.", ), "link_mobile_version" => array( "en" => "Mobile", "fi" => "Mobile", ), "expl_link_mobile_version" => array( "fi" => "Yksinkertaisempi mobiililaiteversio lukujärjestyksestä.", "en" => "Simpler mobile device version of the timetable.", ), "link_current_settings" => array( "fi" => "Linkki", "en" => "Link", ), "expl_link_current_settings" => array( "fi" => "Linkki tähän lukujärjestykseen nykyisillä asetuksilla (kieli, jne.)", "en" => "Link to the this timetable with current settings (language, etc.)", ), "contact" => array( "fi" => "Yhteydenotot <b>%1</b> tai <b>%2</b>. ". "En vastaa mahdollisista virheistä tai epätarkkuuksista tiedoissa!", "en" => "Contact <b>%1</b> or <b>%2</b>. ". "The author can't be held responsible for any errors or inaccuracies in the data!", ), "updated" => array( "fi" => "Päivitetty: <b>%1</b>.", "en" => "Last updated: <b>%1</b>.", ), "beta" => array( "fi" => " HUOM! %2 v%1 on vielä kehityksen alla. Bugeja voi löytyä.", "en" => " NOTICE! %2 v%1 is still under development. There may be bugs.", ), // "" => 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("class_format_error", $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 lukGetHourStamp($classHourTimes[$hour]["start"]). " - ". lukGetHourStamp($classHourTimes[$hour]["end"]); } 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, $indent = "") { $data = $class["data"]; if ($class["grouped"]) { $out = ""; foreach ($data as $col) { $out .= $indent."<div class=\"group\">\n". $indent." <div class=\"groupCell\">".lukMatchCourse($col[0])."</div>\n"; for ($i = 1; $i < count($col); $i++) { $out .= $indent." <div class=\"groupCell\">". (isset($col[$i]) ? chentities($col[$i]) : ""). "</div>\n"; } $out .= $indent."</div>\n"; } if ($class["turns"]) $out .= "<div class=\"groupCell eoWeekly\">".cmQM("vuoroviikoin")."</div>\n"; return $out; } else { $out = $indent."<div class=\"groupCell\">".lukMatchCourse($data[0][0])."</div>\n"; for ($i = 1; $i < count($data[0]); $i++) { $out .= $indent."<div class=\"groupCell\">".chentities($data[0][$i])."</div>\n"; } if ($class["turns"]) $out .= "<div class=\"groupCell eoWeekly\">".cmQM("vuoroviikoin")."</div>\n"; 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) { // Attempt to open file for reading if (($fp = @fopen($filename, "rb")) === false) return false; $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); $out = "<div id=\"timeTable\">\n"; // Create the timetable table if ($mini) { $startDay = $currDay; $lastDay = $currDay + 1; } else { $startDay = 0; $lastDay = $classInfo["maxDays"]; } $out .= " <div class=\"timeTableHourList\" style=\"width: ".($mini ? "20%" : "10%").";\">\n". " <div class=\"timeTableWeekday\"> </div>\n"; for ($hour = $classInfo["firstHour"]; $hour < $classInfo["lastHour"]; $hour++) { $out .= " <div class=\"timeTableHourBox\"><div class=\"timeTableHour\">".lukGetHourStr($hour)."</div></div>\n"; } $out .= " </div>\n"; if ($mini) $tmpS = "style=\"width: 75%;\""; else $tmpS = sprintf("style=\"width: %1.3f%%;\"", 100 / ($lastDay - $startDay + 1)); for ($day = $startDay; $day < $lastDay; $day++) { $out .= " <div class=\"timeTableDay".($day == $currDay ? " active" : "")."\" ".$tmpS.">\n". " <div class=\"timeTableWeekday\">".lukGetDayName($day)."</div>\n"; for ($hour = $classInfo["firstHour"]; $hour < $classInfo["lastHour"]; $hour++) { $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 .= " <div class=\"classInfoBox\" style=\"height: ".($class["hours"] * 6)."em;\">\n". " <div class=\"classInfo". ($isActive ? " clactive" : ""). (!$isActive && $nextActive ? " clnext " : ""). (($class["grouped"] || $class["turns"]) ? " clgrouped" : " clnormal")."\">\n". lukGetClassInfo($class, " "). " <div class=\"nhours\"><span>".lukClassGetHourStr($hour, $hour + $class["hours"] - 1)." (".$class["hours"]."h)</span></div>\n". " </div>\n". " </div>\n"; } } else { $out .= " <div class=\"classInfoBox\"><div class=\"classInfo clnothing\"></div></div>\n"; } } $out .= " </div>\n"; } return $out."</div>\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; } // Cookie info window $showCookieInfo = isset($_COOKIE["lukcookieinfo"]) ? !$_COOKIE["lukcookieinfo"] : TRUE; // Development info window $showDevInfo = isset($_COOKIE["lukdevinfo"]) ? $_COOKIE["lukdevinfo"] : TRUE; setcookie("lukdevinfo", 0, time() + 2*7*24*60*60); // Check class setting (check "luokka" for backwards compatibility also) if ((($classID = stGetRequestItem("class", FALSE, TRUE)) !== FALSE || ($classID = stGetRequestItem("luokka", FALSE, TRUE)) !== FALSE) && lukCheckClassID($classID)) { setcookie("lukclass", $classID, time() + 365*24*60*60); // expire in a year } else if (isset($_COOKIE["lukclass"])) { $classID = $_COOKIE["lukclass"]; lukCheckClassID($classID); } else { stError(cmQM("class_not_set", $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 if (($fp = @fopen($courseCacheFile, "rb")) !== FALSE) { if (flock($fp, LOCK_SH)) { require($courseCacheFile); flock($fp, LOCK_UN); } fclose($fp); } // Read classfile if (($classIDs = lukReadClassFile($classIDFile[$nextPeriod])) === false) stError(cmQM("class_list_not_found")); // Read class data $dataFile = $cachePath.$classID.".data"; if (!file_exists($dataFile)) { stError(cmQM("class_data_not_found", chentities($classID))); $haveData = FALSE; $timestamp = time(); } else { require($dataFile); $haveData = isset($classInfo); $timestamp = filemtime($dataFile); } // 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"; } // XXX: Temporarily no-index (remember also robots.txt!) $extra .= " <meta name=\"robots\" content=\"noindex\">\n"; // For mobile shit $extra .= " <meta name=\"viewport\" content=\"width=device-width\" />\n"; // Start printing the page $pageTitle = $haveData ? $classID." / ".join("; ", $classInfo["info"]) : $classID; cmPrintPageHeader($pageTitle." - ".$pageName, $extra); echo " <script type=\"text/javascript\"> function lukSetCookie(cname, cvalue, cctime) { var de = new Date(); de.setTime(de.getTime() + cctime*1000); document.cookie = cname +\"=\"+ cvalue +\"; expires=\"+ de.toUTCString(); } function lukSetViewDo(elem, state) { elem.style.display = state ? 'block' : 'none'; } function lukSetView(id, state) { var elem = document.getElementById(id); if (elem) lukSetViewDo(elem, state); } function lukToggleView(id) { var elem = document.getElementById(id); if (elem) lukSetViewDo(elem, (elem.style.display == 'none')); } function lukAcknowledgeCookies() { lukSetCookie('lukcookieinfo', 1, 31*24*60*60); var elem = document.getElementById('cookieInfo'); if (elem) lukSetViewDo(elem, false); } </script> <div id=\"devInfo\" ".($showDevInfo ? "" : " style=\"display: none;\"")."> <h1>Notice!</h1> <p> <b>This is the development version of 'Lukkari', tentatively called \"3.0alpha\".</b> It is constantly changing, any features may be broken and fixed at rapid pace. The style/layout is also in flux, and not finished - there may be rendering bugs. Currently I am testing a non-table-based layout, although it is not certain if it will be used in 'production'. </p> <p> If you wish to suggest features, send e-mail to <b>ccr (at) tnsp (dot) org</b> </p> <div class=\"popupControls\"> <button type=\"button\" onClick=\"lukSetView('devInfo', false);\">Okay</button> </div> </div> "; if ($showCookieInfo) { echo "<div id=\"cookieInfo\">\n". " <p>Lukkari web-site uses <a href=\"http://en.wikipedia.org/wiki/HTTP_cookie\">HTTP cookies</a> ". " to store current settings (language, default class ID, etc.) ". " Also, Google Analytics used on this site may store information via cookies.". " </p>". " By continued use of this site, you acknowledge that you have been informed of the situation.". " <div class=\"popupControls\">\n". " <button type=\"button\" onclick=\"lukAcknowledgeCookies()\">Okay</button>\n". " </div>\n". "</div>\n"; } // Additional controls echo " <div id=\"controls\">\n". " <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". " <form action=\"".$baseURI."\" method=\"get\">\n". " <div>\n". " <select id=\"classSelect\" name=\"class\" onChange=\"this.form.submit();\">\n"; if ($classIDs !== FALSE) { foreach ($classIDs as $id) { echo " <option ".($classID == $id ? "selected=\"selected\" " : ""). "value=\"".$id."\">".chentities($id)."</option>\n"; } } echo " </select>\n". " </div>\n". " <noscript><div><input id=\"classSwitch\" class=\"submit\" type=\"submit\" value=\"".cmQM("change")."\" /></div></noscript>\n"; if (!$mobileMode) { echo " <div><a id=\"nextPeriod\" class=\"textctrl\" href=\"". $baseURI.($nextPeriod ? "" : "?next")."\" title=\"".cmQM("expl_toggle_period")."\">". cmQM("period_shown").": ".($nextPeriod ? cmQM("next_period") : cmQM("current_period")). "</a></div>\n"; if ($haveData) { echo " <div><a id=\"origLink\" class=\"textctrl\" href=\"".$origBaseURI.$classID.$origBaseExt."\" title=\"".cmQM("expl_link_original_data")."\">".cmQM("link_original_data")."</a></div>\n"; //" <div><a id=\"xml\" class=\"textctrl\" href=\"".$baseURI.$cachePath."/".$class.".xml\">XML</a></div>\n"; } // echo " <div><a id=\"mobileLink\" class=\"textctrl mobile\" href=\"http://tnsp.org/mluk/\" title=\"".cmQM("expl_link_mobile_version")."\">".cmQM("link_mobile_version")."</a></div>\n"; } $currURL = $baseURI."?class=".$classID."&lang=".$pageLang. (isset($pageCSSIndex) ? "&css=".$pageCSSIndex : ""). ($nextPeriod ? "&next" : ""); echo " <div><a href=\"".$currURL."\" class=\"textctrl\" title=\"".cmQM("expl_link_current_settings")."\">".cmQM("link_current_settings")."</a></div>\n". " <div><button id=\"toggleDevInfo\" type=\"button\" onclick=\"lukToggleView('devInfo')\">INFO</button></div>\n". " </form>\n". "</div>\n". "<div id=\"header\">\n". "<h1>".$pageTitle."</h1>\n"; if (!$mobileMode) { echo "<p>".join("; ", $classInfo["general"]). //" [".cmQM("viikossa", $classInfo["totalHours"])."]". "</p>\n"; } // Show error messages if ($errorSet) { echo "<ul>\n"; foreach ($errorMsgs as $msg) echo "<li>".$msg."</li>\n"; echo "</ul>\n"; } echo "</div>\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 .. } } ?>