slug = $slug; $this->postType = jvbCheckBase($slug); $this->registrar = $registrar; $this->addCalendarFields(); add_filter('post_type_link', [$this, 'handleCalendarLinks'], 15, 2); add_filter('post_type_archive_link', [$this, 'handlePostTypeArchiveLinks'], 15, 2); add_filter('query_vars', [$this, 'addQueryVars']); add_action('init', [$this, 'addCalendarRewrites']); $this->hook = BASE.'mark_'.$this->slug.'_passed'; add_action($this->hook, [$this, 'markPassed']); } public function __debugInfo() { $vars = get_object_vars($this); unset($vars['registrar']); return $vars; } protected function addCalendarFields():void { $fields = [ 'location' => [ 'type' => 'location', 'label' => 'Event Location', ], 'location_notes' => [ 'type' => 'textarea', 'quill' => true, 'label' => 'Location Notes', ], 'date_start' => [ 'type' => 'date', 'label' => 'Date', ], 'time_start' => [ 'type' => 'time', 'label' => 'Time Start', ], 'time_end' => [ 'type' => 'time', 'label' => 'Time End', ], 'make_multiple' => [ 'type' => 'true_false', 'label' => 'Make Multi-Day Event', 'default' => false, ], 'date_end' => [ 'type' => 'date', 'label' => 'Date End', 'condition' => [ 'field' => 'make_multiple', 'operator' => '==', 'value' => 1 ] ], 'past' => [ 'type' => 'true_false', 'label' => 'Past Event', 'hidden' => true, ], 'year' => [ 'type' => 'number', 'label' => 'Year', 'hidden' => true, ], 'month' => [ 'type' => 'number', 'label' => 'Month', 'hidden' => true, ], 'day' => [ 'type' => 'number', 'label' => 'Day', 'hidden' => true, ], 'schedule' => [ 'type' => 'repeater', 'label' => 'Daily Schedule', 'description' => 'Set specific times for multi-day events', 'condition' => [ 'field' => 'make_multiple', 'operator' => '==', 'value' => 1, ], 'fields' => [ 'date' => [ 'type' => 'date', 'label' => 'Date' ], 'time_start' => [ 'type' => 'time', 'label' => 'Start Time' ], 'time_end' => [ 'type' => 'time', 'label' => 'End Time' ], 'notes' => [ 'type' => 'textarea', 'label' => 'Notes for this day' ] ] ], 'max_participants' => [ 'type' => 'number', 'label' => 'Maximum Participants', ], 'is_free' => [ 'type' => 'true_false', 'label' => 'Free Event?', 'default' => 1, ], 'cost' => [ 'type' => 'number', 'label' => 'Cost to Attend', 'description' => 'Leave blank if free', 'condition' => [ 'field' => 'is_free', 'operator' => '!=', 'value' => 1 ], ], 'ticket_url' => [ 'type' => 'url', 'label' => 'Link to Purchase Ticket', 'condition' => [ 'field' => 'is_free', 'operator' => '!=', 'value' => 1 ] ], 'external_url' => [ 'type' => 'url', 'label' => 'Link to More Info' ], 'drop_in_allowed' => [ 'type' => 'true_false', 'label' => 'Drop-ins Welcome?', 'default' => true, ], 'recurrence' => [ 'type' => 'group', 'label' => 'Event Recurrence', 'condition' => [ 'field' => 'status', 'operator' => '==', 'value' => 'repeat', ], 'fields' => [ 'recurrence_type' => [ 'type' => 'select', 'label' => 'Repeats', 'options' => [ 'daily' => 'Daily', 'weekly' => 'Weekly', 'monthly' => 'Monthly', 'custom' => 'Custom' ] ], 'recurrence_interval' => [ 'type' => 'number', 'label' => 'Every', 'description' => 'Repeat every X days/weeks/months', 'default' => 1, ], 'recurrence_days' => [ 'type' => 'set', 'label' => 'On These Days', 'options' => [ 'monday' => 'Monday', 'tuesday' => 'Tuesday', 'wednesday' => 'Wednesday', 'thursday' => 'Thursday', 'friday' => 'Friday', 'saturday' => 'Saturday', 'sunday' => 'Sunday' ], 'condition' => [ 'field' => 'recurrence_type', 'operator' => '==', 'value' => 'weekly' ] ], 'recurrence_ends' => [ 'type' => 'group', 'label' => 'Recurrence Ends', 'fields' => [ 'type' => [ 'type' => 'select', 'label' => 'Recurrence Ends', 'options' => [ 'never' => 'Never', 'after' => 'After X occurrences', 'on' => 'On date' ] ], 'count' => [ 'type' => 'number', 'label' => 'Number of occurrences', 'condition' => [ 'field' => 'type', 'operator' => '==', 'value' => 'after' ] ], 'date' => [ 'type' => 'date', 'label' => 'End date', 'condition' => [ 'field' => 'type', 'operator' => '==', 'value' => 'on' ] ] ], ], 'rsvp_required' => [ 'type' => 'true_false', 'label' => 'RSVP Required?' ], 'rsvp_limit' => [ 'type' => 'number', 'label' => 'RSVP Limit', 'condition' => [ 'field' => 'rsvp_required', 'operator' => '==', 'value' => 1 ] ], 'rsvp_deadline' => [ 'type' => 'datetime', 'label' => 'RSVP Deadline', 'condition' => [ 'field' => 'rsvp_required', 'operator' => '==', 'value' => 1 ] ], ] ] ]; foreach ($fields as $name => $config) { $this->registrar->fields()->addField($name, $config); } $this->registrar->fields()->modifyField('post_status', 'options', [ 'all' => [ 'icon' => 'calendar', 'label' => 'Everything', ], 'future' => [ 'label' => 'Upcoming', 'icon' => 'clock-clockwise', ], 'past' => [ 'label' => 'Past', 'icon' => 'clock-counter-clockwise', ], 'repeat' => [ 'label' => 'Recurring', 'icon' => 'repeat', ], 'draft' => [ 'icon' => 'eye-closed', 'label' => 'Hidden', ], 'trash' => [ 'label' => 'Scrapped', 'icon' => 'trash', ] ]); } public function handleCalendarLinks(string $url, WP_POST $post):string { if ($post->post_type === $this->postType) { $get = ['year', 'month','day']; $values = Meta::forPost($post->ID)->getAll($get); foreach ($values as $key => $value) { if (!empty($value)) { $url = str_replace("%e$key%", $value, $url); } else { $url = str_replace("/%e$key%", '', $url); } } } return $url; } public function handlePostTypeArchiveLinks(string $url, string $post_type):string { if ($post_type == $this->postType) { $url = get_home_url(null, '/'.$this->registrar->registrar->rewrite['slug']??$this->slug.'/'); } return $url; } public function addQueryVars(array $vars):array { $vars[] = 'eyear'; $vars[] = 'emonth'; $vars[] = 'eday'; return $vars; } protected function scheduleMarkPassed():void { if (!wp_next_scheduled($this->hook)) { $time = strtotime('tomorrow at 12:01am'); wp_schedule_event($time, 'daily', $this->hook); } } public function markPassed():void { $now = date('Y-m-d'); $items = new WP_Query([ 'post_type' => $this->postType, 'posts_per_page' => -1, 'post_status' => 'publish', 'meta_query' => [ 'relation' => 'AND', [ 'key' => BASE.'past', 'value' => '1', 'compare'=> '!=' ], [ 'key' => BASE.'date_start', 'value' => $now, 'compare' => '<', 'type' => 'DATETIME' ] ], 'fields' => 'ids', ]); if ($items->have_posts()) { foreach ($items->posts as $ID) { $meta = Meta::forPost($ID); $meta->set('past', '1'); } } } public function addCalendarRewrites():void { $this->scheduleMarkPassed(); add_rewrite_tag('%eyear%', '([^&]+)'); add_rewrite_tag('%emonth%', '([^&]+)'); add_rewrite_tag('%eday%', '([^&]+)'); $registrar = Registrar::getInstance($this->slug); $base = $registrar->registrar->rewrite['slug']??$this->slug; // Adds the rewrite rule to capture URLs with year, month, and day. add_rewrite_rule( '^'.$base.'/([0-9]{4})/([0-9]{2})/([0-9]{2})/?$', 'index.php?post_type='.$this->postType.'&eyear=$matches[1]&emonth=$matches[2]&eday=$matches[3]', 'top' ); add_rewrite_rule( '^'.$base.'/([0-9]{4})/([0-9]{2})/([0-9]{2})/page/([0-9-]+)/?$', 'index.php?post_type='.$this->postType.'&eyear=$matches[1]&emonth=$matches[2]&eday=$matches[3]&paged=$matches[4]', 'top' ); // Adds the rewrite rule to capture URLs with year, month only. add_rewrite_rule( '^'.$base.'/([0-9]{4})/([0-9]{2})/?$', 'index.php?post_type='.$this->postType.'&eyear=$matches[1]&emonth=$matches[2]', 'top' ); add_rewrite_rule( '^'.$base.'/([0-9]{4})/([0-9]{2})/page/([0-9-]+)/?$', 'index.php?post_type='.$this->postType.'&eyear=$matches[1]&emonth=$matches[2]&paged=$matches[3]', 'top' ); // Adds the rewrite rule to capture URLs with year only. add_rewrite_rule( '^'.$base.'/([0-9]{4})/?$', 'index.php?post_type='.$this->postType.'&eyear=$matches[1]', 'top' ); add_rewrite_rule( '^'.$base.'/([0-9]{4})/page/([0-9-]+)/?$', 'index.php?post_type='.$this->postType.'&eyear=$matches[1]&paged=$matches[2]', 'top' ); // Adds the rewrite rule for all events add_rewrite_rule( '^'.$base.'/?$', 'index.php?post_type='.$this->postType, 'top' ); add_rewrite_rule( '^'.$base.'/page/([0-9-]+)/?$', 'index.php?post_type='.$this->postType.'&paged=$matches[1]', 'top' ); add_rewrite_rule( "^{$base}/([^/]+)/?$", "index.php?post_type={$this->postType}&name=\$matches[1]", 'top' ); } }