1*815440faSAtari911<?php 2*815440faSAtari911/** 3*815440faSAtari911 * Calendar Plugin - Event Manager 4*815440faSAtari911 * 5*815440faSAtari911 * Consolidates event CRUD operations with proper file locking and caching. 6*815440faSAtari911 * This class is the single point of entry for all event data operations. 7*815440faSAtari911 * 8*815440faSAtari911 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 9*815440faSAtari911 * @author DokuWiki Community 10*815440faSAtari911 * @version 7.0.8 11*815440faSAtari911 */ 12*815440faSAtari911 13*815440faSAtari911if (!defined('DOKU_INC')) die(); 14*815440faSAtari911 15*815440faSAtari911// Require dependencies 16*815440faSAtari911require_once __DIR__ . '/FileHandler.php'; 17*815440faSAtari911require_once __DIR__ . '/EventCache.php'; 18*815440faSAtari911 19*815440faSAtari911class CalendarEventManager { 20*815440faSAtari911 21*815440faSAtari911 /** @var string Base data directory */ 22*815440faSAtari911 private static $baseDir = null; 23*815440faSAtari911 24*815440faSAtari911 /** 25*815440faSAtari911 * Get the base calendar data directory 26*815440faSAtari911 * 27*815440faSAtari911 * @return string Base directory path 28*815440faSAtari911 */ 29*815440faSAtari911 private static function getBaseDir() { 30*815440faSAtari911 if (self::$baseDir === null) { 31*815440faSAtari911 self::$baseDir = DOKU_INC . 'data/meta/'; 32*815440faSAtari911 } 33*815440faSAtari911 return self::$baseDir; 34*815440faSAtari911 } 35*815440faSAtari911 36*815440faSAtari911 /** 37*815440faSAtari911 * Get the data directory for a namespace 38*815440faSAtari911 * 39*815440faSAtari911 * @param string $namespace Namespace (empty for default) 40*815440faSAtari911 * @return string Directory path 41*815440faSAtari911 */ 42*815440faSAtari911 private static function getNamespaceDir($namespace = '') { 43*815440faSAtari911 $dir = self::getBaseDir(); 44*815440faSAtari911 if ($namespace) { 45*815440faSAtari911 $dir .= str_replace(':', '/', $namespace) . '/'; 46*815440faSAtari911 } 47*815440faSAtari911 $dir .= 'calendar/'; 48*815440faSAtari911 return $dir; 49*815440faSAtari911 } 50*815440faSAtari911 51*815440faSAtari911 /** 52*815440faSAtari911 * Get the event file path for a specific month 53*815440faSAtari911 * 54*815440faSAtari911 * @param string $namespace Namespace 55*815440faSAtari911 * @param int $year Year 56*815440faSAtari911 * @param int $month Month 57*815440faSAtari911 * @return string File path 58*815440faSAtari911 */ 59*815440faSAtari911 private static function getEventFile($namespace, $year, $month) { 60*815440faSAtari911 $dir = self::getNamespaceDir($namespace); 61*815440faSAtari911 return $dir . sprintf('%04d-%02d.json', $year, $month); 62*815440faSAtari911 } 63*815440faSAtari911 64*815440faSAtari911 /** 65*815440faSAtari911 * Load events for a specific month 66*815440faSAtari911 * 67*815440faSAtari911 * @param string $namespace Namespace filter 68*815440faSAtari911 * @param int $year Year 69*815440faSAtari911 * @param int $month Month 70*815440faSAtari911 * @param bool $useCache Whether to use caching 71*815440faSAtari911 * @return array Events indexed by date 72*815440faSAtari911 */ 73*815440faSAtari911 public static function loadMonth($namespace, $year, $month, $useCache = true) { 74*815440faSAtari911 // Check cache first 75*815440faSAtari911 if ($useCache) { 76*815440faSAtari911 $cached = CalendarEventCache::getMonthEvents($namespace, $year, $month); 77*815440faSAtari911 if ($cached !== null) { 78*815440faSAtari911 return $cached; 79*815440faSAtari911 } 80*815440faSAtari911 } 81*815440faSAtari911 82*815440faSAtari911 $events = []; 83*815440faSAtari911 84*815440faSAtari911 // Handle wildcards and multiple namespaces 85*815440faSAtari911 if (strpos($namespace, '*') !== false || strpos($namespace, ';') !== false) { 86*815440faSAtari911 $events = self::loadMonthMultiNamespace($namespace, $year, $month); 87*815440faSAtari911 } else { 88*815440faSAtari911 $eventFile = self::getEventFile($namespace, $year, $month); 89*815440faSAtari911 $events = CalendarFileHandler::readJson($eventFile); 90*815440faSAtari911 } 91*815440faSAtari911 92*815440faSAtari911 // Store in cache 93*815440faSAtari911 if ($useCache) { 94*815440faSAtari911 CalendarEventCache::setMonthEvents($namespace, $year, $month, $events); 95*815440faSAtari911 } 96*815440faSAtari911 97*815440faSAtari911 return $events; 98*815440faSAtari911 } 99*815440faSAtari911 100*815440faSAtari911 /** 101*815440faSAtari911 * Load events from multiple namespaces 102*815440faSAtari911 * 103*815440faSAtari911 * @param string $namespacePattern Namespace pattern (with * or ;) 104*815440faSAtari911 * @param int $year Year 105*815440faSAtari911 * @param int $month Month 106*815440faSAtari911 * @return array Merged events indexed by date 107*815440faSAtari911 */ 108*815440faSAtari911 private static function loadMonthMultiNamespace($namespacePattern, $year, $month) { 109*815440faSAtari911 $allEvents = []; 110*815440faSAtari911 $namespaces = self::expandNamespacePattern($namespacePattern); 111*815440faSAtari911 112*815440faSAtari911 foreach ($namespaces as $ns) { 113*815440faSAtari911 $eventFile = self::getEventFile($ns, $year, $month); 114*815440faSAtari911 $events = CalendarFileHandler::readJson($eventFile); 115*815440faSAtari911 116*815440faSAtari911 foreach ($events as $date => $dateEvents) { 117*815440faSAtari911 if (!isset($allEvents[$date])) { 118*815440faSAtari911 $allEvents[$date] = []; 119*815440faSAtari911 } 120*815440faSAtari911 foreach ($dateEvents as $event) { 121*815440faSAtari911 // Ensure namespace is set 122*815440faSAtari911 if (!isset($event['namespace'])) { 123*815440faSAtari911 $event['namespace'] = $ns; 124*815440faSAtari911 } 125*815440faSAtari911 $allEvents[$date][] = $event; 126*815440faSAtari911 } 127*815440faSAtari911 } 128*815440faSAtari911 } 129*815440faSAtari911 130*815440faSAtari911 return $allEvents; 131*815440faSAtari911 } 132*815440faSAtari911 133*815440faSAtari911 /** 134*815440faSAtari911 * Expand namespace pattern to list of namespaces 135*815440faSAtari911 * 136*815440faSAtari911 * @param string $pattern Namespace pattern 137*815440faSAtari911 * @return array List of namespace paths 138*815440faSAtari911 */ 139*815440faSAtari911 private static function expandNamespacePattern($pattern) { 140*815440faSAtari911 $namespaces = []; 141*815440faSAtari911 142*815440faSAtari911 // Handle semicolon-separated namespaces 143*815440faSAtari911 if (strpos($pattern, ';') !== false) { 144*815440faSAtari911 $parts = explode(';', $pattern); 145*815440faSAtari911 foreach ($parts as $part) { 146*815440faSAtari911 $expanded = self::expandNamespacePattern(trim($part)); 147*815440faSAtari911 $namespaces = array_merge($namespaces, $expanded); 148*815440faSAtari911 } 149*815440faSAtari911 return array_unique($namespaces); 150*815440faSAtari911 } 151*815440faSAtari911 152*815440faSAtari911 // Handle wildcard 153*815440faSAtari911 if (strpos($pattern, '*') !== false) { 154*815440faSAtari911 // Get base directory 155*815440faSAtari911 $basePattern = str_replace('*', '', $pattern); 156*815440faSAtari911 $basePattern = rtrim($basePattern, ':'); 157*815440faSAtari911 158*815440faSAtari911 $searchDir = self::getBaseDir(); 159*815440faSAtari911 if ($basePattern) { 160*815440faSAtari911 $searchDir .= str_replace(':', '/', $basePattern) . '/'; 161*815440faSAtari911 } 162*815440faSAtari911 163*815440faSAtari911 // Always include the base namespace 164*815440faSAtari911 $namespaces[] = $basePattern; 165*815440faSAtari911 166*815440faSAtari911 // Find subdirectories with calendar data 167*815440faSAtari911 if (is_dir($searchDir)) { 168*815440faSAtari911 $iterator = new RecursiveIteratorIterator( 169*815440faSAtari911 new RecursiveDirectoryIterator($searchDir, RecursiveDirectoryIterator::SKIP_DOTS), 170*815440faSAtari911 RecursiveIteratorIterator::SELF_FIRST 171*815440faSAtari911 ); 172*815440faSAtari911 173*815440faSAtari911 foreach ($iterator as $file) { 174*815440faSAtari911 if ($file->isDir() && $file->getFilename() === 'calendar') { 175*815440faSAtari911 // Extract namespace from path 176*815440faSAtari911 $path = dirname($file->getPathname()); 177*815440faSAtari911 $relPath = str_replace(self::getBaseDir(), '', $path); 178*815440faSAtari911 $ns = str_replace('/', ':', trim($relPath, '/')); 179*815440faSAtari911 if ($ns && !in_array($ns, $namespaces)) { 180*815440faSAtari911 $namespaces[] = $ns; 181*815440faSAtari911 } 182*815440faSAtari911 } 183*815440faSAtari911 } 184*815440faSAtari911 } 185*815440faSAtari911 186*815440faSAtari911 return $namespaces; 187*815440faSAtari911 } 188*815440faSAtari911 189*815440faSAtari911 // Simple namespace 190*815440faSAtari911 return [$pattern]; 191*815440faSAtari911 } 192*815440faSAtari911 193*815440faSAtari911 /** 194*815440faSAtari911 * Save an event 195*815440faSAtari911 * 196*815440faSAtari911 * @param array $eventData Event data 197*815440faSAtari911 * @param string|null $oldDate Previous date (for moves) 198*815440faSAtari911 * @param string|null $oldNamespace Previous namespace (for moves) 199*815440faSAtari911 * @return array Result with success status and event data 200*815440faSAtari911 */ 201*815440faSAtari911 public static function saveEvent(array $eventData, $oldDate = null, $oldNamespace = null) { 202*815440faSAtari911 // Validate required fields 203*815440faSAtari911 if (empty($eventData['date']) || empty($eventData['title'])) { 204*815440faSAtari911 return ['success' => false, 'error' => 'Missing required fields']; 205*815440faSAtari911 } 206*815440faSAtari911 207*815440faSAtari911 $date = $eventData['date']; 208*815440faSAtari911 $namespace = $eventData['namespace'] ?? ''; 209*815440faSAtari911 $eventId = $eventData['id'] ?? uniqid(); 210*815440faSAtari911 211*815440faSAtari911 // Parse date 212*815440faSAtari911 if (!preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $date, $matches)) { 213*815440faSAtari911 return ['success' => false, 'error' => 'Invalid date format']; 214*815440faSAtari911 } 215*815440faSAtari911 list(, $year, $month, $day) = $matches; 216*815440faSAtari911 $year = (int)$year; 217*815440faSAtari911 $month = (int)$month; 218*815440faSAtari911 219*815440faSAtari911 // Ensure ID is set 220*815440faSAtari911 $eventData['id'] = $eventId; 221*815440faSAtari911 222*815440faSAtari911 // Set created timestamp if new 223*815440faSAtari911 if (!isset($eventData['created'])) { 224*815440faSAtari911 $eventData['created'] = date('Y-m-d H:i:s'); 225*815440faSAtari911 } 226*815440faSAtari911 227*815440faSAtari911 // Handle event move (different date or namespace) 228*815440faSAtari911 $dateChanged = $oldDate && $oldDate !== $date; 229*815440faSAtari911 $namespaceChanged = $oldNamespace !== null && $oldNamespace !== $namespace; 230*815440faSAtari911 231*815440faSAtari911 if ($dateChanged || $namespaceChanged) { 232*815440faSAtari911 // Delete from old location 233*815440faSAtari911 if (!preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $oldDate ?: $date, $oldMatches)) { 234*815440faSAtari911 return ['success' => false, 'error' => 'Invalid old date format']; 235*815440faSAtari911 } 236*815440faSAtari911 list(, $oldYear, $oldMonth, $oldDay) = $oldMatches; 237*815440faSAtari911 238*815440faSAtari911 $oldEventFile = self::getEventFile($oldNamespace ?? $namespace, (int)$oldYear, (int)$oldMonth); 239*815440faSAtari911 $oldEvents = CalendarFileHandler::readJson($oldEventFile); 240*815440faSAtari911 241*815440faSAtari911 $deleteDate = $oldDate ?: $date; 242*815440faSAtari911 if (isset($oldEvents[$deleteDate])) { 243*815440faSAtari911 $oldEvents[$deleteDate] = array_values(array_filter( 244*815440faSAtari911 $oldEvents[$deleteDate], 245*815440faSAtari911 function($evt) use ($eventId) { 246*815440faSAtari911 return $evt['id'] !== $eventId; 247*815440faSAtari911 } 248*815440faSAtari911 )); 249*815440faSAtari911 250*815440faSAtari911 if (empty($oldEvents[$deleteDate])) { 251*815440faSAtari911 unset($oldEvents[$deleteDate]); 252*815440faSAtari911 } 253*815440faSAtari911 254*815440faSAtari911 CalendarFileHandler::writeJson($oldEventFile, $oldEvents); 255*815440faSAtari911 256*815440faSAtari911 // Invalidate old location cache 257*815440faSAtari911 CalendarEventCache::invalidateMonth($oldNamespace ?? $namespace, (int)$oldYear, (int)$oldMonth); 258*815440faSAtari911 } 259*815440faSAtari911 } 260*815440faSAtari911 261*815440faSAtari911 // Load current events 262*815440faSAtari911 $eventFile = self::getEventFile($namespace, $year, $month); 263*815440faSAtari911 $events = CalendarFileHandler::readJson($eventFile); 264*815440faSAtari911 265*815440faSAtari911 // Ensure date array exists 266*815440faSAtari911 if (!isset($events[$date]) || !is_array($events[$date])) { 267*815440faSAtari911 $events[$date] = []; 268*815440faSAtari911 } 269*815440faSAtari911 270*815440faSAtari911 // Update or add event 271*815440faSAtari911 $found = false; 272*815440faSAtari911 foreach ($events[$date] as $key => $evt) { 273*815440faSAtari911 if ($evt['id'] === $eventId) { 274*815440faSAtari911 $events[$date][$key] = $eventData; 275*815440faSAtari911 $found = true; 276*815440faSAtari911 break; 277*815440faSAtari911 } 278*815440faSAtari911 } 279*815440faSAtari911 280*815440faSAtari911 if (!$found) { 281*815440faSAtari911 $events[$date][] = $eventData; 282*815440faSAtari911 } 283*815440faSAtari911 284*815440faSAtari911 // Save with atomic write 285*815440faSAtari911 if (!CalendarFileHandler::writeJson($eventFile, $events)) { 286*815440faSAtari911 return ['success' => false, 'error' => 'Failed to save event']; 287*815440faSAtari911 } 288*815440faSAtari911 289*815440faSAtari911 // Invalidate cache 290*815440faSAtari911 CalendarEventCache::invalidateMonth($namespace, $year, $month); 291*815440faSAtari911 292*815440faSAtari911 return ['success' => true, 'event' => $eventData]; 293*815440faSAtari911 } 294*815440faSAtari911 295*815440faSAtari911 /** 296*815440faSAtari911 * Delete an event 297*815440faSAtari911 * 298*815440faSAtari911 * @param string $eventId Event ID 299*815440faSAtari911 * @param string $date Event date 300*815440faSAtari911 * @param string $namespace Namespace 301*815440faSAtari911 * @return array Result with success status 302*815440faSAtari911 */ 303*815440faSAtari911 public static function deleteEvent($eventId, $date, $namespace = '') { 304*815440faSAtari911 if (!preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $date, $matches)) { 305*815440faSAtari911 return ['success' => false, 'error' => 'Invalid date format']; 306*815440faSAtari911 } 307*815440faSAtari911 list(, $year, $month, $day) = $matches; 308*815440faSAtari911 $year = (int)$year; 309*815440faSAtari911 $month = (int)$month; 310*815440faSAtari911 311*815440faSAtari911 $eventFile = self::getEventFile($namespace, $year, $month); 312*815440faSAtari911 $events = CalendarFileHandler::readJson($eventFile); 313*815440faSAtari911 314*815440faSAtari911 if (!isset($events[$date])) { 315*815440faSAtari911 return ['success' => false, 'error' => 'Event not found']; 316*815440faSAtari911 } 317*815440faSAtari911 318*815440faSAtari911 $originalCount = count($events[$date]); 319*815440faSAtari911 $events[$date] = array_values(array_filter( 320*815440faSAtari911 $events[$date], 321*815440faSAtari911 function($evt) use ($eventId) { 322*815440faSAtari911 return $evt['id'] !== $eventId; 323*815440faSAtari911 } 324*815440faSAtari911 )); 325*815440faSAtari911 326*815440faSAtari911 if (count($events[$date]) === $originalCount) { 327*815440faSAtari911 return ['success' => false, 'error' => 'Event not found']; 328*815440faSAtari911 } 329*815440faSAtari911 330*815440faSAtari911 if (empty($events[$date])) { 331*815440faSAtari911 unset($events[$date]); 332*815440faSAtari911 } 333*815440faSAtari911 334*815440faSAtari911 if (!CalendarFileHandler::writeJson($eventFile, $events)) { 335*815440faSAtari911 return ['success' => false, 'error' => 'Failed to delete event']; 336*815440faSAtari911 } 337*815440faSAtari911 338*815440faSAtari911 // Invalidate cache 339*815440faSAtari911 CalendarEventCache::invalidateMonth($namespace, $year, $month); 340*815440faSAtari911 341*815440faSAtari911 return ['success' => true]; 342*815440faSAtari911 } 343*815440faSAtari911 344*815440faSAtari911 /** 345*815440faSAtari911 * Get a single event by ID 346*815440faSAtari911 * 347*815440faSAtari911 * @param string $eventId Event ID 348*815440faSAtari911 * @param string $date Event date 349*815440faSAtari911 * @param string $namespace Namespace (use * for all) 350*815440faSAtari911 * @return array|null Event data or null if not found 351*815440faSAtari911 */ 352*815440faSAtari911 public static function getEvent($eventId, $date, $namespace = '') { 353*815440faSAtari911 if (!preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $date, $matches)) { 354*815440faSAtari911 return null; 355*815440faSAtari911 } 356*815440faSAtari911 list(, $year, $month, $day) = $matches; 357*815440faSAtari911 358*815440faSAtari911 $events = self::loadMonth($namespace, (int)$year, (int)$month); 359*815440faSAtari911 360*815440faSAtari911 if (!isset($events[$date])) { 361*815440faSAtari911 return null; 362*815440faSAtari911 } 363*815440faSAtari911 364*815440faSAtari911 foreach ($events[$date] as $event) { 365*815440faSAtari911 if ($event['id'] === $eventId) { 366*815440faSAtari911 return $event; 367*815440faSAtari911 } 368*815440faSAtari911 } 369*815440faSAtari911 370*815440faSAtari911 return null; 371*815440faSAtari911 } 372*815440faSAtari911 373*815440faSAtari911 /** 374*815440faSAtari911 * Find which namespace an event is in 375*815440faSAtari911 * 376*815440faSAtari911 * @param string $eventId Event ID 377*815440faSAtari911 * @param string $date Event date 378*815440faSAtari911 * @return string|null Namespace or null if not found 379*815440faSAtari911 */ 380*815440faSAtari911 public static function findEventNamespace($eventId, $date) { 381*815440faSAtari911 if (!preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $date, $matches)) { 382*815440faSAtari911 return null; 383*815440faSAtari911 } 384*815440faSAtari911 list(, $year, $month, $day) = $matches; 385*815440faSAtari911 386*815440faSAtari911 // Load all namespaces 387*815440faSAtari911 $events = self::loadMonth('*', (int)$year, (int)$month, false); 388*815440faSAtari911 389*815440faSAtari911 if (!isset($events[$date])) { 390*815440faSAtari911 return null; 391*815440faSAtari911 } 392*815440faSAtari911 393*815440faSAtari911 foreach ($events[$date] as $event) { 394*815440faSAtari911 if ($event['id'] === $eventId) { 395*815440faSAtari911 return $event['namespace'] ?? ''; 396*815440faSAtari911 } 397*815440faSAtari911 } 398*815440faSAtari911 399*815440faSAtari911 return null; 400*815440faSAtari911 } 401*815440faSAtari911 402*815440faSAtari911 /** 403*815440faSAtari911 * Search events across all namespaces 404*815440faSAtari911 * 405*815440faSAtari911 * @param string $query Search query 406*815440faSAtari911 * @param array $options Search options (dateFrom, dateTo, namespace) 407*815440faSAtari911 * @return array Matching events 408*815440faSAtari911 */ 409*815440faSAtari911 public static function searchEvents($query, array $options = []) { 410*815440faSAtari911 $results = []; 411*815440faSAtari911 $query = strtolower(trim($query)); 412*815440faSAtari911 413*815440faSAtari911 $namespace = $options['namespace'] ?? '*'; 414*815440faSAtari911 $dateFrom = $options['dateFrom'] ?? date('Y-m-01'); 415*815440faSAtari911 $dateTo = $options['dateTo'] ?? date('Y-m-d', strtotime('+1 year')); 416*815440faSAtari911 417*815440faSAtari911 // Parse date range 418*815440faSAtari911 $startDate = new DateTime($dateFrom); 419*815440faSAtari911 $endDate = new DateTime($dateTo); 420*815440faSAtari911 421*815440faSAtari911 // Iterate through months 422*815440faSAtari911 $current = clone $startDate; 423*815440faSAtari911 $current->modify('first day of this month'); 424*815440faSAtari911 425*815440faSAtari911 while ($current <= $endDate) { 426*815440faSAtari911 $year = (int)$current->format('Y'); 427*815440faSAtari911 $month = (int)$current->format('m'); 428*815440faSAtari911 429*815440faSAtari911 $events = self::loadMonth($namespace, $year, $month); 430*815440faSAtari911 431*815440faSAtari911 foreach ($events as $date => $dateEvents) { 432*815440faSAtari911 if ($date < $dateFrom || $date > $dateTo) { 433*815440faSAtari911 continue; 434*815440faSAtari911 } 435*815440faSAtari911 436*815440faSAtari911 foreach ($dateEvents as $event) { 437*815440faSAtari911 $titleMatch = stripos($event['title'] ?? '', $query) !== false; 438*815440faSAtari911 $descMatch = stripos($event['description'] ?? '', $query) !== false; 439*815440faSAtari911 440*815440faSAtari911 if ($titleMatch || $descMatch) { 441*815440faSAtari911 $event['_date'] = $date; 442*815440faSAtari911 $results[] = $event; 443*815440faSAtari911 } 444*815440faSAtari911 } 445*815440faSAtari911 } 446*815440faSAtari911 447*815440faSAtari911 $current->modify('+1 month'); 448*815440faSAtari911 } 449*815440faSAtari911 450*815440faSAtari911 // Sort by date 451*815440faSAtari911 usort($results, function($a, $b) { 452*815440faSAtari911 return strcmp($a['_date'], $b['_date']); 453*815440faSAtari911 }); 454*815440faSAtari911 455*815440faSAtari911 return $results; 456*815440faSAtari911 } 457*815440faSAtari911 458*815440faSAtari911 /** 459*815440faSAtari911 * Get all namespaces that have calendar data 460*815440faSAtari911 * 461*815440faSAtari911 * @return array List of namespaces 462*815440faSAtari911 */ 463*815440faSAtari911 public static function getNamespaces() { 464*815440faSAtari911 return self::expandNamespacePattern('*'); 465*815440faSAtari911 } 466*815440faSAtari911 467*815440faSAtari911 /** 468*815440faSAtari911 * Debug log helper 469*815440faSAtari911 * 470*815440faSAtari911 * @param string $message Message to log 471*815440faSAtari911 */ 472*815440faSAtari911 private static function log($message) { 473*815440faSAtari911 if (defined('CALENDAR_DEBUG') && CALENDAR_DEBUG) { 474*815440faSAtari911 error_log("[Calendar EventManager] $message"); 475*815440faSAtari911 } 476*815440faSAtari911 } 477*815440faSAtari911} 478