--- a/simple-membership/classes/admin-includes/class.swpm-payment-settings-menu-tab.php
+++ b/simple-membership/classes/admin-includes/class.swpm-payment-settings-menu-tab.php
@@ -523,16 +523,31 @@
</p>
</td>
</tr>
+
<tr valign="top">
<th scope="row">
<label>
- <?php _e('Webhook Signing Secret Key (Optional)', 'simple-membership'); ?>
+ <?php _e('Webhook Endpoint URL', 'simple-membership'); ?>
+ </label>
+ </th>
+ <td>
+ <kbd><?php echo SIMPLE_WP_MEMBERSHIP_SITE_HOME_URL . '/?swpm_process_stripe_subscription=1&hook=1'; ?></kbd>
+ <p class="description">
+ <?php _e('This is the webhook endpoint URL that you will need to configure in your Stripe account. You can get more info in the', 'simple-membership') ?> <a href="https://simple-membership-plugin.com/sca-compliant-stripe-subscription-button/#create-a-webhook-in-your-stripe-account" target="_blank"><?php _e('documentation', 'simple-membership') ?></a>.
+ </p>
+ </td>
+ </tr>
+ <tr valign="top">
+ <th scope="row">
+ <label>
+ <?php _e('Webhook Signing Secret Key', 'simple-membership'); ?>
</label>
</th>
<td>
<input type="text" name="stripe-webhook-signing-secret" size="100" value="<?php echo $stripe_webhook_signing_secret_key; ?>">
<p class="description">
- <?php _e('Enter a webhook signing secret key to apply webhook event protection.', 'simple-membership'); ?>
+ <?php _e('Enter the webhook signing secret from your Stripe dashboard here. ', 'simple-membership'); ?>
+ <?php _e('Read <a href="https://simple-membership-plugin.com/configuring-the-stripe-webhook-signing-secret/" target="_blank">this documentation</a> to learn more.', 'simple-membership'); ?>
</p>
</td>
</tr>
--- a/simple-membership/classes/class.simple-wp-membership.php
+++ b/simple-membership/classes/class.simple-wp-membership.php
@@ -961,8 +961,10 @@
'ajax_url' => $ajax_url,
'query_args' => isset($params['query_args']) ? $params['query_args'] : array(),
)), "before");
-
- wp_add_inline_script($handle, "var form_id = '".$params['form_id']."';", "before");
+
+ if (isset($params['form_id']) && !empty($params['form_id'])){
+ wp_add_inline_script($handle, "var form_id = '".$params['form_id']."';", "before");
+ }
if (isset($params['custom_pass_pattern_validator']) && !empty($params['custom_pass_pattern_validator'])) {
wp_add_inline_script($handle, "var custom_pass_pattern_validator = ".$params['custom_pass_pattern_validator'].";", "before");
--- a/simple-membership/classes/class.swpm-auth.php
+++ b/simple-membership/classes/class.swpm-auth.php
@@ -679,7 +679,7 @@
public function get_expire_date() {
if ( $this->isLoggedIn ) {
- return SwpmUtils::get_formatted_expiry_date( $this->get( 'subscription_starts' ), $this->get( 'subscription_period' ), $this->get( 'subscription_duration_type' ) );
+ return SwpmMemberUtils::get_formatted_expiry_date_by_user_id( SwpmMemberUtils::get_logged_in_members_id() );
}
return '';
}
--- a/simple-membership/classes/class.swpm-level-form.php
+++ b/simple-membership/classes/class.swpm-level-form.php
@@ -43,7 +43,7 @@
return;
}
- $subscription_period = filter_input(INPUT_POST, 'subscription_period_'. $subscript_duration_type);
+ $subscription_period = isset($_POST['subscription_period_'. $subscript_duration_type]) ? $_POST['subscription_period_'. $subscript_duration_type] : '';
if (($subscript_duration_type == SwpmMembershipLevel::FIXED_DATE)){
$dateinfo = date_parse($subscription_period);
if ($dateinfo['warning_count']|| $dateinfo['error_count']){
@@ -54,6 +54,29 @@
return;
}
+ if ( $subscript_duration_type == SwpmMembershipLevel::ANNUAL_FIXED_DATE ){
+ if (!is_array($subscription_period)){
+ $this->errors['subscription_period'] = __("Annual expiry date is not valid.", "simple-membership");
+ }
+
+ $subscription_period = implode('-', array(
+ date('Y'),
+ SwpmUtils::pad_zero($subscription_period['m']),
+ SwpmUtils::pad_zero($subscription_period['d']),
+ ));
+
+ $dateinfo = date_parse($subscription_period);
+
+ if ($dateinfo['warning_count']|| $dateinfo['error_count']){
+ $this->errors['subscription_period'] = __("Date format is not valid. " . $subscription_period, "simple-membership");
+ return;
+ }
+
+ $this->sanitized['subscription_period'] = sanitize_text_field($subscription_period);
+
+ return;
+ }
+
if (!is_numeric($subscription_period)) {
$this->errors['subscription_period'] = SwpmUtils::_("Access duration must be > 0.");
return;
--- a/simple-membership/classes/class.swpm-membership-level.php
+++ b/simple-membership/classes/class.swpm-membership-level.php
@@ -11,6 +11,7 @@
const MONTHS = 3;
const YEARS = 4;
const FIXED_DATE = 5;
+ const ANNUAL_FIXED_DATE = 6;
private static $_instance = null;
@@ -83,9 +84,20 @@
'meta_context'=> 'account-status',
);
+ $annual_fixed_date_min_period = isset( $_POST['annual_fixed_date_min_period'] ) && !empty($_POST['annual_fixed_date_min_period']) ? absint(sanitize_text_field( $_POST['annual_fixed_date_min_period'] )) : "";
+ $annual_fixed_date_min_period = array(
+ 'meta_key'=>'annual_fixed_date_min_period',
+ 'level_id'=> $id,
+ 'meta_label'=> 'Annual Fixed Date Minimum Period',
+ 'meta_value'=> $annual_fixed_date_min_period,
+ 'meta_type'=> 'number',
+ 'meta_context'=> 'subscription-type',
+ );
+
$custom = apply_filters('swpm_admin_add_membership_level', array());
$custom[] = $after_activation_redirect_page_meta;
$custom[] = $default_account_status_meta;
+ $custom[] = $annual_fixed_date_min_period;
$this->save_custom_fields($id, $custom);
$message = array('succeeded' => true, 'message' => '<p>' . SwpmUtils::_('Membership Level Creation Successful.') . '</p>');
SwpmTransfer::get_instance()->set('status', $message);
@@ -137,9 +149,20 @@
'meta_context'=> 'account-status',
);
- $custom = apply_filters('swpm_admin_edit_membership_level', array(), $id);
+ $annual_fixed_date_min_period = isset( $_POST['annual_fixed_date_min_period'] ) && !empty($_POST['annual_fixed_date_min_period']) ? absint(sanitize_text_field( $_POST['annual_fixed_date_min_period'] )) : "";
+ $annual_fixed_date_min_period = array(
+ 'meta_key'=>'annual_fixed_date_min_period',
+ 'level_id'=> $id,
+ 'meta_label'=> 'Annual Fixed Date Minimum Period',
+ 'meta_value'=> $annual_fixed_date_min_period,
+ 'meta_type'=> 'number',
+ 'meta_context'=> 'subscription-type',
+ );
+
+ $custom = apply_filters('swpm_admin_edit_membership_level', array(), $id);
$custom[] = $after_activation_redirect_page_meta;
$custom[] = $default_account_status_meta;
+ $custom[] = $annual_fixed_date_min_period;
$this->save_custom_fields($id, $custom);
$message = array('succeeded' => true, 'message' => '<p>'. SwpmUtils::_('Membership Level Updated Successfully.') . '</p>');
SwpmTransfer::get_instance()->set('status', $message);
--- a/simple-membership/classes/class.swpm-membership-levels.php
+++ b/simple-membership/classes/class.swpm-membership-levels.php
@@ -59,6 +59,10 @@
if ($item['subscription_duration_type'] == SwpmMembershipLevel::YEARS) {
return $item['subscription_period'] . " Year(s)";
}
+ if ($item['subscription_duration_type'] == SwpmMembershipLevel::ANNUAL_FIXED_DATE) {
+ $formatted_date = date('F d', strtotime($item['subscription_period']));
+ return $formatted_date;
+ }
}
if ($column_name == 'role') {
return ucfirst($item['role']);
@@ -164,6 +168,7 @@
$custom_fields = SwpmMembershipLevelCustom::get_instance_by_id($id);
$after_activation_redirect_page = sanitize_url($custom_fields->get('after_activation_redirect_page'));
$default_account_status = sanitize_text_field($custom_fields->get('default_account_status'));
+ $annual_fixed_date_min_period = sanitize_text_field($custom_fields->get('annual_fixed_date_min_period'));
include_once(SIMPLE_WP_MEMBERSHIP_PATH . 'views/admin_edit_level.php');
return false;
}
--- a/simple-membership/classes/class.swpm-utils-misc.php
+++ b/simple-membership/classes/class.swpm-utils-misc.php
@@ -1446,4 +1446,53 @@
SwpmMiscUtils::mail( $to_email, $subject, $body, $headers );
SwpmLog::log_simple_debug( 'Account activation email for member ID: '.$member_id.' successfully sent to: ' . $to_email . '. From email address value used: ' . $from_address, true );
}
+
+
+ public static function get_months_data(int $year = null): array {
+ $year = $year ?? (int) date('Y');
+ $data = array();
+
+ for ($m = 1; $m <= 12; $m++) {
+ $date = DateTime::createFromFormat('Y-n-j', "$year-$m-1");
+
+ $data[] = [
+ 'm' => $m,
+ 'name' => $date->format('F'),
+ 'days' => $date->format('t'),
+ ];
+ }
+
+ return $data;
+ }
+
+ public static function month_day_selector($selected_date = '') {
+ $currentYear = (int) date('Y');
+
+ if (!empty($selected_date)){
+ $selected_date = strtotime($selected_date);
+ } else {
+ $selected_date = strtotime(date('Y-01-01'));
+ }
+
+ $months = self::get_months_data($currentYear);
+
+ SimpleWpMembership::enqueue_validation_scripts_v2( 'swpm-month-day-selector' );
+
+ ?>
+ <span class="swpm-month-day-selector" data-day-month-options="<?php echo esc_attr(json_encode($months)) ?>">
+ <select class="swpm-month-selector" name="subscription_period_<?php echo SwpmMembershipLevel::ANNUAL_FIXED_DATE?>[m]">
+ <?php foreach ($months as $month) { ?>
+ <option value="<?php echo esc_attr($month['m']) ?>" <?php selected( intval(date('m', $selected_date)), $month['m'] ) ?>><?php echo esc_attr($month['name']) ?></option>
+ <?php } ?>
+ </select>
+ <select class="swpm-day-selector" name="subscription_period_<?php echo SwpmMembershipLevel::ANNUAL_FIXED_DATE?>[d]">
+ <?php
+ $selected_month_data = $months[intval(date('m', $selected_date)) - 1];
+ for ($i = 1; $i <= $selected_month_data['days']; $i++) { ?>
+ <option value="<?php echo esc_attr($i) ?>" <?php selected( intval(date('d', $selected_date)), $i ) ?> ><?php echo esc_attr(SwpmUtils::pad_zero($i)) ?></option>
+ <?php } ?>
+ </select>
+ </span>
+ <?php
+ }
}
--- a/simple-membership/classes/class.swpm-utils.php
+++ b/simple-membership/classes/class.swpm-utils.php
@@ -97,7 +97,48 @@
$permission = SwpmPermission::get_instance( $user->membership_level );
if ( SwpmMembershipLevel::FIXED_DATE == $permission->get( 'subscription_duration_type' ) ) {
return strtotime( $permission->get( 'subscription_period' ) );
+
+ } else if ( SwpmMembershipLevel::ANNUAL_FIXED_DATE == $permission->get( 'subscription_duration_type' ) ) {
+ $user_sub_start_date = new DateTime($user->subscription_starts);
+
+ $current_year = intval(date('Y'));
+
+ $expiry_date = new DateTime($permission->get( 'subscription_period' ));
+
+ // Replace year with current year
+ $expiry_date->setDate(
+ $current_year,
+ (int) $expiry_date->format('m'),
+ (int) $expiry_date->format('d')
+ );
+
+ $expiry_timestamp = $expiry_date->getTimestamp();
+
+ // Check if expiry date has reached or not.
+ if ($user_sub_start_date < $expiry_date) {
+ // Expiry date has not reached year. Now check if expiry date and user subscription date satisfies min period days.
+
+ $diff = $user_sub_start_date->diff($expiry_date);
+
+ $custom_fields = SwpmMembershipLevelCustom::get_instance_by_id($user->membership_level);
+ $annual_fixed_date_min_period = sanitize_text_field($custom_fields->get('annual_fixed_date_min_period'));
+ $annual_fixed_date_min_period = absint($annual_fixed_date_min_period);
+
+ if ($diff->days < $annual_fixed_date_min_period){
+ $expiry_date->modify('+1 year'); // expiry date is in next year.
+
+ $expiry_timestamp = $expiry_date->getTimestamp();
+ }
+ } else {
+ // User sub started AFTER membership level expiry date of this year.
+ $expiry_date->modify('+1 year'); // expiry date is in next year.
+
+ $expiry_timestamp = $expiry_date->getTimestamp();
+ }
+
+ return $expiry_timestamp;
}
+
$days = self::calculate_subscription_period_days( $permission->get( 'subscription_period' ), $permission->get( 'subscription_duration_type' ) );
if ( $days == 'noexpire' ) {
return PHP_INT_MAX; // which is equivalent to
@@ -123,6 +164,9 @@
//Membership will expire after a fixed date.
return self::get_formatted_and_translated_date_according_to_wp_settings( $subscription_duration );
}
+ if ( $subscription_duration_type == SwpmMembershipLevel::ANNUAL_FIXED_DATE ) {
+ return self::get_formatted_and_translated_date_according_to_wp_settings( $subscription_duration );
+ }
$expires = self::calculate_subscription_period_days( $subscription_duration, $subscription_duration_type );
if ( $expires == 'noexpire' ) {
@@ -797,4 +841,7 @@
return $shortcode;
}
+ public static function pad_zero($number = 0): string {
+ return str_pad((string) $number, 2, '0', STR_PAD_LEFT);
+ }
}
--- a/simple-membership/ipn/swpm-stripe-webhook-handler.php
+++ b/simple-membership/ipn/swpm-stripe-webhook-handler.php
@@ -35,6 +35,15 @@
} else {
SwpmLog::log_simple_debug( 'Stripe webhook event data validated successfully!', true );
}
+ } else {
+ if ( empty( self::validate_webhook_data_no_signing_key($input) ) ) {
+ //Invalid webhook data received. Don't process this request.
+ http_response_code( 400 );
+ echo 'Error: Invalid webhook data received.';
+ exit();
+ } else {
+ SwpmLog::log_simple_debug( 'Stripe webhook event data validated successfully!', true );
+ }
}
$type = isset($event_json->type) ? $event_json->type : '';
@@ -268,6 +277,129 @@
return $event_json;
}
+ public static function validate_webhook_data_no_signing_key($event_data_raw){
+ $received_event = json_decode( $event_data_raw );
+
+ $events_to_validate = array(
+ 'invoice.payment_succeeded',
+ 'customer.subscription.deleted',
+ 'charge.refunded'
+ );
+
+ if (!in_array($received_event->type, $events_to_validate)) {
+ // No need to validate other unused events.
+ return true;
+ }
+
+ $max_allowed_event_creation_time_diff = 6 * 60 * 60; // 6 hours.
+
+ $received_sub_id = '';
+
+ $received_event_object = $received_event->data->object;
+ $received_object_name = $received_event_object->object;
+
+ switch(strtolower($received_object_name)){
+ case 'subscription':
+ $received_sub_id = isset($received_event_object->id) ? $received_event_object->id : '';
+ break;
+ case 'invoice':
+ $received_sub_id = isset($received_event_object->parent->subscription_details->subscription) ? $received_event_object->parent->subscription_details->subscription : '';
+
+ $billing_reason = isset( $received_event_object->billing_reason ) ? $received_event_object->billing_reason : '';
+ if ( $billing_reason != 'subscription_cycle' ) {
+ // We don't need to validate invoice event with billing reason other than subscription_cycle.
+ return true;
+ }
+
+ break;
+ case 'charge':
+ // Change object does not directly contains any sub id, so its not possible to get the sub agreement cpt id hence not the payment button id.
+ // So its not possible to get the stripe api secret key. Thats why we are only checking the event creation time to validate this event.
+ if ((time() - $received_event->created) > $max_allowed_event_creation_time_diff ) {
+ SwpmLog::log_simple_debug('Error: Event creation time is too far in the past!', false);
+ return false;
+ } else {
+ return true;
+ }
+ default:
+ SwpmLog::log_simple_debug("Error: Invalid webhook event object '" . $received_object_name . "'", false);
+ return false;
+ }
+
+ $sub_agreement_cpt_id = SWPM_Utils_Subscriptions::get_subscription_agreement_cpt_id_by_subs_id($received_sub_id);
+
+ if (empty($sub_agreement_cpt_id)) {
+ SwpmLog::log_simple_debug("Error: can't retrieve subscription cpt record!", false);
+ return false;
+ }
+
+ // Check if the sandbox mode is enabled
+ $sandbox_enabled = SwpmSettings::get_instance()->get_value( 'enable-sandbox-testing' );
+
+ $payment_button_id = get_post_meta($sub_agreement_cpt_id, 'payment_button_id', true);
+
+ $api_keys = SwpmMiscUtils::get_stripe_api_keys_from_payment_button( $payment_button_id, !$sandbox_enabled );
+
+ if (empty($api_keys['secret'])){
+ SwpmLog::log_simple_debug('Error: The Stripe API secret key could not be retrieved. Could not validate this webhook!', false);
+ return false;
+ }
+
+ // Include the Stripe library.
+ SwpmMiscUtils::load_stripe_lib();
+
+ StripeStripe::setApiKey( $api_keys['secret'] );
+
+ try {
+ // Re-fetch the event again by event id. Then check if the subscription id and creation time is valid or not.
+ $event = StripeEvent::retrieve($received_event->id);
+
+ // Check if invalid event creation time.
+ if ($event->created !== $received_event->created || (time() - $event->created) > $max_allowed_event_creation_time_diff ) {
+ SwpmLog::log_simple_debug('Error: Event creation time is too far in the past!', false);
+ return false;
+ }
+
+ $sub_id = '';
+ $event_object = $event->data->object;
+ $event_object_name = $event_object->object;
+
+ if ($event_object_name != $received_object_name) {
+ SwpmLog::log_simple_debug("Error: Webhook event object mismatch!", false);
+ return false;
+ }
+
+ switch($event_object_name){
+ case 'subscription':
+ $sub_id = isset($event_object->id) ? $event_object->id : '';
+ break;
+ case 'invoice':
+ $sub_id = isset($event_object->parent->subscription_details->subscription) ? $event_object->parent->subscription_details->subscription : '';
+ break;
+ default:
+ SwpmLog::log_simple_debug("Error: Invalid webhook event object '" . $received_object_name . "'", false);
+ return false;
+ }
+
+ // Check if subscription id mismatch.
+ if ($sub_id != $received_sub_id) {
+ SwpmLog::log_simple_debug('Error: Subscription ID mismatch!', false);
+ return false;
+ }
+
+ } catch(UnexpectedValueException $e) {
+ // Invalid payload. Don't Process this request.
+ SwpmLog::log_simple_debug('Error parsing payload: ' . $e->getMessage() , false);
+ return false;
+ } catch(Exception $e) {
+ // Invalid signature. Don't Process this request.
+ SwpmLog::log_simple_debug('Error: ' . $e->getMessage() , false);
+ return false;
+ }
+
+ // Everything seems fine.
+ return true;
+ }
}
new SwpmStripeWebhookHandler();
--- a/simple-membership/ipn/swpm_handle_subsc_ipn.php
+++ b/simple-membership/ipn/swpm_handle_subsc_ipn.php
@@ -378,6 +378,10 @@
// This is a level with a "fixed expiry date" duration.
swpm_debug_log_subsc( 'This is a level with a "fixed expiry date" duration.', true );
swpm_debug_log_subsc( 'Nothing to do here. The account will expire on the fixed set date.', true );
+ } elseif ( SwpmMembershipLevel::ANNUAL_FIXED_DATE == $subs_duration_type ) {
+ // This is a level with an "annual fixed date" duration.
+ swpm_debug_log_subsc( 'This is a level with a "annual fixed date" duration.', true );
+ swpm_debug_log_subsc( 'Nothing to do here. The account will expire on the set date.', true );
} else {
// This is a level with "duration" type expiry (example: 30 days, 1 year etc). subscription_period has the duration/period.
$subs_period = $level_row->subscription_period;
--- a/simple-membership/simple-wp-membership.php
+++ b/simple-membership/simple-wp-membership.php
@@ -1,7 +1,7 @@
<?php
/*
Plugin Name: Simple Membership
-Version: 4.7.0
+Version: 4.7.1
Plugin URI: https://simple-membership-plugin.com/
Author: smp7, wp.insider
Author URI: https://simple-membership-plugin.com/
@@ -17,7 +17,7 @@
}
//Define plugin constants
-define( 'SIMPLE_WP_MEMBERSHIP_VER', '4.7.0' );
+define( 'SIMPLE_WP_MEMBERSHIP_VER', '4.7.1' );
define( 'SIMPLE_WP_MEMBERSHIP_DB_VER', '1.5' );
define( 'SIMPLE_WP_MEMBERSHIP_SITE_HOME_URL', home_url() );
define( 'SIMPLE_WP_MEMBERSHIP_PATH', dirname( __FILE__ ) . '/' );
--- a/simple-membership/views/admin_add_level.php
+++ b/simple-membership/views/admin_add_level.php
@@ -13,7 +13,7 @@
<form action="" method="post" name="swpm-create-level" id="<?php echo esc_attr($form_id) ?>" class="swpm-validate-form">
<input name="action" type="hidden" value="createlevel" />
<h3><?php echo SwpmUtils::_('Add Membership Level'); ?></h3>
-<p>
+<p class="swpm-grey-box">
<?php
echo __('Create new membership level.', 'simple-membership');
echo __(' Refer to ', 'simple-membership');
@@ -47,6 +47,12 @@
<input type="text" value="" name="subscription_period_<?php echo SwpmMembershipLevel::YEARS?>"> <?php echo SwpmUtils::_('Years (Access expires after given number of years)')?></p>
<p><input type="radio" value="<?php echo SwpmMembershipLevel::FIXED_DATE?>" name="subscription_duration_type" /> <?php echo SwpmUtils::_('Fixed Date Expiry')?>
<input type="text" class="swpm-date-picker" value="<?php echo date('Y-m-d');?>" name="subscription_period_<?php echo SwpmMembershipLevel::FIXED_DATE?>"> <?php echo SwpmUtils::_('(Access expires on a fixed date)')?></p>
+ <p><input type="radio" value="<?php echo SwpmMembershipLevel::ANNUAL_FIXED_DATE?>" name="subscription_duration_type" /> <?php _e('Annual Expiration Date','wp-express-checkout')?>
+ <?php SwpmMiscUtils::month_day_selector(); ?>
+ <?php printf(__(' with a minimum period of %s days', 'simple-membership'), '<span><input name="annual_fixed_date_min_period" type="number" min="0" value="" style="width: 60px;"></span>')?>
+ <?php _e('(Memberships will expire on this date every year. Example value: December 31 for calendar-year memberships or June 30 for fiscal alignments). ', 'simple-membership'); ?>
+ <?php echo '<a href="https://simple-membership-plugin.com/annual-calendar-or-fiscal-year-memberships/" target="_blank">' . __('View Documentation', 'simple-membership') . '</a>.'; ?>
+ </p>
</td>
</tr>
<tr class="form-field">
--- a/simple-membership/views/admin_add_ons_page.php
+++ b/simple-membership/views/admin_add_ons_page.php
@@ -252,8 +252,16 @@
'description' => 'Allows you to signup the member to your MailChimp list after registration',
'page_url' => 'https://simple-membership-plugin.com/signup-members-mailchimp-list/',
);
- array_push($addons_data, $addon_30);
-
+ array_push($addons_data, $addon_30);
+
+ $addon_31 = array(
+ 'name' => 'Social Login Addon',
+ 'thumbnail' => SIMPLE_WP_MEMBERSHIP_URL . '/images/addons/swpm-social-login-addon.png',
+ 'description' => 'Integrates Google, Facebook social login options for Simple Membership users.',
+ 'page_url' => 'https://simple-membership-plugin.com/simple-membership-social-login-addon/',
+ );
+ array_push($addons_data, $addon_31);
+
/*** Show the addons list ***/
foreach ($addons_data as $addon) {
$output .= '<div class="swpm_addon_item_canvas">';
--- a/simple-membership/views/admin_edit_level.php
+++ b/simple-membership/views/admin_edit_level.php
@@ -15,7 +15,7 @@
<input name="action" type="hidden" value="editlevel" />
<?php wp_nonce_field( 'edit_swpmlevel_admin_end', '_wpnonce_edit_swpmlevel_admin_end' ) ?>
<h2><?php echo SwpmUtils::_('Edit membership level'); ?></h2>
-<p>
+<p class="swpm-grey-box">
<?php
echo __('You can edit details of a selected membership level from this interface. ', 'simple-membership');
echo __(' Refer to ', 'simple-membership');
@@ -58,7 +58,15 @@
<input type="text" value="<?php echo checked(SwpmMembershipLevel::YEARS,$subscription_duration_type,false)? $subscription_period: "";?>" name="subscription_period_<?php echo SwpmMembershipLevel::YEARS?>"> <?php echo SwpmUtils::_('Years (Access expires after given number of years)')?></p>
<p><input type="radio" <?php echo checked(SwpmMembershipLevel::FIXED_DATE,$subscription_duration_type,false)?> value="<?php echo SwpmMembershipLevel::FIXED_DATE?>" name="subscription_duration_type" /> <?php echo SwpmUtils::_('Fixed Date Expiry')?>
- <input type="text" class="swpm-date-picker" value="<?php echo checked(SwpmMembershipLevel::FIXED_DATE,$subscription_duration_type,false)? $subscription_period: "";?>" name="subscription_period_<?php echo SwpmMembershipLevel::FIXED_DATE?>" id="subscription_period_<?php echo SwpmMembershipLevel::FIXED_DATE?>"> <?php echo SwpmUtils::_('(Access expires on a fixed date)')?></p>
+ <input type="text" class="swpm-date-picker" value="<?php echo checked(SwpmMembershipLevel::FIXED_DATE,$subscription_duration_type,false)? $subscription_period: "";?>" name="subscription_period_<?php echo SwpmMembershipLevel::FIXED_DATE?>" id="subscription_period_<?php echo SwpmMembershipLevel::FIXED_DATE?>"> <?php echo SwpmUtils::_('(Access expires on a fixed date)')?></p>
+
+ <?php $is_annual_fixed_date_checked = checked(SwpmMembershipLevel::ANNUAL_FIXED_DATE, $subscription_duration_type, false) ?>
+ <p><input type="radio" <?php echo !empty($is_annual_fixed_date_checked) ? 'checked' : '' ?> value="<?php echo SwpmMembershipLevel::ANNUAL_FIXED_DATE?>" name="subscription_duration_type" /> <?php _e('Annual Expiration Date','wp-express-checkout')?>
+ <?php SwpmMiscUtils::month_day_selector( !empty($is_annual_fixed_date_checked) ? $subscription_period : '' ); ?>
+ <?php printf(__(' with a minimum period of %s days', 'simple-membership'), '<span><input name="annual_fixed_date_min_period" type="number" min="0" value="'.esc_attr(!empty($is_annual_fixed_date_checked) ? $annual_fixed_date_min_period : '').'" style="width: 60px;"></span>')?>
+ <?php _e('(Memberships will expire on this date every year. Example value: December 31 for calendar-year memberships or June 30 for fiscal alignments). ', 'simple-membership'); ?>
+ <?php echo '<a href="https://simple-membership-plugin.com/annual-calendar-or-fiscal-year-memberships/" target="_blank">' . __('View Documentation', 'simple-membership') . '</a>.'; ?>
+ </p>
</td>
</tr>
<tr class="form-field">