'capability_here'
* To add a page to the network admin (wp-admin/network), use: 'network_admin_menu' => 'capability_here'
* To add a page to the user (wp-admin/user) admin, use: 'user_admin_menu' => 'capability_here'
*
* @since 2.0.0
* @var array
*/
protected $supported_panels = [
'network_admin_menu' => 'wu_edit_memberships',
];
/**
* Override the page load.
*
* @since 2.0.0
* @return void
*/
public function page_loaded(): void {
parent::page_loaded();
/*
* Adds the swap notices, if needed.
*/
$this->add_swap_notices();
}
/**
* Displays swap notices, if necessary.
*
* @since 2.0.0
* @return void
*/
protected function add_swap_notices() {
$swap_order = $this->get_object()->get_scheduled_swap();
if ( ! $swap_order || wu_request('preview-swap')) {
return;
}
$actions = [
'preview' => [
'title' => __('Preview', 'wp-multisite-waas'),
'url' => add_query_arg('preview-swap', 1),
],
];
$date = new \DateTime($swap_order->scheduled_date);
// translators: %s is the date, using the site format options
$message = sprintf(__('There is a change scheduled to take place on this membership in %s. You can preview the changes here. Scheduled changes are usually created by downgrades.', 'wp-multisite-waas'), $date->format(get_option('date_format')));
WP_Ultimo()->notices->add($message, 'warning', 'network-admin', false, $actions);
}
/**
* Registers the necessary scripts and styles for this admin page.
*
* @since 2.0.4
* @return void
*/
public function register_scripts(): void {
parent::register_scripts();
wp_enqueue_editor();
}
/**
* Register ajax forms that we use for membership.
*
* @since 2.0.0
* @return void
*/
public function register_forms(): void {
/*
* Transfer membership - Confirmation modal
*/
wu_register_form(
'transfer_membership',
[
'render' => [$this, 'render_transfer_membership_modal'],
'handler' => [$this, 'handle_transfer_membership_modal'],
'capability' => 'wu_transfer_memberships',
]
);
/*
* Edit/Add product
*/
wu_register_form(
'edit_membership_product',
[
'render' => [$this, 'render_edit_membership_product_modal'],
'handler' => [$this, 'handle_edit_membership_product_modal'],
]
);
/*
* Change Plan
*/
wu_register_form(
'change_membership_plan',
[
'render' => [$this, 'render_change_membership_plan_modal'],
'handler' => [$this, 'handle_change_membership_plan_modal'],
]
);
/*
* Delete Product
*/
wu_register_form(
'remove_membership_product',
[
'render' => [$this, 'render_remove_membership_product'],
'handler' => [$this, 'handle_remove_membership_product'],
]
);
add_filter(
'wu_data_json_success_delete_membership_modal',
fn($data_json) => [
'redirect_url' => wu_network_admin_url('wp-ultimo-memberships', ['deleted' => 1]),
]
);
}
/**
* Renders the deletion confirmation form.
*
* @since 2.0.0
* @return void
*/
function render_transfer_membership_modal(): void {
$membership = wu_get_membership(wu_request('id'));
if ( ! $membership) {
return;
}
$fields = [
'confirm' => [
'type' => 'toggle',
'title' => __('Confirm Transfer', 'wp-multisite-waas'),
'desc' => __('This will start the transfer of assets from one customer to another.', 'wp-multisite-waas'),
'html_attr' => [
'v-model' => 'confirmed',
],
],
'submit_button' => [
'type' => 'submit',
'title' => __('Start Transfer', 'wp-multisite-waas'),
'placeholder' => __('Start Transfer', 'wp-multisite-waas'),
'value' => 'save',
'classes' => 'button button-primary wu-w-full',
'wrapper_classes' => 'wu-items-end',
'html_attr' => [
'v-bind:disabled' => '!confirmed',
],
],
'id' => [
'type' => 'hidden',
'value' => $membership->get_id(),
],
'target_customer_id' => [
'type' => 'hidden',
'value' => wu_request('target_customer_id'),
],
];
$form = new \WP_Ultimo\UI\Form(
'total-actions',
$fields,
[
'views' => 'admin-pages/fields',
'classes' => 'wu-modal-form wu-widget-list wu-striped wu-m-0 wu-mt-0',
'field_wrapper_classes' => 'wu-w-full wu-box-border wu-items-center wu-flex wu-justify-between wu-p-4 wu-m-0 wu-border-t wu-border-l-0 wu-border-r-0 wu-border-b-0 wu-border-gray-300 wu-border-solid',
'html_attr' => [
'data-wu-app' => 'true',
'data-state' => wp_json_encode(
[
'confirmed' => false,
]
),
],
]
);
$form->render();
}
/**
* Handles the deletion of line items.
*
* @since 2.0.0
* @return void
*/
public function handle_transfer_membership_modal(): void {
$membership = wu_get_membership(wu_request('id'));
if ( ! $membership) {
wp_send_json_error(new \WP_Error('not-found', __('Membership not found.', 'wp-multisite-waas')));
}
$target_customer = wu_get_customer(wu_request('target_customer_id'));
if ( ! $target_customer) {
wp_send_json_error(new \WP_Error('not-found', __('Target customer not found.', 'wp-multisite-waas')));
}
if ($target_customer->get_id() === $membership->get_customer_id()) {
wp_send_json_error(new \WP_Error('not-found', __('Cannot transfer to the same customer.', 'wp-multisite-waas')));
}
/*
* Lock the membership to prevent memberships.
*/
$membership->lock();
/*
* Enqueue task
*/
wu_enqueue_async_action(
'wu_async_transfer_membership',
[
'membership_id' => $membership->get_id(),
'target_customer_id' => $target_customer->get_id(),
],
'membership'
);
wp_send_json_success(
[
'redirect_url' => wu_network_admin_url(
'wp-ultimo-edit-membership',
[
'id' => $membership->get_id(),
'transfer-started' => 1,
]
),
]
);
}
/**
* Allow child classes to register widgets, if they need them.
*
* @since 1.8.2
* @return void
*/
public function register_widgets(): void {
parent::register_widgets();
$labels = $this->get_labels();
$label = $this->get_object()->get_status_label();
$class = $this->get_object()->get_status_class();
$tag = "{$label}";
$gateway_message = false;
if ( ! empty($this->get_object()->get_gateway())) {
$gateway = wu_get_gateway($this->get_object()->get_gateway());
$gateway_message = $gateway ? $gateway->get_amount_update_message() : '';
}
$this->add_fields_widget(
'at_a_glance',
[
'title' => __('At a Glance', 'wp-multisite-waas'),
'position' => 'normal',
'classes' => 'wu-overflow-hidden wu-widget-inset',
'field_wrapper_classes' => 'wu-w-1/3 wu-box-border wu-items-center wu-flex wu-justify-between wu-p-4 wu-m-0 wu-border-t-0 wu-border-l-0 wu-border-r wu-border-b-0 wu-border-gray-300 wu-border-solid wu-float-left wu-relative',
'fields' => [
'status' => [
'type' => 'text-display',
'title' => __('Membership Status', 'wp-multisite-waas'),
'display_value' => $tag,
'tooltip' => '',
],
'hash' => [
'copy' => true,
'type' => 'text-display',
'title' => __('Reference ID', 'wp-multisite-waas'),
'display_value' => $this->get_object()->get_hash(),
],
'total_grossed' => [
'type' => 'text-display',
'title' => __('Total Grossed', 'wp-multisite-waas'),
'display_value' => wu_format_currency($this->get_object()->get_total_grossed(), $this->get_object()->get_currency()),
'wrapper_classes' => 'sm:wu-border-r-0',
],
],
]
);
$this->add_list_table_widget(
'membership-products',
[
'position' => 'normal',
'title' => __('Products', 'wp-multisite-waas'),
'table' => new \WP_Ultimo\List_Tables\Membership_Line_Item_List_Table(),
'after' => $this->output_widget_products(),
]
);
$this->add_list_table_widget(
'payments',
[
'title' => __('Payments', 'wp-multisite-waas'),
'table' => new \WP_Ultimo\List_Tables\Customers_Payment_List_Table(),
'query_filter' => [$this, 'payments_query_filter'],
]
);
$this->add_list_table_widget(
'sites',
[
'title' => __('Sites', 'wp-multisite-waas'),
'table' => new \WP_Ultimo\List_Tables\Memberships_Site_List_Table(),
'query_filter' => [$this, 'sites_query_filter'],
]
);
$this->add_list_table_widget(
'customer',
[
'title' => __('Linked Customer', 'wp-multisite-waas'),
'table' => new \WP_Ultimo\List_Tables\Site_Customer_List_Table(),
'query_filter' => [$this, 'customer_query_filter'],
]
);
$this->add_tabs_widget(
'options',
[
'title' => __('Membership Options', 'wp-multisite-waas'),
'position' => 'normal',
'sections' => apply_filters(
'wu_membership_options_sections',
[
'general' => [
'title' => __('General', 'wp-multisite-waas'),
'desc' => __('General membership options', 'wp-multisite-waas'),
'icon' => 'dashicons-wu-globe',
'fields' => [
'blocking' => [
'type' => 'toggle',
'title' => __('Is Blocking?', 'wp-multisite-waas'),
'desc' => __('Should we block access to the site, plugins, themes, and services after the expiration date is reached?', 'wp-multisite-waas'),
'value' => true,
],
],
],
'billing_info' => [
'title' => __('Billing Info', 'wp-multisite-waas'),
'desc' => __('Billing information for this particular membership.', 'wp-multisite-waas'),
'icon' => 'dashicons-wu-address',
'fields' => $this->get_object()->get_billing_address()->get_fields(),
],
],
$this->get_object()
),
]
);
/*
* Hide sensitive things when in swap preview.
*/
if ( ! $this->is_swap_preview) {
$this->add_list_table_widget(
'events',
[
'title' => __('Events', 'wp-multisite-waas'),
'table' => new \WP_Ultimo\List_Tables\Inside_Events_List_Table(),
'query_filter' => [$this, 'events_query_filter'],
]
);
}
$regular_fields = [
'status' => [
'type' => 'select',
'title' => __('Status', 'wp-multisite-waas'),
'desc' => __('The membership current status.', 'wp-multisite-waas'),
'value' => $this->get_object()->get_status(),
'options' => Membership_Status::to_array(),
'tooltip' => '',
'html_attr' => [
'v-model' => 'status',
],
'wrapper_html_attr' => [
'v-cloak' => '1',
],
],
'cancellation_reason' => [
'type' => 'textarea',
'title' => __('Cancellation Reason', 'wp-multisite-waas'),
'desc' => __('The reason why the customer cancelled this membership.', 'wp-multisite-waas'),
'value' => $this->get_object()->get_cancellation_reason(),
'wrapper_html_attr' => [
'v-show' => 'status == \'cancelled\'',
'v-cloak' => '1',
],
],
'cancel_gateway' => [
'type' => 'toggle',
'title' => __('Cancel on gateway', 'wp-multisite-waas'),
'desc' => __('If enable we will cancel the subscription on payment method', 'wp-multisite-waas'),
'value' => false,
'wrapper_html_attr' => [
'v-show' => ! empty($this->get_object()->get_gateway_customer_id()) ? 'status == \'cancelled\'' : 'false',
'v-cloak' => '1',
],
],
'preview-swap' => [
'type' => 'hidden',
'value' => wu_request('preview-swap', 0),
],
'customer_id' => [
'type' => 'model',
'title' => __('Customer', 'wp-multisite-waas'),
'placeholder' => __('Search a Customer...', 'wp-multisite-waas'),
'desc' => __('The owner of this membership.', 'wp-multisite-waas'),
'value' => $this->get_object()->get_customer_id(),
'tooltip' => '',
'html_attr' => [
'data-base-link' => wu_network_admin_url('wp-ultimo-edit-customer', ['id' => '']),
'v-model' => 'customer_id',
'data-model' => 'customer',
'data-value-field' => 'id',
'data-label-field' => 'display_name',
'data-search-field' => 'display_name',
'data-max-items' => 1,
'data-selected' => $this->get_object()->get_customer() ? wp_json_encode($this->get_object()->get_customer()->to_search_results()) : '',
],
'wrapper_html_attr' => [
'v-cloak' => '1',
],
],
'transfer_note' => [
'type' => 'note',
'desc' => __('Changing the customer will transfer this membership and all its assets, including sites, to the new customer.', 'wp-multisite-waas'),
'classes' => 'wu-p-2 wu-bg-red-100 wu-text-red-600 wu-rounded wu-w-full',
'wrapper_html_attr' => [
'v-show' => '(original_customer_id != customer_id) && customer_id',
'v-cloak' => '1',
],
],
'submit_save' => [
'type' => 'submit',
'title' => $labels['save_button_label'],
'placeholder' => $labels['save_button_label'],
'value' => 'save',
'classes' => 'button button-primary wu-w-full',
'html_attr' => [],
'wrapper_html_attr' => [
'v-show' => 'original_customer_id == customer_id || !customer_id',
'v-cloak' => '1',
],
],
'transfer' => [
'type' => 'link',
'display_value' => __('Transfer Membership', 'wp-multisite-waas'),
'wrapper_classes' => 'wu-bg-gray-200',
'classes' => 'button wubox wu-w-full wu-text-center',
'wrapper_html_attr' => [
'v-show' => 'original_customer_id != customer_id && customer_id',
'v-cloak' => '1',
],
'html_attr' => [
'v-bind:href' => "'" . wu_get_form_url(
'transfer_membership',
[
'id' => $this->get_object()->get_id(),
'target_customer_id' => '',
]
) . "=' + customer_id",
'title' => __('Transfer Membership', 'wp-multisite-waas'),
],
],
];
if ($this->get_object()->is_locked()) {
unset($regular_fields['transfer_note']);
unset($regular_fields['transfer']);
$regular_fields['submit_save']['title'] = __('Locked', 'wp-multisite-waas');
$regular_fields['submit_save']['value'] = 'none';
$regular_fields['submit_save']['html_attr']['disabled'] = 'disabled';
}
$this->add_fields_widget(
'save',
[
'html_attr' => [
'data-wu-app' => 'membership_save',
'data-state' => wp_json_encode(
[
'status' => $this->get_object()->get_status(),
'original_customer_id' => $this->get_object()->get_customer_id(),
'customer_id' => $this->get_object()->get_customer_id(),
'plan_id' => $this->get_object()->get_plan_id(),
]
),
],
'fields' => $regular_fields,
]
);
$this->add_fields_widget(
'pricing',
[
'title' => __('Billing Amount', 'wp-multisite-waas'),
'html_attr' => [
'data-wu-app' => 'true',
'data-state' => wp_json_encode(
[
'is_recurring' => $this->get_object()->is_recurring(),
'is_auto_renew' => $this->get_object()->should_auto_renew(),
'amount' => $this->get_object()->get_amount(),
'initial_amount' => $this->get_object()->get_initial_amount(),
'duration' => $this->get_object()->get_duration(),
'duration_unit' => $this->get_object()->get_duration_unit(),
'gateway' => $this->get_object()->get_gateway(),
'gateway_subscription_id' => $this->get_object()->get_gateway_subscription_id(),
'gateway_customer_id' => $this->get_object()->get_gateway_customer_id(),
]
),
],
'fields' => [
// Fields for price
'_initial_amount' => [
'type' => 'text',
'title' => __('Initial Amount', 'wp-multisite-waas'),
// translators: %s is a price placeholder value.
'placeholder' => sprintf(__('E.g. %s', 'wp-multisite-waas'), wu_format_currency(199)),
'desc' => __('The initial amount collected on the first payment.', 'wp-multisite-waas'),
'value' => $this->get_object()->get_initial_amount(),
'money' => true,
'html_attr' => [
'v-model' => 'initial_amount',
],
'wrapper_html_attr' => [
'v-cloak' => '1',
],
],
'initial_amount' => [
'type' => 'hidden',
'html_attr' => [
'v-model' => 'initial_amount',
],
],
'recurring' => [
'type' => 'toggle',
'title' => __('Is Recurring', 'wp-multisite-waas'),
'desc' => __('Use this option to manually enable or disable this membership.', 'wp-multisite-waas'),
'value' => $this->get_object()->is_recurring(),
'html_attr' => [
'v-model' => 'is_recurring',
],
'wrapper_html_attr' => [
'v-cloak' => '1',
],
],
'amount' => [
'type' => 'hidden',
'html_attr' => [
'v-model' => 'amount',
],
],
'recurring_amount_group' => [
'type' => 'group',
'title' => __('Recurring Amount', 'wp-multisite-waas'),
// translators: placeholder %1$s is the amount, %2$s is the duration (such as 1, 2, 3), and %3$s is the unit (such as month, year, week)
'desc' => sprintf(__('The customer will be charged %1$s every %2$s %3$s(s).', 'wp-multisite-waas'), '{{ wu_format_money(amount) }}', '{{ duration }}', '{{ duration_unit }}'),
'wrapper_html_attr' => [
'v-show' => 'is_recurring',
'v-cloak' => '1',
],
'fields' => [
'_amount' => [
'type' => 'text',
'value' => $this->get_object()->get_amount(),
'placeholder' => wu_format_currency('99'),
'wrapper_classes' => '',
'money' => true,
'html_attr' => [
'v-model' => 'amount',
],
],
'duration' => [
'type' => 'number',
'value' => $this->get_object()->get_duration(),
'placeholder' => '',
'wrapper_classes' => 'wu-mx-2 wu-w-1/3',
'min' => 0,
'html_attr' => [
'v-model' => 'duration',
'steps' => 1,
],
],
'duration_unit' => [
'type' => 'select',
'value' => $this->get_object()->get_duration_unit(),
'placeholder' => '',
'wrapper_classes' => 'wu-w-2/3',
'html_attr' => [
'v-model' => 'duration_unit',
],
'options' => [
'day' => __('Days', 'wp-multisite-waas'),
'week' => __('Weeks', 'wp-multisite-waas'),
'month' => __('Months', 'wp-multisite-waas'),
'year' => __('Years', 'wp-multisite-waas'),
],
],
],
],
'recurring_note' => [
'type' => 'note',
'desc' => $gateway_message,
'classes' => 'wu-p-2 wu-bg-red-100 wu-text-red-600 wu-rounded wu-w-full',
'wrapper_html_attr' => [
'v-show' => implode(
' && ',
[
'is_recurring',
'gateway',
'gateway === "' . $this->get_object()->get_gateway() . '"',
'gateway_subscription_id === "' . $this->get_object()->get_gateway_subscription_id() . '"',
'gateway_customer_id === "' . $this->get_object()->get_gateway_customer_id() . '"',
'(' . implode(
' || ',
[
$this->get_object()->get_amount() . ' !== amount',
$this->get_object()->get_duration() . ' != duration',
'"' . $this->get_object()->get_duration_unit() . '" !== duration_unit',
]
) . ')',
]
),
'v-cloak' => '1',
],
],
'billing_cycles' => [
'type' => 'number',
'title' => __('Billing Cycles', 'wp-multisite-waas'),
'placeholder' => __('E.g. 0', 'wp-multisite-waas'),
'desc' => __('How many times should we bill this customer. Leave 0 to charge until cancelled.', 'wp-multisite-waas'),
'value' => $this->get_object()->get_billing_cycles(),
'min' => 0,
'wrapper_html_attr' => [
'v-show' => 'is_recurring',
'v-cloak' => '1',
],
],
'times_billed' => [
'type' => 'number',
'title' => __('Times Billed', 'wp-multisite-waas'),
'desc' => __('The number of times this membership was billed so far.', 'wp-multisite-waas'),
'value' => $this->get_object()->get_times_billed(),
'min' => 0,
'wrapper_html_attr' => [
'v-show' => 'is_recurring',
'v-cloak' => '1',
],
],
'auto_renew' => [
'type' => 'toggle',
'title' => __('Auto-Renew?', 'wp-multisite-waas'),
'desc' => __('Activating this will tell the gateway to try to automatically charge for this membership.', 'wp-multisite-waas'),
'value' => $this->get_object()->should_auto_renew(),
'wrapper_html_attr' => [
'v-show' => 'is_recurring',
'v-cloak' => '1',
],
'html_attr' => [
'v-model' => 'is_auto_renew',
],
],
'gateway' => [
'type' => 'text',
'title' => __('Gateway', 'wp-multisite-waas'),
'placeholder' => __('e.g. stripe', 'wp-multisite-waas'),
'description' => __('e.g. stripe', 'wp-multisite-waas'),
'desc' => __('Payment gateway used to process the payment.', 'wp-multisite-waas'),
'value' => $this->get_object()->get_gateway(),
'wrapper_classes' => 'wu-w-full',
'html_attr' => [
'v-on:input' => 'gateway = $event.target.value.toLowerCase().replace(/[^a-z0-9-_]+/g, "")',
'v-bind:value' => 'gateway',
],
'wrapper_html_attr' => [
'v-cloak' => '1',
],
],
'gateway_customer_id_group' => [
'type' => 'group',
'desc' => function (): string {
$gateway_id = $this->get_object()->get_gateway();
if (empty($this->get_object()->get_gateway_customer_id())) {
return '';
}
$url = apply_filters("wu_{$gateway_id}_remote_customer_url", $this->get_object()->get_gateway_customer_id());
if ($url) {
return sprintf('%s', esc_attr($url), __('View on Gateway →', 'wp-multisite-waas'));
}
return '';
},
'wrapper_html_attr' => [
'v-show' => 'is_recurring && is_auto_renew',
'v-cloak' => '1',
],
'fields' => [
'gateway_customer_id' => [
'type' => 'text',
'title' => __('Gateway Customer ID', 'wp-multisite-waas'),
'placeholder' => __('Gateway Customer ID', 'wp-multisite-waas'),
'value' => $this->get_object()->get_gateway_customer_id(),
'tooltip' => '',
'wrapper_classes' => 'wu-w-full',
'wrapper_html_attr' => [],
'html_attr' => [
'v-model' => 'gateway_customer_id',
],
],
],
],
'gateway_subscription_id_group' => [
'type' => 'group',
'desc' => function (): string {
$gateway_id = $this->get_object()->get_gateway();
if (empty($this->get_object()->get_gateway_subscription_id())) {
return '';
}
$url = apply_filters("wu_{$gateway_id}_remote_subscription_url", $this->get_object()->get_gateway_subscription_id());
if ($url) {
return sprintf('%s', esc_attr($url), __('View on Gateway →', 'wp-multisite-waas'));
}
return '';
},
'wrapper_html_attr' => [
'v-show' => 'is_recurring && is_auto_renew',
'v-cloak' => '1',
],
'fields' => [
'gateway_subscription_id' => [
'type' => 'text',
'title' => __('Gateway Subscription ID', 'wp-multisite-waas'),
'placeholder' => __('Gateway Subscription ID', 'wp-multisite-waas'),
'value' => $this->get_object()->get_gateway_subscription_id(),
'tooltip' => '',
'wrapper_classes' => 'wu-w-full',
'wrapper_html_attr' => [],
'html_attr' => [
'v-model' => 'gateway_subscription_id',
],
],
],
],
'gateway_note' => [
'type' => 'note',
'desc' => __('We will try to cancel the old subscription on the gateway.', 'wp-multisite-waas'),
'classes' => 'wu-p-2 wu-bg-red-100 wu-text-red-600 wu-rounded wu-w-full',
'wrapper_html_attr' => [
'v-show' => 'is_recurring && (' . implode(
' || ',
[
'"' . $this->get_object()->get_gateway() . '" !== "" && gateway !== "' . $this->get_object()->get_gateway() . '"',
'"' . $this->get_object()->get_gateway_subscription_id() . '" !== "" && gateway_subscription_id !== "' . $this->get_object()->get_gateway_subscription_id() . '"',
'"' . $this->get_object()->get_gateway_customer_id() . '" !== "" && gateway_customer_id !== "' . $this->get_object()->get_gateway_customer_id() . '"',
]
) . ')',
'v-cloak' => '1',
],
],
],
]
);
$timestamp_fields = [];
$timestamps = [
'date_expiration' => __('Expires at', 'wp-multisite-waas'),
'date_renewed' => __('Last Renewed at', 'wp-multisite-waas'),
'date_trial_end' => __('Trial Ends at', 'wp-multisite-waas'),
'date_cancellation' => __('Cancelled at', 'wp-multisite-waas'),
];
foreach ($timestamps as $timestamp_name => $timestamp_label) {
$value = $this->get_object()->{"get_$timestamp_name"}();
$timestamp_fields[ $timestamp_name ] = [
'title' => $timestamp_label,
'type' => 'text-edit',
'date' => true,
'edit' => true,
'display_value' => $this->edit ? $value : '',
'value' => $value,
'placeholder' => '2020-04-04 12:00:00',
'html_attr' => [
'wu-datepicker' => 'true',
'data-format' => 'Y-m-d H:i:S',
'data-allow-time' => 'true',
],
];
}
if ( ! $this->get_object()->is_lifetime()) {
$timestamp_fields['convert_to_lifetime'] = [
'type' => 'submit',
'title' => __('Convert to Lifetime', 'wp-multisite-waas'),
'value' => 'convert_to_lifetime',
'classes' => 'button wu-w-full',
'wrapper_html_attr' => [],
];
}
$this->add_fields_widget(
'membership-timestamps',
[
'title' => __('Important Timestamps', 'wp-multisite-waas'),
'fields' => $timestamp_fields,
]
);
}
/**
* Renders the widget used to display the product list.
*
* @since 2.0.0
* @return string
*/
public function output_widget_products() {
return wu_get_template_contents(
'memberships/product-list',
[
'membership' => $this->get_object(),
]
);
}
/**
* Returns the title of the page.
*
* @since 2.0.0
* @return string Title of the page.
*/
public function get_title() {
return $this->edit ? __('Edit Membership', 'wp-multisite-waas') : __('Add new Membership', 'wp-multisite-waas');
}
/**
* Returns the title of menu for this page.
*
* @since 2.0.0
* @return string Menu label of the page.
*/
public function get_menu_title() {
return __('Edit Membership', 'wp-multisite-waas');
}
/**
* Returns the action links for that page.
*
* @since 1.8.2
* @return array
*/
public function action_links() {
return [];
}
/**
* Returns the labels to be used on the admin page.
*
* @since 2.0.0
* @return array
*/
public function get_labels() {
return [
'edit_label' => __('Edit Membership', 'wp-multisite-waas'),
'add_new_label' => __('Add new Membership', 'wp-multisite-waas'),
'updated_message' => __('Membership updated with success!', 'wp-multisite-waas'),
'title_placeholder' => __('Enter Membership Name', 'wp-multisite-waas'),
'title_description' => __('This name will be used on pricing tables, invoices, and more.', 'wp-multisite-waas'),
'save_button_label' => __('Save Membership', 'wp-multisite-waas'),
'save_description' => '',
'delete_button_label' => __('Delete Membership', 'wp-multisite-waas'),
'delete_description' => __('Be careful. This action is irreversible.', 'wp-multisite-waas'),
];
}
/**
* Filters the list table to return only relevant payments.
*
* @since 2.0.0
*
* @param array $args Query args passed to the list table.
* @return array Modified query args.
*/
public function payments_query_filter($args) {
$args['membership_id'] = $this->get_object()->get_id();
return $args;
}
/**
* Filters the list table to return only relevant sites.
*
* @since 2.0.0
*
* @param array $args Query args passed to the list table.
* @return array Modified query args.
*/
public function sites_query_filter($args) {
$args['meta_query'] = [
'membership_id' => [
'key' => 'wu_membership_id',
'value' => $this->get_object()->get_id(),
],
];
return $args;
}
/**
* Filters the list table to return only relevant customer.
*
* @since 2.0.0
*
* @param array $args Query args passed to the list table.
* @return array Modified query args.
*/
public function customer_query_filter($args) {
$args['id'] = $this->get_object()->get_customer_id();
return $args;
}
/**
* Filters the list table to return only relevant events.
*
* @since 2.0.0
*
* @param array $args Query args passed to the list table.
* @return array Modified query args.
*/
public function events_query_filter($args) {
$extra_args = [
'object_type' => 'membership',
'object_id' => absint($this->get_object()->get_id()),
];
return array_merge($args, $extra_args);
}
/**
* Returns the object being edit at the moment.
*
* @since 2.0.0
* @return \WP_Ultimo\Models\Membership
*/
public function get_object() {
if (null !== $this->object) {
return $this->object;
}
$item_id = wu_request('id', 0);
$item = wu_get_membership($item_id);
if ( ! $item) {
wp_safe_redirect(wu_network_admin_url('wp-ultimo-memberships'));
exit;
}
$this->object = $item;
/**
* Deal with scheduled swaps.
*/
if (wu_request('preview-swap')) {
$swap_order = $this->get_object()->get_scheduled_swap();
if ( ! $swap_order) {
return $this->object;
}
$this->is_swap_preview = true;
$actions = [
'preview' => [
'title' => __('← Go back', 'wp-multisite-waas'),
'url' => remove_query_arg('preview-swap', wu_get_current_url()),
],
];
$date = new \DateTime($swap_order->scheduled_date);
// translators: %s is the date, using the site format options
$message = sprintf(__('This is a preview. This page displays the final stage of the membership after the changes scheduled for %s. Saving here will persist these changes, so be careful.', 'wp-multisite-waas'), $date->format(get_option('date_format')));
WP_Ultimo()->notices->add($message, 'info', 'network-admin', false, $actions);
$this->object->swap($swap_order->order);
}
return $this->object;
}
/**
* Memberships have titles.
*
* @since 2.0.0
*/
public function has_title(): bool {
return false;
}
/**
* Handle convert to lifetime.
*
* @since 2.0.0
*/
protected function handle_convert_to_lifetime(): bool {
$object = $this->get_object();
$object->set_date_expiration(null);
$save = $object->save();
if (is_wp_error($save)) {
$errors = implode('
', $save->get_error_messages());
WP_Ultimo()->notices->add($errors, 'error', 'network-admin');
return false;
}
$array_params = [
'updated' => 1,
];
if (false === $this->edit) {
$array_params['id'] = $object->get_id();
}
$url = add_query_arg($array_params);
wp_safe_redirect($url);
return true;
}
/**
* Should implement the processes necessary to save the changes made to the object.
*
* @since 2.0.0
* @return true
*/
public function handle_save() {
$object = $this->get_object();
// Cancel membership on gateway
if ( (bool) wu_request('cancel_gateway', false) && wu_request('status', Membership_Status::CANCELLED)) {
$gateway = wu_get_gateway(wu_request('gateway'));
if ($gateway) {
$gateway->process_cancellation($object, $object->get_customer());
$_POST['gateway'] = '';
}
}
if (wu_request('submit_button') === 'convert_to_lifetime') {
return $this->handle_convert_to_lifetime();
}
$_POST['auto_renew'] = (bool) wu_request('auto_renew', false);
$billing_address = $object->get_billing_address();
$billing_address->attributes($_POST);
$valid_address = $billing_address->validate();
if (is_wp_error($valid_address)) {
$errors = implode('
', $valid_address->get_error_messages());
WP_Ultimo()->notices->add($errors, 'error', 'network-admin');
return false;
}
$object->set_billing_address($billing_address);
ob_start();
$status = parent::handle_save();
if ($this->is_swap_preview) {
ob_clean();
$object->delete_scheduled_swap();
$array_params = [
'updated' => 1,
];
$url = add_query_arg($array_params);
$url = remove_query_arg('preview-swap', $url);
wp_safe_redirect($url);
return true;
}
return $status;
}
/**
* Renders the add/edit line items form.
*
* @since 2.0.0
* @return void
*/
public function render_edit_membership_product_modal(): void {
$membership = wu_get_membership(wu_request('id'));
if ( ! $membership) {
return;
}
$gateway_message = false;
if ( ! empty($membership->get_gateway())) {
$gateway = wu_get_gateway($membership->get_gateway());
$gateway_message = $gateway ? $gateway->get_amount_update_message() : '';
}
$fields = [
'product_id' => [
'type' => 'model',
'title' => __('Product', 'wp-multisite-waas'),
'placeholder' => __('Search product...', 'wp-multisite-waas'),
'value' => '',
'tooltip' => '',
'html_attr' => [
'data-model' => 'product',
'data-value-field' => 'id',
'data-label-field' => 'name',
'data-search-field' => 'name',
'data-max-items' => 1,
'data-selected' => '',
],
],
'quantity' => [
'type' => 'number',
'title' => __('Quantity', 'wp-multisite-waas'),
'value' => 1,
'placeholder' => 1,
'wrapper_classes' => 'wu-w-1/2',
'html_attr' => [
'min' => 1,
'required' => 'required',
],
],
'update_price' => [
'type' => 'toggle',
'title' => __('Update Pricing', 'wp-multisite-waas'),
'desc' => __('Checking this box will update the membership pricing. Otherwise, the products will be added without changing the membership prices.', 'wp-multisite-waas'),
'html_attr' => [
'v-model' => 'update_pricing',
],
],
'transfer_note' => [
'type' => 'note',
'desc' => $gateway_message,
'classes' => 'sm:wu-p-2 wu-bg-red-100 wu-text-red-600 wu-rounded wu-w-full',
'wrapper_html_attr' => [
'v-show' => 'update_pricing',
'v-cloak' => '1',
],
],
'submit_button' => [
'type' => 'submit',
'title' => __('Add Product', 'wp-multisite-waas'),
'placeholder' => __('Add Product', 'wp-multisite-waas'),
'value' => 'save',
'classes' => 'wu-w-full button button-primary',
'wrapper_classes' => 'wu-items-end',
],
'id' => [
'type' => 'hidden',
'value' => $membership->get_id(),
],
];
if ( ! $gateway_message) {
unset($fields['transfer_note']);
}
$form = new \WP_Ultimo\UI\Form(
'edit_membership_product',
$fields,
[
'views' => 'admin-pages/fields',
'classes' => 'wu-modal-form wu-widget-list wu-striped wu-m-0 wu-mt-0',
'field_wrapper_classes' => 'wu-w-full wu-box-border wu-items-center wu-flex wu-justify-between wu-p-4 wu-m-0 wu-border-t wu-border-l-0 wu-border-r-0 wu-border-b-0 wu-border-gray-300 wu-border-solid',
'html_attr' => [
'data-wu-app' => 'edit_membership_product',
'data-state' => wu_convert_to_state(
[
'update_pricing' => 0,
]
),
],
]
);
$form->render();
}
/**
* Handles the add/edit of line items.
*
* @since 2.0.0
* @return mixed
*/
public function handle_edit_membership_product_modal(): void {
$membership = wu_get_membership(wu_request('id'));
if ( ! $membership) {
$error = new \WP_Error('membership-not-found', __('Membership not found.', 'wp-multisite-waas'));
wp_send_json_error($error);
}
$product = wu_get_product(wu_request('product_id'));
if ( ! $product) {
$error = new \WP_Error('product-not-found', __('Product not found.', 'wp-multisite-waas'));
wp_send_json_error($error);
}
$membership->add_product($product->get_id(), (int) wu_request('quantity', 1));
// if we are updating the pricing, we need to update the membership price.
if (wu_request('update_price')) {
$value_to_add = wu_get_membership_product_price($membership, $product->get_id(), (int) wu_request('quantity', 1));
if (is_wp_error($value_to_add)) {
wp_send_json_error($value_to_add);
}
$membership->set_amount($membership->get_amount() + $value_to_add);
}
$saved = $membership->save();
if (is_wp_error($saved)) {
wp_send_json_error($saved);
}
wp_send_json_success(
[
'redirect_url' => add_query_arg('updated', 1, $_SERVER['HTTP_REFERER']),
]
);
}
/**
* Renders the deletion confirmation form.
*
* @since 2.0.0
* @return void
*/
public function render_remove_membership_product(): void {
$membership = wu_get_membership(wu_request('id'));
if ( ! $membership) {
return;
}
$gateway_message = false;
if ($membership->get_gateway()) {
$gateway = wu_get_gateway($membership->get_gateway());
$gateway_message = $gateway ? $gateway->get_amount_update_message() : '';
}
$fields = [
'quantity' => [
'type' => 'number',
'title' => __('Quantity', 'wp-multisite-waas'),
'value' => 1,
'placeholder' => 1,
'wrapper_classes' => 'wu-w-1/2',
'html_attr' => [
'min' => 1,
'required' => 'required',
],
],
'update_price' => [
'type' => 'toggle',
'title' => __('Update Pricing?', 'wp-multisite-waas'),
'desc' => __('Checking this box will update the membership pricing. Otherwise, the products will be added without changing the membership prices.', 'wp-multisite-waas'),
'html_attr' => [
'v-model' => 'update_pricing',
],
],
'transfer_note' => [
'type' => 'note',
'desc' => $gateway_message,
'classes' => 'sm:wu-p-2 wu-bg-red-100 wu-text-red-600 wu-rounded wu-w-full',
'wrapper_html_attr' => [
'v-show' => 'update_pricing',
'v-cloak' => '1',
],
],
'submit_button' => [
'type' => 'submit',
'title' => __('Remove Product', 'wp-multisite-waas'),
'placeholder' => __('Remove Product', 'wp-multisite-waas'),
'value' => 'save',
'classes' => 'wu-w-full button button-primary',
'wrapper_classes' => 'wu-items-end',
],
'id' => [
'type' => 'hidden',
'value' => $membership->get_id(),
],
'product_id' => [
'type' => 'hidden',
'value' => wu_request('product_id', 0),
],
];
if ( ! $gateway_message) {
unset($fields['transfer_note']);
}
$form = new \WP_Ultimo\UI\Form(
'edit_membership_product',
$fields,
[
'views' => 'admin-pages/fields',
'classes' => 'wu-modal-form wu-widget-list wu-striped wu-m-0 wu-mt-0',
'field_wrapper_classes' => 'wu-w-full wu-box-border wu-items-center wu-flex wu-justify-between wu-p-4 wu-m-0 wu-border-t wu-border-l-0 wu-border-r-0 wu-border-b-0 wu-border-gray-300 wu-border-solid',
'html_attr' => [
'data-wu-app' => 'edit_membership_product',
'data-state' => wu_convert_to_state(
[
'update_pricing' => 0,
]
),
],
]
);
$form->render();
}
/**
* Handles the deletion of line items.
*
* @since 2.0.0
* @return void
*/
public function handle_remove_membership_product(): void {
$membership = wu_get_membership(wu_request('id'));
if ( ! $membership) {
$error = new \WP_Error('membership-not-found', __('Membership not found.', 'wp-multisite-waas'));
wp_send_json_error($error);
}
$product = wu_get_product(wu_request('product_id'));
if ( ! $product) {
$error = new \WP_Error('product-not-found', __('Product not found.', 'wp-multisite-waas'));
wp_send_json_error($error);
}
// Get the existing quantity by filtering the products array.
$existing_quantity = array_filter($membership->get_addon_products(), fn($item) => $item['product']->get_id() === $product->get_id())[0]['quantity'];
$quantity = (int) wu_request('quantity', 1);
$quantity = $quantity > $existing_quantity ? $existing_quantity : $quantity;
$membership->remove_product($product->get_id(), $quantity);
// if we are updating the pricing, we need to update the membership price.
if (wu_request('update_price')) {
$value_to_remove = wu_get_membership_product_price($membership, $product->get_id(), $quantity);
if (is_wp_error($value_to_remove)) {
wp_send_json_error($value_to_remove);
}
$membership->set_amount($membership->get_amount() - $value_to_remove);
}
$saved = $membership->save();
if (is_wp_error($saved)) {
wp_send_json_error($saved);
}
wp_send_json_success(
[
'redirect_url' => add_query_arg('updated', 1, $_SERVER['HTTP_REFERER']),
]
);
}
/**
* Renders the add/edit line items form.
*
* @since 2.0.0
* @return void
*/
public function render_change_membership_plan_modal(): void {
$membership = wu_get_membership(wu_request('id'));
if ( ! $membership) {
return;
}
$product = wu_get_product(wu_request('product_id'));
if ( ! $product) {
return;
}
$gateway_message = false;
if ($membership->get_gateway()) {
$gateway = wu_get_gateway($membership->get_gateway());
$gateway_message = $gateway ? $gateway->get_amount_update_message() : '';
}
$fields = [
'plan_id' => [
'type' => 'model',
'title' => __('Plan', 'wp-multisite-waas'),
'placeholder' => __('Search new Plan...', 'wp-multisite-waas'),
'desc' => __('Select a new plan for this membership.', 'wp-multisite-waas'),
'value' => $product->get_id(),
'tooltip' => '',
'html_attr' => [
'data-model' => 'plan',
'v-model' => 'plan_id',
'data-value-field' => 'id',
'data-label-field' => 'name',
'data-search-field' => 'name',
'data-max-items' => 1,
'data-selected' => wp_json_encode($product->to_search_results()),
],
],
'update_price' => [
'type' => 'toggle',
'title' => __('Update Pricing', 'wp-multisite-waas'),
'desc' => __('Checking this box will update the membership pricing. Otherwise, the products will be added without changing the membership prices.', 'wp-multisite-waas'),
'html_attr' => [
'v-model' => 'update_pricing',
],
],
'transfer_note' => [
'type' => 'note',
'desc' => $gateway_message,
'classes' => 'sm:wu-p-2 wu-bg-red-100 wu-text-red-600 wu-rounded wu-w-full',
'wrapper_html_attr' => [
'v-show' => 'update_pricing',
'v-cloak' => '1',
],
],
'submit_button' => [
'type' => 'submit',
'title' => __('Change Product', 'wp-multisite-waas'),
'placeholder' => __('Change Product', 'wp-multisite-waas'),
'value' => 'save',
'classes' => 'wu-w-full button button-primary',
'wrapper_classes' => 'wu-items-end',
'html_attr' => [
'v-bind:class' => 'plan_id == original_plan_id ? "button-disabled" : ""',
'v-bind:disabled' => 'plan_id == original_plan_id',
],
],
'id' => [
'type' => 'hidden',
'value' => $membership->get_id(),
],
];
if ( ! $gateway_message) {
unset($fields['transfer_note']);
}
$form = new \WP_Ultimo\UI\Form(
'change_membership_plan',
$fields,
[
'views' => 'admin-pages/fields',
'classes' => 'wu-modal-form wu-widget-list wu-striped wu-m-0 wu-mt-0',
'field_wrapper_classes' => 'wu-w-full wu-box-border wu-items-center wu-flex wu-justify-between wu-p-4 wu-m-0 wu-border-t wu-border-l-0 wu-border-r-0 wu-border-b-0 wu-border-gray-300 wu-border-solid',
'html_attr' => [
'data-wu-app' => 'change_membership_plan',
'data-state' => wu_convert_to_state(
[
'update_pricing' => 0,
'original_plan_id' => $product->get_id(),
'plan_id' => $product->get_id(),
]
),
],
]
);
$form->render();
}
/**
* Handles the add/edit of line items.
*
* @since 2.0.0
* @return mixed
*/
public function handle_change_membership_plan_modal(): void {
$membership = wu_get_membership(wu_request('id'));
if ( ! $membership) {
$error = new \WP_Error('membership-not-found', __('Membership not found.', 'wp-multisite-waas'));
wp_send_json_error($error);
}
$plan = wu_get_product(wu_request('plan_id'));
if ( ! $plan) {
$error = new \WP_Error('plan-not-found', __('Plan not found.', 'wp-multisite-waas'));
wp_send_json_error($error);
}
$original_plan_id = $membership->get_plan_id();
if (absint($original_plan_id) === absint($plan->get_id())) {
$error = new \WP_Error('same-plan', __('No change performed. The same plan selected.', 'wp-multisite-waas'));
wp_send_json_error($error);
}
$membership->set_plan_id($plan->get_id());
// if we are updating the pricing, we need to update the membership price.
if (wu_request('update_price')) {
$value_to_add = wu_get_membership_product_price($membership, $plan->get_id(), 1);
if (is_wp_error($value_to_add)) {
wp_send_json_error($value_to_add);
}
$value_to_remove = wu_get_membership_product_price($membership, $original_plan_id, 1);
if (is_wp_error($value_to_remove)) {
wp_send_json_error($value_to_remove);
}
$membership->set_amount($membership->get_amount() + $value_to_add - $value_to_remove);
}
$saved = $membership->save();
if (is_wp_error($saved)) {
wp_send_json_error($saved);
}
wp_send_json_success(
[
'redirect_url' => add_query_arg('updated', 1, $_SERVER['HTTP_REFERER']),
]
);
}
}