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'),
'prefill_no_url' => __('Please enter the address first.', '3task-calendar'),
'prefill_wait' => __('Reading the website …', '3task-calendar'),
'prefill_error' => __('The website could not be read.', '3task-calendar'),
/* translators: %s: list of field names */
'prefill_done' => __('Taken from the website: %s.', '3task-calendar'),
'prefill_nothing_new' => __('All fields were already filled in, nothing changed.', '3task-calendar'),
/* translators: %s: name and address of the location */
'prefill_location' => __('Location on the website: %s. Add it under Locations and choose it here.', '3task-calendar'),
/* translators: %s: image address */
'prefill_image' => __('Image on the website: %s', '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'],
),
'posts' => array(
'title' => __( 'Posts', '3task-calendar' ),
'icon' => 'article',
),
'import' => array(
'title' => __( 'Import', '3task-calendar' ),
'icon' => 'file-import',
),
'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();
?>
$tab ) : ?>
current_tab === $tab_key ); ?>
>
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 'posts':
ThreeCal_Post_Dates::render_settings_page();
break;
case 'import':
$this->render_import();
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' ) );
$pending = ThreeCal_Submit::pending_count();
if ( $pending ) {
echo '' . esc_html( sprintf(
/* translators: %s: number of submitted events */
_n( '%s event was submitted by a visitor and waits for your check.', '%s events were submitted by visitors and wait for your check.', $pending, '3task-calendar' ),
number_format_i18n( $pending )
) ) . ' ' . esc_html__( 'Show submitted events', '3task-calendar' ) . '
';
}
$new_url = admin_url( 'admin.php?page=3task-calendar&tab=events&action=new' );
?>
id ] ) ? $cats[ $event->id ] : array() );
$start = strtotime( $event->start_date );
$edit = admin_url( 'admin.php?page=3task-calendar&tab=events&action=edit&event=' . ( $event->parent_id ? $event->parent_id : $event->id ) );
$from = is_array( $event->settings ) ? $event->settings : json_decode( (string) $event->settings, true );
if ( ! empty( $from['post_id'] ) && get_edit_post_link( (int) $from['post_id'] ) ) {
$edit = get_edit_post_link( (int) $from['post_id'], 'raw' );
}
?>
is_month_only() ? 'Y' : 'j', $start ) ); ?>
title ); ?>
is_month_only() ) {
echo esc_html( $event->month_label() . ' · ' . ThreeCal_Event::tba_text() );
} else {
echo esc_html( $event->all_day ? __( 'All day', '3task-calendar' ) : date_i18n( threecal_time_format(), $start ) );
}
?>
parent_id ) : ?> ·
' . 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' ) );
?>
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', 'submitted' ), true ) ? $scope : 'upcoming';
/**
* Filters the events shown in the admin list.
*
* @param array $events Event rows.
* @param string $scope upcoming, past or all.
*/
$events = apply_filters( 'threecal_admin_events', $this->get_events( $scope ), $scope );
// Dates that come from posts are kept in the posts, not in this list.
$from_posts = 0;
foreach ( $events as $index => $row ) {
$row_settings = json_decode( (string) $row->settings, true );
if ( ! empty( $row_settings['post_id'] ) ) {
$from_posts++;
unset( $events[ $index ] );
}
}
$events = array_values( $events );
$row_settings_all = array();
foreach ( $events as $row ) {
$row_settings_all[ $row->id ] = json_decode( (string) $row->settings, true );
}
$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' ),
);
$pending_count = ThreeCal_Submit::pending_count();
if ( $pending_count || 'submitted' === $scope ) {
/* translators: %s: number of submitted events */
$scopes['submitted'] = sprintf( __( 'Submitted (%s)', '3task-calendar' ), number_format_i18n( $pending_count ) );
}
$status_labels = array(
'published' => __( 'Published', '3task-calendar' ),
'draft' => __( 'Draft', '3task-calendar' ),
'cancelled' => __( 'Cancelled', '3task-calendar' ),
);
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Message param is for display only.
$message = isset( $_GET['message'] ) ? sanitize_key( wp_unslash( $_GET['message'] ) ) : '';
?>
$type_settings ) {
$type_object = get_post_type_object( $type_key );
if ( $type_object ) {
echo ' ' . esc_html( $type_object->labels->name ) . ' ';
}
}
?>
id ] ) ? $cats[ $event->id ] : array();
$color = ThreeCal_Themes::event_color( $event, $ecats );
$start = strtotime( $event->start_date );
$edit_url = admin_url( 'admin.php?page=3task-calendar&tab=events&action=edit&event=' . $event->id );
$del_url = wp_nonce_url( admin_url( 'admin.php?page=3task-calendar&tab=events&action=delete&id=' . $event->id ), 'threecal_delete_' . $event->id );
?>
is_month_only() ? 'Y' : 'j', $start ) ); ?>
title ); ?>
id ][ ThreeCal_Submit::KEY ] ) ) : ?>
is_month_only() ) : ?>
month_label() . ' · ' . ThreeCal_Event::tba_text() ); ?>
all_day ? ' · ' . __( 'All day', '3task-calendar' ) : ' · ' . date_i18n( threecal_time_format(), $start ) ) ); ?>
recurrence_rule ) && isset( $rules[ $event->recurrence_rule ] ) ) : ?>
recurrence_rule ] ); ?>recurrence_end ) : ?>, recurrence_end ) ) ) ); ?>
name ); ?>
status ] ) ? $status_labels[ $event->status ] : $event->status ); ?>
' . 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'] ) ) : '';
$settings['time_format'] = isset( $_POST['time_format'] ) ? sanitize_text_field( wp_unslash( $_POST['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['external_links'] = ( isset( $_POST['external_links'] ) && 'follow' === $_POST['external_links'] ) ? 'follow' : 'nofollow';
$settings['ics_reminder'] = isset( $_POST['ics_reminder'] ) && in_array( absint( $_POST['ics_reminder'] ), self::reminder_choices_minutes(), true ) ? absint( $_POST['ics_reminder'] ) : 0;
$settings['submit_category'] = isset( $_POST['submit_category'] ) ? absint( $_POST['submit_category'] ) : 0;
$settings['submit_notify'] = ! empty( $_POST['submit_notify'] );
$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' => '',
'time_format' => '',
'week_starts_on' => 1,
'show_event_time' => true,
'show_event_location' => true,
'enable_schema' => true,
'external_links' => 'nofollow',
'ics_reminder' => 0,
'submit_category' => 0,
'submit_notify' => true,
'delete_data_on_uninstall' => false,
)
);
?>
render_schema_doktor(); ?>
render_llms(); ?>
llms_snippet();
?>
schema_report( 20 );
$labels = ThreeCal::schema_reason_labels();
$pages = threecal()->calendar_pages( 5 );
?>
label
*/
public static function reminder_choices() {
return array(
0 => __( 'No reminder', '3task-calendar' ),
30 => __( '30 minutes before', '3task-calendar' ),
60 => __( '1 hour before', '3task-calendar' ),
120 => __( '2 hours before', '3task-calendar' ),
1440 => __( '1 day before', '3task-calendar' ),
2880 => __( '2 days before', '3task-calendar' ),
);
}
/**
* Allowed reminder values. Since 1.7.0
*
* @return int[]
*/
public static function reminder_choices_minutes() {
return array_keys( self::reminder_choices() );
}
/**
* Render help tab
*/
private function render_help() {
?>
[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_post_date]
[threecal_events view="poster" category="1" columns="4"]
[threecal_events view="poster" month="current"]
[threecal_events view="poster" running="40"]
[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
Widgets', '3task-calendar' ); ?>
[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' );
// Events of which only the month is known count as upcoming until their month is over.
$month_start = substr( $now, 0, 7 ) . '-01 00:00:00';
$month_like = '%"' . ThreeCal_Event::PRECISION_KEY . '":"month"%';
// 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) OR (start_date >= %s AND settings LIKE %s))
ORDER BY start_date ASC",
$now,
$now,
$month_start,
$month_like
)
);
} 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)
AND NOT (start_date >= %s AND COALESCE(settings, '') LIKE %s)
ORDER BY start_date DESC",
$now,
$now,
$month_start,
$month_like
)
);
} elseif ( 'submitted' === $scope ) {
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}threecal_events WHERE (parent_id IS NULL OR parent_id = 0) AND status = 'draft' AND settings LIKE %s ORDER BY start_date ASC",
'%"' . ThreeCal_Submit::KEY . '":%'
)
);
} 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) {
$input = is_array($input) ? $input : array();
$themes = ThreeCal_Themes::all();
$theme = isset($input['default_theme']) ? sanitize_key($input['default_theme']) : 'default';
$view = isset($input['default_view']) ? sanitize_key($input['default_view']) : 'month';
$accent = isset($input['accent_color']) ? sanitize_hex_color($input['accent_color']) : '';
// Every key the plugin reads. A key missing here is lost on the next save.
$sanitized = array(
'date_format' => sanitize_text_field($input['date_format'] ?? ''),
'time_format' => sanitize_text_field($input['time_format'] ?? ''),
'week_starts_on' => (isset($input['week_starts_on']) && 0 === absint($input['week_starts_on'])) ? 0 : 1,
'default_view' => in_array($view, array('month', 'list'), true) ? $view : 'month',
'default_theme' => isset($themes[$theme]) ? $theme : 'default',
'accent_color' => $accent ? $accent : '',
'show_event_time' => isset($input['show_event_time']) ? !empty($input['show_event_time']) : true,
'show_event_location' => isset($input['show_event_location']) ? !empty($input['show_event_location']) : true,
'show_event_description' => isset($input['show_event_description']) ? !empty($input['show_event_description']) : true,
'events_per_page' => max(1, absint($input['events_per_page'] ?? 10)),
'enable_event_popup' => isset($input['enable_event_popup']) ? !empty($input['enable_event_popup']) : true,
'enable_schema' => isset($input['enable_schema']) ? !empty($input['enable_schema']) : true,
'external_links' => (isset($input['external_links']) && 'follow' === $input['external_links']) ? 'follow' : 'nofollow',
'ics_reminder' => isset($input['ics_reminder']) && in_array(absint($input['ics_reminder']), self::reminder_choices_minutes(), true) ? absint($input['ics_reminder']) : 0,
'submit_category' => absint($input['submit_category'] ?? 0),
'submit_notify' => isset($input['submit_notify']) ? !empty($input['submit_notify']) : true,
'delete_data_on_uninstall' => !empty($input['delete_data_on_uninstall']),
);
/**
* Filters the sanitized settings, so extensions can keep their own keys.
*
* @param array $sanitized Sanitized settings.
* @param array $input Raw settings.
*/
return apply_filters('threecal_sanitize_settings', $sanitized, $input);
}
/**
* Handle form submissions
*/
private function handle_form_submissions() {
// Sample file for the CSV import.
if ( isset( $_GET['threecal_import_sample'], $_GET['_wpnonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'threecal_import_sample' ) ) {
$this->send_import_sample();
}
// 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 );
}
}
// Cancel, restore or remove a single date of a series.
if ( isset( $_GET['threecal_date_action'], $_GET['series'], $_GET['date'], $_GET['_wpnonce'] ) ) {
$series_id = absint( $_GET['series'] );
$nonce = sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) );
if ( wp_verify_nonce( $nonce, 'threecal_series_' . $series_id ) ) {
$this->series_date_action(
$series_id,
sanitize_key( wp_unslash( $_GET['threecal_date_action'] ) ),
sanitize_text_field( wp_unslash( $_GET['date'] ) )
);
}
}
// 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';
$event->set_organizer(
isset( $_POST['event_organizer_name'] ) ? sanitize_text_field( wp_unslash( $_POST['event_organizer_name'] ) ) : '',
isset( $_POST['event_organizer_url'] ) ? esc_url_raw( wp_unslash( $_POST['event_organizer_url'] ), array( 'http', 'https' ) ) : ''
);
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 ) : '';
// Precision: the day is known, or only the month ("July 2027, date to be announced").
$precision = ( isset( $_POST['event_date_precision'] ) && 'month' === sanitize_key( wp_unslash( $_POST['event_date_precision'] ) ) ) ? 'month' : 'day';
if ( 'month' === $precision ) {
$event->start_date = self::normalize_month( isset( $_POST['event_start_month'] ) ? sanitize_text_field( wp_unslash( $_POST['event_start_month'] ) ) : '' );
if ( '' === $event->title || '' === $event->start_date ) {
wp_die( esc_html__( 'Please enter a title and a valid month, for example 2027-07.', '3task-calendar' ), '', array( 'back_link' => true ) );
}
$rule = isset( $_POST['event_recurrence'] ) ? sanitize_key( wp_unslash( $_POST['event_recurrence'] ) ) : '';
if ( '' !== $rule && ! $event->parent_id ) {
wp_die( esc_html__( 'An event of which only the month is known cannot repeat. Set "Repeat" to "Does not repeat".', '3task-calendar' ), '', array( 'back_link' => true ) );
}
}
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 ) );
}
$categories = isset( $_POST['event_categories'] ) ? array_map( 'absint', (array) $_POST['event_categories'] ) : array();
/**
* Filters whether the current user may save this event.
*
* @param bool $allowed Whether saving is allowed.
* @param ThreeCal_Event $event The event with the submitted values (ID 0 for a new one).
* @param int[] $categories Submitted category IDs.
*/
if ( ! apply_filters( 'threecal_user_can_save_event', true, $event, $categories ) ) {
wp_die( esc_html__( 'Permission denied.', '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' : '';
}
// Month only: first of the month, all day, no end, no repeat. Day: as before.
$event->set_date_precision( $precision );
if ( $event->save() ) {
// Set categories.
$event->set_categories( $categories );
// phpcs:enable WordPress.Security.NonceVerification.Missing
// Create or update the repetitions of a series.
$event->regenerate_series();
/**
* Fires after an event has been saved from the admin form.
*
* @param ThreeCal_Event $event The saved event.
* @param int[] $categories Category IDs.
*/
do_action( 'threecal_admin_event_saved', $event, $categories );
// 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 '';
}
/**
* Convert a month (YYYY-MM) to the first day of that month as Y-m-d H:i:s.
*
* @since 1.6.0
* @param string $value Submitted value.
* @return string Empty string for invalid input.
*/
public static function normalize_month( $value ) {
return ThreeCal_Event::parse_month( $value );
}
/**
* 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);
/**
* Filters whether the current user may delete this event.
*
* @param bool $allowed Whether deleting is allowed.
* @param ThreeCal_Event $event The event.
*/
if ( $event && ! apply_filters( 'threecal_user_can_delete_event', true, $event ) ) {
wp_die( esc_html__( 'Permission denied.', '3task-calendar' ), '', array( 'back_link' => true ) );
}
if ($event && $event->delete()) {
wp_safe_redirect( add_query_arg( array(
'page' => '3task-calendar',
'tab' => 'events',
'message' => 'deleted'
), admin_url('admin.php')));
exit;
}
}
/**
* Cancel, restore or remove a single date of a series.
*
* @param int $series_id ID of the series (its first event).
* @param string $action cancel, restore or remove.
* @param string $day Date (Y-m-d).
*/
private function series_date_action( $series_id, $action, $day ) {
if ( ! current_user_can( 'edit_threecal_events' ) ) {
wp_die( esc_html__( 'Permission denied.', '3task-calendar' ) );
}
$series = ThreeCal_Event::get( $series_id );
if ( ! $series || $series->parent_id || ! $series->recurrence_rule || ! preg_match( '/^\d{4}-\d{2}-\d{2}$/', $day ) || ! in_array( $action, array( 'cancel', 'restore', 'remove' ), true ) ) {
wp_die( esc_html__( 'This date could not be changed.', '3task-calendar' ), '', array( 'back_link' => true ) );
}
/** This filter is documented in admin/class-admin.php */
if ( ! apply_filters( 'threecal_user_can_save_event', true, $series, wp_list_pluck( ThreeCal_Event::get_categories( $series->id ), 'id' ) ) ) {
wp_die( esc_html__( 'Permission denied.', '3task-calendar' ), '', array( 'back_link' => true ) );
}
$target = $series->id;
$split = null;
foreach ( $series->get_split_events() as $candidate ) {
if ( substr( (string) $candidate->start_date, 0, 10 ) === $day ) {
$split = $candidate;
break;
}
}
if ( $split ) {
// A former first date that is a separate event now.
if ( 'remove' === $action ) {
$split->delete();
$ids = array_diff( array_map( 'absint', (array) $series->get_setting( 'split_dates', array() ) ), array( (int) $split->id ) );
$series->set_setting( 'split_dates', array_values( $ids ) );
$series->save();
} else {
$split->status = ( 'cancel' === $action && 'published' === $series->status ) ? 'cancelled' : $series->status;
$split->save();
}
} elseif ( substr( (string) $series->start_date, 0, 10 ) === $day ) {
// The first date is the series itself: the next date takes the series over.
$next = $series->split_off_first_date();
if ( 'remove' === $action ) {
$series->delete();
} elseif ( 'cancel' === $action ) {
$series->status = 'cancelled';
$series->save();
}
$target = $next ? $next : ( 'remove' === $action ? 0 : $series->id );
} elseif ( 'remove' === $action ) {
$series->remove_date( $day );
} else {
$series->set_date_cancelled( $day, 'cancel' === $action );
}
$args = array(
'page' => '3task-calendar',
'tab' => 'events',
'message' => 'date_' . $action,
);
if ( $target ) {
$args['action'] = 'edit';
$args['event'] = $target;
}
wp_safe_redirect( add_query_arg( $args, 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;
$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() ) {
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in handle_form_submissions.
ThreeCal_Category::set_schema_excluded( $category->id, ! empty( $_POST['category_no_schema'] ) );
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in handle_form_submissions.
ThreeCal_Category::set_highlighted( $category->id, ! empty( $_POST['category_highlight'] ) );
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in handle_form_submissions.
ThreeCal_Category::set_follow( $category->id, ! empty( $_POST['category_follow'] ) );
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()) {
ThreeCal_Category::set_schema_excluded($id, false);
wp_safe_redirect( add_query_arg( array(
'page' => '3task-calendar',
'tab' => 'categories',
'message' => 'deleted'
), admin_url('admin.php')));
exit;
}
}
/**
* Transient that holds a checked import between preview and import.
*
* @since 1.6.0
* @param int $user_id User ID.
* @param string $token Random token of the preview.
* @return string
*/
private static function import_transient( $user_id, $token ) {
return 'threecal_import_' . (int) $user_id . '_' . $token;
}
/**
* Importer with the rights of the current user: categories and locations
* are only created by users who may manage them.
*
* @since 1.6.0
* @return ThreeCal_Importer
*/
private function importer() {
return new ThreeCal_Importer(
array(
'create_categories' => current_user_can( 'manage_threecal_categories' ),
'create_locations' => current_user_can( 'manage_threecal_locations' ),
)
);
}
/**
* Check the uploaded CSV file.
*
* @since 1.6.0
* @return array|WP_Error Array( temporary path, file name ).
*/
private function uploaded_csv() {
// phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce checked in render_import() before this is called.
if ( empty( $_FILES['threecal_csv'] ) || ! is_array( $_FILES['threecal_csv'] ) ) {
return new WP_Error( 'threecal_import_nofile', __( 'Please choose a CSV file.', '3task-calendar' ) );
}
$error = isset( $_FILES['threecal_csv']['error'] ) ? (int) $_FILES['threecal_csv']['error'] : UPLOAD_ERR_NO_FILE;
$size = isset( $_FILES['threecal_csv']['size'] ) ? (int) $_FILES['threecal_csv']['size'] : 0;
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Temporary path from PHP, checked with is_uploaded_file() below.
$tmp = isset( $_FILES['threecal_csv']['tmp_name'] ) ? (string) $_FILES['threecal_csv']['tmp_name'] : '';
$name = isset( $_FILES['threecal_csv']['name'] ) ? sanitize_file_name( wp_unslash( $_FILES['threecal_csv']['name'] ) ) : '';
// phpcs:enable WordPress.Security.NonceVerification.Missing
if ( UPLOAD_ERR_NO_FILE === $error ) {
return new WP_Error( 'threecal_import_nofile', __( 'Please choose a CSV file.', '3task-calendar' ) );
}
if ( UPLOAD_ERR_INI_SIZE === $error || UPLOAD_ERR_FORM_SIZE === $error || $size > ThreeCal_Importer::MAX_BYTES ) {
/* translators: %s: maximum file size, for example "2 MB" */
return new WP_Error( 'threecal_import_size', sprintf( __( 'The file is larger than %s.', '3task-calendar' ), size_format( ThreeCal_Importer::MAX_BYTES ) ) );
}
if ( UPLOAD_ERR_OK !== $error || '' === $tmp || ! is_uploaded_file( $tmp ) ) {
return new WP_Error( 'threecal_import_upload', __( 'The upload did not work. Please try again.', '3task-calendar' ) );
}
if ( ! in_array( strtolower( pathinfo( $name, PATHINFO_EXTENSION ) ), array( 'csv', 'txt' ), true ) ) {
return new WP_Error( 'threecal_import_type', __( 'Please upload a file ending in .csv.', '3task-calendar' ) );
}
return array( $tmp, $name );
}
/**
* Send the sample CSV for the import.
*
* @since 1.6.0
*/
private function send_import_sample() {
if ( ! current_user_can( 'edit_threecal_events' ) ) {
wp_die( esc_html__( 'Permission denied.', '3task-calendar' ) );
}
nocache_headers();
header( 'Content-Type: text/csv; charset=utf-8' );
header( 'Content-Disposition: attachment; filename="3task-calendar-import-sample.csv"' );
echo ThreeCal_Importer::sample_csv(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- CSV download, not HTML.
exit;
}
/**
* Render import tab: upload a CSV file, check the preview, then import.
*
* @since 1.6.0
*/
private function render_import() {
if ( ! current_user_can( 'edit_threecal_events' ) ) {
echo '' . esc_html__( 'Permission denied.', '3task-calendar' ) . '
';
return;
}
$user_id = get_current_user_id();
$plan = null;
$result = null;
$token = '';
$error = '';
$file = '';
if ( isset( $_POST['threecal_import_preview'] ) ) {
check_admin_referer( 'threecal_import_upload' );
$status = ( isset( $_POST['threecal_import_status'] ) && 'publish' === sanitize_key( wp_unslash( $_POST['threecal_import_status'] ) ) ) ? 'publish' : 'draft';
$upload = $this->uploaded_csv();
$parsed = is_wp_error( $upload ) ? $upload : ThreeCal_Importer::read_file( $upload[0] );
if ( is_wp_error( $parsed ) ) {
$error = $parsed->get_error_message();
} else {
$file = $upload[1];
$plan = $this->importer()->analyze( $parsed, $status );
$token = wp_generate_password( 24, false );
// Only the checked plan is kept, on the server and for this user.
set_transient(
self::import_transient( $user_id, $token ),
array(
'user' => $user_id,
'file' => $file,
'plan' => $plan,
),
30 * MINUTE_IN_SECONDS
);
}
} elseif ( isset( $_POST['threecal_import_run'] ) ) {
$token = isset( $_POST['threecal_import_token'] ) ? preg_replace( '/[^A-Za-z0-9]/', '', sanitize_text_field( wp_unslash( $_POST['threecal_import_token'] ) ) ) : '';
check_admin_referer( 'threecal_import_run_' . $token );
$stored = '' !== $token ? get_transient( self::import_transient( $user_id, $token ) ) : false;
if ( ! is_array( $stored ) || (int) $stored['user'] !== $user_id || empty( $stored['plan'] ) ) {
$error = __( 'The preview has expired. Please upload the file again.', '3task-calendar' );
} else {
delete_transient( self::import_transient( $user_id, $token ) );
$plan = $stored['plan'];
$file = $stored['file'];
$result = $this->importer()->run( $plan );
}
}
$labels = array(
'new' => __( 'New', '3task-calendar' ),
'update' => __( 'Will be updated', '3task-calendar' ),
'error' => __( 'Error', '3task-calendar' ),
);
$sample_url = wp_nonce_url( admin_url( 'admin.php?page=3task-calendar&tab=import&threecal_import_sample=1' ), 'threecal_import_sample' );
$base_url = admin_url( 'admin.php?page=3task-calendar&tab=import' );
?>
import_key
title *
start *
end
all_day
url
description
category
location_name, address, postal_code, city, country
status
organizer_name, organizer_url