Import) and its form handlers.
*
* @package BeariusCalendar
*/
defined( 'ABSPATH' ) || exit;
/**
* Address of the Import screen.
*
* @param array $args Query arguments.
* @return string
*/
function bcal_import_url( $args = array() ) {
return add_query_arg( $args, admin_url( 'edit.php?post_type=' . BCAL_PT . '&page=bcal-settings&tab=import' ) );
}
/**
* Select with terms of a taxonomy.
*
* @param string $name Field name.
* @param string $taxonomy Taxonomy.
* @param int $selected Selected term ID.
* @param string $none Label of the empty option.
* @param string $id Element ID.
*/
function bcal_import_term_select( $name, $taxonomy, $selected, $none, $id = '' ) {
$terms = get_terms(
array(
'taxonomy' => $taxonomy,
'hide_empty' => false,
'number' => 500,
'orderby' => 'name',
)
);
echo '' . esc_html( $none ) . ' ';
if ( ! is_wp_error( $terms ) ) {
foreach ( $terms as $term ) {
echo 'term_id, false ) . '>' . esc_html( $term->name ) . ' ';
}
}
echo ' ';
}
/**
* Switch row.
*
* @param string $name Field name.
* @param bool $checked Checked.
* @param string $label Label.
* @param string $help Help text.
* @param string $attrs Extra attributes of the input.
*/
function bcal_import_switch( $name, $checked, $label, $help = '', $attrs = '' ) {
$id = 'bcal-' . md5( $name );
echo '
' . esc_html( $label ) . ' ';
if ( $help ) {
echo '
' . esc_html( $help ) . '
';
}
echo wp_kses( '
', bcal_kses_html() );
}
/**
* Segmented choice row.
*
* @param string $name Field name.
* @param string $value Current value.
* @param array $options value => label.
* @param string $label Label.
* @param string $help Help text.
*/
function bcal_import_seg( $name, $value, $options, $label, $help = '' ) {
echo '' . esc_html( $label ) . ' ';
if ( $help ) {
echo '
' . esc_html( $help ) . '
';
}
echo '
';
foreach ( $options as $key => $text ) {
echo '' . esc_html( $text ) . ' ';
}
echo '
';
}
/**
* Options shared by one-time imports and subscriptions.
*
* @param array $o Current values.
* @param string $context once|feed.
*/
function bcal_import_option_fields( $o, $context ) {
$o = array_merge( BCal_Import_ICS::defaults(), (array) $o );
echo '';
bcal_import_seg(
'bcal_opt[status_new]',
$o['status_new'],
array(
'publish' => __( 'Publish', 'bearius-calendar' ),
'pending' => __( 'Pending review', 'bearius-calendar' ),
'draft' => __( 'Draft', 'bearius-calendar' ),
),
__( 'New events', 'bearius-calendar' )
);
if ( 'feed' === $context ) {
bcal_import_seg(
'bcal_opt[on_removed]',
$o['on_removed'],
array(
'keep' => __( 'Keep', 'bearius-calendar' ),
'draft' => __( 'Move to drafts', 'bearius-calendar' ),
'cancelled' => __( 'Mark as cancelled', 'bearius-calendar' ),
),
__( 'Event disappears from the source', 'bearius-calendar' ),
__( 'Only future events within the dates the source still covers are affected.', 'bearius-calendar' )
);
}
echo '
' . esc_html__( 'Default category', 'bearius-calendar' ) . ' ' . esc_html__( 'For events without a category in the source.', 'bearius-calendar' ) . '
';
bcal_import_term_select( 'bcal_opt[default_category]', 'bcal_category', (int) $o['default_category'], __( '(none)', 'bearius-calendar' ), 'bcal-imp-cat' );
echo '
';
echo '
' . esc_html__( 'Default city', 'bearius-calendar' ) . ' ' . esc_html__( 'When the city cannot be recognised from the location.', 'bearius-calendar' ) . '
';
bcal_import_term_select( 'bcal_opt[default_city]', 'bcal_city', (int) $o['default_city'], __( '(none)', 'bearius-calendar' ), 'bcal-imp-city' );
echo '
';
bcal_import_seg(
'bcal_opt[cat_new]',
$o['cat_new'],
array(
'create' => __( 'Add them', 'bearius-calendar' ),
'skip' => __( 'Skip them', 'bearius-calendar' ),
),
__( 'Categories that do not exist yet', 'bearius-calendar' ),
__( 'Existing categories are matched by name and aliases first.', 'bearius-calendar' )
);
echo '
';
// Images.
$rights = isset( $o['rights'] ) && is_array( $o['rights'] ) ? $o['rights'] : array();
$granted = ! empty( $rights['time'] );
echo '';
bcal_import_switch( 'bcal_opt[images]', ! empty( $o['images'] ) && $granted, __( 'Download images', 'bearius-calendar' ), __( 'Images already in your media library are always reused, without downloading.', 'bearius-calendar' ), 'data-bcal-images-toggle' );
echo '
';
echo wp_kses( bcal_admin_icon( 'shield' ), bcal_kses_svg() );
echo '
';
if ( $granted ) {
$user = get_userdata( (int) $rights['user'] );
echo '
' . esc_html(
sprintf(
/* translators: 1: user name, 2: date, 3: website address */
__( 'Rights confirmed by %1$s on %2$s for %3$s.', 'bearius-calendar' ),
$user ? $user->display_name : '#' . (int) $rights['user'],
wp_date( (string) get_option( 'date_format' ), (int) $rights['time'] ),
isset( $rights['host'] ) ? $rights['host'] : ''
)
) . '
';
} else {
echo '
' . esc_html__( 'I confirm that I have the right to publish images from this source (my own material, a licence or the permission of the owner).', 'bearius-calendar' ) . ' ';
echo '
' . esc_html__( 'Your name, the date and the source address are saved with the confirmation. The plugin cannot check the rights itself.', 'bearius-calendar' ) . '
';
}
echo '
';
bcal_import_switch( 'bcal_opt[credit]', ! empty( $o['credit'] ), __( 'Caption with the source', 'bearius-calendar' ), __( 'Adds "Source: domain" to each downloaded image.', 'bearius-calendar' ) );
bcal_import_switch( 'bcal_opt[enrich]', ! empty( $o['enrich'] ), __( 'Fill in details from event pages', 'bearius-calendar' ), __( 'Reads the event page linked in the calendar: image, price, ticket link, address, organizer and status. Only empty fields are filled.', 'bearius-calendar' ) );
bcal_import_switch( 'bcal_opt[keep_images]', ! empty( $o['keep_images'] ), __( 'Keep images inside descriptions', 'bearius-calendar' ), __( 'Off: images loaded from other servers are removed from descriptions. They can track your visitors.', 'bearius-calendar' ) );
echo '
';
// Search engines and filters.
echo '';
bcal_import_seg(
'bcal_opt[seo]',
$o['seo'],
array(
'none' => __( 'Index normally', 'bearius-calendar' ),
'canonical' => __( 'Canonical to the original', 'bearius-calendar' ),
'noindex' => __( 'Do not index', 'bearius-calendar' ),
),
__( 'Search engines', 'bearius-calendar' ),
__( 'For events copied from other websites, pointing to the original avoids duplicate content.', 'bearius-calendar' )
);
bcal_import_switch( 'bcal_opt[show_source]', ! empty( $o['show_source'] ), __( 'Show the source on the event page', 'bearius-calendar' ), __( 'A "Source: domain" link under the dates.', 'bearius-calendar' ) );
bcal_import_switch( 'bcal_opt[past]', ! empty( $o['past'] ), __( 'Import past events too', 'bearius-calendar' ) );
echo '
' . esc_html__( 'Only events with these words', 'bearius-calendar' ) . ' ' . esc_html__( 'Comma or new line separated. Empty imports everything.', 'bearius-calendar' ) . '
';
echo '
' . esc_html__( 'Skip events with these words', 'bearius-calendar' ) . '
';
echo '
';
}
/**
* Reads options posted by the forms.
*
* @param array $in Raw input.
* @return array
*/
function bcal_import_read_options( $in ) {
$in = is_array( $in ) ? $in : array();
$pick = function ( $key, $allowed, $default ) use ( $in ) {
$value = isset( $in[ $key ] ) ? sanitize_key( (string) $in[ $key ] ) : '';
return in_array( $value, $allowed, true ) ? $value : $default;
};
$term = function ( $key, $taxonomy ) use ( $in ) {
$id = isset( $in[ $key ] ) ? absint( $in[ $key ] ) : 0;
return ( $id && term_exists( $id, $taxonomy ) ) ? $id : 0;
};
return array(
'status_new' => $pick( 'status_new', array( 'publish', 'pending', 'draft' ), 'publish' ),
'on_removed' => $pick( 'on_removed', array( 'keep', 'draft', 'cancelled' ), 'keep' ),
'images' => empty( $in['images'] ) ? 0 : 1,
'credit' => empty( $in['credit'] ) ? 0 : 1,
'enrich' => empty( $in['enrich'] ) ? 0 : 1,
'keep_images' => empty( $in['keep_images'] ) ? 0 : 1,
'seo' => $pick( 'seo', array( 'none', 'canonical', 'noindex' ), 'none' ),
'show_source' => empty( $in['show_source'] ) ? 0 : 1,
'past' => empty( $in['past'] ) ? 0 : 1,
'default_category' => $term( 'default_category', 'bcal_category' ),
'default_city' => $term( 'default_city', 'bcal_city' ),
'cat_new' => $pick( 'cat_new', array( 'create', 'skip' ), 'create' ),
'include' => isset( $in['include'] ) ? sanitize_textarea_field( (string) $in['include'] ) : '',
'exclude' => isset( $in['exclude'] ) ? sanitize_textarea_field( (string) $in['exclude'] ) : '', // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude -- Keyword filter for imported events, not a query argument.
);
}
/**
* Notices after form actions.
*/
function bcal_import_notices() {
$code = isset( $_GET['bcal_msg'] ) ? sanitize_key( wp_unslash( $_GET['bcal_msg'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( '' === $code ) {
return;
}
$messages = array(
'started' => array( 'success', __( 'Import started. It runs in the background, you can leave this page.', 'bearius-calendar' ) ),
'tec_none' => array( 'error', __( 'No data from The Events Calendar was found.', 'bearius-calendar' ) ),
'file_error' => array( 'error', __( 'The file could not be used. Choose an .ics calendar file up to 20 MB.', 'bearius-calendar' ) ),
'url_error' => array( 'error', __( 'Enter a valid calendar address (https:// or webcal://).', 'bearius-calendar' ) ),
'rights' => array( 'error', __( 'To download images, confirm that you have the right to publish them.', 'bearius-calendar' ) ),
'feed_saved' => array( 'success', __( 'Subscription saved.', 'bearius-calendar' ) ),
'feed_started' => array( 'success', __( 'Subscription saved. The first import has started.', 'bearius-calendar' ) ),
'feed_deleted' => array( 'success', __( 'Subscription removed.', 'bearius-calendar' ) ),
'feed_busy' => array( 'warning', __( 'This subscription is already being imported.', 'bearius-calendar' ) ),
'control' => array( 'success', __( 'Done.', 'bearius-calendar' ) ),
);
if ( isset( $messages[ $code ] ) ) {
echo '' . esc_html( $messages[ $code ][1] ) . '
';
}
}
/**
* Import tab.
*/
function bcal_render_import_tab() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
bcal_import_notices();
echo '';
$feed_id = isset( $_GET['feed'] ) ? sanitize_key( wp_unslash( $_GET['feed'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( '' !== $feed_id ) {
bcal_render_feed_editor( $feed_id );
bcal_render_import_history();
echo '
';
return;
}
bcal_render_import_history();
bcal_render_import_tec();
bcal_render_import_ics();
bcal_render_import_feeds();
echo '';
}
/**
* Section header.
*
* @param string $title Title.
* @param string $desc Description.
* @param string $icon Icon.
*/
function bcal_import_section_head( $title, $desc, $icon ) {
echo wp_kses( '' . bcal_admin_icon( $icon ) . ' ' . esc_html( $title ) . '', bcal_kses_html() );
if ( $desc ) {
echo '
' . esc_html( $desc ) . '
';
}
echo '
';
}
/**
* The Events Calendar card.
*/
function bcal_render_import_tec() {
$info = BCal_Import_TEC::detect();
echo '';
bcal_import_section_head( __( 'From The Events Calendar', 'bearius-calendar' ), __( 'Events are converted in place: addresses, images, comments and SEO data stay. The original data is not changed, so the conversion can be undone.', 'bearius-calendar' ), 'import' );
echo '';
if ( ! $info['events'] && ! $info['venues'] && ! $info['organizers'] ) {
echo '
';
echo $info['converted'] ? esc_html( sprintf( /* translators: %d: number of events */ _n( '%d event was converted from The Events Calendar.', '%d events were converted from The Events Calendar.', $info['converted'], 'bearius-calendar' ), $info['converted'] ) ) : esc_html__( 'No data from The Events Calendar was found on this site.', 'bearius-calendar' );
echo '
';
return;
}
$stats = array(
array( $info['events'], __( 'Events', 'bearius-calendar' ) ),
array( $info['recurring'] + $info['occurrence'], __( 'Recurring or series dates', 'bearius-calendar' ) ),
array( $info['images'], __( 'With an image', 'bearius-calendar' ) ),
array( $info['venues'], __( 'Venues', 'bearius-calendar' ) ),
array( $info['organizers'], __( 'Organizers', 'bearius-calendar' ) ),
array( $info['categories'], __( 'Categories', 'bearius-calendar' ) ),
);
echo '';
foreach ( $stats as $stat ) {
echo '
' . esc_html( number_format_i18n( $stat[0] ) ) . ' ' . esc_html( $stat[1] ) . '
';
}
echo '
';
$notes = array();
if ( $info['active'] ) {
$notes[] = array( 'warn', __( 'The Events Calendar is active. Deactivate it after the conversion so both plugins do not use the same addresses.', 'bearius-calendar' ) );
}
if ( $info['listed'] ) {
/* translators: %d: number of events */
$notes[] = array( 'info', sprintf( _n( '%d repeat rule is not supported: its dates will be saved as a list of dates.', '%d repeat rules are not supported: their dates will be saved as lists of dates.', $info['listed'], 'bearius-calendar' ), $info['listed'] ) );
}
if ( $info['occurrence'] ) {
$notes[] = array( 'info', __( 'Older recurring series store every date as a separate post. Their dates are added to the main event; the separate posts are left untouched.', 'bearius-calendar' ) );
}
if ( $info['tagged'] ) {
$notes[] = array( 'info', __( 'Tags stay in the database but are not shown for events.', 'bearius-calendar' ) );
}
if ( $info['converted'] ) {
/* translators: %d: number of events */
$notes[] = array( 'info', sprintf( _n( '%d event was already converted earlier.', '%d events were already converted earlier.', $info['converted'], 'bearius-calendar' ), $info['converted'] ) );
}
foreach ( $notes as $note ) {
echo wp_kses( '
' . bcal_admin_icon( 'warn' === $note[0] ? 'alert' : 'check' ) . '
' . esc_html( $note[1] ) . '
', bcal_kses_html() );
}
echo '
';
echo '';
}
/**
* One-time .ics import.
*/
function bcal_render_import_ics() {
echo '';
bcal_import_section_head( __( 'Import an .ics calendar', 'bearius-calendar' ), __( 'A calendar file or address, imported once. Running the same calendar again updates events instead of adding them twice.', 'bearius-calendar' ), 'when' );
echo ' ';
}
/**
* Subscriptions list.
*/
function bcal_render_import_feeds() {
$feeds = BCal_Import_Feeds::all();
$intervals = BCal_Import_Feeds::intervals();
echo '';
bcal_import_section_head( __( 'Subscriptions', 'bearius-calendar' ), __( 'Calendars checked on a schedule: new events are added and changed ones updated. Fields you edit by hand are never overwritten.', 'bearius-calendar' ), 'feed' );
echo '';
if ( ! $feeds ) {
echo '
' . esc_html__( 'No subscriptions yet.', 'bearius-calendar' ) . '
';
} else {
echo '
';
foreach ( $feeds as $feed ) {
$state = (array) $feed['state'];
$host = (string) wp_parse_url( $feed['url'], PHP_URL_HOST );
$name = '' !== $feed['name'] ? $feed['name'] : $host;
echo '';
echo wp_kses( '' . bcal_admin_icon( 'feed' ) . ' ', bcal_kses_html() );
echo '' . esc_html( $name ) . ' ';
echo '' . esc_html( $host ) . ' · ' . esc_html( isset( $intervals[ $feed['interval'] ] ) ? $intervals[ $feed['interval'] ][1] : '' ) . ' ';
echo '';
if ( ! $feed['enabled'] ) {
echo '' . esc_html__( 'Paused', 'bearius-calendar' ) . ' ';
} elseif ( ! empty( $state['error'] ) ) {
echo '' . esc_html__( 'Last check failed', 'bearius-calendar' ) . ' ';
} elseif ( ! empty( $state['last_ok'] ) ) {
/* translators: %s: time ago such as "5 minutes" */
echo wp_kses( '' . bcal_admin_icon( 'check' ) . esc_html( sprintf( __( 'Checked %s ago', 'bearius-calendar' ), human_time_diff( (int) $state['last_ok'] ) ) ) . ' ', bcal_kses_html() );
} else {
echo '' . esc_html__( 'Not checked yet', 'bearius-calendar' ) . ' ';
}
echo ' ';
echo '';
echo '', bcal_kses_html() );
echo '' . esc_html__( 'Settings', 'bearius-calendar' ) . ' ';
echo ' ';
}
echo ' ';
}
echo wp_kses( '
', bcal_kses_html() );
echo '
';
}
/**
* Subscription editor.
*
* @param string $feed_id Subscription ID or "new".
*/
function bcal_render_feed_editor( $feed_id ) {
$feed = 'new' === $feed_id ? BCal_Import_Feeds::defaults() : BCal_Import_Feeds::get( $feed_id );
$intervals = BCal_Import_Feeds::intervals();
if ( ! $feed ) {
echo '' . esc_html__( 'This subscription does not exist.', 'bearius-calendar' ) . '
';
return;
}
echo '← ' . esc_html__( 'All import options', 'bearius-calendar' ) . '
';
echo '';
bcal_import_section_head( 'new' === $feed_id ? __( 'New subscription', 'bearius-calendar' ) : __( 'Subscription settings', 'bearius-calendar' ), __( 'Paste the calendar address (.ics or webcal). The first import starts when you save.', 'bearius-calendar' ), 'feed' );
echo '';
echo '
';
if ( 'new' !== $feed_id ) {
echo '
', bcal_kses_html() );
}
echo '
';
}
/**
* Import history with progress, summaries and actions.
*/
function bcal_render_import_history() {
// Quiet subscription checks: only the latest one per subscription is shown.
$sessions = array();
$quiet = array();
foreach ( BCal_Import::sessions( true ) as $session ) {
if ( 'approved' === $session['status'] && in_array( $session['message'], array( 'no_changes', 'not_modified' ), true ) ) {
if ( isset( $quiet[ $session['feed'] ] ) ) {
continue;
}
$quiet[ $session['feed'] ] = true;
}
$sessions[] = $session;
}
$sessions = array_slice( $sessions, 0, 15 );
if ( ! $sessions ) {
return;
}
$counts = BCal_Import::count_labels();
$warnings = BCal_Import::warning_labels();
$active = false;
echo '';
bcal_import_section_head( __( 'Imports', 'bearius-calendar' ), __( 'Imports run in the background in small batches. Until you approve an import, everything it added or changed can be undone.', 'bearius-calendar' ), 'repeat' );
echo '';
foreach ( $sessions as $session ) {
$state = BCal_Import::public_state( $session );
$running = in_array( $session['status'], array( 'queued', 'running', 'undoing', 'paused' ), true );
$active = $active || in_array( $session['status'], array( 'queued', 'running', 'undoing' ), true );
$icon = 'tec' === $session['source'] ? 'import' : ( '' !== $session['feed'] ? 'feed' : 'when' );
echo '';
echo wp_kses( '' . bcal_admin_icon( $icon ) . ' ', bcal_kses_html() );
echo '' . esc_html( $session['label'] ) . ' ' . esc_html( wp_date( (string) get_option( 'date_format' ) . ' ' . (string) get_option( 'time_format' ), (int) $session['created'] ) ) . ' ';
echo '' . esc_html( $state['label'] ) . '
';
if ( $running ) {
echo '
';
echo '
' . esc_html( $state['phase'] ) . ' ' . ( $state['total'] ? esc_html( number_format_i18n( $state['done'] ) . ' / ' . number_format_i18n( $state['total'] ) ) : '' ) . ' ';
}
if ( 'not_modified' === $session['message'] ) {
echo '' . esc_html__( 'The calendar has not changed since the last check.', 'bearius-calendar' ) . '
';
} elseif ( 'no_changes' === $session['message'] ) {
echo '' . esc_html__( 'Checked: no new or changed events.', 'bearius-calendar' ) . '
';
} elseif ( 'failed' === $session['status'] && $session['message'] ) {
echo '' . esc_html( $session['message'] ) . '
';
}
if ( 'undone' === $session['status'] ) {
/* translators: 1: number of removed items, 2: number of kept items */
echo '' . esc_html( sprintf( __( 'Undone: %1$d events restored or removed, %2$d kept because they were edited.', 'bearius-calendar' ), (int) ( isset( $session['undo']['removed'] ) ? $session['undo']['removed'] : 0 ), (int) ( isset( $session['undo']['kept'] ) ? $session['undo']['kept'] : 0 ) ) ) . '
';
}
$shown = array();
foreach ( $counts as $key => $label ) {
if ( ! empty( $session['counts'][ $key ] ) ) {
$shown[] = ' ' . esc_html( $label ) . ' ' . esc_html( number_format_i18n( (int) $session['counts'][ $key ] ) ) . ' ';
}
}
if ( $shown ) {
echo wp_kses( '' . implode( '', $shown ) . ' ', bcal_kses_html() );
}
if ( $session['warnings'] ) {
$total = 0;
foreach ( $session['warnings'] as $warning ) {
$total += (int) $warning['count'];
}
/* translators: %d: number of notes */
echo wp_kses( '' . bcal_admin_icon( 'alert' ) . esc_html( sprintf( _n( '%d note', '%d notes', $total, 'bearius-calendar' ), $total ) ) . ' ', bcal_kses_html() );
foreach ( $session['warnings'] as $code => $warning ) {
echo '' . esc_html( isset( $warnings[ $code ] ) ? $warnings[ $code ] : $code ) . ' ' . esc_html( number_format_i18n( (int) $warning['count'] ) ) . ' ';
if ( ! empty( $warning['samples'] ) ) {
echo '' . esc_html( implode( ' · ', $warning['samples'] ) ) . ' ';
}
echo ' ';
}
echo ' ';
}
// Actions.
$actions = array();
if ( in_array( $session['status'], array( 'queued', 'running', 'undoing' ), true ) ) {
$actions[] = array( 'pause', __( 'Pause', 'bearius-calendar' ), 'pause', '' );
}
if ( 'paused' === $session['status'] ) {
$actions[] = array( 'resume', __( 'Resume', 'bearius-calendar' ), 'play', '' );
}
$paused_undo = 'paused' === $session['status'] && isset( $session['undo']['paused_from'] ) && 'undoing' === $session['undo']['paused_from'];
if ( in_array( $session['status'], array( 'queued', 'running' ), true ) || ( 'paused' === $session['status'] && ! $paused_undo ) ) {
$actions[] = array( 'cancel', __( 'Stop', 'bearius-calendar' ), 'close', __( 'Stop this import? What was imported so far stays and can be undone.', 'bearius-calendar' ) );
}
if ( in_array( $session['status'], array( 'done', 'cancelled', 'failed' ), true ) ) {
$actions[] = array( 'undo', __( 'Undo', 'bearius-calendar' ), 'undo', __( 'Undo this import? Everything it added is removed and converted events go back to where they came from.', 'bearius-calendar' ) );
$actions[] = array( 'approve', __( 'Approve', 'bearius-calendar' ), 'check', __( 'Approve this import? It can no longer be undone afterwards.', 'bearius-calendar' ) );
}
if ( $actions ) {
echo '';
foreach ( $actions as $action ) {
echo '', bcal_kses_html() );
}
echo '
';
}
echo '';
}
echo ' ';
if ( $active ) {
echo ' ';
}
}
/* --------------------------------------------------------------------------
Handlers
-------------------------------------------------------------------------- */
/**
* Checks permission. Each handler verifies its own nonce right after.
*/
function bcal_import_guard() {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'You are not allowed to do this.', 'bearius-calendar' ), 403 );
}
}
/**
* Moves an uploaded calendar file into the private import folder through
* the WordPress upload API. The file type is checked by the caller (name
* and BEGIN:VCALENDAR header), so the MIME sniffing of the upload API is
* skipped: many servers report calendar files as plain text.
*
* @param array $file Entry from $_FILES.
* @param string $dir Import folder with a trailing slash.
* @return string Full path, or an empty string on failure.
*/
function bcal_import_store_upload( $file, $dir ) {
if ( ! function_exists( 'wp_handle_upload' ) ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
}
$folder = untrailingslashit( $dir );
$name = 'upload-' . strtolower( wp_generate_password( 12, false, false ) ) . '.ics';
$to_dir = static function ( $uploads ) use ( $folder ) {
$uploads['path'] = $folder;
$uploads['subdir'] = '';
$uploads['url'] = '';
$uploads['error'] = false;
return $uploads;
};
add_filter( 'upload_dir', $to_dir );
$moved = wp_handle_upload(
$file,
array(
'test_form' => false,
'test_type' => false,
'unique_filename_callback' => static function () use ( $name ) {
return $name;
},
)
);
remove_filter( 'upload_dir', $to_dir );
if ( ! is_array( $moved ) || ! empty( $moved['error'] ) || empty( $moved['file'] ) ) {
return '';
}
return (string) $moved['file'];
}
/**
* Back to the Import screen with a message.
*
* @param string $message Message code.
* @param array $args Extra arguments.
*/
function bcal_import_back( $message, $args = array() ) {
wp_safe_redirect( bcal_import_url( array_merge( array( 'bcal_msg' => $message ), $args ) ) );
exit;
}
add_action(
'admin_post_bcal_import_tec',
function () {
bcal_import_guard();
check_admin_referer( 'bcal_import_tec' );
$info = BCal_Import_TEC::detect();
if ( ! $info['events'] && ! $info['venues'] && ! $info['organizers'] ) {
bcal_import_back( 'tec_none' );
}
BCal_Import::create(
'tec',
__( 'The Events Calendar', 'bearius-calendar' ),
array(
'use_slugs' => empty( $_POST['use_slugs'] ) ? 0 : 1,
'slug_archive' => isset( $_POST['slug_archive'] ) ? sanitize_title( wp_unslash( $_POST['slug_archive'] ) ) : '',
'slug_single' => isset( $_POST['slug_single'] ) ? sanitize_title( wp_unslash( $_POST['slug_single'] ) ) : '',
'website_to_ticket' => empty( $_POST['website_to_ticket'] ) ? 0 : 1,
)
);
bcal_import_back( 'started' );
}
);
add_action(
'admin_post_bcal_import_ics',
function () {
bcal_import_guard();
check_admin_referer( 'bcal_import_ics' );
$raw = isset( $_POST['bcal_opt'] ) && is_array( $_POST['bcal_opt'] ) ? wp_unslash( $_POST['bcal_opt'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$options = bcal_import_read_options( $raw );
$source = isset( $_POST['source'] ) && 'file' === $_POST['source'] ? 'file' : 'url';
if ( 'url' === $source ) {
$url = BCal_Import_ICS::normalize_url( isset( $_POST['url'] ) ? wp_unslash( $_POST['url'] ) : '' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
if ( ! $url ) {
bcal_import_back( 'url_error' );
}
$options['mode'] = 'url';
$options['url'] = $url;
$host = (string) wp_parse_url( $url, PHP_URL_HOST );
$label = $host;
} else {
$file = isset( $_FILES['bcal_file'] ) ? $_FILES['bcal_file'] : null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
$dir = BCal_Import::work_dir();
$name = $file && isset( $file['name'] ) ? sanitize_file_name( (string) $file['name'] ) : '';
$ok = $file && $dir && isset( $file['error'], $file['tmp_name'], $file['size'] ) && UPLOAD_ERR_OK === (int) $file['error'] && (int) $file['size'] > 0 && (int) $file['size'] <= 20 * MB_IN_BYTES && is_uploaded_file( $file['tmp_name'] ) && preg_match( '/\.(ics|ical|ifb|txt)$/i', $name );
if ( ! $ok ) {
bcal_import_back( 'file_error' );
}
$head = (string) file_get_contents( $file['tmp_name'], false, null, 0, 2048 ); // phpcs:ignore WordPress.WP.AlternativeFunctions
if ( 0 !== stripos( preg_replace( '/^\xEF\xBB\xBF/', '', ltrim( $head ) ), 'BEGIN:VCALENDAR' ) ) {
bcal_import_back( 'file_error' );
}
$target = bcal_import_store_upload( $file, $dir );
if ( '' === $target ) {
bcal_import_back( 'file_error' );
}
$options['mode'] = 'file';
$options['file'] = wp_basename( $target );
$host = __( 'file', 'bearius-calendar' );
$label = $name;
}
if ( $options['images'] ) {
if ( empty( $raw['rights_confirm'] ) ) {
bcal_import_back( 'rights' );
}
$options['rights'] = array(
'user' => get_current_user_id(),
'time' => time(),
'host' => $host,
);
}
BCal_Import::create( 'ics', $label, $options );
bcal_import_back( 'started' );
}
);
add_action(
'admin_post_bcal_feed_save',
function () {
bcal_import_guard();
check_admin_referer( 'bcal_feed_save' );
$id = isset( $_POST['id'] ) ? sanitize_key( wp_unslash( $_POST['id'] ) ) : '';
$feed = $id ? BCal_Import_Feeds::get( $id ) : null;
$feed = $feed ? $feed : BCal_Import_Feeds::defaults();
$new = empty( $feed['id'] );
$url = BCal_Import_ICS::normalize_url( isset( $_POST['url'] ) ? wp_unslash( $_POST['url'] ) : '' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
if ( ! $url ) {
bcal_import_back( 'url_error', array( 'feed' => $new ? 'new' : $feed['id'] ) );
}
$raw = isset( $_POST['bcal_opt'] ) && is_array( $_POST['bcal_opt'] ) ? wp_unslash( $_POST['bcal_opt'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$host = (string) wp_parse_url( $url, PHP_URL_HOST );
$feed = array_merge( $feed, bcal_import_read_options( $raw ) );
$feed['name'] = isset( $_POST['name'] ) ? sanitize_text_field( wp_unslash( $_POST['name'] ) ) : '';
$feed['enabled'] = empty( $_POST['enabled'] ) ? 0 : 1;
$interval = isset( $_POST['interval'] ) ? sanitize_key( wp_unslash( $_POST['interval'] ) ) : 'daily';
$feed['interval'] = array_key_exists( $interval, BCal_Import_Feeds::intervals() ) ? $interval : 'daily';
// A different source address needs a new confirmation of image rights.
if ( $feed['url'] !== $url || empty( $feed['rights']['host'] ) || $feed['rights']['host'] !== $host ) {
$feed['rights'] = array();
}
$feed['url'] = $url;
$message = $new ? 'feed_started' : 'feed_saved';
if ( $feed['images'] && empty( $feed['rights'] ) ) {
if ( ! empty( $raw['rights_confirm'] ) ) {
$feed['rights'] = array(
'user' => get_current_user_id(),
'time' => time(),
'host' => $host,
);
} else {
$feed['images'] = 0;
$message = 'rights';
}
}
$map = array();
if ( isset( $_POST['catmap'] ) && is_array( $_POST['catmap'] ) ) {
foreach ( wp_unslash( $_POST['catmap'] ) as $norm => $choice ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$norm = bcal_normalize_name( (string) $norm );
$choice = sanitize_key( (string) $choice );
if ( '' !== $norm && ( in_array( $choice, array( 'auto', 'skip' ), true ) || ctype_digit( $choice ) ) ) {
$map[ $norm ] = $choice;
}
}
}
$feed['cat_map'] = $map;
if ( empty( $feed['owner'] ) ) {
$feed['owner'] = get_current_user_id();
}
$saved = BCal_Import_Feeds::save( $feed );
if ( $new && $feed['enabled'] ) {
BCal_Import_Feeds::start( $saved, true );
}
bcal_import_back( $message, 'rights' === $message ? array( 'feed' => $saved ) : array() );
}
);
add_action(
'admin_post_bcal_feed_sync',
function () {
bcal_import_guard();
check_admin_referer( 'bcal_feed_sync' );
$id = isset( $_POST['id'] ) ? sanitize_key( wp_unslash( $_POST['id'] ) ) : '';
bcal_import_back( BCal_Import_Feeds::start( $id, true ) ? 'started' : 'feed_busy' );
}
);
add_action(
'admin_post_bcal_feed_delete',
function () {
bcal_import_guard();
check_admin_referer( 'bcal_feed_delete' );
$id = isset( $_POST['id'] ) ? sanitize_key( wp_unslash( $_POST['id'] ) ) : '';
$feed = BCal_Import_Feeds::get( $id );
if ( $feed && ! empty( $_POST['purge'] ) ) {
BCal_Import::create(
'ics',
/* translators: %s: subscription name */
sprintf( __( 'Deleting events of %s', 'bearius-calendar' ), '' !== $feed['name'] ? $feed['name'] : (string) wp_parse_url( $feed['url'], PHP_URL_HOST ) ),
array(
'mode' => 'purge',
'purge_feed' => $id,
)
);
}
BCal_Import_Feeds::delete( $id );
bcal_import_back( 'feed_deleted' );
}
);
add_action(
'admin_post_bcal_import_control',
function () {
bcal_import_guard();
check_admin_referer( 'bcal_import_control' );
$id = isset( $_POST['id'] ) ? sanitize_key( wp_unslash( $_POST['id'] ) ) : '';
$do = isset( $_POST['do'] ) ? sanitize_key( wp_unslash( $_POST['do'] ) ) : '';
BCal_Import::control( $id, $do, array( 'keep_edited' => ! empty( $_POST['keep_edited'] ) ) );
bcal_import_back( 'control' );
}
);