Source: my-calendar-import.php

<?php
/**
 * Events importer
 *
 * @category Features
 * @package  My Calendar Pro
 * @author   Joe Dolson
 * @license  GPLv2 or later
 * @link     https://www.joedolson.com/my-calendar-pro/
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Update importer settings.
 *
 * @param string $url Remote import URL.
 * @param string $type Type of import(csv or ics).
 * @param string $nonce Nonce.
 *
 * @return mixed string|boolean
 */
function mcs_importer_update( $url = false, $type = false, $nonce = false ) {
	// save settings.
	if ( isset( $_FILES['mcs_importer'] ) || isset( $_POST['mcs_remote_import'] ) || ( false !== $url && false !== $type ) ) {
		$nonce = isset( $_POST['_wpnonce'] ) ? $_POST['_wpnonce'] : $nonce;
		if ( ! wp_verify_nonce( $nonce, 'importer' ) ) {
			return false;
		}
		$url         = ( ( $url ) && false !== stripos( $url, 'mc-api' ) ) ? add_query_arg( 'file', 'false', $url ) : $url;
		$constructed = false;
		$delimiter   = ',';
		$type        = ( ( isset( $_POST['mcs_remote_type'] ) && 'csv' === $_POST['mcs_remote_type'] ) || 'csv' === $type ) ? 'csv' : 'ics';
		$url         = ( isset( $_POST['mcs_remote_import'] ) ) ? str_replace( 'webcals://', 'http://', $_POST['mcs_remote_import'] ) : $url;
		$event_cat   = isset( $_POST['mcs_import_category'] ) ? absint( $_POST['mcs_import_category'] ) : false;
		if ( isset( $_POST['mcs_import_limit'] ) ) {
			$import_limit = sanitize_text_field( $_POST['mcs_import_limit'] );
			update_option( 'mcs_import_limit', $import_limit );
		}
		if ( isset( $_POST['mcs_new_import_status'] ) ) {
			$import_status = sanitize_text_field( $_POST['mcs_new_import_status'] );
			update_option( 'mcs_new_import_status', $import_status );
		}
		if ( isset( $_POST['mcs_import_status'] ) && ! empty( $_POST['mcs_import_status'] ) ) {
			// If import status set for this import, use that for import management.
			$import_status = sanitize_text_field( $_POST['mcs_import_status'] );
		}
		if ( $event_cat || $import_status ) {
			$import_conditions = array(
				'event_cat'     => $event_cat,
				'import_status' => $import_status,
			);
			update_option( md5( $url ), $import_conditions );
		}
		$import_settings = get_option( md5( $url ) );
		$event_cat       = ( '' !== $import_settings && is_string( $import_settings ) ) ? absint( get_option( md5( $url ) ) ) : $event_cat;
		$url             = ( ! $url ) ? __( 'the current uploaded file', 'my-calendar-pro' ) : $url;
		$schedule        = ( isset( $_POST['mcs_schedule'] ) && '' !== $_POST['mcs_schedule'] ) ? sanitize_text_field( $_POST['mcs_schedule'] ) : false;
		$offset          = ( isset( $_POST['mcs_schedule_start'] ) ) ? sanitize_text_field( $_POST['mcs_schedule_start'] ) : 0;
		$prev_schedule   = false;
		if ( $schedule ) {
			if ( isset( $_POST['mcs_schedule_id'] ) ) {
				$prev_schedule = explode( ',', sanitize_text_field( urldecode( $_POST['mcs_schedule_data'] ) ) );
				$prev_schedule = array(
					'ts'     => $prev_schedule[6],
					'url'    => urlencode( $prev_schedule[0] ),
					'format' => $prev_schedule[4],
				);
				delete_option( md5( $prev_schedule['url'] ) );
			}
			$scheduled = mcs_setup_schedule( $schedule, $url, $type, $offset, $prev_schedule );
			if ( $prev_schedule ) {
				wp_admin_notice(
					__( 'Scheduled Import has been updated.', 'my-calendar-pro' ),
					array(
						'type' => 'success',
					)
				);

				return false;
			}
		} else {
			// Translators: Remote URL.
			$scheduled = sprintf( __( 'One time import of the events from <code>%s</code>', 'my-calendar-pro' ), $url );
		}

		if ( isset( $_FILES['mcs_importer'] ) && ! empty( $_FILES['mcs_importer']['name'] ) ) {
			$data        = mcs_get_file_import( $_FILES, $event_cat );
			$csv         = $data['csv'];
			$constructed = $data['constructed'];
		} else {
			$data        = mcs_get_remote_import( $url, $event_cat, $type );
			$csv         = $data['csv'];
			$constructed = $data['constructed'];
		}
		if ( ! $csv ) {
			return false;
		}

		/**
		 * Separate each file into an index of an array and store the total
		 * number of rows that exist in the array. This assumes that
		 * each row of the CSV file is on a new line.
		 */
		$csv_rows = ( ! is_array( $csv ) ) ? explode( PHP_EOL, $csv ) : $csv;
		// If there are blank lines, unset them in array.
		foreach ( $csv_rows as $key => $row ) {
			if ( empty( $row ) ) {
				unset( $csv_rows[ $key ] );
			}
		}
		$total_rows = count( $csv_rows );
		// number of rows per file.
		$number_per_row = 30;

		// Store the title row. This will be written at the top of every file.
		$title_row = $csv_rows[0];

		if ( is_array( $title_row ) ) {
			$title_row = implode( $delimiter, array_map( 'trim', $title_row ) );
		} else {
			$titles    = explode( $delimiter, $title_row );
			$titles    = array_map( 'trim', $titles );
			$title_row = implode( $delimiter, $titles );
		}

		/*
		 * Calculate the number of rows that will consist of $number_per_row, and then calculate
		 * the number of rows for the final file.
		 */
		$rows = ceil( $total_rows / $number_per_row );
		// have to have at least one file.
		$rows          = ( 0 === $rows ) ? 1 : $rows;
		$remaining_row = ( $total_rows % $number_per_row );
		// Prepare to write out the files. This could just as easily be a for loop.
		$file_counter = 0;

		while ( 0 < $rows ) {
			$csv_data = '';

			/*
			 * Create the output file that will contain a title row and a set of ten rows.
			 * The filename is generated based on which file we're importing.
			 */
			$loops = ( 1 === $rows && 0 !== $remaining_row ) ? $remaining_row : $number_per_row;
			// set a batch of rows into a string.
			for ( $i = 1; $i < $loops; $i++ ) {
				/**
				 * Read the value from the array and then remove from
				 * the array so it's not read again.
				 */
				if ( isset( $csv_rows[ $i ] ) && is_array( $csv_rows[ $i ] ) ) {
					$data = implode( $delimiter, array_map( 'mcs_wrap_field', $csv_rows[ $i ] ) );
				} elseif ( isset( $csv_rows[ $i ] ) && ! is_array( $csv_rows[ $i ] ) ) {
					$data = $csv_rows[ $i ];
				} else {
					$data = false;
				}

				// extra PHP EOL will break data.
				$csv_data .= ( $data && '' !== trim( $data ) ) ? str_replace( PHP_EOL, '', $data ) . PHP_EOL : PHP_EOL;
				unset( $csv_rows[ $i ] );
			}
			/**
			 * Write out the file and then close the handle since we'll be opening
			 * a new one on the next iteration.
			 */
			if ( '' !== trim( $csv_data ) ) {
				$csv_data    = $title_row . PHP_EOL . $csv_data;
				$output_file = fopen( trailingslashit( mcs_import_directory() ) . 'mcs_csv_' . $file_counter . '.csv', 'w' );
				if ( is_bool( $output_file ) ) {
					wp_admin_notice(
						__( 'Cannot open CSV stream to prepare for import.', 'my-calendar-pro' ),
						array(
							'type' => 'error',
						)
					);

					return false;
				} else {
					fwrite( $output_file, $csv_data );
					fclose( $output_file );
				}
				/**
				 * Increase the file counter by one and decrement the rows,
				 * reset the keys for the rows of the array, and then reset the
				 * string of data to the title row
				 */
				++$file_counter;
			}
			--$rows;
			$csv_rows = array_values( $csv_rows );
		}

		// lengthened expiration time on all transients for cron execution.
		set_transient( 'mcs-number-of-files', $file_counter, 90 * $file_counter );
		set_transient( 'mcs-parsed-files', 'true', 90 * $file_counter );

		if ( $constructed ) {
			// needed to change this delimiter to a single character to remove errors.
			$delimiter = '|';
		} else {
			$delimiter = ',';
		}
		set_transient( 'mcs-delimiter', $delimiter, 90 * $file_counter );

		$titles  = explode( $delimiter, $title_row );
		$imports = $total_rows - 1;
		// Translators: Number of events to import.
		$heading = sprintf( _n( 'Importing %s event.', 'Importing %s events.', $imports, 'my-calendar-pro' ), "<strong>$imports</strong>" );
		$return  = "<div class='notice notice-success'><p>$scheduled</p></div>
			<h3>" . $heading . '</h3>
				<p><strong>' . __( 'Mapping your data to My Calendar fields:', 'my-calendar-pro' ) . "</strong></p>
				<ul class='mcs-import-data'>";
		$odd     = 0;
		foreach ( $titles as $title ) {
			// If CSV is UTF-8 with BOM, strip BOM.
			$title   = str_replace( "\xEF\xBB\xBF", '', trim( str_replace( '"', '', $title ) ) );
			$display = ( '' === $title ) ? __( '(No field name)', 'my-calendar-pro' ) : $title;
			$fields  = mcs_option_fields();
			$class   = ( 1 === $odd ) ? 'odd' : 'even';
			$keys    = array_map( 'trim', array_keys( $fields ) );
			if ( in_array( $title, $keys, true ) ) {
				$target = $fields[ $title ];
				$class .= '';
			} else {
				$target = ( '' !== $title ) ? __( 'Unrecognized field - will be ignored', 'my-calendar-pro' ) : false;
				$class .= ' unknown';
			}
			if ( $target ) {
				// Translators: Imported field title, target field title.
				$return .= "<li class='$class'>" . sprintf( __( '&ldquo;<strong>%1$s</strong>&rdquo; to %2$s', 'my-calendar-pro' ), $display, $target ) . '</li>';
				$odd     = ( 1 === $odd ) ? 0 : 1;
			}
		}
		unset( $display );
		$return .= '</ul>';

		$return .= "<button type='button' name='mcs_import_events' class='button-primary' type='button'>" . __( 'Import Events', 'my-calendar-pro' ) . "</button>
		<div class='mcs-importer-progress' aria-live='assertive' aria-atomic='false'><span>" . __( 'Importing...', 'my-calendar-pro' ) . "<strong class='percent'></strong></span></div>";

		return $return;

	}

	return false;
}

/**
 * Fetch event data from a remote URL.
 *
 * @param string $url A remote URL.
 * @param int    $event_cat Event category to assign.
 * @param string $type Type of data to expect.
 *
 * @return array
 */
function mcs_get_remote_import( $url, $event_cat, $type ) {
	// remote file.
	$constructed = false;
	$response    = wp_remote_get(
		$url,
		array(
			'user-agent' => 'WordPress/My Calendar Importer; ' . home_url(),
		)
	);
	$body        = wp_remote_retrieve_body( $response );
	if ( 'csv' === $type ) {
		$csv = $body;
	} else {
		// converting ics requires file input.
		$file = fopen( trailingslashit( mcs_import_directory() ) . 'mcs_csv_source.ics', 'w' );
		fwrite( $file, $body );
		fclose( $file );
		$constructed = true;
		$csv         = mcs_convert_ics( trailingslashit( mcs_import_directory() ) . 'mcs_csv_source.ics', $event_cat );

		unlink( trailingslashit( mcs_import_directory() ) . 'mcs_csv_source.ics' );
	}

	return array(
		'csv'         => $csv,
		'constructed' => $constructed,
	);
}

/**
 * Read the contents of an uploaded file and convert to CSV.
 *
 * @param array $files A global $_FILES array.
 * @param int   $event_cat Event category ID for this import.
 *
 * @return array
 */
function mcs_get_file_import( $files, $event_cat ) {
	// Read the contents of the file.
	$path        = $files['mcs_importer']['name'];
	$ext         = pathinfo( $path, PATHINFO_EXTENSION );
	$rows        = '';
	$constructed = false;

	if ( 'ics' === $ext ) {
		$constructed = true;
		$rows        = mcs_convert_ics( $files['mcs_importer']['tmp_name'], $event_cat );
	}
	if ( '' === $rows ) {
		$file = fopen( $_FILES['mcs_importer']['tmp_name'], 'r' );
		while ( ( $row = fgetcsv( $file, 0, ',' ) ) !== false ) { // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
			$csv[] = $row;
		}
	} else {
		$csv = $rows;
	}

	return array(
		'constructed' => $constructed,
		'csv'         => $csv,
	);
}

/**
 * Try to make sure all fields are delimited correctly
 *
 * @param string $content to delimit.
 *
 * @return wrapped string
 */
function mcs_wrap_field( $content ) {
	return '"' . wptexturize( $content ) . '"';
}

/**
 * Fix iCal params that aren't supported.
 *
 * @param string $check String to check for undesired charset parameters.
 *
 * @return string
 */
function mcs_fix_params( $check ) {
	$check = str_replace( 'CHARSET=utf-8:', '', $check );

	return $check;
}

/**
 * Convert an .ics (iCal) formatted file into a CSV for importing
 *
 * @param string      $file Path to file to convert.
 * @param boolean|int $category Default category ID for this import.
 *
 * @return string CSV rows.
 */
function mcs_convert_ics( $file, $category = false ) {
	require_once __DIR__ . '/classes/class.iCalReader.php';
	$ics = new ical( $file );
	if ( 0 === (int) $ics->event_count ) {
		wp_admin_notice(
			__( 'That event resource did not contain any events.', 'my-calendar-pro' ),
			array(
				'type' => 'warning',
			)
		);
		return;
	}
	// is timezone from/to data in $ics->cal['VCALENDAR'].
	$events = $ics->cal['VEVENT'];
	$uids   = array();
	/**
	 * Override status of event to automatically approve publication during imports.
	 *
	 * @hook mcs_publish_imports
	 *
	 * @param {bool}  $import true to import with published status. Default false.
	 * @param {array} $events Array of events being imported in this pull.
	 *
	 * @return {bool}
	 */
	$publish_imports = apply_filters( 'mcs_publish_imports', false, $events );
	/**
	 * Set locations to import to location database. Note: ical data is unstructured, so locations will only import as a location name.
	 *
	 * @hook mcs_import_locations
	 *
	 * @param {bool}  $import true to import with published status. Default false.
	 * @param {array} $events Array of events being imported in this pull.
	 *
	 * @return {bool}
	 */
	$import_locations = apply_filters( 'mcs_import_locations', false, $events );
	// map each element to existing My Calendar fields.
	$row_category = ( $category ) ? '|||event_category' : '';
	$row_approval = ( 'true' === get_option( 'mcs_automatic_approval' ) || $publish_imports ) ? '|||event_approved' : '';
	$row_import   = ( $import_locations ) ? '|||copy_to_locations' : '';
	$rows         = 'event_begin|||event_time|||event_end|||event_endtime|||content|||event_label|||event_title|||event_group_id|||UID' . $row_category . $row_approval . $row_import . PHP_EOL;
	if ( ! is_array( $events ) ) {
		return;
	}
	$tz = array();
	$i  = 0;
	foreach ( $events as $event ) {
		if ( ! isset( $event['DTSTART'] ) ) {
			continue;
		}
		if ( is_array( $event ) ) {
			foreach ( $event as $key => $check ) {
				// This has only come up once, and this fixes that case.
				$event[ $key ] = ( is_array( $check ) ) ? map_deep( $check, 'mcs_fix_params' ) : mcs_fix_params( $check );
			}
		}
		++$i;

		$start = 'datetime';
		$end   = 'datetime';
		if ( false !== stripos( $event['DTSTART'], 'VALUE=DATE:' ) ) {
			$event['DTSTART'] = str_replace( 'VALUE=DATE:', '', $event['DTSTART'] );
			$start            = 'date';
		}
		// DTSTART;TZID=America/Detroit:20160426T153000.
		if ( false !== stripos( $event['DTSTART'], 'TZID' ) ) {
			$items = explode( ':', $event['DTSTART'] );
			if ( isset( $items[2] ) && ! is_numeric( $items[1] ) ) {
				$tz   = explode( '=', $items[0] . $items[1] );
				$date = $items[2];
			} else {
				$tz   = explode( '=', $items[0] );
				$date = $items[1];
			}
			/**
			 * Take date & timezone ID string and return timezone-adapted date
			 */
			$date = apply_filters( 'mcs_apply_timezone', $date, $tz );

			$event['DTSTART'] = $date;
		} elseif ( false !== stripos( $event['DTSTART'], 'Z' ) ) {
			$date_start       = mcs_date( 'Y-m-d H:i:s', strtotime( str_replace( array( 'T', 'Z' ), ' ', $event['DTSTART'] ) ), false );
			$date             = get_date_from_gmt( $date_start, 'Y-m-d\TH:i:s' );
			$event['DTSTART'] = $date;
		}
		$event['DTSTART'] = apply_filters( 'mcs_import_dtstart', $event['DTSTART'], $event );
		$event_begin      = mcs_date( 'Y-m-d', strtotime( $event['DTSTART'] ), false );
		$event_time       = ( 'datetime' === $start ) ? mcs_date( 'H:i:00', strtotime( $event['DTSTART'] ), false ) : '00:00:00';
		// If this event is before 'x' time restriction, ignore.
		$import_limit = get_option( 'mcs_import_limit' );
		if ( $import_limit ) {
			$import_limit = ( 'past' === $import_limit ) ? 'now' : $import_limit;
			if ( strtotime( $event_begin . ' ' . $event_time ) < strtotime( $import_limit ) ) {
				continue;
			}
		}
		$event_dtend = isset( $event['DTEND'] ) ? $event['DTEND'] : '';
		if ( false !== stripos( $event_dtend, 'VALUE=DATE:' ) ) {
			$event['DTEND'] = str_replace( 'VALUE=DATE:', '', $event['DTEND'] );
			$end            = 'date';
		}
		if ( false !== stripos( $event_dtend, 'TZID' ) ) {
			$items = explode( ':', $event['DTEND'] );
			$tz    = explode( '=', $items[0] );
			$date  = $items[1];
			/**
			 * Take date & timezone ID string and return timezone-adapted date
			 */
			$date           = apply_filters( 'mcs_apply_timezone', $date, $tz );
			$event['DTEND'] = $date;
		} elseif ( false !== stripos( $event_dtend, 'Z' ) ) {
			$date_end       = mcs_date( 'Y-m-d H:i:s', strtotime( str_replace( array( 'T', 'Z' ), ' ', $event['DTEND'] ) ), false );
			$date           = get_date_from_gmt( $date_end, 'Y-m-d\TH:i:s' );
			$event['DTEND'] = $date;
		}
		if ( ! isset( $event['DTEND'] ) ) {
			$end_date_time  = strtotime( $event_begin . ' ' . $event_time . ' + 1 hour' );
			$event['DTEND'] = get_date_from_gmt( mcs_date( 'Y-m-d H:i:s', $end_date_time, false ), 'Y-m-d\TH:i:s' );
		}
		$event['DTEND'] = apply_filters( 'mcs_import_dtend', $event['DTEND'], $event );
		$event_end      = mcs_date( 'Y-m-d', strtotime( $event['DTEND'] ), false );
		$event_endtime  = ( 'datetime' === $end ) ? mcs_date( 'H:i:00', strtotime( $event['DTEND'] ), false ) : '23:59:59';
		$time_diff      = strtotime( $event_endtime ) - strtotime( $event_time );

		// if time_diff = 1 second - subtract one day from date - this is an all day event.
		if ( ( DAY_IN_SECONDS - 1 ) === $time_diff ) {
			$event_end = mcs_date( 'Y-m-d', ( strtotime( $event_end ) - DAY_IN_SECONDS ), false );
		}

		$description = ( isset( $event['DESCRIPTION'] ) ) ? $event['DESCRIPTION'] : '';
		$location    = ( isset( $event['LOCATION'] ) ) ? $event['LOCATION'] : '';
		$summary     = ( isset( $event['SUMMARY'] ) ) ? $event['SUMMARY'] : '';
		$uid         = ( isset( $event['UID'] ) ) ? $event['UID'] : '';
		// add UID field to event object; use to track for imports.
		if ( in_array( $uid, $uids, true ) ) {
			// If the UID already exists, this is an exploded recurring event.
			// Generate a new UID, but use the same Group ID.
			$uid      = ( in_array( $uid, $uids, true ) ) ? $uid . '-' . $i : $uid;
			$group_id = mc_group_id();
		} else {
			$group_id = 'default';
			$uids[]   = $uid;
		}
		$event_category  = ( $category ) ? "|||\"$category\"" : '';
		$event_approval  = ( 'true' === get_option( 'mcs_automatic_approval' ) || $publish_imports ) ? '|||\"1\"' : '';
		$location_import = ( $import_locations ) ? '|||\"1\"' : '';
		$row             = "\"$event_begin\"|||\"$event_time\"|||\"$event_end\"|||\"$event_endtime\"|||\"$description\"|||\"$location\"|||\"$summary\"|||\"$group_id\"|||\"$uid\"" . $event_category . $event_approval . $location_import . PHP_EOL;
		$rows           .= $row;
	}
	unset( $event );

	return trim( $rows );
}

add_action( 'admin_enqueue_scripts', 'mcs_importer_enqueue_scripts' );
/**
 * Enqueue importer scripts.
 */
function mcs_importer_enqueue_scripts() {
	global $mcs_version;
	if ( isset( $_GET['page'] ) && 'my-calendar-submissions' === $_GET['page'] ) {
		wp_enqueue_script( 'mcs.import', plugins_url( 'js/import.js', __FILE__ ), array( 'jquery', 'wp-a11y', 'wp-i18n' ), $mcs_version, true );
	}
}

add_filter( 'mcs_settings_tabs', 'mcs_importer_tabs' );
/**
 * Add importer tab.
 *
 * @param array $tabs Array of added tabs.
 *
 * @return array
 */
function mcs_importer_tabs( $tabs ) {
	$tabs['importer'] = __( 'Importer', 'my-calendar-pro' );

	return $tabs;
}

add_filter( 'mcs_settings_panels', 'mcs_importer_settings' );
/**
 * Add settings page.
 *
 * @param array $panels Existing panels.
 */
function mcs_importer_settings( $panels ) {
	// delete any old data from incomplete imports.
	$update = mcs_importer_update();
	$crons  = mcs_import_schedules();

	$importer = '
		<h2>' . __( 'Import Events', 'my-calendar-pro' ) . '</h2>
		<div class="inside">
		' . $update;

	if ( false === $update ) {
		if ( isset( $_GET['test_import'] ) ) {
			mcs_import_files();
		}
		$import_limit  = get_option( 'mcs_import_limit' );
		$import_status = get_option( 'mcs_new_import_status' );
		$importer     .= '
			<fieldset class="mcs-importer">
				<legend>' . __( 'Default Importer Settings', 'my-calendar-pro' ) . '</legend>
				<p>
					<label for="mcs_import_limit">' . __( 'Restrict event import by time:', 'my-calendar-pro' ) . '</label>
					<select name="mcs_import_limit" id="mcs_import_limit">
						<option value="">' . __( 'Import All Events', 'my-calendar-pro' ) . '</option>
						<option value="past"' . selected( 'past', $import_limit, false ) . '>' . __( 'Ignore events in the past', 'my-calendar-pro' ) . '</option>
						<option value="-1 month"' . selected( '-1 month', $import_limit, false ) . '>' . __( 'Ignore events more than one month in the past', 'my-calendar-pro' ) . '</option>
						<option value="-1 year"' . selected( '-1 year', $import_limit, false ) . '>' . __( 'Ignore events more than one year in the past', 'my-calendar-pro' ) . '</option>
					</select>
				</p>
				<p>
					<label for="mcs_new_import_status">' . __( 'Default imported event status', 'my-calendar-pro' ) . '</label>
					<select name="mcs_new_import_status" id="mcs_new_import_status" aria-describedby="mcs_import_status_description">
						<option value="">--</option>
						<option value="1"' . selected( 1, $import_status, false ) . '>' . __( 'Publish', 'my-calendar-pro' ) . '</option>
						<option value="0"' . selected( 0, $import_status, false ) . '>' . __( 'Draft', 'my-calendar-pro' ) . '</option>
						<option value="2"' . selected( 2, $import_status, false ) . '>' . __( 'Trash', 'my-calendar-pro' ) . '</option>
					</select>
					<span class="description" id="mcs_import_status_description">' . __( 'Imported events that set the <code>event_approved</code> status will override this setting.', 'my-calendar-pro' ) . '</span>
				</p>
			</fieldset>
			<p>
				<label for="mcs_importer_mode">' . __( 'Upload File (.csv or .ics)', 'my-calendar-pro' ) . '</label>
				<input type="file" name="mcs_importer" id="mcs_importer_mode" />
			</p>
			<fieldset class="mcs-importer">
			<legend>' . __( 'Remote/scheduled import', 'my-calendar-pro' ) . '</legend>
			<p>
				<label for="mcs_remote_import">' . __( 'Import from URL', 'my-calendar-pro' ) . '</label><br />
				<input type="url" name="mcs_remote_import" id="mcs_remote_import" class="widefat" />
			</p>
			<p>
				<input type="radio" name="mcs_remote_type" id="mcs_remote_type_ics" value="ics" /> <label for="mcs_remote_type_ics">' . __( 'iCal format (.ics)', 'my-calendar-pro' ) . '</label> <input type="radio" name="mcs_remote_type" id="mcs_remote_type_csv" value="csv" checked="checked" /> <label for="mcs_remote_type_csv">' . __( 'Character separated values (.csv)', 'my-calendar-pro' ) . '</label>
			</p>
			<p class="import_category">
				<label for="mcs_import_category">' . __( 'Import to category:', 'my-calendar-pro' ) . '</label>
				<select name="mcs_import_category" id="mcs_import_category">
					<option value="">' . __( 'Default', 'my-calendar-pro' ) . '</option>
					' . mc_category_select( false, true, false ) . '
				</select>
			</p>
			<p class="import_status">
				<label for="mcs_import_status">' . __( 'Default status', 'my-calendar-pro' ) . '</label>
				<select name="mcs_import_status" id="mcs_import_status">
					<option value="">--</option>
					<option value="1">' . __( 'Publish', 'my-calendar-pro' ) . '</option>
					<option value="0">' . __( 'Draft', 'my-calendar-pro' ) . '</option>
					<option value="2">' . __( 'Trash', 'my-calendar-pro' ) . '</option>
				</select>
			</p>
			<p>
				<label for="mcs_schedule">' . __( 'Remote import frequency', 'my-calendar-pro' ) . '</label>
				<select name="mcs_schedule" id="mcs_schedule">
						<option value="">' . __( 'One time import', 'my-calendar-pro' ) . '</option>';
						// note: if a schedule is set, show checkbox to delete existing schedule and create new.
		$schedules = wp_get_schedules();
		foreach ( $schedules as $key => $schedule ) {
			$importer .= "<option value='$key'>$schedule[display]</option>";
		}
		$importer .= '
				</select>
			</p>
			<p>
				<label for="mcs_schedule_start">' . __( 'Remote import start time', 'my-calendar-pro' ) . '</label>
				<input type="time" name="mcs_schedule_start" id="mcs_schedule_start" value="' . esc_attr( gmdate( 'H:i', mc_date() + 300 ) ) . '">
			</p>
			</fieldset>
			{submit}';
	}
	$importer .= '
			<div role="alert"><button type="button" class="button-primary mcs-reset">' . __( 'Import more events', 'my-calendar-pro' ) . '</button></div>
		</div>';

	$panels['importer']['after_form'] = $crons;
	$panels['importer']['content']    = $importer;
	$panels['importer']['label']      = __( 'Import Events', 'my-calendar-pro' );

	return $panels;
}


add_filter( 'cron_schedules', 'mcs_custom_schedules' );
/**
 * Add custom cron schedules.
 *
 * @param array $schedules Existing schedules.
 *
 * @return array new schedules.
 */
function mcs_custom_schedules( $schedules ) {
	$schedules            = apply_filters( 'mcs_cron_schedules', $schedules );
	$schedules['weekly']  = array(
		'interval' => 604800,
		'display'  => __( 'Once Weekly', 'my-calendar-pro' ),
	);
	$schedules['monthly'] = array(
		'interval' => 2635200,
		'display'  => __( 'Once Monthly', 'my-calendar-pro' ),
	);

	return $schedules;
}

/**
 * Set up import schedule.
 */
function mcs_import_schedules() {
	$cron        = _get_cron_array();
	$schedules   = wp_get_schedules();
	$date_format = _x( 'M j, Y @ G:i', 'Scheduled imports date format', 'my-calendar-pro' );
	$clear_queue = wp_nonce_url( admin_url( 'admin.php?page=my-calendar-submissions&amp;mcsimport=clear#mcs_importer_tab' ) );
	$schedule    = '';
	$jobs        = '';
	$notice      = '';

	$offset = ( 60 * 60 * get_option( 'gmt_offset' ) );
	foreach ( $cron as $ts => $cronhooks ) {
		foreach ( (array) $cronhooks as $hook => $events ) {
			$i = 0;
			foreach ( (array) $events as $event ) {
				if ( 'mcsimport' === $hook ) {
					++$i;
					if ( count( $event['args'] ) ) {
						$url      = $event['args']['url'];
						$format   = $event['args']['format'];
						$schedule = ( '' !== $event['schedule'] ) ? $event['schedule'] : '<em>' . __( 'Currently processing import', 'my-calendar-pro' ) . '</em>';
					}
					$id = md5( implode( '', $event['args'] ) );

					if ( ( isset( $_GET['mcsimport'] ) && 'clear' === $_GET['mcsimport'] ) && ( isset( $_GET['_wpnonce'] ) && wp_verify_nonce( $_GET['_wpnonce'] ) ) ) {
						wp_unschedule_event(
							$ts,
							$hook,
							array(
								'url'       => $url,
								'format'    => $format,
								'iteration' => 0,
							)
						);
						$notice = "<div id='message' class='notice notice-success'><p>" . sprintf( __( 'Import schedules have been cleared.', 'my-calendar-pro' ) ) . '</p></div>';
					} elseif ( isset( $_GET['mcsdelete'] ) && $id === $_GET['mcsdelete'] ) {
						wp_unschedule_event(
							$ts,
							$hook,
							array(
								'url'       => $url,
								'format'    => $format,
								'iteration' => 0,
							)
						);
						$notice = "<div id='message' class='notice notice-success'><p>" . __( 'Scheduled import has been deleted.', 'my-calendar-pro' ) . '</p></div>';
					} else {
						$time_diff       = ( $ts < mc_date() ) ? __( 'Missed Schedule', 'my-calendar-pro' ) : '~' . human_time_diff( $ts + $offset, mc_date() ); // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested
						$import_settings = get_option( md5( $url ) );
						$event_cat       = false;
						$import_status   = get_option( 'mcs_new_import_status' );
						if ( $import_settings ) {
							$event_cat     = ( is_string( $import_settings ) ) ? $import_settings : $import_settings['event_cat'];
							$import_status = ( ! is_string( $import_settings ) ) ? $import_settings['import_status'] : $import_status;
						}
						$category = ( $event_cat ) ? '(' . mc_get_category_detail( $event_cat, 'category_name' ) . ')' : '';
						$status   = ( $import_status ) ? mc_event_states_label( $import_status ) : '';
						$status   = ( $status ) ? '<br>' . esc_html__( 'Status when imported:', 'my-calendar-pro' ) . ' <strong>' . $status . '</strong>' : '';
						$time     = gmdate( 'H:i', $ts );
						$values   = json_encode( array( $url, $time, $schedule, $event_cat, $format, $import_status, $ts ) );
						$jobs    .= "
						<tr>
							<th scope='row'>" . wp_date( $date_format, ( $ts + $offset ) ) . '<br /><small>(' . $time_diff . ")</small></th>
							<td><code>$url</code> $category $status</td>
							<td>$schedule</td>
							<td>
								<a class='button-secondary delete' href='" . esc_url( add_query_arg( 'mcsdelete', $id, admin_url( 'admin.php?page=my-calendar-submissions' ) ) ) . "#mcs_importer_tab'>" . __( 'Delete', 'my-calendar-pro' ) . "</a>
								<button type='button' data-value='" . esc_attr( wp_unslash( $values ) ) . "' class='button-secondary edit-import-schedule' href='" . esc_url( add_query_arg( 'mcsedit', $id, admin_url( 'admin.php?page=my-calendar-submissions' ) ) ) . "#mcs_importer_tab'>" . __( 'Edit', 'my-calendar-pro' ) . '</button>
							</td>
						</tr>';
					}
				}
			}
		}
	}

	$approval_note = ( 'true' === get_option( 'mcs_automatic_approval' ) ) ? __( 'Events imported will be automatically published.', 'my-calendar-pro' ) : __( 'Events will be imported as drafts unless otherwise indicated.', 'my-calendar-pro' );
	$schedules     = "<div class='ui-sortable meta-box-sortables'><div class='postbox'><h2>" . __( 'Scheduled Imports', 'my-calendar-pro' ) . "</h2>
	<div class='inside'>
	<p>$approval_note</p>
	<table class='widefat fixed mcs-importer-schedule'>
		<thead>
			<tr>
				<th scope='col'>" . __( 'Next', 'my-calendar-pro' ) . "</th>
				<th scope='col'>" . __( 'URL', 'my-calendar-pro' ) . "</th>
				<th scope='col'>" . __( 'Frequency', 'my-calendar-pro' ) . "</th>
				<th scope='col'>" . __( 'Manage', 'my-calendar-pro' ) . '</th>
			</tr>
		</thead>
		<tbody>';

	if ( '' !== $jobs ) {
		$schedules .= $jobs;
	}

	$schedules .= "
		</tbody>
	</table>
	<p><a href='$clear_queue'>" . __( 'Clear Schedule', 'my-calendar-pro' ) . '</a></p>
	</div>
	</div>
	</div>';

	return ( '' === $jobs ) ? $notice . '<p>' . __( 'No event import schedules are currently set up.', 'my-calendar-pro' ) . '</p>' : $notice . $schedules;
}

/**
 * Schedule custom importer event.
 *
 * @param string     $schedule Type of schedule.
 * @param string     $url URL to query.
 * @param string     $format Type of data at URL.
 * @param string|int $offset Time when initial schedule should start. `0` for now.
 * @param array|bool $scheduled Existing schedule if updating schedule.
 *
 * @return string message.
 */
function mcs_setup_schedule( $schedule, $url, $format, $offset = 0, $scheduled = false ) {
	$schedules = wp_get_schedules();
	$current   = $schedules[ $schedule ];
	$offset    = ( $offset ) ? strtotime( $offset ) - strtotime( '00:00:00' ) : 0;
	$start     = mc_date( 'Y-m-d', false, false );
	$timestamp = strtotime( $start ) + $offset;
	$args      = array(
		'url'       => $url,
		'format'    => $format,
		'iteration' => 0,
	);

	if ( $scheduled ) {
		// Remove the previous version of this import schedule.
		wp_unschedule_event(
			$scheduled['ts'],
			'mcsimport',
			array(
				'url'       => $scheduled['url'],
				'format'    => $scheduled['format'],
				'iteration' => 0,
			)
		);
	}

	wp_schedule_event( $timestamp, $schedule, 'mcsimport', $args );
	// Translators: 1) Schedule frequency, 2) remote URL.
	return sprintf( __( '%1$s import of the events at <code>%2$s</code> has been scheduled. You should still complete the current import.', 'my-calendar-pro' ), $current['display'], $url );
}

register_deactivation_hook( __FILE__, 'mcs_deactivate_cron' );
/**
 * Deactivate cron job on deactivation.
 */
function mcs_deactivate_cron() {
	wp_clear_scheduled_hook( 'mcsimport' );
}

add_action( 'mcsimport', 'mcs_import_schedule', 10, 3 );
/**
 * Set up import schedule for larger than max events.
 *
 * @param string $url URL to import.
 * @param string $format Format of file to import.
 * @param int    $iteration Number of this execution.
 */
function mcs_import_schedule( $url, $format, $iteration ) {
	// mapped arguments to not require POST variables, still need to handle nonce.
	if ( 0 === $iteration ) {
		$nonce = wp_create_nonce( 'importer' );
		mcs_importer_update( $url, $format, $nonce );
	}

	if ( mcs_import_files( $iteration, $url ) ) {
		$iteration = $iteration + 1;
		wp_schedule_single_event(
			time() + 60,
			'mcsimport',
			array(
				'url'       => $url,
				'format'    => $format,
				'iteration' => $iteration,
			)
		);
	}
}

/**
 * Setup the hook for the Ajax request.
 *
 * @param int    $i Repetition.
 * @param string $url Source URL for a scheduled import.
 *
 * @return bool
 */
function mcs_import_files( $i = 0, $url = '' ) {
	set_transient( "mcs-parsing-$i", 'true', 90 );
	$content         = array();
	$defaults        = mcs_default_event_values();
	$number_of_files = get_transient( 'mcs-number-of-files' );
	$delimiter       = get_transient( 'mcs-delimiter' );
	$return          = true;

	if ( ! $delimiter || ! $number_of_files ) {
		return false;
	}

	if ( $i < $number_of_files ) {
		$filename = trailingslashit( mcs_import_directory() ) . 'mcs_csv_' . $i . '.csv';
		if ( file_exists( $filename ) ) {
			$file = fopen( $filename, 'r' );
			// $content    = fread( $input_file, filesize( $filename ) );.
			// $f    = file( $filename );.
			while ( ( $row = fgetcsv( $file, 0, $delimiter ) ) !== false ) { // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
				// at least two fields are required to import.
				if ( ! empty( $row ) && count( $row ) > 1 ) {
					$content[] = $row;
				}
			}

			$array = mcs_translate_csv( $content, $delimiter );

			unset( $content );
			fclose( $file );

			foreach ( $array as $event ) {
				if ( ! is_array( $event ) || ( isset( $event['event_title'] ) && 'event_title' === $event['event_title'] ) ) {
					continue;
				}
				if ( isset( $event['event_group_id'] ) && 'default' === $event['event_group_id'] ) {
					unset( $event['event_group_id'] );
				}
				$event = array_merge( $defaults, $event );
				if ( isset( $event['event_desc'] ) ) {
					// If contains decoded line breaks.
					$event['event_desc'] = str_replace( '\r\n', PHP_EOL, $event['event_desc'] );
				}
				// Verify if author exists.
				$author_exists = false;
				if ( isset( $event['event_author'] ) ) {
					if ( is_numeric( $event['event_author'] ) ) {
						$author_exists = get_user_by( 'ID', $event['event_author'] );
					} else {
						if ( is_email( $event['event_author'] ) ) {
							$author_exists = get_user_by( 'email', $event['event_author'] );
						} else {
							$author_exists = get_user_by( 'slug', $event['event_author'] );
						}
					}
				}
				if ( ! $author_exists ) {
					$event['event_author'] = apply_filters( 'mc_event_author_passed_not_exists', 0, $event );
				}
				// Verify if category exists & fetch category ID if a string passed.
				$category_exists = false;
				if ( is_numeric( $event['event_category'] ) ) {
					$category_exists = mcs_category_exists( $event['event_category'] );
				}

				$label = ( isset( $event['event_label'] ) ) ? $event['event_label'] : '';
				// If no label, don't copy.
				if ( '' !== $label ) {
					// If location already exists, get location ID.
					$posts = get_posts(
						array(
							's'           => $label,
							'post_type'   => 'mc-locations',
							'numberposts' => 1,
						)
					);

					if ( ! empty( $posts ) ) {
						if ( $posts[0]->post_title === $label ) {
							$location_post            = $posts[0]->ID;
							$location                 = mc_get_location_id( $location_post );
							$event['location_preset'] = $location;
						}
					}
				}

				if ( isset( $event['event_category'] ) && ! $category_exists ) {
					// event category is not numeric, so check database to see if exists.
					$cat_id = mcs_category_by_name( $event['event_category'] );
					if ( ! $cat_id ) {
						$color   = false;
						$icon    = false;
						$private = false;
						if ( isset( $event['category_color'] ) ) {
							$color = $event['category_color'];
						}
						if ( isset( $event['category_icon'] ) ) {
							$icon = $event['category_icon'];
						}
						if ( isset( $event['category_private'] ) ) {
							$private = $event['category_private'];
						}
						$cat_id = ( '' === $event['event_category'] ) ? 1 : mcs_insert_category( $event['event_category'], $color, $icon, $private );
					}
					$event['event_category'] = $cat_id;
				}
				// If a non-numeric value is used in the location preset, move it to event_label if that is not set.
				if ( isset( $event['location_preset'] ) && ! is_numeric( $event['location_preset'] ) ) {
					if ( ! isset( $event['event_label'] ) || empty( $event['event_label'] ) ) {
						$event['event_label'] = $event['location_preset'];
					}
					unset( $event['location_preset'] );
				}
				$mcs_guid_event = false;
				if ( isset( $event['UID'] ) ) {
					$mcs_guid_event = mcs_guid_exists( $event['UID'], $event );
				}
				if ( $mcs_guid_event ) {
					mcs_update_event( $event, $mcs_guid_event );
				} else {
					mcs_import_event( $event, $url );
				}
			}
			unset( $event );
			// update count of parsed files; close & delete input file.
			if ( file_exists( $filename ) ) {
				unlink( $filename );
			}
		} else {
			$return = false;
		}
		$return = true;
	} else {
		// delete data about imports.
		delete_transient( 'mcs-parsed-files' );
		delete_transient( 'mcs-number-of-files' );
		delete_transient( 'mcs-delimiter' );

		$return = false;
	}

	delete_transient( "mcs-parsing-$i" );

	return $return;
}
add_action( 'wp_ajax_mcs_import_files', 'mcs_import_files' );

/**
 * See if this event has already been imported & return event ID.
 *
 * @param string $guid Unique ID.
 * @param object $event Event object to examine.
 *
 * @return bool|int
 */
function mcs_guid_exists( $guid, $event ) {
	// If the event source doesn't maintain a persistent UID, the next best option may be some other field.
	$guid = apply_filters( 'mcs_alternate_guid', $guid, $event );
	// check for existing $guid.
	$post_ID = false;
	$args    = array(
		'post_type'      => 'mc-events',
		'posts_per_page' => 1,
		'meta_query'     => array(
			array(
				'key'   => '_mc_guid',
				'value' => $guid,
			),
		),
	);

	$posts = get_posts( $args );

	if ( ! empty( $posts ) && is_object( $posts[0] ) ) {
		$post_ID = $posts[0]->ID;
	}
	$event_id = get_post_meta( $post_ID, '_mc_event_id', true );
	// If this event doesn't return a My Calendar object, it's leftover.
	if ( ! $event_id || ! mc_get_event_core( $event_id ) ) {
		return false;
	}

	return $event_id;
}

add_action( 'wp_ajax_mcs_get_import_status', 'mcs_get_import_status' );
/**
 * Define the hook for getting import status.
 */
function mcs_get_import_status() {
	/*
	* $progress indicates how far we are during the import,
	* -1 indicates that we're done
	*/
	if ( false !== get_transient( 'mcs-parsed-files' ) ) {
		$parsed_files = floatval( get_transient( 'mcs-parsed-files' ) );
		$total_files  = floatval( get_transient( 'mcs-number-of-files' ) );
		if ( 0 !== (int) $total_files ) {
			// prevent duplicate imports.
			if ( 'true' === $parsed_files ) {
				$parsed_files = 0;
			}
			if ( 'true' !== get_transient( "mcs-parsing-$parsed_files" ) ) {
				mcs_import_files( $parsed_files );
				// Update data about imports.
				set_transient( 'mcs-parsed-files', $parsed_files + 1, 90 );
			}
			$progress = $parsed_files / $total_files;
			// Return progress values.
			if ( 1 === (int) $progress ) {
				die( '-1' );
			}
			die( esc_html( $progress ) );
		} else {
			die( '0' );
		}
	} else {
		die( '-1' );
	}
}

/**
 * Switch status types to integers.
 *
 * @param string $type Status type (publish, trash, draft).
 *
 * @return int
 */
function mcs_get_status( $type ) {
	switch ( $type ) {
		case 'draft':
			$return = 0;
			break;
		case 'publish':
			$return = 1;
			break;
		case 'trash':
			$return = 2;
			break;
		default:
			$return = 0;
	}

	return $return;
}

/**
 * Take parsed array and import event if new event
 *
 * @param array  $event Array of event data.
 * @param string $url Source URL for scheduled imports.
 *
 * @return int
 */
function mcs_import_event( $event, $url = '' ) {
	// When importing an event, use the new import status unless an event approval value is set.
	$default_approval = get_option( 'mcs_new_import_status', '' );
	$source_config    = ( $url ) ? get_option( md5( $url ) ) : false;
	if ( $source_config ) {
		$source_approval = ( is_array( $source_config ) ) ? $source_config['import_status'] : $default_approval;
	} else {
		$source_approval = $default_approval;
	}
	$event_approval = isset( $event['event_approved'] ) ? $event['event_approved'] : $source_approval;
	// If the value passed is a string label, attempt to get integer state.
	$event_approval = ( is_string( $event_approval ) && ! is_numeric( $event_approval ) ) ? mcs_get_status( $event_approval ) : $event_approval;
	if ( ! isset( $event['event_approved'] ) && is_numeric( $event_approval ) ) {
		$event['event_approved'] = $default_approval;
	}
	if ( isset( $event['event_approved'] ) && empty( $event['event_approved'] ) ) {
		unset( $event['event_approved'] );
	}
	/**
	 * Filter imported event to customize what gets added to database. Return false to skip event.
	 *
	 * @hook mcs_imported_event
	 *
	 * @param {array} $event Array of event data passed to `mc_check_data`.
	 *
	 * @return {array|false}
	 */
	$event = apply_filters( 'mcs_imported_event', $event );
	if ( false === $event ) {
		return;
	}

	$check    = mc_check_data( 'add', $event, 0, true );
	$event_id = false;
	if ( $check[0] ) {
		$response = my_calendar_save( 'add', $check );
		$event_id = $response['event_id'];
		$response = $response['message'];
		$e        = mc_get_first_event( $event_id );
		$post_id  = $e->event_post;
		if ( isset( $event['event_image'] ) && '' !== $event['event_image'] ) {
			$image = media_sideload_image( $event['event_image'], $post_id, null, 'id' );
			if ( ! is_wp_error( $image ) ) {
				set_post_thumbnail( $post_id, $image );
			}
		}
		if ( isset( $event['event_location'] ) && '' !== $event['event_location'] ) {
			$location_id = (int) $event['event_location'];

			update_post_meta( $post_id, '_mc_event_location', $location_id );
			mc_update_event( 'event_location', $location_id, $event_id );
		}
		if ( isset( $event['UID'] ) ) {
			$uid = apply_filters( 'mcs_set_alternate_guid', $event['UID'], $event );

			update_post_meta( $post_id, '_mc_guid', $uid );
		}

		if ( isset( $event['language'] ) ) {
			$available = get_available_languages();
			if ( in_array( $event['language'], $available, true ) ) {
				update_post_meta( $post_id, '_event_language', $event['language'] );
			}
		}
	}

	return $event_id;
}

/**
 * Take parsed event and update existing event if identified as already existing
 *
 * @param array $event Event data.
 * @param int   $event_edit Event ID.
 *
 * @return int
 */
function mcs_update_event( $event, $event_edit ) {
	/**
	 * Filter imported event data that is updating an existing event.
	 *
	 * @hook mcs_updated_event
	 *
	 * @param {array} $event Event data.
	 * @param {int}   $event_edit Event ID.
	 *
	 * @return {array}
	 */
	$event = apply_filters( 'mcs_updated_event', $event, $event_edit );
	// If the event's status isn't set in the source, set it to the current value so it isn't changed.
	$event_approved = ( isset( $event['event_approved'] ) ) ? $event['event_approved'] : false;
	$event_approved = ( isset( $event['event_status'] ) ) ? $event['status'] : $event_approved;
	// If the value passed is a string label, attempt to get integer state.
	$event_approved = ( is_string( $event_approved ) && ! is_numeric( $event_approved ) ) ? mcs_get_status( $event_approved ) : $event_approved;
	if ( ! $event_approved ) {
		$event['event_approved'] = mc_get_data( 'event_approved', $event_edit );
	} else {
		$event['event_approved'] = $event_approved;
	}
	$check    = mc_check_data( 'add', $event, 0, true );
	$event_id = false;
	if ( $check[0] ) {
		// Grant the importer permissions to edit events.
		add_filter( 'mc_api_can_edit_event', 'mcs_api_can_edit_event', 10, 2 );
		$response = my_calendar_save( 'edit', $check, $event_edit );
		// Remove permissions.
		remove_filter( 'mc_api_can_edit_event', 'mcs_api_can_edit_event', 10, 2 );
		$event_id = $response['event_id'];
		$response = $response['message'];
		if ( isset( $event['event_image'] ) && '' !== $event['event_image'] ) {
			$e       = mc_get_first_event( $event_id );
			$post_id = $e->event_post;
			$image   = media_sideload_image( $event['event_image'], $post_id, null, 'id' );
			if ( ! is_wp_error( $image ) ) {
				set_post_thumbnail( $post_id, $image );
			}
		}

		if ( isset( $event['language'] ) ) {
			$available = get_available_languages();
			if ( in_array( $event['language'], $available, true ) ) {
				update_post_meta( $post_id, '_event_language', $event['language'] );
			}
		}
	}

	return $event_id;
}

/**
 * Grant permissions for importer to edit event.
 *
 * @param bool $default_permission Default permissions value.
 * @param int  $event_id ID of event being edited.
 *
 * @return true
 */
function mcs_api_can_edit_event( $default_permission, $event_id ) {
	// Need to refine this to provide some filtering & awareness of whether API is firing.

	return true;
}

/**
 * Array of field descriptions to display in import process
 */
function mcs_option_fields() {
	$return = apply_filters(
		'mcs_option_fields',
		array(
			// Event data.
			'event_title'        => __( 'Title', 'my-calendar-pro' ),
			'event_begin'        => __( 'Starting Date', 'my-calendar-pro' ),
			'occur_begin'        => __( 'Starting Date/Time', 'my-calendar-pro' ),
			'event_end'          => __( 'Ending Date', 'my-calendar-pro' ),
			'occur_end'          => __( 'Ending Date/Time', 'my-calendar-pro' ),
			'event_time'         => __( 'Starting Time', 'my-calendar-pro' ),
			'event_endtime'      => __( 'Ending Time', 'my-calendar-pro' ),
			'content'            => __( 'Description', 'my-calendar-pro' ),
			'event_desc'         => __( 'Description', 'my-calendar-pro' ),
			'event_short'        => __( 'Excerpt', 'my-calendar-pro' ),
			'event_link'         => __( 'External event URL', 'my-calendar-pro' ),
			'event_link_expires' => __( 'Link expiration', 'my-calendar-pro' ),
			'event_recur'        => __( 'Recurring frequency period', 'my-calendar-pro' ),
			// above codes: S - single,D - day,E - weekdays,W - weekly,M - month/date,U - month/day,Y - year.
			'event_repeats'      => __( 'Number of repetitions', 'my-calendar-pro' ),
			// above means: 4 = event repeats 4 times, for a total of 5 occurrences.
			'event_every'        => __( 'Recurrence frequency multiplier', 'my-calendar-pro' ),
			// above represents: D + 3 == every 3 days, 2 + W == every two weeks.
			'event_image'        => __( 'Event Image URL', 'my-calendar-pro' ),
			'event_allday'       => __( 'Event is all-day', 'my-calendar-pro' ),
			'event_author'       => __( 'Author ID, username, or email.', 'my-calendar-pro' ),
			'event_approved'     => __( 'Publishing status', 'my-calendar-pro' ),
			'event_category'     => __( 'Category Name or ID', 'my-calendar-pro' ),
			'category_color'     => __( 'Category Color', 'my-calendar-pro' ),
			'category_icon'      => __( 'Category Icon', 'my-calendar-pro' ),
			'category_private'   => __( 'Category Privacy status', 'my-calendar-pro' ),
			'event_fifth_week'   => __( 'Omit week 5 recurrences', 'my-calendar-pro' ),
			'event_holiday'      => __( 'Cancel on Holidays', 'my-calendar-pro' ),
			'event_group_id'     => __( 'Event Group ID', 'my-calendar-pro' ),
			'event_span'         => __( 'Event spans multiple days', 'my-calendar-pro' ),
			'event_hide_end'     => __( 'Hide end date', 'my-calendar-pro' ),
			// Ticketing/Registration data.
			'event_open'         => __( 'Obsolete field; will be ignored', 'my-calendar-pro' ),
			'event_status'       => __( 'Event Status', 'my-calendar-pro' ),
			'event_flagged'      => __( 'Event Flagged as Spam', 'my-calendar-pro' ),
			'event_tickets'      => __( 'Event Tickets Link', 'my-calendar-pro' ),
			'event_registration' => __( 'Event Registration Info', 'my-calendar-pro' ),
			'event_host'         => __( 'Event Host ID', 'my-calendar-pro' ),
			'events_access'      => __( 'Event Accessibility Data', 'my-calendar-pro' ), // Event access features.
			// location data.
			'location_preset'    => __( 'Location ID', 'my-calendar-pro' ),
			'event_label'        => __( 'Location Label', 'my-calendar-pro' ),
			'event_street'       => __( 'Location Street', 'my-calendar-pro' ),
			'event_street2'      => __( 'Location Street (2)', 'my-calendar-pro' ),
			'event_city'         => __( 'Location City', 'my-calendar-pro' ),
			'event_state'        => __( 'Location State', 'my-calendar-pro' ),
			'event_postcode'     => __( 'Location Postcode', 'my-calendar-pro' ),
			'event_region'       => __( 'Location Region', 'my-calendar-pro' ),
			'event_country'      => __( 'Location Country', 'my-calendar-pro' ),
			'event_url'          => __( 'Location URL', 'my-calendar-pro' ),
			'event_phone'        => __( 'Location Phone Number', 'my-calendar-pro' ),
			'event_phone2'       => __( 'Alternate Location Phone', 'my-calendar-pro' ),
			'event_longitude'    => __( 'Location longitude', 'my-calendar-pro' ),
			'event_latitude'     => __( 'Location latitude', 'my-calendar-pro' ),
			'event_zoom'         => __( 'Location map zoom level', 'my-calendar-pro' ),
			'event_location'     => __( 'Location ID in locations table', 'my-calendar-pro' ),
			'event_access'       => __( 'Location access features', 'my-calendar-pro' ),
			// meta data.
			'UID'                => __( 'Unique event ID. If an event exists with this UID already, it will be updated instead of added.', 'my-calendar-pro' ),
			'occur_id'           => __( 'Unique date ID in source calendar', 'my-calendar-pro' ),
			'event_added'        => __( 'Date this event was added to source calendar', 'my-calendar-pro' ),
			'language'           => __( 'Language this event listing is written in', 'my-calendar-pro' ),
		)
	);

	return $return;
}

/**
 * Turn a CSV into an array of events for importing
 *
 * @param array  $content Array of rows from a CSV document.
 * @param string $delimiter What separates content.
 * @param string $enclosure What encloses complex string content.
 * @param string $escape What's the escape character.
 * @param string $terminator End of line indicator.
 *
 * @return array
 */
function mcs_translate_csv( $content, $delimiter = ';', $enclosure = '"', $escape = '\\', $terminator = PHP_EOL ) {
	$output = array();
	$titles = $content[0];
	unset( $content[0] );

	foreach ( $content as $key => $row ) {
		if ( $row ) {
			$r = array();
			// convert back into string.
			$values = $row;
			$i      = 0;
			foreach ( $values as $value ) {
				$value = str_replace( '\n', '<br />', $value );
				$value = str_replace( '\r', '<br />', $value );
				$value = str_replace( array( $enclosure, $escape ), '', $value );
				if ( in_array( $titles[ $i ], array( 'event_begin', 'event_end', 'event_time', 'event_endtime', 'occur_begin', 'occur_end' ), true ) ) {
					if ( in_array( $titles[ $i ], array( 'event_begin', 'event_end', 'occur_begin', 'occur_end' ), true ) ) {
						$value = mcs_date( 'Y-m-d', strtotime( $value ), false );
					} else {
						$value = mcs_date( 'H:i:s', strtotime( $value ), false );
					}
					// endtime must be listed after start time.
					if ( 'event_endtime' === $titles[ $i ] && '00:00:00' === $value ) {
						$value = mcs_date( 'H:i:s', strtotime( $r['event_time'] . ' + 1 hour' ), false );
					}
					$r[ $titles[ $i ] ] = ( isset( $value ) ) ? trim( $value ) : '';
				} else {
					$r[ $titles[ $i ] ] = ( isset( $value ) ) ? trim( $value ) : '';
				}
				++$i;
			}

			unset( $value );
		}
		$is_recurring = ( isset( $r['event_recur'] ) && ( 0 !== stripos( 'S', $r['event_recur'] ) ) ) ? true : false;
		$event_begin  = ( isset( $r['event_begin'] ) ) ? $r['event_begin'] : '';
		$event_end    = ( isset( $r['event_end'] ) ) ? $r['event_end'] : '';

		$event_begin = ( isset( $r['occur_begin'] ) && ! $is_recurring ) ? $r['occur_begin'] : $event_begin;
		$event_end   = ( isset( $r['occur_end'] ) && ! $is_recurring ) ? $r['occur_end'] : $event_end;

		$r['event_begin'] = array( $event_begin );
		$r['event_end']   = array( $event_end );

		$r['event_time'] = ( ! is_array( $r['event_time'] ) ) ? array( $r['event_time'] ) : $r['event_time'];
		if ( isset( $r['event_endtime'] ) ) {
			$r['event_endtime'] = ( ! is_array( $r['event_endtime'] ) ) ? array( $r['event_endtime'] ) : $r['event_endtime'];
		}

		if ( isset( $r['event_desc'] ) && ! isset( $r['content'] ) || ( isset( $r['event_desc'] ) && isset( $r['content'] ) && $r['event_desc'] !== $r['content'] ) ) {
			// If event_desc present, it will be preferred over content.
			$r['content'] = $r['event_desc'];
		}

		if ( strtotime( $event_end ) < strtotime( $event_begin ) ) {
			$r['event_end'] = array( $event_begin );
		}

		$output[] = $r;
	}

	unset( $row );

	return $output;
}

/**
 * Set up default event values for fields that are set by default when creating events in admin
 */
function mcs_default_event_values() {
	$expires = ( function_exists( 'mc_event_link_expires' ) && ! mc_event_link_expires() ) ? 1 : 0;

	// import values from settings & autogenerate generated values.
	$defaults = array(
		'event_fifth_week'   => ( 'true' === get_option( 'event_fifth_week' ) ) ? 1 : '',
		'event_holiday'      => ( 'true' === get_option( 'mc_skip_holidays' ) ) ? 1 : '',
		'event_group_id'     => mc_group_id(),
		'event_nonce_name'   => wp_create_nonce( 'event_nonce' ),
		'event_category'     => 1,
		'event_recur'        => 'S',
		'event_repeats'      => 0,
		'event_link_expires' => $expires,
	);

	return apply_filters( 'mcs_default_event_values', $defaults );
}

/**
 * Fetch the category ID for categories passed by name
 *
 * @param string $category_name Name of a possible category.
 *
 * @return int
 */
function mcs_category_by_name( $category_name ) {
	global $wpdb;
	$cat_id = false;
	$sql    = 'SELECT * FROM ' . my_calendar_categories_table() . ' WHERE category_name = %s';
	$cat    = $wpdb->get_row( $wpdb->prepare( $sql, $category_name ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared

	if ( is_object( $cat ) ) {
		$cat_id = $cat->category_id;
	}

	return $cat_id;
}

/**
 * See whether category exists if ID passed
 *
 * @param int $number Category ID.
 *
 * @return int
 */
function mcs_category_exists( $number ) {
	global $wpdb;
	$number = (int) $number;
	$cat_id = false;
	$sql    = 'SELECT * FROM ' . my_calendar_categories_table() . ' WHERE category_id = %d';
	$cat    = $wpdb->get_row( $wpdb->prepare( $sql, $number ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared

	if ( is_object( $cat ) ) {
		$cat_id = $cat->category_id;
	}

	return $cat_id;
}

/**
 * Insert a new category using provided data if did not already exist.
 *
 * @param string $category_name Name of category.
 * @param string $color Color string.
 * @param string $icon Img name.
 * @param bool   $is_private Is a private category.
 *
 * @return int
 */
function mcs_insert_category( $category_name, $color, $icon, $is_private ) {
	global $wpdb;
	$cat_id  = false;
	$formats = array( '%s', '%s', '%s', '%d', '%d' );
	$term    = wp_insert_term( $category_name, 'mc-event-category' );
	if ( ! is_wp_error( $term ) ) {
		$term = $term['term_id'];
	} else {
		$term = false;
	}
	$add = array(
		'category_name'    => $category_name,
		'category_color'   => ( false !== $color ) ? $color : '#243f82',
		'category_icon'    => ( false !== $icon ) ? $icon : 'event.png',
		'category_private' => ( false !== $is_private ) ? $is_private : 0,
		'category_term'    => $term,
	);
	// actions and filters.
	$wpdb->insert( my_calendar_categories_table(), $add, $formats );
	$cat_id = $wpdb->insert_id;

	return $cat_id;
}

/**
 * Get My Calendar import directory root.
 *
 * @param string $path Subdirectory, optional.
 *
 * @return string
 */
function mcs_import_directory( $path = '' ) {
	/**
	 * Filter the My Calendar import directory path.
	 *
	 * @hook mcs_import_directory
	 *
	 * @param {string} $path Subdirectory to append to source path. Optional.
	 *
	 * @return {string}
	 */
	$filepath = apply_filters( 'mcs_import_directory', wp_upload_dir()['path'], $path );
	$path     = trailingslashit( $filepath ) . $path;

	return $path;
}