xref: /web-php/cal.php (revision 7023ed38)
1<?php
2$_SERVER['BASE_PAGE'] = 'cal.php';
3include_once __DIR__ . '/include/prepend.inc';
4
5$site_header_config = [
6    "current" => "community",
7    "css" => ['calendar.css'],
8    "layout_span" => 12,
9];
10
11/*
12 This script serves three different forms of the calendar data:
13    a monthly view ($cm, $cy)
14    a daily view ($cm, $cd, $cy)
15    an individual item view ($id)
16 For the last two, the month view is also displayed beneath the
17 specifically requested data. If we encounter an error, we have
18 a fallback to display the actual month/year.
19*/
20
21$begun = false; $errors = [];
22$id = isset($_GET['id']) ? (int) $_GET['id'] : 0;
23$cy = isset($_GET['cy']) ? (int) $_GET['cy'] : 0;
24$cm = isset($_GET['cm']) ? (int) $_GET['cm'] : 0;
25$cd = isset($_GET['cd']) ? (int) $_GET['cd'] : 0;
26
27// If the year is not valid, set it to the current year
28// This excludes all the "too old", or "too far in the future"
29// calendar displays (so search engines can handle this page too)
30if ($cy != 0 && !valid_year($cy)) {
31    $cy = date("Y");
32}
33
34// We need to look up an event with an ID
35if ($id) {
36    // Try to load event by ID and display header and info for that event
37    if ($event = load_event($id)) {
38        site_header("Event: " . stripslashes(htmlentities($event['sdesc'], ENT_QUOTES | ENT_IGNORE, 'UTF-8')), $site_header_config);
39        display_event($event, 0);
40        $begun = true;
41    }
42    // Unable to find event, put this to the error messages' list
43    else {
44        $errors[] = "There is no event for specified id ('" . htmlentities($id, ENT_QUOTES | ENT_IGNORE, 'UTF-8') . "')";
45    }
46}
47
48// Year, month and day specified, display a daily view
49elseif ($cy && $cm && $cd) {
50
51    // Check if date is valid
52    if (checkdate($cm,$cd,$cy)) {
53
54        // Date integer for that day
55        $date = mktime(0, 0, 1, $cm, $cd, $cy);
56
57        // Try to load events for that day, and display them all
58        if ($events = load_events($date)) {
59            $site_header_config = ['classes' => 'calendar calendar-day'] + $site_header_config;
60            site_header("Events: " . date("F j, Y", $date), $site_header_config);
61            echo "<h2>", date("F j, Y", $date), "</h2>\n";
62            foreach ($events as $event) {
63                display_event($event, 0);
64                echo "<br>";
65            }
66            $begun = true;
67        }
68
69        // Unable to load events for that day
70        else {
71            $errors[] = "There are no events for the specified date (" . date("F j, Y",$date) . ").";
72        }
73    }
74
75    // Wrong date specified
76    else {
77        $errors[] = "The specified date (" . htmlentities("$cy/$cm/$cd", ENT_QUOTES | ENT_IGNORE, 'UTF-8') . ") was not valid.";
78        unset($cm, $cd, $cy);
79    }
80}
81
82// Check if month and year is valid
83if ($cm && $cy && !checkdate($cm,1,$cy)) {
84    $errors[] = "The specified year and month (" . htmlentities("$cy, $cm", ENT_QUOTES | ENT_IGNORE, 'UTF-8') . ") are not valid.";
85    unset($cm, $cy);
86}
87
88// Give defaults for the month and day values if they were invalid
89if (empty($cm)) { $cm = date("m"); }
90if (empty($cy)) { $cy = date("Y"); }
91
92// Start of the month date
93$date = mktime(0, 0, 1, $cm, 1, $cy);
94
95if (!$begun) {
96  site_header("Events: " . date("F Y", $date), $site_header_config);
97?>
98<div class="tip">
99 <p>
100  If you would like to suggest an upcoming event to be listed on this
101  calendar, you can use <a href="submit-event.php">our event submission
102  form</a>.
103 </p>
104 <p>
105  You can click on each of the events for details, or on the number for a day
106  to get the details for all of the events taking place that day.
107 </p>
108</div>
109<?php
110}
111
112// Get events list for a whole month
113$events = load_events($date, true);
114
115// If there was an error, or there are no events, this is an error
116if ($events === false || count($events) == 0) {
117    $errors[] = "No events found for this month";
118}
119
120// If there were any error, display them
121if (count($errors) > 0) {
122    display_errors($errors);
123    site_footer();
124    exit;
125}
126
127// Beginning and end of this month
128$bom = mktime(0, 0, 1, $cm,   1, $cy);
129$eom = mktime(0, 0, 1, $cm + 1, 0, $cy);
130
131// Link to previous month (but do not link to too early dates)
132$prev_link = (function () use ($cm, $cy) {
133    $lm = mktime(0, 0, 1, $cm, 0, $cy);
134    $year = date('Y', $lm);
135    if (!valid_year($year)) {
136        return '&nbsp;';
137    }
138
139    $month = date('m', $lm);
140    $monthName = date('F', $lm);
141    return sprintf('<a href="/cal.php?cm=%s&amp;cy=%s">%s, %s</a>',
142                   urlencode($month),
143                   urlencode($year),
144                   htmlentities($monthName),
145                   htmlentities($year));
146})();
147
148// Link to next month (but do not link to too early dates)
149$next_link = (function () use ($cm, $cy) {
150    $nm = mktime(0, 0, 1, $cm + 1, 1, $cy);
151    $year = date('Y', $nm);
152    if (!valid_year($year)) {
153        return '&nbsp;';
154    }
155
156    $month = date('m', $nm);
157    $monthName = date('F', $nm);
158    return sprintf('<a href="/cal.php?cm=%s&amp;cy=%s">%s, %s</a>',
159                   urlencode($month),
160                   urlencode($year),
161                   htmlentities($monthName),
162                   htmlentities($year));
163})();
164
165// Print out navigation links for previous and next month
166echo '<br><table id="calnav" width="100%" border="0" cellspacing="0" cellpadding="3">',
167     "\n<tr>", '<td align="left" width="33%">', $prev_link, '</td>',
168     '<td align="center" width="33%"><b>', htmlentities(date('F, Y', $bom)), '</b></td>',
169     '<td align="right" width="33%">', $next_link, "</td></tr>\n</table>\n";
170
171// Begin the calendar table
172echo '<table id="cal" width="100%" border="1" cellspacing="0" cellpadding="3">',
173     "\n",'<tr>',"\n";
174
175// Print out headers for weekdays
176for ($i = 0; $i < 7; $i++) {
177    echo '<th width="14%">', date("l",mktime(0,0,1,4,$i + 1,2001)), "</th>\n";
178}
179echo "</tr>\n<tr>";
180
181// Generate the requisite number of blank days to get things started
182for ($days = $i = date("w",$bom); $i > 0; $i--) {
183    echo '<td class="notaday">&nbsp;</td>';
184}
185
186// Print out all the days in this month
187for ($i = 1; $i <= date("t",$bom); $i++) {
188
189    // Print out day number and all events for the day
190    echo '<td><a class="day" href="/cal.php', "?cm=$cm&amp;cd=$i&amp;cy=$cy",
191         '">',$i,'</a>';
192    display_events_for_day(date("Y-m-",$bom) . sprintf("%02d",$i), $events);
193    echo '</td>';
194
195    // Break HTML table row if at end of week
196    if (++$days % 7 == 0) echo "</tr>\n<tr>";
197}
198
199// Generate the requisite number of blank days to wrap things up
200for (; $days % 7; $days++) {
201    echo '<td class="notaday">&nbsp;</td>';
202}
203
204// End HTML table of events
205echo "</tr>\n</table>\n";
206
207// Print out common footer
208site_footer();
209
210// Generate the date on which a recurring event falls for a given month
211// $bom and $eom are the first and last day of the month to look at
212function date_for_recur($recur, $day, $bom, $eom)
213{
214
215    // $day == 1 == 'Sunday' == date("w",'some sunday')+1
216
217    // ${recur}th $day of the month
218    if ($recur > 0) {
219        $bomd = date("w", $bom) + 1;
220        $days = (($day - $bomd + 7) % 7) + (($recur - 1) * 7);
221        return mktime(0,0,1, date("m",$bom), $days + 1, date("Y",$bom));
222    }
223
224    // ${recur}th to last $day of the month
225    $eomd = date("w",$eom) + 1;
226    $days = (($eomd - $day + 7) % 7) + ((abs($recur) - 1) * 7);
227
228    return mktime(0, 0, 1, date("m", $bom) + 1, -$days, date("Y", $bom));
229}
230
231// Display a <div> for each of the events that are on a given day
232function display_events_for_day($day, $events): void
233{
234    // For preservation of state in the links
235    global $cm, $cy;
236
237    // For all events, try to find the events for this day
238    foreach ($events as $event) {
239
240        // Multiday event, which still lasts, or the event starts today
241        if (($event['type'] == 2 && $event['start'] <= $day && $event['end'] >= $day)
242         || ($event['start'] == $day)) {
243            echo '<div class="event">',
244                 '<a class="cat' . $event['category'] . '" href="/cal.php',
245                 "?id=$event[id]&amp;cm=$cm&amp;cy=$cy", '">',
246                 stripslashes(htmlentities($event['sdesc'], ENT_QUOTES | ENT_IGNORE, 'UTF-8')),
247                 '</a>',
248                 '</div>';
249        }
250    }
251}
252
253// Find a single event in the events file by ID
254function load_event($id)
255{
256    // Open events CSV file, return on error
257    $fp = @fopen("backend/events.csv",'r');
258    if (!$fp) { return false; }
259
260    // Read as we can, event by event
261    while (!feof($fp)) {
262
263      $event = read_event($fp);
264
265      // Return with the event, if it's ID is the one
266      // we search for (also close the file)
267      if ($event !== false && $event['id'] == $id) {
268        fclose($fp);
269        return $event;
270      }
271    }
272
273    // Close file, and return sign of failure
274    fclose($fp);
275    return false;
276}
277
278// Load a list of events. Either for a particular day ($from) or a whole
279// month (if second parameter specified with TRUE)
280function load_events($from, $whole_month = false)
281{
282    // Take advantage of the equality behavior of this date format
283    $from_date = date("Y-m-d", $from);
284    $bom = mktime(0, 0, 1, date("m",$from), 1, date("Y",$from));
285    $eom = mktime(0, 0, 1, date("m",$from) + 1, 0, date("Y",$from));
286    $to_date = date("Y-m-d", $whole_month ? $eom : $from);
287
288    // Set arrays to their default
289    $events = $seen = [];
290
291    // Try to open the events file for reading, return if unable to
292    $fp = @fopen("backend/events.csv",'r');
293    if (!$fp) { return false; }
294
295    // For all events, read in the event and check it if fits our scope
296    while (!feof($fp)) {
297
298        // Read the event data into $event, or continue with next
299        // line, if there was an error with this line
300        if (($event = read_event($fp)) === false) {
301            continue;
302        }
303
304        // Keep event's seen list up to date
305        // (for repeating events with the same ID)
306        if (!isset($seen[$event['id']])) { $seen[$event['id']] = 1; }
307        else { continue; }
308
309        // Check if event is in our scope, depending on type
310        switch ($event['type']) {
311
312             // Recurring event
313            case 3:
314                $date = date_for_recur($event['recur'], $event['recur_day'], $bom, $eom);
315                $event['start'] = date("Y-m-d", $date);
316                // Fall through. Now it is just like a single-day event
317
318            // Single-day event
319            case 1:
320                if ($event['start'] >= $from_date && $event['start'] <= $to_date) {
321                    $events[] = $event;
322                }
323                break;
324
325            // Multi-day event
326            case 2:
327                if (($event['start'] >= $from_date && $event['start'] <= $to_date)
328                 || ($event['end'] >= $from_date && $event['end'] <= $to_date)
329                 || ($event['start'] <= $from_date && $event['end'] >= $to_date)) {
330                  $events[] = $event;
331                }
332                break;
333        }
334    }
335
336    // Close file and return with results
337    fclose($fp);
338    return $events;
339}
340
341// Reads an event from the event listing
342// Parameter: opened event listing file
343function read_event($fp)
344{
345    // We were unable to read a line from the file, return
346    if (($linearr = fgetcsv($fp, 8192)) === false) {
347        return false;
348    }
349
350    // Corrupt line in CSV file
351    if (count($linearr) < 13) { return false; }
352
353    // Get components
354    [
355        $day, $month, $year, $country,
356        $sdesc, $id, $ldesc, $url, $recur, $tipo, $sdato, $edato, $category
357    ] = $linearr;
358
359    // Get info on recurring event
360    @[$recur, $recur_day] = explode(":", $recur, 2);
361
362    // Return with SQL-resultset like array
363    return [
364        'id' => $id,
365        'type' => $tipo,
366        'start' => $sdato,
367        'end' => $edato,
368        'recur' => $recur,
369        'recur_day' => $recur_day,
370        'sdesc' => $sdesc,
371        'url' => $url,
372        'ldesc' => base64_decode($ldesc, false),
373        'country' => $country,
374        'category' => $category,
375    ];
376}
377
378// We would not like to allow any year to be viewed, because
379// it would fool some [not clever enough] search engines
380function valid_year($year)
381{
382    // Get current year and compare to one sent in
383    $current_year = date("Y");
384
385    // We only allow this and the next year for displays
386    if ($year != $current_year && $year != $current_year + 1) {
387        return false;
388    }
389
390    // The year is all right
391    return true;
392}
393
394?>
395