xref: /plugin/calendar/action.php (revision 1d05cddc261a22328c4671319b0963b94fa1a7e9)
119378907SAtari911<?php
219378907SAtari911/**
319378907SAtari911 * DokuWiki Plugin calendar (Action Component)
419378907SAtari911 *
519378907SAtari911 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
619378907SAtari911 * @author  DokuWiki Community
719378907SAtari911 */
819378907SAtari911
919378907SAtari911if (!defined('DOKU_INC')) die();
1019378907SAtari911
1119378907SAtari911class action_plugin_calendar extends DokuWiki_Action_Plugin {
1219378907SAtari911
1319378907SAtari911    public function register(Doku_Event_Handler $controller) {
1419378907SAtari911        $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handleAjax');
1519378907SAtari911        $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'addAssets');
1619378907SAtari911    }
1719378907SAtari911
1819378907SAtari911    public function handleAjax(Doku_Event $event, $param) {
1919378907SAtari911        if ($event->data !== 'plugin_calendar') return;
2019378907SAtari911        $event->preventDefault();
2119378907SAtari911        $event->stopPropagation();
2219378907SAtari911
2319378907SAtari911        $action = $_REQUEST['action'] ?? '';
2419378907SAtari911
2519378907SAtari911        switch ($action) {
2619378907SAtari911            case 'save_event':
2719378907SAtari911                $this->saveEvent();
2819378907SAtari911                break;
2919378907SAtari911            case 'delete_event':
3019378907SAtari911                $this->deleteEvent();
3119378907SAtari911                break;
3219378907SAtari911            case 'get_event':
3319378907SAtari911                $this->getEvent();
3419378907SAtari911                break;
3519378907SAtari911            case 'load_month':
3619378907SAtari911                $this->loadMonth();
3719378907SAtari911                break;
3819378907SAtari911            case 'toggle_task':
3919378907SAtari911                $this->toggleTaskComplete();
4019378907SAtari911                break;
4119378907SAtari911            default:
4219378907SAtari911                echo json_encode(['success' => false, 'error' => 'Unknown action']);
4319378907SAtari911        }
4419378907SAtari911    }
4519378907SAtari911
4619378907SAtari911    private function saveEvent() {
4719378907SAtari911        global $INPUT;
4819378907SAtari911
4919378907SAtari911        $namespace = $INPUT->str('namespace', '');
5019378907SAtari911        $date = $INPUT->str('date');
5119378907SAtari911        $eventId = $INPUT->str('eventId', '');
5219378907SAtari911        $title = $INPUT->str('title');
5319378907SAtari911        $time = $INPUT->str('time', '');
54*1d05cddcSAtari911        $endTime = $INPUT->str('endTime', '');
5519378907SAtari911        $description = $INPUT->str('description', '');
5619378907SAtari911        $color = $INPUT->str('color', '#3498db');
5719378907SAtari911        $oldDate = $INPUT->str('oldDate', ''); // Track original date for moves
5819378907SAtari911        $isTask = $INPUT->bool('isTask', false);
5919378907SAtari911        $completed = $INPUT->bool('completed', false);
6019378907SAtari911        $endDate = $INPUT->str('endDate', '');
6187ac9bf3SAtari911        $isRecurring = $INPUT->bool('isRecurring', false);
6287ac9bf3SAtari911        $recurrenceType = $INPUT->str('recurrenceType', 'weekly');
6387ac9bf3SAtari911        $recurrenceEnd = $INPUT->str('recurrenceEnd', '');
6419378907SAtari911
6519378907SAtari911        if (!$date || !$title) {
6619378907SAtari911            echo json_encode(['success' => false, 'error' => 'Missing required fields']);
6719378907SAtari911            return;
6819378907SAtari911        }
6919378907SAtari911
70*1d05cddcSAtari911        // If editing, find the event's stored namespace (for finding/deleting old event)
71e3a9f44cSAtari911        $storedNamespace = '';
72*1d05cddcSAtari911        $oldNamespace = '';
73e3a9f44cSAtari911        if ($eventId) {
74*1d05cddcSAtari911            // Use oldDate if available (date was changed), otherwise use current date
75*1d05cddcSAtari911            $searchDate = ($oldDate && $oldDate !== $date) ? $oldDate : $date;
76*1d05cddcSAtari911            $storedNamespace = $this->findEventNamespace($eventId, $searchDate, $namespace);
77*1d05cddcSAtari911
78*1d05cddcSAtari911            // Store the old namespace for deletion purposes
79*1d05cddcSAtari911            if ($storedNamespace !== null) {
80*1d05cddcSAtari911                $oldNamespace = $storedNamespace;
81*1d05cddcSAtari911                error_log("Calendar saveEvent: Found existing event in namespace '$oldNamespace'");
82*1d05cddcSAtari911            }
83e3a9f44cSAtari911        }
84e3a9f44cSAtari911
85*1d05cddcSAtari911        // Use the namespace provided by the user (allow namespace changes!)
86*1d05cddcSAtari911        // But normalize wildcards and multi-namespace to empty for NEW events
87*1d05cddcSAtari911        if (!$eventId) {
88*1d05cddcSAtari911            error_log("Calendar saveEvent: NEW event, received namespace='$namespace'");
89e3a9f44cSAtari911            // Normalize namespace: treat wildcards and multi-namespace as empty (default) for NEW events
90e3a9f44cSAtari911            if (!empty($namespace) && (strpos($namespace, '*') !== false || strpos($namespace, ';') !== false)) {
91*1d05cddcSAtari911                error_log("Calendar saveEvent: Namespace contains wildcard/multi, clearing to empty");
92e3a9f44cSAtari911                $namespace = '';
93*1d05cddcSAtari911            } else {
94*1d05cddcSAtari911                error_log("Calendar saveEvent: Namespace is clean, keeping as '$namespace'");
95e3a9f44cSAtari911            }
96*1d05cddcSAtari911        } else {
97*1d05cddcSAtari911            error_log("Calendar saveEvent: EDITING event $eventId, user selected namespace='$namespace'");
98e3a9f44cSAtari911        }
99e3a9f44cSAtari911
10087ac9bf3SAtari911        // Generate event ID if new
10187ac9bf3SAtari911        $generatedId = $eventId ?: uniqid();
10287ac9bf3SAtari911
10387ac9bf3SAtari911        // If recurring, generate multiple events
10487ac9bf3SAtari911        if ($isRecurring) {
10587ac9bf3SAtari911            $this->createRecurringEvents($namespace, $date, $endDate, $title, $time, $description,
10687ac9bf3SAtari911                                        $color, $isTask, $recurrenceType, $recurrenceEnd, $generatedId);
10787ac9bf3SAtari911            echo json_encode(['success' => true]);
10887ac9bf3SAtari911            return;
10987ac9bf3SAtari911        }
11087ac9bf3SAtari911
11119378907SAtari911        list($year, $month, $day) = explode('-', $date);
11219378907SAtari911
113*1d05cddcSAtari911        // NEW namespace directory (where we'll save)
11419378907SAtari911        $dataDir = DOKU_INC . 'data/meta/';
11519378907SAtari911        if ($namespace) {
11619378907SAtari911            $dataDir .= str_replace(':', '/', $namespace) . '/';
11719378907SAtari911        }
11819378907SAtari911        $dataDir .= 'calendar/';
11919378907SAtari911
12019378907SAtari911        if (!is_dir($dataDir)) {
12119378907SAtari911            mkdir($dataDir, 0755, true);
12219378907SAtari911        }
12319378907SAtari911
12419378907SAtari911        $eventFile = $dataDir . sprintf('%04d-%02d.json', $year, $month);
12519378907SAtari911
12619378907SAtari911        $events = [];
12719378907SAtari911        if (file_exists($eventFile)) {
12819378907SAtari911            $events = json_decode(file_get_contents($eventFile), true);
12919378907SAtari911        }
13019378907SAtari911
131*1d05cddcSAtari911        // If editing and (date changed OR namespace changed), remove from old location first
132*1d05cddcSAtari911        $namespaceChanged = ($eventId && $oldNamespace !== '' && $oldNamespace !== $namespace);
133*1d05cddcSAtari911        $dateChanged = ($eventId && $oldDate && $oldDate !== $date);
134*1d05cddcSAtari911
135*1d05cddcSAtari911        if ($namespaceChanged || $dateChanged) {
136*1d05cddcSAtari911            // Construct OLD data directory using OLD namespace
137*1d05cddcSAtari911            $oldDataDir = DOKU_INC . 'data/meta/';
138*1d05cddcSAtari911            if ($oldNamespace) {
139*1d05cddcSAtari911                $oldDataDir .= str_replace(':', '/', $oldNamespace) . '/';
140*1d05cddcSAtari911            }
141*1d05cddcSAtari911            $oldDataDir .= 'calendar/';
142*1d05cddcSAtari911
143*1d05cddcSAtari911            $deleteDate = $dateChanged ? $oldDate : $date;
144*1d05cddcSAtari911            list($oldYear, $oldMonth, $oldDay) = explode('-', $deleteDate);
145*1d05cddcSAtari911            $oldEventFile = $oldDataDir . sprintf('%04d-%02d.json', $oldYear, $oldMonth);
14619378907SAtari911
14719378907SAtari911            if (file_exists($oldEventFile)) {
14819378907SAtari911                $oldEvents = json_decode(file_get_contents($oldEventFile), true);
149*1d05cddcSAtari911                if (isset($oldEvents[$deleteDate])) {
150*1d05cddcSAtari911                    $oldEvents[$deleteDate] = array_values(array_filter($oldEvents[$deleteDate], function($evt) use ($eventId) {
15119378907SAtari911                        return $evt['id'] !== $eventId;
152e3a9f44cSAtari911                    }));
15319378907SAtari911
154*1d05cddcSAtari911                    if (empty($oldEvents[$deleteDate])) {
155*1d05cddcSAtari911                        unset($oldEvents[$deleteDate]);
15619378907SAtari911                    }
15719378907SAtari911
15819378907SAtari911                    file_put_contents($oldEventFile, json_encode($oldEvents, JSON_PRETTY_PRINT));
159*1d05cddcSAtari911                    error_log("Calendar saveEvent: Deleted event from old location - namespace:'$oldNamespace', date:'$deleteDate'");
16019378907SAtari911                }
16119378907SAtari911            }
16219378907SAtari911        }
16319378907SAtari911
16419378907SAtari911        if (!isset($events[$date])) {
16519378907SAtari911            $events[$date] = [];
166e3a9f44cSAtari911        } elseif (!is_array($events[$date])) {
167e3a9f44cSAtari911            // Fix corrupted data - ensure it's an array
168e3a9f44cSAtari911            error_log("Calendar saveEvent: Fixing corrupted data at $date - was not an array");
169e3a9f44cSAtari911            $events[$date] = [];
17019378907SAtari911        }
17119378907SAtari911
172e3a9f44cSAtari911        // Store the namespace with the event
17319378907SAtari911        $eventData = [
17487ac9bf3SAtari911            'id' => $generatedId,
17519378907SAtari911            'title' => $title,
17619378907SAtari911            'time' => $time,
177*1d05cddcSAtari911            'endTime' => $endTime,
17819378907SAtari911            'description' => $description,
17919378907SAtari911            'color' => $color,
18019378907SAtari911            'isTask' => $isTask,
18119378907SAtari911            'completed' => $completed,
18219378907SAtari911            'endDate' => $endDate,
183e3a9f44cSAtari911            'namespace' => $namespace, // Store namespace with event
18419378907SAtari911            'created' => date('Y-m-d H:i:s')
18519378907SAtari911        ];
18619378907SAtari911
187*1d05cddcSAtari911        // Debug logging
188*1d05cddcSAtari911        error_log("Calendar saveEvent: Saving event '$title' with namespace='$namespace' to file $eventFile");
189*1d05cddcSAtari911
19019378907SAtari911        // If editing, replace existing event
19119378907SAtari911        if ($eventId) {
19219378907SAtari911            $found = false;
19319378907SAtari911            foreach ($events[$date] as $key => $evt) {
19419378907SAtari911                if ($evt['id'] === $eventId) {
19519378907SAtari911                    $events[$date][$key] = $eventData;
19619378907SAtari911                    $found = true;
19719378907SAtari911                    break;
19819378907SAtari911                }
19919378907SAtari911            }
20019378907SAtari911            if (!$found) {
20119378907SAtari911                $events[$date][] = $eventData;
20219378907SAtari911            }
20319378907SAtari911        } else {
20419378907SAtari911            $events[$date][] = $eventData;
20519378907SAtari911        }
20619378907SAtari911
20719378907SAtari911        file_put_contents($eventFile, json_encode($events, JSON_PRETTY_PRINT));
20819378907SAtari911
209e3a9f44cSAtari911        // If event spans multiple months, add it to the first day of each subsequent month
210e3a9f44cSAtari911        if ($endDate && $endDate !== $date) {
211e3a9f44cSAtari911            $startDateObj = new DateTime($date);
212e3a9f44cSAtari911            $endDateObj = new DateTime($endDate);
213e3a9f44cSAtari911
214e3a9f44cSAtari911            // Get the month/year of the start date
215e3a9f44cSAtari911            $startMonth = $startDateObj->format('Y-m');
216e3a9f44cSAtari911
217e3a9f44cSAtari911            // Iterate through each month the event spans
218e3a9f44cSAtari911            $currentDate = clone $startDateObj;
219e3a9f44cSAtari911            $currentDate->modify('first day of next month'); // Jump to first of next month
220e3a9f44cSAtari911
221e3a9f44cSAtari911            while ($currentDate <= $endDateObj) {
222e3a9f44cSAtari911                $currentMonth = $currentDate->format('Y-m');
223e3a9f44cSAtari911                $firstDayOfMonth = $currentDate->format('Y-m-01');
224e3a9f44cSAtari911
225e3a9f44cSAtari911                list($currentYear, $currentMonthNum, $currentDay) = explode('-', $firstDayOfMonth);
226e3a9f44cSAtari911
227e3a9f44cSAtari911                // Get the file for this month
228e3a9f44cSAtari911                $currentEventFile = $dataDir . sprintf('%04d-%02d.json', $currentYear, $currentMonthNum);
229e3a9f44cSAtari911
230e3a9f44cSAtari911                $currentEvents = [];
231e3a9f44cSAtari911                if (file_exists($currentEventFile)) {
232e3a9f44cSAtari911                    $contents = file_get_contents($currentEventFile);
233e3a9f44cSAtari911                    $decoded = json_decode($contents, true);
234e3a9f44cSAtari911                    if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
235e3a9f44cSAtari911                        $currentEvents = $decoded;
236e3a9f44cSAtari911                    } else {
237e3a9f44cSAtari911                        error_log("Calendar saveEvent: JSON decode error in $currentEventFile: " . json_last_error_msg());
238e3a9f44cSAtari911                    }
239e3a9f44cSAtari911                }
240e3a9f44cSAtari911
241e3a9f44cSAtari911                // Add entry for the first day of this month
242e3a9f44cSAtari911                if (!isset($currentEvents[$firstDayOfMonth])) {
243e3a9f44cSAtari911                    $currentEvents[$firstDayOfMonth] = [];
244e3a9f44cSAtari911                } elseif (!is_array($currentEvents[$firstDayOfMonth])) {
245e3a9f44cSAtari911                    // Fix corrupted data - ensure it's an array
246e3a9f44cSAtari911                    error_log("Calendar saveEvent: Fixing corrupted data at $firstDayOfMonth - was not an array");
247e3a9f44cSAtari911                    $currentEvents[$firstDayOfMonth] = [];
248e3a9f44cSAtari911                }
249e3a9f44cSAtari911
250e3a9f44cSAtari911                // Create a copy with the original start date preserved
251e3a9f44cSAtari911                $eventDataForMonth = $eventData;
252e3a9f44cSAtari911                $eventDataForMonth['originalStartDate'] = $date; // Preserve the actual start date
253e3a9f44cSAtari911
254e3a9f44cSAtari911                // Check if event already exists (when editing)
255e3a9f44cSAtari911                $found = false;
256e3a9f44cSAtari911                if ($eventId) {
257e3a9f44cSAtari911                    foreach ($currentEvents[$firstDayOfMonth] as $key => $evt) {
258e3a9f44cSAtari911                        if ($evt['id'] === $eventId) {
259e3a9f44cSAtari911                            $currentEvents[$firstDayOfMonth][$key] = $eventDataForMonth;
260e3a9f44cSAtari911                            $found = true;
261e3a9f44cSAtari911                            break;
262e3a9f44cSAtari911                        }
263e3a9f44cSAtari911                    }
264e3a9f44cSAtari911                }
265e3a9f44cSAtari911
266e3a9f44cSAtari911                if (!$found) {
267e3a9f44cSAtari911                    $currentEvents[$firstDayOfMonth][] = $eventDataForMonth;
268e3a9f44cSAtari911                }
269e3a9f44cSAtari911
270e3a9f44cSAtari911                file_put_contents($currentEventFile, json_encode($currentEvents, JSON_PRETTY_PRINT));
271e3a9f44cSAtari911
272e3a9f44cSAtari911                // Move to next month
273e3a9f44cSAtari911                $currentDate->modify('first day of next month');
274e3a9f44cSAtari911            }
275e3a9f44cSAtari911        }
276e3a9f44cSAtari911
27719378907SAtari911        echo json_encode(['success' => true, 'events' => $events, 'eventId' => $eventData['id']]);
27819378907SAtari911    }
27919378907SAtari911
28019378907SAtari911    private function deleteEvent() {
28119378907SAtari911        global $INPUT;
28219378907SAtari911
28319378907SAtari911        $namespace = $INPUT->str('namespace', '');
28419378907SAtari911        $date = $INPUT->str('date');
28519378907SAtari911        $eventId = $INPUT->str('eventId');
28619378907SAtari911
287e3a9f44cSAtari911        // Find where the event actually lives
288e3a9f44cSAtari911        $storedNamespace = $this->findEventNamespace($eventId, $date, $namespace);
289e3a9f44cSAtari911
290e3a9f44cSAtari911        if ($storedNamespace === null) {
291e3a9f44cSAtari911            echo json_encode(['success' => false, 'error' => 'Event not found']);
292e3a9f44cSAtari911            return;
293e3a9f44cSAtari911        }
294e3a9f44cSAtari911
295e3a9f44cSAtari911        // Use the found namespace
296e3a9f44cSAtari911        $namespace = $storedNamespace;
297e3a9f44cSAtari911
29819378907SAtari911        list($year, $month, $day) = explode('-', $date);
29919378907SAtari911
30019378907SAtari911        $dataDir = DOKU_INC . 'data/meta/';
30119378907SAtari911        if ($namespace) {
30219378907SAtari911            $dataDir .= str_replace(':', '/', $namespace) . '/';
30319378907SAtari911        }
30419378907SAtari911        $dataDir .= 'calendar/';
30519378907SAtari911
30619378907SAtari911        $eventFile = $dataDir . sprintf('%04d-%02d.json', $year, $month);
30719378907SAtari911
308e3a9f44cSAtari911        // First, get the event to check if it spans multiple months
309e3a9f44cSAtari911        $eventToDelete = null;
31019378907SAtari911        if (file_exists($eventFile)) {
31119378907SAtari911            $events = json_decode(file_get_contents($eventFile), true);
31219378907SAtari911
31319378907SAtari911            if (isset($events[$date])) {
314e3a9f44cSAtari911                foreach ($events[$date] as $event) {
315e3a9f44cSAtari911                    if ($event['id'] === $eventId) {
316e3a9f44cSAtari911                        $eventToDelete = $event;
317e3a9f44cSAtari911                        break;
318e3a9f44cSAtari911                    }
319e3a9f44cSAtari911                }
320e3a9f44cSAtari911
321e3a9f44cSAtari911                $events[$date] = array_values(array_filter($events[$date], function($event) use ($eventId) {
32219378907SAtari911                    return $event['id'] !== $eventId;
323e3a9f44cSAtari911                }));
32419378907SAtari911
32519378907SAtari911                if (empty($events[$date])) {
32619378907SAtari911                    unset($events[$date]);
32719378907SAtari911                }
32819378907SAtari911
32919378907SAtari911                file_put_contents($eventFile, json_encode($events, JSON_PRETTY_PRINT));
33019378907SAtari911            }
33119378907SAtari911        }
33219378907SAtari911
333e3a9f44cSAtari911        // If event spans multiple months, delete it from the first day of each subsequent month
334e3a9f44cSAtari911        if ($eventToDelete && isset($eventToDelete['endDate']) && $eventToDelete['endDate'] && $eventToDelete['endDate'] !== $date) {
335e3a9f44cSAtari911            $startDateObj = new DateTime($date);
336e3a9f44cSAtari911            $endDateObj = new DateTime($eventToDelete['endDate']);
337e3a9f44cSAtari911
338e3a9f44cSAtari911            // Iterate through each month the event spans
339e3a9f44cSAtari911            $currentDate = clone $startDateObj;
340e3a9f44cSAtari911            $currentDate->modify('first day of next month'); // Jump to first of next month
341e3a9f44cSAtari911
342e3a9f44cSAtari911            while ($currentDate <= $endDateObj) {
343e3a9f44cSAtari911                $firstDayOfMonth = $currentDate->format('Y-m-01');
344e3a9f44cSAtari911                list($currentYear, $currentMonth, $currentDay) = explode('-', $firstDayOfMonth);
345e3a9f44cSAtari911
346e3a9f44cSAtari911                // Get the file for this month
347e3a9f44cSAtari911                $currentEventFile = $dataDir . sprintf('%04d-%02d.json', $currentYear, $currentMonth);
348e3a9f44cSAtari911
349e3a9f44cSAtari911                if (file_exists($currentEventFile)) {
350e3a9f44cSAtari911                    $currentEvents = json_decode(file_get_contents($currentEventFile), true);
351e3a9f44cSAtari911
352e3a9f44cSAtari911                    if (isset($currentEvents[$firstDayOfMonth])) {
353e3a9f44cSAtari911                        $currentEvents[$firstDayOfMonth] = array_values(array_filter($currentEvents[$firstDayOfMonth], function($event) use ($eventId) {
354e3a9f44cSAtari911                            return $event['id'] !== $eventId;
355e3a9f44cSAtari911                        }));
356e3a9f44cSAtari911
357e3a9f44cSAtari911                        if (empty($currentEvents[$firstDayOfMonth])) {
358e3a9f44cSAtari911                            unset($currentEvents[$firstDayOfMonth]);
359e3a9f44cSAtari911                        }
360e3a9f44cSAtari911
361e3a9f44cSAtari911                        file_put_contents($currentEventFile, json_encode($currentEvents, JSON_PRETTY_PRINT));
362e3a9f44cSAtari911                    }
363e3a9f44cSAtari911                }
364e3a9f44cSAtari911
365e3a9f44cSAtari911                // Move to next month
366e3a9f44cSAtari911                $currentDate->modify('first day of next month');
367e3a9f44cSAtari911            }
368e3a9f44cSAtari911        }
369e3a9f44cSAtari911
37019378907SAtari911        echo json_encode(['success' => true]);
37119378907SAtari911    }
37219378907SAtari911
37319378907SAtari911    private function getEvent() {
37419378907SAtari911        global $INPUT;
37519378907SAtari911
37619378907SAtari911        $namespace = $INPUT->str('namespace', '');
37719378907SAtari911        $date = $INPUT->str('date');
37819378907SAtari911        $eventId = $INPUT->str('eventId');
37919378907SAtari911
380e3a9f44cSAtari911        // Find where the event actually lives
381e3a9f44cSAtari911        $storedNamespace = $this->findEventNamespace($eventId, $date, $namespace);
382e3a9f44cSAtari911
383e3a9f44cSAtari911        if ($storedNamespace === null) {
384e3a9f44cSAtari911            echo json_encode(['success' => false, 'error' => 'Event not found']);
385e3a9f44cSAtari911            return;
386e3a9f44cSAtari911        }
387e3a9f44cSAtari911
388e3a9f44cSAtari911        // Use the found namespace
389e3a9f44cSAtari911        $namespace = $storedNamespace;
390e3a9f44cSAtari911
39119378907SAtari911        list($year, $month, $day) = explode('-', $date);
39219378907SAtari911
39319378907SAtari911        $dataDir = DOKU_INC . 'data/meta/';
39419378907SAtari911        if ($namespace) {
39519378907SAtari911            $dataDir .= str_replace(':', '/', $namespace) . '/';
39619378907SAtari911        }
39719378907SAtari911        $dataDir .= 'calendar/';
39819378907SAtari911
39919378907SAtari911        $eventFile = $dataDir . sprintf('%04d-%02d.json', $year, $month);
40019378907SAtari911
40119378907SAtari911        if (file_exists($eventFile)) {
40219378907SAtari911            $events = json_decode(file_get_contents($eventFile), true);
40319378907SAtari911
40419378907SAtari911            if (isset($events[$date])) {
40519378907SAtari911                foreach ($events[$date] as $event) {
40619378907SAtari911                    if ($event['id'] === $eventId) {
407*1d05cddcSAtari911                        // Include the namespace so JavaScript knows where this event actually lives
408*1d05cddcSAtari911                        $event['namespace'] = $namespace;
40919378907SAtari911                        echo json_encode(['success' => true, 'event' => $event]);
41019378907SAtari911                        return;
41119378907SAtari911                    }
41219378907SAtari911                }
41319378907SAtari911            }
41419378907SAtari911        }
41519378907SAtari911
41619378907SAtari911        echo json_encode(['success' => false, 'error' => 'Event not found']);
41719378907SAtari911    }
41819378907SAtari911
41919378907SAtari911    private function loadMonth() {
42019378907SAtari911        global $INPUT;
42119378907SAtari911
422e3a9f44cSAtari911        // Prevent caching of AJAX responses
423e3a9f44cSAtari911        header('Cache-Control: no-cache, no-store, must-revalidate');
424e3a9f44cSAtari911        header('Pragma: no-cache');
425e3a9f44cSAtari911        header('Expires: 0');
426e3a9f44cSAtari911
42719378907SAtari911        $namespace = $INPUT->str('namespace', '');
42819378907SAtari911        $year = $INPUT->int('year');
42919378907SAtari911        $month = $INPUT->int('month');
43019378907SAtari911
431e3a9f44cSAtari911        error_log("=== Calendar loadMonth DEBUG ===");
432e3a9f44cSAtari911        error_log("Requested: year=$year, month=$month, namespace='$namespace'");
433e3a9f44cSAtari911
434e3a9f44cSAtari911        // Check if multi-namespace or wildcard
435e3a9f44cSAtari911        $isMultiNamespace = !empty($namespace) && (strpos($namespace, ';') !== false || strpos($namespace, '*') !== false);
436e3a9f44cSAtari911
437e3a9f44cSAtari911        error_log("isMultiNamespace: " . ($isMultiNamespace ? 'true' : 'false'));
438e3a9f44cSAtari911
439e3a9f44cSAtari911        if ($isMultiNamespace) {
440e3a9f44cSAtari911            $events = $this->loadEventsMultiNamespace($namespace, $year, $month);
441e3a9f44cSAtari911        } else {
442e3a9f44cSAtari911            $events = $this->loadEventsSingleNamespace($namespace, $year, $month);
443e3a9f44cSAtari911        }
444e3a9f44cSAtari911
445e3a9f44cSAtari911        error_log("Returning " . count($events) . " date keys");
446e3a9f44cSAtari911        foreach ($events as $dateKey => $dayEvents) {
447e3a9f44cSAtari911            error_log("  dateKey=$dateKey has " . count($dayEvents) . " events");
448e3a9f44cSAtari911        }
449e3a9f44cSAtari911
450e3a9f44cSAtari911        echo json_encode([
451e3a9f44cSAtari911            'success' => true,
452e3a9f44cSAtari911            'year' => $year,
453e3a9f44cSAtari911            'month' => $month,
454e3a9f44cSAtari911            'events' => $events
455e3a9f44cSAtari911        ]);
456e3a9f44cSAtari911    }
457e3a9f44cSAtari911
458e3a9f44cSAtari911    private function loadEventsSingleNamespace($namespace, $year, $month) {
45919378907SAtari911        $dataDir = DOKU_INC . 'data/meta/';
46019378907SAtari911        if ($namespace) {
46119378907SAtari911            $dataDir .= str_replace(':', '/', $namespace) . '/';
46219378907SAtari911        }
46319378907SAtari911        $dataDir .= 'calendar/';
46419378907SAtari911
465e3a9f44cSAtari911        // Load ONLY current month
46687ac9bf3SAtari911        $eventFile = $dataDir . sprintf('%04d-%02d.json', $year, $month);
46719378907SAtari911        $events = [];
46819378907SAtari911        if (file_exists($eventFile)) {
46987ac9bf3SAtari911            $contents = file_get_contents($eventFile);
47087ac9bf3SAtari911            $decoded = json_decode($contents, true);
47187ac9bf3SAtari911            if (json_last_error() === JSON_ERROR_NONE) {
47287ac9bf3SAtari911                $events = $decoded;
47387ac9bf3SAtari911            }
47487ac9bf3SAtari911        }
47587ac9bf3SAtari911
476e3a9f44cSAtari911        return $events;
47787ac9bf3SAtari911    }
478e3a9f44cSAtari911
479e3a9f44cSAtari911    private function loadEventsMultiNamespace($namespaces, $year, $month) {
480e3a9f44cSAtari911        // Check for wildcard pattern
481e3a9f44cSAtari911        if (preg_match('/^(.+):\*$/', $namespaces, $matches)) {
482e3a9f44cSAtari911            $baseNamespace = $matches[1];
483e3a9f44cSAtari911            return $this->loadEventsWildcard($baseNamespace, $year, $month);
484e3a9f44cSAtari911        }
485e3a9f44cSAtari911
486e3a9f44cSAtari911        // Check for root wildcard
487e3a9f44cSAtari911        if ($namespaces === '*') {
488e3a9f44cSAtari911            return $this->loadEventsWildcard('', $year, $month);
489e3a9f44cSAtari911        }
490e3a9f44cSAtari911
491e3a9f44cSAtari911        // Parse namespace list (semicolon separated)
492e3a9f44cSAtari911        $namespaceList = array_map('trim', explode(';', $namespaces));
493e3a9f44cSAtari911
494e3a9f44cSAtari911        // Load events from all namespaces
495e3a9f44cSAtari911        $allEvents = [];
496e3a9f44cSAtari911        foreach ($namespaceList as $ns) {
497e3a9f44cSAtari911            $ns = trim($ns);
498e3a9f44cSAtari911            if (empty($ns)) continue;
499e3a9f44cSAtari911
500e3a9f44cSAtari911            $events = $this->loadEventsSingleNamespace($ns, $year, $month);
501e3a9f44cSAtari911
502e3a9f44cSAtari911            // Add namespace tag to each event
503e3a9f44cSAtari911            foreach ($events as $dateKey => $dayEvents) {
504e3a9f44cSAtari911                if (!isset($allEvents[$dateKey])) {
505e3a9f44cSAtari911                    $allEvents[$dateKey] = [];
506e3a9f44cSAtari911                }
507e3a9f44cSAtari911                foreach ($dayEvents as $event) {
508e3a9f44cSAtari911                    $event['_namespace'] = $ns;
509e3a9f44cSAtari911                    $allEvents[$dateKey][] = $event;
510e3a9f44cSAtari911                }
51187ac9bf3SAtari911            }
51287ac9bf3SAtari911        }
51387ac9bf3SAtari911
514e3a9f44cSAtari911        return $allEvents;
515e3a9f44cSAtari911    }
51619378907SAtari911
517e3a9f44cSAtari911    private function loadEventsWildcard($baseNamespace, $year, $month) {
518e3a9f44cSAtari911        $dataDir = DOKU_INC . 'data/meta/';
519e3a9f44cSAtari911        if ($baseNamespace) {
520e3a9f44cSAtari911            $dataDir .= str_replace(':', '/', $baseNamespace) . '/';
521e3a9f44cSAtari911        }
522e3a9f44cSAtari911
523e3a9f44cSAtari911        $allEvents = [];
524e3a9f44cSAtari911
525e3a9f44cSAtari911        // First, load events from the base namespace itself
526e3a9f44cSAtari911        $events = $this->loadEventsSingleNamespace($baseNamespace, $year, $month);
527e3a9f44cSAtari911
528e3a9f44cSAtari911        foreach ($events as $dateKey => $dayEvents) {
529e3a9f44cSAtari911            if (!isset($allEvents[$dateKey])) {
530e3a9f44cSAtari911                $allEvents[$dateKey] = [];
531e3a9f44cSAtari911            }
532e3a9f44cSAtari911            foreach ($dayEvents as $event) {
533e3a9f44cSAtari911                $event['_namespace'] = $baseNamespace;
534e3a9f44cSAtari911                $allEvents[$dateKey][] = $event;
535e3a9f44cSAtari911            }
536e3a9f44cSAtari911        }
537e3a9f44cSAtari911
538e3a9f44cSAtari911        // Recursively find all subdirectories
539e3a9f44cSAtari911        $this->findSubNamespaces($dataDir, $baseNamespace, $year, $month, $allEvents);
540e3a9f44cSAtari911
541e3a9f44cSAtari911        return $allEvents;
542e3a9f44cSAtari911    }
543e3a9f44cSAtari911
544e3a9f44cSAtari911    private function findSubNamespaces($dir, $baseNamespace, $year, $month, &$allEvents) {
545e3a9f44cSAtari911        if (!is_dir($dir)) return;
546e3a9f44cSAtari911
547e3a9f44cSAtari911        $items = scandir($dir);
548e3a9f44cSAtari911        foreach ($items as $item) {
549e3a9f44cSAtari911            if ($item === '.' || $item === '..') continue;
550e3a9f44cSAtari911
551e3a9f44cSAtari911            $path = $dir . $item;
552e3a9f44cSAtari911            if (is_dir($path) && $item !== 'calendar') {
553e3a9f44cSAtari911                // This is a namespace directory
554e3a9f44cSAtari911                $namespace = $baseNamespace ? $baseNamespace . ':' . $item : $item;
555e3a9f44cSAtari911
556e3a9f44cSAtari911                // Load events from this namespace
557e3a9f44cSAtari911                $events = $this->loadEventsSingleNamespace($namespace, $year, $month);
558e3a9f44cSAtari911                foreach ($events as $dateKey => $dayEvents) {
559e3a9f44cSAtari911                    if (!isset($allEvents[$dateKey])) {
560e3a9f44cSAtari911                        $allEvents[$dateKey] = [];
561e3a9f44cSAtari911                    }
562e3a9f44cSAtari911                    foreach ($dayEvents as $event) {
563e3a9f44cSAtari911                        $event['_namespace'] = $namespace;
564e3a9f44cSAtari911                        $allEvents[$dateKey][] = $event;
565e3a9f44cSAtari911                    }
566e3a9f44cSAtari911                }
567e3a9f44cSAtari911
568e3a9f44cSAtari911                // Recurse into subdirectories
569e3a9f44cSAtari911                $this->findSubNamespaces($path . '/', $namespace, $year, $month, $allEvents);
570e3a9f44cSAtari911            }
571e3a9f44cSAtari911        }
57219378907SAtari911    }
57319378907SAtari911
57419378907SAtari911    private function toggleTaskComplete() {
57519378907SAtari911        global $INPUT;
57619378907SAtari911
57719378907SAtari911        $namespace = $INPUT->str('namespace', '');
57819378907SAtari911        $date = $INPUT->str('date');
57919378907SAtari911        $eventId = $INPUT->str('eventId');
58019378907SAtari911        $completed = $INPUT->bool('completed', false);
58119378907SAtari911
582e3a9f44cSAtari911        // Find where the event actually lives
583e3a9f44cSAtari911        $storedNamespace = $this->findEventNamespace($eventId, $date, $namespace);
584e3a9f44cSAtari911
585e3a9f44cSAtari911        if ($storedNamespace === null) {
586e3a9f44cSAtari911            echo json_encode(['success' => false, 'error' => 'Event not found']);
587e3a9f44cSAtari911            return;
588e3a9f44cSAtari911        }
589e3a9f44cSAtari911
590e3a9f44cSAtari911        // Use the found namespace
591e3a9f44cSAtari911        $namespace = $storedNamespace;
592e3a9f44cSAtari911
59319378907SAtari911        list($year, $month, $day) = explode('-', $date);
59419378907SAtari911
59519378907SAtari911        $dataDir = DOKU_INC . 'data/meta/';
59619378907SAtari911        if ($namespace) {
59719378907SAtari911            $dataDir .= str_replace(':', '/', $namespace) . '/';
59819378907SAtari911        }
59919378907SAtari911        $dataDir .= 'calendar/';
60019378907SAtari911
60119378907SAtari911        $eventFile = $dataDir . sprintf('%04d-%02d.json', $year, $month);
60219378907SAtari911
60319378907SAtari911        if (file_exists($eventFile)) {
60419378907SAtari911            $events = json_decode(file_get_contents($eventFile), true);
60519378907SAtari911
60619378907SAtari911            if (isset($events[$date])) {
60719378907SAtari911                foreach ($events[$date] as $key => $event) {
60819378907SAtari911                    if ($event['id'] === $eventId) {
60919378907SAtari911                        $events[$date][$key]['completed'] = $completed;
61019378907SAtari911                        break;
61119378907SAtari911                    }
61219378907SAtari911                }
61319378907SAtari911
61419378907SAtari911                file_put_contents($eventFile, json_encode($events, JSON_PRETTY_PRINT));
61519378907SAtari911                echo json_encode(['success' => true, 'events' => $events]);
61619378907SAtari911                return;
61719378907SAtari911            }
61819378907SAtari911        }
61919378907SAtari911
62019378907SAtari911        echo json_encode(['success' => false, 'error' => 'Event not found']);
62119378907SAtari911    }
62219378907SAtari911
62387ac9bf3SAtari911    private function createRecurringEvents($namespace, $startDate, $endDate, $title, $time,
62487ac9bf3SAtari911                                          $description, $color, $isTask, $recurrenceType,
62587ac9bf3SAtari911                                          $recurrenceEnd, $baseId) {
62687ac9bf3SAtari911        $dataDir = DOKU_INC . 'data/meta/';
62787ac9bf3SAtari911        if ($namespace) {
62887ac9bf3SAtari911            $dataDir .= str_replace(':', '/', $namespace) . '/';
62987ac9bf3SAtari911        }
63087ac9bf3SAtari911        $dataDir .= 'calendar/';
63187ac9bf3SAtari911
63287ac9bf3SAtari911        if (!is_dir($dataDir)) {
63387ac9bf3SAtari911            mkdir($dataDir, 0755, true);
63487ac9bf3SAtari911        }
63587ac9bf3SAtari911
63687ac9bf3SAtari911        // Calculate recurrence interval
63787ac9bf3SAtari911        $interval = '';
63887ac9bf3SAtari911        switch ($recurrenceType) {
63987ac9bf3SAtari911            case 'daily': $interval = '+1 day'; break;
64087ac9bf3SAtari911            case 'weekly': $interval = '+1 week'; break;
64187ac9bf3SAtari911            case 'monthly': $interval = '+1 month'; break;
64287ac9bf3SAtari911            case 'yearly': $interval = '+1 year'; break;
64387ac9bf3SAtari911            default: $interval = '+1 week';
64487ac9bf3SAtari911        }
64587ac9bf3SAtari911
64687ac9bf3SAtari911        // Set maximum end date if not specified (1 year from start)
64787ac9bf3SAtari911        $maxEnd = $recurrenceEnd ?: date('Y-m-d', strtotime($startDate . ' +1 year'));
64887ac9bf3SAtari911
64987ac9bf3SAtari911        // Calculate event duration for multi-day events
65087ac9bf3SAtari911        $eventDuration = 0;
65187ac9bf3SAtari911        if ($endDate && $endDate !== $startDate) {
65287ac9bf3SAtari911            $start = new DateTime($startDate);
65387ac9bf3SAtari911            $end = new DateTime($endDate);
65487ac9bf3SAtari911            $eventDuration = $start->diff($end)->days;
65587ac9bf3SAtari911        }
65687ac9bf3SAtari911
65787ac9bf3SAtari911        // Generate recurring events
65887ac9bf3SAtari911        $currentDate = new DateTime($startDate);
65987ac9bf3SAtari911        $endLimit = new DateTime($maxEnd);
66087ac9bf3SAtari911        $counter = 0;
66187ac9bf3SAtari911        $maxOccurrences = 100; // Prevent infinite loops
66287ac9bf3SAtari911
66387ac9bf3SAtari911        while ($currentDate <= $endLimit && $counter < $maxOccurrences) {
66487ac9bf3SAtari911            $dateKey = $currentDate->format('Y-m-d');
66587ac9bf3SAtari911            list($year, $month, $day) = explode('-', $dateKey);
66687ac9bf3SAtari911
66787ac9bf3SAtari911            // Calculate end date for this occurrence if multi-day
66887ac9bf3SAtari911            $occurrenceEndDate = '';
66987ac9bf3SAtari911            if ($eventDuration > 0) {
67087ac9bf3SAtari911                $occurrenceEnd = clone $currentDate;
67187ac9bf3SAtari911                $occurrenceEnd->modify('+' . $eventDuration . ' days');
67287ac9bf3SAtari911                $occurrenceEndDate = $occurrenceEnd->format('Y-m-d');
67387ac9bf3SAtari911            }
67487ac9bf3SAtari911
67587ac9bf3SAtari911            // Load month file
67687ac9bf3SAtari911            $eventFile = $dataDir . sprintf('%04d-%02d.json', $year, $month);
67787ac9bf3SAtari911            $events = [];
67887ac9bf3SAtari911            if (file_exists($eventFile)) {
67987ac9bf3SAtari911                $events = json_decode(file_get_contents($eventFile), true);
68087ac9bf3SAtari911            }
68187ac9bf3SAtari911
68287ac9bf3SAtari911            if (!isset($events[$dateKey])) {
68387ac9bf3SAtari911                $events[$dateKey] = [];
68487ac9bf3SAtari911            }
68587ac9bf3SAtari911
68687ac9bf3SAtari911            // Create event for this occurrence
68787ac9bf3SAtari911            $eventData = [
68887ac9bf3SAtari911                'id' => $baseId . '-' . $counter,
68987ac9bf3SAtari911                'title' => $title,
69087ac9bf3SAtari911                'time' => $time,
691*1d05cddcSAtari911                'endTime' => $endTime,
69287ac9bf3SAtari911                'description' => $description,
69387ac9bf3SAtari911                'color' => $color,
69487ac9bf3SAtari911                'isTask' => $isTask,
69587ac9bf3SAtari911                'completed' => false,
69687ac9bf3SAtari911                'endDate' => $occurrenceEndDate,
69787ac9bf3SAtari911                'recurring' => true,
69887ac9bf3SAtari911                'recurringId' => $baseId,
699*1d05cddcSAtari911                'namespace' => $namespace,  // Add namespace!
70087ac9bf3SAtari911                'created' => date('Y-m-d H:i:s')
70187ac9bf3SAtari911            ];
70287ac9bf3SAtari911
70387ac9bf3SAtari911            $events[$dateKey][] = $eventData;
70487ac9bf3SAtari911            file_put_contents($eventFile, json_encode($events, JSON_PRETTY_PRINT));
70587ac9bf3SAtari911
70687ac9bf3SAtari911            // Move to next occurrence
70787ac9bf3SAtari911            $currentDate->modify($interval);
70887ac9bf3SAtari911            $counter++;
70987ac9bf3SAtari911        }
71087ac9bf3SAtari911    }
71187ac9bf3SAtari911
71219378907SAtari911    public function addAssets(Doku_Event $event, $param) {
71319378907SAtari911        $event->data['link'][] = array(
71419378907SAtari911            'type' => 'text/css',
71519378907SAtari911            'rel' => 'stylesheet',
71619378907SAtari911            'href' => DOKU_BASE . 'lib/plugins/calendar/style.css'
71719378907SAtari911        );
71819378907SAtari911
71919378907SAtari911        $event->data['script'][] = array(
72019378907SAtari911            'type' => 'text/javascript',
72119378907SAtari911            'src' => DOKU_BASE . 'lib/plugins/calendar/script.js'
72219378907SAtari911        );
72319378907SAtari911    }
724e3a9f44cSAtari911    // Helper function to find an event's stored namespace
725e3a9f44cSAtari911    private function findEventNamespace($eventId, $date, $searchNamespace) {
726e3a9f44cSAtari911        list($year, $month, $day) = explode('-', $date);
727e3a9f44cSAtari911
728e3a9f44cSAtari911        // List of namespaces to check
729e3a9f44cSAtari911        $namespacesToCheck = [''];
730e3a9f44cSAtari911
731e3a9f44cSAtari911        // If searchNamespace is a wildcard or multi, we need to search multiple locations
732e3a9f44cSAtari911        if (!empty($searchNamespace)) {
733e3a9f44cSAtari911            if (strpos($searchNamespace, ';') !== false) {
734e3a9f44cSAtari911                // Multi-namespace - check each one
735e3a9f44cSAtari911                $namespacesToCheck = array_map('trim', explode(';', $searchNamespace));
736e3a9f44cSAtari911                $namespacesToCheck[] = ''; // Also check default
737e3a9f44cSAtari911            } elseif (strpos($searchNamespace, '*') !== false) {
738e3a9f44cSAtari911                // Wildcard - need to scan directories
739e3a9f44cSAtari911                $baseNs = trim(str_replace('*', '', $searchNamespace), ':');
740e3a9f44cSAtari911                $namespacesToCheck = $this->findAllNamespaces($baseNs);
741e3a9f44cSAtari911                $namespacesToCheck[] = ''; // Also check default
742e3a9f44cSAtari911            } else {
743e3a9f44cSAtari911                // Single namespace
744e3a9f44cSAtari911                $namespacesToCheck = [$searchNamespace, '']; // Check specified and default
745e3a9f44cSAtari911            }
746e3a9f44cSAtari911        }
747e3a9f44cSAtari911
748e3a9f44cSAtari911        // Search for the event in all possible namespaces
749e3a9f44cSAtari911        foreach ($namespacesToCheck as $ns) {
750e3a9f44cSAtari911            $dataDir = DOKU_INC . 'data/meta/';
751e3a9f44cSAtari911            if ($ns) {
752e3a9f44cSAtari911                $dataDir .= str_replace(':', '/', $ns) . '/';
753e3a9f44cSAtari911            }
754e3a9f44cSAtari911            $dataDir .= 'calendar/';
755e3a9f44cSAtari911
756e3a9f44cSAtari911            $eventFile = $dataDir . sprintf('%04d-%02d.json', $year, $month);
757e3a9f44cSAtari911
758e3a9f44cSAtari911            if (file_exists($eventFile)) {
759e3a9f44cSAtari911                $events = json_decode(file_get_contents($eventFile), true);
760e3a9f44cSAtari911                if (isset($events[$date])) {
761e3a9f44cSAtari911                    foreach ($events[$date] as $evt) {
762e3a9f44cSAtari911                        if ($evt['id'] === $eventId) {
763e3a9f44cSAtari911                            // Found the event! Return its stored namespace
764e3a9f44cSAtari911                            return isset($evt['namespace']) ? $evt['namespace'] : $ns;
765e3a9f44cSAtari911                        }
766e3a9f44cSAtari911                    }
767e3a9f44cSAtari911                }
768e3a9f44cSAtari911            }
769e3a9f44cSAtari911        }
770e3a9f44cSAtari911
771e3a9f44cSAtari911        return null; // Event not found
772e3a9f44cSAtari911    }
773e3a9f44cSAtari911
774e3a9f44cSAtari911    // Helper to find all namespaces under a base namespace
775e3a9f44cSAtari911    private function findAllNamespaces($baseNamespace) {
776e3a9f44cSAtari911        $dataDir = DOKU_INC . 'data/meta/';
777e3a9f44cSAtari911        if ($baseNamespace) {
778e3a9f44cSAtari911            $dataDir .= str_replace(':', '/', $baseNamespace) . '/';
779e3a9f44cSAtari911        }
780e3a9f44cSAtari911
781e3a9f44cSAtari911        $namespaces = [];
782e3a9f44cSAtari911        if ($baseNamespace) {
783e3a9f44cSAtari911            $namespaces[] = $baseNamespace;
784e3a9f44cSAtari911        }
785e3a9f44cSAtari911
786e3a9f44cSAtari911        $this->scanForNamespaces($dataDir, $baseNamespace, $namespaces);
787e3a9f44cSAtari911
788e3a9f44cSAtari911        return $namespaces;
789e3a9f44cSAtari911    }
790e3a9f44cSAtari911
791e3a9f44cSAtari911    // Recursive scan for namespaces
792e3a9f44cSAtari911    private function scanForNamespaces($dir, $baseNamespace, &$namespaces) {
793e3a9f44cSAtari911        if (!is_dir($dir)) return;
794e3a9f44cSAtari911
795e3a9f44cSAtari911        $items = scandir($dir);
796e3a9f44cSAtari911        foreach ($items as $item) {
797e3a9f44cSAtari911            if ($item === '.' || $item === '..' || $item === 'calendar') continue;
798e3a9f44cSAtari911
799e3a9f44cSAtari911            $path = $dir . $item;
800e3a9f44cSAtari911            if (is_dir($path)) {
801e3a9f44cSAtari911                $namespace = $baseNamespace ? $baseNamespace . ':' . $item : $item;
802e3a9f44cSAtari911                $namespaces[] = $namespace;
803e3a9f44cSAtari911                $this->scanForNamespaces($path . '/', $namespace, $namespaces);
804e3a9f44cSAtari911            }
805e3a9f44cSAtari911        }
806e3a9f44cSAtari911    }
80719378907SAtari911}
808