HEX
Server: LiteSpeed
System: Linux server315.web-hosting.com 4.18.0-553.54.1.lve.el8.x86_64 #1 SMP Wed Jun 4 13:01:13 UTC 2025 x86_64
User: globfdxw (6114)
PHP: 8.1.34
Disabled: NONE
Upload Files
File: //home/globfdxw/www/wp-content/plugins/wpforms-google-calendar/src/Provider/FieldMapper.php
<?php

namespace WPFormsGoogleCalendar\Provider;

use RuntimeException;

/**
 * Class Mapper
 * Maps form data to Google Calendar event data.
 *
 * @since 1.0.0
 */
class FieldMapper {

	/**
	 * Connection data.
	 *
	 * @since 1.0.0
	 *
	 * @var array
	 */
	private $connection;

	/**
	 * Form data.
	 *
	 * @since 1.0.0
	 *
	 * @var array
	 */
	private $form_data;

	/**
	 * Fields data.
	 *
	 * @since 1.0.0
	 *
	 * @var array
	 */
	private $fields;

	/**
	 * Entry ID.
	 *
	 * @since 1.0.0
	 *
	 * @var int|string
	 */
	private $entry_id;

	/**
	 * Account calendars.
	 *
	 * @since 1.0.0
	 *
	 * @var array
	 */
	private $calendars;

	/**
	 * Constructor.
	 *
	 * @since 1.0.0
	 *
	 * @param array      $connection Connection data.
	 * @param array      $form_data  Form data.
	 * @param array      $fields     Fields data.
	 * @param int|string $entry_id   Entry ID.
	 * @param array      $calendars  Calendars.
	 */
	public function __construct( array $connection, array $form_data, array $fields, $entry_id, array $calendars ) {

		$this->connection = $connection;
		$this->form_data  = $form_data;
		$this->fields     = $fields;
		$this->entry_id   = $entry_id;
		$this->calendars  = $calendars;
	}

	/**
	 * Prepare event data for Google Calendar.
	 *
	 * @since 1.0.0
	 *
	 * @return array Event data.
	 *
	 * @throws RuntimeException When required data is missing.
	 */
	public function prepare_event(): array {

		$calendar_id = $this->get_option( 'calendar_id', true );
		$timezone    = $this->get_timezone( $calendar_id );
		$event       = [
			'calendar_id' => $calendar_id,
			'summary'     => $this->get_event_title(),
			'attendees'   => $this->get_guests(),
			'start'       => [
				'dateTime' => $this->get_start_datetime(),
				'timeZone' => $timezone,
			],
			'end'         => [
				'dateTime' => $this->get_end_datetime(),
				'timeZone' => $timezone,
			],
			'source'      => [
				'title' => $this->process_smart_tags(
					sprintf( /* translators: %1$s is entry ID, %2$s - form name, %3$s - form ID. */
						__( 'Submission #%1$s – %2$s(#%3$s)' ),
						'{entry_id}',
						'{form_name}',
						'{form_id}'
					)
				),
				'url'   => $this->process_smart_tags( '{page_url}' ),
			],
		];

		$this->add_optional_event_data( $event );
		$this->add_guest_permissions( $event );

		return $event;
	}

	/**
	 * Get event title.
	 *
	 * @since 1.0.0
	 *
	 * @return string Event title.
	 *
	 * @throws RuntimeException When the event title is empty.
	 */
	private function get_event_title(): string {

		$event_title = $this->process_smart_tags( $this->get_option( 'event_title', true ) );

		if ( wpforms_is_empty_string( $event_title ) ) {
			throw new RuntimeException( 'Event title is required but is empty.' );
		}

		return $event_title;
	}

	/**
	 * Get timezone from calendar settings.
	 *
	 * @since 1.0.0
	 *
	 * @param string $calendar_id Calendar ID.
	 *
	 * @return string
	 *
	 * @throws RuntimeException Calendar details not found.
	 */
	private function get_timezone( string $calendar_id ): string {

		if ( empty( $this->calendars[ $calendar_id ]['timezone'] ) ) {
			throw new RuntimeException( sprintf( 'Calendar %s not found.', esc_html( $calendar_id ) ) );
		}

		return (string) $this->calendars[ $calendar_id ]['timezone'];
	}

	/**
	 * Get start datetime.
	 *
	 * @since 1.0.0
	 *
	 * @return string Start datetime data.
	 *
	 * @throws RuntimeException When the start datetime field is empty.
	 */
	private function get_start_datetime(): string {

		$field_id = $this->get_option( 'start_datetime', true );

		if ( empty( $this->fields[ $field_id ]['unix'] ) ) {
			throw new RuntimeException( sprintf( 'Date Time field with ID %d has wrong format.', (int) $field_id ) );
		}

		return $this->get_formatted_datetime( (int) $this->fields[ $field_id ]['unix'] );
	}

	/**
	 * Get formatted end datetime.
	 *
	 * @since 1.0.0
	 *
	 * @return string End datetime data.
	 *
	 * @throws RuntimeException When the start datetime field is empty.
	 */
	private function get_end_datetime(): string {

		$start_field_id = $this->get_option( 'start_datetime', true );

		if ( empty( $this->fields[ $start_field_id ]['unix'] ) ) {
			throw new RuntimeException( sprintf( 'Date Time field with ID %d has wrong format.', (int) $start_field_id ) );
		}

		$duration        = $this->get_option( 'duration', true );
		$start_timestamp = (int) $this->fields[ $start_field_id ]['unix'];

		if ( $duration !== 'user_defined' ) {

			$duration = (int) $duration;

			if ( $duration <= 0 ) {
				throw new RuntimeException( 'Event duration must be greater than 0.' );
			}

			$end_timestamp = $start_timestamp + ( $duration * MINUTE_IN_SECONDS );

			return $this->get_formatted_datetime( $end_timestamp );
		}

		$end_field_id = $this->get_option( 'end_datetime', true );

		if ( empty( $this->fields[ $end_field_id ]['unix'] ) ) {
			throw new RuntimeException( 'Invalid end time field.' );
		}

		if ( $start_timestamp >= (int) $this->fields[ $end_field_id ]['unix'] ) {
			throw new RuntimeException( 'End time can not be less than start time.' );
		}

		return $this->get_formatted_datetime( (int) $this->fields[ $end_field_id ]['unix'] );
	}

	/**
	 * Convert the date to a specific format.
	 *
	 * @since 1.0.0
	 *
	 * @param int $unix_timestamp Timestamp.
	 *
	 * @return false|string
	 */
	private function get_formatted_datetime( int $unix_timestamp ) {

		return gmdate( 'Y-m-d\TH:i:s', $unix_timestamp );
	}

	/**
	 * Add optional event data.
	 *
	 * @since 1.0.0
	 *
	 * @param array $event Event data.
	 */
	private function add_optional_event_data( array &$event ): void {

		$event_description = $this->process_smart_tags( $this->get_option( 'event_description' ) );

		if ( $event_description ) {
			$event['description'] = $event_description;
		}

		$event_location = $this->process_smart_tags( $this->get_option( 'event_location' ) );

		if ( $event_location ) {
			$event['location'] = $event_location;
		}

		$recurrence = $this->get_recurrence();

		if ( $recurrence ) {
			$event['recurrence'][] = $recurrence;
		}
	}

	/**
	 * Add guest permissions.
	 *
	 * @since 1.0.0
	 *
	 * @param array $event Event.
	 *
	 * @return void
	 */
	private function add_guest_permissions( array &$event ): void {

		$permissions = [
			'invite_others'  => [
				'name'    => 'guestsCanInviteOthers',
				'default' => true,
			],
			'modify_event'   => [
				'name'    => 'guestsCanModify',
				'default' => false,
			],
			'see_guest_list' => [
				'name'    => 'guestsCanSeeOtherGuests',
				'default' => true,
			],
		];

		foreach ( $permissions as $option => $permission ) {
			$value = $this->get_option( $option );

			if ( $value !== null && $value !== $permission['default'] ) {
				$event[ $permission['name'] ] = (bool) $value;
			}
		}
	}

	/**
	 * Get option from connection data.
	 *
	 * @since 1.0.0
	 *
	 * @param string $key         Option key.
	 * @param bool   $is_required Whether the option is required.
	 *
	 * @return mixed Option value.
	 *
	 * @throws RuntimeException When a required option is missing.
	 */
	public function get_option( string $key, bool $is_required = false ) {

		if ( isset( $this->connection[ $key ] ) && ! wpforms_is_empty_string( $this->connection[ $key ] ) ) {
			return $this->connection[ $key ];
		}

		if ( $is_required ) {
			throw new RuntimeException( sprintf( 'Required option "%s" is missing.', esc_html( $key ) ) );
		}

		return null;
	}

	/**
	 * Process smart tags in a value.
	 *
	 * @since 1.0.0
	 *
	 * @param string|null $value Value to process.
	 *
	 * @return string Processed value.
	 */
	private function process_smart_tags( ?string $value ): string {

		if ( $value === null || wpforms_is_empty_string( $value ) ) {
			return '';
		}

		return wpforms_process_smart_tags( $value, $this->form_data, $this->fields, $this->entry_id, 'google-calendar-field-value' );
	}

	/**
	 * Get guests for the event.
	 *
	 * @since 1.0.0
	 *
	 * @return array Guests data.
	 *
	 * @throws RuntimeException When guests are not configured correctly or are empty.
	 */
	private function get_guests(): array {

		if ( empty( $this->connection['guests'] ) || ! is_array( $this->connection['guests'] ) ) {
			throw new RuntimeException( 'Guests are not configured correctly. Please check your form settings.' );
		}

		$emails = [];

		foreach ( $this->connection['guests'] as $field_id ) {
			$email_value = $this->get_field_value( $field_id );

			if ( ! is_array( $email_value ) ) {
				$emails[] = [ $email_value ];

				continue;
			}

			$emails[] = $email_value;
		}

		$emails = array_filter( array_unique( array_merge( [], ...$emails ) ) );

		return $this->convert_emails_to_guests( $emails );
	}

	/**
	 * Convert emails to guests objects.
	 *
	 * @since 1.0.0
	 *
	 * @param array $emails Submitted values.
	 *
	 * @return array
	 *
	 * @throws RuntimeException No valid emails were submitted.
	 */
	private function convert_emails_to_guests( array $emails ): array {

		$guests = [];

		foreach ( $emails as $email ) {
			if ( wpforms_is_email( $email ) ) {
				$guests[] = [
					'email' => $email,
				];
			}
		}

		if ( empty( $guests ) ) {
			throw new RuntimeException( 'No valid attendee emails were found. Please check your form submission.' );
		}

		return $guests;
	}

	/**
	 * Get recurrence rule for the event.
	 *
	 * @since        1.0.0
	 *
	 * @return string Recurrence rule in RFC5545 format.
	 */
	private function get_recurrence(): string {

		$recurrence = $this->get_option( 'recurrence' );

		if ( ! $recurrence ) {
			return '';
		}

		$recurrence_rules = [
			'daily'     => 'RRULE:FREQ=DAILY',
			'weekday'   => 'RRULE:FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR',
			'weekly'    => 'RRULE:FREQ=WEEKLY',
			'monthly'   => 'RRULE:FREQ=MONTHLY',
			'quarterly' => 'RRULE:FREQ=MONTHLY;INTERVAL=3',
			'yearly'    => 'RRULE:FREQ=YEARLY',
		];

		/**
		 * Filter recurrence rules.
		 *
		 * @since 1.0.0
		 *
		 * @param array      $recurrence_rules Recurrence rules in RFC5545 format.
		 * @param string     $recurrence       Recurrence type.
		 * @param array      $connection       Connection data.
		 * @param array      $form_data        Form data and settings.
		 * @param array      $fields           Form fields data.
		 * @param int|string $entry_id         Entry ID.
		 */
		$recurrence_rules = (array) apply_filters(
			'wpforms_google_calendar_provider_field_mapper_recurrence_rules',
			$recurrence_rules,
			$recurrence,
			$this->connection,
			$this->form_data,
			$this->fields,
			$this->entry_id
		);

		return $recurrence_rules[ $recurrence ] ?? '';
	}

	/**
	 * Get field value by field ID.
	 *
	 * @since 1.0.0
	 *
	 * @param string $field_id Field ID.
	 *
	 * @return string|array Field value or array of values for repeater fields.
	 */
	private function get_field_value( string $field_id ) {

		if ( wpforms_is_repeated_field( $field_id, $this->fields ) ) {
			return $this->get_repeater_field_values( $field_id );
		}

		return $this->fields[ $field_id ]['value'] ?? '';
	}

	/**
	 * Get a list of repeated field values by field ID.
	 *
	 * @since 1.0.0
	 *
	 * @param string $repeated_field_id Repeated field ID.
	 *
	 * @return array
	 */
	private function get_repeater_field_values( string $repeated_field_id ): array {

		$values          = [
			$this->fields[ $repeated_field_id ]['value'] ?? '',
		];
		$repeater_ids    = wpforms_get_repeater_field_ids( $repeated_field_id );
		$repeater_prefix = $repeater_ids['original_id'] . '_';

		foreach ( $this->fields as $field_id => $field ) {
			if ( strpos( $field_id, $repeater_prefix ) === 0 ) {
				$values[] = $this->fields[ $field_id ]['value'] ?? '';
			}
		}

		return array_filter( $values );
	}
}