Skip to content

Instantly share code, notes, and snippets.

@indigetal
Last active April 12, 2024 23:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save indigetal/5d49e4c2f62bb2c6007595ceb11cabb4 to your computer and use it in GitHub Desktop.
Save indigetal/5d49e4c2f62bb2c6007595ceb11cabb4 to your computer and use it in GitHub Desktop.
WORK IN PROGRESS: Monetize SpacesEngine using Paid Memberships Pro and Selection of Addons. See link to accompanying tutorial in comments.
<?php
/*
Plugin Name: Paid Memberships Pro Integration with SpacesEngine for BuddyBoss
Plugin URI: https://www.paidmembershipspro.com/create-a-plugin-for-pmpro-customizations/
Description: Integrate Paid Memberships Pro With SpacesEngine for BuddyBoss
Version: 1.1.5
Author: Brandon Meyer
Author URI: indigetal.com
*/
// Check if the pmpro_get_level_ids_for_group() exists before calling it
if ( ! function_exists( 'pmpro_get_level_ids_for_group' ) ) {
// Function does not exist, handle the error or log it
error_log( 'pmpro_get_level_ids_for_group() function does not exist' );
} else {
// EDIT HERE: First, lets retrieve the level IDs in the "Individual Levels" group (here it's Group ID: 3)
$individual_levels = pmpro_get_level_ids_for_group(3);
// EDIT HERE: Retrieve level IDs in the "SpacesEngine Levels" group (here it's Group ID: 4)
$spacesengine_levels = pmpro_get_level_ids_for_group(4);
}
// (OPTIONAL) Disable the pmpro redirect to levels page when user tries to register
add_filter("pmpro_login_redirect", "__return_false");
// (OPTIONAL) EDIT HERE: ...And give all newly registered users a default membership level (here it's Level ID: 1)
function my_pmpro_default_registration_level($user_id) {
pmpro_changeMembershipLevel(1, $user_id);
}
add_action('user_register', 'my_pmpro_default_registration_level');
// Restrict access to certain pages based on level group:
/*
* NOTE: If you are using a PMPro addon that requires new users to be validated, such as the
* Email Confirmation addon, users will not have access to restricted pages until they are
* validated. See the comment in Github Gist for an alternative approach that bypasses any
* validation requirements.
*
*/
function my_pmpro_custom_redirects() {
// Check if the function has already been executed to avoid multiple executions
if ( defined( 'MY_PMPRO_CUSTOM_REDIRECTS_EXECUTED' ) ) {
return;
}
// Set the flag to indicate that the function has been executed
define( 'MY_PMPRO_CUSTOM_REDIRECTS_EXECUTED', true );
global $individual_levels, $spacesengine_levels, $pmpro_pages; // Make the variables accessible inside the function
// EDIT HERE: The pages to be restricted
$page_names = array('create-space-page', 'other-restricted-page');
// First check if PMPro is active
if ( ! function_exists( 'pmpro_hasMembershipLevel' ) ) {
return;
}
// Loop through each page name
foreach ( $page_names as $page_name ) {
// Does the page name match?
if ( strpos( $_SERVER['REQUEST_URI'], '/' . $page_name . '/' ) !== false ) {
$current_page_name = $page_name; // Set the current page name
// EDIT HERE: If a visitor trying to access a restricted area doesn't have an individual membership... (EDIT 'other-restricted-page'):
if ( $current_page_name == 'other-restricted-page' && ! pmpro_hasMembershipLevel( $individual_levels ) && ( ! is_page( $pmpro_pages['levels'] ) || ! is_page( $pmpro_pages['checkout'] ) ) ) {
// Redirect them to the individual plans page (EDIT '/membership/individual-plans-page/'):
wp_safe_redirect( '/membership/individual-plans-page/' );
exit;
// EDIT HERE: Redirect members that don't have level ID's associated with SpacesEngine levels to the relevant plans page (EDIT 'create-space-page'):
} elseif ( $current_page_name == 'create-space-page' && ! pmpro_hasMembershipLevel( $spacesengine_levels ) && ( ! is_page( $pmpro_pages['levels'] ) || ! is_page( $pmpro_pages['checkout'] ) ) ) {
// Redirect them to the SpacesEngine plans page (EDIT '/membership/spacesengine-plans-page/'):
wp_safe_redirect( '/membership/spacesengine-plans-page/' );
exit;
}
}
}
}
add_action( 'template_redirect', 'my_pmpro_custom_redirects' );
/*
* Adjust the cost of a level at checkout based on the selection of a custom user field for
* upselling additional options to a plan. Accounts for recurring plans, and adjusts the cost based
* on if it's a monthly or annual plan (see https://gist.github.com/indigetal/e172e17230ceb1f9516b7c147b6898d4)
*/
function my_pmpro_adjustable_level_cost($level) {
// Check if the level object exists and has an 'id' property
if (isset($level->id) && ! in_array($level->id, $GLOBALS['spacesengine_levels'])) {
return $level; // Return the unmodified level if not in SpacesEngine group
}
// Set the field name here
$field_name = 'upgrade_listing';
// Flag to track if adjustments have been made
static $adjustments_made = false;
// Check if adjustments have already been made, and proceed if not
if (!$adjustments_made) {
// Check if the selected upgrade option exists
if (!empty($_REQUEST[$field_name])) {
// EDIT HERE: Set the monthly and annual fees of the available field options
$options = array(
'promoted' => array('monthly_fee' => 10, 'annual_fee' => 100),
'featured' => array('monthly_fee' => 15, 'annual_fee' => 120)
);
/*
* There are a couple of additional housekeeping items to ensure your users are
* presented with accurate information - see comment in
* https://gist.github.com/indigetal/e172e17230ceb1f9516b7c147b6898d4?permalink_comment_id=4651793#gistcomment-4651793
*/
// YOU ARE DONE EDITING. ENJOY!
if (isset($options[$_REQUEST[$field_name]])) {
$option_values = $options[$_REQUEST[$field_name]];
// Initialize extra fee
$extra_fee = 0;
// Determine the additional fee based on the selected option and cycle period
if ($level->cycle_period === 'Month') {
$extra_fee = $option_values['monthly_fee'];
} elseif ($level->cycle_period === 'Year') {
$extra_fee = $option_values['annual_fee'];
}
// Check if there is an extra fee
if ($extra_fee > 0) {
// Add the additional fee to the level's initial payment
$level->initial_payment += $extra_fee;
// Check if the level has a recurring subscription
if (pmpro_isLevelRecurring($level)) {
// Recurring payments
$level->billing_amount += $extra_fee;
}
}
}
}
// Set the flag to true to indicate adjustments have been made
$adjustments_made = true;
}
return $level;
}
add_filter("pmpro_checkout_level", "my_pmpro_adjustable_level_cost", 10, 1);
// Update the billing_amount and initial_payment columns in pmpro_memberships_users table after checkout
function db_adjusted_cost_sync($user_id, $morder) {
global $wpdb;
// Get the specific id of the membership entry
$membership_entry_id = $wpdb->get_var(
$wpdb->prepare(
"SELECT id FROM $wpdb->pmpro_memberships_users WHERE user_id = %d AND membership_id = %d AND status = 'active'",
$user_id,
$morder->membership_id
)
);
if ($membership_entry_id) {
// Update the specific row with the adjusted prices
$wpdb->update(
$wpdb->pmpro_memberships_users,
array(
'initial_payment' => $morder->subtotal,
'billing_amount' => $morder->subtotal
),
array('id' => $membership_entry_id),
array(
'%s', // Format for initial_payment
'%s' // Format for billing_amount
),
array('%d') // Format for id
);
}
}
add_action('pmpro_after_checkout', 'db_adjusted_cost_sync', 10, 2);
/* Hook into the pmpro_get_membership_levels_for_user filter to modify the output
* of the pmpro_getMembershipLevelsForUser() function, ensuring that the adjusted
* price is reflected correctly on the membership account page.
*/
function display_membership_adjusted_price( $levels, $user_id ) {
// Iterate through each level
foreach ( $levels as $key => $level ) {
// Check if the level belongs to the SpacesEngine group
if (in_array($level->id, $GLOBALS['spacesengine_levels'])) {
// Get the selected option for the user
$upgrade_listing = get_user_meta( $user_id, 'upgrade_listing', true );
// Check if the user selected either 'featured' or 'promoted'
$selected_option = ($upgrade_listing === 'featured' || $upgrade_listing === 'promoted');
// If both conditions are true, adjust the price of the level
if ($selected_option) {
// Adjust the price of each SpacesEngine level
$levels[$key]->initial_payment = my_pmpro_adjustable_level_cost( $levels[$key]->initial_payment, $level );
$levels[$key]->billing_amount = my_pmpro_adjustable_level_cost( $levels[$key]->billing_amount, $level );
}
}
}
return $levels;
}
add_filter( 'pmpro_get_membership_levels_for_user', 'display_membership_adjusted_price', 10, 2 );
// Set the listing as promoted or featured if the user purchases that option:
function set_featured_space_meta( $post_id ) {
$post_type = get_post_type( $post_id );
if ( 'wpe_wpspace' === $post_type ) {
$current_user_id = get_current_user_id();
$upgrade_listing = get_user_meta( $current_user_id, 'upgrade_listing', true );
if ( $upgrade_listing === 'promoted' ) {
update_post_meta( $post_id, 'featured_space', 1 );
} elseif ( $upgrade_listing === 'featured' ) {
update_post_meta( $post_id, 'featured_space', 2 );
}
}
}
add_action( 'save_post', 'set_featured_space_meta' );
// Set a Member's Space to Draft and update upgrade_listing user meta to none when their SpacesEngine-based Plan is Removed
function pmpro_space_level_removed_actions( $level_id, $user_id ) {
// Get the user roles
$usermeta = get_userdata( $user_id );
$user_roles = $usermeta->roles;
// Check if the user has any of the specified roles and if their membership level is not in the SpacesEngine level group:
$allowed_roles = array(
'subscriber',
'editor',
'contributor',
'author'
);
global $spacesengine_levels; // Declare the global variable
if ( array_intersect( $allowed_roles, $user_roles ) && ! in_array( $level_id, $spacesengine_levels ) ) {
update_user_posts_to_draft( $user_id );
update_user_upgrade_listing( $user_id, 'none' ); // Update upgrade_listing to 'none'
}
}
function update_user_posts_to_draft( $user_id ) {
// Get the user's posts
$args = array(
'author' => $user_id,
'post_type' => 'wpe_wpspace',
);
$user_posts = get_posts( $args );
foreach ( $user_posts as $user_post ) {
$post = array( 'ID' => $user_post->ID, 'post_status' => 'draft' );
wp_update_post( $post );
}
}
function update_user_upgrade_listing( $user_id, $value ) {
update_user_meta( $user_id, 'upgrade_listing', $value ); // Update upgrade_listing user meta
}
add_action( 'pmpro_after_change_membership_level', 'pmpro_space_level_removed_actions', 10, 2 );
// Define $option_name as a global variable
global $option_name;
// Define an array of options and corresponding filter hooks, see https://spacesengine.com/docs/filters/
$options_and_filters = array(
'enable_space_creation' => 'wpe_wps_is_space_creation_enabled',
'space_creation_limit' => 'wpe_wps_get_space_creation_limit',
'verify_spaces' => 'wpe_wps_is_verification_enabled',
//'feature_spaces' => 'wpe_wps_is_featured_enabled',
//'promote_spaces' => 'wpe_wps_is_promoted_enabled',
'enable_space_admins' => 'wpe_wps_get_admins_enabled',
'enable_space_editors' => 'wpe_wps_get_editors_enabled',
'enable_profile_picture' => 'wpe_wps_can_profile_picture_upload',
'enable_cover_image' => 'wpe_wps_can_cover_image_upload',
'enable_short_description' => 'wpe_wps_is_short_description_enabled',
'display_long_description' => 'wpe_wps_can_display_long_description',
'display_website' => 'wpe_wps_can_display_website',
'display_email' => 'wpe_wps_can_display_email',
'display_address' => 'wpe_wps_can_display_address',
'display_phone_number' => 'wpe_wps_can_display_phone',
'enable_work_hours' => 'wpe_wps_is_work_hours_enabled',
'display_social_icons' => 'wpe_wps_can_display_social_icons',
'enable_categories' => 'wpe_wps_is_categories_enabled',
'enable_messaging' => 'wpe_wps_is_messaging_enabled',
'enable_engagement' => 'wpe_wps_is_engagement_enabled',
'enable_contact_form' => 'wpe_wps_is_contact_form_enabled',
'display_whatsapp_number' => 'wpe_wps_can_display_whatsapp',
'enable_activity_feed' => 'wpe_wps_is_activity_feed_enabled',
'enable_action_buttons' => 'wpe_wps_is_action_buttons_enabled',
'enable_linked_groups' => 'wpe_wps_is_groups_enabled',
'enable_services' => 'wpe_wps_is_services_enabled',
'enable_header_video' => 'wpe_wps_is_video_enabled',
'enable_reviews' => 'wpe_wps_is_reviews_enabled',
'enable_jobs' => 'wpe_wps_is_jobs_enabled',
'enable_events' => 'wpe_wps_is_events_enabled',
'display_custom_fields' => 'wpe_wps_can_display_custom_fields'
);
// Add SpacesEngine filters to PMPro level settings page:
add_action( 'pmpro_membership_level_after_other_settings', 'enable_disable_se_features', 1, 2 );
function enable_disable_se_features($level) {
/* Output the structure of the $level object for inspection
echo '<pre>';
var_dump($level);
echo '</pre>';
*/
global $spacesengine_levels, $options_and_filters; // Declare the global variables
// Retrieve the ID from the level object
$id = $level->id;
// Check if the current level ID is in the organization levels array
if (in_array($id, $spacesengine_levels)) {
?>
<h2><?php esc_html_e('SpacesEngine Features', 'pmpro'); ?></h2>
<table class="form-table">
<tbody>
<?php foreach ($options_and_filters as $option_name => $filter_hook) : ?>
<tr>
<th scope="row"><?php echo esc_html($option_name); ?></th>
<td>
<?php
// Retrieve the option value for the current membership level
$option_value = get_option('wpe_wps_' . $option_name . '_level_' . $id);
?>
<input type="checkbox" name="<?php echo esc_attr($option_name); ?>" value="1" <?php checked($option_value, true); ?> />
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php
}
}
// Define the update_wpe_wps_filters() function
function update_wpe_wps_filters($filter_hook, $value, $level_id) {
// Access $option_name defined as a global variable
global $option_name, $options_and_filters;
// Extracting option name from filter hook
$option_name = array_search($filter_hook, $options_and_filters);
// Update options specific to the membership level
update_option('wpe_wps_' . $option_name . '_level_' . $level_id, $value);
do_action($filter_hook, $value, $level_id);
}
// Hook the update function to the pmpro_save_membership_level action
add_action('pmpro_save_membership_level', 'update_wpe_wps_filters_for_membership_level', 10, 1);
// Define the function to update all options and filters
function update_wpe_wps_filters_for_membership_level($level_id) {
// Access $option_name defined as a global variable
global $option_name, $options_and_filters;
// Iterate over each option and filter:
foreach ($options_and_filters as $option_name => $filter_hook) {
// Check if the checkbox is checked
$value = isset($_REQUEST[$option_name]) ? true : false;
// Call the update function
update_wpe_wps_filters($filter_hook, $value, $level_id);
}
}
/**
* Add a setting to the edit level settings to show or hide a membership level on the level select page.
* See https://www.paidmembershipspro.com/memberships-levels-page-order-hide-display-skip-mega-post/#h-option-2-add-a-setting-to-hide-levels-from-display-on-the-memberships-edit-level-admin
*/
//Save the pmpro_show_level_ID field
function pmpro_hide_level_from_levels_page_save( $level_id ) {
if ( $level_id <= 0 ) {
return;
}
$limit = $_REQUEST['pmpro_show_level'];
update_option( 'pmpro_show_level_'.$level_id, $limit );
}
add_action( 'pmpro_save_membership_level','pmpro_hide_level_from_levels_page_save' );
//Display the setting for the pmpro_show_level_ID field on the Edit Membership Level page
function pmpro_hide_level_from_levels_page_settings() {
?>
<h3 class='topborder'><?php esc_html_e( 'Membership Level Visibility', 'pmpro' ); ?></h3>
<table class='form-table'>
<tbody>
<tr>
<th scope='row' valign='top'><label for='pmpro_show_level'><?php esc_html_e( 'Show Level', 'pmpro' );?>:</label></th>
<td>
<?php
if ( isset( $_REQUEST['edit'] ) ) {
$edit = $_REQUEST['edit'];
$pmpro_show_level = get_option( 'pmpro_show_level_' . $edit );
if ( $pmpro_show_level === false ) {
$pmpro_show_level = 1;
}
} else {
$limit = '';
}
?>
<select id='pmpro_show_level' name='pmpro_show_level'>
<option value='1' <?php if ( $pmpro_show_level == 1 ) { ?>selected='selected'<?php } ?>><?php esc_html_e( 'Yes, show this level in the [pmpro_levels] display.', 'pmpro' );?></option>
<option value='0' <?php if ( ! $pmpro_show_level ) { ?>selected='selected'<?php } ?>><?php _e( 'No, hide this level in the [pmpro_levels] display.', 'pmpro' );?></option>
</select>
</td>
</tr>
</tbody>
</table>
<?php
}
add_action( 'pmpro_membership_level_after_other_settings', 'pmpro_hide_level_from_levels_page_settings' );
?>
@indigetal
Copy link
Author

See full tutorial for how to implement this integration here.

@indigetal
Copy link
Author

indigetal commented Apr 4, 2024

The original custom redirect code snippet from Paid Memberships Pro:

/**
 * This recipe will redirect non-members away from a page
 * according to the page name of the URL.
 */
function my_pmpro_redirect_url_page_name() {

	/*
	 * Set the page name you want to redirect non-members from here.
	 * For example, if the URL is https://example.com/my-page-name/ then set
	 * the $page_name variable's value to my-page-name,
	 * e.g. $page_name = 'my-page-name';
	 *
	 * Note: If a parent page's name matches all child pages will also be redirected.
	*/
	$page_names = array( 'page-name-here', 'another-page-name', 'parent-page-name/child-page-name' );

	// First check if PMPro is active
	if ( ! function_exists( 'pmpro_hasMembershipLevel' ) ) {
		return;
	}

	global $pmpro_pages;

	foreach ( $page_names as $page_name ) {
		// Does the page name match?
		if ( strpos( $_SERVER['REQUEST_URI'], '/' . $page_name . '/' ) !== false ) {
			// Redirect members that doesn't belong to certain levels
			if ( ! pmpro_hasMembershipLevel() && ( ! is_page( $pmpro_pages['levels'] ) || ! is_page( $pmpro_pages['checkout'] ) ) ) {
				wp_safe_redirect( pmpro_url( 'levels' ) );
				exit;
			}
		}
	}
}
add_action( 'template_redirect', 'my_pmpro_redirect_url_page_name' );

@indigetal
Copy link
Author

indigetal commented Apr 5, 2024

Example implementation of the custom redirect function that utilizes the default membership to restrict access to the members directory and social profiles from logged out users, and also restricts the create new blog post of the BuddyPress User Blog plugin (replaces lines 51-78):

    // EDIT HERE: The pages to be restricted
    $page_names = array('members', 'new-post', 'create-space');

    // First check if PMPro is active
    if ( ! function_exists( 'pmpro_hasMembershipLevel' ) ) {
        return;
    }

    // Loop through each page name
    foreach ( $page_names as $page_name ) {
        
        // Does the page name match?
        if ( strpos( $_SERVER['REQUEST_URI'], '/' . $page_name . '/' ) !== false ) {
            
            $current_page_name = $page_name; // Set the current page name
            
            // EDIT HERE: If a visitor trying to access the members area doesn't have any level ID...
            if ( $current_page_name == 'members' && ! pmpro_hasMembershipLevel() && ( ! is_page( $pmpro_pages['levels'] ) || ! is_page( $pmpro_pages['checkout'] ) ) ) {
                // Redirect them to the login page:
                wp_safe_redirect( '/wp-login.php' );
                exit;
            // EDIT HERE: If a visitor trying to create a new blog post doesn't have the relevant membership...
            } elseif ( $current_page_name == 'new-post' && ! pmpro_hasMembershipLevel( $individual_levels ) && ( ! is_page( $pmpro_pages['levels'] ) || ! is_page( $pmpro_pages['checkout'] ) ) ) {
                // EDIT HERE: Redirect them to the individual plans page:
                wp_safe_redirect('/membership/individual-plans/');
                exit;
            // EDIT HERE: If a visitor trying to create a new Space doesn't have a SpacesEngine membership...
            } elseif ( $current_page_name == 'create-space' && ! pmpro_hasMembershipLevel( $spacesengine_levels ) && ( ! is_page( $pmpro_pages['levels'] ) || ! is_page( $pmpro_pages['checkout'] ) ) ) {
                // EDIT HERE: Redirect them to the SpacesEngine plans page:
                wp_safe_redirect( '/membership/spacesengine-plans/' );
                exit;
            }
        }
    }
}

@indigetal
Copy link
Author

indigetal commented Apr 8, 2024

Alternative version of custom redirect function that bypasses validation:

// Restrict access to certain pages based on level group:
function my_pmpro_custom_redirects() {
    
    // Check if the function has already been executed to avoid multiple executions
    if ( defined( 'MY_PMPRO_CUSTOM_REDIRECTS_EXECUTED' ) ) {
        return;
    }
    // Set the flag to indicate that the function has been executed
    define( 'MY_PMPRO_CUSTOM_REDIRECTS_EXECUTED', true );
    
    global $individual_levels, $spacesengine_levels, $pmpro_pages; // Make the variables accessible inside the function
    
    // Retrieve the membership levels for the current user
    $user_membership_levels = pmpro_getMembershipLevelsForUser(); // Used instead of pmpro_hasMembershipLevel() that may return false because of addons or custom code validation requests
    // Extract the IDs of the membership levels the user belongs to
    $user_level_ids = empty( $user_membership_levels ) ? array() : wp_list_pluck( $user_membership_levels, 'id' );
    // Check if the user has any individual membership level from the specified list
    $user_has_individual_level = ! empty( array_intersect( $user_level_ids, $individual_levels ) );
    // Check if the user has the SpacesEngine membership level from the specified list
    $user_has_spacesengine_level = ! empty( array_intersect( $user_level_ids, $spacesengine_levels ) );

    // EDIT HERE: The pages to be restricted
    $page_names = array('members', 'new-post', 'create-space');

    // First check if PMPro is active
    if ( ! function_exists( 'pmpro_hasMembershipLevel' ) ) {
        return;
    }

    // Loop through each page name
    foreach ( $page_names as $page_name ) {
        
        // Does the page name match?
        if ( strpos( $_SERVER['REQUEST_URI'], '/' . $page_name . '/' ) !== false ) {
            
            $current_page_name = $page_name; // Set the current page name
            
            // EDIT HERE: If a visitor trying to access the members area doesn't have any level ID...
            if ( $current_page_name == 'members' && empty($user_membership_levels) && ( ! is_page( $pmpro_pages['levels'] ) || ! is_page( $pmpro_pages['checkout'] ) ) ) {
                // Redirect them to the login page:
                wp_safe_redirect( '/wp-login.php' );
                exit;
            // EDIT HERE: If a visitor trying to create a new blog post doesn't have the relevant membership...
            } elseif ( $current_page_name == 'new-post' && !$user_has_individual_level && ( ! is_page( $pmpro_pages['levels'] ) || ! is_page( $pmpro_pages['checkout'] ) ) ) {
                // EDIT HERE: Redirect them to the individual plans page:
                wp_safe_redirect('/membership/individual-plans/');
                exit;
            // EDIT HERE: If a visitor trying to create a new Space doesn't have a SpacesEngine membership...
            } elseif ( $current_page_name == 'create-space' && !$user_has_spacesengine_level && ( ! is_page( $pmpro_pages['levels'] ) || ! is_page( $pmpro_pages['checkout'] ) ) ) {
                // EDIT HERE: Redirect them to the SpacesEngine plans page:
                wp_safe_redirect( '/membership/spacesengine-plans/' );
                exit;
            }
        }
    }
}
add_action( 'template_redirect', 'my_pmpro_custom_redirects' );

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment