diff --git a/page-schedule.php b/page-schedule.php
new file mode 100644
index 0000000..c8a4342
--- /dev/null
+++ b/page-schedule.php
@@ -0,0 +1,54 @@
+
+
+
+
+
+
diff --git a/schedule/class.SmartCurl.php b/schedule/class.SmartCurl.php
new file mode 100644
index 0000000..b963a31
--- /dev/null
+++ b/schedule/class.SmartCurl.php
@@ -0,0 +1,137 @@
+cache_dir = __DIR__ . DIRECTORY_SEPARATOR . $cache_dir;
+ }
+
+ if (!file_exists($this->cache_dir)) {
+ mkdir($this->cache_dir);
+ }
+
+ $this->cache_index = $this->cache_dir . '.json';
+
+ $cache = file_exists($this->cache_index) ? file_get_contents($this->cache_index) : false;
+
+ if ($cache !== false) {
+ $cache = json_decode($cache, true);
+ }
+
+ if ($cache !== false) {
+ static::importEtags($cache);
+ }
+
+ if (!is_null($url_root)) {
+ $this->url_root = $url_root;
+ }
+
+ $ch = curl_init();
+
+ if ($ch === false) {
+ throw new Exception('curl init failed');
+ }
+
+ $this->ch = $ch;
+
+ if (curl_setopt_array($ch, [
+ CURLOPT_FAILONERROR => true,
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_HEADER => true,
+ //CURLINFO_HEADER_OUT => true,
+ ]) === false) {
+ throw new Exception('curl setopt failed');
+ }
+ }
+
+ public function __destruct() {
+ curl_close($this->ch);
+ file_put_contents($this->cache_index, json_encode(static::exportEtags()));
+ }
+
+ public static function importEtags(array $etags) {
+ static::$etags = array_merge(static::$etags, $etags);
+ }
+
+ public static function exportEtags() {
+ return static::$etags;
+ }
+
+ public function getUrl($filename) {
+ if (is_null($this->url_root)) {
+ $url = $filename;
+ }
+ else {
+ $url = $this->url_root . $filename;
+ }
+
+ if (curl_setopt($this->ch, CURLOPT_URL, $url) === false) {
+ throw new Exception('set url failed');
+ }
+
+ $cache_file = $this->cache_dir . DIRECTORY_SEPARATOR . $filename;
+
+ $etag = array_key_exists($url, static::$etags) && file_exists($cache_file) ? static::$etags[$url] : null;
+
+ if (curl_setopt($this->ch, CURLOPT_HTTPHEADER, [
+ 'If-None-Match:' . (is_null($etag) ? '' : ' ' . $etag),
+ ]) === false) {
+ throw new Exception('set etag failed');
+ }
+
+ $response = curl_exec($this->ch);
+
+ if ($response === false) {
+ return false;
+ }
+
+ //var_dump(curl_getinfo($this->ch, CURLINFO_HEADER_OUT));
+ //var_dump($response);
+
+ $header_size = curl_getinfo($this->ch, CURLINFO_HEADER_SIZE);
+ $header = substr($response, 0, $header_size);
+ $headers = array_filter(explode("\r\n", $header));
+
+ foreach ($headers as $header_line) {
+ if (stripos($header_line, 'etag:') === 0) {
+ static::$etags[$url] = trim(substr($header_line, strlen('etag:')));
+ }
+ }
+
+ $http_code = curl_getinfo($this->ch, CURLINFO_HTTP_CODE);
+
+ if ($http_code === 304 && !is_null($this->url_root)) {
+ // use cache
+
+ if (file_exists($cache_file)) {
+ return file_get_contents($cache_file);
+ }
+ else {
+ return false;
+ }
+ }
+
+ if (strlen($response) === $header_size) {
+ return false;
+ }
+
+ $body = substr($response, $header_size);
+
+ if ($http_code === 200) {
+ $dirname = dirname($filename);
+
+ if ($dirname !== '.') {
+ mkdir($this->cache_dir . DIRECTORY_SEPARATOR . $dirname, 0777, true);
+ }
+
+ file_put_contents($cache_file, $body);
+ }
+
+ return $body;
+ }
+}
diff --git a/schedule/index.php b/schedule/index.php
new file mode 100644
index 0000000..32fc571
--- /dev/null
+++ b/schedule/index.php
@@ -0,0 +1,49 @@
+
+
+Test schedule
+
+
+
+
+
+
+
+
+ 'events.json',
+ 'speakers' => 'speakers.json',
+ 'tracks' => 'tracks.json',
+ 'event_types' => 'event_types.json',
+ 'halls' => 'halls.json',
+ 'slots' => 'slots.json',
+];
+
+$data = [];
+
+foreach ($filenames as $name => $filename) {
+ $curl = new SmartCurl($base_url);
+ $json = $curl->getUrl($filename);
+
+ if ($json === false) {
+ echo 'get failed: ', $filename, PHP_EOL;
+ exit;
+ }
+
+ $decoded = json_decode($json, true);
+
+ if ($decoded === false) {
+ echo 'decode failed: ', $filename, PHP_EOL;
+ exit;
+ }
+
+ $add = true;
+
+ switch ($name) {
+ case 'halls':
+ $decoded = array_map(function($el) {
+ return $el['name'];
+ }, $decoded);
+ break;
+ case 'slots':
+ $decoded = array_map(function($el) {
+ foreach (['starts_at', 'ends_at'] as $key) {
+ $el[$key] = strtotime($el[$key]);
+ }
+
+ return $el;
+ }, $decoded);
+ break;
+ }
+
+ $data[$name] = $decoded;
+}
+
+function compareKeys($a, $b, $key) {
+ $valA = &$a[$key];
+ $valB = &$b[$key];
+
+ return ($valA < $valB) ? -1 : (($valA > $valB) ? 1 : 0);
+}
+
+uasort($data['slots'], function($a, $b) {
+ return compareKeys($a, $b, 'starts_at') ?: compareKeys($a, $b, 'hall_id');
+});
+
+array_pop($data['halls']);
+
+return $data;
diff --git a/schedule/parse.php b/schedule/parse.php
new file mode 100644
index 0000000..4ef5deb
--- /dev/null
+++ b/schedule/parse.php
@@ -0,0 +1,147 @@
+ $slot) {
+ $slotTime = $slot['starts_at'];
+ $slotDate = date('d', $slotTime);
+
+ if ($slotDate !== $date) {
+ $lines[] = '';
+ $lines[] = '' . date('d F - l', $slotTime) . ' | ';
+ $lines[] = ' | ';
+ $lines[] = '
';
+
+ $date = $slotDate;
+ }
+
+ if ($slotTime !== $time) {
+ if ($time !== 0) {
+ $lines[] = '';
+ }
+
+ $lines[] = '';
+ $lines[] = '' . date('H:i', $slot['starts_at']) . ' - ' . date('H:i', $slot['ends_at']) . ' | ';
+
+ $time = $slotTime;
+ }
+
+ $eid = &$slot['event_id'];
+ $event = &$data['events'][$eid];
+
+ if (is_null($eid)) {
+ $lines[] = 'TBA | ';
+ }
+ else {
+ $title = mb_substr($event['title'], 0, $cut_len) . (strlen($event['title']) > $cut_len ? '...' : '');
+ $speakers = '';
+
+ if (count($event['participant_user_ids']) > 0) {
+ $speakers = json_encode($event['participant_user_ids']) . '
';
+
+ $spk = array();
+ $speaker_name = array();
+ foreach ($event['participant_user_ids'] as $uid) {
+ /* The check for uid==4 is for us not to show the "Opefest Team" as a presenter for lunches, etc. */
+ if ($uid == 4 || empty ($data['speakers'][$uid])) {
+ continue;
+ } else {
+ /* TODO: fix the URL */
+ $name = $data['speakers'][$uid]['first_name'] . ' ' . $data['speakers'][$uid]['last_name'];
+ $spk[$uid] = '' . $name . '';
+ }
+ }
+ $speakers = implode (', ', $spk);
+ }
+
+
+ /* Hack, we don't want language for the misc track. This is the same for all years. */
+ if ('misc' !== $data['tracks'][$event['track_id']]['name']['en']) {
+ $csslang = "schedule-".$event['language'];
+ } else {
+ $csslang = "";
+ }
+ $cssclass = &$data['tracks'][$event['track_id']]['css_class'];
+ $style = ' class="' . $cssclass . ' ' . $csslang . '"';
+ $content = '' . htmlspecialchars($title) . '
' . $speakers;
+
+
+ /* these are done by $eid, as otherwise we get some talks more than once (for example the lunch) */
+ $fulltalks[$eid] = '';
+ $fulltalks[$eid] .= '';
+ /* We don't want '()' when we don't have a speaker name */
+ $fulltalk_spkr = strlen($speakers)>1 ? ' (' . $speakers . ')' : '';
+ $fulltalks[$eid] .= '' . $event['title'] . ' ' . $fulltalk_spkr . '';
+ $fulltalks[$eid] .= '
' . $event['abstract'] . '
';
+ $fulltalks[$eid] .= '';
+
+ if ($slot['event_id'] === $prev_event_id) {
+ array_pop($lines);
+ $lines[] = '' . $content . ' | ';
+ }
+ else {
+ $lines[] = '' . $content . ' | ';
+ $colspan = 1;
+ }
+ }
+
+ $prev_event_id = $slot['event_id'];
+}
+
+$lines[] = '
';
+
+$gspk = [];
+$fspk = [];
+$types = [];
+$types['twitter']['url']='https://twitter.com/';
+$types['twitter']['class']='fa fa-twitter';
+$types['github']['url']='https://github.com/';
+$types['github']['class']='fa fa-github';
+$types['email']['url']='mailto:';
+$types['email']['class']='fa fa-envelope';
+
+$gspk[] = '';
+
+foreach ($data['speakers'] as $speaker) {
+ $name = $speaker['first_name'] . ' ' . $speaker['last_name'];
+
+ $gspk[] = '
';
+
+ $fspk[] = '
';
+ $fspk[] = '
';
+ $fspk[] = '
' . $name . '
';
+ $fspk[] = '
';
+ foreach ($types as $type => $parm) {
+ if (!empty($speaker[$type])) {
+ $fspk[] = '
';
+ }
+ }
+ $fspk[] = '
' . $speaker['biography'] . '
';
+ $fspk[] = '
';
+}
+
+$gspk[] = '
';
+
+return array_merge($data, compact('lines', 'fulltalks', 'gspk', 'fspk'));