<?php
/**
 * Group functions
 *
 * @since 2.1.0
 *
 * @package LearnDash\Groups
 */

use LearnDash\Core\Models\Product;
use LearnDash\Core\Utilities\Cast;
use StellarWP\Learndash\StellarWP\DB\DB;

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

/**
 * Handles group email messages.
 *
 * Fires on `learndash_group_emails` AJAX action.
 *
 * @since 2.1.0
 */
function learndash_group_emails() {
	if ( ( isset( $_POST['action'] ) ) && ( 'learndash_group_emails' === $_POST['action'] ) && ( isset( $_POST['group_email_data'] ) ) && ( ! empty( $_POST['group_email_data'] ) ) ) {

		if ( ! is_user_logged_in() ) {
			exit;
		}
		$current_user = wp_get_current_user();
		if ( ( ! learndash_is_group_leader_user( $current_user->ID ) ) && ( ! learndash_is_admin_user( $current_user->ID ) ) ) {
			exit;
		}

		$group_email_data = json_decode( stripslashes( $_POST['group_email_data'] ), true );

		if ( ( ! isset( $group_email_data['group_id'] ) ) || ( empty( $group_email_data['group_id'] ) ) ) {
			die();
		}
		$group_email_data['group_id'] = intval( $group_email_data['group_id'] );

		if ( ( ! isset( $_POST['nonce'] ) ) || ( empty( $_POST['nonce'] ) ) || ( ! wp_verify_nonce( $_POST['nonce'], 'group_email_nonce_' . $group_email_data['group_id'] . '_' . $current_user->ID ) ) ) {
			die();
		}

		if ( ( ! isset( $group_email_data['email_subject'] ) ) || ( empty( $group_email_data['email_subject'] ) ) ) {
			die();
		}
		$group_email_data['email_subject'] = wp_strip_all_tags( stripcslashes( $group_email_data['email_subject'] ) );

		if ( ( ! isset( $group_email_data['email_message'] ) ) || ( empty( $group_email_data['email_message'] ) ) ) {
			die();
		}
		$group_email_data['email_message'] = wpautop( stripcslashes( $group_email_data['email_message'] ) );

		$group_admin_ids = learndash_get_groups_administrator_ids( $group_email_data['group_id'] );
		if ( in_array( $current_user->ID, $group_admin_ids, true ) === false ) {
			die();
		}

		$mail_args = array(
			'to'          => $current_user->user_email,
			'subject'     => $group_email_data['email_subject'],
			'message'     => $group_email_data['email_message'],
			'attachments' => '',
			'headers'     => array(
				'MIME-Version: 1.0',
				'content-type: text/html',
				'From: ' . $current_user->display_name . ' <' . $current_user->user_email . '>',
				'Reply-to: ' . $current_user->display_name . ' <' . $current_user->user_email . '>',
			),
		);

		$group_user_ids = learndash_get_groups_user_ids( $group_email_data['group_id'] );
		if ( ! empty( $group_user_ids ) ) {
			$email_addresses = array();
			if ( ( defined( 'LEARNDASH_GROUP_EMAIL_SINGLE' ) ) && ( true === LEARNDASH_GROUP_EMAIL_SINGLE ) ) {
				$group_email_error_message = array();
				foreach ( $group_user_ids as $user_id ) {
					$user = get_user_by( 'id', $user_id );

					$group_email_error = null;
					add_action(
						'wp_mail_failed',
						function ( $mail_error ) {
							global $group_email_error;
							$group_email_error = $mail_error; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- It is what it is.
						}
					);

					if ( $user ) {
						$mail_args['to'] = sanitize_email( $user->user_email );

						/**
						 * Filters group email user arguments.
						 *
						 * @param array $mail_args Group mail arguments.
						 */
						$mail_args = apply_filters( 'ld_group_email_users_args', $mail_args );
						if ( ! empty( $mail_args ) ) {

							/**
							 * Fires before sending user group email.
							 *
							 * @param array $mail_args Mail arguments.
							 */
							do_action( 'ld_group_email_users_before', $mail_args );

							$mail_ret = wp_mail( $mail_args['to'], $mail_args['subject'], $mail_args['message'], $mail_args['headers'], $mail_args['attachments'] );

							/**
							 * Fires after sending user group email.
							 *
							 * @param array   $mail_args Mail arguments.
							 * @param boolean $success   Whether the email contents were sent successfully.
							 */
							do_action( 'ld_group_email_users_after', $mail_args, $mail_ret );

							if ( ! $mail_ret ) {
								if ( is_wp_error( $group_email_error ) ) { // @phpstan-ignore-line - No time to investigate.
									$group_email_error_message[ $user->user_email ] = $group_email_error->get_error_message();
								}
								wp_send_json_error(
									array(
										// translators: mail_ret error, group email error message.
										'message' => sprintf( wp_kses_post( __( '<span style="color:red">Error: Email(s) not sent. Please try again or check with your hosting provider.<br />wp_mail() returned %1$d.<br />Error: %2$s</span>', 'learndash' ) ), $mail_ret, $group_email_error_message[ $user->user_email ] ),
									)
								);
								die();
							} else {
								$email_addresses[] = $user->user_email;
							}
						} else {
							wp_send_json_error(
								array(
									'message' => '<span style="color:red">' . esc_html__( 'Mail Args empty. Unexpected condition from filter: ld_group_email_users_args', 'learndash' ) . '</span>',
								)
							);
						}
					}
				}

				wp_send_json_success(
					array(
						'message' => '<span style="color:green">' .
						sprintf(
							// translators: total of users emailed, group.
							esc_html__(
								'Success: Email sent to %1$d %2$s users.',
								'learndash'
							),
							count( $email_addresses ),
							learndash_get_custom_label_lower( 'group' )
						),
						'</span>',
					)
				);
			} else {
				foreach ( $group_user_ids as $user_id ) {
					$user = get_user_by( 'id', $user_id );

					if ( $user ) {
						$email_addresses[] = 'Bcc: ' . sanitize_email( $user->user_email );
					}
				}

				$group_email_error = null; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- It is what it is.
				add_action(
					'wp_mail_failed',
					function ( $mail_error ) {
						global $group_email_error;
						$group_email_error = $mail_error; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- It is what it is.
					}
				);

				if ( $email_addresses ) {
					$mail_args['headers'] = array_merge( $mail_args['headers'], $email_addresses );

					/**
					 * Filters group email user arguments.
					 *
					 * @param array $mail_args Group mail arguments.
					 */
					$mail_args = apply_filters( 'ld_group_email_users_args', $mail_args );
					if ( ! empty( $mail_args ) ) {

						/**
						 * Fires before sending user group email.
						 *
						 * @param array $mail_args Mail arguments.
						 */
						do_action( 'ld_group_email_users_before', $mail_args );

						$mail_ret = wp_mail( $mail_args['to'], $mail_args['subject'], $mail_args['message'], $mail_args['headers'], $mail_args['attachments'] );

						/**
						 * Fires after sending user group email.
						 *
						 * @param array   $mail_args Mail arguments.
						 * @param boolean $success   Whether the email contents were sent successfully.
						 */
						do_action( 'ld_group_email_users_after', $mail_args, $mail_ret );

						if ( ! $mail_ret ) {
							$group_email_error_message = '';

							if ( is_wp_error( $group_email_error ) ) { // @phpstan-ignore-line - No time to investigate.
								$group_email_error_message = $group_email_error->get_error_message();
							}
							wp_send_json_error(
								array(
									// translators: mail_ret error, group email error message.
									'message' => sprintf( wp_kses_post( __( '<span style="color:red">Error: Email(s) not sent. Please try again or check with your hosting provider.<br />wp_mail() returned %1$d.<br />Error: %2$s</span>', 'learndash' ) ), $mail_ret, $group_email_error_message ),
								)
							);
						} else {
							wp_send_json_success(
								array(
									'message' => '<span style="color:green">' . sprintf(
										wp_kses_post(
											// translators: total of users emailed, group.
											_nx(
												'Success: Email sent to %1$d %2$s user.',
												'Success: Email sent to %1$d %2$s users.',
												count( $email_addresses ),
												'placeholders: email addresses, group.',
												'learndash'
											)
										),
										number_format_i18n( count( $email_addresses ) ),
										learndash_get_custom_label_lower( 'group' )
									) . '</span>',
								)
							);
						}
					} else {
						wp_send_json_error(
							array(
								'message' => '<span style="color:red">' . esc_html__( 'Mail Args empty. Unexpected condition from filter: ld_group_email_users_args', 'learndash' ) . '</span>',
							)
						);
					}
				}
			}
		} else {
			wp_send_json_error(
				array(
					'message' => esc_html__( 'No users found.', 'learndash' ),
				)
			);
		}
		wp_send_json_error();
		die();
	}
}
add_action( 'wp_ajax_learndash_group_emails', 'learndash_group_emails' );

/**
 * Adds Group Leader role if it does not exist.
 *
 * Fires on `learndash_activated` hook.
 *
 * @since 2.1.0
 */
function learndash_add_group_admin_role() {
	$group_leader = get_role( 'group_leader' );

	// We can't call the class settings because it is not loaded yet.
	$group_leader_user_caps = get_option( 'learndash_groups_group_leader_user', array() );

	$role_caps = array(
		'read'                      => true,
		'group_leader'              => true,
		'wpProQuiz_show_statistics' => true,
	);

	/**
	 * Controls showing the Group Leader user in the Authors selector shown on the post editor. Seems that metabox query checks the
	 * user_meta key wp_user_level value to ensure the level is greater than 0. By default Group Leaders are set to level 0.
	 */
	if ( ( isset( $group_leader_user_caps['show_authors_selector'] ) ) && ( 'yes' === $group_leader_user_caps['show_authors_selector'] ) ) {
		$role_caps['level_1'] = true;
		$role_caps['level_0'] = false;
	} else {
		$role_caps['level_1'] = false;
		$role_caps['level_0'] = true;
	}

	if ( is_null( $group_leader ) ) {
		$group_leader = add_role(
			'group_leader',
			'Group Leader',
			$role_caps
		);
	} else {
		foreach ( $role_caps as $role_cap => $active ) {
			$group_leader->add_cap( $role_cap, $active );
		}
	}

	/**
	 * Added to correct issues with Group Leader User capabilities.
	 * See LEARNDASH-5707. See changes in
	 * includes/settings/settings-sections/class-ld-settings-section-groups-group-leader-user.php
	 *
	 * @since 3.4.0.2
	 */
	update_option( 'learndash_groups_group_leader_user_activate', time() );
}

add_action( 'learndash_activated', 'learndash_add_group_admin_role' );

/**
 * Allows group leader access to the admin dashboard.
 *
 * WooCommerce prevents access to the dashboard for all non-admin user roles. This filter allows
 * us to check if the current user is group_leader and override WC access.
 * Fires on `woocommerce_prevent_admin_access` hook.
 *
 * @since 2.2.0.1
 *
 * @param boolean $prevent_access value from WC.
 *
 * @return boolean The adjusted value based on user's access/role.
 */
function learndash_check_group_leader_access( $prevent_access ) {
	if ( learndash_is_group_leader_user() ) {

		if ( defined( 'LEARNDASH_GROUP_LEADER_DASHBOARD_ACCESS' ) ) {
			if ( LEARNDASH_GROUP_LEADER_DASHBOARD_ACCESS == true ) {
				$prevent_access = false;
			} elseif ( LEARNDASH_GROUP_LEADER_DASHBOARD_ACCESS == false ) {
				$prevent_access = true;
			}
		} else {
			$prevent_access = false;
		}
	}

	return $prevent_access;
}
add_filter( 'woocommerce_prevent_admin_access', 'learndash_check_group_leader_access', 20, 1 );

/**
 * Gets the list of enrolled courses for a group.
 *
 * @since 2.1.0
 *
 * @param int     $group_id         Optional. Group ID. Default 0.
 * @param boolean $bypass_transient Optional. Whether to bypass transient cache or not. Default false.
 *
 * @return array An array of course IDs.
 */
function learndash_group_enrolled_courses( $group_id = 0, $bypass_transient = false ) {
	$course_ids = array();

	$group_id = absint( $group_id );
	if ( ! empty( $group_id ) ) {

		$query_args = array(
			'post_type'      => learndash_get_post_type_slug( 'course' ),
			'fields'         => 'ids',
			'posts_per_page' => -1,
			'meta_query'     => array(
				array(
					'key'     => 'learndash_group_enrolled_' . $group_id,
					'compare' => 'EXISTS',
				),
			),
		);

		$query = new WP_Query( $query_args );
		if ( ( is_a( $query, 'WP_Query' ) ) && ( property_exists( $query, 'posts' ) ) ) {
			$course_ids = $query->posts;
		}
	}

	return $course_ids;
}

/**
 * Sets the list of enrolled courses for a group.
 *
 * @since 2.2.1
 *
 * @param int   $group_id          Optional. Group ID. Default 0.
 * @param array $group_courses_new Optional. An array of courses to enroll a group. Default empty array.
 */
function learndash_set_group_enrolled_courses( $group_id = 0, $group_courses_new = array() ) {
	$group_id = absint( $group_id );
	if ( ! empty( $group_id ) ) {

		$group_courses_old = learndash_group_enrolled_courses( $group_id, true );

		$group_courses_intersect = array_intersect( $group_courses_new, $group_courses_old );

		$group_courses_add = array_diff( $group_courses_new, $group_courses_intersect );
		if ( ! empty( $group_courses_add ) ) {
			foreach ( $group_courses_add as $course_id ) {
				ld_update_course_group_access( $course_id, $group_id, false );
			}
		}

		$group_courses_remove = array_diff( $group_courses_old, $group_courses_intersect );
		if ( ! empty( $group_courses_remove ) ) {
			foreach ( $group_courses_remove as $course_id ) {
				ld_update_course_group_access( $course_id, $group_id, true );
			}
		}

		/**
		 * Finally clear our cache for other services.
		 * $transient_key = 'learndash_group_courses_' . $group_id;
		 * LDLMS_Transients::delete( $transient_key );
		 */
	}
}

/**
 * Groups all the related course ids for a set of groups IDs.
 *
 * @since 2.3.0
 *
 * @param int   $user_id   Optional. The User ID to get the associated groups.
 *                         Defaults to current user ID.
 * @param array $group_ids Optional. An array of group IDs to source the course IDs from.
 *                         If not provided will use group ids based on user_id access.
 *                         Default empty array.
 *
 * @return array An array of course_ids.
 */
function learndash_get_groups_courses_ids( $user_id = 0, $group_ids = array() ) {
	$course_ids = array();

	$user_id = absint( $user_id );
	if ( ( is_array( $group_ids ) ) && ( ! empty( $group_ids ) ) ) {
		$group_ids = array_map( 'absint', $group_ids );
	}

	if ( empty( $user_id ) ) {
		// If the current user is not able to be determined. Then abort.
		if ( ! is_user_logged_in() ) {
			return $course_ids;
		}

		$user_id = get_current_user_id();
	}

	if ( learndash_is_group_leader_user( $user_id ) ) {
		$group_leader_group_ids = learndash_get_administrators_group_ids( $user_id );

		// If user is group leader and the group ids is empty, nothing else to do. abort.
		if ( empty( $group_leader_group_ids ) ) {
			return $course_ids;
		}

		if ( empty( $group_ids ) ) {
			$group_ids = $group_leader_group_ids;
		} else {
			$group_ids = array_intersect( $group_leader_group_ids, $group_ids );
		}
	} elseif ( ! learndash_is_admin_user( $user_id ) ) {
		return $course_ids;
	}

	if ( ! empty( $group_ids ) ) {

		foreach ( $group_ids as $group_id ) {
			$group_course_ids = learndash_group_enrolled_courses( $group_id );
			if ( ! empty( $group_course_ids ) ) {
				$course_ids = array_merge( $course_ids, $group_course_ids );
			}
		}
	}

	if ( ! empty( $course_ids ) ) {
		$course_ids = array_unique( $course_ids );
	}

	return $course_ids;
}

/**
 * Checks whether a group is enrolled in a certain course.
 *
 * @since 2.1.0
 *
 * @param int $group_id  Group ID.
 * @param int $course_id Course ID.
 *
 * @return boolean Whether a group is enrolled in a course or not.
 */
function learndash_group_has_course( $group_id = 0, $course_id = 0 ) {
	$group_id  = absint( $group_id );
	$course_id = absint( $course_id );
	if ( ( ! empty( $group_id ) ) && ( ! empty( $course_id ) ) ) {
		return get_post_meta( $course_id, 'learndash_group_enrolled_' . $group_id, true );
	}

	return false;
}

/**
 * Gets the timestamp of when a course is available to the group.
 *
 * @since 2.1.0
 *
 * @param int $group_id  Group ID.
 * @param int $course_id Course ID.
 *
 * @return string The timestamp of when a course is available to the group.
 */
function learndash_group_course_access_from( $group_id = 0, $course_id = 0 ) {
	$group_id  = absint( $group_id );
	$course_id = absint( $course_id );
	if ( ( ! empty( $group_id ) ) && ( ! empty( $course_id ) ) ) {
		$timestamp = absint( get_post_meta( $course_id, 'learndash_group_enrolled_' . $group_id, true ) );

		/**
		 * Filters group courses order query arguments.
		 *
		 * @param int $timestamp The timestamp when the course was enrolled to the Group.
		 * @param int $group_id  Group ID.
		 * @param int $course_id Course ID.
		 */
		return apply_filters( 'learndash_group_course_access_from', $timestamp, $group_id, $course_id );
	}

	return '';
}

/**
 * Checks whether a course can be accessed by the user's group.
 *
 * @since 2.1.0
 *
 * @param int $user_id   User ID.
 * @param int $course_id Course ID.
 *
 * @return bool Whether a course can be accessed by the user's group.
 */
function learndash_user_group_enrolled_to_course( $user_id = 0, $course_id = 0 ) {
	$user_id   = Cast::to_int( $user_id );
	$course_id = Cast::to_int( $course_id );

	if (
		0 === $user_id
		|| 0 === $course_id
	) {
		return false;
	}

	$user_group_ids = learndash_get_users_group_ids( $user_id );

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

	$user = get_user_by( 'ID', $user_id );

	if ( ! $user ) {
		return false;
	}

	foreach ( $user_group_ids as $group_id ) {
		if ( ! learndash_group_has_course( $group_id, $course_id ) ) {
			continue;
		}

		$group_product = Product::find( $group_id );

		if (
			$group_product
			&& $group_product->user_has_access( $user )
		) {
			return true;
		}
	}

	return false;
}

/**
 * Gets timestamp of when the course is available to a user in a group.
 *
 * @since 2.1.0
 *
 * @param int     $user_id   User ID.
 * @param int     $course_id Course ID.
 * @param boolean $bypass_transient Optional. Whether to bypass transient cache. Default false.
 *
 * @return string|void The timestamp of when a course is available to a user in a group.
 */
function learndash_user_group_enrolled_to_course_from( $user_id = 0, $course_id = 0, $bypass_transient = false ) {
	$enrolled_from = null;
	$user_id       = absint( $user_id );
	$course_id     = absint( $course_id );
	if ( ( empty( $user_id ) ) || ( empty( $course_id ) ) ) {
		return $enrolled_from;
	}

	$userdata = get_userdata( $user_id );
	if ( ! $userdata ) {
		return $enrolled_from;
	}
	$user_registered_timestamp = strtotime( $userdata->user_registered );

	$user_group_ids = learndash_get_users_group_ids( $user_id, $bypass_transient );
	if ( empty( $user_group_ids ) ) {
		return $enrolled_from;
	}
	$user_group_ids = array_map( 'absint', $user_group_ids );

	$course_group_ids = learndash_get_course_groups( $course_id );
	if ( empty( $course_group_ids ) ) {
		return $enrolled_from;
	}
	$course_group_ids = array_map( 'absint', $course_group_ids );

	$course_group_ids = array_intersect( $course_group_ids, $user_group_ids );
	if ( empty( $course_group_ids ) ) {
		return $enrolled_from;
	}

	if ( ! empty( $course_group_ids ) ) {
		$group_course_enrolled_times = array();

		foreach ( $course_group_ids as $course_group_id ) {
			$enrolled_from_temp = learndash_group_course_access_from( $course_group_id, $course_id );
			if ( ! empty( $enrolled_from_temp ) ) {
				$group_course_enrolled_times[ $course_group_id ] = absint( $enrolled_from_temp );
			}
		}

		if ( ! empty( $group_course_enrolled_times ) ) {
			asort( $group_course_enrolled_times );

			/**
			 * Filter the user group enrollment to course timestamps.
			 *
			 * @since 3.5.0
			 *
			 * @param array $group_course_enrolled_times Array of course to group enrollment timestamps.
			 * @param int   $user_id                     User ID.
			 * @param int   $course_id                   Course Post ID.
			 */
			$group_course_enrolled_times = apply_filters( 'learndash_user_group_enrolled_to_course_from_timestamps', $group_course_enrolled_times, $user_id, $course_id );

			foreach ( $group_course_enrolled_times as $group_id => $group_course_timestamp ) {
				$enrolled_from = $group_course_timestamp;
				break;
			}
		}
	}

	if ( ! is_null( $enrolled_from ) ) {
		if ( $enrolled_from <= time() ) {
			/** If the user registered AFTER the course was enrolled into the group
			 * then we use the user registration date.
			 */
			if ( $user_registered_timestamp > $enrolled_from ) {
				if ( ( defined( 'LEARNDASH_GROUP_ENROLLED_COURSE_FROM_USER_REGISTRATION' ) ) && ( true === LEARNDASH_GROUP_ENROLLED_COURSE_FROM_USER_REGISTRATION ) ) {
					$enrolled_from = $user_registered_timestamp;
				}
			}
		} else {
			/**
			 * If $enrolled_from is greater than the current timestamp
			 * we reset the enrolled from time to null. Not sure why.
			 */
			$enrolled_from = null;
		}
	}

	/**
	 * Filters user courses order query arguments.
	 *
	 * @param int $enrolled_from Calculated timestamp when user enrolled to course through group.
	 * @param int $user_id   User ID.
	 * @param int $course_id Course ID.
	 * @param int $group_id  Determined Group ID.
	 */
	return apply_filters( 'learndash_user_group_enrolled_to_course_from', $enrolled_from, $user_id, $course_id, $group_id );
}

/**
 * Gets the list of group IDs administered by the user.
 *
 * @since 2.1.0
 *
 * @global wpdb   $wpdb    WordPress database abstraction object.
 *
 * @param int     $user_id User ID.
 * @param boolean $menu    Optional. Menu. Default false.
 *
 * @return array A list of group ids managed by user.
 */
function learndash_get_administrators_group_ids( $user_id, $menu = false ) {
	$group_ids = array();

	$user_id = absint( $user_id );
	if ( ! empty( $user_id ) ) {
		if ( ( learndash_is_admin_user( $user_id ) ) && ( true !== $menu ) ) {
			$group_ids = learndash_get_groups( true, $user_id );
		} else {
			$all_user_meta = get_user_meta( $user_id );
			if ( ! empty( $all_user_meta ) ) {
				foreach ( $all_user_meta as $meta_key => $meta_set ) {
					if ( 'learndash_group_leaders_' == substr( $meta_key, 0, strlen( 'learndash_group_leaders_' ) ) ) {
						$group_ids = array_merge( $group_ids, $meta_set );
					}
				}
			}

			if ( ! empty( $group_ids ) ) {
				$group_ids = array_map( 'absint', $group_ids );
				$group_ids = array_diff( $group_ids, array( 0 ) ); // Removes zeros.
				$group_ids = learndash_validate_groups( $group_ids );
				if ( ! empty( $group_ids ) ) {
					if ( learndash_is_groups_hierarchical_enabled() ) {
						foreach ( $group_ids as $group_id ) {
							$group_children = learndash_get_group_children( $group_id );
							if ( ! empty( $group_children ) ) {
								$group_ids = array_merge( $group_ids, $group_children );
							}
						}
					}

					$group_ids = array_map( 'absint', $group_ids );
					$group_ids = array_unique( $group_ids, SORT_NUMERIC );
				}
			}
		}
	}

	return $group_ids;
}

/**
 * Makes user an administrator of the given group IDs.
 *
 * @since 2.2.1
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @param int   $user_id           User ID.
 * @param array $leader_groups_new Optional. A list of group ids. Default empty array.
 *
 * @return array
 */
function learndash_set_administrators_group_ids( $user_id = 0, $leader_groups_new = array() ) {
	global $wpdb;

	$user_id = absint( $user_id );
	if ( ! is_array( $leader_groups_new ) ) {
		$leader_groups_new = array();
	}

	if ( ! empty( $user_id ) ) {
		$leader_groups_old       = learndash_get_administrators_group_ids( $user_id, true );
		$leader_groups_intersect = array_intersect( $leader_groups_new, $leader_groups_old );

		$leader_groups_add = array_diff( $leader_groups_new, $leader_groups_intersect );
		if ( ! empty( $leader_groups_add ) ) {
			foreach ( $leader_groups_add as $group_id ) {
				ld_update_leader_group_access( $user_id, $group_id, false );
			}
		}

		$leader_groups_remove = array_diff( $leader_groups_old, $leader_groups_intersect );
		if ( ! empty( $leader_groups_remove ) ) {
			foreach ( $leader_groups_remove as $group_id ) {
				ld_update_leader_group_access( $user_id, $group_id, true );
			}
		}

		/**
		 * Finally clear our cache for other services.
		 * $transient_key = "learndash_user_groups_" . $user_id;
		 * LDLMS_Transients::delete( $transient_key );
		 */
	}
	return array();
}



/**
 * Gets the list of all groups.
 *
 * @since 2.1.0
 *
 * @param boolean $id_only         Optional. Whether to return only IDs. Default false.
 * @param int     $current_user_id Optional. ID of the user for checking capabilities. Default 0.
 *
 * @return array An array of group IDs.
 */
function learndash_get_groups( $id_only = false, $current_user_id = 0 ) {

	if ( empty( $current_user_id ) ) {
		if ( ! is_user_logged_in() ) {
			return array();
		}
		$current_user_id = get_current_user_id();
	}

	if ( learndash_is_group_leader_user( $current_user_id ) ) {
		return learndash_get_administrators_group_ids( $current_user_id );
	} elseif ( learndash_is_admin_user( $current_user_id ) ) {

		$groups_query_args = array(
			'post_type'   => 'groups',
			'nopaging'    => true,
			'post_status' => array( 'publish', 'pending', 'draft', 'future', 'private' ),
		);

		if ( $id_only ) {
			$groups_query_args['fields'] = 'ids';
		}

		$groups_query = new WP_Query( $groups_query_args );
		return $groups_query->posts;
	}
	return array();
}

/**
 * Get a users group IDs.
 *
 * @since 2.1.0
 *
 * @param int     $user_id          Optional. User ID. Default 0.
 * @param boolean $bypass_transient Optional. Whether to bypass transient cache or not. Default false.
 *
 * @return array A list of user's group IDs.
 */
function learndash_get_users_group_ids( $user_id = 0, $bypass_transient = false ) {
	$group_ids = array();

	$user_id = absint( $user_id );
	if ( ! empty( $user_id ) ) {
		$transient_key = 'learndash_user_groups_' . $user_id;
		if ( ! $bypass_transient ) {
			$group_ids_transient = LDLMS_Transients::get( $transient_key );
		} else {
			$group_ids_transient = false;
		}

		if ( false === $group_ids_transient ) {
			if ( learndash_is_group_leader_user( $user_id ) && ( 'yes' === LearnDash_Settings_Section::get_section_setting( 'LearnDash_Settings_Section_Groups_Group_Leader_User', 'groups_autoenroll_managed' ) ) ) {
				$group_ids = learndash_get_administrators_group_ids( $user_id );
			} else {
				$all_user_meta = get_user_meta( $user_id );
				if ( ! empty( $all_user_meta ) ) {
					foreach ( $all_user_meta as $meta_key => $meta_set ) {
						if ( 'learndash_group_users_' == substr( $meta_key, 0, strlen( 'learndash_group_users_' ) ) ) {
							$group_ids = array_merge( $group_ids, $meta_set );
						}
					}
				}
			}

			if ( ! empty( $group_ids ) ) {
				$group_ids = array_map( 'absint', $group_ids );
				$group_ids = array_diff( $group_ids, array( 0 ) ); // Removes zeros.
				$group_ids = learndash_validate_groups( $group_ids );
				if ( ! empty( $group_ids ) ) {
					if ( learndash_is_groups_hierarchical_enabled() ) {
						foreach ( $group_ids as $group_id ) {
							$group_children = learndash_get_group_children( $group_id );
							if ( ! empty( $group_children ) ) {
								$group_ids = array_merge( $group_ids, $group_children );
							}
						}
					}

					$group_ids = array_map( 'absint', $group_ids );
					$group_ids = array_unique( $group_ids, SORT_NUMERIC );
				}
			}
			LDLMS_Transients::set( $transient_key, $group_ids, MINUTE_IN_SECONDS );
		} else {
			$group_ids = $group_ids_transient;
		}
	}

	return $group_ids;
}

/**
 * Adds a user to the list of given group IDs.
 *
 * @param int   $user_id         Optional. User ID. Default 0.
 * @param array $user_groups_new Optional. An array of group IDs to add a user. Default empty array.
 */
function learndash_set_users_group_ids( $user_id = 0, $user_groups_new = array() ) {

	$user_id = absint( $user_id );
	if ( ! is_array( $user_groups_new ) ) {
		$user_groups_new = array();
	}

	if ( ! empty( $user_id ) ) {
		$user_groups_old = learndash_get_users_group_ids( $user_id, true );

		$user_groups_intersect = array_intersect( $user_groups_new, $user_groups_old );

		$user_groups_add = array_diff( $user_groups_new, $user_groups_intersect );
		if ( ! empty( $user_groups_add ) ) {
			foreach ( $user_groups_add as $group_id ) {
				ld_update_group_access( $user_id, $group_id, false );
			}
		}

		$user_groups_remove = array_diff( $user_groups_old, $user_groups_intersect );
		if ( ! empty( $user_groups_remove ) ) {
			foreach ( $user_groups_remove as $group_id ) {
				ld_update_group_access( $user_id, $group_id, true );
			}
		}
	}
}

/**
 * Gets the list of groups associated with the course.
 *
 * @since 2.2.1
 *
 * @param int     $course_id        Optional. Course ID. Default 0.
 * @param boolean $bypass_transient Optional. Whether to bypass transient cache or not. Default false.
 *
 * @return array An array of group IDs associated with the course.
 */
function learndash_get_course_groups( $course_id = 0, $bypass_transient = false ) {
	$group_ids = array();

	$course_id = absint( $course_id );
	if ( ! empty( $course_id ) ) {
		$course_post_meta = get_post_meta( $course_id );
		if ( ! empty( $course_post_meta ) ) {
			foreach ( $course_post_meta as $meta_key => $meta_set ) {
				if ( 'learndash_group_enrolled_' == substr( $meta_key, 0, strlen( 'learndash_group_enrolled_' ) ) ) {
					/**
					 * For Course Groups the meta_value is a datetime. This is the datetime the course
					 * was added to the group. So we need to pull the group_id from the meta_key.
					 */
					$group_id    = str_replace( 'learndash_group_enrolled_', '', $meta_key );
					$group_ids[] = absint( $group_id );
				}
			}

			if ( ! empty( $group_ids ) ) {
				$group_ids = learndash_validate_groups( $group_ids );
			}
		}
	}

	return $group_ids;
}

/**
 * Adds a course to the list of the given group IDs.
 *
 * @param int   $course_id         Optional. Course ID. Default 0.
 * @param array $course_groups_new Optional. A list of group IDs to add a course. Default empty array.
 */
function learndash_set_course_groups( $course_id = 0, $course_groups_new = array() ) {

	$course_id = absint( $course_id );
	if ( ! is_array( $course_groups_new ) ) {
		$course_groups_new = array();
	}

	if ( ! empty( $course_id ) ) {
		$course_groups_old       = learndash_get_course_groups( $course_id, true );
		$course_groups_intersect = array_intersect( $course_groups_new, $course_groups_old );

		$course_groups_add = array_diff( $course_groups_new, $course_groups_intersect );
		if ( ! empty( $course_groups_add ) ) {
			foreach ( $course_groups_add as $group_id ) {
				ld_update_course_group_access( $course_id, $group_id, false );
			}
		}

		$course_groups_remove = array_diff( $course_groups_old, $course_groups_intersect );
		if ( ! empty( $course_groups_remove ) ) {
			foreach ( $course_groups_remove as $group_id ) {
				ld_update_course_group_access( $course_id, $group_id, true );
			}
		}

		// Finally clear our cache for other services.
		$transient_key = 'learndash_course_groups_' . $course_id;
		LDLMS_Transients::delete( $transient_key );
	}
}

/**
 * Returns the list of user ids that belong to a group.
 *
 * @since 2.1.0
 * @since 4.12.0 Passing `$bypass_transient` is not used anymore,
 *            but not deprecated yet in case we need to use it again for better performance.
 *
 * @param int  $group_id         Group ID. Default 0. Optional only to allow for backward compatibility.
 * @param bool $bypass_transient Optional. Whether to bypass transient cache or not. Default false. Not used anymore.
 *
 * @return int[] User ids that belong to a group.
 */
function learndash_get_groups_user_ids( $group_id = 0, bool $bypass_transient = false ): array {
	$group_id = Cast::to_int( $group_id );

	if ( $group_id <= 0 ) {
		return [];
	}

	$ids = DB::get_col(
		DB::table( 'usermeta' )
			->select( 'user_id' )
			->where( 'meta_key', 'learndash_group_users_' . $group_id )
			->getSQL()
	);

	return array_map( [ Cast::class, 'to_int' ], $ids );
}

/**
 * Gets the list of user objects that belong to a group.
 *
 * @since 2.1.2
 *
 * @param int     $group_id         Group ID.
 * @param boolean $bypass_transient Optional. Whether to bypass transient cache or not. Default false.
 *
 * @return array An array user objects that belong to group.
 */
function learndash_get_groups_users( $group_id, $bypass_transient = false ) {

	$group_id = absint( $group_id );
	if ( ! empty( $group_id ) ) {
		if ( ! $bypass_transient ) {
			$transient_key       = 'learndash_group_users_' . $group_id;
			$group_users_objects = LDLMS_Transients::get( $transient_key );
		} else {
			$group_users_objects = false;
		}

		if ( false === $group_users_objects ) {

			/**
			 * Changed in v2.3 we no longer exclude ALL group leaders from groups.
			 * A group leader CAN be a member of a group user list.
			 *
			 * For this group get the group leaders. They will be excluded from the regular users.
			 * $group_leader_user_ids = learndash_get_groups_administrator_ids( $group_id );
			 */

			$user_query_args = array(
				'orderby'    => 'display_name',
				'order'      => 'ASC',
				'meta_query' => array(
					array(
						'key'     => 'learndash_group_users_' . intval( $group_id ),
						'compare' => 'EXISTS',
					),
				),
			);
			$user_query      = new WP_User_Query( $user_query_args );
			if ( isset( $user_query->results ) ) {
				$group_users_objects = $user_query->results;
			} else {
				$group_users_objects = array();
			}

			if ( ! $bypass_transient ) {
				LDLMS_Transients::set( $transient_key, $group_users_objects, MINUTE_IN_SECONDS );
			}
		}

		return $group_users_objects;
	}
	return array();
}


/**
 * Adds the list of given users to the group.
 *
 * @since 2.1.2
 *
 * @param int   $group_id        Optional. Group ID. Default 0.
 * @param array $group_users_new Optional. A list of user IDs to add to the group. Default empty array.
 */
function learndash_set_groups_users( $group_id = 0, $group_users_new = array() ) {

	$group_id = absint( $group_id );
	if ( ( is_array( $group_users_new ) ) && ( ! empty( $group_users_new ) ) ) {
		$group_users_new = array_map( 'absint', $group_users_new );
	} else {
		$group_users_new = array();
	}
	if ( ! empty( $group_id ) ) {
		update_post_meta( $group_id, 'learndash_group_users_' . $group_id, $group_users_new );

		$group_users_old = learndash_get_groups_user_ids( $group_id, true );

		$group_users_intersect = array_intersect( $group_users_new, $group_users_old );

		$group_users_add = array_diff( $group_users_new, $group_users_intersect );
		if ( ! empty( $group_users_add ) ) {
			foreach ( $group_users_add as $user_id ) {
				ld_update_group_access( $user_id, $group_id, false );
			}
		}

		$group_users_remove = array_diff( $group_users_old, $group_users_intersect );
		if ( ! empty( $group_users_remove ) ) {
			foreach ( $group_users_remove as $user_id ) {
				ld_update_group_access( $user_id, $group_id, true );
			}

			/**
			 * Fires after removing a user from the group.
			 *
			 * $group_id           int   ID of the group.
			 * $group_users_remove array An array of user IDs that are removed from the group.
			 */
			do_action( 'learndash_remove_group_users', $group_id, $group_users_remove );
		}

		// Finally clear our cache for other services.
		$transient_key = 'learndash_group_users_' . $group_id;
		LDLMS_Transients::delete( $transient_key );
	}
}

/**
 * Returns the list of administrator IDs for a group.
 *
 * @since 2.1.0
 * @since 4.12.0 Passing `$bypass_transient` is not used anymore,
 *            but not deprecated yet in case we need to use it again for better performance.
 *
 * @param int  $group_id         Group ID. Default 0. Optional only to allow for backward compatibility.
 * @param bool $bypass_transient Optional. Whether to bypass transient cache or not. Default false. Not used anymore.
 *
 * @return int[] Group administrator IDs.
 */
function learndash_get_groups_administrator_ids( $group_id = 0, $bypass_transient = false ) {
	$group_id = Cast::to_int( $group_id );

	if ( $group_id <= 0 ) {
		return [];
	}

	$ids = DB::get_col(
		DB::table( 'usermeta' )
			->select( 'user_id' )
			->where( 'meta_key', 'learndash_group_leaders_' . $group_id )
			->getSQL()
	);

	return array_map( [ Cast::class, 'to_int' ], $ids );
}

/**
 * Gets the list of group leaders for the given group ID.
 *
 * @since 2.1.2
 *
 * @param int     $group_id         Group ID.
 * @param boolean $bypass_transient Optional. Whether to bypass transient cache or not. Default 0.
 *
 * @return array An array of group leader user objects.
 */
function learndash_get_groups_administrators( $group_id = 0, $bypass_transient = false ) {

	$group_id = absint( $group_id );
	if ( ! empty( $group_id ) ) {
		$transient_key = 'learndash_group_leaders_' . $group_id;

		if ( ! $bypass_transient ) {
			$group_user_objects = LDLMS_Transients::get( $transient_key );
		} else {
			$group_user_objects = false;
		}
		if ( false === $group_user_objects ) {

			$user_query_args = array(
				'orderby'    => 'display_name',
				'order'      => 'ASC',
				'meta_query' => array(
					array(
						'key'     => 'learndash_group_leaders_' . intval( $group_id ),
						'value'   => intval( $group_id ),
						'compare' => '=',
						'type'    => 'NUMERIC',
					),
				),
			);
			$user_query      = new WP_User_Query( $user_query_args );
			if ( isset( $user_query->results ) ) {
				$group_user_objects = $user_query->results;
			} else {
				$group_user_objects = array();
			}

			if ( ! $bypass_transient ) {
				LDLMS_Transients::set( $transient_key, $group_user_objects, MINUTE_IN_SECONDS );
			}
		}

		return $group_user_objects;
	}
	return array();
}

/**
 * Makes the user leader for the given group ID.
 *
 * @since 2.1.2
 *
 * @param int   $group_id          Optional. Group ID. Default 0.
 * @param array $group_leaders_new Optional. A list of user IDs to make group leader. Default empty array.
 */
function learndash_set_groups_administrators( $group_id = 0, $group_leaders_new = array() ) {

	$group_id = absint( $group_id );
	if ( ! empty( $group_id ) ) {
		$group_leaders_old = learndash_get_groups_administrator_ids( $group_id, true );

		$group_leaders_intersect = array_intersect( $group_leaders_new, $group_leaders_old );
		$group_leaders_add       = array_diff( $group_leaders_new, $group_leaders_intersect );
		if ( ! empty( $group_leaders_add ) ) {
			foreach ( $group_leaders_add as $user_id ) {
				ld_update_leader_group_access( $user_id, $group_id );
			}
		}

		$group_leaders_remove = array_diff( $group_leaders_old, $group_leaders_intersect );
		if ( ! empty( $group_leaders_remove ) ) {
			foreach ( $group_leaders_remove as $user_id ) {
				ld_update_leader_group_access( $user_id, $group_id, true );
			}
		}

		// Finally clear our cache for other services.
		$transient_key = 'learndash_group_leaders_' . $group_id;
		LDLMS_Transients::delete( $transient_key );
	}
}

/**
 * Gets the list of groups associated with the course step.
 *
 * @since 3.1.8
 *
 * @param int $step_id Course Step ID. Required.
 *
 * @return array An array of group IDs associated with the course step.
 */
function learndash_get_course_step_groups( $step_id = 0 ) {
	$step_group_ids = array();

	$step_id = absint( $step_id );
	if ( ! empty( $step_id ) ) {
		$step_courses = learndash_get_courses_for_step( $step_id, true );
		if ( ! empty( $step_courses ) ) {
			foreach ( array_keys( $step_courses ) as $course_id ) {
				$step_group_ids = array_merge( $step_group_ids, learndash_get_course_groups( $course_id ) );
			}
		}
	}

	if ( ! empty( $step_group_ids ) ) {
		$step_group_ids = array_unique( $step_group_ids );
	}

	return $step_group_ids;
}

/**
 * Get all Users within all Groups managed by the Group Leader.
 *
 * @since   3.1.8
 *
 * @param  integer $group_leader_id  WP_User ID.
 * @return array WP_User IDs
 */
function learndash_get_groups_administrators_users( $group_leader_id = 0 ) {
	$user_ids = array();

	$group_leader_id = absint( $group_leader_id );
	if ( ! empty( $group_leader_id ) ) {
		// Get all the Group IDs of Groups they Manage.
		$group_ids = learndash_get_administrators_group_ids( $group_leader_id );
		if ( ! empty( $group_ids ) ) {
			foreach ( $group_ids as $group_id ) {
				// Get all the User IDs belonging to their Groups.
				$user_ids = array_merge( $user_ids, learndash_get_groups_user_ids( $group_id ) );
			}
		}
	}

	// Remove any overlap.
	if ( ! empty( $user_ids ) ) {
		$user_ids = array_unique( $user_ids );
	}

	return $user_ids;
}

/**
 * Get all Courses within all Groups managed by the Group Leader.
 *
 * @since   3.1.8
 *
 * @param integer $group_leader_id WP_User ID.
 * @return array Array of WP_Post Course IDs.
 */
function learndash_get_groups_administrators_courses( $group_leader_id = 0 ) {
	$course_ids = array();

	$group_leader_id = absint( $group_leader_id );
	if ( ! empty( $group_leader_id ) ) {
		// Get all the Group IDs of Groups they Manage.
		$group_ids = learndash_get_administrators_group_ids( $group_leader_id );
		if ( ! empty( $group_ids ) ) {
			foreach ( $group_ids as $group_id ) {
				// Get all the User IDs belonging to their Groups.
				$course_ids = array_merge( $course_ids, learndash_group_enrolled_courses( $group_id ) );
			}
		}
	}

	// Remove any overlap.
	if ( ! empty( $course_ids ) ) {
		$course_ids = array_unique( $course_ids );
	}

	return $course_ids;
}

/**
 * Get the Group Leader user for a specific Course step.
 *
 * @since 3.1.8
 *
 * @param integer $step_id         Course Step Post ID.
 * @param integer $group_leader_id Group Leader User ID. Optional.
 * @return array of user IDs.
 */
function learndash_get_groups_leaders_users_for_course_step( $step_id = 0, $group_leader_id = 0 ) {
	$user_ids = array();

	$step_id = absint( $step_id );

	if ( empty( $group_leader_id ) ) {
		$group_leader_id = get_current_user_id();
		if ( ! learndash_is_group_leader_user( $group_leader_id ) ) {
			$group_leader_id = 0;
		}
	}

	if ( ( ! empty( $step_id ) ) && ( ! empty( $group_leader_id ) ) ) {
		$gl_groups = learndash_get_administrators_group_ids( $group_leader_id );
		if ( ! empty( $gl_groups ) ) {
			$step_groups = learndash_get_course_step_groups( $step_id );
			$gl_groups   = array_intersect( $gl_groups, $step_groups );
		}

		if ( ! empty( $gl_groups ) ) {
			foreach ( $gl_groups as $group_id ) {
				$user_ids = array_merge( $user_ids, learndash_get_groups_user_ids( $group_id ) );
			}
		}
	}

	return $user_ids;
}

/**
 * Filter Quiz Statistics user listing to show only related users.
 *
 * @since 3.1.8
 *
 * @param string $where Statistics WHERE clause string.
 * @param array  $args  Array of query args.
 * @return string $where
 */
function learndash_fetch_quiz_statistic_history_where_filter( $where = '', $args = array() ) {

	if ( ! learndash_is_admin_user( get_current_user_id() ) ) {

		if ( learndash_is_group_leader_user( get_current_user_id() ) ) {
			$group_user_ids = array();
			if ( ( isset( $args['quiz'] ) ) && ( ! empty( $args['quiz'] ) ) ) {
				$group_user_ids = learndash_get_groups_leaders_users_for_course_step( $args['quiz'], get_current_user_id() );
			} else {
				$group_user_ids = learndash_get_groups_administrators_users( get_current_user_id() );
			}

			if ( ! empty( $group_user_ids ) ) {
				$where .= ' AND user_id IN (' . implode( ',', $group_user_ids ) . ') ';
			} else {
				$where .= ' AND user_id = -1 ';
			}
		} else {
			$where .= ' AND user_id =' . get_current_user_id() . ' ';
		}
	}

	// Always return $where.
	return $where;
}
add_filter( 'learndash_fetch_quiz_statistic_history_where', 'learndash_fetch_quiz_statistic_history_where_filter', 10, 2 );
add_filter( 'learndash_fetch_quiz_toplist_history_where', 'learndash_fetch_quiz_statistic_history_where_filter', 10, 2 );
add_filter( 'learndash_fetch_quiz_statistic_overview_where', 'learndash_fetch_quiz_statistic_history_where_filter', 10, 2 );


/**
 * Checks if a user has the group leader capabilities.
 *
 * Replaces the `is_group_leader` function.
 *
 * @since 2.3.9
 *
 * @param int|WP_User $user Optional. The `WP_User` object or user ID to check. Default 0.
 *
 * @return boolean Returns true if the user is group leader otherwise false.
 */
function learndash_is_group_leader_user( $user = 0 ) {
	$user_id = 0;

	if ( ( is_numeric( $user ) ) && ( ! empty( $user ) ) ) {
		$user_id = $user;
	} elseif ( $user instanceof WP_User ) {
		$user_id = $user->ID;
	} else {
		$user_id = get_current_user_id();
	}

	if ( ( ! empty( $user_id ) ) && ( ! learndash_is_admin_user( $user_id ) ) && ( defined( 'LEARNDASH_GROUP_LEADER_CAPABILITY_CHECK' ) ) && ( LEARNDASH_GROUP_LEADER_CAPABILITY_CHECK != '' ) ) {
		return user_can( $user_id, LEARNDASH_GROUP_LEADER_CAPABILITY_CHECK );
	}

	return false;
}

/**
 * Checks if a user has the admin capabilities.
 *
 * @param int|WP_User $user Optional. The `WP_User` object or user ID to check. Default 0.
 *
 * @return boolean Returns true if the user is admin otherwise false.
 */
function learndash_is_admin_user( $user = 0 ) {
	$user_id = 0;

	if ( ( is_numeric( $user ) ) && ( ! empty( $user ) ) ) {
		$user_id = $user;
	} elseif ( $user instanceof WP_User ) {
		$user_id = $user->ID;
	} else {
		$user_id = get_current_user_id();
	}

	if ( ( ! empty( $user_id ) ) && ( defined( 'LEARNDASH_ADMIN_CAPABILITY_CHECK' ) ) && ( LEARNDASH_ADMIN_CAPABILITY_CHECK != '' ) ) {
		return user_can( $user_id, LEARNDASH_ADMIN_CAPABILITY_CHECK );
	}

	return false;
}

/**
 * Checks whether a group leader is an admin of a user's group.
 *
 * @since 2.1.0
 *
 * @param int $group_leader_id Group leader ID.
 * @param int $user_id         User ID.
 *
 * @return boolean Returns true if group leader is an admin of a user's group otherwise false.
 */
function learndash_is_group_leader_of_user( $group_leader_id = 0, $user_id = 0 ) {
	$group_leader_id = absint( $group_leader_id );
	$user_id         = absint( $user_id );

	$admin_groups     = learndash_get_administrators_group_ids( $group_leader_id );
	$has_admin_groups = ! empty( $admin_groups ) && is_array( $admin_groups ) && ! empty( $admin_groups[0] );

	foreach ( $admin_groups as $group_id ) {
		$learndash_is_user_in_group = learndash_is_user_in_group( $user_id, $group_id );

		if ( $learndash_is_user_in_group ) {
			return true;
		}
	}

	return false;
}



/**
 * Checks whether a user is part of the group or not.
 *
 * @since 2.1.0
 *
 * @param int $user_id  User ID.
 * @param int $group_id Group ID.
 *
 * @return boolean Returns true if the user is part of the group otherwise false.
 */
function learndash_is_user_in_group( $user_id = 0, $group_id = 0 ) {
	$user_id  = absint( $user_id );
	$group_id = absint( $group_id );
	if ( ( ! empty( $user_id ) ) && ( ! empty( $group_id ) ) ) {
		if ( learndash_is_groups_hierarchical_enabled() ) {
			$group_ids = learndash_get_users_group_ids( $user_id );
			if ( in_array( $group_id, $group_ids, true ) ) {
				return true;
			}
		} else {
			return get_user_meta( $user_id, 'learndash_group_users_' . $group_id, true );
		}
	}

	return false;
}

/**
 * Deletes group ID from all users meta when the group is deleted.
 *
 * Fires on `delete_post` hook.
 *
 * @todo  restrict function to only run if post type is group
 *        will run against db every time a post is deleted
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @since 2.1.0
 *
 * @param int $pid ID of the group being deleted.
 *
 * @return boolean|void Returns true if the deletion was successful.
 */
function learndash_delete_group( $pid = 0 ) {
	global $wpdb;

	$pid = absint( $pid );
	if ( ! empty( $pid ) && is_numeric( $pid ) ) {
		$wpdb->delete(
			$wpdb->usermeta,
			array(
				'meta_key'   => 'learndash_group_users_' . $pid,
				'meta_value' => $pid,
			)
		);
		$wpdb->delete(
			$wpdb->usermeta,
			array(
				'meta_key'   => 'learndash_group_leaders_' . $pid,
				'meta_value' => $pid,
			)
		);
	}

	return true;
}

add_action( 'delete_post', 'learndash_delete_group', 10 );


/**
 * Updates a user's group access.
 *
 * @since 2.1.0
 * @since 3.4.0 Added return boolean.
 *
 * @param int     $user_id  User ID.
 * @param int     $group_id Group ID.
 * @param boolean $remove   Optional. Whether to remove user from the group. Default false.
 *
 * @return bool true on action success otherwise false.
 */
function ld_update_group_access( $user_id = 0, $group_id = 0, $remove = false ): bool {
	$action_success = false;

	$user_id  = absint( $user_id );
	$group_id = absint( $group_id );

	if ( ! empty( $user_id ) && ! empty( $group_id ) ) {
		if ( $remove ) {
			$user_enrolled = get_user_meta( $user_id, 'learndash_group_users_' . $group_id, true );

			if ( $user_enrolled ) {
				$action_success = true;

				delete_user_meta( $user_id, 'learndash_group_users_' . $group_id );
				// we don't delete the group enrollment date because it is used in reports.
				delete_user_meta( $user_id, 'group_' . $group_id . '_access_from' );

				/**
				 * If the user is removed from the course then also remove the group_progress Activity.
				 */
				$group_user_activity_args = array(
					'activity_type' => 'group_progress',
					'user_id'       => $user_id,
					'post_id'       => $group_id,
					'course_id'     => 0,
				);

				$group_user_activity = learndash_get_user_activity( $group_user_activity_args );
				if ( is_object( $group_user_activity ) ) {
					learndash_delete_user_activity( $group_user_activity->activity_id );
				}

				/**
				 * Fires after the user is removed from group access meta.
				 *
				 * @since 2.1.0
				 *
				 * @param int $user_id  User ID.
				 * @param int $group_id Group ID.
				 */
				do_action( 'ld_removed_group_access', $user_id, $group_id );
			}
		} else {
			$user_enrolled = get_user_meta( $user_id, 'learndash_group_users_' . $group_id, true );

			if ( ! $user_enrolled ) {
				$action_success = true;

				update_user_meta( $user_id, 'learndash_group_users_' . $group_id, $group_id );
				update_user_meta( $user_id, 'learndash_group_' . $group_id . '_enrolled_at', time() );

				// Set the course access time to the course start date if it exists.
				// We need that to avoid issues with content dripping in the future and keep it consistent with a course access meta.

				$product = Product::find( $group_id );

				update_user_meta(
					$user_id,
					'group_' . $group_id . '_access_from',
					$product && $product->get_start_date() ? $product->get_start_date() : time()
				);

				/**
				 * Fires after the user is added to group access meta.
				 *
				 * @since 2.1.0
				 *
				 * @param int $user_id  User ID.
				 * @param int $group_id Group ID.
				 */
				do_action( 'ld_added_group_access', $user_id, $group_id );
			}
		}

		// Purge User Groups cache.
		$transient_key = 'learndash_user_groups_' . $user_id;
		LDLMS_Transients::delete( $transient_key );

		// Purge User Courses cache.
		$transient_key = 'learndash_user_courses_' . $user_id;
		LDLMS_Transients::delete( $transient_key );

	}

	return $action_success;
}


/**
 * Updates the course group access.
 *
 * @since 2.1.0
 * @since 3.4.0 Added return boolean.
 *
 * @param int     $course_id Course ID.
 * @param int     $group_id  Group ID.
 * @param boolean $remove    Optional. Whether to remove the group from the course. Default false.
 *
 * @return boolean true on action success otherwise false.
 */
function ld_update_course_group_access( $course_id = 0, $group_id = 0, $remove = false ) {
	$action_success = false;

	$course_id = absint( $course_id );
	$group_id  = absint( $group_id );

	if ( ( ! empty( $course_id ) ) && ( ! empty( $group_id ) ) ) {
		$activity_type = 'group_access_course';

		if ( $remove ) {
			$group_enrolled = get_post_meta( $course_id, 'learndash_group_enrolled_' . $group_id, true );
			if ( $group_enrolled ) {
				$action_success = true;
				delete_post_meta( $course_id, 'learndash_group_enrolled_' . $group_id );

				/**
				 * Fires after the user is removed from the course group meta.
				 *
				 * @since 2.1.0
				 *
				 * @param int $user_id  User ID.
				 * @param int $group_id Group ID.
				 */
				do_action( 'ld_removed_course_group_access', $course_id, $group_id );
			}
		} else {
			$group_enrolled = get_post_meta( $course_id, 'learndash_group_enrolled_' . $group_id, true );
			if ( empty( $group_enrolled ) ) {
				$action_success = true;
				update_post_meta( $course_id, 'learndash_group_enrolled_' . $group_id, time() );

				/**
				 * Fires after the user is added to the course group access meta.
				 *
				 * @since 2.1.0
				 *
				 * @param int $user_id  User ID.
				 * @param int $group_id Group ID.
				 */
				do_action( 'ld_added_course_group_access', $course_id, $group_id );
			}
		}
	}

	return $action_success;
}


/**
 * Updates the group access for a group leader.
 *
 * @since 2.2.1
 * @since 3.4.0 Added return boolean.
 *
 * @param int  $user_id       User ID.
 * @param int  $group_id      Group ID.
 * @param bool $remove_access Optional. Whether to remove user from the group. Default false.
 *
 * @return bool True on action success, otherwise false.
 */
function ld_update_leader_group_access( int $user_id, int $group_id, bool $remove_access = false ): bool {
	if ( empty( $user_id ) || empty( $group_id ) ) {
		return false;
	}

	$group_leader_meta_key = 'learndash_group_leaders_' . $group_id;
	$has_group_leader_meta = ! empty( get_user_meta( $user_id, $group_leader_meta_key, true ) );

	// Adding (not updating, updating always returns false).

	if ( ! $remove_access && ! $has_group_leader_meta ) {
		update_user_meta( $user_id, $group_leader_meta_key, $group_id );

		/**
		 * Fires after the user is added to the group as a leader.
		 *
		 * @since 2.1.0
		 *
		 * @param int $user_id  User ID.
		 * @param int $group_id Group ID.
		 */
		do_action( 'ld_added_leader_group_access', $user_id, $group_id );

		return true;
	}

	// Removing.

	if ( $remove_access && $has_group_leader_meta ) {
		delete_user_meta( $user_id, $group_leader_meta_key );

		/**
		 * Fires after the user is removed from a group as a leader.
		 *
		 * @since 2.1.0
		 *
		 * @param int $user_id  User ID.
		 * @param int $group_id Group ID.
		 */
		do_action( 'ld_removed_leader_group_access', $user_id, $group_id );

		return true;
	}

	return false;
}

/**
 * Gets the group's user IDs if the course is associated with the group.
 *
 * @since 2.3.0
 *
 * @param int $course_id Optional. Course ID. Default 0.
 *
 * @return array An array of user IDs.
 */
function learndash_get_course_groups_users_access( $course_id = 0 ) {
	$user_ids = array();

	$course_id = absint( $course_id );
	if ( ! empty( $course_id ) ) {
		$course_groups = learndash_get_course_groups( $course_id );
		if ( ( is_array( $course_groups ) ) && ( ! empty( $course_groups ) ) ) {
			foreach ( $course_groups as $group_id ) {
				$group_users_ids = learndash_get_groups_user_ids( $group_id );
				if ( ! empty( $group_users_ids ) ) {
					$user_ids = array_merge( $user_ids, $group_users_ids );
				}
			}
		}
	}

	if ( ! empty( $user_ids ) ) {
		$user_ids = array_unique( $user_ids );
	}

	return $user_ids;
}

/**
 * Gets all quizzes related to Group Courses.
 *
 * Given a group ID will determine all quizzes associated with courses of the group
 *
 * @since 2.3.0
 *
 * @param int $group_id Optional. Group ID. Default 0.
 *
 * @return array An array of quiz IDs.
 */
function learndash_get_group_course_quiz_ids( $group_id = 0 ) {
	$group_quiz_ids = array();

	$group_id = absint( $group_id );
	if ( ! empty( $group_id ) ) {
		$group_course_ids = learndash_group_enrolled_courses( intval( $group_id ) );
		if ( ! empty( $group_course_ids ) ) {
			foreach ( $group_course_ids as $course_id ) {
				$group_quiz_query_args = array(
					'post_type'  => 'sfwd-quiz',
					'nopaging'   => true,
					'fields'     => 'ids',
					'meta_query' => array(
						'relation' => 'OR',
						array(
							'key'     => 'course_id',
							'value'   => $course_id,
							'compare' => '=',
						),
						array(
							'key'     => 'ld_course_' . $course_id,
							'value'   => $course_id,
							'compare' => '=',
						),
					),
				);

				$group_quiz_query = new WP_Query( $group_quiz_query_args );
				if ( ! empty( $group_quiz_query->posts ) ) {
					$group_quiz_ids = array_merge( $group_quiz_ids, $group_quiz_query->posts );
					$group_quiz_ids = array_unique( $group_quiz_ids );
				}
			}
		}
	}

	return $group_quiz_ids;
}

/**
 * Check and recalculate the the status of the Group Courses for the User.
 *
 * @since 3.2.0
 *
 * @param integer $group_id Group ID to check.
 * @param integer $user_id  User ID to check.
 * @param boolean $recalc   Force the logic to recheck all courses.
 */
function learndash_get_user_group_progress( $group_id = 0, $user_id = 0, $recalc = false ) {
	static $progress_group_user = array();

	$group_id = absint( $group_id );
	$user_id  = absint( $user_id );

	if ( empty( $user_id ) ) {
		if ( is_user_logged_in() ) {
			$user_id = get_current_user_id();
		}
	}

	if ( ( empty( $group_id ) ) || ( empty( $user_id ) ) ) {
		return array();
	}

	if ( ! learndash_is_user_in_group( $user_id, $group_id ) ) {
		return array();
	}

	if ( ( isset( $progress_group_user[ $group_id ][ $user_id ] ) ) && ( ! empty( $progress_group_user[ $group_id ][ $user_id ] ) ) && ( true !== $recalc ) ) {
		return $progress_group_user[ $group_id ][ $user_id ];
	}

	$progress = array(
		'percentage'      => 0,
		'in-progress'     => 0,
		'not-started'     => 0,
		'completed'       => 0,
		'total'           => 0,
		'completed_on'    => 0,
		'started_on'      => 0,
		'course_ids'      => array(),
		'course_activity' => array(),
		'activity_id'     => 0,
		'group_activity'  => array(),
	);

	$group_user_activity_args = array(
		'activity_type' => 'group_progress',
		'user_id'       => $user_id,
		'post_id'       => $group_id,
		'course_id'     => 0,
	);

	$group_user_activity = learndash_get_user_activity( $group_user_activity_args );
	if ( is_object( $group_user_activity ) ) {
		$group_user_activity = json_decode( wp_json_encode( $group_user_activity ), true );
		if ( ( true === $group_user_activity['activity_status'] ) && ( true !== $recalc ) ) {
			$activity_meta = learndash_get_user_activity_meta( $group_user_activity['activity_id'] );
			if ( ( $activity_meta ) && ( ! empty( $activity_meta ) ) ) {

				foreach ( $activity_meta as $activity_set ) {
					if ( ( property_exists( $activity_set, 'activity_meta_key' ) ) && ( ! empty( $activity_set->activity_meta_key ) ) ) {
						if ( property_exists( $activity_set, 'activity_meta_value' ) ) {
							$meta[ $activity_set->activity_meta_key ] = $activity_set->activity_meta_value;
						} else {
							$meta[ $activity_set->activity_meta_key ] = '';
						}
					}
				}

				foreach ( $progress as $key => $val ) {
					switch ( $key ) {
						case 'percentage':
						case 'in-progress':
						case 'not-started':
						case 'completed':
						case 'total':
						case 'completed_on':
						case 'started_on':
							if ( isset( $meta[ $key ] ) ) {
								$progress[ $key ] = intval( $meta[ $key ] );
							}
							break;

						case 'group_activity':
							$progress[ $key ] = $group_user_activity;
							break;

						case 'activity_id':
							if ( isset( $group_user_activity['activity_id'] ) ) {
								$progress[ $key ] = absint( $group_user_activity['activity_id'] );
							}
							break;

						case 'course_ids':
						case 'course_activity':
						default:
							break;
					}
				}

				$progress_group_user[ $group_id ][ $user_id ] = $progress;
				return $progress;
			}
		}
	} else {
		$group_user_activity                    = $group_user_activity_args;
		$group_user_activity['changed']         = true;
		$group_user_activity['activity_status'] = 0;
	}

	$last_completed_course_time = 0;
	$last_started_course_time   = 0;
	$last_updated_course_time   = 0;

	$progress['course_ids'] = learndash_group_enrolled_courses( $group_id );
	if ( ! empty( $progress['course_ids'] ) ) {
		$progress['course_ids'] = array_map( 'absint', $progress['course_ids'] );
		$progress['total']      = count( $progress['course_ids'] );

		$group_courses_activity_args = array(
			'user_ids'       => $user_id,
			'post_types'     => learndash_get_post_type_slug( 'course' ),
			'activity_types' => 'course',
			'course_ids'     => $progress['course_ids'],
			'per_page'       => '',
		);

		$group_courses_activity = learndash_reports_get_activity( $group_courses_activity_args );
		if ( ( isset( $group_courses_activity['results'] ) ) && ( ! empty( $group_courses_activity['results'] ) ) ) {
			$progress['course_activity'] = array();
			foreach ( $group_courses_activity['results'] as $result ) {
				$result->activity_status    = absint( $result->activity_status );
				$result->activity_completed = absint( $result->activity_completed );
				$result->activity_started   = absint( $result->activity_started );
				$result->activity_updated   = absint( $result->activity_updated );

				$progress['course_activity'][ $result->activity_course_id ] = json_decode( wp_json_encode( $result ), true );

				if ( ( empty( $result->activity_started ) ) && ( ! empty( $result->activity_updated ) ) ) {
					$result->activity_started = $result->activity_updated;
				}

				if ( ( empty( $last_started_course_time ) ) || ( $result->activity_started < $last_started_course_time ) ) {
					$last_started_course_time = $result->activity_started;
				}

				if ( ( empty( $last_updated_course_time ) ) || ( $result->activity_updated < $last_updated_course_time ) ) {
					$last_updated_course_time = $result->activity_updated;
				}

				if ( ( 1 === $result->activity_status ) && ( ! empty( $result->activity_completed ) ) ) {
					$progress['completed']++;

					if ( $result->activity_completed > $last_completed_course_time ) {
						$last_completed_course_time = $result->activity_completed;
					}
				} elseif ( ! empty( $result->activity_started ) ) {
					$progress['in-progress']++;
				}
			}
		}
	}

	$progress['completed']   = absint( $progress['completed'] );
	$progress['total']       = absint( $progress['total'] );
	$progress['in-progress'] = absint( $progress['in-progress'] );
	$progress['not-started'] = $progress['total'] - $progress['completed'] - $progress['in-progress'];

	if ( ( ! empty( $progress['total'] ) ) && ( ! empty( $progress['completed'] ) ) ) {
		$progress['percentage'] = ceil( ( $progress['completed'] / $progress['total'] ) * 100 );
	} else {
		$progress['percentage'] = 0;
	}

	// Fire the Group Completed action. But after we add the activity record.
	$send_group_complete_action = false;

	if ( ( ! empty( $progress['total'] ) ) && ( $progress['total'] === $progress['completed'] ) ) {
		if ( true !== $group_user_activity['activity_status'] ) {
			$send_group_complete_action = true;
		}

		$group_user_activity['activity_status']    = true;
		$group_user_activity['activity_completed'] = absint( $last_completed_course_time );
		$progress['completed_on']                  = absint( $last_completed_course_time );
	} else {
		$group_user_activity['activity_status']    = false;
		$group_user_activity['activity_completed'] = 0;
	}

	$group_user_activity['activity_started'] = absint( $last_started_course_time );
	$progress['started_on']                  = absint( $last_started_course_time );

	$group_user_activity['activity_updated'] = absint( $last_updated_course_time );

	$group_user_activity['activity_meta'] = $progress;
	unset( $group_user_activity['activity_meta']['course_activity'] );
	unset( $group_user_activity['activity_meta']['group_activity'] );

	$progress['activity_id'] = learndash_update_user_activity( $group_user_activity );

	if ( true === $send_group_complete_action ) {
		/**
		 *
		 * Fires after the group is completed.
		 *
		 * @param array $group_data An array of group complete data.
		 */
		do_action(
			'learndash_group_completed',
			array(
				'user'            => get_user_by( 'id', $user_id ),
				'group'           => get_post( $group_id ),
				'progress'        => $progress,
				'group_completed' => $group_user_activity['activity_completed'],
			)
		);
	}

	$progress_group_user[ $group_id ][ $user_id ] = $progress;

	return $progress;
}

/**
 * Get User's group status
 *
 * @since 3.2.0
 *
 * @param int  $group_id Group ID.
 * @param int  $user_id  User ID.
 * @param bool $return_slug Optional. Default false.
 */
function learndash_get_user_group_status( $group_id = 0, $user_id = 0, $return_slug = false ) {
	$learndash_group_status_str = '';

	$group_id = absint( $group_id );
	$user_id  = absint( $user_id );

	if ( empty( $user_id ) ) {
		if ( ! is_user_logged_in() ) {
			return $learndash_group_status_str;
		}

		$user_id = get_current_user_id();
	} else {
		$user_id = absint( $user_id );
	}

	if ( ( empty( $group_id ) ) || ( empty( $user_id ) ) ) {
		return '';
	}

	$progress = learndash_get_user_group_progress( $group_id, $user_id );
	if ( ( ! empty( $progress ) ) && ( is_array( $progress ) ) && ( isset( $progress['percentage'] ) ) ) {
		if ( 100 === absint( $progress['percentage'] ) ) {
			if ( true === $return_slug ) {
				$learndash_group_status_str = 'completed';
			} else {
				$learndash_group_status_str = esc_html__( 'Completed', 'learndash' );
			}
		} elseif ( $progress['in-progress'] > 0 ) {
			if ( true === $return_slug ) {
				$learndash_group_status_str = 'in-progress';
			} else {
				$learndash_group_status_str = esc_html__( 'In Progress', 'learndash' );
			}
		}
	}

	if ( empty( $learndash_group_status_str ) ) {
		if ( true === $return_slug ) {
			$learndash_group_status_str = 'not-started';
		} else {
			$learndash_group_status_str = esc_html__( 'Not Started', 'learndash' );
		}
	}

	return $learndash_group_status_str;
}

/**
 * Get the user started group timestamp.
 *
 * @since 3.2.0
 *
 * @param  integer $group_id Group ID to check.
 * @param  integer $user_id  User ID to check.
 * @return integer time user started group courses.
 */
function learndash_get_user_group_started_timestamp( $group_id = 0, $user_id = 0 ) {
	$group_timestamp = 0;

	$group_id = absint( $group_id );
	$user_id  = absint( $user_id );

	if ( empty( $user_id ) ) {
		if ( ! is_user_logged_in() ) {
			return $group_timestamp;
		}

		$user_id = get_current_user_id();
	} else {
		$user_id = absint( $user_id );
	}

	if ( ( empty( $group_id ) ) || ( empty( $user_id ) ) ) {
		return '';
	}

	$progress = learndash_get_user_group_progress( $group_id, $user_id );
	if ( ( ! empty( $progress ) ) && ( is_array( $progress ) ) && ( isset( $progress['started_on'] ) ) ) {
		$group_timestamp = absint( $progress['started_on'] );
	}

	return $group_timestamp;
}

/**
 * Get the user completed group timestamp.
 *
 * @since 3.2.0
 *
 * @param  integer $group_id Group ID to check.
 * @param  integer $user_id  User ID to check.
 * @return integer time user started group courses.
 */
function learndash_get_user_group_completed_timestamp( $group_id = 0, $user_id = 0 ) {
	$group_timestamp = 0;

	$group_id = absint( $group_id );
	$user_id  = absint( $user_id );

	if ( empty( $user_id ) ) {
		if ( ! is_user_logged_in() ) {
			return $group_timestamp;
		}

		$user_id = get_current_user_id();
	} else {
		$user_id = absint( $user_id );
	}

	if ( ( empty( $group_id ) ) || ( empty( $user_id ) ) ) {
		return '';
	}

	$progress = learndash_get_user_group_progress( $group_id, $user_id );
	if ( ( ! empty( $progress ) ) && ( is_array( $progress ) ) && ( isset( $progress['completed_on'] ) ) ) {
		$group_timestamp = absint( $progress['completed_on'] );
	}

	return $group_timestamp;
}

/**
 * Get the user completed group percentage.
 *
 * @since 3.2.0
 *
 * @param  integer $group_id Group ID to check.
 * @param  integer $user_id  User ID to check.
 * @return integer time user started group courses.
 */
function learndash_get_user_group_completed_percentage( $group_id = 0, $user_id = 0 ) {
	$group_percentage = 0;

	$group_id = absint( $group_id );
	$user_id  = absint( $user_id );

	if ( empty( $user_id ) ) {
		if ( ! is_user_logged_in() ) {
			return $group_percentage;
		}

		$user_id = get_current_user_id();
	} else {
		$user_id = absint( $user_id );
	}

	if ( ( empty( $group_id ) ) || ( empty( $user_id ) ) ) {
		return '';
	}

	$progress = learndash_get_user_group_progress( $group_id, $user_id );
	if ( ( ! empty( $progress ) ) && ( is_array( $progress ) ) && ( isset( $progress['percentage'] ) ) ) {
		$group_percentage = $progress['percentage'];
	}

	return $group_percentage;
}

/**
 * Hook into the User Course Complete action.
 *
 * When the user completes a Course we check if that course
 * is part of any group the user is enrolled into.
 *
 * @since 3.2.0
 *
 * @param array $course_data Array of course data.
 */
function learndash_group_course_completed( $course_data = array() ) {

	if ( ( isset( $course_data['course'] ) ) && ( isset( $course_data['user'] ) ) ) {
		learndash_update_group_course_user_progress( $course_data['course']->ID, $course_data['user']->ID, true );
	}
}
add_action( 'learndash_course_completed', 'learndash_group_course_completed', 30, 1 );


/**
 * Update Group User Course progress.
 *
 * @since 3.2.0
 *
 * @param integer $course_id Course ID.
 * @param integer $user_id   User ID.
 * @param boolean $recalc    Force the logic to recheck all courses.
 */
function learndash_update_group_course_user_progress( $course_id = 0, $user_id = 0, $recalc = false ) {
	$course_id = absint( $course_id );
	$user_id   = absint( $user_id );

	if ( ( ! empty( $user_id ) ) && ( ! empty( $course_id ) ) ) {
		$user_group_ids = learndash_get_users_group_ids( $user_id );
		if ( empty( $user_group_ids ) ) {
			return;
		}

		$course_group_ids = learndash_get_course_groups( $course_id );
		if ( empty( $course_group_ids ) ) {
			return;
		}

		$group_ids = array_intersect( $user_group_ids, $course_group_ids );
		if ( ! empty( $group_ids ) ) {
			foreach ( $group_ids as $group_id ) {
				learndash_get_user_group_progress( $group_id, $user_id, $recalc );
			}
		}
	}
}

/**
 * Utility function to return all groups below the parent.
 *
 * @since 3.2.0
 *
 * @param integer $group_id Group parent ID.
 * @return array of children groups IDs.
 */
function learndash_get_group_children( $group_id = 0 ) {
	$group_children = array();

	$group_id = absint( $group_id );
	if ( ! empty( $group_id ) ) {

		$child_args = array(
			'post_parent' => $group_id, // The parent id.
			'post_type'   => learndash_get_post_type_slug( 'group' ),
		);

		$children = get_children( $child_args );
		if ( ! empty( $children ) ) {
			foreach ( $children as $child_group ) {
				$group_children[] = $child_group->ID;
				$children2        = learndash_get_group_children( $child_group->ID );
				if ( ! empty( $children2 ) ) {
					$group_children = array_merge( $group_children, $children2 );
				}
			}
		}
	}

	if ( ! empty( $group_children ) ) {
		$group_children = array_map( 'absint', $group_children );
		$group_children = array_unique( $group_children, SORT_NUMERIC );
	}

	return $group_children;
}

/**
 * Validate an array of Group post IDs.
 *
 * @param array $group_ids Array of Groups post IDs to check.
 * @return array validated Group post IDS.
 */
function learndash_validate_groups( $group_ids = array() ) {
	if ( ( is_array( $group_ids ) ) && ( ! empty( $group_ids ) ) ) {
		$groups_query_args = array(
			'post_type'      => learndash_get_post_type_slug( 'group' ),
			'fields'         => 'ids',
			'orderby'        => 'title',
			'order'          => 'ASC',
			'post__in'       => $group_ids,
			'posts_per_page' => -1,
		);

		$groups_query = new WP_Query( $groups_query_args );
		if ( ( is_a( $groups_query, 'WP_Query' ) ) && ( property_exists( $groups_query, 'posts' ) ) ) {
			return $groups_query->posts;
		}
	}

	return array();
}

/**
 * Gets the group courses per page setting.
 *
 * @since 3.2.0
 *
 * @param int $group_id Optional. The ID of the group. Default 0.
 *
 * @return int The number of courses per page or 0.
 */
function learndash_get_group_courses_per_page( $group_id = 0 ) {
	$group_courses_per_page = 0;

	// From the WP > Settings > Reading > Posts per page.
	$group_courses_per_page = (int) get_option( 'posts_per_page' );

	// From the LearnDash > Settings > General > Global Pagination Settings > Shortcodes & Widgets per page.
	$group_courses_per_page = LearnDash_Settings_Section::get_section_setting( 'LearnDash_Settings_Section_General_Per_Page', 'per_page', $group_courses_per_page );

	// From the LearnDash > Courses > Settings > Global Group Management > Group Table Pagination > Courses per page.
	$group_global_settings = LearnDash_Settings_Section::get_section_settings_all( 'LearnDash_Settings_Groups_Management_Display' );
	if ( ( isset( $group_global_settings['group_pagination_enabled'] ) ) && ( 'yes' === $group_global_settings['group_pagination_enabled'] ) ) {
		if ( isset( $group_global_settings['group_pagination_courses'] ) ) {
			$group_courses_per_page = absint( $group_global_settings['group_pagination_courses'] );
		} else {
			$group_courses_per_page = LEARNDASH_LMS_DEFAULT_WIDGET_PER_PAGE;
		}
	} else {
		$group_courses_per_page = LEARNDASH_LMS_DEFAULT_WIDGET_PER_PAGE;
	}

	if ( ! empty( $group_id ) ) {
		$group_settings = learndash_get_setting( intval( $group_id ) );

		if ( ( isset( $group_settings['group_courses_per_page_enabled'] ) ) && ( 'CUSTOM' === $group_settings['group_courses_per_page_enabled'] ) && ( isset( $group_settings['group_courses_per_page_custom'] ) ) ) {
			$group_courses_per_page = absint( $group_settings['group_courses_per_page_custom'] );
		}
	}

	/**
	 * Filters group courses per page.
	 *
	 * @since 3.2.0
	 *
	 * @param int $group_courses_per_page Per page value.
	 * @param int $group_id               Group ID.
	 */
	return apply_filters( 'learndash_group_courses_per_page', $group_courses_per_page, $group_id );
}

/**
 * Gets the group courses order query arguments.
 *
 * @since 3.2.0
 *
 * @param int $group_id Optional. The ID of the group. Default 0.
 *
 * @return array An array of group courses order query arguments.
 */
function learndash_get_group_courses_order( $group_id = 0 ) {
	$group_courses_args = array(
		'order'   => LEARNDASH_DEFAULT_GROUP_ORDER,
		'orderby' => LEARNDASH_DEFAULT_GROUP_ORDERBY,
	);

	$group_global_settings = LearnDash_Settings_Section::get_section_settings_all( 'LearnDash_Settings_Groups_Management_Display' );
	if ( ( isset( $group_global_settings['group_courses_orderby'] ) ) && ( LEARNDASH_DEFAULT_GROUP_ORDERBY !== $group_global_settings['group_courses_orderby'] ) ) {
		$group_courses_args['orderby'] = esc_attr( $group_global_settings['group_courses_orderby'] );
	}
	if ( ( isset( $group_global_settings['group_courses_order'] ) ) && ( LEARNDASH_DEFAULT_GROUP_ORDER !== $group_global_settings['group_courses_order'] ) ) {
		$group_courses_args['order'] = esc_attr( $group_global_settings['group_courses_order'] );
	}

	if ( ! empty( $group_id ) ) {
		$group_settings = learndash_get_setting( $group_id );
		if ( ( isset( $group_settings['group_courses_order_enabled'] ) ) && ( 'on' === $group_settings['group_courses_order_enabled'] ) ) {
			if ( ( isset( $group_settings['group_courses_order'] ) ) && ( ! empty( $group_settings['group_courses_order'] ) ) ) {
				$group_courses_args['order'] = esc_attr( $group_settings['group_courses_order'] );
			}

			if ( ( isset( $group_settings['group_courses_orderby'] ) ) && ( ! empty( $group_settings['group_courses_orderby'] ) ) ) {
				$group_courses_args['orderby'] = esc_attr( $group_settings['group_courses_orderby'] );
			}
		}
	}

	/**
	 * Filters group courses order query arguments.
	 *
	 * @since 3.2.0
	 *
	 * @param array $group_courses_args An array of group courses order arguments.
	 * @param int   $group_id          Group ID.
	 */
	return apply_filters( 'learndash_group_courses_order', $group_courses_args, $group_id );
}


/**
 * Gets the list of enrolled courses for a group.
 *
 * @since 2.1.0
 * @since 4.0.0 Added `$query_args` parameter.
 *
 * @param int   $group_id   Optional. Group ID. Default 0.
 * @param array $query_args Optional. An array of query arguments to get lesson list. Default empty array. (@since 4.0.0).
 *
 * @return array An array of course IDs.
 */
function learndash_get_group_courses_list( $group_id = 0, $query_args = array() ) {
	global $course_pager_results;

	$course_ids = array();

	$group_id = absint( $group_id );
	if ( ! empty( $group_id ) ) {

		if ( ! isset( $query_args['paged'] ) ) {
			$query_args['paged'] = 1;
			if ( isset( $_GET['ld-group-courses-page'] ) ) {
				$query_args['paged'] = absint( $_GET['ld-group-courses-page'] );
			}
		}

		if ( isset( $query_args['num'] ) ) {
			$query_args['per_page'] = intval( $query_args['num'] );
			unset( $query_args['num'] );
		}

		if ( isset( $query_args['posts_per_page'] ) ) {
			if ( ( ! isset( $query_args['per_page'] ) ) || ( empty( $query_args['per_page'] ) ) ) {
				$query_args['per_page'] = intval( $query_args['posts_per_page'] );
			}
			unset( $query_args['posts_per_page'] );
		}

		if ( ! isset( $query_args['per_page'] ) ) {
			$query_args['per_page'] = learndash_get_group_courses_per_page( $group_id );
		}
		$group_courses_order_args = learndash_get_group_courses_order( $group_id );

		$offset = $query_args['offset'] ?? null;

		$query_args = array(
			'post_type'      => learndash_get_post_type_slug( 'course' ),
			'fields'         => 'ids',
			'posts_per_page' => $query_args['per_page'],
			'paged'          => $query_args['paged'],
			'meta_query'     => array(
				array(
					'key'     => 'learndash_group_enrolled_' . $group_id,
					'compare' => 'EXISTS',
				),
			),
		);

		if ( ! is_null( $offset ) ) {
			$query_args['offset'] = $offset;
		}

		$query_args = array_merge( $query_args, $group_courses_order_args );

		$query = new WP_Query( $query_args );
		if ( ( is_a( $query, 'WP_Query' ) ) && ( property_exists( $query, 'posts' ) ) ) {
			$course_ids = $query->posts;

			if ( ! isset( $course_pager_results['pager'] ) ) {
				$course_pager_results['pager'] = array();
			}
			$course_pager_results['pager']['paged']       = $query_args['paged'];
			$course_pager_results['pager']['total_items'] = $query->found_posts;
			$course_pager_results['pager']['total_pages'] = $query->max_num_pages;
		}
	}

	return $course_ids;
}

/**
 * Utility function to check if Groups post type is hierarchical.
 *
 * @since 3.2.1
 *
 * @return bool Returns true if hierarchical.
 */
function learndash_is_groups_hierarchical_enabled() {
	$group_hierarchical_enabled = LearnDash_Settings_Section::get_section_setting( 'LearnDash_Settings_Groups_Management_Display', 'group_hierarchical_enabled' );
	if ( 'yes' === $group_hierarchical_enabled ) {
		$group_hierarchical_enabled = true;
	} else {
		$group_hierarchical_enabled = false;
	}

	return $group_hierarchical_enabled;
}

/**
 * Get all Courses having Group associations.
 *
 * @since 3.2.3
 * @return array Array of Course ID or empty array.
 */
function learndash_get_all_courses_with_groups() {
	$query_args = array(
		'post_type'      => learndash_get_post_type_slug( 'course' ),
		'fields'         => 'ids',
		'posts_per_page' => -1,
		'meta_query'     => array(
			array(
				'key'     => '[LD_XXX_GROUP_LIKE_FILTER]',
				'compare' => 'EXISTS',
			),
		),
	);

	add_filter( 'posts_where', 'learndash_filter_by_group_where_filter' );
	$query = new WP_Query( $query_args );
	remove_filter( 'posts_where', 'learndash_filter_by_group_where_filter' );
	if ( ( is_a( $query, 'WP_Query' ) ) && ( property_exists( $query, 'posts' ) ) ) {
		return $query->posts;
	}

	return array();
}

/**
 * Filter by group WHERE filter
 *
 * @since 3.2.3
 *
 * @param string $where WHERE clause.
 */
function learndash_filter_by_group_where_filter( $where ) {
	if ( false !== strpos( $where, '[LD_XXX_GROUP_LIKE_FILTER]' ) ) {
		return str_replace( "meta_key = '[LD_XXX_GROUP_LIKE_FILTER]'", "meta_key LIKE 'learndash_group_enrolled_%'", $where );
	}
}

/**
 * Utility function to check if a Group Leader can manage Groups.
 *
 * @since 3.2.3
 */
function learndash_get_group_leader_manage_groups() {
	if ( 'yes' === LearnDash_Settings_Section::get_section_setting( 'LearnDash_Settings_Section_Groups_Group_Leader_User', 'manage_groups_enabled' ) ) {
		return LearnDash_Settings_Section::get_section_setting( 'LearnDash_Settings_Section_Groups_Group_Leader_User', 'manage_groups_capabilities' );
	}
}

/**
 * Utility function to check if a Group Leader can manage Courses.
 *
 * @since 3.2.3
 */
function learndash_get_group_leader_manage_courses() {
	if ( 'yes' === LearnDash_Settings_Section::get_section_setting( 'LearnDash_Settings_Section_Groups_Group_Leader_User', 'manage_courses_enabled' ) ) {
		return LearnDash_Settings_Section::get_section_setting( 'LearnDash_Settings_Section_Groups_Group_Leader_User', 'manage_courses_capabilities' );
	}
}

/**
 * Utility function to check if a Group Leader can manage Users.
 *
 * @since 3.2.3
 */
function learndash_get_group_leader_manage_users() {
	if ( 'yes' === LearnDash_Settings_Section::get_section_setting( 'LearnDash_Settings_Section_Groups_Group_Leader_User', 'manage_users_enabled' ) ) {
		return LearnDash_Settings_Section::get_section_setting( 'LearnDash_Settings_Section_Groups_Group_Leader_User', 'manage_users_capabilities' );
	}
}

/**
 * Check if the Group Leader can edit the Group or Course posts.
 *
 * Override the default WordPress user capability when editing a Group.
 * See wp-includes/class-wp-user.php for details.
 *
 * @since 3.2.3
 *
 * @param bool|array   $allcaps Array of key/value pairs where keys represent a capability name
 *                              and boolean values represent whether the user has that capability.
 * @param string|array $cap     Required primitive capabilities for the requested capability.
 * @param array        $args    Additional arguments.
 * @param WP_User      $user    WP_User object.
 */
function learndash_group_leader_has_cap_filter( $allcaps, $cap, $args, $user ) {

	global $pagenow;

	if ( in_array( 'edit_posts', $cap, true ) ) {
		/**
		 * If the Group Leader is attempting to manage a comment we enable that
		 * IF they are viewing the comments for an Assignment or Essay.
		 * At this point we are not concerned about other LD post types.
		 */
		if ( ( 'edit-comments.php' === $pagenow ) && ( isset( $_GET['p'] ) ) ) {
			$comment_post = get_post( absint( $_GET['p'] ) );
			if ( ( $comment_post ) && ( is_a( $comment_post, 'WP_Post' ) ) && ( in_array( $comment_post->post_type, array( learndash_get_post_type_slug( 'assignment' ), learndash_get_post_type_slug( 'essay' ) ), true ) ) ) {
				$course_id = get_post_meta( $comment_post->ID, 'course_id', true );
				$course_id = absint( $course_id );
				if ( ( ! empty( $course_id ) ) && ( learndash_check_group_leader_course_user_intersect( get_current_user_id(), $comment_post->post_author, $course_id ) ) ) {
					foreach ( $cap as $cap_slug ) {
						$allcaps[ $cap_slug ] = true;
					}

					return $allcaps;
				}
			}
		}

		if ( in_array( learndash_get_group_leader_manage_courses(), array( 'basic', 'advanced' ), true ) ) {
			/** This filter is documented in includes/ld-groups.php */
			if ( apply_filters( 'learndash_group_leader_has_cap_filter', true, $cap, $args, $user ) ) {
				if ( ! isset( $args[2] ) ) {
					$post_id = get_the_id();
					if ( $post_id ) {
						if ( ( in_array( get_post_type( $post_id ), learndash_get_post_type_slug( array( 'course', 'lesson', 'topic', 'quiz', 'group' ) ), true ) ) ) {
							$args[2] = $post_id;
						}
					}
				}

				if ( ( isset( $args[2] ) ) && ( ! empty( $args[2] ) ) ) {
					foreach ( $cap as $cap_slug ) {
						$allcaps[ $cap_slug ] = true;
					}
				} elseif ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
					// Total KLUDGE. When editing in Gutenberg there is a call to /wp/v2/blocks with 'edit' context.
					$route = untrailingslashit( $GLOBALS['wp']->query_vars['rest_route'] );
					if ( '/wp/v2/blocks' === $route ) {
						foreach ( $cap as $cap_slug ) {
							$allcaps[ $cap_slug ] = true;
						}
					}
				}
			}
		}
	} elseif ( in_array( 'edit_others_groups', $cap, true ) ) { // Check if Group Leader can edit Groups they are Leader of.
		if ( ( ! isset( $allcaps['edit_others_groups'] ) ) || ( true !== $allcaps['edit_others_groups'] ) ) {
			if ( 'basic' === learndash_get_group_leader_manage_groups() ) {
				/**
				 * Filter override for Group Leader edit cap.
				 *
				 * @since 3.2.3
				 *
				 * @param bool     $true Always True if user can edit post.
				 * @param bool[]   $allcaps Array of key/value pairs where keys represent a capability name
				 *                          and boolean values represent whether the user has that capability.
				 * @param array    $args {
				 *     @type string    $index_0 Requested capability.
				 *     @type int       $index_1 Concerned user ID.
				 *     @type mixed  ...$other   Optional second and further parameters, typically object ID.
				 * } Arguments that accompany the requested capability check.
				 * @param WP_User  $user    The user object.
				 *
				 * @return bool True if Group Leader is allowed to edit post.
				 */
				if ( apply_filters( 'learndash_group_leader_has_cap_filter', true, $cap, $args, $user ) ) {
					/**
					 * During the save post cycle the args[2] is empty. So we can't check if the GL can edit a specific
					 * Group ID. But if we find the 'action' and 'post_ID' POST vars we can check indirectly.
					 */
					if ( ! isset( $args[2] ) ) {
						if ( ( isset( $_POST['action'] ) ) && ( 'editpost' === $_POST['action'] ) ) {
							if ( isset( $_POST['post_ID'] ) ) {
								$args[2] = absint( $_POST['post_ID'] );
							}
						}
					}

					if ( ( isset( $args[2] ) ) && ( in_array( get_post_type( $args[2] ), array( learndash_get_post_type_slug( 'group' ) ), true ) ) ) {
						if ( ( isset( $args[1] ) ) && ( ! empty( $args[1] ) ) ) {
							$gl_group_ids = learndash_get_administrators_group_ids( absint( $args[1] ) );
							if ( ( ! empty( $gl_group_ids ) ) && ( in_array( absint( $args[2] ), $gl_group_ids, true ) ) ) {
								foreach ( $cap as $cap_slug ) {
									$allcaps[ $cap_slug ] = true;
								}
							}
						}
					}
				}
			}
		}
	} // phpcs:ignore Squiz.ControlStructures.ControlSignature.SpaceAfterCloseBrace -- Explanatory comment follows
	// Check if Group Leader can edit Course or Steps within their Groups.
	elseif ( ( ( in_array( 'edit_others_courses', $cap, true ) ) ) && ( isset( $allcaps['edit_others_courses'] ) ) && ( true !== $allcaps['edit_others_courses'] ) ) {
		if ( 'basic' === learndash_get_group_leader_manage_courses() ) {

			/** This filter is documented in includes/ld-groups.php */
			if ( apply_filters( 'learndash_group_leader_has_cap_filter', true, $cap, $args, $user ) ) {
				/**
				 * During the save post cycle the args[2] is empty. So we can't check if the GL can edit a specific
				 * Course Post ID. But if we find the 'action' and 'post_ID' POST vars we can check indirectly.
				 */
				if ( ! isset( $args[2] ) ) {
					if ( ( isset( $_POST['action'] ) ) && ( 'editpost' === $_POST['action'] ) ) {
						if ( isset( $_POST['post_ID'] ) ) {
							$args[2] = absint( $_POST['post_ID'] );
						}
					}
				}

				if ( ( isset( $args[2] ) ) && ( in_array( get_post_type( $args[2] ), learndash_get_post_types( 'course' ), true ) ) ) {
					if ( get_post_type( $args[2] ) === learndash_get_post_type_slug( 'course' ) ) {
						$courses = array( $args[2] );
					} else {
						$courses = learndash_get_courses_for_step( $args[2], true );
						$courses = array_keys( $courses );
					}

					$leader_group_ids = array();
					if ( ( isset( $args[1] ) ) && ( ! empty( $args[1] ) ) ) {
						$leader_group_ids = learndash_get_administrators_group_ids( absint( $args[1] ) );
					}

					if ( ! empty( $leader_group_ids ) ) {

						$course_group_ids = array();
						foreach ( $courses as $course_id ) {
							$course_group_ids = array_merge( $course_group_ids, learndash_get_course_groups( absint( $course_id ) ) );
						}

						if ( ( ! empty( $leader_group_ids ) ) && ( ! empty( $course_group_ids ) ) ) {
							$common_course_ids = array_intersect( $leader_group_ids, $course_group_ids );
							if ( ! empty( $common_course_ids ) ) {
								$include_caps = true;
								if ( true === $include_caps ) {
									foreach ( $cap as $cap_slug ) {
										$allcaps[ $cap_slug ] = true;
									}
								}
							}
						}
					}
				}
			}
		}
	}

	return $allcaps;
}
add_action(
	'init',
	function () {
		if ( learndash_is_group_leader_user() ) {
			add_filter( 'user_has_cap', 'learndash_group_leader_has_cap_filter', 10, 4 );
		}
	},
	10
);

/**
 * Check if the Group Leader AND User and Course have common Groups.
 *
 * @since 3.4.0
 *
 * @param int $gl_user_id Group Leader User ID.
 * @param int $user_id    User ID.
 * @param int $course_id  Course ID.
 *
 * @return bool true if a common group intersect is determined.
 */
function learndash_check_group_leader_course_user_intersect( $gl_user_id = 0, $user_id = 0, $course_id = 0 ) {

	if ( ( empty( $gl_user_id ) ) || ( empty( $user_id ) ) || ( empty( $course_id ) ) ) {
		return false;
	}

	if ( ! learndash_is_group_leader_user( $gl_user_id ) ) {
		return false;
	}

	$common_group_ids = array();
	// And that the Course is associated with some Groups.
	$course_group_ids = learndash_get_course_groups( $course_id );
	$course_group_ids = array_map( 'absint', $course_group_ids );
	if ( ! empty( $course_group_ids ) ) {
		/**
		 * If the Group Leader can manage all Users or all Groups then return. Note
		 * we are performing this check AFTER we check if the Course is part of a
		 * Group. This is on purpose.
		 */
		if ( ( 'advanced' === learndash_get_group_leader_manage_users() ) || ( 'advanced' === learndash_get_group_leader_manage_groups() ) ) {
			return true;
		}

		// Now check the Group Leader managed Groups...
		$leader_group_ids = learndash_get_administrators_group_ids( $gl_user_id );
		$leader_group_ids = array_map( 'absint', $leader_group_ids );
		if ( ! empty( $leader_group_ids ) ) {
			// ...and the user (post author) Groups...
			$author_group_ids = learndash_get_users_group_ids( $user_id );
			$author_group_ids = array_map( 'absint', $author_group_ids );

			// ...and the course groups have an intersect.
			$common_group_ids = array_intersect( $leader_group_ids, $course_group_ids, $author_group_ids );
			$common_group_ids = array_map( 'absint', $common_group_ids );
		}
	}

	if ( ! empty( $common_group_ids ) ) {
		return true;
	}

	return false;
}

/**
 * Returns message if groups are not public in the admin dashboard
 *
 * @since 3.4.2
 */
function learndash_groups_get_not_public_message() {
	$groups_setting_link = '<a href="' . esc_url( add_query_arg( array( 'page' => 'groups-options' ), admin_url( 'admin.php' ) ) . '#learndash_settings_groups_cpt_cpt_options' ) . '">' . esc_html__( 'Settings', 'learndash' ) . '</a>';

	// translators: placeholders: Groups, link to Group settings page.
	$message = '<div class="notice notice-error is-dismissible"><p>' . sprintf( esc_html_x( '%1$s are not public, please visit the %2$s page and set them to Public to enable access on the front end.', 'placeholders: Groups, link to Group settings page', 'learndash' ), esc_html( learndash_get_custom_label( 'groups' ) ), $groups_setting_link ) . '</p></div>';

	/**
	 * Filters groups not set to Public message
	 *
	 * @since 3.4.2
	 *
	 * @param string $message The message when groups are not set to Public
	 * @return string $message The message when groups are not set to Public
	 */
	return apply_filters( 'learndash_groups_get_not_public_message', $message );
}

/**
 * Returns true if it's a group post.
 *
 * @param WP_Post|int|null $post Post or Post ID.
 *
 * @since 4.1.0
 *
 * @return bool
 */
function learndash_is_group_post( $post ): bool {
	if ( empty( $post ) ) {
		return false;
	}

	$post_type = is_a( $post, WP_Post::class ) ? $post->post_type : get_post_type( $post );

	return LDLMS_Post_Types::get_post_type_slug( 'group' ) === $post_type;
}

/**
 * Deletes group leader metadata when a group leader role is removed from a user.
 *
 * @since 4.7.0
 */
add_action(
	'remove_user_role',
	function( int $user_id, string $role ) {
		$referer = wp_get_referer();
		$found   = [];

		if (
			$referer
			&& strpos( $referer, '/wp-admin/user-edit.php' ) !== false
			// phpcs:ignore WordPress.Security.NonceVerification.Missing
			&& is_array( $_POST )
		) {
			// phpcs:ignore WordPress.Security.NonceVerification.Missing
			foreach ( $_POST as $key => $value ) {
				if ( strpos( $key, 'role' ) === false ) {
					continue;
				}

				$found[] = is_array( $value )
					? in_array( 'group_leader', $value, true )
					: (
						is_string( $value )
							? strpos( $value, 'group_leader' ) !== false
							: null
					);
			}
		}

		if ( 'group_leader' === $role && ! in_array( true, $found, true ) ) {
			global $wpdb;

			$wpdb->query(
				$wpdb->prepare(
					"DELETE FROM {$wpdb->usermeta} WHERE user_id = %d AND meta_key LIKE %s",
					$user_id,
					'learndash_group_leaders_%'
				)
			);
		}
	},
	10,
	2
);

if ( ! function_exists( 'learndash_group_access_from' ) ) {
	/**
	 * Returns the date when a group becomes available for a user.
	 *
	 * It can return a future date if the group has not started yet (group with a start date).
	 * Admin users don't have an access date even if they have access to the group.
	 *
	 * @since 4.8.0
	 *
	 * @param int $group_id Group ID to check.
	 * @param int $user_id  User ID to check.
	 *
	 * @return int|null The date when a group becomes available for a user. Can be NULL when a user has been enrolled before 4.8.0 version.
	 */
	function learndash_group_access_from( int $group_id, int $user_id ): ?int {
		$enrolled_timestamp = Cast::to_int(
			get_user_meta( $user_id, 'group_' . $group_id . '_access_from', true )
		);

		/**
		 * Filters the date when a group becomes available for a user.
		 *
		 * @since 4.8.0
		 *
		 * @param int|null $timestamp Enrollment date timestamp. Can be NULL when a user has been enrolled before 4.8.0 version.
		 * @param int      $group_id  Group ID.
		 * @param int      $user_id   User ID.
		 *
		 * @return int|null The date when a group becomes available for a user.
		 */
		return apply_filters( 'learndash_group_access_from', $enrolled_timestamp, $group_id, $user_id );
	}
}
