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

// CALENDAR EDITOR

/**
 * Get booking system data
 * @since 1.7.4
 * @version 1.15.6
 * @param array $atts (see bookacti_format_booking_system_attributes())
 * @param int $template_id
 * @return array
 */
function bookacti_get_editor_booking_system_data( $atts, $template_id ) {
	$booking_system_data = $atts;
	$groups = bookacti_get_groups_of_events( array( 'templates' => array( $template_id ), 'nb_events' => array(), 'past_events' => 1, 'data_only' => 1 ) );
	$template_data = bookacti_get_template_data( $template_id );
	
	$booking_system_data[ 'calendars' ]             = array( $template_id );
	$booking_system_data[ 'events' ]                = array();
	$booking_system_data[ 'events_data' ]           = array();
	$booking_system_data[ 'events_interval' ]       = array();
	$booking_system_data[ 'bookings' ]              = array(); // Retrieved when navigating on the calendar
	$booking_system_data[ 'activities_data' ]       = bookacti_get_activities_by_template( $template_id, false, true );
	$booking_system_data[ 'groups_data' ]           = $groups[ 'data' ];
	$booking_system_data[ 'groups_events' ]         = array(); // Retrieved when a group is updated
	$booking_system_data[ 'group_categories_data' ] = bookacti_get_group_categories( array( 'templates' => array( $template_id ) ) );
	$booking_system_data[ 'start' ]                 = '';
	$booking_system_data[ 'end' ]                   = '';
	$booking_system_data[ 'display_data' ]          = $template_data[ 'settings' ];
	$booking_system_data[ 'template_data' ]         = $template_data;
	
	return apply_filters( 'bookacti_editor_booking_system_data', $booking_system_data, $atts );
}




// PERMISSIONS

/**
 * Check if user is allowed to manage template
 * @version 1.16.2
 * @param int|array $template_ids
 * @param int|false $user_id False for current user
 * @return boolean
 */
function bookacti_user_can_manage_template( $template_ids, $user_id = false ) {
	$user_can_manage_template = false;
	if( ! $user_id ) { $user_id = get_current_user_id(); }
	$bypass_template_managers_check = apply_filters( 'bookacti_bypass_template_managers_check', false, $user_id );
	if( is_super_admin( $user_id ) || $bypass_template_managers_check ) { $user_can_manage_template = true; }
	else {
		$admins = bookacti_get_template_managers( $template_ids );
		if( $admins ) {
			if( in_array( intval( $user_id ), $admins, true ) ) { $user_can_manage_template = true; }
		}
	}

	return apply_filters( 'bookacti_user_can_manage_template', $user_can_manage_template, $template_ids, $user_id );
}


/**
 * Check if user is allowed to manage activity
 * @version 1.16.2
 * @param int|array $activity_ids
 * @param int|false $user_id False for current user
 * @param array|false $admins False to retrieve the activity managers
 * @return boolean
 */
function bookacti_user_can_manage_activity( $activity_ids, $user_id = false, $admins = false ) {
	$user_can_manage_activity = false;
	if( ! $user_id ) { $user_id = get_current_user_id(); }
	$bypass_activity_managers_check = apply_filters( 'bookacti_bypass_activity_managers_check', false, $user_id );
	if( is_super_admin( $user_id ) || $bypass_activity_managers_check ) { $user_can_manage_activity = true; }
	else {
		$admins = $admins === false ? bookacti_get_activity_managers( $activity_ids ) : $admins;
		if( $admins ) {
			if( in_array( intval( $user_id ), $admins, true ) ) { $user_can_manage_activity = true; }
		}
	}

	return apply_filters( 'bookacti_user_can_manage_activity', $user_can_manage_activity, $activity_ids, $user_id );
}


/**
 * Get template managers
 * @version 1.16.2
 * @param int|array $template_ids
 * @return array
 */
function bookacti_get_template_managers( $template_ids ) {
	$template_ids = bookacti_ids_to_array( $template_ids );
	$managers     = $template_ids ? bookacti_get_managers( 'template', $template_ids ) : array();
	
	$i = 0;
	$intersect_managers = array();
	foreach( $template_ids as $template_id ) {
		$manager_ids = ! empty( $managers[ $template_id ] ) ? bookacti_ids_to_array( $managers[ $template_id ] ) : array();
		if( $i === 0 ) {
			$intersect_managers = $manager_ids;
		} else {
			$intersect_managers = array_intersect( $intersect_managers, $manager_ids );
		}
		++$i;
	}
	
	return array_values( bookacti_ids_to_array( $intersect_managers ) );
}


/**
 * Get activity managers
 * @version 1.16.2
 * @param int|array $activity_ids
 * @return array
 */
function bookacti_get_activity_managers( $activity_ids ) {	
	$activity_ids = bookacti_ids_to_array( $activity_ids );
	$managers     = $activity_ids ? bookacti_get_managers( 'activity', $activity_ids ) : array();
	
	$i = 0;
	$intersect_managers = array();
	foreach( $activity_ids as $activity_id ) {
		$manager_ids = ! empty( $managers[ $activity_id ] ) ? bookacti_ids_to_array( $managers[ $activity_id ] ) : array();
		if( $i === 0 ) {
			$intersect_managers = $manager_ids;
		} else {
			$intersect_managers = array_intersect( $intersect_managers, $manager_ids );
		}
		++$i;
	}
	
	return array_values( bookacti_ids_to_array( $intersect_managers ) );
}




// TEMPLATE X ACTIVITIES

/**
 * Retrieve template activities list
 * @version 1.16.4
 * @param array $activities see bookacti_get_activities_by_template
 * @param int $template_id
 * @return string 
 */
function bookacti_get_template_activities_list( $activities, $template_id = 0 ) {
	if( ! $activities ) { return ''; }
	
	// Sort the activities by custom order
	$ordered_activities = $activities;
	$activities_order = $template_id ? bookacti_get_metadata( 'template', $template_id, 'activities_order', true ) : array();
	if( $activities_order ) {
		$sorted_activities = array();
		foreach( $activities_order as $activity_id ) {
			if( isset( $activities[ $activity_id ] ) ) { $sorted_activities[] = $activities[ $activity_id ]; }
		}
		$ordered_activities = array_merge( $sorted_activities, array_diff_key( $activities, array_flip( $activities_order ) ) );
	}
	
	ob_start();
	foreach( $ordered_activities as $activity ) {
		$title    = $activity[ 'title' ];
		$duration = $activity[ 'duration' ] ? $activity[ 'duration' ] : '000.01:00:00';
		$color    = isset( $activity[ 'settings' ][ 'text_color' ] ) ? sanitize_hex_color( $activity[ 'settings' ][ 'text_color' ] ) : '#fff';
		?>
		<div class='bookacti-activity' data-activity-id='<?php echo esc_attr( $activity[ 'id' ] ); ?>'>
			<div class='bookacti-activity-visibility dashicons dashicons-visibility' data-activity-visible='1'></div>
			<div class='bookacti-activity-container'>
				<div
					class='bookacti-activity-draggable'
					data-event='{"title": "<?php echo htmlentities( esc_attr( $title ), ENT_QUOTES ); ?>", "duration": "<?php echo esc_attr( $duration ); ?>", "textColor": "<?php echo esc_attr( $color ) ?>", "color": "<?php echo esc_attr( $activity[ 'color' ] ); ?>", "extendedProps": { "activity_id": <?php echo intval( $activity[ 'id' ] ); ?> } }' 
					data-activity-id='<?php echo esc_attr( $activity[ 'id' ] ); ?>'
					data-duration='<?php echo esc_attr( $duration ); ?>'
					title='<?php esc_attr_e( $title ); ?>'
					style='border-color:<?php echo esc_attr( $activity[ 'color' ] ); ?>; background-color:<?php echo esc_attr( $activity[ 'color' ] );?>;  color: <?php echo esc_attr( $color ); ?>'
				>
					<?php echo $title; ?>
				</div>
			</div>
		<?php
		if( current_user_can( 'bookacti_edit_activities' ) && bookacti_user_can_manage_activity( $activity[ 'id' ] ) ) {
		?>
			<div class='bookacti-activity-settings dashicons dashicons-admin-generic'></div>
		<?php
		}
		?>
		</div>
		<?php
	}
	return ob_get_clean();
}




// TEMPLATES

/**
 * Get template data
 * @since 1.15.0
 * @param int $template_id
 * @param boolean $raw
 * @param int $user_id User ID to check the permissions for. -1 for current user (default). 0 to ignore permission. 
 * @return array
 */
function bookacti_get_template_data( $template_id, $raw = false, $user_id = -1 ) {
	$template  = array();
	$templates = bookacti_fetch_templates( $template_id, $user_id );
	
	if( isset( $templates[ $template_id ] ) ) { 
		$template_meta     = bookacti_get_metadata( 'template', $template_id );
		$template_managers = bookacti_get_managers( 'template', $template_id );
		$template_meta     = is_array( $template_meta ) ? $template_meta : array();
		$template          = ! $raw ? bookacti_format_template_data( array_merge( $templates[ $template_id ], $template_meta ) ) : array_merge( $templates[ $template_id ], $template_meta );
		$template[ 'admin' ] = ! empty( $template_managers ) ? ( ! $raw ? bookacti_ids_to_array( $template_managers ) : $template_managers ) : array();
	}

	return $template;
}


/**
 * Get templates data
 * @since 1.7.3
 * @version 1.15.0
 * @param array $template_ids
 * @param boolean $raw
 * @param int $user_id User ID to check the permissions for. -1 for current user (default). 0 to ignore permission. 
 * @return array
 */
function bookacti_get_templates_data( $template_ids = array(), $raw = false, $user_id = -1 ) {
	$templates = bookacti_fetch_templates( $template_ids, $user_id );

	$retrieved_template_ids = array_keys( $templates );

	$templates_meta     = bookacti_get_metadata( 'template', $retrieved_template_ids );
	$templates_managers = bookacti_get_managers( 'template', $retrieved_template_ids );

	foreach( $templates as $template_id => $template ) {
		$template_meta = isset( $templates_meta[ $template_id ] ) ? $templates_meta[ $template_id ] : array();
		$templates[ $template_id ] = ! $raw ? bookacti_format_template_data( array_merge( $template, $template_meta ) ) : array_merge( $template, $template_meta );
		$templates[ $template_id ][ 'admin' ] = ! empty( $templates_managers[ $template_id ] ) ? ( ! $raw ? bookacti_ids_to_array( $templates_managers[ $template_id ] ) : $templates_managers[ $template_id ] ) : array();
	}

	return $templates;
}


/**
 * Get a unique template setting made from a combination of multiple template settings
 * @since 1.2.2 (was bookacti_get_mixed_template_settings)
 * @version 1.15.0
 * @param array $template_ids Array of template ids
 * @return array
 */
function bookacti_get_mixed_template_data( $template_ids ) {
	$templates_data = bookacti_get_templates_data( $template_ids );
	$mixed_data = array();
	$mixed_settings	= array();

	foreach( $templates_data as $template_data ){
		$settings = $template_data[ 'settings' ];
		if( isset( $settings[ 'slotMinTime' ] ) ) {
			// Keep the lower value
			if(  ! isset( $mixed_settings[ 'slotMinTime' ] ) 
				|| isset( $mixed_settings[ 'slotMinTime' ] ) && intval( str_replace( ':', '', $settings[ 'slotMinTime' ] ) ) < intval( str_replace( ':', '', $mixed_settings[ 'slotMinTime' ] ) ) ) {

				$mixed_settings[ 'slotMinTime' ] = $settings[ 'slotMinTime' ];
			} 
		}
		if( isset( $settings[ 'slotMaxTime' ] ) ) {
			// Keep the higher value
			if(  ! isset( $mixed_settings[ 'slotMaxTime' ] ) 
				|| isset( $mixed_settings[ 'slotMaxTime' ] ) && intval( str_replace( ':', '', $settings[ 'slotMaxTime' ] ) ) > intval( str_replace( ':', '', $mixed_settings[ 'slotMaxTime' ] ) ) ) {

				$mixed_settings[ 'slotMaxTime' ] = $settings[ 'slotMaxTime' ];
			} 
		}
		if( isset( $settings[ 'snapDuration' ] ) ) {
			// Keep the lower value
			if(  ! isset( $mixed_settings[ 'snapDuration' ] ) 
				|| isset( $mixed_settings[ 'snapDuration' ] ) && strtotime( $settings[ 'snapDuration' ] ) < strtotime( $mixed_settings[ 'snapDuration' ] ) ) {

				$mixed_settings[ 'snapDuration' ] = $settings[ 'snapDuration' ];
			}
		}
		if( isset( $settings[ 'days_off' ] ) ) {
			// Merge the days off
			if( ! isset( $mixed_settings[ 'days_off' ] ) ) { $mixed_settings[ 'days_off' ] = array(); }
			$mixed_settings[ 'days_off' ] = array_merge( $mixed_settings[ 'days_off' ], $settings[ 'days_off' ] );
		}
	}
	
	// Sanitize merged days off
	if( ! empty( $mixed_settings[ 'days_off' ] ) ) { $mixed_settings[ 'days_off' ] = bookacti_sanitize_days_off( $mixed_settings[ 'days_off' ] ); }

	// Add mixed settings
	$mixed_data[ 'settings' ] = $mixed_settings;

	return apply_filters( 'bookacti_mixed_template_settings', $mixed_data, $templates_data, $template_ids );
}




// TEMPLATES X ACTIVITIES ASSOCIATION

/**
 * Bind activities to a template
 * @version 1.12.0
 * @param array $new_activities
 * @param int $template_id
 * @return int|false
 */
function bookacti_bind_activities_to_template( $new_activities, $template_id ) {
	$new_activities = bookacti_ids_to_array( $new_activities );

	// Keep only new activities
	$old_activities = bookacti_get_activity_ids_by_template( $template_id, false );
	$new_activities = array_diff( $new_activities, $old_activities );

	// Insert new activities
	$inserted = $new_activities ? bookacti_insert_templates_x_activities( array( $template_id ), $new_activities ) : 0;

	return $inserted;
}




// EVENTS

/**
 * Unbind selected occurrence of an event
 * @since 1.12.0 (was bookacti_unbind_selected_occurrence)
 * @version 1.13.0
 * @param object $event
 * @param string $event_start Y-m-d H:i:s
 * @param string $event_end Y-m-d H:i:s
 * @return int
 */
function bookacti_unbind_selected_event_occurrence( $event, $event_start, $event_end ) {
	$event_id = $event->event_id;
	
	// Duplicate the event occurrence
	$duplicated_event_data = bookacti_sanitize_event_data( array_merge( (array) $event, array( 'start' => $event_start, 'end' => $event_end, 'repeat_freq' => 'none' ) ) );
	$duplicated_event_id = bookacti_insert_event( $duplicated_event_data );
	if( ! $duplicated_event_id ) { return 0; }
	
	// Duplicate event metadata
	bookacti_duplicate_metadata( 'event', $event_id, $duplicated_event_id );

	// If the event was part of a group, change its id to the new one
	bookacti_update_grouped_event_id( $event_id, $duplicated_event_id, $event_start, $event_end );

	// If the event was booked, move its bookings to the new single event
	bookacti_update_bookings_event_id( $event_id, $duplicated_event_id, $event_start, $event_end );
	
	// Get original event exceptions and add the unbound event date to them
	$event_data = (array) $event;
	$event_data[ 'repeat_exceptions' ] = ! empty( $event->repeat_exceptions ) && is_array( $event->repeat_exceptions ) ? $event->repeat_exceptions : array();
	$event_data[ 'repeat_exceptions' ][] = array( 'from' => substr( $event_start, 0, 10 ), 'to' => substr( $event_start, 0, 10 ) );
	
	// Sanitize and update the original event dates
	$original_event_data = bookacti_sanitize_event_data( $event_data );
	bookacti_update_event( $original_event_data );
	
	return $duplicated_event_id;
}


/**
 * Unbind booked occurrences of an event
 * @since 1.12.0 (was bookacti_unbind_booked_occurrences)
 * @version 1.13.0
 * @param object $event
 * @return int
 */
function bookacti_unbind_booked_event_occurrences( $event ) {
	$event_id = $event->event_id;
	
	// Get the booked occurrences (the original event will keep the booked occurrences only)
	$booked_events = bookacti_fetch_booked_events( array( 'events' => array( $event_id ), 'active' => 1, 'past_events' => 1 ) );
	if( empty( $booked_events[ 'events' ] ) ) { return 0; }
	
	// Duplicate the original event
	$duplicated_event_id = bookacti_duplicate_event( $event_id );
	if( ! $duplicated_event_id ) { return 0; }
	
	// Duplicate event metadata
	bookacti_duplicate_metadata( 'event', $event_id, $duplicated_event_id );
	
	// Replace all occurrences' event id in groups (we will turn it back to the original id for booked events)
	bookacti_update_grouped_event_id( $event_id, $duplicated_event_id );
	
	$max_repeat_from = $event->repeat_from;
	$min_repeat_to = $event->repeat_to;
	$booked_dates = array();
	$not_booked_dates = array();
	
	// For each booked event...
	foreach( $booked_events[ 'events' ] as $event_to_unbind ) {
		// Give back its original event id to booked occurrences
		bookacti_update_grouped_event_id( $duplicated_event_id, $event_id, $event_to_unbind[ 'start' ], $event_to_unbind[ 'end' ] );

		// Get the smallest repeat period possible
		if( ! $booked_dates ) { $max_repeat_from = substr( $event_to_unbind[ 'start' ], 0, 10 ); }
		$min_repeat_to = substr( $event_to_unbind[ 'start' ], 0, 10 );

		// Store the booked dates for exceptions
		$booked_date = substr( $event_to_unbind[ 'start' ], 0, 10 );
		if( ! in_array( $booked_date, $booked_dates, true ) ) { $booked_dates[] = $booked_date; }
	}
	
	// Add an exception on days that are not booked on the original event
	if( $max_repeat_from !== $min_repeat_to ) {
		$dummy_event = clone $event;
		$dummy_event->repeat_from = $max_repeat_from;
		$dummy_event->repeat_to = $min_repeat_to;
		$occurrences = bookacti_get_occurrences_of_repeated_event( $dummy_event, array( 'past_events' => 1 ) );
		foreach( $occurrences as $occurrence ) {
			$occurrence_date = substr( $occurrence[ 'start' ], 0, 10 );
			if( ! in_array( $occurrence_date, $booked_dates, true ) ) { $not_booked_dates[] = $occurrence_date; }
		}
	}
	
	// Sanitize and update the duplicated event dates and exceptions
	$duplicated_event_exceptions_merged = ! empty( $event->repeat_exceptions ) && is_array( $event->repeat_exceptions ) ? $event->repeat_exceptions : array();
	foreach( $booked_dates as $booked_date ) { $duplicated_event_exceptions_merged[] = array( 'from' => $booked_date, 'to' => $booked_date ); }
	$duplicated_event_data = bookacti_sanitize_event_data( array_merge( (array) $event, array( 'id' => $duplicated_event_id, 'repeat_exceptions' => $duplicated_event_exceptions_merged ) ) );
	bookacti_update_event( $duplicated_event_data );
	
	// Sanitize and update the original event dates and exceptions
	$original_event_exceptions_merged = ! empty( $event->repeat_exceptions ) && is_array( $event->repeat_exceptions ) ? $event->repeat_exceptions : array();
	foreach( $not_booked_dates as $not_booked_date ) { $original_event_exceptions_merged[] = array( 'from' => $not_booked_date, 'to' => $not_booked_date ); }
	$original_event_data = bookacti_sanitize_event_data( array_merge( (array) $event, array( 'repeat_from' => $max_repeat_from, 'repeat_to' => $min_repeat_to, 'repeat_exceptions' => $original_event_exceptions_merged ) ) );
	bookacti_update_event( $original_event_data );
	
	return $duplicated_event_id;
}


/**
 * Unbind future occurrences of an event
 * @since 1.12.0 (was bookacti_unbind_future_occurrences)
 * @version 1.13.0
 * @param object $event
 * @param string $unbind_from Y-m-d
 * @return int
 */
function bookacti_unbind_future_event_occurrences( $event, $unbind_from ) {
	// Duplicate the original event and make its repetition begins on the desired date
	$duplicated_event_data = bookacti_sanitize_event_data( array_merge( (array) $event, array( 'repeat_from' => $unbind_from ) ) );
	$duplicated_event_id = bookacti_insert_event( $duplicated_event_data );
	if( ! $duplicated_event_id ) { return 0; }
	
	// Duplicate event metadata
	bookacti_duplicate_metadata( 'event', $event->event_id, $duplicated_event_id );
	
	// Replace the event_id of future grouped occurrences
	bookacti_update_grouped_event_id( $event->event_id, $duplicated_event_id, '', '', $unbind_from . ' 00:00:00' );
	
	// Change the event_id of bookings made on future events
	bookacti_update_bookings_event_id( $event->event_id, $duplicated_event_id, '', '', $unbind_from . ' 00:00:00' );
	
	// Stop the original event repetition where the new event repetition begins
	$repeat_to_dt = DateTime::createFromFormat( 'Y-m-d', $unbind_from );
	$repeat_to_dt->sub( new DateInterval( 'P1D' ) );
	
	// Update the original event dates
	$original_event_data = bookacti_sanitize_event_data( array_merge( (array) $event, array( 'repeat_to' => $repeat_to_dt->format( 'Y-m-d' ) ) ) );
	bookacti_update_event( $original_event_data );
	
	return $duplicated_event_id;
}


/**
 * Unbind each occurrence of an event
 * @since 1.12.0 (was bookacti_unbind_all_occurrences)
 * @version 1.13.0
 * @param object $event
 * @return array
 */
function bookacti_unbind_all_event_occurrences( $event ) {
	// Get the event occurrences (skip exceptions)
	$occurrences = bookacti_get_occurrences_of_repeated_event( $event, array( 'past_events' => 1 ) );
	if( ! $occurrences ) { return array(); }
	
	$occurrences_ids = array();
	foreach( $occurrences as $occurrence ) {
		// Get the occurrence data
		$occurrence_data = bookacti_sanitize_event_data( array( 
			'template_id'   => $event->template_id,
			'activity_id'   => $event->activity_id,
			'title'         => $event->title,
			'start'         => $occurrence[ 'start' ],
			'end'           => $occurrence[ 'end' ],
			'availability'	=> $event->availability,
			'repeat_freq'	=> 'none'
		));
		
		// Create one event per occurrence
		$occurrence_id = bookacti_insert_event( $occurrence_data );
		if( ! $occurrence_id ) { continue; }
		
		// Duplicate event metadata
		bookacti_duplicate_metadata( 'event', $event->event_id, $occurrence_id );
		
		// Replace the event_id of grouped occurrences
		bookacti_update_grouped_event_id( $event->event_id, $occurrence_id, $occurrence[ 'start' ], $occurrence[ 'end' ] );
		
		// Change the event_id of bookings
		bookacti_update_bookings_event_id( $event->event_id, $occurrence_id, $occurrence[ 'start' ], $occurrence[ 'end' ] );
		
		$occurrences_ids[] = $occurrence_id;
	}
	
	// Deactivate the original event
	if( $occurrences_ids ) { bookacti_deactivate_event( $event->event_id ); }
	
	return $occurrences_ids;
}




// GROUP OF EVENTS

/**
 * Unbind selected occurrence of a group of events
 * @since 1.12.0
 * @version 1.13.0
 * @param array $group
 * @param string $group_date
 * @return int
 */
function bookacti_unbind_selected_group_of_events_occurrence( $group, $group_date ) {
	$group_id = $group[ 'data' ][ 'id' ];
	
	// Make sure the group occurrence exists
	if( ! $group_date || empty( $group[ 'groups' ][ $group_date ] ) ) { return 0; }
	
	// Duplicate the group occurrence
	$duplicated_group_data = bookacti_sanitize_group_of_events_data( array_merge( $group[ 'data' ], array( 'id' => 0, 'repeat_freq' => 'none', 'events' => $group[ 'groups' ][ $group_date ], 'title' => $group[ 'data' ][ 'multilingual_title' ] ) ) );
	$duplicated_group_id = bookacti_insert_group_of_events( $duplicated_group_data );
	if( ! $duplicated_group_id ) { return 0; }
	
	// Insert the events in the group
	$inserted = bookacti_insert_events_into_group( $duplicated_group_id, $group[ 'groups' ][ $group_date ] );
	if( ! $inserted ) { return 0; }
	
	// Duplicate group metadata
	bookacti_duplicate_metadata( 'group_of_events', $group_id, $duplicated_group_id );

	// If the group was booked, move its bookings to the new single group
	bookacti_update_booking_groups_event_group_id( $group_id, $duplicated_group_id, $group_date );
	
	// Get original group exceptions and add the unbound group date to them
	$group_data = $group[ 'data' ];
	$group_data[ 'repeat_exceptions' ] = ! empty( $group_data[ 'repeat_exceptions' ] ) && is_array( $group_data[ 'repeat_exceptions' ] ) ? $group_data[ 'repeat_exceptions' ] : array();
	$group_data[ 'repeat_exceptions' ][] = array( 'from' => $group_date, 'to' => $group_date );
	
	// Sanitize and update the original group of events and its exceptions
	$original_group_data = bookacti_sanitize_group_of_events_data( array_merge( $group_data, array( 'title' => $group[ 'data' ][ 'multilingual_title' ] ) ) );
	bookacti_update_group_of_events( $original_group_data );
	
	return $duplicated_group_id;
}


/**
 * Unbind booked occurrences of a group of events
 * @since 1.12.0
 * @version 1.13.0
 * @param array $group
 * @return int
 */
function bookacti_unbind_booked_group_of_events_occurrences( $group ) {
	$group_id = $group[ 'data' ][ 'id' ];
	
	// Get the booked occurrences (the original group of events will keep the booked occurrences only)
	$booking_groups_filters = bookacti_format_booking_filters( array( 'event_group_id' => $group_id, 'active' => 1 ) );
	$booking_groups = bookacti_get_booking_groups( $booking_groups_filters );
	$booked_dates = array();
	if( $booking_groups ) {
		foreach( $booking_groups as $booking_group ) {
			if( ! empty( $booking_group->group_date ) && ! in_array( $booking_group->group_date, $booked_dates, true ) ) {
				$booked_dates[] = $booking_group->group_date;
			}
		}
	}
	if( ! $booked_dates ) { return 0; }
	
	$occurrences_dates	= array_keys( $group[ 'groups' ] );
	$booked_dates		= array_intersect( $occurrences_dates, $booked_dates );
	$not_booked_dates	= array_diff( $occurrences_dates, $booked_dates );
	
	// Duplicate group of events
	$duplicated_group_exceptions_merged = ! empty( $group[ 'data' ][ 'repeat_exceptions' ] ) && is_array( $group[ 'data' ][ 'repeat_exceptions' ] ) ? $group[ 'data' ][ 'repeat_exceptions' ] : array();
	foreach( $booked_dates as $booked_date ) { $duplicated_group_exceptions_merged[] = array( 'from' => $booked_date, 'to' => $booked_date ); }
	$duplicated_group_data = bookacti_sanitize_group_of_events_data( array_merge( $group[ 'data' ], array( 'id' => 0, 'repeat_exceptions' => $duplicated_group_exceptions_merged, 'title' => $group[ 'data' ][ 'multilingual_title' ] ) ) );
	$duplicated_group_id = bookacti_insert_group_of_events( $duplicated_group_data );
	if( ! $duplicated_group_id ) { return 0; }
	
	// Insert duplicated grouped events
	$grouped_events = $duplicated_group_data[ 'repeat_freq' ] !== 'none' && $duplicated_group_data[ 'repeat_from' ] && ! empty( $group[ 'groups' ][ $duplicated_group_data[ 'repeat_from' ] ] ) ? $group[ 'groups' ][ $duplicated_group_data[ 'repeat_from' ] ] : $group[ 'data' ][ 'events' ];
	$inserted = bookacti_insert_events_into_group( $duplicated_group_id, $grouped_events );
	if( ! $inserted ) { return 0; }
	
	// Duplicate group metadata
	bookacti_duplicate_metadata( 'group_of_events', $group_id, $duplicated_group_id );
	
	// Sanitize and update the original group of events and its exceptions
	$original_group_exceptions_merged = ! empty( $group[ 'data' ][ 'repeat_exceptions' ] ) && is_array( $group[ 'data' ][ 'repeat_exceptions' ] ) ? $group[ 'data' ][ 'repeat_exceptions' ] : array();
	foreach( $not_booked_dates as $not_booked_date ) { $original_group_exceptions_merged[] = array( 'from' => $not_booked_date, 'to' => $not_booked_date ); }
	$original_group_data = bookacti_sanitize_group_of_events_data( array_merge( $group[ 'data' ], array( 'repeat_exceptions' => $original_group_exceptions_merged, 'title' => $group[ 'data' ][ 'multilingual_title' ] ) ) );
	bookacti_update_group_of_events( $original_group_data );
	
	// Update original grouped events
	if( $original_group_data[ 'repeat_freq' ] !== 'none' && $original_group_data[ 'repeat_from' ] && ! empty( $group[ 'groups' ][ $original_group_data[ 'repeat_from' ] ] ) ) {
		bookacti_delete_events_from_group( $group_id );
		bookacti_insert_events_into_group( $group_id, $group[ 'groups' ][ $original_group_data[ 'repeat_from' ] ] );
	}
	
	return $duplicated_group_id;
}


/**
 * Unbind future occurrences of a group of events
 * @since 1.12.0
 * @version 1.13.0
 * @param array $group
 * @param string $unbind_from Y-m-d
 * @return int
 */
function bookacti_unbind_future_group_of_events_occurrences( $group, $unbind_from ) {
	$group_id = $group[ 'data' ][ 'id' ];
	
	// Make sure the group occurrence exists
	if( ! $unbind_from || empty( $group[ 'groups' ][ $unbind_from ] ) ) { return 0; }
	
	// Duplicate the original group of events and make its repetition begins on the desired date
	$duplicated_group_data = bookacti_sanitize_group_of_events_data( array_merge( $group[ 'data' ], array( 'id' => 0, 'repeat_from' => $unbind_from, 'title' => $group[ 'data' ][ 'multilingual_title' ] ) ) );
	$duplicated_group_id = bookacti_insert_group_of_events( $duplicated_group_data );
	if( ! $duplicated_group_id ) { return 0; }
	
	// Insert the events in the group
	$inserted = bookacti_insert_events_into_group( $duplicated_group_id, $group[ 'groups' ][ $unbind_from ] );
	if( ! $inserted ) { return 0; }
	
	// Duplicate group of events metadata
	bookacti_duplicate_metadata( 'group_of_events', $group_id, $duplicated_group_id );
	
	// Change the event_group_id of booking groups made on future group of events
	bookacti_update_booking_groups_event_group_id( $group_id, $duplicated_group_id, '', $unbind_from );
	
	// Stop the original group of events repetition where the new group of events repetition begins
	$repeat_to_dt = DateTime::createFromFormat( 'Y-m-d', $unbind_from );
	$repeat_to_dt->sub( new DateInterval( 'P1D' ) );
	
	// Update the original group of events dates
	$original_group_data = bookacti_sanitize_group_of_events_data( array_merge( $group[ 'data' ], array( 'repeat_to' => $repeat_to_dt->format( 'Y-m-d' ), 'title' => $group[ 'data' ][ 'multilingual_title' ] ) ) );
	bookacti_update_group_of_events( $original_group_data );
	
	return $duplicated_group_id;
}


/**
 * Unbind each occurrence of a group of events
 * @since 1.12.0
 * @param array $group
 * @return array
 */
function bookacti_unbind_all_group_of_events_occurrences( $group ) {
	$group_id = $group[ 'data' ][ 'id' ];
	
	$occurrences_ids = array();
	foreach( $group[ 'groups' ] as $group_date => $group_events ) {
		// Sanitize the occurrence data
		$occurrence_data = bookacti_sanitize_group_of_events_data( array( 
			'template_id'   => $group[ 'data' ][ 'template_id' ],
			'category_id'   => $group[ 'data' ][ 'category_id' ],
			'title'         => $group[ 'data' ][ 'multilingual_title' ],
			'repeat_freq'	=> 'none'
		));
		
		// Create one group of events per occurrence
		$occurrence_id = bookacti_insert_group_of_events( $occurrence_data );
		if( ! $occurrence_id ) { continue; }
		
		// Insert the events in the group
		$inserted = bookacti_insert_events_into_group( $occurrence_id, $group_events );
		if( ! $inserted ) { continue; }
		
		// Duplicate group of events metadata
		bookacti_duplicate_metadata( 'group_of_events', $group_id, $occurrence_id );
		
		// Change the event_group_id of booking groups
		bookacti_update_booking_groups_event_group_id( $group_id, $occurrence_id, $group_date );
		
		$occurrences_ids[] = $occurrence_id;
	}
	
	// Deactivate the original group of events
	if( $occurrences_ids ) { bookacti_deactivate_group_of_events( $group_id ); }
	
	return $occurrences_ids;
}


/**
 * Check if a group category exists
 * @since 1.1.0
 * @version 1.12.0
 * @param int $category_id
 * @param int $template_id
 * @return boolean
 */
function bookacti_group_category_exists( $category_id, $template_id = 0 ) {
	if( ! $category_id || ! is_numeric( $category_id ) ) { return false; }
	$template_ids = $template_id ? array( $template_id ) : array();
	$available_category_ids = array_map( 'intval', bookacti_get_group_category_ids_by_template( $template_ids ) );
	return in_array( intval( $category_id ), $available_category_ids, true );
}


/**
 * Update events of a group
 * @since 1.1.0
 * @version 1.12.0
 * @param int $group_id
 * @param array $new_events [[id: int, start: "Y-m-d H:i:s", end: "Y-m-d H:i:s"], ...]
 * @return int|boolean
 */
function bookacti_update_events_of_group( $group_id, $new_events ) {
	// Get events currently in the group
	$current_events = bookacti_get_group_events( $group_id );

	// Determine what events are to be added or removed
	$to_insert = $new_events;
	$to_delete = $current_events;
	foreach( $new_events as $i => $new_event ) {
		foreach( $current_events as $j => $current_event ) {
			// If the event already exists, remove it from both arrays
			if( intval( $current_event[ 'id' ] ) === intval( $new_event[ 'id' ] )
			&&  $current_event[ 'start' ] === $new_event[ 'start' ]
			&&  $current_event[ 'end' ] === $new_event[ 'end' ] ) {
				unset( $to_insert[ $i ] );
				unset( $to_delete[ $j ] );
				break;
			}
		}
	}

	// Delete old events
	$deleted = $to_delete ? bookacti_delete_events_from_group( $group_id, $to_delete ) : 0;

	// Insert new events
	$inserted = $to_insert ? bookacti_insert_events_into_group( $group_id, $to_insert ) : 0;

	return $deleted === false && $inserted === false ? false : intval( $deleted ) + intval( $inserted );
}


/**
 * Get the groups of events list for the desired template
 * @since 1.1.0
 * @version 1.12.0
 * @param array $categories see bookacti_get_group_categories
 * @param array $groups see bookacti_get_groups_of_events
 * @param int $template_id
 * @return string
 */
function bookacti_get_template_groups_of_events_list( $categories, $groups, $template_id = 0 ) {
	if( ! $categories || ! $groups ) { return ''; }

	$current_user_can_edit_template	= current_user_can( 'bookacti_edit_templates' );
	
	// Sort the group categories by custom order
	$ordered_categories = $categories;
	$categories_order = bookacti_get_metadata( 'template', $template_id, 'group_categories_order', true );
	if( $categories_order ) {
		$sorted_categories = array();
		foreach( $categories_order as $category_id ) {
			if( isset( $categories[ $category_id ] ) ) { $sorted_categories[] = $categories[ $category_id ]; }
		}
		$ordered_categories = array_merge( $sorted_categories, array_diff_key( $categories, array_flip( $categories_order ) ) );
	}
	
	ob_start();
	
	foreach( $ordered_categories as $category ) {
		$category_short_title = strlen( $category[ 'title' ] ) > 16 ? substr( $category[ 'title' ], 0, 16 ) . '&#8230;' : $category[ 'title' ];
	?>
		<div class='bookacti-group-category' data-group-category-id='<?php echo $category[ 'id' ]; ?>' data-show-groups='0' data-visible='1'>
			<div class='bookacti-group-category-title' title='<?php echo $category[ 'title' ]; ?>'>
				<span><?php echo $category_short_title; ?></span>
			</div>
	<?php
		if( $current_user_can_edit_template ) {
			?><div class='bookacti-update-group-category dashicons dashicons-admin-generic'></div><?php
		}
	?>
			<div class='bookacti-groups-of-events-editor-list bookacti-custom-scrollbar'>
			<?php
				// Sort the groups of events by custom order
				$ordered_groups = $groups[ 'data' ];
				$groups_order = ! empty( $categories[ $category[ 'id' ] ][ 'settings' ][ 'groups_of_events_order' ] ) ? $categories[ $category[ 'id' ] ][ 'settings' ][ 'groups_of_events_order' ] : array();
				if( $groups_order ) {
					$sorted_groups = array();
					foreach( $groups_order as $group_id ) {
						if( isset( $ordered_groups[ $group_id ] ) ) { $sorted_groups[] = $groups[ 'data' ][ $group_id ]; }
					}
					$ordered_groups = array_merge( $sorted_groups, array_diff_key( $groups[ 'data' ], array_flip( $groups_order ) ) );
				}
				
				foreach( $ordered_groups as $group ) {
					if( intval( $group[ 'category_id' ] ) !== intval( $category[ 'id' ] ) ) { continue; }
						$group_title = strip_tags( $group[ 'title' ] );
					?>
						<div class='bookacti-group-of-events' data-group-id='<?php echo $group[ 'id' ]; ?>'>
							<div class='bookacti-group-of-events-title' title='<?php echo $group_title; ?>'><?php echo $group_title; ?></div>
					<?php
						if( $current_user_can_edit_template ) {
							?><div class='bookacti-update-group-of-events dashicons dashicons-admin-generic'></div><?php
						}
					?>
						</div>
					<?php
				}
			?>
			</div>
		</div>
	<?php
	}

	return ob_get_clean();
}




// MISC

/**
 * Display a promo area of Prices and Credits add-on
 * @version 1.16.46
 * @param string $type
 */
function bookacti_promo_for_bapap_addon( $type = 'event' ) {
	$is_plugin_active = bookacti_is_plugin_active( 'ba-prices-and-credits/ba-prices-and-credits.php' );
	$license_status   = get_option( 'bapap_license_status' );

	// If the plugin is activated but the license is not active yet
	if( $is_plugin_active && $license_status !== 'valid' ) {
		?>
		<div class='bookacti-addon-promo'>
			<p>
			<?php 
				/* translators: %s = add-on name */
				echo sprintf( esc_html__( 'Thank you for purchasing %s add-on!', 'booking-activities' ), '<strong>Prices&nbsp;and&nbsp;Credits</strong>' ); 
			?>
			</p><p>
				<?php esc_html_e( 'It seems you didn\'t activate your license yet. Please follow these instructions to activate your license:', 'booking-activities' ); ?>
			</p><p>
				<strong>
					<a href='https://booking-activities.fr/en/docs/user-documentation/get-started-with-prices-and-credits-add-on/prerequisite-installation-license-activation-of-prices-and-credits-add-on/?utm_source=plugin&utm_medium=plugin&utm_content=encart-promo-<?php echo esc_attr( $type ); ?>' target='_blank'>
						<?php 
						/* translators: %s = add-on name */
							echo sprintf( esc_html__( 'How to activate %s license?', 'booking-activities' ), 'Prices&nbsp;and&nbsp;Credits' ); 
						?>
					</a>
				</strong>
			</p>
		</div>
		<?php
	}

	else if( $license_status !== 'valid' ) {
		?>
		<div class='bookacti-addon-promo'>
			<?php 
			$addon_link = '<strong><a href="https://booking-activities.fr/en/downloads/prices-and-credits/?utm_source=plugin&utm_medium=plugin&utm_medium=plugin&utm_campaign=prices-and-credits&utm_content=encart-promo-' . $type . '" target="_blank" >Prices&nbsp;and&nbsp;Credits</a></strong>';
			$message = '';
			$event_name = '';
			if( $type === 'group-of-events' ) {
				/* translators: %s is the placeholder for Prices and Credits add-on link */
				$message = esc_html__( 'Set a price or a promotion in cash or in credits on your groups of events with %s add-on !', 'booking-activities' );
				$event_name = esc_html__( 'My grouped event', 'booking-activities' );
			} else {
				/* translators: %s is the placeholder for Prices and Credits add-on link */
				$message = esc_html__( 'Set a price or a promotion in cash or in credits on your events with %s add-on !', 'booking-activities' );
				$event_name = esc_html__( 'My event', 'booking-activities' );
			}
			echo sprintf( $message, $addon_link );
			?>
			<div class='bookacti-promo-events-examples'>
				<a class='fc-timegrid-event fc-v-event fc-event fc-event-start fc-event-end bookacti-narrow-event'>
					<div class='fc-event-main'>
						<div class='fc-event-time'><span>7:00 - 8:30</span></div>
						<div class='fc-event-title-container'><div class='fc-event-title'><?php echo $event_name; ?></div></div>
						<div class='bookacti-price-container'>
							<span class='bookacti-price bookacti-promo'>$30</span>
						</div>
						<div class='bookacti-availability-container'>
							<span class='bookacti-available-places bookacti-not-booked'>
								<span class='bookacti-available-places-number'>50</span>
								<span class='bookacti-available-places-unit-name'></span>
								<span class='bookacti-available-places-avail-particle'> <?php echo esc_html_x( 'avail.', 'Short for availabilities [plural noun]', 'booking-activities' ); ?></span>
							</span>
						</div>
					</div>
				</a>
				<a class='fc-timegrid-event fc-v-event fc-event fc-event-start fc-event-end bookacti-narrow-event'>
					<div class='fc-event-main'>
						<div class='fc-event-time'><span>7:00 - 8:30</span></div>
						<div class='fc-event-title-container'><div class='fc-event-title'><?php echo $event_name; ?></div></div>
					
						<div class='bookacti-price-container'>
							<span class='bookacti-price bookacti-promo'>- 20%</span>
						</div>
						<div class='bookacti-availability-container'>
							<span class='bookacti-available-places bookacti-not-booked'>
								<span class='bookacti-available-places-number'>50</span>
								<span class='bookacti-available-places-unit-name'></span>
								<span class='bookacti-available-places-avail-particle'> <?php echo esc_html_x( 'avail.', 'Short for availabilities [plural noun]', 'booking-activities' ); ?></span>
							</span>
						</div>
					</div>
				</a>
				<a class='fc-timegrid-event fc-v-event fc-event fc-event-start fc-event-end bookacti-narrow-event'>
					<div class='fc-event-main'>
						<div class='fc-event-time'><span>7:00 - 8:30</span></div>
						<div class='fc-event-title-container'><div class='fc-event-title'><?php echo $event_name; ?></div></div>
						<div class='bookacti-price-container'>
							<span class='bookacti-price bookacti-promo'>
								<?php 
								$amount = 12;
								/* translators: %d is an integer (an amount of credits) */
								echo sprintf( _n( '%d credit', '%d credits', $amount, 'booking-activities' ), $amount ); 
								?>
							</span>
						</div>
						<div class='bookacti-availability-container'>
							<span class='bookacti-available-places bookacti-not-booked'>
								<span class='bookacti-available-places-number'>50</span>
								<span class='bookacti-available-places-unit-name'></span>
								<span class='bookacti-available-places-avail-particle'> <?php echo esc_html_x( 'avail.', 'Short for availabilities [plural noun]', 'booking-activities' ); ?></span>
							</span>
						</div>
					</div>
				</a>
			</div>
			<div><a href='https://booking-activities.fr/en/downloads/prices-and-credits/?utm_source=plugin&utm_medium=plugin&utm_medium=plugin&utm_campaign=prices-and-credits&utm_content=encart-promo-<?php echo $type; ?>' class='button' target='_blank'><?php esc_html_e( 'Learn more', 'booking-activities' ); ?></a></div>
		</div>
		<?php
	}
}