WS_OK_7.4.33 HEX
HEX
Server: nginx/1.21.4
System: Linux v12674 6.8.0-85-generic #85-Ubuntu SMP PREEMPT_DYNAMIC Thu Sep 18 15:26:59 UTC 2025 x86_64
User: endtheignorance (1014)
PHP: 7.4.33
Disabled: exec,passthru,shell_exec,system,proc_open,popen,parse_ini_file,show_source
Upload Files
File: /storage/v12674/lcaginesisstore/new/wp-content/plugins/woocommerce-memberships/src/Shortcodes.php
<?php
/**
 * WooCommerce Memberships
 *
 * This source file is subject to the GNU General Public License v3.0
 * that is bundled with this package in the file license.txt.
 * It is also available through the world-wide-web at this URL:
 * http://www.gnu.org/licenses/gpl-3.0.html
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@skyverge.com so we can send you a copy immediately.
 *
 * DISCLAIMER
 *
 * Do not edit or add to this file if you wish to upgrade WooCommerce Memberships to newer
 * versions in the future. If you wish to customize WooCommerce Memberships for your
 * needs please refer to https://docs.woocommerce.com/document/woocommerce-memberships/ for more information.
 *
 * @author    SkyVerge
 * @copyright Copyright (c) 2014-2025, SkyVerge, Inc. (info@skyverge.com)
 * @license   http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
 */

namespace SkyVerge\WooCommerce\Memberships;

defined( 'ABSPATH' ) or exit;

/**
 * Memberships shortcodes.
 *
 * This class is responsible for adding and handling shortcodes for Memberships.
 *
 * @since 1.21.0
 */
class Shortcodes {


	/**
	 * Initializes and registers Memberships shortcodes.
	 *
	 * @since 1.21.0
	 */
	public static function initialize() {

		$shortcodes = [
			'wcm_restrict'           => __CLASS__ . '::restrict',             /** @see Shortcodes::restrict() */
			'wcm_nonmember'          => __CLASS__ . '::nonmember',            /** @see Shortcodes::nonmember() */
			'wcm_content_restricted' => __CLASS__ . '::content_restricted',   /** @see Shortcodes::content_restricted() */
			'wcm_discounted_product' => __CLASS__ . '::has_product_discount', /** @see Shortcodes::has_product_discount() */
			'wcm_product_discount'   => __CLASS__ . '::get_product_discount', /** @see Shortcodes::get_product_discount() */
		];

		if ( wc_memberships()->is_member_directory_enabled() ) {
			$shortcodes['wcm_directory'] = __CLASS__ . '::directory'; /** @see Shortcodes::directory() */
		}

		foreach ( $shortcodes as $shortcode => $function ) {

			/**
			 * Filters a Memberships shortcode tag.
			 *
			 * @since 1.0.0
			 *
			 * @param string $shortcode shortcode tag
			 */
			add_shortcode( apply_filters( "{$shortcode}_shortcode_tag", $shortcode ), $function );
		}
	}


	/**
	 * Restrict content shortcode.
	 *
	 * Shortcode: [wcm_restrict]
	 * Usage: [wcm_restrict plans="{int|int[]|string|string[]}" delay="{string|datetime}" start_after_trial="{yes/no}"]<Content, HTML>[/wcm_restrict]
	 *
	 * Attributes usage:
	 *
	 * - plans: the plan slugs or IDs to limit the wrapped content to certain members
	 * - delay: a period of time (e.g. '5 days', '2 weeks', '3 months', '1 year') or a fixed date that can be parsed by PHP to delay access to the wrapped content by a certain time, or makes it available on a particular date
	 * - start_after_trial: either 'yes' or 'no' -  delays access to the wrapped content until a trial period is over (when WooCommerce Subscriptions is in use)
	 *
	 * @internal
	 *
	 * @since 1.21.0
	 *
	 * @param array $atts shortcode attributes
	 * @param string|null $content the content
	 * @return string HTML output
	 */
	public static function restrict( $atts, $content = null ) {

		$defaults = array(
			'plan'              => null,
			'plans'             => null,
			'delay'             => null,
			'start_after_trial' => 'no',
		);

		// filters attributes
		$atts = shortcode_atts( $defaults, is_array( $atts ) ? $atts : array(), 'wcm_restrict' );

		// parse attributes for use in function
		$plans      = self::parse_atts( 'plans', $atts, null );
		$delay      = ! empty( $atts['delay'] ) ? $atts['delay'] : null;
		$free_trial = isset( $atts['start_after_trial'] ) && 'yes' === $atts['start_after_trial'];

		ob_start();

		wc_memberships_restrict( do_shortcode( $content ), $plans, $delay, $free_trial );

		return ob_get_clean();
	}


	/**
	 * Nonmember content shortcode.
	 *
	 * Shortcode: [wcm_nonmember]
	 * Usage: [wcm_nonmember plans="{int|int[]|string|string[]}"]<Content, HTML>[/wcm_nonmember]
	 *
	 * Attributes behavior:
	 *
	 * When no attributes are specified, only non-members (including non-active members of any plan) will see shortcode content.
	 * When a 'plans' attribute is used, non-members but also members who are not in the plans specified will see the content.
	 * The 'plans' attribute can be a single or a comma separated list of plan IDs or plan names.
	 *
	 * @internal
	 *
	 * @since 1.1.0
	 *
	 * @param array $atts shortcode attributes
	 * @param string|null $content the shortcode content
	 * @return string HTML content intended to non-members (or empty string)
	 */
	public static function nonmember( $atts, $content = null ) {

		$non_member_content = '';

		// hide non-member messages for super users
		if ( ! current_user_can( 'wc_memberships_access_all_restricted_content' ) ) {

			// default attribute values
			$defaults = array(
				'plan'  => null,
				'plans' => null,
			);

			// filters attributes
			$atts = shortcode_atts( $defaults, $atts, 'wcm_nonmember' );

			$plans         = wc_memberships_get_membership_plans();
			$non_member    = true;
			$exclude_plans = self::parse_atts( 'plans', $atts, array() );

			foreach ( $plans as $plan ) {

				// excluded plans can use plan IDs or slugs
				if ( ! empty( $exclude_plans ) && ! in_array( $plan->get_id(), $exclude_plans, false ) && ! in_array( $plan->get_slug(), $exclude_plans, false ) ) {
					continue;
				}

				if ( wc_memberships_is_user_active_member( get_current_user_id(), $plan ) ) {
					$non_member = false;
					break;
				}
			}

			if ( $non_member ) {
				$non_member_content = do_shortcode( $content );
			}
		}

		return $non_member_content;
	}


	/**
	 * Restricted content messages shortcode.
	 *
	 * Shortcode: [wcm_content_restricted]
	 * Usage: [wcm_content_restricted]
	 *
	 * This shortcode has no optional attributes.
	 *
	 * @internal
	 *
	 * @since 1.21.0
	 *
	 * @param array $atts shortcode attributes
	 * @param string|null $content content
	 * @return string HTML shortcode result
	 */
	public static function content_restricted( $atts, $content = null ) {

		$object_id = isset( $_GET['r'] ) && is_numeric( $_GET['r'] ) ? absint( $_GET['r'] ) : null;
		$post      = null;
		$term      = null;
		$output    = '';

		if ( isset( $_GET['wcm_redirect_to'], $_GET['wcm_redirect_id'] ) && is_numeric( $_GET['wcm_redirect_id'] ) ) {

			$object_id        = absint( $_GET['wcm_redirect_id'] );
			$object_type_name = sanitize_text_field( wp_unslash( (string) $_GET['wcm_redirect_to'] ) );

			if ( in_array( $object_type_name, get_post_types(), true ) ) {
				$post = get_post( $object_id );
			} else {
				$term = get_term( $object_id, $object_type_name );
			}

		} elseif ( $object_id > 0 ) {

			$term = get_term( $object_id );
			$post = get_post( $object_id );
		}

		if ( $term instanceof \WP_Term ) {

			if ( 'product_cat' === $term->taxonomy ) {

				if ( ! current_user_can( 'wc_memberships_view_restricted_product_taxonomy_term', $term->taxonomy, $term->term_id ) ) {
					$output .= \WC_Memberships_User_Messages::get_message_html( 'product_category_viewing_restricted', array( 'term' => $term ) );
				} elseif ( ! current_user_can( 'wc_memberships_view_delayed_taxonomy_term', $term->taxonomy, $term->term_id ) ) {
					$output .= \WC_Memberships_User_Messages::get_message_html( 'product_category_viewing_delayed', array( 'term' => $term ) );
				}

			} else {

				if ( ! current_user_can( 'wc_memberships_view_restricted_taxonomy_term', $term->taxonomy, $term->term_id ) ) {
					$output .= \WC_Memberships_User_Messages::get_message_html( 'content_category_restricted', array( 'term' => $term ) );
				} elseif ( ! current_user_can( 'wc_memberships_view_delayed_taxonomy_term', $term->taxonomy, $term->term_id ) ) {
					$output .= \WC_Memberships_User_Messages::get_message_html( 'content_category_delayed', array( 'term' => $term ) );
				}
			}

		} elseif ( $post instanceof \WP_Post ) {

			$message_code = \WC_Memberships_User_Messages::get_message_code_shorthand_by_post_type( $post );
			$output .= \WC_Memberships_User_Messages::get_message_html( $message_code, array( 'post' => $post ) );

		}

		return $output;
	}


	/**
	 * Displays content conditionally whether a product has discounts.
	 *
	 * Shortcode: [wcm_discounted_product]
	 * Usage: [wcm_discounted_product id="{int}" plan="{int|string}"]<Content, HTML>[/wcm_discounted_product]
	 *
	 * Optional attributes:
	 *
	 * - id: check discounts for a specific product (if unspecified will get discounts for the current product)
	 * - plans: check discounts for a specific plan (if unspecified will gather results based on all discounts offered by all plans)
	 *
	 * @internal
	 *
	 * @since 1.21.0
	 *
	 * @param array $atts shortcode arguments
	 * @param null $content the shortcode content
	 * @return string HTML shortcode result
	 */
	public static function has_product_discount( $atts, $content = null ) {

		$output   = '';
		$defaults = array(
			'id'         => null,
			'product_id' => null,
			'plan'       => null,
			'plans'      => null,
		);

		// filters shortcode attributes
		$atts       = shortcode_atts( $defaults, $atts, 'wcm_discounted_product' );
		$product_id = self::parse_atts( 'product_id', $atts );

		if ( $product_id && $product_id > 0 ) {

			$plan            = self::parse_atts( 'plans', $atts, null );
			$discount_amount = wc_memberships()->get_member_discounts_instance()->get_product_discount( $product_id, 'max', $plan );
			$output          = $discount_amount > 0 && is_string( $content ) ? do_shortcode( $content ) : '';
		}

		return $output;
	}


	/**
	 * Displays the discount for a product.
	 *
	 * Shortcode: [wcm_product_discount]
	 * Usage: [wcm_product_discount id="{int}" plan="{int|string}" display="{string}"]
	 *
	 * Optional attributes:
	 *
	 * - id: get discounts for a specific product (if unspecified will get discounts for the current product)
	 * - plans: get discounts for a specific plan (if unspecified will gather results based on all discounts offered by all plans)
	 * - display: either 'min', 'max' or 'average' discount (default 'max', i.e. the highest possible discount)
	 * - format: 'amount' (default) or 'percentage', to display the discount as a fixed price amount or a percentage of the normal price
	 *
	 * @internal
	 *
	 * @since 1.21.0
	 *
	 * @param array $atts shortcode arguments
	 * @param null|string $content the shortcode content
	 * @return string formatted discount HTML
	 */
	public static function get_product_discount( $atts, $content = null ) {

		// shortcode defaults
		$defaults = array(
			'id'         => null,
			'product_id' => null,
			'plan'       => null,
			'plans'      => null,
			'display'    => 'max',
			'format'     => 'amount',
		);

		$atts       = shortcode_atts( $defaults, $atts, 'wcm_product_discount' );
		$product_id = self::parse_atts( 'product_id', $atts );

		if ( $product_id && $product_id > 0 ) {

			$plan   = self::parse_atts( 'plans', $atts, null );
			$value  = in_array( $atts['display'], array( 'min', 'max', 'avg', 'average', 'mean' ), true ) ? $atts['display'] : 'max';
			$format = in_array( $atts['format'], array( 'amount', 'percentage', 'percent', '%' ), true ) ? $atts['format'] : '%';
			$output = wc_memberships()->get_member_discounts_instance()->get_product_discount_html( $product_id, $value, $format, $plan );

		} else {

			$output = wc_price( 0 );
		}

		return $output; // nosemgrep
	}


	/**
	 * Outputs the Member Directory.
	 *
	 * Shortcode: [wcm_directory]
	 * Usage: [wcm_directory plans="{string}" status="{string}" per_page="{int}" bios="{string}" avatars="{string}" avatar_size="{int}"]
	 *
	 * Optional attributes:
	 *
	 * - plans: comma-separated list of plans to limit returned members by plan (default 'any')
	 * - status: comma-separated list of the status of the members to list for output (default 'any')
	 * - per_page: number of members to show per page (default 12)
	 * - bios: 'yes' or 'no' if user descriptions should be shown (default 'yes')
	 * - avatars: 'yes' or 'no' if avatars should be shown (default 'yes')
	 * - avatar_size: pixel size for avatars (default 128)
	 *
	 * @internal
	 *
	 * @since 1.21.0
	 *
	 * @param array|string $atts shortcode arguments
	 * @return string formatted discount HTML
	 */
	public static function directory( $atts ) : string {
		global $paged;

		// sanity check: bail if the directory is not enabled
		if ( ! wc_memberships()->is_member_directory_enabled() ) {
			return '';
		}

		$atts = shortcode_atts( [
			'plans'       => 'any',
			'status'      => 'any',
			'per_page'    => 12,
			'bios'        => 'yes',
			'avatars'     => 'yes',
			'avatar_size' => 128,
		], $atts );

		$members       = self::get_directory_members( $atts );
		$display_count = (int) $atts['per_page'];
		$total_found   = count( $members );
		$total_pages   = ceil( $total_found / $display_count );

		ob_start();

		if ( ! empty( $members ) ) {

			// determine if we should be slicing member display into pages
			if ( $total_pages > 1 ) {
				$start   = $paged ? ( absint( $paged ) - 1 ) * $display_count : 0;
				$members = array_slice( $members, $start, $display_count, true );
			}

			wc_get_template( 'member-directory-start.php', [
				'members' => $members,
			] );

			foreach ( $members as $member => $membership_ids ) {

				// setup a membership object for our template
				setup_postdata( get_post( $membership_ids[0] ) );

				$plans = [];

				// build a set of plans for this member
				foreach ( $membership_ids as $id ) {
					$plan_id           = wp_get_post_parent_id( $id );
					$plans[ $plan_id ] = get_the_title( $plan_id );
				}

				/**
				 * Filters the plans that will be listed in the template.
				 *
				 * @since 1.21.0
				 *
				 * @param string[] $plans the plans for this member (slugs)
				 * @param int $member the member's WP_User ID
				 */
				$plans = (array) apply_filters( 'wc_memberships_member_directory_listing_plans', $plans, $member );

				wc_get_template( 'member-directory-listing.php', [
					'shortcode' => $atts,
					'plans'     => $plans,
				] );
			}

			wc_get_template( 'member-directory-navigation.php', [
				'total_pages' => $total_pages,
			] );

			// reset queried object
			wp_reset_query();

			wc_get_template( 'member-directory-end.php', [
				'members' => $members,
			] );
		}

		return ob_get_clean();
	}


	/**
	 * Gets the set of members for the directory shortcode.
	 *
	 * @since 1.21.0
	 *
	 * @param array $atts shortcode attributes
	 * @return array $members the set of members for the directory
	 */
	private static function get_directory_members( array $atts ) : array {
		global $wpdb;

		$members     = [];
		// nosemgrep: audit.php.wp.security.sqli.shortcode-attr
		$members_raw = $wpdb->get_results( self::prepare_member_directory_query( $atts ), ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared

		foreach( $members_raw as $member ) {
			if ( array_key_exists( $member['post_author'], $members ) ) {
				$members[ (int) $member['post_author'] ][] = (int) $member['ID'];
			} else {
				$members[ (int) $member['post_author'] ] = [ (int) $member['ID'] ];
			}
		}

		/**
		 * Filters the members included in the directory.
		 *
		 * @since 1.21.0
		 *
		 * @param array $members the members returned by the shortcode args (associative array of user IDs and user memberships per user)
		 */
		return (array) apply_filters( 'wc_memberships_member_directory_included_members', $members );
	}


	/**
	 * Builds the SQL to find all members.
	 *
	 * @since 1.21.0
	 *
	 * @param array $atts shortcode attributes
	 * @return string the SQL query
	 */
	private static function prepare_member_directory_query( array $atts ) : string {
		global $wpdb;

		// build the basic SQL to find all authors of a user membership
		$sql = "
			SELECT {$wpdb->posts}.post_author, {$wpdb->posts}.ID
			FROM {$wpdb->posts}
			WHERE {$wpdb->posts}.post_type = 'wc_user_membership'
		";

		// now add a clause for status if set in the shortcode
		if ( 'any' !== $atts['status'] ) {

			$status_list  = array_map( 'trim', explode( ',', $atts['status'] ) );
			$status_where = [];

			foreach ( $status_list as $key => $status ) {

				// ensure we have status prefixes for the direct SQL query
				if ( strpos( $status, 'wcm-' ) !== 0 ) {
					$status_list[ $key ] = "wcm-{$status}";
				}

				// store this condition
				if ( in_array( $status_list[ $key ], wc_memberships_get_user_membership_statuses( false, true ), false ) ) {
					$status_where[] = $wpdb->prepare( "{$wpdb->posts}.post_status = %s", $status_list[ $key ] );
				}
			}

			// add a condition for all statuses
			if ( ! empty( $status_where ) ) {
				$sql .= " AND (" . implode( ' OR ', $status_where ) . ")";
			}
		}

		// check for plans by searching parent IDs
		if ( 'any' !== $atts['plans'] ) {

			$plans_list  = array_map( 'trim', explode( ',', $atts['plans'] ) );
			$plans_where = ["{$wpdb->posts}.post_parent = 0"];

			foreach ( $plans_list as $plan_slug_or_id ) {

				$plan = wc_memberships_get_membership_plan( $plan_slug_or_id );

				// be sure the plan slug is a valid one (exclude drafts as well)
				if ( $plan instanceof \WC_Memberships_Membership_Plan && $plan->is_public() ) {
					$plans_where[] = $wpdb->prepare( "{$wpdb->posts}.post_parent = %s", $plan->get_id() );
				}
			}

			// add a condition for all plans
			$sql .= " AND (" . implode( ' OR ', $plans_where ) . ")";

		} else {

			$public_plan_statuses = [];

			// wrap all plan statuses in single quotes
			foreach ( wc_memberships()->get_plans_instance()->get_membership_plans_public_statuses() as $status ) {
				$public_plan_statuses[] = "'{$status}'";
			}

			$public_plan_statuses = implode( ', ', $public_plan_statuses );

			// exclude membership plan posts having a "private" status
			if ( ! empty( $public_plan_statuses ) ) {
				$sql .= " AND {$wpdb->posts}.post_parent IN ( SELECT ID FROM {$wpdb->posts} WHERE post_type = 'wc_membership_plan' AND post_status IN ( {$public_plan_statuses} ) )";
			}
		}

		return $sql;
	}


	/**
	 * Parses common shortcode attributes into useful variables (helper method).
	 *
	 * Do not open this method to public.
	 *
	 * @since 1.21.0
	 *
	 * @param string $key item of entity to determine from $atts
	 * @param array $atts shortcode attributes
	 * @param null|mixed $default default value to return
	 * @return mixed
	 */
	private static function parse_atts( $key, $atts, $default = null ) {
		global $post, $product;

		$value = $default;

		switch ( $key ) {

			case 'product_id' :

				// we accept both 'id' or 'product_id' as long as they're set by the user (non-null)
				if ( ! empty( $atts['id'] ) ) {
					$product_id = is_numeric( $atts['id'] ) ? (int) $atts['id'] : $default;
				} elseif ( ! empty( $atts['product_id'] ) ) {
					$product_id = is_numeric( $atts['product_id'] ) ? (int) $atts['product_id'] : $default;
				} elseif( $product instanceof \WC_Product ) {
					$product_id = $product->get_id();
				} elseif ( $post instanceof \WP_Post ) {
					$product_id = (int) $post->ID;
				} else {
					$product_id = $default;
				}

				$value = $product_id;

			break;

			case 'plan' :
			case 'plans' :

				$plan     = $default;
				$plan_att = null;

				// we accept either 'plan' or 'plans'
				if ( ! empty( $atts['plan'] ) ) {
					$plan_att = trim( $atts['plan'] );
				} elseif ( ! empty( $atts['plans'] ) ) {
					$plan_att = trim( $atts['plans'] );
				}

				if ( is_numeric( $plan_att ) ) {

					$plan = (int) $plan_att;

				} elseif ( '' !== $plan_att && is_string( $plan_att ) ) {

					$plan_ids          = [];
					$plan_ids_or_slugs = array_map( 'trim', explode( ',', $plan_att ) );

					foreach ( $plan_ids_or_slugs as $plan_id_or_slug ) {

						if ( ! $plan_id_or_slug || ( ! is_numeric( $plan_id_or_slug ) && ! is_string( $plan_id_or_slug ) ) ) {
							continue;
						}

						$plan = wc_memberships_get_membership_plan( $plan_id_or_slug );

						// exclude plans having private status
						if ( $plan instanceof \WC_Memberships_Membership_Plan && $plan->is_public() ) {
							$plan_ids[] = $plan->get_id();
						}
					}

					$plan = empty( $plan_ids ) ? null : $plan_ids;
				}

				$value = $plan;

			break;
		}

		return $value;
	}


}