'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' => 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() ? 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' => 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' => 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_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_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_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' => 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']), ] ); } }