Lines Matching +full:security +full:- +full:events
5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
37 if ($this->auditLogger === null) {
38 $this->auditLogger = new CalendarAuditLogger();
40 return $this->auditLogger;
47 if ($this->googleSync === null) {
48 $this->googleSync = new GoogleCalendarSync();
50 return $this->googleSync;
84 $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handleAjax');
85 $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'addAssets');
89 if ($event->data !== 'plugin_calendar') return;
90 $event->preventDefault();
91 $event->stopPropagation();
103 // Rate limiting check - apply to all AJAX actions
127 // Check for valid security token - try multiple sources
128 $sectok = $INPUT->str('sectok', '');
133 // Use DokuWiki's built-in check
136 $this->debugLog("Security token check failed. Received: '$sectok'");
137 …echo json_encode(['success' => false, 'error' => 'Invalid security token. Please refresh the page …
144 $this->saveEvent();
147 $this->deleteEvent();
150 $this->getEvent();
153 $this->loadMonth();
156 $this->getStaticCalendar();
159 $this->searchAllDates();
162 $this->toggleTaskComplete();
165 $this->getGoogleAuthUrl();
168 $this->handleGoogleCallback();
171 $this->getGoogleStatus();
174 $this->getGoogleCalendars();
177 $this->googleImport();
180 $this->googleExport();
183 $this->googleDisconnect();
194 $this->routeToAdmin($action);
207 $admin->handleAjaxAction($action);
216 $namespace = $INPUT->str('namespace', '');
217 $date = $INPUT->str('date');
218 $eventId = $INPUT->str('eventId', '');
219 $title = $INPUT->str('title');
220 $time = $INPUT->str('time', '');
221 $endTime = $INPUT->str('endTime', '');
222 $description = $INPUT->str('description', '');
223 $color = $INPUT->str('color', '#3498db');
224 $oldDate = $INPUT->str('oldDate', ''); // Track original date for moves
225 $isTask = $INPUT->bool('isTask', false);
226 $completed = $INPUT->bool('completed', false);
227 $endDate = $INPUT->str('endDate', '');
228 $isRecurring = $INPUT->bool('isRecurring', false);
229 $recurrenceType = $INPUT->str('recurrenceType', 'weekly');
230 $recurrenceEnd = $INPUT->str('recurrenceEnd', '');
233 $recurrenceInterval = $INPUT->int('recurrenceInterval', 1);
237 $weekDaysStr = $INPUT->str('weekDays', '');
240 $monthlyType = $INPUT->str('monthlyType', 'dayOfMonth');
241 $monthDay = $INPUT->int('monthDay', 0);
242 $ordinalWeek = $INPUT->int('ordinalWeek', 1);
243 $ordinalDay = $INPUT->int('ordinalDay', 0);
245 $this->debugLog("=== Calendar saveEvent START ===");
246 …$this->debugLog("Calendar saveEvent: INPUT namespace='$namespace', eventId='$eventId', date='$date…
247 …$this->debugLog("Calendar saveEvent: Recurrence - type='$recurrenceType', interval=$recurrenceInte…
254 // Validate date format (YYYY-MM-DD)
255 if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date) || !strtotime($date)) {
261 if ($oldDate && (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $oldDate) || !strtotime($oldDate))) {
267 if ($endDate && (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $endDate) || !strtotime($endDate))) {
285 if (!preg_match('/^#[0-9A-Fa-f]{6}$/', $color)) {
290 if ($namespace && !preg_match('/^[a-zA-Z0-9_:;*-]*$/', $namespace)) {
302 …if ($recurrenceEnd && (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $recurrenceEnd) || !strtotime($recurre…
321 $foundNamespace = $this->findEventNamespace($eventId, $searchDate, '*');
325 … $this->debugLog("Calendar saveEvent: Found existing event in namespace '$oldNamespace'");
327 $this->debugLog("Calendar saveEvent: Event $eventId not found in any namespace");
332 // But normalize wildcards and multi-namespace to empty for NEW events
334 $this->debugLog("Calendar saveEvent: NEW event, received namespace='$namespace'");
335 … // Normalize namespace: treat wildcards and multi-namespace as empty (default) for NEW events
337 … $this->debugLog("Calendar saveEvent: Namespace contains wildcard/multi, clearing to empty");
340 $this->debugLog("Calendar saveEvent: Namespace is clean, keeping as '$namespace'");
343 …$this->debugLog("Calendar saveEvent: EDITING event $eventId, user selected namespace='$namespace'"…
354 …$existingEventData = $this->getExistingEventData($eventId, $searchDate, $oldNamespace ?? $namespac…
356 …$this->debugLog("Calendar saveEvent recurring: Loaded existing data - namespace='" . ($existingEve…
360 // If recurring, generate multiple events
381 …$this->debugLog("Calendar saveEvent recurring: Preserving namespace '$namespace' (received='$recei…
383 …$this->debugLog("Calendar saveEvent recurring: No existing namespace to preserve (received='$recei…
386 …$this->debugLog("Calendar saveEvent recurring: Using new namespace '$namespace' (received='$receiv…
389 …$this->debugLog("Calendar saveEvent recurring: No existing data found, using namespace='$namespace…
392 … $this->createRecurringEvents($namespace, $date, $endDate, $title, $time, $endTime, $description,
399 list($year, $month, $day) = explode('-', $date);
412 $eventFile = $dataDir . sprintf('%04d-%02d.json', $year, $month);
414 $this->debugLog("Calendar saveEvent: NEW eventFile='$eventFile'");
416 $events = [];
418 $events = json_decode(file_get_contents($eventFile), true);
419 … $this->debugLog("Calendar saveEvent: Loaded " . count($events) . " dates from new location");
421 $this->debugLog("Calendar saveEvent: New location file does not exist yet");
429 …$this->debugLog("Calendar saveEvent: eventId='$eventId', oldNamespace=" . var_export($oldNamespace…
440 list($oldYear, $oldMonth, $oldDay) = explode('-', $deleteDate);
441 $oldEventFile = $oldDataDir . sprintf('%04d-%02d.json', $oldYear, $oldMonth);
443 …$this->debugLog("Calendar saveEvent: Attempting to delete from OLD eventFile='$oldEventFile', dele…
447 … $this->debugLog("Calendar saveEvent: OLD file exists, has " . count($oldEvents) . " dates");
456 … $this->debugLog("Calendar saveEvent: Events on date before=$countBefore, after=$countAfter");
463 …$this->debugLog("Calendar saveEvent: DELETED event from old location - namespace:'$oldNamespace', …
465 … $this->debugLog("Calendar saveEvent: No events found on deleteDate='$deleteDate' in old file");
468 $this->debugLog("Calendar saveEvent: OLD file does NOT exist: $oldEventFile");
471 …$this->debugLog("Calendar saveEvent: No namespace/date change detected, skipping deletion from old…
474 if (!isset($events[$date])) {
475 $events[$date] = [];
476 } elseif (!is_array($events[$date])) {
477 // Fix corrupted data - ensure it's an array
478 … $this->debugLog("Calendar saveEvent: Fixing corrupted data at $date - was not an array");
479 $events[$date] = [];
494 'created' => date('Y-m-d H:i:s')
498 …$this->debugLog("Calendar saveEvent: Saving event '$title' with namespace='$namespace' to file $ev…
503 foreach ($events[$date] as $key => $evt) {
505 $events[$date][$key] = $eventData;
511 $events[$date][] = $eventData;
514 $events[$date][] = $eventData;
517 CalendarFileHandler::writeJson($eventFile, $events);
525 $startMonth = $startDateObj->format('Y-m');
529 $currentDate->modify('first day of next month'); // Jump to first of next month
532 $currentMonth = $currentDate->format('Y-m');
533 $firstDayOfMonth = $currentDate->format('Y-m-01');
535 list($currentYear, $currentMonthNum, $currentDay) = explode('-', $firstDayOfMonth);
538 … $currentEventFile = $dataDir . sprintf('%04d-%02d.json', $currentYear, $currentMonthNum);
547 …$this->debugLog("Calendar saveEvent: JSON decode error in $currentEventFile: " . json_last_error_m…
555 // Fix corrupted data - ensure it's an array
556 …$this->debugLog("Calendar saveEvent: Fixing corrupted data at $firstDayOfMonth - was not an array"…
583 $currentDate->modify('first day of next month');
588 $audit = $this->getAuditLogger();
591 $audit->logMove($namespace, $oldDate ?: $date, $date, $generatedId, $title);
594 $audit->logUpdate($namespace, $date, $generatedId, $title);
597 $audit->logCreate($namespace, $date, $generatedId, $title);
600 echo json_encode(['success' => true, 'events' => $events, 'eventId' => $eventData['id']]);
606 $namespace = $INPUT->str('namespace', '');
607 $date = $INPUT->str('date');
608 $eventId = $INPUT->str('eventId');
611 $storedNamespace = $this->findEventNamespace($eventId, $date, $namespace);
621 list($year, $month, $day) = explode('-', $date);
629 $eventFile = $dataDir . sprintf('%04d-%02d.json', $year, $month);
637 $events = json_decode(file_get_contents($eventFile), true);
639 if (isset($events[$date])) {
640 foreach ($events[$date] as $event) {
649 … $events[$date] = array_values(array_filter($events[$date], function($event) use ($eventId) {
653 if (empty($events[$date])) {
654 unset($events[$date]);
657 CalendarFileHandler::writeJson($eventFile, $events);
663 $this->deleteAllRecurringInstances($recurringId, $namespace, $dataDir);
673 $currentDate->modify('first day of next month'); // Jump to first of next month
676 $firstDayOfMonth = $currentDate->format('Y-m-01');
677 list($currentYear, $currentMonth, $currentDay) = explode('-', $firstDayOfMonth);
680 … $currentEventFile = $dataDir . sprintf('%04d-%02d.json', $currentYear, $currentMonth);
699 $currentDate->modify('first day of next month');
704 $audit = $this->getAuditLogger();
706 $audit->logDelete($namespace, $date, $eventId, $eventTitle);
714 $namespace = $INPUT->str('namespace', '');
715 $date = $INPUT->str('date');
716 $eventId = $INPUT->str('eventId');
719 $storedNamespace = $this->findEventNamespace($eventId, $date, $namespace);
729 list($year, $month, $day) = explode('-', $date);
737 $eventFile = $dataDir . sprintf('%04d-%02d.json', $year, $month);
740 $events = json_decode(file_get_contents($eventFile), true);
742 if (isset($events[$date])) {
743 foreach ($events[$date] as $event) {
761 header('Cache-Control: no-cache, no-store, must-revalidate');
762 header('Pragma: no-cache');
765 $namespace = $INPUT->str('namespace', '');
766 $year = $INPUT->int('year');
767 $month = $INPUT->int('month');
769 // Validate year (reasonable range: 1970-2100)
774 // Validate month (1-12)
780 if ($namespace && !preg_match('/^[a-zA-Z0-9_:;*-]*$/', $namespace)) {
785 $this->debugLog("=== Calendar loadMonth DEBUG ===");
786 $this->debugLog("Requested: year=$year, month=$month, namespace='$namespace'");
788 // Check if multi-namespace or wildcard
791 $this->debugLog("isMultiNamespace: " . ($isMultiNamespace ? 'true' : 'false'));
794 $events = $this->loadEventsMultiNamespace($namespace, $year, $month);
796 $events = $this->loadEventsSingleNamespace($namespace, $year, $month);
799 $this->debugLog("Returning " . count($events) . " date keys");
800 foreach ($events as $dateKey => $dayEvents) {
801 $this->debugLog(" dateKey=$dateKey has " . count($dayEvents) . " events");
808 'events' => $events
818 $namespace = $INPUT->str('namespace', '');
819 $year = $INPUT->int('year');
820 $month = $INPUT->int('month');
847 $method = $reflector->getMethod('renderStaticCalendar');
848 $method->setAccessible(true);
849 $html = $method->invoke($syntax, $data);
865 $eventFile = $dataDir . sprintf('%04d-%02d.json', $year, $month);
866 $events = [];
871 $events = $decoded;
875 return $events;
882 return $this->loadEventsWildcard($baseNamespace, $year, $month);
887 return $this->loadEventsWildcard('', $year, $month);
893 // Load events from all namespaces
899 $events = $this->loadEventsSingleNamespace($ns, $year, $month);
902 foreach ($events as $dateKey => $dayEvents) {
924 // First, load events from the base namespace itself
925 $events = $this->loadEventsSingleNamespace($baseNamespace, $year, $month);
927 foreach ($events as $dateKey => $dayEvents) {
938 $this->findSubNamespaces($dataDir, $baseNamespace, $year, $month, $allEvents);
955 // Load events from this namespace
956 $events = $this->loadEventsSingleNamespace($namespace, $year, $month);
957 foreach ($events as $dateKey => $dayEvents) {
968 $this->findSubNamespaces($path . '/', $namespace, $year, $month, $allEvents);
974 * Search all dates for events matching the search term
979 $searchTerm = strtolower(trim($INPUT->str('search', '')));
980 $namespace = $INPUT->str('namespace', '');
988 $normalizedSearch = $this->normalizeForSearch($searchTerm);
1002 // Skip non-date keys
1003 if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $dateKey)) continue;
1016 $normalizedText = $this->normalizeForSearch($searchableText);
1019 if ($this->fuzzyMatchText($normalizedText, $normalizedSearch)) {
1039 $this->searchNamespaceDirs($dataDir, $searchCalendarDir);
1058 * Supports multi-word search where all words must be present
1066 // Multi-word search: all words must be present
1088 // Remove apostrophes and quotes (father's -> fathers)
1092 $text = preg_replace('/[-_\x{2013}\x{2014}]/u', ' ', $text);
1122 $this->searchNamespaceDirs($nsDir . '/', $callback);
1129 $namespace = $INPUT->str('namespace', '');
1130 $date = $INPUT->str('date');
1131 $eventId = $INPUT->str('eventId');
1132 $completed = $INPUT->bool('completed', false);
1135 $storedNamespace = $this->findEventNamespace($eventId, $date, $namespace);
1145 list($year, $month, $day) = explode('-', $date);
1153 $eventFile = $dataDir . sprintf('%04d-%02d.json', $year, $month);
1156 $events = json_decode(file_get_contents($eventFile), true);
1158 if (isset($events[$date])) {
1160 foreach ($events[$date] as $key => $event) {
1162 $events[$date][$key]['completed'] = $completed;
1168 CalendarFileHandler::writeJson($eventFile, $events);
1171 $audit = $this->getAuditLogger();
1172 $audit->logTaskToggle($namespace, $date, $eventId, $eventTitle, $completed);
1174 echo json_encode(['success' => true, 'events' => $events]);
1195 $sync = $this->getGoogleSync();
1197 if (!$sync->isConfigured()) {
1205 $authUrl = $sync->getAuthUrl($redirectUri);
1216 $code = $INPUT->str('code');
1217 $state = $INPUT->str('state');
1218 $error = $INPUT->str('error');
1222 $this->showGoogleCallbackResult(false, 'Authorization denied: ' . $error);
1227 $this->showGoogleCallbackResult(false, 'No authorization code received');
1231 $sync = $this->getGoogleSync();
1234 if (!$sync->verifyState($state)) {
1235 $this->showGoogleCallbackResult(false, 'Invalid state parameter');
1241 $result = $sync->handleCallback($code, $redirectUri);
1244 $this->showGoogleCallbackResult(true, 'Successfully connected to Google Calendar!');
1246 $this->showGoogleCallbackResult(false, $result['error']);
1260 <title>Google Calendar - ' . $status . '</title>
1262 body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
1263 display: flex; align-items: center; justify-content: center;
1264 min-height: 100vh; margin: 0; background: #f5f5f5; }
1265 .card { background: white; padding: 40px; border-radius: 12px;
1266 box-shadow: 0 4px 20px rgba(0,0,0,0.1); text-align: center; max-width: 400px; }
1270 border-radius: 6px; cursor: pointer; font-size: 14px; }
1294 $sync = $this->getGoogleSync();
1295 echo json_encode(['success' => true, 'status' => $sync->getStatus()]);
1307 $sync = $this->getGoogleSync();
1308 $result = $sync->getCalendars();
1313 * Import events from Google Calendar
1323 $namespace = $INPUT->str('namespace', '');
1324 $startDate = $INPUT->str('startDate', '');
1325 $endDate = $INPUT->str('endDate', '');
1327 $sync = $this->getGoogleSync();
1328 $result = $sync->importEvents($namespace, $startDate ?: null, $endDate ?: null);
1334 * Export events to Google Calendar
1344 $namespace = $INPUT->str('namespace', '');
1345 $startDate = $INPUT->str('startDate', '');
1346 $endDate = $INPUT->str('endDate', '');
1348 $sync = $this->getGoogleSync();
1349 $result = $sync->exportEvents($namespace, $startDate ?: null, $endDate ?: null);
1363 $sync = $this->getGoogleSync();
1364 $sync->disconnect();
1387 $maxEnd = $recurrenceEnd ?: date('Y-m-d', strtotime($startDate . ' +1 year'));
1389 // Calculate event duration for multi-day events
1394 $eventDuration = $start->diff($end)->days;
1397 // Generate recurring events
1405 $startWeekNumber = (int)$currentDate->format('W');
1406 $startYear = (int)$currentDate->format('Y');
1414 $daysSinceStart = $currentDate->diff(new DateTime($startDate))->days;
1420 $currentDayOfWeek = (int)$currentDate->format('w'); // 0=Sun, 6=Sat
1423 $daysSinceStart = $currentDate->diff(new DateTime($startDate))->days;
1441 … $monthsSinceStart = (($currentDate->format('Y') - $startDT->format('Y')) * 12) +
1442 ($currentDate->format('n') - $startDT->format('n'));
1449 $currentDate->modify('first day of next month');
1455 $targetDay = $monthDay ?: (int)(new DateTime($startDate))->format('j');
1456 $currentDay = (int)$currentDate->format('j');
1457 $daysInMonth = (int)$currentDate->format('t');
1464 … $shouldCreateEvent = $this->isOrdinalWeekday($currentDate, $ordinalWeek, $ordinalDay);
1471 $yearsSinceStart = (int)$currentDate->format('Y') - (int)$startDT->format('Y');
1477 $sameMonthDay = ($currentDate->format('m-d') === $startDT->format('m-d'));
1487 $dateKey = $currentDate->format('Y-m-d');
1488 list($year, $month, $day) = explode('-', $dateKey);
1490 // Calculate end date for this occurrence if multi-day
1494 $occurrenceEnd->modify('+' . $eventDuration . ' days');
1495 $occurrenceEndDate = $occurrenceEnd->format('Y-m-d');
1499 $eventFile = $dataDir . sprintf('%04d-%02d.json', $year, $month);
1500 $events = [];
1502 $events = json_decode(file_get_contents($eventFile), true);
1503 if (!is_array($events)) $events = [];
1506 if (!isset($events[$dateKey])) {
1507 $events[$dateKey] = [];
1512 'id' => $baseId . '-' . $counter,
1526 'created' => date('Y-m-d H:i:s')
1543 $events[$dateKey][] = $eventData;
1544 CalendarFileHandler::writeJson($eventFile, $events);
1550 $currentDate->modify('+1 day');
1557 * @param int $ordinalWeek 1-5 for first-fifth, -1 for last
1562 $currentDayOfWeek = (int)$date->format('w');
1569 $dayOfMonth = (int)$date->format('j');
1570 $daysInMonth = (int)$date->format('t');
1572 if ($ordinalWeek === -1) {
1574 $daysRemaining = $daysInMonth - $dayOfMonth;
1584 $event->data['link'][] = array(
1591 // Note: script.js is intentionally empty to avoid DokuWiki's auto-concatenation issues
1592 // The actual code is in calendar-main.js
1593 $event->data['script'][] = array(
1595 'src' => DOKU_BASE . 'lib/plugins/calendar/calendar-main.js'
1600 list($year, $month, $day) = explode('-', $date);
1608 // Multi-namespace - check each one
1612 // Wildcard - need to scan directories
1614 $namespacesToCheck = $this->findAllNamespaces($baseNs);
1622 …$this->debugLog("findEventNamespace: Looking for eventId='$eventId' on date='$date' in namespaces:…
1632 $eventFile = $dataDir . sprintf('%04d-%02d.json', $year, $month);
1635 $events = json_decode(file_get_contents($eventFile), true);
1636 if (isset($events[$date])) {
1637 foreach ($events[$date] as $evt) {
1640 … // The directory is what matters for deletion - that's where the file actually is
1641 …$this->debugLog("findEventNamespace: FOUND event in file=$eventFile (dir namespace='$ns', stored n…
1649 $this->debugLog("findEventNamespace: Event NOT FOUND in any namespace");
1665 $this->scanForNamespaces($dataDir, $baseNamespace, $namespaces);
1667 …$this->debugLog("findAllNamespaces: baseNamespace='$baseNamespace', found " . count($namespaces) .…
1684 $this->scanForNamespaces($path . '/', $namespace, $namespaces);
1698 $events = json_decode(file_get_contents($file), true);
1700 if (!$events) continue;
1703 foreach ($events as $date => &$dayEvents) {
1704 // Filter out events with matching recurringId
1717 unset($events[$date]);
1723 CalendarFileHandler::writeJson($file, $events);
1732 list($year, $month, $day) = explode('-', $date);
1740 $eventFile = $dataDir . sprintf('%04d-%02d.json', $year, $month);
1746 $events = json_decode(file_get_contents($eventFile), true);
1748 if (!isset($events[$date])) {
1753 foreach ($events[$date] as $event) {