[ Avaa Bypassed ]




Upload:

Command:

hmhc3928@18.227.183.94: ~ $
<?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 );
	}
}

Filemanager

Name Type Size Permission Actions
admin Folder 0755
classes Folder 0755
coupon Folder 0755
course Folder 0755
deprecated Folder 0755
dto Folder 0755
exam Folder 0755
group Folder 0755
gutenberg Folder 0755
helpers Folder 0755
import Folder 0755
interfaces Folder 0755
lib Folder 0755
licensing Folder 0755
loggers Folder 0755
models Folder 0755
payments Folder 0755
quiz Folder 0755
reports Folder 0755
rest-api Folder 0755
settings Folder 0755
shortcodes Folder 0755
site-health Folder 0755
views Folder 0755
widgets Folder 0755
class-ld-addons-updater.php File 35.03 KB 0644
class-ld-bitbucket-api.php File 59.32 KB 0644
class-ld-course-wizard.php File 29.51 KB 0644
class-ld-cpt-instance.php File 34.14 KB 0644
class-ld-cpt-widget.php File 198 B 0644
class-ld-cpt.php File 18.17 KB 0644
class-ld-custom-label.php File 24.3 KB 0644
class-ld-design-wizard.php File 37.05 KB 0644
class-ld-gdpr.php File 48.61 KB 0644
class-ld-lms.php File 224.76 KB 0644
class-ld-permalinks.php File 29.92 KB 0644
class-ld-search.php File 6.66 KB 0644
class-ld-semper-fi-module.php File 61.88 KB 0644
class-ld-setup-wizard.php File 23.09 KB 0644
class-ld-translations.php File 28.85 KB 0644
class-ldlms-db.php File 19.01 KB 0644
class-ldlms-post-types.php File 8.64 KB 0644
class-ldlms-transients.php File 5.04 KB 0644
ld-assignment-uploads.php File 38.35 KB 0644
ld-autoupdate.php File 241 B 0644
ld-certificates.php File 26.74 KB 0644
ld-convert-post-pdf.php File 31.07 KB 0644
ld-core-functions.php File 857 B 0644
ld-groups.php File 92.44 KB 0644
ld-license.php File 12.93 KB 0644
ld-misc-functions.php File 72.5 KB 0644
ld-reports.php File 59.38 KB 0644
ld-scripts.php File 6.89 KB 0644
ld-users.php File 47.6 KB 0644
ld-wp-editor.php File 3.46 KB 0644