answer( $schedule_name, $event_id, $event_name ); } /** * Work out the answer. * * @param string|null $schedule_name Schedule title. * @param int|null $event_id Event ID. * @param string|null $event_name Event title. * @return array */ private function answer( ?string $schedule_name, ?int $event_id, ?string $event_name ): array { $check = $this->guard( 'etn_manage_schedule', 'etn_manage_event' ); if ( $check ) { return $check; } try { if ( ( null !== $event_id && $event_id > 0 ) || '' !== trim( (string) $event_name ) ) { return $this->agenda( $event_id, $event_name ); } if ( '' !== trim( (string) $schedule_name ) ) { return $this->one_schedule( trim( (string) $schedule_name ) ); } return $this->overview(); } catch ( \Throwable $e ) { Log::error( 'Eventin_Schedules_Tool error', [ 'error' => $e->getMessage() ] ); return [ 'error' => 'Could not read schedules: ' . $e->getMessage() ]; } } /** * How many schedules and sessions, and what is coming up. * * @return array */ private function overview(): array { $schedules = $this->query->schedules(); $used = array_unique( array_merge( [], ...array_values( $this->query->event_schedules() ) ) ); $today = current_time( 'Y-m-d' ); $upcoming = array_filter( $schedules, static fn( $s ) => '' !== $s['date'] && $s['date'] >= $today ); uasort( $upcoming, static fn( $a, $b ) => strcmp( $a['date'], $b['date'] ) ); return [ 'status' => 'ok', 'total' => count( $schedules ), 'sessions' => array_sum( array_map( static fn( $s ) => count( $s['sessions'] ), $schedules ) ), 'used_by_an_event' => count( array_intersect( array_keys( $schedules ), $used ) ), 'upcoming' => count( $upcoming ), 'next' => array_map( static fn( $s ) => [ 'title' => $s['title'], 'date' => $s['date'], 'sessions' => count( $s['sessions'] ), ], array_values( array_slice( $upcoming, 0, self::SCHEDULE_LIMIT ) ) ), 'today' => $today, 'definitions' => [ 'total' => 'Every schedule. One schedule is one day of an agenda.', 'sessions' => 'Sessions (talks, slots) across all schedules.', 'upcoming' => 'Schedules dated today or later.', ], 'page' => $this->schedules_page(), ]; } /** * One schedule, found by title. * * @param string $name Title, or part of it. * @return array */ private function one_schedule( string $name ): array { $schedules = $this->query->schedules(); $exact = array_filter( $schedules, static fn( $s ) => 0 === strcasecmp( trim( $s['title'] ), $name ) ); $matches = ! empty( $exact ) ? $exact : array_filter( $schedules, static fn( $s ) => false !== mb_stripos( $s['title'], $name ) ); if ( empty( $matches ) ) { return $this->not_found( "No schedule title contains \"{$name}\".", $this->schedules_page() ); } if ( count( $matches ) > 1 ) { return [ 'status' => 'needs_clarification', 'reason' => "More than one schedule matches \"{$name}\".", 'matches' => array_map( static fn( $s ) => [ 'title' => $s['title'], 'date' => $s['date'], ], array_values( array_slice( $matches, 0, self::SCHEDULE_LIMIT * 2 ) ) ), 'say' => 'Do not pick one. Read out the titles with their dates and ask which one they mean.', 'page' => $this->schedules_page(), ]; } $schedule = reset( $matches ); $events = []; foreach ( $this->query->event_schedules() as $event_id => $ids ) { if ( in_array( $schedule['id'], $ids, true ) ) { $events[] = $event_id; } } return [ 'status' => 'ok', 'schedule' => $this->described( $schedule ), 'events' => array_values( $this->query->event_titles( $events ) ), 'page' => $this->schedules_page(), ]; } /** * The agenda of one event. * * @param int|null $event_id Event ID. * @param string|null $event_name Event title. * @return array */ private function agenda( ?int $event_id, ?string $event_name ): array { list( $event_id, $title, $stop ) = $this->resolve_event( $event_id, $event_name, $this->page( __( 'Events', 'aisentic' ), '/events' ) ); if ( null !== $stop ) { return $stop; } $schedules = $this->query->schedules(); $days = []; foreach ( $this->query->event_schedules()[ $event_id ] ?? [] as $id ) { if ( isset( $schedules[ $id ] ) ) { $days[] = $schedules[ $id ]; } } usort( $days, static fn( $a, $b ) => strcmp( $a['date'], $b['date'] ) ); return [ 'status' => 'ok', 'event' => $title, 'days' => count( $days ), 'sessions' => array_sum( array_map( static fn( $s ) => count( $s['sessions'] ), $days ) ), 'schedules' => array_map( fn( $s ) => $this->described( $s ), array_slice( $days, 0, self::SCHEDULE_LIMIT ) ), 'say' => empty( $days ) ? 'Say this event has no schedule set. Do not say the site has none.' : '', 'page' => $this->event_page( $event_id, $title ), ]; } /** * A schedule with its sessions, speakers named. * * @param array $schedule One entry of schedules(). * @return array */ private function described( array $schedule ): array { $speakers = $this->query->people( 'speaker' ); $sessions = []; foreach ( array_slice( $schedule['sessions'], 0, self::SESSION_LIMIT ) as $session ) { $sessions[] = [ 'topic' => $session['topic'], 'start' => $session['start'], 'end' => $session['end'], 'room' => $session['room'], 'speakers' => array_values( array_filter( array_map( static fn( $id ) => $speakers[ $id ]['name'] ?? '', $session['speakers'] ) ) ), ]; } return [ 'title' => $schedule['title'], 'date' => $schedule['date'], 'total_sessions' => count( $schedule['sessions'] ), 'sessions' => $sessions, ]; } /** * Page that lists schedules. * * @return array{title:string,url:string} */ private function schedules_page(): array { return $this->page( __( 'Schedules', 'aisentic' ), '/schedules' ); } }