plugin_name = $plugin_name; $this->version = $version; // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Only reading for tab display $this->current_tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'dashboard'; // phpcs:enable WordPress.Security.NonceVerification.Recommended } /** * Enqueue admin styles */ public function enqueue_styles($hook) { if (strpos($hook, '3task-calendar') === false) { return; } wp_enqueue_style('wp-color-picker'); wp_enqueue_style( $this->plugin_name . '-admin', THREECAL_PLUGIN_URL . 'admin/css/admin.css', array(), threecal_asset_version('admin/css/admin.css') ); } /** * Enqueue admin scripts */ public function enqueue_scripts($hook) { if (strpos($hook, '3task-calendar') === false) { return; } wp_enqueue_media(); wp_enqueue_script('wp-color-picker'); wp_enqueue_script( $this->plugin_name . '-admin', THREECAL_PLUGIN_URL . 'admin/js/admin.js', array('jquery', 'wp-color-picker'), threecal_asset_version('admin/js/admin.js'), true ); wp_localize_script($this->plugin_name . '-admin', 'threecal_admin', array( 'ajax_url' => admin_url('admin-ajax.php'), 'admin_url' => admin_url(), 'nonce' => wp_create_nonce('threecal_admin_nonce'), 'strings' => array( 'confirm_delete' => __('Are you sure you want to delete this item?', '3task-calendar'), 'confirm_delete_multiple' => __('Are you sure you want to delete the selected items?', '3task-calendar'), 'select_image' => __('Select Image', '3task-calendar'), 'use_image' => __('Use Image', '3task-calendar'), 'saving' => __('Saving...', '3task-calendar'), 'saved' => __('Saved!', '3task-calendar'), 'error' => __('An error occurred.', '3task-calendar') ) )); } /** * Add menu pages. */ public function add_menu_pages() { add_menu_page( __( '3task Calendar', '3task-calendar' ), __( '3task Calendar', '3task-calendar' ), 'edit_threecal_events', '3task-calendar', array( $this, 'render_admin_page' ), 'dashicons-calendar-alt', 26 ); } /** * Get available tabs. * * @return array Tabs configuration. */ private function get_tabs() { $counts = $this->get_tab_counts(); return array( 'dashboard' => array( 'title' => __( 'Dashboard', '3task-calendar' ), 'icon' => 'layout-dashboard', ), 'events' => array( 'title' => __( 'Events', '3task-calendar' ), 'icon' => 'calendar-event', 'count' => $counts['events'], ), 'categories' => array( 'title' => __( 'Categories', '3task-calendar' ), 'icon' => 'category', 'count' => $counts['categories'], ), 'locations' => array( 'title' => __( 'Locations', '3task-calendar' ), 'icon' => 'map-pin', 'count' => $counts['locations'], ), 'design' => array( 'title' => __( 'Design', '3task-calendar' ), 'icon' => 'palette', ), 'settings' => array( 'title' => __( 'Settings', '3task-calendar' ), 'icon' => 'settings', ), 'help' => array( 'title' => __( 'Help', '3task-calendar' ), 'icon' => 'help-circle', ), ); } /** * Counts for the tab badges. * * @return array */ private function get_tab_counts() { global $wpdb; // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Counts from custom tables. $events = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}threecal_events WHERE parent_id IS NULL OR parent_id = 0" ); $categories = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}threecal_categories" ); $locations = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}threecal_locations" ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching return compact( 'events', 'categories', 'locations' ); } /** * Render main admin page with tabs. */ public function render_admin_page() { $tabs = $this->get_tabs(); ?>

render_tab_content(); ?>
current_tab ) { case 'events': $this->render_events(); break; case 'categories': $this->render_categories(); break; case 'locations': $this->render_locations(); break; case 'design': $this->render_design(); break; case 'settings': $this->render_settings(); break; case 'help': $this->render_help(); break; default: $this->render_dashboard(); } } /** * Render dashboard tab. */ private function render_dashboard() { $stats = $this->get_event_stats(); $counts = $this->get_tab_counts(); $upcoming = ThreeCal_Event::get_upcoming( 5 ); $cats = ThreeCal_Event::get_categories_for_events( wp_list_pluck( $upcoming, 'id' ) ); $new_url = admin_url( 'admin.php?page=3task-calendar&tab=events&action=new' ); ?>

' . esc_html__( 'You do not have permission to change the calendar settings.', '3task-calendar' ) . '

'; return; } $themes = ThreeCal_Themes::all(); if ( isset( $_POST['threecal_save_design'] ) ) { check_admin_referer( 'threecal_design' ); $settings = (array) get_option( 'threecal_settings', array() ); $theme = isset( $_POST['default_theme'] ) ? sanitize_key( wp_unslash( $_POST['default_theme'] ) ) : 'default'; $settings['default_theme'] = isset( $themes[ $theme ] ) ? $theme : 'default'; $accent = isset( $_POST['accent_color'] ) ? sanitize_hex_color( wp_unslash( $_POST['accent_color'] ) ) : ''; $settings['accent_color'] = $accent ? $accent : ''; update_option( 'threecal_settings', $settings ); echo '

' . esc_html__( 'Settings saved!', '3task-calendar' ) . '

'; } $current = ThreeCal_Themes::normalize( '' ); $settings = (array) get_option( 'threecal_settings', array() ); $accent = isset( $settings['accent_color'] ) ? $settings['accent_color'] : ''; $renderer = new ThreeCal_Calendar_Renderer(); wp_enqueue_style( 'threecal-public', THREECAL_PLUGIN_URL . 'public/css/threecal.css', array(), threecal_asset_version( 'public/css/threecal.css' ) ); wp_enqueue_style( 'threecal-mini', THREECAL_PLUGIN_URL . 'public/css/threecal-mini.css', array(), threecal_asset_version( 'public/css/threecal-mini.css' ) ); ?>

$theme ) : ?>

render_edit_event(); return; } // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Only reading for navigation, no data modification. if ( isset( $_GET['action'] ) && $_GET['action'] === 'new' ) { // phpcs:enable WordPress.Security.NonceVerification.Recommended $this->render_new_event(); return; } // List events (repetitions of a series are managed through their first event). // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display filter only. $scope = isset( $_GET['scope'] ) ? sanitize_key( wp_unslash( $_GET['scope'] ) ) : 'upcoming'; $scope = in_array( $scope, array( 'upcoming', 'past', 'all' ), true ) ? $scope : 'upcoming'; $events = $this->get_events( $scope ); $cats = ThreeCal_Event::get_categories_for_events( wp_list_pluck( $events, 'id' ) ); $rules = ThreeCal_Event::recurrence_options(); $scopes = array( 'upcoming' => __( 'Upcoming', '3task-calendar' ), 'past' => __( 'Past', '3task-calendar' ), 'all' => __( 'All', '3task-calendar' ), ); $status_labels = array( 'published' => __( 'Published', '3task-calendar' ), 'draft' => __( 'Draft', '3task-calendar' ), 'cancelled' => __( 'Cancelled', '3task-calendar' ), ); ?>
$label ) : ?> >

' . esc_html__( 'You do not have permission to change the calendar settings.', '3task-calendar' ) . '

'; return; } if ( isset( $_POST['submit'] ) ) { check_admin_referer( 'threecal_settings' ); // Merge with the stored settings so fields that are not part of this form stay untouched. $settings = (array) get_option( 'threecal_settings', array() ); $settings['date_format'] = isset( $_POST['date_format'] ) ? sanitize_text_field( wp_unslash( $_POST['date_format'] ) ) : get_option( 'date_format' ); $settings['time_format'] = isset( $_POST['time_format'] ) ? sanitize_text_field( wp_unslash( $_POST['time_format'] ) ) : get_option( 'time_format' ); $settings['week_starts_on'] = ( isset( $_POST['week_starts_on'] ) && 0 === absint( $_POST['week_starts_on'] ) ) ? 0 : 1; $settings['show_event_time'] = ! empty( $_POST['show_event_time'] ); $settings['show_event_location'] = ! empty( $_POST['show_event_location'] ); $settings['enable_schema'] = ! empty( $_POST['enable_schema'] ); $settings['delete_data_on_uninstall'] = ! empty( $_POST['delete_data_on_uninstall'] ); update_option( 'threecal_settings', $settings ); echo '

' . esc_html__( 'Settings saved!', '3task-calendar' ) . '

'; } $settings = wp_parse_args( (array) get_option( 'threecal_settings', array() ), array( 'date_format' => get_option( 'date_format' ), 'time_format' => get_option( 'time_format' ), 'week_starts_on' => 1, 'show_event_time' => true, 'show_event_location' => true, 'enable_schema' => true, 'delete_data_on_uninstall' => false, ) ); ?>

/>
/>

[threecal]

[threecal view="month" category="1" location="2" theme="default" show_filters="true" show_legend="true" week_starts_on="1"]
view month
category 0
location 0
theme default
show_filters true
show_legend true
week_starts_on

[threecal_events]

[threecal_events category="1" location="2" limit="10" view="list" show_past="false" show_pagination="true" columns="3"]
category 0
location 0
limit 10
view list
show_past false
show_pagination true
columns 3

[threecal_upcoming]

[threecal_upcoming limit="5" category="1" show_date="true" show_time="true" show_location="true"]
limit 5
category 0
show_date true
show_time true
show_location true

[threecal_event id="123" show_map="true" show_description="true"]
id
show_map true
show_description true

[threecal_mini]

[threecal_mini category="1" show_nav="true" show_today="true" week_starts_on="1"]
category 0
show_nav true
show_today true
week_starts_on

  1. Widgets', '3task-calendar' ); ?>
  2. [threecal_upcoming limit="3"]


[threecal_mini] -
[threecal_upcoming limit="3"] -

' . esc_html__( 'support forum', '3task-calendar' ) . '' ); ?>

get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}threecal_events WHERE parent_id IS NULL OR parent_id = 0" ); $published_events = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}threecal_events WHERE status = 'published' AND (parent_id IS NULL OR parent_id = 0)" ); $draft_events = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}threecal_events WHERE status = 'draft' AND (parent_id IS NULL OR parent_id = 0)" ); $total_categories = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}threecal_categories" ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching return array( 'total_events' => $total_events ? $total_events : 0, 'published_events' => $published_events ? $published_events : 0, 'draft_events' => $draft_events ? $draft_events : 0, 'total_categories' => $total_categories ? $total_categories : 0, ); } /** * Get events */ private function get_events( $scope = 'all' ) { global $wpdb; $now = current_time( 'mysql' ); // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table for events. if ( 'upcoming' === $scope ) { $results = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}threecal_events WHERE (parent_id IS NULL OR parent_id = 0) AND (COALESCE(end_date, start_date) >= %s OR (recurrence_rule IS NOT NULL AND recurrence_rule <> '' AND COALESCE(recurrence_end, start_date) >= %s)) ORDER BY start_date ASC", $now, $now ) ); } elseif ( 'past' === $scope ) { $results = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}threecal_events WHERE (parent_id IS NULL OR parent_id = 0) AND COALESCE(end_date, start_date) < %s AND (recurrence_rule IS NULL OR recurrence_rule = '' OR COALESCE(recurrence_end, start_date) < %s) ORDER BY start_date DESC", $now, $now ) ); } else { $results = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}threecal_events WHERE parent_id IS NULL OR parent_id = 0 ORDER BY start_date ASC" ); } // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching return $results ? $results : array(); } /** * Get categories */ private function get_categories() { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table for categories. $results = $wpdb->get_results( " SELECT c.*, COUNT(e.id) as event_count FROM {$wpdb->prefix}threecal_categories c LEFT JOIN {$wpdb->prefix}threecal_events e ON c.id = e.category_id GROUP BY c.id ORDER BY c.name ASC " ); return $results ? $results : array(); } /** * Get locations */ private function get_locations() { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table for locations. $results = $wpdb->get_results( " SELECT * FROM {$wpdb->prefix}threecal_locations ORDER BY name ASC " ); return $results ? $results : array(); } /** * Render mini calendar */ private function render_mini_calendar() { $current_date = current_time('Y-m-d'); $events = $this->get_upcoming_events(5); if ( empty( $events ) ) { echo '

' . esc_html__( 'No upcoming events.', '3task-calendar' ) . '

'; return; } echo '
'; foreach ($events as $event) { echo '
'; echo '' . esc_html($event->title) . '
'; echo '' . esc_html( wp_date( 'M j, Y', strtotime( $event->start_date ) ) ) . ''; echo '
'; } echo '
'; } /** * Get upcoming events */ private function get_upcoming_events( $limit = 5 ) { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table for events. $results = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}threecal_events WHERE start_date >= %s AND status = 'published' ORDER BY start_date ASC LIMIT %d", current_time( 'Y-m-d' ), $limit ) ); return $results ? $results : array(); } /** * Register settings */ public function register_settings() { register_setting('threecal_settings', 'threecal_settings', array( 'sanitize_callback' => array($this, 'sanitize_settings') )); // Handle form submissions $this->handle_form_submissions(); } /** * Sanitize settings */ public function sanitize_settings($input) { $sanitized = array(); // General $sanitized['date_format'] = sanitize_text_field($input['date_format'] ?? get_option('date_format')); $sanitized['time_format'] = sanitize_text_field($input['time_format'] ?? get_option('time_format')); $sanitized['week_starts_on'] = absint($input['week_starts_on'] ?? 1); $sanitized['default_view'] = sanitize_text_field($input['default_view'] ?? 'month'); $sanitized['default_theme'] = sanitize_text_field($input['default_theme'] ?? 'default'); // Display $sanitized['show_event_time'] = !empty($input['show_event_time']); $sanitized['show_event_location'] = !empty($input['show_event_location']); $sanitized['show_event_description'] = !empty($input['show_event_description']); $sanitized['events_per_page'] = absint($input['events_per_page'] ?? 10); $sanitized['enable_event_popup'] = !empty($input['enable_event_popup']); // Google Maps $sanitized['google_maps_api_key'] = sanitize_text_field($input['google_maps_api_key'] ?? ''); $sanitized['default_map_zoom'] = absint($input['default_map_zoom'] ?? 14); $sanitized['default_map_type'] = sanitize_text_field($input['default_map_type'] ?? 'roadmap'); // Email $sanitized['enable_notifications'] = !empty($input['enable_notifications']); $sanitized['notification_sender_name'] = sanitize_text_field($input['notification_sender_name'] ?? ''); $sanitized['notification_sender_email'] = sanitize_email($input['notification_sender_email'] ?? ''); $sanitized['notification_template'] = wp_kses_post($input['notification_template'] ?? ''); // SEO $sanitized['enable_schema'] = !empty($input['enable_schema']); // Advanced $sanitized['delete_data_on_uninstall'] = !empty($input['delete_data_on_uninstall']); return $sanitized; } /** * Handle form submissions */ private function handle_form_submissions() { // Save event. if ( isset( $_POST['threecal_save_event'] ) && check_admin_referer( 'threecal_save_event' ) ) { $this->save_event(); } // Delete event. if ( isset( $_GET['action'] ) && 'delete' === $_GET['action'] && isset( $_GET['id'], $_GET['_wpnonce'] ) ) { $nonce = sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ); $id = absint( $_GET['id'] ); if ( wp_verify_nonce( $nonce, 'threecal_delete_' . $id ) ) { $this->delete_event( $id ); } } // Save location. if ( isset( $_POST['threecal_save_location'] ) && check_admin_referer( 'threecal_save_location' ) ) { $this->save_location(); } // Delete location. if ( isset( $_GET['action'] ) && 'delete_location' === $_GET['action'] && isset( $_GET['id'], $_GET['_wpnonce'] ) ) { $nonce = sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ); $id = absint( $_GET['id'] ); if ( wp_verify_nonce( $nonce, 'threecal_delete_location_' . $id ) ) { $this->delete_location( $id ); } } // Save category. if ( isset( $_POST['threecal_save_category'] ) && check_admin_referer( 'threecal_save_category' ) ) { $this->save_category(); } // Delete category. if ( isset( $_GET['action'] ) && 'delete_category' === $_GET['action'] && isset( $_GET['id'], $_GET['_wpnonce'] ) ) { $nonce = sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ); $id = absint( $_GET['id'] ); if ( wp_verify_nonce( $nonce, 'threecal_delete_category_' . $id ) ) { $this->delete_category( $id ); } } } /** * Save event */ private function save_event() { if ( ! current_user_can( 'create_threecal_events' ) && ! current_user_can( 'edit_threecal_events' ) ) { wp_die( esc_html__( 'Permission denied.', '3task-calendar' ) ); } // Nonce is already verified in handle_form_submissions() via check_admin_referer(). // phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce verified in handle_form_submissions. $event_id = isset( $_POST['event_id'] ) ? absint( $_POST['event_id'] ) : 0; if ( $event_id > 0 ) { $event = ThreeCal_Event::get( $event_id ); if ( ! $event ) { wp_die( esc_html__( 'Event not found.', '3task-calendar' ) ); } } else { $event = new ThreeCal_Event(); } // Remember where a repetition came from, so its date can be excluded from the series. $original_parent = (int) $event->parent_id; $original_day = substr( (string) $event->start_date, 0, 10 ); $event->title = isset( $_POST['event_title'] ) ? sanitize_text_field( wp_unslash( $_POST['event_title'] ) ) : ''; $event->description = isset( $_POST['event_description'] ) ? wp_kses_post( wp_unslash( $_POST['event_description'] ) ) : ''; $event->start_date = isset( $_POST['event_start_date'] ) ? sanitize_text_field( wp_unslash( $_POST['event_start_date'] ) ) : ''; $event->end_date = isset( $_POST['event_end_date'] ) ? sanitize_text_field( wp_unslash( $_POST['event_end_date'] ) ) : ''; $event->all_day = isset( $_POST['event_all_day'] ); $event->location_id = isset( $_POST['event_location'] ) ? absint( $_POST['event_location'] ) : 0; $event->url = isset( $_POST['event_url'] ) ? esc_url_raw( wp_unslash( $_POST['event_url'] ) ) : ''; $event->featured_image = isset( $_POST['event_featured_image'] ) ? absint( $_POST['event_featured_image'] ) : 0; $event->color = isset( $_POST['event_color'] ) ? ( sanitize_hex_color( wp_unslash( $_POST['event_color'] ) ) ?: '#3788d8' ) : '#3788d8'; $event->status = isset( $_POST['event_status'] ) ? sanitize_key( wp_unslash( $_POST['event_status'] ) ) : 'published'; if ( ! in_array( $event->status, array( 'draft', 'published', 'cancelled' ), true ) ) { $event->status = 'draft'; } // Normalise dates (datetime-local sends Y-m-d\TH:i) and reject invalid input. $event->start_date = $this->normalize_datetime( $event->start_date ); $event->end_date = $event->end_date ? $this->normalize_datetime( $event->end_date ) : ''; if ( '' === $event->title || '' === $event->start_date ) { wp_die( esc_html__( 'Please enter a title and a valid start date.', '3task-calendar' ), '', array( 'back_link' => true ) ); } // An end before the start is treated as "no end". if ( $event->end_date && $event->end_date < $event->start_date ) { $event->end_date = ''; } // Repeat pattern. Editing a single repetition detaches it from its series. if ( $event->parent_id ) { $event->parent_id = 0; $event->recurrence_rule = ''; $event->recurrence_end = ''; } else { $rule = isset( $_POST['event_recurrence'] ) ? sanitize_key( wp_unslash( $_POST['event_recurrence'] ) ) : ''; $event->recurrence_rule = array_key_exists( $rule, ThreeCal_Event::recurrence_options() ) ? $rule : ''; $until = isset( $_POST['event_recurrence_end'] ) ? $this->normalize_datetime( sanitize_text_field( wp_unslash( $_POST['event_recurrence_end'] ) ) ) : ''; $event->recurrence_end = ( $event->recurrence_rule && $until ) ? substr( $until, 0, 10 ) . ' 23:59:59' : ''; } if ( $event->save() ) { // Set categories. $categories = isset( $_POST['event_categories'] ) ? array_map( 'absint', $_POST['event_categories'] ) : array(); $event->set_categories( $categories ); // phpcs:enable WordPress.Security.NonceVerification.Missing // Create or update the repetitions of a series. $event->regenerate_series(); // A detached repetition becomes an exception of its series. if ( $original_parent && $original_day ) { $parent = ThreeCal_Event::get( $original_parent ); if ( $parent ) { $exdates = (array) $parent->get_setting( 'exdates', array() ); $exdates[] = $original_day; $parent->set_setting( 'exdates', array_values( array_unique( $exdates ) ) ); $parent->save(); } } // Redirect $redirect = add_query_arg(array( 'page' => '3task-calendar', 'tab' => 'events', 'action' => 'edit', 'event' => $event->id, 'message' => 'saved' ), admin_url('admin.php')); wp_safe_redirect( $redirect ); exit; } else { wp_die( esc_html__( 'Error saving event.', '3task-calendar' ) ); } } /** * Convert a submitted date to Y-m-d H:i:s or return an empty string. * * @param string $value Submitted value. * @return string */ private function normalize_datetime( $value ) { $value = trim( str_replace( 'T', ' ', (string) $value ) ); foreach ( array( 'Y-m-d H:i:s', 'Y-m-d H:i', 'Y-m-d' ) as $format ) { $date = DateTime::createFromFormat( '!' . $format, $value ); if ( $date && $date->format( $format ) === $value ) { return $date->format( 'Y-m-d H:i:s' ); } } return ''; } /** * Delete event */ private function delete_event($id) { if (!current_user_can('delete_threecal_events')) { wp_die( esc_html__( 'Permission denied.', '3task-calendar' ) ); } $event = ThreeCal_Event::get($id); if ($event && $event->delete()) { wp_safe_redirect( add_query_arg( array( 'page' => '3task-calendar', 'message' => 'deleted' ), admin_url('admin.php'))); exit; } } /** * Save location */ private function save_location() { if ( ! current_user_can( 'manage_threecal_locations' ) ) { wp_die( esc_html__( 'Permission denied.', '3task-calendar' ) ); } // Nonce is already verified in handle_form_submissions() via check_admin_referer(). // phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce verified in handle_form_submissions. $location_id = isset( $_POST['location_id'] ) ? absint( $_POST['location_id'] ) : 0; if ( $location_id > 0 ) { $location = ThreeCal_Location::get( $location_id ); if ( ! $location ) { wp_die( esc_html__( 'Location not found.', '3task-calendar' ) ); } } else { $location = new ThreeCal_Location(); } $location->name = isset( $_POST['location_name'] ) ? sanitize_text_field( wp_unslash( $_POST['location_name'] ) ) : ''; $location->address = isset( $_POST['location_address'] ) ? sanitize_text_field( wp_unslash( $_POST['location_address'] ) ) : ''; $location->city = isset( $_POST['location_city'] ) ? sanitize_text_field( wp_unslash( $_POST['location_city'] ) ) : ''; $location->postal_code = isset( $_POST['location_postal_code'] ) ? sanitize_text_field( wp_unslash( $_POST['location_postal_code'] ) ) : ''; $location->country = isset( $_POST['location_country'] ) ? sanitize_text_field( wp_unslash( $_POST['location_country'] ) ) : 'DE'; $location->phone = isset( $_POST['location_phone'] ) ? sanitize_text_field( wp_unslash( $_POST['location_phone'] ) ) : ''; $location->email = isset( $_POST['location_email'] ) ? sanitize_email( wp_unslash( $_POST['location_email'] ) ) : ''; $location->website = isset( $_POST['location_website'] ) ? esc_url_raw( wp_unslash( $_POST['location_website'] ) ) : ''; $location->description = isset( $_POST['location_description'] ) ? wp_kses_post( wp_unslash( $_POST['location_description'] ) ) : ''; $location->featured_image = isset( $_POST['location_featured_image'] ) ? absint( $_POST['location_featured_image'] ) : 0; // Geocode if API key is set. $do_geocode = isset( $_POST['location_geocode'] ) ? sanitize_text_field( wp_unslash( $_POST['location_geocode'] ) ) : ''; if ( $do_geocode ) { $location->geocode(); } else { $location->latitude = ( isset( $_POST['location_latitude'] ) && '' !== trim( sanitize_text_field( wp_unslash( $_POST['location_latitude'] ) ) ) ) ? floatval( $_POST['location_latitude'] ) : null; $location->longitude = ( isset( $_POST['location_longitude'] ) && '' !== trim( sanitize_text_field( wp_unslash( $_POST['location_longitude'] ) ) ) ) ? floatval( $_POST['location_longitude'] ) : null; } // phpcs:enable WordPress.Security.NonceVerification.Missing if ( $location->save() ) { wp_safe_redirect( add_query_arg( array( 'page' => '3task-calendar', 'tab' => 'locations', 'message' => 'saved', ), admin_url( 'admin.php' ) ) ); exit; } } /** * Delete location */ private function delete_location($id) { if (!current_user_can('manage_threecal_locations')) { wp_die( esc_html__( 'Permission denied.', '3task-calendar' ) ); } $location = ThreeCal_Location::get($id); if ($location && $location->delete()) { wp_safe_redirect( add_query_arg( array( 'page' => '3task-calendar', 'tab' => 'locations', 'message' => 'deleted' ), admin_url('admin.php'))); exit; } } /** * Save category */ private function save_category() { if ( ! current_user_can( 'manage_threecal_categories' ) ) { wp_die( esc_html__( 'Permission denied.', '3task-calendar' ) ); } // Nonce is already verified in handle_form_submissions() via check_admin_referer(). // phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce verified in handle_form_submissions. $category_id = isset( $_POST['category_id'] ) ? absint( $_POST['category_id'] ) : 0; if ( $category_id > 0 ) { $category = ThreeCal_Category::get( $category_id ); if ( ! $category ) { wp_die( esc_html__( 'Category not found.', '3task-calendar' ) ); } } else { $category = new ThreeCal_Category(); } $category->name = isset( $_POST['category_name'] ) ? sanitize_text_field( wp_unslash( $_POST['category_name'] ) ) : ''; $category->slug = isset( $_POST['category_slug'] ) ? sanitize_title( wp_unslash( $_POST['category_slug'] ) ) : ''; $category->description = isset( $_POST['category_description'] ) ? sanitize_textarea_field( wp_unslash( $_POST['category_description'] ) ) : ''; $category->color = isset( $_POST['category_color'] ) ? ( sanitize_hex_color( wp_unslash( $_POST['category_color'] ) ) ?: '#3788d8' ) : '#3788d8'; $category->parent_id = isset( $_POST['category_parent'] ) ? absint( $_POST['category_parent'] ) : 0; $category->sort_order = isset( $_POST['category_sort_order'] ) ? absint( $_POST['category_sort_order'] ) : 0; // phpcs:enable WordPress.Security.NonceVerification.Missing if ( $category->save() ) { wp_safe_redirect( add_query_arg( array( 'page' => '3task-calendar', 'tab' => 'categories', 'message' => 'saved', ), admin_url( 'admin.php' ) ) ); exit; } } /** * Delete category */ private function delete_category($id) { if (!current_user_can('manage_threecal_categories')) { wp_die( esc_html__( 'Permission denied.', '3task-calendar' ) ); } $category = ThreeCal_Category::get($id); if ($category && $category->delete()) { wp_safe_redirect( add_query_arg( array( 'page' => '3task-calendar', 'tab' => 'categories', 'message' => 'deleted' ), admin_url('admin.php'))); exit; } } /** * Render events page */ public function render_events_page() { include THREECAL_PLUGIN_DIR . 'admin/views/events-list.php'; } /** * Render edit event form (inline in events tab) */ private function render_edit_event() { include THREECAL_PLUGIN_DIR . 'admin/views/event-edit.php'; } /** * Render new event form (inline in events tab) */ private function render_new_event() { include THREECAL_PLUGIN_DIR . 'admin/views/event-edit.php'; } /** * Render edit event page */ public function render_edit_event_page() { include THREECAL_PLUGIN_DIR . 'admin/views/event-edit.php'; } /** * Render locations page */ public function render_locations_page() { include THREECAL_PLUGIN_DIR . 'admin/views/locations.php'; } /** * Render categories page */ public function render_categories_page() { include THREECAL_PLUGIN_DIR . 'admin/views/categories.php'; } /** * Render settings page */ public function render_settings_page() { include THREECAL_PLUGIN_DIR . 'admin/views/settings.php'; } }