<?php
/**
 * MDJM_Google class
 *
 * @package     MDJM Google Calendar Sync
 * @subpackage  Classes/Google
 * @copyright   Copyright (c) 2017, Mike Howard
 * @license     http://opensource.org/licenses/gpl-2.0.php GNU Public License
 * @since       2.0
*/

// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class MDJM_Google {

	/**
	 * @var     bool    $connected
	 * @since   2.0
	 */
	public $connected = false;

	/**
	 * @var     obj     $client
	 * @since   2.0
	 */
	private $client;

	/**
	 * @var     str     $calendar_id
	 * @since   2.0
	 */
	private $calendar_id;

	/**
	 * @var     str     $calendar
	 * @since   2.0
	 */
	private $calendar;

	private $auth  = false;
	private $token = false;
	private $refresh_token = false;
	private $full_sync;
	private $absence_sync;
	private $timezone;

	/**
	 * @var     str     $remind_when    Global reminder time
	 * @since   2.0
	 */
	public $remind_when;

	/**
	 * @var     str     $remind_type    Global reminder type
	 * @since   2.0
	 */
	public $remind_type;

	/**
	 * @var     arr     $sync_status    Event Status' to sync
	 * @since   2.0
	 */
	public $sync_status;

	/**
	 * Get things going.
	 */
	public function __construct() {
		$this->auth  = mdjm_get_option( 'google_calendar_auth_code' );
		$this->token = get_transient( 'mdjm_google_calendar_token' );

		$this->refresh_token = mdjm_get_option( 'google_calendar_refresh_token' );
		if ( ! $this->auth ) {
			return false;
		}

		$this->init();

		if ( ! $this->connected ) {
			return false;
		}

		// Logout
		add_action( 'mdjm-logout_google', array( $this, 'logout' ) );

		// Delete Event Entry
		add_action( 'mdjm-delete_event_entry', array( $this, 'delete_entry_for_event' ) );

		// Remove all entries
		add_action( 'mdjm_google_delete_all_entries', array( $this, 'delete_all_entries' ) );

		// Daily sync
		add_action( 'mdjm_daily_scheduled_events', array( $this, 'sync_all_events' ) );

		// All absence sync
		add_action( 'mdjm_google_calendar_absence_sync', array( $this, 'sync_all_absences' ) );

	} // __construct

	/**
	 * Initialise
	 */
	private function init() {

		require_once MDJM_GC_DIR . '/vendor/autoload.php';

		$this->setup_client();

		if ( ! $this->client ) {
			return false;
		}

		if ( ! $this->calendar_connect() ) {
			return false;
		}

		$this->connected = true;

		$this->calendar_id  = mdjm_get_option( 'google_calendar' );
		$this->full_sync    = mdjm_get_option( 'google_calendar_full_sync' );
		$this->absence_sync = mdjm_get_option( 'google_calendar_absence_sync' );
		$this->remind_when  = mdjm_get_option( 'google_calendar_reminder_when', '24' );
		$this->remind_type  = mdjm_get_option( 'google_calendar_reminder_type', false );
		$this->sync_status  = mdjm_get_option( 'google_calendar_event_status', array() );
		$this->timezone = $this->get_calendar_settings( 'timezone' );

		if ( empty( $this->timezone ) ) {
			$this->timezone = $this->get_wp_timezone_fallback();
		}
	} // init


/**
 * Retrieve a valid timezone string from WordPress settings.
 *
 * Falls back to a timezone name derived from gmt_offset, then UTC.
 *
 * @since  2.0
 * @return string
 */
private function get_wp_timezone_fallback() {
	$timezone = function_exists( 'wp_timezone_string' ) ? wp_timezone_string() : get_option( 'timezone_string' );

	if ( ! empty( $timezone ) ) {
		return $timezone;
	}

	$offset   = (float) get_option( 'gmt_offset', 0 );
	$timezone = timezone_name_from_abbr( '', (int) ( $offset * 3600 ), 0 );

	if ( false === $timezone || empty( $timezone ) ) {
		$timezone = 'UTC';
	}

	return $timezone;
}

	/**
	 * Reminder type options
	 *
	 * @access  public
	 * @since   2.0
	 * @return  arr     Array of reminder options
	 */
	public function reminder_types() {
		$reminder_types = array(
			'0'     => __( 'Use Calendar Default', 'mdjm-google-calendar-sync' ),
			'email' => __( 'Email', 'mdjm-google-calendar-sync' ),
			'popup' => __( 'Screen Popup', 'mdjm-google-calendar-sync' ),
			'both'  => __( 'Email and Screen Popup', 'mdjm-google-calendar-sync' ),
		);

		return apply_filters( 'mdjm_google_calendar_reminder_types', $reminder_types );
	} // reminder_types

	/**
	 * Destroy Google session
	 *
	 * @access  public
	 * @since   2.0
	 */
	public function logout( $data ) {

		if ( ! isset( $data['mdjm-google-action'] ) || ! wp_verify_nonce( $data['mdjm-google-action'], 'logout_google' ) ) {
			return;
		}

		mdjm_delete_option( 'google_calendar_auth_code' );
		delete_transient( 'mdjm_google_calendar_token' );

		unset( $this->client );
		$this->connected = false;

		$redirect = add_query_arg( array(
			'post_type' => 'mdjm-event',
			'page'      => 'mdjm-settings',
			'tab'       => 'extensions',
			'section'   => 'google_calendar',
		), admin_url( 'edit.php' ) );

		wp_safe_redirect( $redirect );
		exit;

	} // logout

	/**
	 * Google client object
	 *
	 * @access  public
	 * @since   2.0
	 * @return  obj
	 */
	public function setup_client() {
		$this->client = new Google_Client();
		$this->client->setApprovalPrompt( 'force' );
		$this->client->setAccessType( 'offline' );
		$this->client->setApplicationName( MDJM_NAME );
		$this->client->setClientId( '413510716355-p24bqf875msakqpsp4eegtbl7l566jhn.apps.googleusercontent.com' );
		$this->client->setClientSecret( 'u1j4ahkkVYnW3cEbxybDJCJD' );
		$this->client->setRedirectUri( 'urn:ietf:wg:oauth:2.0:oob' );
		$this->client->setScopes(
			array(
				'https://www.googleapis.com/auth/calendar',
			)
		);
} // setup_client

	/**
	 * Google calendar service object
	 *
	 * @access  public
	 * @since   2.0
	 * @return  obj
	 */
public function calendar_connect() {

	try {
		$this->calendar = new Google_Service_Calendar( $this->client );
	} catch ( Google_Service_Exception $e ) {
		MDJM()->debug->log_it( 'ERROR: ' . $e->getMessage() );
		return false;
	}

	// Normalise token from transient (may be JSON string or array).
	$token = false;

	if ( ! empty( $this->token ) ) {
		$token = $this->token;

		if ( is_string( $token ) ) {
			$decoded = json_decode( $token, true );
			if ( is_array( $decoded ) ) {
				$token = $decoded;
			}
		}
	}

	if ( ! empty( $token ) ) {
		try {
			$this->client->setAccessToken( $token );
		} catch ( Exception $e ) {
			MDJM()->debug->log_it( 'Access token error. ' . $e->getMessage() );
			$token = false;
		}
	}

	// If we have a token, attempt refresh if required.
	if ( ! empty( $token ) ) {
		if ( method_exists( $this->client, 'isAccessTokenExpired' ) && $this->client->isAccessTokenExpired() ) {

			$refresh_token = ! empty( $this->refresh_token ) ? $this->refresh_token : mdjm_get_option( 'google_calendar_refresh_token' );

			if ( ! empty( $refresh_token ) ) {

				try {
					$new_token = $this->client->fetchAccessTokenWithRefreshToken( $refresh_token );
				} catch ( Exception $e ) {
					MDJM()->debug->log_it( 'Unable to refresh Google token. ' . $e->getMessage() );
					$new_token = false;
				}

				if ( is_array( $new_token ) && empty( $new_token['error'] ) ) {

					// Preserve refresh token if Google didn't return it.
					if ( empty( $new_token['refresh_token'] ) ) {
						$new_token['refresh_token'] = $refresh_token;
					}

					$this->client->setAccessToken( $new_token );

					$expires = ! empty( $new_token['expires_in'] ) ? absint( $new_token['expires_in'] ) : HOUR_IN_SECONDS;
					$expires = max( 60, $expires - 60 );

					set_transient( 'mdjm_google_calendar_token', wp_json_encode( $new_token ), $expires );

					$this->token = wp_json_encode( $new_token );
					$this->refresh_token = $refresh_token;

					return true;
				}
			}
		}

		return true;
	}

	// No token stored, attempt to authenticate using the saved auth code.
	try {
		$access_token = $this->client->fetchAccessTokenWithAuthCode( $this->auth );
	} catch ( Exception $e ) {
		MDJM()->debug->log_it( 'Unable to connect to calendar. ' . $e->getMessage() );
		return false;
	}

	if ( empty( $access_token ) ) {
		return false;
	}

	// Token may be returned as a JSON string or an array.
	if ( is_string( $access_token ) ) {
		$decoded = json_decode( $access_token, true );
		if ( is_array( $decoded ) ) {
			$access_token = $decoded;
		}
	}

	try {
		$this->client->setAccessToken( $access_token );
	} catch ( Exception $e ) {
		MDJM()->debug->log_it( 'Access token error. ' . $e->getMessage() . var_export( $access_token, true ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export
		return false;
	}

	// Store refresh token for future refreshes (only returned on first consent).
	if ( is_array( $access_token ) && ! empty( $access_token['refresh_token'] ) ) {
		mdjm_update_option( 'google_calendar_refresh_token', sanitize_text_field( $access_token['refresh_token'] ) );
		$this->refresh_token = sanitize_text_field( $access_token['refresh_token'] );
	}

	$expires = 0;

	if ( is_array( $access_token ) && ! empty( $access_token['expires_in'] ) ) {
		$expires = max( 60, absint( $access_token['expires_in'] ) - 60 );
	} else {
		$expires = HOUR_IN_SECONDS;
	}

	set_transient( 'mdjm_google_calendar_token', is_array( $access_token ) ? wp_json_encode( $access_token ) : $access_token, $expires );

	$redirect = add_query_arg(
		array(
			'post_type' => 'mdjm-event',
			'page'      => 'mdjm-settings',
			'tab'       => 'extensions',
			'section'   => 'google_calendar',
		),
		admin_url( 'edit.php' )
	);

	wp_safe_redirect( $redirect );
	exit;

} // calendar_connect


	/**
	 * List Google Calendars
	 *
	 * @access  public
	 * @since   2.0
	 * @return  arr
	 */
	public function list_calendars() {
		if ( ! $this->connected ) {
			return false;
		}

		$calendar_list = $this->calendar->calendarList->listCalendarList();

		if ( empty( $calendar_list ) ) {
			return false;
		} else {
			foreach ( $calendar_list->getItems() as $calendar ) {
				$calendar_id = $calendar->id;
				if ( ! empty( $calendar->primary ) ) {
					$calendar_id = 'primary';
				}
				$calendars[ $calendar_id ] = $calendar->summary;
			}
		}

		return $calendars;
	} // list_calendars

	/**
	 * Retrieve the specified setting from the calendar
	 *
	 * @param   str     $field  Setting field to fetch
	 * @see     https://developers.google.com/google-apps/calendar/v3/reference/settings#resource
	 *
	 * @return  str     The value of the setting field
	 */
	public function get_calendar_settings( $field = false ) {

		if ( ! $field || ! $this->connected ) {
			return false;
		}

		try {
			$setting = $this->calendar->settings->get( $field );
		} catch ( Google_Service_Exception $e ) {
			MDJM()->debug->log_it( "Unable to retrieve the $field setting. " . $e->getMessage() );
			return false;
		}

		return $setting->getValue();

	} // get_cal_settings

	/**
	 * Determine if a calendar entry exists.
	 *
	 * @param   str         $id             The calendar entry ID
	 * @return  str|bool    Calendar entry ID or false
	 */
	public function entry_exists( $id ) {
		try {
			$entry = $this->calendar->events->get( $this->calendar_id, $id );
			return $entry;
		} catch ( Google_Service_Exception $e ) {
			return false;
		}
	} // entry_exists

	/**
	 * Perform a full event sync.
	 *
	 * @return  void
	 */
	public function sync_all_events() {
		if ( ! $this->full_sync || ! $this->connected ) {
			return;
		}

		$events = mdjm_get_events();

		if ( ! $events ) {
			return;
		}

		foreach ( $events as $event ) {
			$this->sync_event( $event, $event->post_status );
		}

	} // sync_all_events

    /**
	 * Perform a full emplolyee absence sync.
	 *
	 * @return  void
	 */
	public function sync_all_absences() {
		if ( ! $this->absence_sync || ! $this->connected ) {
			return;
		}

		$absences = mdjm_get_all_absences();

        if ( ! empty( $absences ) ) {
            foreach ( $absences as $absence ) {
                $this->sync_absence( $absence );
            }
        }

	} // sync_all_absences

    /**
     * Determine whether to add or update an absence in Google.
     *
     * @since   2.1
     * @param   object      $absence    Absence database object
     * @return  string|bool Calendar entry ID or false on failure
     */
    public function sync_absence( $absence ) {
        if ( ! $this->absence_sync || ! $this->connected ) {
			return false;
		}

        if ( ! is_object( $absence ) ) {
            $absence = MDJM()->availability_db->get_entry( absint( $absence ) );
        }

        if ( ! empty( $absence ) ) {
            $exists = MDJM()->availability_meta_db->get_meta( $absence->id, 'google_calendar_entry', true );

            if ( empty( $exists ) ) {
                return $this->add_absence( $absence );
            }
        }

        return false;
    } // sync_absence

    /**
     * Adds a single absence entry to Google.
     *
     * @since   2.1
     * @param   object  $absence    Absence database object
     * @return  string  Calendar entry ID or false on failure
     */
    public function add_absence( $absence ) {
        $calendar_data = array();
        $start         = new Google_Service_Calendar_EventDateTime();
        $end           = new Google_Service_Calendar_EventDateTime();
		$start_date    = $absence->start;
		$end_date      = $absence->end;

		$start->setTimeZone( $this->timezone );
		$start->setDateTime( date( 'c', strtotime( $start_date ) ) ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
        $end->setTimeZone( $this->timezone );
		$end->setDateTime( date( 'c', strtotime( $end_date ) ) ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date

		$calendar_data['summary']      = $this->set_absence_summary( $absence );
		$calendar_data['description']  = $this->set_absence_description( $absence );
		$calendar_data['start']        = $start;
		$calendar_data['end']          = $end;
		$calendar_data['status']       = 'confirmed';
		$calendar_data['transparency'] = 'transparent';

		$google_entry = new Google_Service_Calendar_Event( $calendar_data );

        try {
			$insert = $this->calendar->events->insert( $this->calendar_id, $google_entry );
		} catch ( Google_Service_Exception $e ) {
			MDJM()->debug->log_it( 'Absence ' . $absence->id . ' could not be added to Google. ' . $e->getMessage() );
			return false;
		}

        $calendar_meta = array(
            'google_calendar'       => $this->calendar_id,
            'google_calendar_entry' => $insert->id,
            'google_calendar_url'   => esc_url( $insert->htmlLink ), // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
        );

        $calendar_data = apply_filters( 'mdjm_google_calendar_absence_meta', $calendar_meta, $calendar_data );

        foreach ( $calendar_data as $key => $value ) {
            MDJM()->availability_meta_db->add_meta( $absence->id, $key, $value, true );
        }

        if ( ! empty( $insert->id ) ) {
            $google_entry = $this->calendar->events->get( $this->calendar_id, $insert->id );
            $google_entry->setReminders( $this->set_absence_reminders( $absence ) );

            try {
                $updated = $this->calendar->events->update( $this->calendar_id, $google_entry->getId(), $google_entry );
            } catch ( Google_Service_Exception $e ) {
                return false;
            }
        }

        return $insert->id;
    } // add_absence

    /**
     * Deletes a single absence entry from Google.
     *
     * @since   2.1
     * @param   id      $id    Absence database row id
     * @return  string  Calendar entry ID or false on failure
     */
    public function delete_absence( $id ) {
        $entry_id = MDJM()->availability_meta_db->get_meta( $id, 'google_calendar_entry', true );

        if ( ! empty( $entry_id ) && $this->entry_exists( $entry_id ) ) {
            try {
                $this->calendar->events->delete( $this->calendar_id, $entry_id );

				$meta_keys = array(
					'google_calendar',
					'google_calendar_entry',
					'google_calendar_url',
				);

				foreach ( $meta_keys as $meta_key ) {
					MDJM()->availability_meta_db->delete_meta( $id, $meta_key );
				}
			} catch ( Google_Service_Exception $e ) {
                MDJM()->debug->log_it( 'Calendar entry could not be deleted for absence ' . $id . '. ' . $e->getMessage() );
            }
        }
    } // delete_absence

	/**
	 * Syncronise the calendar on event save.
	 *
	 * @param   obj     $post   The post object
	 * @param   str     $status The post status
	 * @return  str     The value of the setting field
	 */
	public function sync_event( $post, $status ) {
		if ( ! $this->connected ) {
			return false;
		}

		if ( 'auto-draft' === $status ) {
			return false;
		}

		$existing_entry = get_post_meta( $post->ID, '_mdjm_event_google_calendar_entry', true );
		$exclude_event  = get_post_meta( $post->ID, '_mdjm_event_google_calendar_excluded', true );

		if ( ! in_array( $status, $this->sync_status, true ) || ! empty( $exclude_event ) ) {
			if ( $existing_entry ) {
				$this->delete_entry( $existing_entry['id'], $post->ID );
			}

			return;
		}

		if ( ! empty( $existing_entry ) ) {
			return $this->update_entry( $post->ID, $existing_entry['id'] );
		} else {
			return $this->add_entry( $post->ID );
		}

	} // sync_event

	/**
	 * Adds a calendar entry.
	 *
	 * @param   int         $id             The post ID
	 * @param   obj         $event          MDJM_Event object
	 * @return  str|bool    Calendar entry ID or false
	 */
	public function add_entry( $id, $event = '' ) {

		if ( empty( $event ) ) {
			$event = new MDJM_Event( $id );
			if ( empty( $event ) ) {
				return false;
			}
		}

		$exclude_event = get_post_meta( $event->ID, '_mdjm_event_google_calendar_excluded', true );
		if ( ! empty( $exclude_event ) ) {
			return;
		}

		$calendar_entry = array();

		$calendar_entry['summary']      = $this->set_event_summary( $event );
		$calendar_entry['location']     = $this->set_event_location( $event );
		$calendar_entry['description']  = $this->set_event_description( $event );
		$calendar_entry['start']        = $this->set_event_start_time( $event );
		$calendar_entry['end']          = $this->set_event_end_time( $event );
		$calendar_entry['reminders']    = $this->set_event_reminders( $event );
		$calendar_entry['status']       = $this->set_event_status( $event );
		$calendar_entry['transparency'] = $this->set_event_transparency( $event );

		$google_entry = new Google_Service_Calendar_Event( $calendar_entry );

		try {

			$insert = $this->calendar->events->insert( $this->calendar_id, $google_entry );

		} catch ( Google_Service_Exception $e ) {
			MDJM()->debug->log_it( 'Calendar entry could not be created for event ' . $event->ID . '. ' . $e->getMessage() );

			return false;
		}

		if ( ! empty( $insert ) ) {

			MDJM()->debug->log_it( 'Calendar entry successfully created for event ' . $event->ID );

			$calendar_event = array(
				'calendar' => $this->calendar_id,
				'id'       => $insert->id,
				'link'     => esc_url( $insert->htmlLink ), // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
				'iCalUID'  => $insert->iCalUID, // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
			);

			add_post_meta( $event->ID, '_mdjm_event_google_calendar_entry', $calendar_event, true );

			return $insert->id;

		}

	} // add_entry

	/**
	 * Updates a calendar entry.
	 *
	 * @param   int         $id             The post ID
	 * @param   str         $calendar_entry The calendar entry ID
	 * @param   obj         $event          MDJM_Event object
	 * @return  str|bool    Calendar entry ID or false
	 */
	public function update_entry( $id, $calendar_entry, $event = '' ) {

		if ( empty( $event ) ) {
			$event = new MDJM_Event( $id );
			if ( empty( $event ) ) {
				return false;
			}
		}

		$exclude_event = get_post_meta( $event->ID, '_mdjm_event_google_calendar_excluded', true );
		if ( ! empty( $exclude_event ) ) {
			return;
		}

		if ( ! $this->entry_exists( $calendar_entry ) ) {
			delete_post_meta( $event->ID, '_mdjm_event_google_calendar_entry' );
			return $this->add_entry( $event->ID, $event );
		}

		$google_entry = $this->calendar->events->get( $this->calendar_id, $calendar_entry );

		$google_entry->setSummary( $this->set_event_summary( $event ) );
		$google_entry->setLocation( $this->set_event_location( $event ) );
		$google_entry->setDescription( $this->set_event_description( $event ) );
		$google_entry->setStart( $this->set_event_start_time( $event ) );
		$google_entry->setEnd( $this->set_event_end_time( $event ) );
		$google_entry->setReminders( $this->set_event_reminders( $event ) );
		$google_entry->setStatus( $this->set_event_status( $event ) );
		$google_entry->setTransparency( $this->set_event_transparency( $event ) );

		try {

			$updated = $this->calendar->events->update( $this->calendar_id, $google_entry->getId(), $google_entry );

		} catch ( Google_Service_Exception $e ) {

			MDJM()->debug->log_it( 'Calendar entry could not be updated for event ' . $event->ID . '. ' . $e->getMessage() );

			return false;

		}

		if ( ! empty( $updated ) ) {

			MDJM()->debug->log_it( 'Calendar entry successfully updated for event ' . $event->ID );
			MDJM()->debug->log_it( var_export( $google_entry, true ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export

			$calendar_event = array(
				'calendar' => $this->calendar_id,
				'id'       => $updated->id,
				'link'     => esc_url( $updated->htmlLink ), // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
				'iCalUID'  => $updated->iCalUID, // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
			);

			update_post_meta( $event->ID, '_mdjm_event_google_calendar_entry', $calendar_event );

			return $updated->id;

		}

	} // update_entry

	/**
	 * Delete a calendar event entry.
	 *
	 * @param   arr     $data
	 * @return  void
	 */
	public function delete_entry_for_event( $data ) {
		if ( ! isset( $data['mdjm-google-action'] ) || ! wp_verify_nonce( $data['mdjm-google-action'], 'delete_google_event_entry' ) ) {
			return;
		}

		if ( empty( $data['calendar_entry'] ) || empty( $data['event_id'] ) ) {
			return;
		}

		if ( ! $this->connected ) {
			return;
		}

		if ( ! $this->entry_exists( $data['calendar_entry'] ) ) {
			return;
		}

		if ( $this->delete_entry( $data['calendar_entry'], $data['event_id'] ) ) {
			$message = 'calendar-entry-deleted';
		} else {
			$message = 'calendar-entry-delete-failed';
		}

		$redirect = add_query_arg( array(
			'post'            => $data['event_id'],
			'action'          => 'edit',
			'mdjm-gc-message' => $message,
		), admin_url( 'post.php' ) );

		wp_safe_redirect( $redirect );
		exit;
	} // delete_entry_for_event

	/**
	 * Delete a calendar entry.
	 *
	 * @param   str     $calendar_entry The calendar entry ID
	 * @param   int     $event_id       The event ID
	 * @return  void
	 */
	public function delete_entry( $calendar_entry, $event_id = false ) {
		if ( $this->entry_exists( $calendar_entry ) ) {
			try {
				$this->calendar->events->delete( $this->calendar_id, $calendar_entry );
			} catch ( Google_Service_Exception $e ) {
				MDJM()->debug->log_it( 'Calendar entry could not be deleted for event ' . $event_id . '. ' . $e->getMessage() );
			}
		}

		if ( $event_id ) {
			return delete_post_meta( $event_id, '_mdjm_event_google_calendar_entry' );
		}
	} // delete_entry

	/**
	 * Delete all calendar entries.
	 *
	 * Searches for all events that have a calendar entry and removes the entry
	 * as well as the associated post meta
	 *
	 * @return  void
	 */
	public function delete_all_entries() {
		global $wpdb;

		$args = array(
			'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
				'key'     => '_mdjm_event_google_calendar_entry',
				'compare' => 'EXISTS',
			),
			'fields'     => 'ids',
		);

		$event_ids = mdjm_get_events( $args );

		if ( ! empty( $event_ids ) ) {
			foreach ( $event_ids as $event_id ) {
				$calendar_entry = get_post_meta( $event_id, '_mdjm_event_google_calendar_entry', true );

				if ( ! empty( $calendar_entry ) && ! empty( $calendar_entry['id'] ) ) {
					$this->delete_entry( $calendar_entry['id'], $event_id );
				}
			}
		}

		$absence_table = esc_sql( MDJM()->availability_meta_db->table_name );
		$absences      = $wpdb->get_results( $wpdb->prepare( "SELECT `mdjm_availability_id` as `id`, `meta_value` as `value` FROM $absence_table WHERE `meta_key` = 'google_calendar_entry'" ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching

		if ( ! empty( $absences ) ) {
			foreach ( $absences as $absence ) {
				$this->delete_absence( $absence->id );
			}
		}

		wp_clear_scheduled_hook( 'mdjm_google_calendar_daily_sync' );
		wp_schedule_event( time() + 10, 'daily', 'mdjm_google_calendar_daily_sync' );
	} // delete_all_entries

	/*----------------------------
	-- CALENDAR FORMATTING
	----------------------------*/
	/**
	 * Absence entry content tags.
	 *
	 * @since   2.1
	 * @param   string  $content    The content to search and replace
	 * @param   object  $absence    Absence database object
	 * @return  string  Filtered content
	 */
	public function absence_content_tags( $content = '', $absence = false ) {

		if ( ! empty( $content && ! empty( $absence ) ) ) {
			$employee_id = $absence->employee_id;
			$start       = strtotime( $absence->start );
			$end         = strtotime( $absence->end );
			$notes       = ! empty( $absence->notes ) ? stripslashes( $absence->notes ) : '';
			$date_format = get_option( 'date_format' ) . ' ' . get_option( 'time_format' );

			$search = array(
				'{employee_name}',
				'{start}',
				'{end}',
				'{notes}',
			);

			$replace = array(
				mdjm_get_employee_display_name( $employee_id ),
				date( $date_format, $start ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
				date( $date_format, $end ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
				$notes,
			);

			$content = str_replace( $search, $replace, $content );
		}

		return $content;
	} // absence_content_tags


	/**
	 * Sets the summary for an absence calendar entry.
	 *
	 * @since   2.1
	 * @param   object  $absence    Absence database result object
	 * @return  string
	 */
	public function set_absence_summary( $absence ) {
		$summary = html_entity_decode( mdjm_do_absence_content_tags(
			mdjm_get_option( 'google_calendar_absence_title' ),
			$absence
		) );

		return apply_filters( 'mdjm_google_set_absence_summary', $summary );
	} // set_absence_summary

	/**
	 * Sets the description for an absdnce calendar entry.
	 *
	 * @since   2.1
	 * @param   object  $absence    Absence database result object
	 * @return  string
	 */
	public function set_absence_description( $absence ) {
		$description = utf8_encode( str_replace( PHP_EOL, '<br />', mdjm_do_absence_content_tags(
			mdjm_get_option( 'google_calendar_absence_body' ),
			$absence
		) ) );

		return apply_filters( 'mdjm_google_set_absence_description', $description );
	} // set_absence_description

	/**
	 * Configures reminder for an absence calendar entry.
	 *
	 * @since   2.1
	 * @param   object  $absence    Absence database result object
	 * @return  array
	 */
	public function set_absence_reminders( $absence ) {

		$reminder_type = mdjm_get_option( 'google_calendar_absence_reminder_type' );
		$reminder_when = mdjm_get_option( 'google_calendar_absence_reminder_when' );

		$absence_reminders = new Google_Service_Calendar_EventReminders();
		$absence_reminders->setUseDefault( ! empty( $reminder_type ) ? false : true );

		if ( ! empty( $reminder_type ) && ! empty( $reminder_when ) && $reminder_when > 0 && $reminder_when <= 672 ) {

			$reminder_type = strtolower( $reminder_type );

			if ( 'email' === $reminder_type || 'both' === $reminder_type ) {
				$reminders[] = array(
					'method'  => 'email',
					'minutes' => $reminder_when * 60,
				);
			}

			if ( 'popup' === $reminder_type || 'both' === $reminder_type ) {
				$reminders[] = array(
					'method'  => 'popup',
					'minutes' => $reminder_when * 60,
				);
			}

			if ( ! empty( $reminders ) ) {
				$absence_reminders->setOverrides( $reminders );
			}
		}

		return $absence_reminders;
	} // set_absence_reminders

	/**
	 * Sets the summary for an event calendar entry.
	 *
	 * @param   obj $event  MDJM_Event object
	 * @return  str
	 */
	public function set_event_summary( $event ) {
		$summary = html_entity_decode( mdjm_do_content_tags(
			mdjm_get_option( 'google_calendar_entry_title' ),
			$event->ID,
			$event->client
		) );

		return apply_filters( 'mdjm_google_set_event_summary', $summary );
	} // set_event_summary

	/**
	 * Sets the location for an event calendar entry.
	 *
	 * @param   obj $event  MDJM_Event object
	 * @return  str
	 */
	public function set_event_location( $event ) {
		$location = html_entity_decode( str_replace( '<br />', ', ', mdjm_do_content_tags(
			mdjm_get_option( 'google_calendar_entry_venue' ),
			$event->ID,
			$event->client
		) ) );

		return apply_filters( 'mdjm_google_set_event_location', $location );
	} // set_event_location

	/**
	 * Sets the description for an event calendar entry.
	 *
	 * @param   obj $event  MDJM_Event object
	 * @return  str
	 */
	public function set_event_description( $event ) {
		$description = mdjm_do_content_tags(
			mdjm_get_option( 'google_calendar_entry_body' ),
			$event->ID,
			$event->client
		);
		$description = str_replace( PHP_EOL, '<br />', $description );
		$description = utf8_encode( $description );

		return apply_filters( 'mdjm_google_set_event_description', $description );
	} // set_event_description

	/**
	 * Sets the start time for an event calendar entry.
	 *
	 * @param   obj $event  MDJM_Event object
	 * @return  arr
	 */
	public function set_event_start_time( $event ) {
		$start = new Google_Service_Calendar_EventDateTime();

		$event_date = $event->date;
		$time       = $event->get_start_time();

		$tz_string = ! empty( $this->timezone ) ? $this->timezone : $this->get_wp_timezone_fallback();

		try {
			$tz = new DateTimeZone( $tz_string );
		} catch ( Exception $e ) {
			$tz_string = $this->get_wp_timezone_fallback();
			$tz        = new DateTimeZone( $tz_string );
		}

		$start_dt = DateTimeImmutable::createFromFormat( 'Y-m-d H:i:s', $event_date . ' ' . $time, $tz );

		if ( false === $start_dt ) {
			$start_dt = new DateTimeImmutable( $event_date . ' ' . $time, $tz );
		}

		$start->setTimeZone( $tz_string );
		$start->setDateTime( $start_dt->format( DateTimeInterface::RFC3339 ) );

		return $start;
	} // set_event_start_time

	/**
	 * Sets the end time for an event calendar entry.
	 *
	 * @param   obj $event  MDJM_Event object
	 * @return  arr
	 */
	public function set_event_end_time( $event ) {
		$end = new Google_Service_Calendar_EventDateTime();

		$time     = $event->get_finish_time();
		$end_date = get_post_meta( $event->ID, '_mdjm_event_end_date', true );

		if ( empty( $end_date ) ) {
			$end_date = $event->date;
		}

		$tz_string = ! empty( $this->timezone ) ? $this->timezone : $this->get_wp_timezone_fallback();

		try {
			$tz = new DateTimeZone( $tz_string );
		} catch ( Exception $e ) {
			$tz_string = $this->get_wp_timezone_fallback();
			$tz        = new DateTimeZone( $tz_string );
		}

		$end_dt = DateTimeImmutable::createFromFormat( 'Y-m-d H:i:s', $end_date . ' ' . $time, $tz );

		if ( false === $end_dt ) {
			$end_dt = new DateTimeImmutable( $end_date . ' ' . $time, $tz );
		}

		$end->setTimeZone( $tz_string );
		$end->setDateTime( $end_dt->format( DateTimeInterface::RFC3339 ) );

		return $end;
	} // set_event_end_time

	/**
	 * Configures reminder for an event calendar entry.
	 *
	 * @param   obj $event  MDJM_Event object
	 * @return  arr
	 */
	public function set_event_reminders( $event ) {
		$reminder_type = get_post_meta( $event->ID, '_mdjm_event_google_calendar_reminder_type', true );
		$reminder_when = get_post_meta( $event->ID, '_mdjm_event_google_calendar_reminder_when', true );

		if ( empty( $reminder_type ) ) {
			$reminder_type = $this->remind_type;
		}

		if ( empty( $reminder_when ) ) {
			$reminder_when = $this->remind_when;
		}

		$event_reminders = new Google_Service_Calendar_EventReminders();
		$event_reminders->setUseDefault( ! empty( $reminder_type ) ? false : true );

		if ( ! empty( $reminder_type ) && ! empty( $reminder_when ) && $reminder_when > 0 && $reminder_when <= 672 ) {

			$reminder_type = strtolower( $reminder_type );

			if ( 'email' === $reminder_type || 'both' === $reminder_type ) {
				$reminders[] = array(
					'method'  => 'email',
					'minutes' => $reminder_when * 60,
				);
			}

			if ( 'popup' === $reminder_type || 'both' === $reminder_type ) {
				$reminders[] = array(
					'method'  => 'popup',
					'minutes' => $reminder_when * 60,
				);
			}

			if ( ! empty( $reminders ) ) {
				$event_reminders->setOverrides( $reminders );
			}
		}

		return $event_reminders;
	} // set_event_reminders

	/**
	 * Configures the status for an event calendar entry.
	 *
	 * @param   obj $event  MDJM_Event object
	 * @return  arr
	 */
	public function set_event_status( $event ) {

		$event_status = get_post_status( $event->ID );

		switch ( $event_status ) {
			case 'mdjm-unattended':
			case 'mdjm-enquiry':
			case 'mdjm-contract':
			case 'mdjm-awaitingdeposit':
				$status = 'tentative';
				break;

			case 'mdjm-cancelled':
			case 'mdjm-rejected':
			case 'mdjm-failed':
				$status = 'cancelled';
				break;

			case 'mdjm-approved':
			case 'mdjm-completed':
			default:
				$status = 'confirmed';
				break;
		}

		return $status;
	} // set_event_status

	/**
	 * Configures the transparency for an event calendar entry.
	 *
	 * @since   2.1
	 * @param   object  $event  MDJM_Event object
	 * @return  array
	 */
	public function set_event_transparency( $event ) {

		$event_status = get_post_status( $event->ID );

		switch ( $event_status ) {
			case 'mdjm-contract':
			case 'mdjm-awaitingdeposit':
			case 'mdjm-approved':
			case 'mdjm-completed':
				$transparency = 'opaque';
				break;

			default:
				$transparency = 'transparent';
				break;
		}

		return $transparency;
	} // set_event_transparency

} // MDJM_Google class