}} current month * - {{calendar>YYYY-MM}} specific month * - {{calendar>YYYY-MM&base=chronological}} custom base namespace (optional) */ class syntax_plugin_luxtools_calendar extends SyntaxPlugin { /** @inheritdoc */ public function getType() { return 'substition'; } /** @inheritdoc */ public function getPType() { return 'block'; } /** @inheritdoc */ public function getSort() { return 224; } /** @inheritdoc */ public function connectTo($mode) { $this->Lexer->addSpecialPattern('\{\{calendar>.*?\}\}', $mode, 'plugin_luxtools_calendar'); } /** @inheritdoc */ public function handle($match, $state, $pos, Doku_Handler $handler) { $match = substr($match, strlen('{{calendar>'), -2); [$target, $flags] = array_pad(explode('&', $match, 2), 2, ''); $target = trim((string)$target); $params = $this->parseFlags($flags); $baseNs = $params['base'] ?? 'chronological'; $resolved = $this->resolveTargetMonth($target); if ($resolved === null) { return [ 'ok' => false, 'error' => 'calendar_err_badmonth', ]; } return [ 'ok' => true, 'year' => $resolved['year'], 'month' => $resolved['month'], 'base' => $baseNs, 'size' => ChronologicalCalendarWidget::normalizeSize((string)($params['size'] ?? 'large')), 'show_times' => ChronologicalCalendarWidget::normalizeShowTimes($params['show_times'] ?? null), ]; } /** @inheritdoc */ public function render($format, Doku_Renderer $renderer, $data) { if ($data === false || !is_array($data)) return false; if ($format !== 'xhtml') return false; if (!($renderer instanceof Doku_Renderer_xhtml)) return false; $renderer->nocache(); if (!($data['ok'] ?? false)) { $message = (string)$this->getLang((string)($data['error'] ?? 'calendar_err_badmonth')); if ($message === '') $message = 'Invalid calendar month. Use YYYY-MM.'; $renderer->doc .= '
' . hsc($message) . '
'; return true; } $year = (int)$data['year']; $month = (int)$data['month']; $baseNs = (string)$data['base']; $size = ChronologicalCalendarWidget::normalizeSize((string)($data['size'] ?? 'large')); $showTimes = (bool)($data['show_times'] ?? true); if ($size === 'small') { $resolved = $this->resolveMonthFromCookie( 'luxtools_calendar_month_' . preg_replace('/[^a-zA-Z0-9]/', '_', $baseNs) ) ?? $this->resolveMonthFromCookie('luxtools_client_month'); if ($resolved !== null) { $year = $resolved['year']; $month = $resolved['month']; } } $slots = CalendarSlot::loadEnabled($this); $widgetSlots = CalendarSlot::filterWidgetVisible($slots); $indicators = []; $dayEvents = []; if ($size === 'large') { $widgetData = CalendarService::monthWidgetData($widgetSlots, $year, $month); $indicators = $widgetData['indicators']; $dayEvents = $widgetData['events']; } else { $indicators = CalendarService::monthIndicators($widgetSlots, $year, $month); } $slotColors = []; $slotDisplays = []; foreach ($widgetSlots as $slot) { $color = $slot->getColor(); if ($color !== '') { $slotColors[$slot->getKey()] = $color; } $slotDisplays[$slot->getKey()] = $slot->getDisplay(); } $renderer->doc .= ChronologicalCalendarWidget::render($year, $month, $baseNs, $indicators, $slotColors, $slotDisplays, [ 'size' => $size, 'showTimes' => $showTimes, 'dayEvents' => $dayEvents, ]); return true; } /** * @param string $cookieName * @return array{year:int,month:int}|null */ protected function resolveMonthFromCookie(string $cookieName): ?array { $raw = $_COOKIE[$cookieName] ?? null; if ($raw === null) return null; $decoded = json_decode($raw, true); if (!is_array($decoded)) return null; $year = (int)($decoded['year'] ?? 0); $month = (int)($decoded['month'] ?? 0); if ($year < 1 || $month < 1 || $month > 12) return null; return ['year' => $year, 'month' => $month]; } /** * @param string $flags * @return array */ protected function parseFlags(string $flags): array { $params = []; foreach (explode('&', $flags) as $flag) { if (trim($flag) === '') continue; [$name, $value] = array_pad(explode('=', $flag, 2), 2, ''); $name = strtolower(trim($name)); $value = trim($value); if ($name === '') continue; $params[$name] = $value; } if (!isset($params['base']) || trim($params['base']) === '') { $params['base'] = 'chronological'; } return $params; } /** * Resolve target string to year/month. * * Accepted formats: * - '' (current month) * - YYYY-MM * * @param string $target * @return array{year:int,month:int}|null */ protected function resolveTargetMonth(string $target): ?array { if ($target === '') { return [ 'year' => (int)date('Y'), 'month' => (int)date('m'), ]; } if (!preg_match('/^(\d{4})-(\d{2})$/', $target, $matches)) { return null; } $year = (int)$matches[1]; $month = (int)$matches[2]; if ($year < 1) return null; if ($month < 1 || $month > 12) return null; return [ 'year' => $year, 'month' => $month, ]; } }