--- a/user-registration/chunks/analytics.asset.php
+++ b/user-registration/chunks/analytics.asset.php
@@ -0,0 +1 @@
+<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-i18n'), 'version' => '639ccaa4401a7346e863');
--- a/user-registration/chunks/blocks.asset.php
+++ b/user-registration/chunks/blocks.asset.php
@@ -0,0 +1 @@
+<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-server-side-render'), 'version' => '83541f72f93cba92b652');
--- a/user-registration/chunks/content-access-rules.asset.php
+++ b/user-registration/chunks/content-access-rules.asset.php
@@ -0,0 +1 @@
+<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-i18n'), 'version' => 'da6ae11d6454af9eb432');
--- a/user-registration/chunks/dashboard.asset.php
+++ b/user-registration/chunks/dashboard.asset.php
@@ -0,0 +1 @@
+<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-data', 'wp-i18n'), 'version' => '3a00fdc837501cc9afc0');
--- a/user-registration/chunks/divi-builder.asset.php
+++ b/user-registration/chunks/divi-builder.asset.php
@@ -0,0 +1 @@
+<?php return array('dependencies' => array('react', 'react-jsx-runtime'), 'version' => '5390382781ce3d42d976');
--- a/user-registration/chunks/form_templates.asset.php
+++ b/user-registration/chunks/form_templates.asset.php
@@ -0,0 +1 @@
+<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-i18n'), 'version' => '6afc73ed711fcf9e2936');
--- a/user-registration/chunks/formblock.asset.php
+++ b/user-registration/chunks/formblock.asset.php
@@ -0,0 +1 @@
+<?php return array('dependencies' => array('react', 'react-jsx-runtime'), 'version' => '1d1b30bdd376f22e508c');
--- a/user-registration/chunks/welcome.asset.php
+++ b/user-registration/chunks/welcome.asset.php
@@ -0,0 +1 @@
+<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-i18n'), 'version' => '396fd2dfb76c4c7666cf');
--- a/user-registration/includes/3rd-party/DiviBuilder/BuilderAbstract.php
+++ b/user-registration/includes/3rd-party/DiviBuilder/BuilderAbstract.php
@@ -110,12 +110,12 @@
true
);
- if ( defined( 'UR_MEMBERSHIP_VERSION' ) ) {
+ if ( defined( 'UR_VERSION' ) ) {
$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
- wp_register_script( 'user-registration-membership-frontend-script', UR_MEMBERSHIP_JS_ASSETS_URL . '/frontend/user-registration-membership-frontend' . $suffix . '.js', array( 'jquery' ), '1.0.0', true );
+ wp_register_script( 'user-registration-membership-frontend-script', UR()->plugin_url(). '/assets/js/modules/membership/frontend/user-registration-membership-frontend' . $suffix . '.js', array( 'jquery' ), UR_VERSION, true );
- wp_register_style( 'user-registration-membership-frontend-style', UR_MEMBERSHIP_CSS_ASSETS_URL . '/user-registration-membership-frontend.css', array(), UR_MEMBERSHIP_VERSION );
+ wp_register_style( 'user-registration-membership-frontend-style', UR()->plugin_url(). '/assets/css/modules/membership/user-registration-membership-frontend.css', array(), UR_VERSION );
wp_enqueue_script( 'user-registration-membership-frontend-script' );
wp_enqueue_style( 'user-registration-membership-frontend-style' );
--- a/user-registration/includes/3rd-party/DiviBuilder/Modules/MembershipGroups.php
+++ b/user-registration/includes/3rd-party/DiviBuilder/Modules/MembershipGroups.php
@@ -114,7 +114,7 @@
return sprintf( '<div class="user-registration ur-frontend-form"><div class="user-registration-info">%s</div></div>', esc_html__( 'Please Select the membership groups', 'user-registration' ) );
}
- if ( ! defined( 'UR_MEMBERSHIP_VERSION' ) ) {
+ if ( ! defined( 'UR_VERSION' ) ) {
return sprintf( '<div class="user-registration ur-frontend-form"><div class="user-registration-info">%s</div></div>', esc_html__( 'Please Active the membership.', 'user-registration' ) );
}
--- a/user-registration/includes/3rd-party/DiviBuilder/Modules/MembershipThankYou.php
+++ b/user-registration/includes/3rd-party/DiviBuilder/Modules/MembershipThankYou.php
@@ -93,7 +93,7 @@
$parameters = array();
- if ( ! defined( 'UR_MEMBERSHIP_VERSION' ) ) {
+ if ( ! defined( 'UR_VERSION' ) ) {
return sprintf( '<div class="user-registration ur-frontend-form"><div class="user-registration-info">%s</div></div>', esc_html__( 'Please Active the membership.', 'user-registration' ) );
}
--- a/user-registration/includes/3rd-party/elementor/class-ur-elementor.php
+++ b/user-registration/includes/3rd-party/elementor/class-ur-elementor.php
@@ -25,7 +25,6 @@
/**
* Initialize elementor hooks.
- *
*/
public function init() {
@@ -41,7 +40,6 @@
/**
* Register User Registration forms Widget.
- *
*/
public function register_widget() {
// Include Widget files.
@@ -57,27 +55,25 @@
ElementorPlugin::instance()->widgets_manager->register( new UR_Elementor_Widget_Edit_Profile() );
ElementorPlugin::instance()->widgets_manager->register( new UR_Elementor_Widget_Edit_Password() );
-
- //include if pro version
- if(is_plugin_active('user-registration-pro/user-registration.php')){
- require_once UR_ABSPATH . 'includes/3rd-party/elementor/widgets/class-ur-widgets-profile-details.php';
- require_once UR_ABSPATH . 'includes/3rd-party/elementor/widgets/class-ur-widgets-popup.php';
- ElementorPlugin::instance()->widgets_manager->register( new UR_Elementor_Widget_View_Details() );
- ElementorPlugin::instance()->widgets_manager->register( new UR_Elementor_Widget_Popup() );
- }
+ // include if pro version
+ if ( is_plugin_active( 'user-registration-pro/user-registration.php' ) ) {
+ require_once UR_ABSPATH . 'includes/3rd-party/elementor/widgets/class-ur-widgets-profile-details.php';
+ require_once UR_ABSPATH . 'includes/3rd-party/elementor/widgets/class-ur-widgets-popup.php';
+ ElementorPlugin::instance()->widgets_manager->register( new UR_Elementor_Widget_View_Details() );
+ ElementorPlugin::instance()->widgets_manager->register( new UR_Elementor_Widget_Popup() );
+ }
}
/**
* Custom Widgets Category.
*
* @param object $elements_manager Elementor elements manager.
- *
*/
public function ur_elementor_widget_categories( $elements_manager ) {
$elements_manager->add_category(
'user-registration',
array(
- 'title' => esc_html__( 'User Registration', 'user-registration' ),
+ 'title' => esc_html__( 'User Registration & Membership', 'user-registration' ),
'icon' => 'fa fa-plug',
)
);
@@ -88,7 +84,7 @@
*/
public function editor_assets() {
wp_register_style( 'user-registration-admin', UR()->plugin_url() . '/assets/css/admin.css', array(), UR()->version );
- wp_register_style( 'user-registration-my-account', UR()->plugin_url() . '/assets/css/my-account-layout.css', array( ), UR()->version );
+ wp_register_style( 'user-registration-my-account', UR()->plugin_url() . '/assets/css/my-account-layout.css', array(), UR()->version );
wp_enqueue_style( 'user-registration-admin' );
wp_enqueue_style( 'user-registration-my-account' );
--- a/user-registration/includes/3rd-party/elementor/class-ur-widget.php
+++ b/user-registration/includes/3rd-party/elementor/class-ur-widget.php
@@ -38,7 +38,7 @@
* @return string Widget title.
*/
public function get_title() {
- return __( 'User Registration', 'user-registration' );
+ return __( 'User Registration & Membership', 'user-registration' );
}
/**
* Get widget icon.
@@ -119,7 +119,7 @@
$settings = $this->get_settings_for_display();
if ( ! $settings['user_registration_form'] ) {
- return '<p>' . __( 'Please select a User Registration Forms.', 'user-registration' ) . '</p>';
+ return '<p>' . __( 'Please select a User Registration & Membership Forms.', 'user-registration' ) . '</p>';
}
$attributes = array(
--- a/user-registration/includes/3rd-party/elementor/widgets/class-ur-widgets-popup.php
+++ b/user-registration/includes/3rd-party/elementor/widgets/class-ur-widgets-popup.php
@@ -90,7 +90,6 @@
$this->end_controls_section();
do_action( 'user_registration_elementor_popup_style', $this );
-
}
/**
* Retrieve the shortcode.
@@ -99,7 +98,7 @@
$settings = $this->get_settings_for_display();
if ( ! $settings['ur_popup_form'] ) {
- return '<p>' . __( 'Please select a User Registration Popup.', 'user-registration' ) . '</p>';
+ return '<p>' . __( 'Please select a User Registration & Membership Popup.', 'user-registration' ) . '</p>';
}
$attributes = array(
'id' => $settings['ur_popup_form'],
--- a/user-registration/includes/3rd-party/elementor/widgets/class-ur-widgets-registration.php
+++ b/user-registration/includes/3rd-party/elementor/widgets/class-ur-widgets-registration.php
@@ -12,7 +12,6 @@
/**
* User Registration Forms Widget for Elementor.
- *
*/
class UR_Elementor_Widget_Registration extends Widget_Base {
/**
@@ -40,7 +39,6 @@
*
* Retrieve shortcode widget icon.
*
- *
* @return string Widget icon.
*/
public function get_icon() {
@@ -49,7 +47,6 @@
/**
* Get widget categories.
*
- *
* @return array Widget categories.
*/
public function get_categories() {
@@ -63,7 +60,6 @@
*
* Retrieve the list of keywords the widget belongs to.
*
- *
* @return array Widget keywords.
*/
public function get_keywords() {
@@ -71,7 +67,6 @@
}
/**
* Register controls.
- *
*/
protected function register_controls() { // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore
$this->start_controls_section(
@@ -97,13 +92,12 @@
}
/**
* Retrieve the shortcode.
- *
*/
private function get_shortcode() {
$settings = $this->get_settings_for_display();
if ( ! $settings['user_registration_form'] ) {
- return '<p>' . __( 'Please select a User Registration Forms.', 'user-registration' ) . '</p>';
+ return '<p>' . __( 'Please select a User Registration & Membership Forms.', 'user-registration' ) . '</p>';
}
$attributes = array(
@@ -113,20 +107,18 @@
$this->add_render_attribute( 'shortcode', $attributes );
$shortcode = array();
$shortcode[] = sprintf( '[user_registration_form %s]', $this->get_render_attribute_string( 'shortcode' ) );
- $shortcode = implode( '', $shortcode );
- $shortcode = sprintf( apply_filters( 'user_registration_elementor_shortcode_registration_form', $shortcode, $settings ) );
+ $shortcode = implode( '', $shortcode );
+ $shortcode = sprintf( apply_filters( 'user_registration_elementor_shortcode_registration_form', $shortcode, $settings ) );
return $shortcode;
}
/**
* Render widget output.
- *
*/
protected function render() {
echo do_shortcode( $this->get_shortcode() );
}
/**
* Retrieve the available UR forms.
- *
*/
public function get_forms() {
$user_registration_forms = array();
--- a/user-registration/includes/3rd-party/oxygen/class-ur-oxygen.php
+++ b/user-registration/includes/3rd-party/oxygen/class-ur-oxygen.php
@@ -40,7 +40,6 @@
add_action( 'oxygen_add_plus_user-registration_section_content', array( $this, 'register_add_plus_subsections' ) );
add_action( 'wp_enqueue_scripts', array( $this, 'custom_init' ) );
-
$this->register_widgets();
}
@@ -50,14 +49,14 @@
* @since 3.3.5
*/
public function add_accordion_section() {
- $brand_name = __( 'User Registration', 'user-registration' );
+ $brand_name = __( 'User Registration & Membership', 'user-registration' );
CT_Toolbar::oxygen_add_plus_accordion_section( 'user-registration', $brand_name );
}
/**
* Enqueue the styles.
*
- * @since 3.3.5
+ * @since 3.3.5
*/
public function custom_init() {
wp_register_style( 'user-registration-general', UR()->plugin_url() . '/assets/css/user-registration.css', array(), UR()->version );
--- a/user-registration/includes/Analytics/Analytics.php
+++ b/user-registration/includes/Analytics/Analytics.php
@@ -0,0 +1,128 @@
+<?php
+
+namespace WPEverestURMAnalytics;
+
+use WPEverestURMAnalyticsControllersV1AnalyticsController;
+use WPEverestURMAnalyticsServicesMembershipService;
+
+class Analytics {
+
+ /**
+ * @var Analytics $instance
+ */
+ private static $instance;
+
+ /**
+ * @return Analytics
+ */
+ public static function get_instance() {
+ if ( ! isset( self::$instance ) ) {
+ self::$instance = new self();
+ }
+ return self::$instance;
+ }
+
+ protected function __construct() {
+ $this->init_hooks();
+ }
+
+ protected function __clone() {}
+
+ public function __wakeup() {
+ throw new Exception( 'Cannot unserialize a singleton.' );
+ }
+
+ private function init_hooks() {
+ add_action( 'admin_menu', array( $this, 'add_admin_menu' ), 40 );
+ add_action( 'rest_api_init', array( $this, 'register_rest_routes' ) );
+ add_filter( 'user_registration_notice_excluded_pages', array( $this, 'add_excluded_page' ) );
+ }
+
+ public function add_excluded_page( $excluded_pages ) {
+ $excluded_pages[] = 'user-registration-analytics';
+ return $excluded_pages;
+ }
+
+
+ public function add_admin_menu() {
+ $hook = add_submenu_page(
+ 'user-registration',
+ __( 'User Registration Analytics', 'user-registration' ),
+ __( 'Analytics', 'user-registration' ),
+ 'manage_user_registration',
+ 'user-registration-analytics',
+ array( $this, 'render_analytics_root' ),
+ 1
+ );
+
+ add_action( 'load-' . $hook, array( $this, 'enqueue_scripts_styles' ) );
+ }
+
+ public function register_rest_routes() {
+ /**
+ * @var array<int, WP_REST_Controller> $controllers
+ */
+ $controllers = apply_filters(
+ 'user_registration_analytics_controllers',
+ [
+ AnalyticsController::class,
+ ]
+ );
+ foreach ( $controllers as $controller ) {
+ $instance = new $controller();
+ $instance->register_routes();
+ }
+ }
+
+ public function enqueue_scripts_styles() {
+ $asset_file = UR()->plugin_path() . '/chunks/analytics.asset.php';
+ if ( ! file_exists( $asset_file ) ) {
+ return;
+ }
+
+ if ( ! wp_style_is( 'ur-core-builder-style', 'registered' ) ) {
+ wp_register_style(
+ 'ur-core-builder-style',
+ UR()->plugin_url() . '/assets/css/admin.css',
+ array(),
+ UR_VERSION
+ );
+ }
+ wp_enqueue_style( 'ur-core-builder-style' );
+
+ $asset = require $asset_file;
+
+ wp_enqueue_script(
+ 'ur-analytics',
+ UR()->plugin_url() . '/chunks/analytics.js',
+ $asset['dependencies'],
+ $asset['version'],
+ true
+ );
+
+ wp_localize_script(
+ 'ur-analytics',
+ '__UR_ANALYTICS__',
+ apply_filters(
+ 'user_registration_analytics_localized_data',
+ array(
+ 'install_date' => get_option( 'user_registration_installation_date' ),
+ 'memberships' => ( new MembershipService() )->get_memberships_list(),
+ 'currency' => strtoupper( get_option( 'user_registration_payment_currency', 'USD' ) ),
+ )
+ )
+ );
+
+ wp_enqueue_style(
+ 'ur-analytics',
+ UR()->plugin_url() . '/chunks/analytics.css',
+ array(),
+ $asset['version']
+ );
+ }
+
+ public function render_analytics_root() {
+ echo user_registration_plugin_main_header(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ echo '<div id="UR-Pro-Analytics-Root"></div>';
+ }
+}
--- a/user-registration/includes/Analytics/Controllers/V1/AnalyticsController.php
+++ b/user-registration/includes/Analytics/Controllers/V1/AnalyticsController.php
@@ -0,0 +1,200 @@
+<?php
+
+namespace WPEverestURMAnalyticsControllersV1;
+
+use WPEverestURMAnalyticsServicesAnalyticsDataService;
+
+class AnalyticsController extends WP_REST_Controller {
+
+ protected $namespace = 'user-registration/v1';
+
+ protected $rest_base = 'analytics';
+
+ public function register_routes() {
+ register_rest_route(
+ $this->namespace,
+ '/' . $this->rest_base,
+ array(
+ array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => array( $this, 'get_overview' ),
+ 'permission_callback' => array( $this, 'check_permissions' ),
+ 'args' => $this->get_analytics_args(),
+ ),
+ )
+ );
+ }
+
+ /**
+ * @param WP_REST_Request $request
+ * @return WP_Error|WP_REST_Response
+ */
+ public function get_overview( $request ) {
+ $date_from = $request->get_param( 'date_from' );
+ $date_to = $request->get_param( 'date_to' );
+ $unit = $request->get_param( 'unit' ) ?? 'day';
+ $scope = $request->get_param( 'scope' ) ?? 'all';
+ $membership = $request->get_param( 'membership' ) ?? null;
+
+ if ( empty( $date_from ) || empty( $date_to ) ) {
+ return new WP_Error(
+ 'rest_invalid_param',
+ __( 'date_from and date_to are required parameters.', 'user-registration' ),
+ [ 'status' => 400 ]
+ );
+ }
+
+ $start_date = strtotime( $date_from . ' 00:00:00' );
+ $end_date = strtotime( $date_to . ' 23:59:59' );
+
+ if ( false === $start_date || false === $end_date ) {
+ return new WP_Error(
+ 'rest_invalid_param',
+ __( 'Invalid date format. Use YYYY-MM-DD format.', 'user-registration' ),
+ [ 'status' => 400 ]
+ );
+ }
+
+ if ( ! in_array( $scope, [ 'membership', 'others', 'all' ], true ) ) {
+ return new WP_Error(
+ 'rest_invalid_param',
+ __( 'Invalid scope parameter. Allowed values are all, others, membership.', 'user-registration' ),
+ [ 'status' => 400 ]
+ );
+ }
+
+ if ( 'membership' === $scope && ( null === $membership || ! is_numeric( $membership ) ) ) {
+ return new WP_Error(
+ 'rest_invalid_param',
+ __( 'Valid membership ID is required when scope is membership.', 'user-registration' ),
+ [ 'status' => 400 ]
+ );
+ }
+
+ $data_service = new AnalyticsDataService();
+
+ $date_range_data = $data_service->get_date_range_members_data( $start_date, $end_date, $unit );
+ $revenue_data = $data_service->get_revenue_date_range_data( $start_date, $end_date, $unit, $scope, $membership );
+
+ $duration = $end_date - $start_date;
+ $previous_end = $start_date - DAY_IN_SECONDS;
+ $previous_start = $previous_end - $duration;
+
+ $comparison_data = $data_service->get_date_range_members_data( $previous_start, $previous_end, $unit );
+ $previous_revenue_data = $data_service->get_revenue_date_range_data( $previous_start, $previous_end, $unit, $scope, $membership );
+
+ $new_members_change = $data_service->calculate_percentage_change( $date_range_data['new_members'], $comparison_data['new_members'] );
+ $approved_members_change = $data_service->calculate_percentage_change( $date_range_data['approved_members'], $comparison_data['approved_members'] );
+ $pending_members_change = $data_service->calculate_percentage_change( $date_range_data['pending_members'], $comparison_data['pending_members'] );
+ $denied_members_change = $data_service->calculate_percentage_change( $date_range_data['denied_members'], $comparison_data['denied_members'] );
+ $total_revenue_change = $data_service->calculate_percentage_change( $revenue_data['total_revenue'], $previous_revenue_data['total_revenue'] );
+ $avg_order_value_change = $data_service->calculate_percentage_change( $revenue_data['average_order_value'], $previous_revenue_data['average_order_value'] );
+ $refunded_revenue_change = $data_service->calculate_percentage_change( $revenue_data['refunded_revenue'], $previous_revenue_data['refunded_revenue'] );
+
+ $refunded_revenue_count = $data_service->get_refunds_count( $start_date, $end_date );
+ $previous_refunded_count = $data_service->get_refunds_count( $previous_start, $previous_end );
+ $refunded_revenue_count_change = $data_service->calculate_percentage_change( $refunded_revenue_count, $previous_refunded_count );
+
+ $response = array(
+ 'new_members' => array(
+ 'count' => $date_range_data['new_members'],
+ 'previous' => $comparison_data['new_members'],
+ 'percentage_change' => $new_members_change,
+ 'currency' => false,
+ ),
+ 'approved_members' => array(
+ 'count' => $date_range_data['approved_members'],
+ 'previous' => $comparison_data['approved_members'],
+ 'percentage_change' => $approved_members_change,
+ 'currency' => false,
+ ),
+ 'pending_members' => array(
+ 'count' => $date_range_data['pending_members'],
+ 'previous' => $comparison_data['pending_members'],
+ 'percentage_change' => $pending_members_change,
+ 'currency' => false,
+ ),
+ 'denied_members' => array(
+ 'count' => $date_range_data['denied_members'],
+ 'previous' => $comparison_data['denied_members'],
+ 'percentage_change' => $denied_members_change,
+ 'currency' => false,
+ ),
+ 'total_revenue' => array(
+ 'count' => $revenue_data['total_revenue'],
+ 'previous' => $previous_revenue_data['total_revenue'],
+ 'percentage_change' => $total_revenue_change,
+ 'currency' => true,
+ ),
+ 'average_order_value' => array(
+ 'count' => $revenue_data['average_order_value'],
+ 'previous' => $previous_revenue_data['average_order_value'],
+ 'percentage_change' => $avg_order_value_change,
+ 'currency' => true,
+ ),
+ 'refunded_revenue' => array(
+ 'count' => $revenue_data['refunded_revenue'],
+ 'previous' => $previous_revenue_data['refunded_revenue'],
+ 'percentage_change' => $refunded_revenue_change,
+ 'currency' => true,
+ ),
+ 'refunded_revenue_count' => array(
+ 'count' => $refunded_revenue_count,
+ 'previous' => $previous_refunded_count,
+ 'percentage_change' => $refunded_revenue_count_change,
+ 'currency' => false,
+ ),
+ );
+
+ return rest_ensure_response( $response );
+ }
+
+ public function check_permissions( $request ) {
+ if ( ! current_user_can( 'manage_user_registration' ) ) { // phpcs:ignore WordPress.WP.Capabilities.Unknown
+ return new WP_Error(
+ 'rest_forbidden',
+ __( 'Sorry, you are not allowed to access analytics data.', 'user-registration' ),
+ array( 'status' => rest_authorization_required_code() )
+ );
+ }
+
+ return true;
+ }
+
+ protected function get_analytics_args() {
+ return array(
+ 'date_from' => [
+ 'description' => __( 'Start date in YYYY-MM-DD format.', 'user-registration' ),
+ 'type' => 'string',
+ 'required' => false,
+ 'default' => gmdate( 'Y-m-d', strtotime( '-30 days' ) ),
+ ],
+ 'date_to' => [
+ 'description' => __( 'End date in YYYY-MM-DD format.', 'user-registration' ),
+ 'type' => 'string',
+ 'required' => false,
+ 'default' => gmdate( 'Y-m-d' ),
+ ],
+ 'unit' => [
+ 'description' => __( 'Time unit for data aggregation (hour, day, week, month, year).', 'user-registration' ),
+ 'type' => 'string',
+ 'required' => false,
+ 'default' => 'day',
+ 'enum' => [ 'hour', 'day', 'week', 'month', 'year' ],
+ ],
+ 'scope' => [
+ 'description' => __( 'Scope of the analytics data (all, others, membership).', 'user-registration' ),
+ 'type' => 'string',
+ 'required' => false,
+ 'default' => 'all',
+ 'enum' => [ 'all', 'others', 'membership' ],
+ ],
+ 'membership' => [
+ 'description' => __( 'Membership ID for filtering data when scope is membership.', 'user-registration' ),
+ 'type' => 'string',
+ 'required' => false,
+ 'default' => null,
+ ],
+ );
+ }
+}
--- a/user-registration/includes/Analytics/Services/AnalyticsDataService.php
+++ b/user-registration/includes/Analytics/Services/AnalyticsDataService.php
@@ -0,0 +1,562 @@
+<?php
+
+namespace WPEverestURMAnalyticsServices;
+
+defined( 'ABSPATH' ) || exit;
+
+use WPEverestURMAnalyticsTraitsDateUtils;
+use WPEverestURMembershipTableList;
+
+// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+
+class AnalyticsDataService {
+
+ use DateUtils;
+
+ /**
+ * @param int $start_date
+ * @param int $end_date
+ * @param string $unit
+ * @return array
+ */
+ public function get_date_range_members_data( $start_date, $end_date, $unit = 'day' ) {
+ global $wpdb;
+
+ $new_members = 0;
+ $approved_members = 0;
+ $pending_members = 0;
+ $denied_members = 0;
+
+ $date_keys = $this->generate_date_keys( $start_date, $end_date, $unit );
+
+ $default_value = [
+ 'new_members_in_a_day' => 0,
+ 'approved_members_in_a_day' => 0,
+ 'pending_members_in_a_day' => 0,
+ 'denied_members_in_a_day' => 0,
+ ];
+
+ $daily_data = $this->initialize_data_structure( $date_keys, $default_value );
+
+ $members = $wpdb->get_results(
+ $wpdb->prepare(
+ "SELECT u.ID, u.user_registered
+ FROM {$wpdb->users} u
+ INNER JOIN {$wpdb->usermeta} um ON u.ID = um.user_id
+ WHERE um.meta_key = %s
+ AND u.user_registered BETWEEN %s AND %s",
+ 'ur_form_id',
+ wp_date( 'Y-m-d H:i:s', $start_date ),
+ wp_date( 'Y-m-d H:i:s', $end_date )
+ )
+ );
+
+ if ( empty( $members ) ) {
+ return [
+ 'new_members' => 0,
+ 'approved_members' => 0,
+ 'pending_members' => 0,
+ 'denied_members' => 0,
+ 'date_difference' => human_time_diff( $start_date, $end_date ),
+ 'daily_data' => $daily_data,
+ ];
+ }
+
+ $member_ids = wp_list_pluck( $members, 'ID' );
+ $member_meta_data = $this->fetch_member_meta_bulk( $member_ids );
+
+ foreach ( $members as $member ) {
+ $member_id = $member->ID;
+ $registration_timestamp = strtotime( $member->user_registered );
+ $registration_date = $this->get_date_key_for_timestamp( $registration_timestamp, $unit );
+
+ ++$new_members;
+
+ $status = $member_meta_data[ $member_id ]['user_status'] ?? '';
+ $email_status = $member_meta_data[ $member_id ]['user_email_status'] ?? '';
+
+ if ( '' === $status && '' === $email_status ) {
+ ++$approved_members;
+ $member_status = 'approved';
+ } elseif ( '' !== $status && '' === $email_status ) {
+ if ( 1 === (int) $status ) {
+ ++$approved_members;
+ $member_status = 'approved';
+ } elseif ( 0 === (int) $status ) {
+ ++$pending_members;
+ $member_status = 'pending';
+ } else {
+ ++$denied_members;
+ $member_status = 'denied';
+ }
+ } elseif ( '' !== $email_status ) {
+ if ( 1 === (int) $email_status ) {
+ ++$approved_members;
+ $member_status = 'approved';
+ } else {
+ ++$pending_members;
+ $member_status = 'pending';
+ }
+ }
+
+ if ( isset( $daily_data[ $registration_date ] ) ) {
+ ++$daily_data[ $registration_date ]['new_members_in_a_day'];
+ if ( 'approved' === $member_status ) {
+ ++$daily_data[ $registration_date ]['approved_members_in_a_day'];
+ } elseif ( 'pending' === $member_status ) {
+ ++$daily_data[ $registration_date ]['pending_members_in_a_day'];
+ } elseif ( 'denied' === $member_status ) {
+ ++$daily_data[ $registration_date ]['denied_members_in_a_day'];
+ }
+ }
+ }
+
+ $total_members = max( $new_members, 1 );
+
+ return [
+ 'new_members' => $new_members,
+ 'approved_members' => $approved_members,
+ 'approved_members_percentage' => round( ( $approved_members / $total_members ) * 100, 2 ),
+ 'pending_members' => $pending_members,
+ 'pending_members_percentage' => round( ( $pending_members / $total_members ) * 100, 2 ),
+ 'denied_members' => $denied_members,
+ 'denied_members_percentage' => round( ( $denied_members / $total_members ) * 100, 2 ),
+ 'date_difference' => human_time_diff( $start_date, $end_date ),
+ 'daily_data' => $daily_data,
+ ];
+ }
+
+
+ /**
+ * @param array $member_ids
+ * @return array
+ */
+ protected function fetch_member_meta_bulk( $member_ids ) {
+ if ( empty( $member_ids ) ) {
+ return [];
+ }
+
+ update_meta_cache( 'user', $member_ids );
+
+ $meta_data = [];
+ foreach ( $member_ids as $member_id ) {
+ $user_status = get_user_meta( $member_id, 'ur_user_status', true );
+ $user_email_status = get_user_meta( $member_id, 'ur_confirm_email', true );
+
+ $meta_data[ $member_id ] = [
+ 'user_status' => $user_status,
+ 'user_email_status' => $user_email_status,
+ ];
+ }
+
+ return $meta_data;
+ }
+
+ /**
+ * @param float $current
+ * @param float $previous
+ * @return float Percentage
+ */
+ public function calculate_percentage_change( $current, $previous ) {
+ $current = (float) $current;
+ $previous = (float) $previous;
+
+ if ( 0.0 === $previous && 0.0 === $current ) {
+ return 0.0;
+ }
+
+ if ( 0.0 === $previous ) {
+ return $current > 0 ? 100.0 : -100.0;
+ }
+
+ return round( ( ( $current - $previous ) / $previous ) * 100, 2 );
+ }
+
+ /**
+ * @param int $start_date
+ * @param int $end_date
+ * @return int
+ */
+ public function get_refunds_count( $start_date, $end_date ) {
+ global $wpdb;
+ $orders_table = TableList::orders_table();
+
+ if ( ! $this->table_exists( $orders_table ) ) {
+ return 0;
+ }
+
+ $start_date_str = wp_date( 'Y-m-d H:i:s', $start_date );
+ $end_date_str = wp_date( 'Y-m-d H:i:s', $end_date );
+
+ $result = $wpdb->get_var(
+ $wpdb->prepare(
+ "SELECT COUNT(*)
+ FROM {$orders_table}
+ WHERE status = 'refunded'
+ AND created_at BETWEEN %s AND %s",
+ $start_date_str,
+ $end_date_str
+ )
+ );
+
+ return (int) $result;
+ }
+
+ /**
+ * @param string $table_name
+ * @return bool
+ */
+ protected function table_exists( $table_name ) {
+ global $wpdb;
+ $result = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ) );
+ return $result === $table_name;
+ }
+
+ /**
+ * @param int $start_date
+ * @param int $end_date
+ * @param string $unit
+ * @return array
+ */
+ protected function generate_date_keys( $start_date, $end_date, $unit = 'day' ) {
+ $date_keys = [];
+
+ if ( 'hour' === $unit ) {
+ $date_format = 'Y-m-d H';
+ $incrementor = HOUR_IN_SECONDS;
+ for ( $i = $start_date; $i <= $end_date; $i += $incrementor ) {
+ $date_keys[] = wp_date( $date_format, $i );
+ }
+ } elseif ( 'month' === $unit ) {
+ $current_date = new DateTime();
+ $current_date->setTimestamp( $start_date );
+ $end_date_obj = new DateTime();
+ $end_date_obj->setTimestamp( $end_date );
+ $end_date_obj->setTime( 23, 59, 59 );
+ $current_date->modify( 'first day of this month' );
+ $current_date->setTime( 0, 0, 0 );
+
+ while ( $current_date <= $end_date_obj ) {
+ $date_keys[] = $current_date->format( 'Y-m' );
+ $current_date->modify( '+1 month' );
+ }
+ } elseif ( 'year' === $unit ) {
+ $current_date = new DateTime();
+ $current_date->setTimestamp( $start_date );
+ $end_date_obj = new DateTime();
+ $end_date_obj->setTimestamp( $end_date );
+ $end_date_obj->setTime( 23, 59, 59 );
+ $current_date->modify( 'first day of January this year' );
+ $current_date->setTime( 0, 0, 0 );
+
+ while ( $current_date <= $end_date_obj ) {
+ $date_keys[] = $current_date->format( 'Y' );
+ $current_date->modify( '+1 year' );
+ }
+ } elseif ( 'week' === $unit ) {
+ $current_date = new DateTime();
+ $current_date->setTimestamp( $start_date );
+ $end_date_obj = new DateTime();
+ $end_date_obj->setTimestamp( $end_date );
+ $end_date_obj->setTime( 23, 59, 59 );
+ $day_of_week = (int) $current_date->format( 'w' );
+ $days_to_monday = ( $day_of_week === 0 ? 6 : $day_of_week - 1 );
+ $current_date->modify( "-{$days_to_monday} days" );
+ $current_date->setTime( 0, 0, 0 );
+
+ while ( $current_date <= $end_date_obj ) {
+ $date_keys[] = $current_date->format( 'Y-m-d' );
+ $current_date->modify( '+1 week' );
+ }
+ } else {
+ $date_format = 'Y-m-d';
+ $incrementor = DAY_IN_SECONDS;
+ for ( $i = $start_date; $i <= $end_date; $i += $incrementor ) {
+ $date_keys[] = wp_date( $date_format, $i );
+ }
+ }
+
+ return $date_keys;
+ }
+
+ /**
+ * @param array $date_keys
+ * @param mixed $default_value
+ * @return array
+ */
+ protected function initialize_data_structure( $date_keys, $default_value ) {
+ $data = [];
+ foreach ( $date_keys as $key ) {
+ $data[ $key ] = $default_value;
+ }
+ return $data;
+ }
+
+ /**
+ * @param int $start_date
+ * @param int $end_date
+ * @param string $unit
+ * @return array
+ */
+ public function initialize_daily_data_structure( $start_date, $end_date, $unit = 'day' ) {
+ $date_keys = $this->generate_date_keys( $start_date, $end_date, $unit );
+
+ $default_value = [
+ 'total_revenue' => 0,
+ 'paid_revenue' => 0,
+ 'refunded_revenue' => 0,
+ 'completed_orders' => 0,
+ 'refunded_orders' => 0,
+ 'new_payments_revenue' => 0,
+ 'single_item_revenue' => 0,
+ 'average_order_value' => 0,
+ ];
+
+ return $this->initialize_data_structure( $date_keys, $default_value );
+ }
+
+ /**
+ * @param int $start_date
+ * @param int $end_date
+ * @param string $unit
+ * @param string $scope 'all'|'membership'|'others'
+ * @param int|null $membership
+ * @return array
+ */
+ public function get_revenue_date_range_data( $start_date, $end_date, $unit = 'day', $scope = 'all', $membership = null ) {
+ global $wpdb;
+ $orders_table = ( new TableList() )->orders_table();
+ $daily_data = $this->initialize_daily_data_structure( $start_date, $end_date, $unit );
+
+ $include_single_items = ( 'others' === $scope || 'all' === $scope );
+
+ $single_item_revenue = [];
+ if ( $include_single_items ) {
+ $single_item_revenue = $this->get_single_item_revenue_data( $start_date, $end_date, $unit );
+ }
+
+ if ( 'others' === $scope ) {
+ $total_single_item_revenue = 0;
+ foreach ( $single_item_revenue as $time_key => $data ) {
+ $single_item_amount = $data['single_item_revenue'] ?? 0;
+ if ( isset( $daily_data[ $time_key ] ) ) {
+ $daily_data[ $time_key ]['total_revenue'] = $single_item_amount;
+ $daily_data[ $time_key ]['new_payments_revenue'] = $single_item_amount;
+ $daily_data[ $time_key ]['single_item_revenue'] = $single_item_amount;
+ $total_single_item_revenue += $single_item_amount;
+ }
+ }
+
+ return [
+ 'total_revenue' => $total_single_item_revenue,
+ 'net_revenue' => $total_single_item_revenue,
+ 'refunded_revenue' => 0,
+ 'total_orders' => 0,
+ 'total_refunds' => 0,
+ 'new_payments_revenue' => $total_single_item_revenue,
+ 'single_item_revenue' => $total_single_item_revenue,
+ 'average_order_value' => 0,
+ 'daily_data' => $daily_data,
+ ];
+ }
+
+ if ( ! $this->table_exists( $orders_table ) ) {
+ if ( $include_single_items ) {
+ $total_single_item_revenue = 0;
+ foreach ( $single_item_revenue as $time_key => $data ) {
+ $single_item_amount = $data['single_item_revenue'] ?? 0;
+ if ( isset( $daily_data[ $time_key ] ) ) {
+ $daily_data[ $time_key ]['total_revenue'] = $single_item_amount;
+ $daily_data[ $time_key ]['new_payments_revenue'] = $single_item_amount;
+ $daily_data[ $time_key ]['single_item_revenue'] = $single_item_amount;
+ $total_single_item_revenue += $single_item_amount;
+ }
+ }
+
+ return [
+ 'total_revenue' => $total_single_item_revenue,
+ 'net_revenue' => $total_single_item_revenue,
+ 'refunded_revenue' => 0,
+ 'total_orders' => 0,
+ 'total_refunds' => 0,
+ 'new_payments_revenue' => $total_single_item_revenue,
+ 'single_item_revenue' => $total_single_item_revenue,
+ 'average_order_value' => 0,
+ 'daily_data' => $daily_data,
+ ];
+ }
+
+ return [
+ 'total_revenue' => 0,
+ 'net_revenue' => 0,
+ 'refunded_revenue' => 0,
+ 'total_orders' => 0,
+ 'total_refunds' => 0,
+ 'new_payments_revenue' => 0,
+ 'single_item_revenue' => 0,
+ 'average_order_value' => 0,
+ 'daily_data' => $daily_data,
+ ];
+ }
+
+ $membership = ( 'membership' === $scope && null !== $membership ) ? absint( $membership ) : null;
+ $group_by_map = [
+ 'hour' => "DATE_FORMAT(created_at, '%Y-%m-%d %H')",
+ 'day' => 'DATE(created_at)',
+ 'week' => 'DATE(DATE_SUB(created_at, INTERVAL WEEKDAY(created_at) DAY))',
+ 'month' => "DATE_FORMAT(created_at, '%Y-%m')",
+ 'year' => "DATE_FORMAT(created_at, '%Y')",
+ ];
+
+ $group_by = $group_by_map[ $unit ] ?? $group_by_map['day'];
+ $start_date_str = wp_date( 'Y-m-d H:i:s', $start_date );
+ $end_date_str = wp_date( 'Y-m-d H:i:s', $end_date );
+
+ $daily_data = $this->initialize_daily_data_structure( $start_date, $end_date, $unit );
+
+ $membership_filter = '';
+ $query_params = [ $start_date_str, $end_date_str ];
+ if ( ! empty( $membership ) ) {
+ $membership_filter = ' AND item_id = %d';
+ $query_params[] = absint( $membership );
+ }
+
+ $results = $wpdb->get_results(
+ $wpdb->prepare(
+ "SELECT
+ {$group_by} AS time_key,
+ COALESCE(SUM(CASE WHEN status = 'completed' THEN total_amount ELSE 0 END), 0) AS total_revenue,
+ COALESCE(SUM(CASE WHEN status = 'completed' AND order_type = 'paid' THEN total_amount ELSE 0 END), 0) AS paid_revenue,
+ COALESCE(SUM(CASE WHEN status = 'refunded' THEN total_amount ELSE 0 END), 0) AS refunded_revenue,
+ COUNT(CASE WHEN status = 'completed' THEN 1 END) AS completed_orders,
+ COUNT(CASE WHEN status = 'refunded' THEN 1 END) AS refunded_orders
+ FROM {$orders_table}
+ WHERE created_at BETWEEN %s AND %s {$membership_filter}
+ GROUP BY time_key
+ ORDER BY time_key",
+ ...$query_params
+ )
+ );
+
+ foreach ( $results as $row ) {
+ $time_key = $row->time_key;
+ $paid_revenue = (float) $row->paid_revenue;
+ $completed_orders = (int) $row->completed_orders;
+ $new_payments_revenue = $paid_revenue;
+ $average_order_value = $completed_orders > 0 ? ( (float) $row->total_revenue ) / $completed_orders : 0;
+
+ if ( isset( $daily_data[ $time_key ] ) ) {
+ $daily_data[ $time_key ] = [
+ 'total_revenue' => (float) $row->total_revenue,
+ 'paid_revenue' => $paid_revenue,
+ 'refunded_revenue' => (float) $row->refunded_revenue,
+ 'completed_orders' => $completed_orders,
+ 'refunded_orders' => (int) $row->refunded_orders,
+ 'new_payments_revenue' => $new_payments_revenue,
+ 'average_order_value' => $average_order_value,
+ 'single_item_revenue' => 0,
+ ];
+ }
+ }
+
+ if ( $include_single_items ) {
+ foreach ( $single_item_revenue as $time_key => $data ) {
+ $single_item_amount = $data['single_item_revenue'] ?? 0;
+
+ if ( isset( $daily_data[ $time_key ] ) ) {
+ $daily_data[ $time_key ]['single_item_revenue'] = $single_item_amount;
+ $daily_data[ $time_key ]['total_revenue'] += $single_item_amount;
+ $daily_data[ $time_key ]['new_payments_revenue'] += $single_item_amount;
+
+ $orders = $daily_data[ $time_key ]['completed_orders'];
+ if ( $orders > 0 ) {
+ $daily_data[ $time_key ]['average_order_value'] = $daily_data[ $time_key ]['total_revenue'] / $orders;
+ }
+ }
+ }
+ }
+
+ $total_revenue = array_sum( array_column( $daily_data, 'total_revenue' ) );
+ $total_refunded = array_sum( array_column( $daily_data, 'refunded_revenue' ) );
+ $net_revenue = $total_revenue - $total_refunded;
+ $total_orders = array_sum( array_column( $daily_data, 'completed_orders' ) );
+ $total_refunds = array_sum( array_column( $daily_data, 'refunded_orders' ) );
+ $total_new_payments_revenue = array_sum( array_column( $daily_data, 'new_payments_revenue' ) );
+ $total_single_item_revenue = array_sum( array_column( $daily_data, 'single_item_revenue' ) );
+ $total_average_order_value = $total_orders > 0 ? $total_revenue / $total_orders : 0;
+
+ return [
+ 'total_revenue' => $total_revenue,
+ 'net_revenue' => $net_revenue,
+ 'refunded_revenue' => $total_refunded,
+ 'total_orders' => $total_orders,
+ 'total_refunds' => $total_refunds,
+ 'new_payments_revenue' => $total_new_payments_revenue,
+ 'single_item_revenue' => $total_single_item_revenue,
+ 'average_order_value' => $total_average_order_value,
+ 'daily_data' => $daily_data,
+ ];
+ }
+
+ public function get_single_item_revenue_data( $start_date, $end_date, $unit = 'day' ) {
+ /** @var wpdb $wpdb */
+ global $wpdb;
+
+ $daily_data = $this->initialize_daily_data_structure( $start_date, $end_date, $unit );
+
+ $invoices = array_unique(
+ $wpdb->get_col(
+ $wpdb->prepare(
+ "SELECT meta_value FROM {$wpdb->usermeta} WHERE meta_key = %s",
+ 'ur_payment_invoices'
+ )
+ )
+ );
+
+ $format_map = [
+ 'hour' => 'Y-m-d H',
+ 'day' => 'Y-m-d',
+ 'week' => 'Y-m-d',
+ 'month' => 'Y-m',
+ 'year' => 'Y',
+ ];
+
+ $format = $format_map[ $unit ] ?? $format_map['day'];
+
+ foreach ( $invoices as $invoice ) {
+ $invoice_data = maybe_unserialize( $invoice, true );
+ if ( ! is_array( $invoice_data ) || ! isset( $invoice_data[0] ) || ! is_array( $invoice_data[0] ) ) {
+ continue;
+ }
+ $invoice_data = $invoice_data[0];
+
+ if ( ! isset( $invoice_data['invoice_date'], $invoice_data['invoice_amount'], $invoice_data['invoice_status'] ) ) {
+ continue;
+ }
+
+ if ( 'completed' !== $invoice_data['invoice_status'] ) {
+ continue;
+ }
+
+ $date = new DateTime( $invoice_data['invoice_date'] );
+ $invoice_timestamp = $date->getTimestamp();
+
+ if ( $invoice_timestamp < $start_date || $invoice_timestamp > $end_date ) {
+ continue;
+ }
+
+ if ( 'week' === $unit ) {
+ $date->modify( '-' . ( (int) $date->format( 'N' ) - 1 ) . ' days' );
+ }
+
+ $time_key = $date->format( $format );
+ $amount = (float) $invoice_data['invoice_amount'];
+
+ if ( isset( $daily_data[ $time_key ] ) ) {
+ $daily_data[ $time_key ]['single_item_revenue'] = ( $daily_data[ $time_key ]['single_item_revenue'] ?? 0 ) + $amount;
+ }
+ }
+
+ return $daily_data;
+ }
+}
--- a/user-registration/includes/Analytics/Services/MembershipService.php
+++ b/user-registration/includes/Analytics/Services/MembershipService.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace WPEverestURMAnalyticsServices;
+
+defined( 'ABSPATH' ) || exit;
+
+class MembershipService {
+
+ public function get_memberships_list() {
+ $memberships = get_posts(
+ [
+ 'post_type' => 'ur_membership',
+ 'post_status' => 'publish',
+ 'posts_per_page' => -1,
+ ]
+ );
+
+ return array_map(
+ function ( $membership ) {
+ return [
+ 'id' => $membership->ID,
+ 'name' => $membership->post_title,
+ ];
+ },
+ $memberships
+ );
+ }
+}
--- a/user-registration/includes/Analytics/Traits/DateUtils.php
+++ b/user-registration/includes/Analytics/Traits/DateUtils.php
@@ -0,0 +1,95 @@
+<?php
+
+namespace WPEverestURMAnalyticsTraits;
+
+defined( 'ABSPATH' ) || exit;
+
+trait DateUtils {
+
+ /**
+ * @param int $timestamp
+ * @param string $unit
+ * @return string
+ */
+ protected function get_date_key_for_timestamp( $timestamp, $unit = 'day' ) {
+ if ( 'hour' === $unit ) {
+ return wp_date( 'Y-m-d H', $timestamp );
+ } elseif ( 'month' === $unit ) {
+ return wp_date( 'Y-m', $timestamp );
+ } elseif ( 'year' === $unit ) {
+ return wp_date( 'Y', $timestamp );
+ } elseif ( 'week' === $unit ) {
+ return $this->get_week_monday_key( $timestamp );
+ } else {
+ return wp_date( 'Y-m-d', $timestamp );
+ }
+ }
+
+ /**
+ * @param int $timestamp
+ * @return string
+ */
+ protected function get_week_monday_key( $timestamp ) {
+ $date = new DateTime();
+ $date->setTimestamp( $timestamp );
+
+ $day_of_week = (int) $date->format( 'w' );
+ $days_to_monday = ( $day_of_week === 0 ? 6 : $day_of_week - 1 );
+
+ if ( $days_to_monday > 0 ) {
+ $date->modify( "-{$days_to_monday} days" );
+ }
+
+ return $date->format( 'Y-m-d' );
+ }
+
+ /**
+ * @param int $timestamp
+ * @param string $unit
+ * @param bool $is_end_date
+ * @return DateTime
+ */
+ protected function create_aligned_datetime( $timestamp, $unit, $is_end_date = false ) {
+ $date = new DateTime();
+ $date->setTimestamp( $timestamp );
+
+ if ( $is_end_date && in_array( $unit, [ 'week', 'month', 'year' ], true ) ) {
+ $date->setTime( 23, 59, 59 );
+ } else {
+ $date->setTime( 0, 0, 0 );
+ }
+
+ switch ( $unit ) {
+ case 'week':
+ $day_of_week = (int) $date->format( 'w' );
+ $days_to_monday = ( $day_of_week === 0 ? 6 : $day_of_week - 1 );
+ if ( $days_to_monday > 0 && ! $is_end_date ) {
+ $date->modify( "-{$days_to_monday} days" );
+ $date->setTime( 0, 0, 0 );
+ }
+ break;
+ case 'month':
+ if ( ! $is_end_date ) {
+ $date->modify( 'first day of this month' );
+ $date->setTime( 0, 0, 0 );
+ }
+ break;
+ case 'year':
+ if ( ! $is_end_date ) {
+ $date->modify( 'first day of January this year' );
+ $date->setTime( 0, 0, 0 );
+ }
+ break;
+ }
+
+ return $date;
+ }
+
+ /**
+ * @param int|null $timestamp
+ * @return string
+ */
+ protected function timestamp_to_sql_datetime( $timestamp ) {
+ return wp_date( 'Y-m-d H:i:s', $timestamp );
+ }
+}
--- a/user-registration/includes/Functions/CoreFunctions.php
+++ b/user-registration/includes/Functions/CoreFunctions.php
@@ -370,6 +370,7 @@
$symbol = $currencies[ $currency ]['symbol'];
$new_mem = array();
$active_payment_gateways = array();
+ $is_new_installation = ur_string_to_bool( get_option( 'urm_is_new_installation', '' ) );
foreach ( $memberships as $k => $membership ) {
@@ -379,7 +380,7 @@
$amount = ! empty( $membership['meta_value']['amount'] ) ? number_format( (float) $membership['meta_value']['amount'], 2 ) : 0;
$symbol_pos = isset( $currencies[ $currency ]['symbol_pos'] ) ? $currencies[ $currency ]['symbol_pos'] : 'left';
$membership_cur_amount = ! empty( $amount ) ? ( 'right' === $symbol_pos ? $amount . $symbol : $symbol . $amount ) : '';
- $duration_label = '';
+ $duration_label = '';
if ( ! empty( $membership['meta_value']['subscription']['duration'] ) ) {
$duration_key = isset( $membership['meta_value']['subscription']['duration'] ) ? strtolower( $membership['meta_value']['subscription']['duration'] ) : '';
$duration_labels = array(
@@ -398,14 +399,25 @@
'amount' => ! empty( $membership_meta_value ) ? $membership['meta_value']['amount'] : 0,
'currency_symbol' => $symbol,
'calculated_amount' => 'free' === $membership_type ? 0 : ( ! empty( $membership_meta_value ) ? round( $membership_meta_value['amount'] ) : 0 ),
- 'period' => 'free' === $membership_type ? __( 'Free', 'user-registration' ) : ( ( ! empty( $membership_meta_value ) && 'subscription' === $membership_meta_value['type'] ) ? $membership_cur_amount . ' / ' . number_format( $membership['meta_value']['subscription']['value'] ) . ' ' . ucfirst( $duration_label ) . ( $membership['meta_value']['subscription']['value'] > 1 ? 's' : '' ) : $membership_cur_amount ),
+ 'period' => 'free' === $membership_type ? __( 'Free', 'user-registration' ) : ( ( ! empty( $membership_meta_value ) && 'subscription' === $membership_meta_value['type'] ) ? $membership_cur_amount . ' / ' . number_format( $membership['meta_value']['subscription']['value'] ) . ' ' . ucfirst( $duration_label ) . ( $membership['meta_value']['subscription']['value'] > 1 ? __( 's', 'user-registration' ) : '' ) : $membership_cur_amount ),
);
+
if ( isset( $membership['meta_value']['payment_gateways'] ) ) {
+
foreach ( $membership['meta_value']['payment_gateways'] as $key => $gateways ) {
- if ( 'on' !== $gateways['status'] ) {
- continue;
+
+ if ( $is_new_installation ) {
+ if ( ! urm_is_payment_gateway_configured( $key ) ) {
+ continue;
+ }
+ $active_payment_gateways[ $key ] = true;
+ } else {
+ if ( isset( $gateways['status'] ) && 'on' !== $gateways['status'] ) {
+ continue;
+ }
+ $active_payment_gateways[ $key ] = isset($gateways['status']) ? $gateways['status'] : 'off'; //setting users gateway to off for now if no status received.
}
- $active_payment_gateways[ $key ] = $gateways['status'];
+
}
$new_mem[ $k ]['active_payment_gateways'] = ( wp_unslash( wp_json_encode( $active_payment_gateways ) ) );
@@ -413,6 +425,31 @@
$active_payment_gateways = array();
}
+ // Sort memberships by saved order if available
+ $saved_order = get_option( 'ur_membership_order', array() );
+ if ( ! empty( $saved_order ) && ! empty( $new_mem ) ) {
+ $order_map = array_flip( $saved_order );
+
+ $ordered_memberships = array();
+ $unordered_memberships = array();
+
+ foreach ( $new_mem as $membership ) {
+ $membership_id = isset( $membership['ID'] ) ? (int) $membership['ID'] : 0;
+ if ( isset( $order_map[ $membership_id ] ) ) {
+ $ordered_memberships[ $order_map[ $membership_id ] ] = $membership;
+ } else {
+ // Membership is not in saved order, append at end
+ $unordered_memberships[] = $membership;
+ }
+ }
+
+ ksort( $ordered_memberships );
+
+ // Merge ordered and unordered memberships
+ $new_mem = array_values( $ordered_memberships );
+ $new_mem = array_merge( $new_mem, $unordered_memberships );
+ }
+
return $new_mem;
}
}
@@ -429,21 +466,21 @@
'label' => __( 'Memberships', 'user-registration' ),
'url' => admin_url( 'admin.php?page=user-registration-membership' ),
'active' => isset( $_GET['page'] ) &&
- $_GET['page'] === 'user-registration-membership' &&
- ( isset( $_GET['action'] ) ? ! in_array(
- $_GET['action'],
- array(
- 'list_groups',
- 'add_groups',
- )
- ) : true ),
+ $_GET['page'] === 'user-registration-membership' &&
+ ( isset( $_GET['action'] ) ? ! in_array(
+ $_GET['action'],
+ array(
+ 'list_groups',
+ 'add_groups',
+ )
+ ) : true ),
),
'membership_groups' => array(
'label' => __( 'Membership Groups', 'user-registration' ),
'url' => admin_url( 'admin.php?page=user-registration-membership&action=list_groups' ),
'active' => isset( $_GET['page'], $_GET['action'] ) &&
- $_GET['page'] === 'user-registration-membership' &&
- in_array( $_GET['action'], array( 'list_groups', 'add_groups' ) ),
+ $_GET['page'] === 'user-registration-membership' &&
+ in_array( $_GET['action'], array( 'list_groups', 'add_groups' ) ),
),
'members' => array(
'label' => __( 'Members', 'user-registration' ),
@@ -557,3 +594,476 @@
return $targetDate->format( 'Y-m-d 00:00:00' );
}
}
+
+if ( ! function_exists( 'urm_get_plan_description' ) ) {
+ /**
+ * Get plan description based on type and period.
+ *
+ * @param string $plan_type Plan type.
+ * @param string $plan_period Plan period.
+ *
+ * @return string
+ */
+ function urm_get_plan_description( $plan_type, $plan_period ) {
+ if ( 'free' === $plan_type ) {
+ return __( 'Completely Free', 'user-registration' );
+ }
+
+ $period_lower = strtolower( $plan_period );
+ if ( false !== strpos( $period_lower, 'month' ) ) {
+ return __( 'This will be billed every month', 'user-registration' );
+ }
+
+ if ( false !== strpos( $period_lower, 'lifetime' ) ) {
+ return __( 'This is a one-time payment', 'user-registration' );
+ }
+
+ return '';
+ }
+}
+
+if ( ! function_exists( 'urm_get_gateway_image_url' ) ) {
+ /**
+ * Get payment gateway image URL.
+ *
+ * @param string $gateway_key Gateway key.
+ * @param array $gateway_images Gateway images mapping.
+ * @param string $plugin_url Plugin URL.
+ *
+ * @return string
+ */
+ function urm_get_gateway_image_url( $gateway_key, $gateway_images, $plugin_url ) {
+ $image_file = isset( $gateway_images[ $gateway_key ] ) ? $gateway_images[ $gateway_key ] : '';
+
+ if ( empty( $image_file ) ) {
+ return '';
+ }
+
+ return esc_url( $plugin_url . '/assets/images/settings-icons/membership-field/' . $image_file );
+ }
+}
+
+if ( ! function_exists( 'urm_get_active_gateways_for_plan' ) ) {
+ /**
+ * Parse active payment gateways from membership option.
+ *
+ * @param array $option Membership option data.
+ * @param array $payment_gateways Available payment gateways.
+ *
+ * @return array
+ */
+ function urm_get_active_gateways_for_plan( $option, $payment_gateways ) {
+ $active_gateways = array();
+
+ if ( empty( $option['active_payment_gateways'] ) ) {
+ return $active_gateways;
+ }
+
+ $gateways_json = is_string( $option['active_payment_gateways'] )
+ ? json_decode( $option['active_payment_gateways'], true )
+ : $option['active_payment_gateways'];
+
+ if ( ! is_array( $gateways_json ) ) {
+ return $active_gateways;
+ }
+
+ foreach ( $gateways_json as $gateway => $status ) {
+ if ( 'on' === $status && isset( $payment_gateways[ $gateway ] ) ) {
+ $active_gateways[ $gateway ] = $payment_gateways[ $gateway ];
+ }
+ }
+
+ return $active_gateways;
+ }
+}
+
+if ( ! function_exists( 'urm_get_all_active_payment_gateways' ) ) {
+ /**
+ * Get all payment gateways that are configured/setup.
+ *
+ * @param string $membership_type Optional. The membership type ('paid' or 'subscription')
+ *
+ * @return array
+ */
+ function urm_get_all_active_payment_gateways( $membership_type = 'paid' ) {
+ // Get all available payment gateways.
+ $payment_gateways = get_option(
+ 'ur_membership_payment_gateways',
+ array(
+ 'paypal' => __( 'PayPal', 'user-registration' ),
+ 'stripe' => __( 'Stripe', 'user-registration' ),
+ 'bank' => __( 'Bank', 'user-registration' ),
+ )
+ );
+
+ if ( empty( $payment_gateways ) || ! is_array( $payment_gateways ) ) {
+ return array();
+ }
+
+ $active_gateways = array();
+
+ foreach ( $payment_gateways as $gateway_key => $gateway_label ) {
+ // Check if payment gateway is configured.
+ if ( urm_is_payment_gateway_configured( $gateway_key, $membership_type ) ) {
+ $active_gateways[ $gateway_key ] = $gateway_label;
+ }
+ }
+
+ /**
+ * Filters the list of active payment gateways.
+ *
+ * @param array $active_gateways Active payment gateways.
+ * @param string $membership_type Membership type.
+ *
+ * @return array
+ */
+ return apply_filters( 'urm_active_payment_gateways', $active_gateways, $membership_type );
+ }
+}
+
+if ( ! function_exists( 'urm_is_payment_gateway_configured' ) ) {
+ /**
+ * Check if a payment gateway is configured (has settings).
+ *
+ * This function checks if a payment gateway has the required settings configured,
+ * without validating if they are correct or complete.
+ *
+ * @param string $gateway_key Payment gateway key (e.g., 'paypal', 'stripe', 'bank').
+ * @param string $membership_type Optional. Membership type for PayPal check. Default 'paid'.
+ *
+ * @return bool True if gateway is configured, false otherwise.
+ */
+ function urm_is_payment_gateway_configured( $gateway_key, $membership_type = 'paid' ) {
+ $is_configured = false;
+ $is_new_installation = ur_string_to_bool( get_option( 'urm_is_new_installation', '' ) );
+
+ // First check if the gateway is enabled
+ $enabled_option = '';
+ switch ( $gateway_key ) {
+ case 'paypal':
+ $enabled_option = get_option( 'user_registration_paypal_enabled', '' );
+ break;
+ case 'stripe':
+ $enabled_option = get_option( 'user_registration_stripe_enabled', '' );
+ break;
+ case 'authorize':
+ $enabled_option = get_option( 'user_registration_authorize-net_enabled', '' );
+ break;
+ case 'mollie':
+ $enabled_option = get_option( 'user_registration_mollie_enabled', '' );
+ break;
+ case 'bank':
+ $enabled_option = get_option( 'user_registration_bank_enabled', '' );
+ break;
+ }
+
+
+ if ( empty( $enabled_option ) ) {
+ $is_enabled = ! $is_new_installation;
+ } else {
+ $is_enabled = ur_string_to_bool( $enabled_option );
+ }
+
+ if ( ! $is_enabled ) {
+ return false;
+ }
+
+ switch ( $gateway_key ) {
+ case 'paypal':
+ $mode = get_option('user_registration_global_paypal_mode', 'test') == "test" ? 'test' : 'live';
+ $paypal_email = get_option(sprintf('user_registration_global_paypal_%s_email_address', $mode), get_option('user_registration_global_paypal_email_address'));
+
+ if ('subscription' === $membership_type) {
+ $paypal_client_id = get_option(sprintf('user_registration_global_paypal_%s_client_id', $mode), get_option('user_registration_global_paypal_client_id'));
+ $paypal_client_secret = get_option(sprintf('user_registration_global_paypal_%s_client_secret', $mode), get_option('user_registration_global_paypal_client_secret'));
+ $is_configured = !empty($paypal_email) && !empty($paypal_client_id) && !empty($paypal_client_secret);
+ } else {
+ $is_configured = !empty($paypal_email);
+ }
+ break;
+
+ case 'stripe':
+ $mode = get_option( 'user_registration_stripe_test_mode', false ) ? 'test' : 'live';
+ $publishable_key = get_option( sprintf( 'user_registration_stripe_%s_publishable_key', $mode ) );
+ $secret_key = get_option( sprintf( 'user_registration_stripe_%s_secret_key', $mode ) );
+ $is_configured = ! empty( $publishable_key ) && ! empty( $secret_key );
+ break;
+ case 'bank':
+ // For bank and other gateways, check if bank details are configured.
+ $bank_details = get_option( 'user_registration_global_bank_details' );
+ $is_configured = ! empty( $bank_details );
+ break;
+ case 'default':
+ break;
+ }
+
+ /**
+ * Filters whether the payment gateway is configured.
+ *
+ * @param bool $is_configured Whether the gateway is configured.
+ * @param string $gateway_key Payment gateway key.
+ * @param string $membership_type Membership type.
+ *
+ * @return bool
+ */
+ return apply_filters( 'urm_is_payment_gateway_configured', $is_configured, $gateway_key, $membership_type );
+ }
+}
+
+if ( ! function_exists( 'urcr_build_migration_actions' ) ) {
+ /**
+ * Build migration actions array.
+ *
+ * @param string $migration_source Migration source type ('membership' or 'content').
+ * @param int $timestamp Optional timestamp to use for action IDs. If not provided, generates a new one.
+ *
+ * @return array Actions array.
+ */
+ function urcr_build_migration_actions( $migration_source = 'content', $timestamp = null ) {
+ if ( null === $timestamp ) {
+ $timestamp = time() * 1000;
+ }
+
+ if ( $migration_source === 'membership' ) {
+ $message = '';
+ } else {
+ $default_message = '<h3>' . __( 'Membership Required', 'user-registration' ) . '</h3>
+<p>' . __( 'This content is available to members only.', 'user-registration' ) . '</p>
+<p>' . __( 'Sign up to unlock access or log in if you already have an account.', 'user-registration' ) . '</p>
+<p>{{sign_up}} {{log_in}}</p>';
+ $message = get_option( 'user_registration_content_restriction_message', $default_message );
+
+ if ( ! empty( $message ) ) {
+ $message = wp_unslash( $message );
+ $message = str_replace( array( '\n', '\r\n', '\r' ), array( "n", "n", "n" ), $message );
+ $message = str_replace( array( "rn", "r" ), "n", $message );
+