1800, 'display' => __('Every 30 Minutes', 'hi-theme-toolkit') ]; } if (!isset($schedules['weekly'])) { $schedules['weekly'] = [ 'interval' => 604800, 'display' => __('Once Weekly', 'hi-theme-toolkit') ]; } return $schedules; } /** * Maybe schedule cron if not already scheduled */ public static function maybe_schedule_cron() { $settings = self::get_settings(); $google_enabled = isset($settings['google']['enabled']) && $settings['google']['enabled']; $wp_events_enabled = isset($settings['wordpress_events']['enabled']) && $settings['wordpress_events']['enabled']; // If both are disabled, unschedule if (!$google_enabled && !$wp_events_enabled) { self::unschedule_cron(); return; } // Single cron for both sources: use the shortest interval among enabled ones $intervals = []; if ($google_enabled) { $intervals[] = $settings['google']['sync_interval'] ?? 'daily'; } if ($wp_events_enabled) { $intervals[] = $settings['wordpress_events']['sync_interval'] ?? 'daily'; } $schedules = wp_get_schedules(); $intervals = array_filter($intervals, fn($name) => isset($schedules[$name])); usort($intervals, fn($a, $b) => $schedules[$a]['interval'] <=> $schedules[$b]['interval']); $interval = $intervals[0] ?? 'daily'; // Check if already scheduled with correct interval $scheduled = wp_next_scheduled(self::CRON_HOOK); if ($scheduled) { $current_schedule = wp_get_schedule(self::CRON_HOOK); // If interval changed, reschedule if ($current_schedule !== $interval) { self::unschedule_cron(); wp_schedule_event(time(), $interval, self::CRON_HOOK); } } else { // Not scheduled, schedule it wp_schedule_event(time(), $interval, self::CRON_HOOK); } } /** * Unschedule cron */ public static function unschedule_cron() { $timestamp = wp_next_scheduled(self::CRON_HOOK); if ($timestamp) { wp_unschedule_event($timestamp, self::CRON_HOOK); } } /** * Get calendar settings from options * * @return array Settings array */ public static function get_settings() { return get_option('toolkit_calendar_settings', [ 'google' => [ 'enabled' => false, 'api_key' => '', 'calendar_id' => '', 'sync_interval' => 'daily', 'max_results' => 250, 'time_min_offset' => -30, 'time_max_offset' => 365, ] ]); } /** * Sync all enabled calendar sources * * @return array Result of sync operations */ public static function sync_all() { $settings = self::get_settings(); $results = []; // Sync Google Calendar if enabled if (isset($settings['google']['enabled']) && $settings['google']['enabled']) { if (class_exists('Toolkit\utils\GoogleCalendarSource')) { $results['google'] = \Toolkit\utils\GoogleCalendarSource::sync(); } else { $results['google'] = [ 'success' => false, 'message' => 'GoogleCalendarSource class not found' ]; } } // Sync WordPress Events if enabled if (isset($settings['wordpress_events']['enabled']) && $settings['wordpress_events']['enabled']) { if (class_exists('Toolkit\utils\EventWPCalendar')) { $results['wordpress_events'] = \Toolkit\utils\EventWPCalendar::sync(); } else { $results['wordpress_events'] = [ 'success' => false, 'message' => 'EventWPCalendar class not found' ]; } } // Update last sync time update_option('toolkit_calendar_last_sync', time()); return $results; } /** * Get all calendar events * * @param array $args Query arguments * @return array Array of calendar events */ public static function get_all_events($args = []) { $defaults = [ 'post_type' => 'calendar_event', 'post_status' => 'publish', 'posts_per_page' => -1, 'orderby' => 'meta_value', 'order' => 'ASC', // phpcs:ignore WordPress.DB.SlowDBQuery 'meta_key' => '_event_date', ]; $args = wp_parse_args($args, $defaults); $query = new \WP_Query($args); return $query->posts; } /** * Get upcoming events * * @param int $limit Number of events to retrieve * @return array Array of upcoming events */ public static function get_upcoming($limit = 10) { return self::get_all_events([ 'posts_per_page' => $limit, // phpcs:ignore WordPress.DB.SlowDBQuery 'meta_query' => [ [ 'key' => '_event_date', 'value' => current_time('mysql'), 'compare' => '>=', 'type' => 'DATETIME' ] ] ]); } /** * Get events within date range * * @param string $from Start date (Y-m-d format) * @param string $to End date (Y-m-d format) * @return array Array of events */ public static function get_events_by_range($from, $to) { return self::get_all_events([ // phpcs:ignore WordPress.DB.SlowDBQuery 'meta_query' => [ 'relation' => 'AND', [ 'key' => '_event_date', 'value' => $from, 'compare' => '>=', 'type' => 'DATE' ], [ 'key' => '_event_date', 'value' => $to, 'compare' => '<=', 'type' => 'DATE' ] ] ]); } /** * Get event by Google Calendar ID * * @param string $google_event_id Google Calendar event ID * @return \WP_Post|null Post object or null if not found */ public static function get_event_by_google_id($google_event_id) { $args = [ 'post_type' => 'calendar_event', 'posts_per_page' => 1, // phpcs:ignore WordPress.DB.SlowDBQuery 'meta_query' => [ [ 'key' => '_google_event_id', 'value' => $google_event_id, 'compare' => '=' ] ] ]; $query = new \WP_Query($args); return $query->have_posts() ? $query->posts[0] : null; } /** * Delete events of a source that were not seen during the last sync * * @param string $source_meta_key Meta key identifying the source (_google_event_id or _wp_event_source_id) * @param array $synced_post_ids calendar_event IDs created/updated during the sync * @param array $extra_meta_query Additional meta_query clauses to restrict the candidates * @return int Number of deleted events */ public static function cleanup_deleted_events($source_meta_key, $synced_post_ids = [], $extra_meta_query = []) { $candidates = get_posts([ 'post_type' => 'calendar_event', 'post_status' => 'any', 'posts_per_page' => -1, 'fields' => 'ids', 'post__not_in' => array_map('intval', $synced_post_ids), // phpcs:ignore WordPress.DB.SlowDBQuery 'meta_query' => array_merge([ 'relation' => 'AND', [ 'key' => $source_meta_key, 'compare' => 'EXISTS' ] ], $extra_meta_query) ]); // Force delete: a trashed copy would be ignored by the next sync and duplicated foreach ($candidates as $post_id) { wp_delete_post($post_id, true); } return count($candidates); } /** * Get statistics about calendar events * * @return array Statistics array */ public static function get_statistics() { $total = wp_count_posts('calendar_event'); return [ 'total' => $total->publish ?? 0, 'draft' => $total->draft ?? 0, 'upcoming' => count(self::get_upcoming(9999)), 'last_sync' => get_option('toolkit_calendar_last_sync', null), ]; } }