add_rewrite_rules();
}
flush_rewrite_rules();
}
/**
* Enqueue admin assets.
*/
public function enqueue_assets( string $hook ) : void {
$allowed_hooks = [
'toplevel_page_webcalendar',
'webcalendar_page_webcalendar-categories',
'webcalendar_page_webcalendar-remote',
'webcalendar_page_webcalendar-holidays',
'webcalendar_page_webcalendar-shared',
'webcalendar_page_webcalendar-layers',
'webcalendar_page_webcalendar-submissions',
'webcalendar_page_webcalendar-places',
'webcalendar_page_webcalendar-extra-fields'
];
if ( !in_array( $hook, $allowed_hooks, true ) ) {
return;
}
wp_enqueue_media();
$asset_file = WEBCALENDAR_WP_PATH . 'build/index.asset.php';
if ( !file_exists( $asset_file ) ) {
return;
}
$assets = (require $asset_file);
wp_enqueue_script(
'webcalendar-admin',
WEBCALENDAR_WP_URL . 'build/index.js',
$assets['dependencies'],
$assets['version'],
true
);
// The admin bundle carries the bulk of the plugin's translatable JS.
// Without this, WordPress never loads the JSON catalogue for it and
// every __() in the React admin stays in English.
wp_set_script_translations( 'webcalendar-admin', 'agenticdaisy-calendar', WEBCALENDAR_WP_PATH . 'languages' );
// Build layer data for admin color matching (mirrors Plugin.php frontend logic)
$layers_data = [];
if ( is_user_logged_in() ) {
$layer_service = \WebCalendar\WP\Container::get_instance()->get_layer_service();
$layers = $layer_service->getLayersForUser( wp_get_current_user()->user_login );
foreach ( $layers as $layer ) {
$layer_name = $layer->layerUser();
$wp_layer_user = get_user_by( 'login', $layer->layerUser() );
if ( $wp_layer_user ) {
$layer_name = $wp_layer_user->display_name;
} else {
$resource_repo = \WebCalendar\WP\Container::get_instance()->get_resource_repository();
$resource = $resource_repo->findByLogin( $layer->layerUser() );
if ( $resource !== null ) {
$layer_name = $resource->name();
}
}
$layers_data[] = [
'layerUser' => $layer->layerUser(),
'color' => $layer->color(),
'showDuplicates' => $layer->showDuplicates(),
'name' => $layer_name,
];
}
}
wp_localize_script( 'webcalendar-admin', 'webcalApiSettings', [
'root' => esc_url_raw( rest_url() ),
'nonce' => wp_create_nonce( 'wp_rest' ),
'timeFormat' => webcal_time_format(),
'dateFormat' => webcal_date_format(),
'startOfWeek' => $this->start_of_week(),
'mapProvider' => get_option( 'webcal_map_provider', 'osm' ),
'googleMapsApiKey' => get_option( 'webcal_google_maps_api_key', '' ),
'currentUser' => wp_get_current_user()->user_login,
'canEditOthers' => current_user_can( 'webcal_can_edit_others' ),
'licenseTier' => \WebCalendar\WP\Container::get_instance()->get_license()->get_tier(),
'upgradeUrl' => esc_url_raw( $this->get_upgrade_url() ),
'layers' => $layers_data,
'features' => webcal_feature_flags(),
] );
// wp-components is declared as a dependency rather than merely enqueued,
// so WordPress loads it before our overrides.
//
// The React admin is built from @wordpress/components, and listing
// wp-components among the *script* dependencies does not load its
// stylesheet. WordPress 7.x enqueues that stylesheet by itself, which
// is why this was invisible: on 6.6 -- the version this plugin declares
// as its minimum -- it is registered and never enqueued, so every
// component renders unstyled. The event editor is the visible casualty:
// .components-modal__screen-overlay falls back to position: static and
// lays out in document flow instead of covering the viewport, leaving
// the admin menu (z-index: 9990) painted over the modal.
wp_enqueue_style(
'webcalendar-admin',
WEBCALENDAR_WP_URL . 'build/index.css',
['wp-components'],
$assets['version']
);
// Premium admin bundles. Each built file is stripped from the free build
// via @fs_premium_only, so enqueue_premium_bundle() no-ops there.
$premium_admin = [
'webcalendar_page_webcalendar-remote' => 'premium-remote-calendars',
'webcalendar_page_webcalendar-submissions' => 'premium-submissions',
];
if ( isset( $premium_admin[$hook] ) ) {
$this->enqueue_premium_bundle( $premium_admin[$hook] );
}
}
/**
* Enqueue a premium UI bundle if it exists in this build.
*
* Premium bundles (build/premium-*.js) are stripped from the auto-generated
* free build via the @fs_premium_only header, so file_exists() is false
* there and nothing is enqueued. The bundle depends on webcalendar-admin so
* React, webcalApiSettings, and apiFetch middleware are already available.
*
* @param string $name Bundle base name, e.g. "premium-remote-calendars".
*/
private function enqueue_premium_bundle( string $name ) : void {
$asset_path = WEBCALENDAR_WP_PATH . 'build/' . $name . '.asset.php';
$js_path = WEBCALENDAR_WP_PATH . 'build/' . $name . '.js';
if ( !file_exists( $asset_path ) || !file_exists( $js_path ) ) {
return;
}
$asset = (require $asset_path);
wp_enqueue_script(
'webcal-' . $name,
WEBCALENDAR_WP_URL . 'build/' . $name . '.js',
array_merge( $asset['dependencies'], ['webcalendar-admin'] ),
$asset['version'],
true
);
}
/**
* Add the menu page.
*/
public function add_menu_page() : void {
add_menu_page(
__( 'WebCalendar', 'agenticdaisy-calendar' ),
__( 'WebCalendar', 'agenticdaisy-calendar' ),
'webcal_can_view_calendar',
'webcalendar',
[$this, 'render_calendar_page'],
'dashicons-calendar-alt'
);
// Rename the auto-created first submenu item to "Calendar".
add_submenu_page(
'webcalendar',
__( 'Calendar', 'agenticdaisy-calendar' ),
__( 'Calendar', 'agenticdaisy-calendar' ),
'webcal_can_view_calendar',
'webcalendar',
[$this, 'render_calendar_page']
);
add_submenu_page(
'webcalendar',
__( 'Categories', 'agenticdaisy-calendar' ),
__( 'Categories', 'agenticdaisy-calendar' ),
'webcal_can_manage_categories',
'webcalendar-categories',
[$this, 'render_categories_page']
);
add_submenu_page(
'webcalendar',
__( 'Custom Fields', 'agenticdaisy-calendar' ),
__( 'Custom Fields', 'agenticdaisy-calendar' ),
'webcal_can_manage_categories',
'webcalendar-extra-fields',
[$this, 'render_extra_fields_page']
);
add_submenu_page(
'webcalendar',
__( 'Venues & Organizers', 'agenticdaisy-calendar' ),
__( 'Venues & Organizers', 'agenticdaisy-calendar' ),
'webcal_can_manage_categories',
'webcalendar-places',
[$this, 'render_places_page']
);
// Submissions submenu with pending count badge
if ( webcal_feature_available( 'frontend_submission' ) && function_exists( 'webcal_fs' ) ) {
}
}
/**
* Add the Settings submenu at a later priority so it appears last.
*/
public function add_settings_submenu() : void {
add_submenu_page(
'webcalendar',
__( 'Settings', 'agenticdaisy-calendar' ),
__( 'Settings', 'agenticdaisy-calendar' ),
'webcal_can_manage_settings',
'webcalendar-settings',
[$this, 'render_settings_page']
);
}
/**
* Render the full calendar page.
*/
public function render_calendar_page() : void {
?>
'string',
'sanitize_callback' => 'sanitize_text_field',
'default' => '12',
] );
register_setting( 'webcalendar_settings', 'webcal_config_DATE_FORMAT', [
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'default' => '',
] );
register_setting( 'webcalendar_settings', 'webcal_calendar_page', [
'type' => 'integer',
'sanitize_callback' => 'absint',
'default' => 0,
] );
register_setting( 'webcalendar_settings', 'webcal_single_event_pages', [
'type' => 'string',
'sanitize_callback' => [$this, 'sanitize_single_event_pages'],
'default' => 'off',
] );
register_setting( 'webcalendar_settings', 'webcal_map_provider', [
'type' => 'string',
'sanitize_callback' => [$this, 'sanitize_map_provider'],
'default' => 'osm',
] );
register_setting( 'webcalendar_settings', 'webcal_google_maps_api_key', [
'type' => 'string',
'sanitize_callback' => [$this, 'sanitize_google_maps_key'],
'default' => '',
] );
register_setting( 'webcalendar_settings', 'webcal_import_max_events', [
'type' => 'integer',
'sanitize_callback' => [$this, 'sanitize_import_max_events'],
'default' => 5000,
] );
register_setting( 'webcalendar_settings', 'webcal_delete_data_on_uninstall', [
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'default' => 'no',
] );
register_setting( 'webcalendar_settings', 'webcal_enable_mcp', [
'type' => 'string',
'sanitize_callback' => [$this, 'sanitize_enable_mcp'],
'default' => 'yes',
] );
register_setting( 'webcalendar_settings', 'webcal_mcp_read_only', [
'type' => 'string',
'sanitize_callback' => [$this, 'sanitize_yes_no'],
'default' => 'no',
] );
register_setting( 'webcalendar_settings', 'webcal_mcp_audience', [
'type' => 'string',
'sanitize_callback' => [$this, 'sanitize_mcp_audience'],
'default' => 'admins',
] );
register_setting( 'webcalendar_settings', 'webcal_frontend_submission', [
'type' => 'string',
'sanitize_callback' => [$this, 'sanitize_yes_no'],
'default' => 'no',
] );
register_setting( 'webcalendar_settings', 'webcal_submission_requires_approval', [
'type' => 'string',
'sanitize_callback' => [$this, 'sanitize_yes_no'],
'default' => 'yes',
] );
register_setting( 'webcalendar_settings', 'webcal_submission_show_location', [
'type' => 'string',
'sanitize_callback' => [$this, 'sanitize_yes_no'],
'default' => 'yes',
] );
register_setting( 'webcalendar_settings', 'webcal_popup_share_links', [
'type' => 'string',
'sanitize_callback' => [$this, 'sanitize_yes_no'],
'default' => 'yes',
] );
register_setting( 'webcalendar_settings', 'webcal_rate_limit', [
'type' => 'integer',
'sanitize_callback' => 'absint',
'default' => 100,
] );
register_setting( 'webcalendar_settings', 'webcal_rate_limit_period', [
'type' => 'integer',
'sanitize_callback' => 'absint',
'default' => 60,
] );
// Dev override fields only where the override can apply: WP_DEBUG on
// and a local or development environment (License::dev_override_allowed()).
if ( $this->dev_override_allowed() ) {
register_setting( 'webcalendar_settings', 'webcal_license_tier', [
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'default' => 'free',
] );
register_setting( 'webcalendar_settings', 'webcal_license_dev_override', [
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'default' => 'no',
] );
}
add_settings_section(
'webcalendar_general',
__( 'General Settings', 'agenticdaisy-calendar' ),
[$this, 'render_general_section'],
'webcalendar-settings'
);
add_settings_section(
'webcalendar_maps',
__( 'Maps', 'agenticdaisy-calendar' ),
[$this, 'render_maps_section'],
'webcalendar-settings'
);
add_settings_section(
'webcalendar_license',
__( 'License Settings', 'agenticdaisy-calendar' ),
[$this, 'render_license_section'],
'webcalendar-settings'
);
add_settings_field(
'webcal_time_format',
__( 'Time Format', 'agenticdaisy-calendar' ),
[$this, 'render_time_format_field'],
'webcalendar-settings',
'webcalendar_general'
);
add_settings_field(
'webcal_date_format',
__( 'Date Format', 'agenticdaisy-calendar' ),
[$this, 'render_date_format_field'],
'webcalendar-settings',
'webcalendar_general'
);
add_settings_field(
'webcal_single_event_pages',
__( 'Single Event Pages', 'agenticdaisy-calendar' ),
[$this, 'render_single_event_pages_field'],
'webcalendar-settings',
'webcalendar_general'
);
add_settings_field(
'webcal_calendar_page',
__( 'Calendar Page', 'agenticdaisy-calendar' ),
[$this, 'render_calendar_page_field'],
'webcalendar-settings',
'webcalendar_general'
);
add_settings_field(
'webcal_import_max_events',
__( 'Import Event Limit', 'agenticdaisy-calendar' ),
[$this, 'render_import_max_events_field'],
'webcalendar-settings',
'webcalendar_general'
);
add_settings_field(
'webcal_rate_limit',
__( 'API Rate Limit', 'agenticdaisy-calendar' ),
[$this, 'render_rate_limit_field'],
'webcalendar-settings',
'webcalendar_general'
);
add_settings_field(
'webcal_delete_data_on_uninstall',
__( 'Delete Data on Uninstall', 'agenticdaisy-calendar' ),
[$this, 'render_delete_data_field'],
'webcalendar-settings',
'webcalendar_general'
);
add_settings_field(
'webcal_enable_mcp',
__( 'AI/MCP Integration', 'agenticdaisy-calendar' ),
[$this, 'render_enable_mcp_field'],
'webcalendar-settings',
'webcalendar_general'
);
add_settings_field(
'webcal_mcp_read_only',
__( 'AI Read-Only Mode', 'agenticdaisy-calendar' ),
[$this, 'render_mcp_read_only_field'],
'webcalendar-settings',
'webcalendar_general'
);
add_settings_field(
'webcal_mcp_audience',
__( 'AI Access', 'agenticdaisy-calendar' ),
[$this, 'render_mcp_audience_field'],
'webcalendar-settings',
'webcalendar_general'
);
add_settings_section(
'webcalendar_submission',
__( 'Frontend Event Submission', 'agenticdaisy-calendar' ),
[$this, 'render_submission_section'],
'webcalendar-settings'
);
add_settings_field(
'webcal_frontend_submission',
__( 'Enable Frontend Submission', 'agenticdaisy-calendar' ),
[$this, 'render_frontend_submission_field'],
'webcalendar-settings',
'webcalendar_submission'
);
add_settings_field(
'webcal_submission_requires_approval',
__( 'Require Approval', 'agenticdaisy-calendar' ),
[$this, 'render_submission_approval_field'],
'webcalendar-settings',
'webcalendar_submission'
);
add_settings_field(
'webcal_submission_show_location',
__( 'Location Field', 'agenticdaisy-calendar' ),
[$this, 'render_submission_location_field'],
'webcalendar-settings',
'webcalendar_submission'
);
add_settings_field(
'webcal_popup_share_links',
__( 'Share Links in Popups', 'agenticdaisy-calendar' ),
[$this, 'render_popup_share_links_field'],
'webcalendar-settings',
'webcalendar_general'
);
add_settings_field(
'webcal_map_provider',
__( 'Map Provider', 'agenticdaisy-calendar' ),
[$this, 'render_map_provider_field'],
'webcalendar-settings',
'webcalendar_maps'
);
add_settings_field(
'webcal_google_maps_api_key',
__( 'Google Maps API Key', 'agenticdaisy-calendar' ),
[$this, 'render_google_maps_key_field'],
'webcalendar-settings',
'webcalendar_maps'
);
add_settings_field(
'webcal_license_status',
__( 'License Status', 'agenticdaisy-calendar' ),
[$this, 'render_license_status_field'],
'webcalendar-settings',
'webcalendar_license'
);
// Dev override fields only where the override can apply.
if ( $this->dev_override_allowed() ) {
add_settings_field(
'webcal_license_dev_override',
__( 'Dev Override', 'agenticdaisy-calendar' ),
[$this, 'render_dev_override_field'],
'webcalendar-settings',
'webcalendar_license'
);
add_settings_field(
'webcal_license_tier',
__( 'Dev Tier', 'agenticdaisy-calendar' ),
[$this, 'render_license_tier_field'],
'webcalendar-settings',
'webcalendar_license'
);
}
add_settings_section(
'webcalendar_feed',
__( 'iCal Subscription Feed', 'agenticdaisy-calendar' ),
[$this, 'render_feed_section'],
'webcalendar-settings'
);
add_settings_field(
'webcal_feed_urls',
__( 'Feed URLs', 'agenticdaisy-calendar' ),
[$this, 'render_feed_urls_field'],
'webcalendar-settings',
'webcalendar_feed'
);
add_settings_section(
'webcalendar_system_info',
__( 'System Info', 'agenticdaisy-calendar' ),
[$this, 'render_system_info_section'],
'webcalendar-settings'
);
}
/**
* Render general section description.
*/
public function render_general_section() : void {
echo '' . esc_html__( 'Configure basic calendar behavior.', 'agenticdaisy-calendar' ) . '
';
// The timezone itself lives in WordPress General Settings, but its effect
// on stored event times is a calendar concern, so surface it here too.
( new TimezoneCheck() )->render_notice();
}
/**
* Render maps section description.
*/
public function render_maps_section() : void {
echo '' . esc_html__( 'Configure how event location maps are displayed on single event pages and calendar popups.', 'agenticdaisy-calendar' ) . '
';
}
/**
* Render the map provider dropdown.
*/
public function render_map_provider_field() : void {
$value = get_option( 'webcal_map_provider', 'osm' );
$value = ( is_scalar( $value ) ? (string) $value : 'osm' );
?>
>
>
>
__( 'Please enter a Google Maps API key, or switch to OpenStreetMap.', 'agenticdaisy-calendar' ),
] );
}
/**
* Render Google Maps API key field.
*/
public function render_google_maps_key_field() : void {
$raw_val = get_option( 'webcal_google_maps_api_key', '' );
$value = ( is_scalar( $raw_val ) ? (string) $raw_val : '' );
?>
Google Cloud Console'
);
?>
>
>
>
>
>
' . esc_html__( 'Allow visitors to submit events from the frontend. Requires Business plan or higher.', 'agenticdaisy-calendar' ) . '';
return;
}
echo '' . esc_html__( 'Allow logged-in users to submit events from the frontend using the [webcalendar-submit] shortcode or Submit Event block.', 'agenticdaisy-calendar' ) . '
';
}
/**
* Render the frontend submission enable checkbox.
*/
public function render_frontend_submission_field() : void {
$available = webcal_feature_available( 'frontend_submission' );
$raw_val = get_option( 'webcal_frontend_submission', 'no' );
$checked = is_scalar( $raw_val ) && (string) $raw_val === 'yes';
?>
>
>
'webcal_calendar_page',
'selected' => ( is_scalar( $raw ) ? absint( $raw ) : 0 ),
'show_option_none' => esc_html__( 'Automatic (the page showing the calendar)', 'agenticdaisy-calendar' ),
'option_none_value' => '0',
] );
?>
>
>
>
>
>
' . esc_html__( 'Manage your WebCalendar license and features.', 'agenticdaisy-calendar' ) . '';
}
/**
* Render the license status field.
*/
public function render_license_status_field() : void {
$license = \WebCalendar\WP\Container::get_instance()->get_license();
$tier = $license->get_tier();
$tier_labels = [
'free' => __( 'Free', 'agenticdaisy-calendar' ),
'starter' => __( 'Starter', 'agenticdaisy-calendar' ),
'business' => __( 'Business', 'agenticdaisy-calendar' ),
'agency' => __( 'Agency', 'agenticdaisy-calendar' ),
];
$label = $tier_labels[$tier] ?? ucfirst( $tier );
echo '' . esc_html( $label ) . ' ';
if ( $license->is_freemius_active() ) {
$fs = $license->get_freemius();
if ( $fs !== null ) {
$account_url = $fs->get_account_url();
echo ' — ' . esc_html__( 'Manage Account', 'agenticdaisy-calendar' ) . ' ';
}
}
if ( $tier === 'free' ) {
$upgrade_url = $this->get_upgrade_url();
echo '' . esc_html__( 'Upgrade to unlock more features.', 'agenticdaisy-calendar' ) . '
';
}
}
/**
* Sanitize the single event pages setting to an allowed value.
*
* @param mixed $value Raw input.
* @return string 'off', 'public', or 'all'.
*/
public function sanitize_single_event_pages( $value ) : string {
$value = ( is_scalar( $value ) ? (string) $value : 'off' );
return ( in_array( $value, ['off', 'public', 'all'], true ) ? $value : 'off' );
}
/**
* Render the dev override checkbox (WP_DEBUG only).
*/
public function render_dev_override_field() : void {
$value = get_option( 'webcal_license_dev_override', 'no' );
$checked = is_scalar( $value ) && (string) $value === 'yes';
?>
>
>
>
>
>
get_license();
if ( $license->is_freemius_active() ) {
$fs = $license->get_freemius();
if ( $fs !== null ) {
return $fs->get_upgrade_url();
}
}
return \WebCalendar\WP\License\License::PRICING_URL;
}
/**
* Whether the dev tier override can apply on this site, so its fields are
* not offered where they would do nothing.
*/
private function dev_override_allowed() : bool {
return \WebCalendar\WP\Container::get_instance()->get_license()->dev_override_allowed();
}
/**
* Render time format field.
*/
public function render_time_format_field() : void {
$value = webcal_time_format();
?>
>
>
render_status_panel();
?>
get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM %i', $wpdb->prefix . 'webcal_entry' ) );
$category_count = (int) $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM %i', $wpdb->prefix . 'webcal_categories' ) );
$user_count = (int) $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(DISTINCT cal_login) FROM %i', $wpdb->prefix . 'webcal_entry_user' ) );
// Only count rows with a URL — internal shared/nonuser calendars
// (cal_url IS NULL) are not "remote" and the Remote Calendars admin
// page filters them out.
/** @psalm-suppress TooManyArguments -- wpdb::prepare is variadic; Psalm stub is fixed-arity. */
$remote_count = (int) $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM %i WHERE cal_url IS NOT NULL AND cal_url <> %s', $wpdb->prefix . 'webcal_nonuser_cals', '' ) );
// phpcs:enable
$next_sync = wp_next_scheduled( 'webcal_sync_remote_calendars' );
$last_sync_raw = get_option( 'webcal_last_sync_time', '' );
$last_sync = ( is_string( $last_sync_raw ) ? $last_sync_raw : '' );
?>
render_status_card( __( 'Total Events', 'agenticdaisy-calendar' ), number_format_i18n( $event_count ), 'calendar-alt' );
$this->render_status_card( __( 'Categories', 'agenticdaisy-calendar' ), number_format_i18n( $category_count ), 'category' );
$this->render_status_card( __( 'Active Users', 'agenticdaisy-calendar' ), number_format_i18n( $user_count ), 'groups' );
$this->render_status_card( __( 'Remote Calendars', 'agenticdaisy-calendar' ), number_format_i18n( $remote_count ), 'update' );
if ( $remote_count > 0 ) {
if ( !empty( $last_sync ) ) {
$sync_label = __( 'Last Sync', 'agenticdaisy-calendar' );
$sync_display = sprintf(
/* translators: %s is a human-readable time difference like "2 hours ago". */
__( '%s ago', 'agenticdaisy-calendar' ),
human_time_diff( intval( $last_sync ), time() )
);
} elseif ( $next_sync !== false ) {
$sync_label = __( 'Next Sync', 'agenticdaisy-calendar' );
$sync_display = sprintf(
/* translators: %s is a human-readable time until next sync like "45 minutes". */
__( 'in %s', 'agenticdaisy-calendar' ),
human_time_diff( time(), $next_sync )
);
} else {
$sync_label = __( 'Sync', 'agenticdaisy-calendar' );
$sync_display = __( 'Not scheduled', 'agenticdaisy-calendar' );
}
$this->render_status_card( $sync_label, $sync_display, 'clock' );
}
?>
' . esc_html__( 'Subscribe to your calendar from external apps like Google Calendar, Apple Calendar, or Outlook.', 'agenticdaisy-calendar' ) . '';
}
/**
* Render the system info section showing dependency versions.
*/
public function render_system_info_section() : void {
$health = new SiteHealth();
$fields = $health->get_version_fields();
echo '';
echo '';
foreach ( $fields as $field ) {
echo '';
echo '' . esc_html( $field['label'] ) . ' ';
echo '' . esc_html( $field['value'] ) . ' ';
echo ' ';
}
echo ' ';
echo '
';
}
/**
* Render the feed URLs field with public/personal URLs and regenerate button.
*/
public function render_feed_urls_field() : void {
$public_url = rest_url( 'webcal/v1/feed' );
$token_manager = new \WebCalendar\WP\FeedTokenManager();
$user_id = get_current_user_id();
$token = $token_manager->get_token( $user_id );
$personal_url = add_query_arg( 'token', $token, $public_url );
$webcal_public = preg_replace( '/^https?:/', 'webcal:', $public_url );
$webcal_personal = preg_replace( '/^https?:/', 'webcal:', $personal_url );
?>
regenerate_token( $user_id );
$redirect_url = add_query_arg( 'webcal_token_regenerated', '1', admin_url( 'admin.php?page=webcalendar-settings' ) );
wp_safe_redirect( (string) $redirect_url );
exit;
}
}