'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 = array( 'network_admin_menu' => 'wu_edit_products', ); /** * Register ajax forms. * * @since 2.0.0 * @return void */ public function register_forms() { /* * Adds the hooks to handle deletion. */ add_filter('wu_form_fields_delete_product_modal', array($this, 'product_extra_delete_fields'), 10, 2); add_action('wu_after_delete_product_modal', array($this, 'product_after_delete_actions')); add_filter("wu_page_{$this->id}_load", array($this, 'add_new_product_warning_message')); } // end register_forms; /** * Adds the new product warning. * * @since 2.0.0 * @return void */ public function add_new_product_warning_message() { if (wu_request('wu-new-model')) { if (!$this->get_object() || $this->get_object()->get_type() !== Product_Type::PLAN) { return; } // end if; \WP_Ultimo\UI\Tours::get_instance()->create_tour('new_product_warning', array( array( 'id' => 'new-product-warning', 'title' => __('On adding a new product...', 'wp-ultimo'), 'text' => array( __("You just successfully added a new product to your WP Ultimo network and that's awesome!", 'wp-ultimo'), __('Keep in mind that newly created products do not appear automatically in your checkout forms.', 'wp-ultimo'), __('To make a product available on registration, you will need to manually add it to the pricing table field of your checkout forms.', 'wp-ultimo'), ), 'buttons' => array( array( 'classes' => 'button wu-text-xs sm:wu-normal-case wu-float-left', 'text' => __('Go to Checkout Forms', 'wp-ultimo'), 'url' => wu_network_admin_url('wp-ultimo-checkout-forms'), ) ), 'attachTo' => array( 'element' => '#message.updated', 'on' => 'top', ), ), )); } // end if; } // end add_new_product_warning_message; /** * Adds the extra delete fields to the delete form. * * @since 2.0.0 * * @param array $fields The original fields. * @param object $product The product object. * @return array */ public function product_extra_delete_fields($fields, $product) { $custom_fields = array( 're_assignment_product_id' => array( 'type' => 'model', 'title' => __('Re-assign Memberships to', 'wp-ultimo'), 'placeholder' => __('Select Product...', 'wp-ultimo'), 'tooltip' => __('The product you select here will be assigned to all the memberships attached to the product you are deleting.', 'wp-ultimo'), 'html_attr' => array( 'data-model' => 'product', 'data-value-field' => 'id', 'data-label-field' => 'name', 'data-search-field' => 'name', 'data-max-items' => 1, 'data-exclude' => json_encode(array($product->get_id())) ), ), ); return array_merge($custom_fields, $fields); } // end product_extra_delete_fields; /** * Adds the primary domain handling to the product deletion. * * @since 2.0.0 * * @param object $product The product object. * @return void */ public function product_after_delete_actions($product) { global $wpdb; $new_product_id = wu_request('re_assignment_product_id'); $re_assignment_product = wu_get_product($new_product_id); if ($re_assignment_product) { $query = $wpdb->prepare( "UPDATE {$wpdb->base_prefix}wu_memberships SET product_id = %d WHERE product_id = %d", $re_assignment_product->get_id(), $product->get_id() ); $wpdb->query($query); // phpcs:ignore } // end if; } // end product_after_delete_actions; /** * Registers the necessary scripts and styles for this admin page. * * @since 2.0.0 * @return void */ public function register_scripts() { parent::register_scripts(); wp_enqueue_media(); } // end register_scripts; /** * Allow child classes to register widgets, if they need them. * * @since 1.8.2 * @return void */ public function register_widgets() { parent::register_widgets(); $this->add_fields_widget('description', array( 'title' => __('Description', 'wp-ultimo'), 'position' => 'normal', 'fields' => array( 'description' => array( 'type' => 'textarea', 'title' => __('Product Description', 'wp-ultimo'), 'placeholder' => __('Tell your customers what this product is about.', 'wp-ultimo'), 'tooltip' => __('This description is made available for layouts and can be shown to end customers.', 'wp-ultimo'), 'value' => $this->get_object()->get_description(), 'html_attr' => array( 'rows' => 3, ), ), ), )); $this->add_tabs_widget('product_options', array( 'title' => __('Product Options', 'wp-ultimo'), 'position' => 'normal', 'sections' => $this->get_product_option_sections(), )); /* * Handle legacy options for back-compat. */ $this->handle_legacy_options(); $this->add_list_table_widget('events', array( 'title' => __('Events', 'wp-ultimo'), 'table' => new \WP_Ultimo\List_Tables\Inside_Events_List_Table(), 'query_filter' => array($this, 'query_filter'), )); $save_widget_args = apply_filters('wu_product_edit_save_widget', array( 'html_attr' => array( 'data-wu-app' => 'product_pricing', 'data-state' => json_encode(array( 'is_recurring' => $this->get_object()->is_recurring(), 'pricing_type' => $this->get_object()->get_pricing_type(), 'has_trial' => $this->get_object()->get_trial_duration() > 0, 'has_setup_fee' => $this->get_object()->has_setup_fee(), 'setup_fee' => $this->get_object()->get_setup_fee(), 'amount' => $this->get_object()->get_formatted_amount(), 'duration' => $this->get_object()->get_duration(), 'duration_unit' => $this->get_object()->get_duration_unit(), )), ), 'fields' => array( // Fields for price 'pricing_type' => array( 'type' => 'select', 'title' => __('Pricing Type', 'wp-ultimo'), 'placeholder' => __('Select Pricing Type', 'wp-ultimo'), 'desc' => __('Products can be free, paid, or require further contact for pricing.', 'wp-ultimo'), 'value' => $this->get_object()->get_pricing_type(), 'tooltip' => '', 'options' => array( 'paid' => __('Paid', 'wp-ultimo'), 'free' => __('Free', 'wp-ultimo'), 'contact_us' => __('Contact Us', 'wp-ultimo'), ), 'wrapper_html_attr' => array( 'v-cloak' => '1', ), 'html_attr' => array( 'v-model' => 'pricing_type', ), ), 'contact_us_label' => array( 'type' => 'text', 'title' => __('Button Label', 'wp-ultimo'), 'placeholder' => __('E.g. Contact us', 'wp-ultimo'), 'desc' => __('This will be used on the pricing table CTA button, as the label.', 'wp-ultimo'), 'value' => $this->get_object()->get_contact_us_label(), 'wrapper_html_attr' => array( 'v-show' => "pricing_type == 'contact_us'", 'v-cloak' => '1', ), ), 'contact_us_link' => array( 'type' => 'url', 'title' => __('Button Link', 'wp-ultimo'), 'placeholder' => __('E.g. https://contactus.page.com', 'wp-ultimo'), 'desc' => __('This will be used on the pricing table CTA button.', 'wp-ultimo'), 'value' => $this->get_object()->get_contact_us_link(), 'wrapper_html_attr' => array( 'v-show' => "pricing_type == 'contact_us'", 'v-cloak' => '1', ), ), 'recurring' => array( 'type' => 'toggle', 'title' => __('Is Recurring?', 'wp-ultimo'), 'desc' => __('Check this if this product has a recurring charge.', 'wp-ultimo'), 'value' => $this->get_object()->is_recurring(), 'wrapper_html_attr' => array( 'v-show' => "pricing_type == 'paid'", 'v-cloak' => '1', ), 'html_attr' => array( 'v-model' => 'is_recurring', ), ), 'amount' => array( 'type' => 'hidden', 'html_attr' => array( 'v-model' => 'amount', ), ), '_amount' => array( 'type' => 'text', 'title' => __('Price', 'wp-ultimo'), 'placeholder' => __('Price', 'wp-ultimo'), 'value' => $this->get_object()->get_formatted_amount(), 'tooltip' => '', 'money' => true, 'wrapper_html_attr' => array( 'v-show' => "pricing_type == 'paid' && !is_recurring ", 'v-cloak' => '1', ), 'html_attr' => array( 'v-bind:name' => '""', 'v-model' => 'amount', ), ), 'amount_group' => array( 'type' => 'group', 'title' => __('Price', 'wp-ultimo'), // 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-ultimo'), '{{ wu_format_money(amount) }}', '{{ duration }}', '{{ duration_unit }}'), 'tooltip' => '', 'wrapper_html_attr' => array( 'v-show' => "is_recurring && pricing_type == 'paid'", 'v-cloak' => '1', ), 'fields' => array( '_amount' => array( 'type' => 'text', 'value' => $this->get_object()->get_formatted_amount(), 'placeholder' => wu_format_currency('99'), 'wrapper_classes' => '', 'money' => true, 'html_attr' => array( 'v-bind:name' => '""', 'v-model' => 'amount', ), ), 'duration' => array( 'type' => 'number', 'value' => $this->get_object()->get_duration(), 'placeholder' => '', 'wrapper_classes' => 'wu-mx-2 wu-w-1/3', 'min' => 0, 'html_attr' => array( 'v-model' => 'duration', 'steps' => 1, ), ), 'duration_unit' => array( 'type' => 'select', 'value' => $this->get_object()->get_duration_unit(), 'placeholder' => '', 'wrapper_classes' => 'wu-w-2/3', 'html_attr' => array( 'v-model' => 'duration_unit', ), 'options' => array( 'day' => __('Days', 'wp-ultimo'), 'week' => __('Weeks', 'wp-ultimo'), 'month' => __('Months', 'wp-ultimo'), 'year' => __('Years', 'wp-ultimo'), ), ), ), ), 'billing_cycles' => array( 'type' => 'number', 'title' => __('Billing Cycles', 'wp-ultimo'), 'placeholder' => __('E.g. 1', 'wp-ultimo'), 'desc' => __('How many times should we bill this customer. Leave 0 to charge until cancelled.', 'wp-ultimo'), 'value' => $this->get_object()->get_billing_cycles(), 'tooltip' => '', 'wrapper_html_attr' => array( 'v-show' => "is_recurring && pricing_type == 'paid'", 'v-cloak' => '1', ), ), 'has_trial' => array( 'type' => 'toggle', 'title' => __('Offer Trial', 'wp-ultimo'), 'desc' => __('Check if you want to add a trial period to this product.', 'wp-ultimo'), 'value' => $this->get_object()->has_trial(), 'wrapper_html_attr' => array( 'v-show' => "pricing_type == 'paid'", 'v-cloak' => '1', ), 'html_attr' => array( 'v-model' => 'has_trial', ), ), 'trial_group' => array( 'type' => 'group', 'title' => __('Trial', 'wp-ultimo'), 'tooltip' => '', 'wrapper_html_attr' => array( 'v-show' => "has_trial && pricing_type == 'paid'", 'v-cloak' => '1', ), 'fields' => array( 'trial_duration' => array( 'type' => 'number', 'value' => $this->get_object()->get_trial_duration(), 'placeholder' => '', 'wrapper_classes' => 'wu-mr-2 wu-w-1/3', ), 'trial_duration_unit' => array( 'type' => 'select', 'value' => $this->get_object()->get_trial_duration_unit(), 'placeholder' => '', 'wrapper_classes' => 'wu-w-2/3', 'options' => array( 'day' => __('Days', 'wp-ultimo'), 'week' => __('Weeks', 'wp-ultimo'), 'month' => __('Months', 'wp-ultimo'), 'year' => __('Years', 'wp-ultimo'), ), ), ), ), 'has_setup_fee' => array( 'type' => 'toggle', 'title' => __('Add Setup Fee?', 'wp-ultimo'), 'desc' => __('Check if you want to add a setup fee.', 'wp-ultimo'), 'value' => $this->get_object()->has_setup_fee(), 'wrapper_html_attr' => array( 'v-show' => "pricing_type == 'paid'", 'v-cloak' => '1', ), 'html_attr' => array( 'v-model' => 'has_setup_fee', ), ), 'setup_fee' => array( 'type' => 'hidden', 'html_attr' => array( 'v-model' => 'setup_fee', ), ), '_setup_fee' => array( 'type' => 'text', 'money' => true, 'title' => __('Setup Fee', 'wp-ultimo'), 'desc' => __('The setup fee will be added to the first charge, in addition to the regular price of the product.', 'wp-ultimo'), // translators: %s is a price placeholder value. 'placeholder' => sprintf(__('E.g. %s', 'wp-ultimo'), wu_format_currency(199)), 'value' => $this->get_object()->get_formatted_amount('setup_fee'), 'wrapper_html_attr' => array( 'v-show' => "has_setup_fee && pricing_type == 'paid'", 'v-cloak' => '1', ), 'html_attr' => array( 'v-model' => 'setup_fee', ), ), ), ), $this->get_object()); $this->add_save_widget('save', $save_widget_args); $this->add_fields_widget('active', array( 'title' => __('Active', 'wp-ultimo'), 'fields' => array( 'active' => array( 'type' => 'toggle', 'title' => __('Active', 'wp-ultimo'), 'desc' => __('Use this option to manually enable or disable this product for new sign-ups.', 'wp-ultimo'), 'value' => $this->get_object()->is_active(), ), ), )); $this->add_fields_widget('image', array( 'title' => __('Product Image', 'wp-ultimo'), 'fields' => array( 'featured_image_id' => array( 'type' => 'image', 'stacked' => true, 'title' => __('Product Image', 'wp-ultimo'), 'desc' => __('This image is used on product list tables and other places.', 'wp-ultimo'), 'value' => $this->get_object()->get_featured_image_id(), 'img' => $this->get_object()->get_featured_image(), ), ), )); } // end register_widgets; /** * Handles legacy advanced options for plans. * * @since 2.0.0 * @return void */ public function handle_legacy_options() { global $wp_filter; $tabs = apply_filters_deprecated('wu_plans_advanced_options_tabs', array( array(), ), '2.0.0', 'wu_product_options_sections'); if (!isset($wp_filter['wu_plans_advanced_options_after_panels'])) { return; } // end if; wp_enqueue_style('wu-legacy-admin-tabs', wu_get_asset('legacy-admin-tabs.css', 'css'), false, wu_get_version()); $priorities = $wp_filter['wu_plans_advanced_options_after_panels']->callbacks; $fields = array( 'heading' => array( 'type' => 'header', 'title' => __('Legacy Options', 'wp-ultimo'), // translators: %s is the name of legacy add-ons. 'desc' => sprintf(__('Options for %s, and others.', 'wp-ultimo'), implode(', ', $tabs)), ), ); foreach ($priorities as $priority => $callbacks) { foreach ($callbacks as $id => $callable) { $fields[$id] = array( 'type' => 'html', 'classes' => 'wu--mt-2', 'content' => function() use ($callable) { call_user_func($callable['function'], new \WU_Plan($this->get_object())); }, ); } // end foreach; } // end foreach; $this->add_fields_widget('legacy-options', array( 'title' => __('Legacy Options', 'wp-ultimo'), 'position' => 'normal', 'fields' => $fields, 'classes' => 'wu-legacy-options-panel', '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' => array( 'style' => 'margin-top: -5px;', ), )); } // end handle_legacy_options; /** * Returns the list of sections and its fields for the product page. * * Can be filtered via 'wu_product_options_sections'. * * @see inc/managers/class-limitation-manager.php * * @since 2.0.0 * @return array */ protected function get_product_option_sections() { $sections = array( 'general' => array( 'title' => __('General', 'wp-ultimo'), 'desc' => __('General product options such as product slug, type, etc.', 'wp-ultimo'), 'icon' => 'dashicons-wu-globe', 'state' => array( 'slug' => $this->get_object()->get_slug(), 'product_type' => $this->get_object()->get_type(), ), 'fields' => array( 'slug' => array( 'type' => 'text', 'title' => __('Product Slug', 'wp-ultimo'), 'placeholder' => __('e.g. premium', 'wp-ultimo'), 'desc' => __('This serves as a id to the product in a number of different contexts.', 'wp-ultimo'), 'value' => $this->get_object()->get_slug(), 'tooltip' => __('Lowercase alpha-numeric characters with dashes or underlines. No spaces allowed.', 'wp-ultimo'), 'html_attr' => array( 'v-on:input' => 'slug = $event.target.value.toLowerCase().replace(/[^a-z0-9-_]+/g, "")', 'v-bind:value' => 'slug', ), ), // Fields for price 'type' => array( 'type' => 'select', 'title' => __('Product Type', 'wp-ultimo'), 'placeholder' => __('Product Type', 'wp-ultimo'), 'desc' => __('Different product types have different options.', 'wp-ultimo'), 'value' => $this->get_object()->get_type(), 'tooltip' => '', 'options' => Product_Type::to_array(), 'html_attr' => array( 'v-model' => 'product_type', ), ), 'modules[customer_user_role][limit]' => array( 'title' => __('Customer Role', 'wp-ultimo'), 'desc' => __('Select the role WP Ultimo should use when adding the user to their newly created site.', 'wp-ultimo'), 'type' => 'select', 'value' => $this->get_object()->get_customer_role(), 'default' => 'administrator', 'options' => fn() => wu_get_roles_as_options(true), 'wrapper_html_attr' => array( 'v-show' => 'product_type === "plan"', 'v-cloak' => 1, ), ), ), ), ); $sections['ups-and-downs'] = array( 'title' => __('Up & Downgrades', 'wp-ultimo'), 'desc' => __('Settings related to upgrade and downgrade flows.', 'wp-ultimo'), 'icon' => 'dashicons-wu-shop', 'v-show' => 'product_type === "plan"', 'state' => array(), 'fields' => array( 'group' => array( 'title' => __('Plan Group', 'wp-ultimo'), 'desc' => __('Add related plans to the same group to have them show up as upgrade/downgrade paths.', 'wp-ultimo'), 'placeholder' => __('Type and press enter to search and/or add.', 'wp-ultimo'), 'type' => 'select', 'value' => $this->get_object()->get_group(), 'options' => array_merge(array('' => __('Select Group', 'wp-ultimo')), wu_get_product_groups()), 'html_attr' => array( 'data-selectize-categories' => 999, 'data-max-items' => 1, ), ), 'list_order' => array( 'title' => __('Product Order', 'wp-ultimo'), 'desc' => __('Plans are shown in the order determined by this parameter, from the lowest to the highest.', 'wp-ultimo'), 'placeholder' => __('Type and press enter to search and/or add.', 'wp-ultimo'), 'type' => 'number', 'value' => $this->get_object()->get_list_order(), ), 'available_addons' => array( 'type' => 'model', 'title' => __('Offer Add-ons', 'wp-ultimo'), 'placeholder' => __('Search for a package or service', 'wp-ultimo'), 'desc' => __('This products will be offered inside upgrade/downgrade forms as order bumps.', 'wp-ultimo'), 'html_attr' => array( 'data-exclude' => implode(',', array_keys(wu_get_plans_as_options())), 'data-model' => 'product', 'data-value-field' => 'id', 'data-label-field' => 'name', 'data-search-field' => 'name', 'data-max-items' => 99, 'data-selected' => json_encode(wu_get_products(array( 'id__in' => $this->get_object()->get_available_addons(), 'id__not_in' => array_keys(wu_get_plans_as_options()), ))), ), ), ), ); $sections['price-variations'] = array( 'title' => __('Price Variations', 'wp-ultimo'), 'desc' => __('Discounts for longer membership commitments.', 'wp-ultimo'), 'icon' => 'dashicons-wu-price-tag', 'state' => array( 'enable_price_variations' => !empty($this->get_object()->get_price_variations()), 'price_variations' => $this->get_object()->get_price_variations(), ), 'fields' => array( 'enable_price_variations' => array( 'type' => 'toggle', 'title' => __('Enable Price Variations', 'wp-ultimo'), 'desc' => __('Price Variations are an easy way to offer discounted prices for longer subscription commitments.', 'wp-ultimo'), 'value' => false, 'html_attr' => array( 'v-model' => 'enable_price_variations', ), ), 'price_variations' => array( 'type' => 'group', // translators: 1 is the price, 2 is the duration and 3 the duration unit 'desc' => sprintf(__('A discounted price of %1$s will be used when memberships are created with the recurrence of %2$s %3$s(s) instead of the regular period.', 'wp-ultimo'), '{{ wu_format_money(price_variation.amount) }}', '{{ price_variation.duration }}', '{{ price_variation.duration_unit }}'), 'tooltip' => '', 'wrapper_classes' => 'wu-relative', 'wrapper_html_attr' => array( 'v-for' => '(price_variation, index) in price_variations', 'v-show' => 'enable_price_variations', 'v-cloak' => '1', ), 'fields' => array( 'price_variations_remove' => array( 'type' => 'note', 'desc' => sprintf('', __('Remove', 'wp-ultimo')), 'wrapper_classes' => 'wu-absolute wu-top-0 wu-right-0', ), 'price_variations_duration' => array( 'type' => 'number', 'title' => __('Duration', 'wp-ultimo'), 'placeholder' => '', 'wrapper_classes' => 'wu-w-1/3', 'min' => 1, 'html_attr' => array( 'v-model' => 'price_variation.duration', 'steps' => 1, 'v-bind:name' => '"price_variations[" + index + "][duration]"', ), ), 'price_variations_duration_unit' => array( 'type' => 'select', 'title' => __('Period', 'wp-ultimo'), 'placeholder' => '', 'wrapper_classes' => 'wu-w-1/3 wu-mx-2', 'html_attr' => array( 'v-model' => 'price_variation.duration_unit', 'v-bind:name' => '"price_variations[" + index + "][duration_unit]"', ), 'options' => array( 'day' => __('Days', 'wp-ultimo'), 'week' => __('Weeks', 'wp-ultimo'), 'month' => __('Months', 'wp-ultimo'), 'year' => __('Years', 'wp-ultimo'), ), ), // Bind the amount of the price variation to another field so we don't send the formatted value to the server. 'price_variations_amount' => array( 'type' => 'hidden', 'html_attr' => array( 'v-bind:value' => 'price_variation.amount', 'v-bind:name' => '"price_variations[" + index + "][amount]"' ), ), '_price_variations_amount' => array( 'type' => 'text', 'title' => __('New Price', 'wp-ultimo'), 'placeholder' => wu_format_currency('99'), 'wrapper_classes' => 'wu-w-1/3', 'money' => true, 'html_attr' => array( 'v-model' => 'price_variation.amount', 'v-bind:name' => '""', ), ), ), ), 'repeat' => array( 'type' => 'submit', 'title' => __('Add new Price Variation', 'wp-ultimo'), 'classes' => 'button wu-self-end', 'wrapper_classes' => 'wu-bg-whiten wu-items-end', 'wrapper_html_attr' => array( 'v-show' => 'enable_price_variations', 'v-cloak' => '1', ), 'html_attr' => array( 'v-on:click.prevent' => '() => price_variations.push({ duration: 1, duration_unit: "month", amount: get_value("wu_product_pricing").amount, })', ), ), ), ); $sections['taxes'] = array( 'title' => __('Taxes', 'wp-ultimo'), 'desc' => __('Tax settings for your products.', 'wp-ultimo'), 'icon' => 'dashicons-wu-credit', 'state' => array( 'taxable' => $this->get_object()->is_taxable(), ), 'fields' => array( 'taxable' => array( 'type' => 'toggle', 'title' => __('Is Taxable?', 'wp-ultimo'), 'desc' => __('Enable this if you plan to collect taxes for this product.', 'wp-ultimo'), 'value' => $this->get_object()->is_taxable(), 'html_attr' => array( 'v-model' => 'taxable', ), ), 'tax_category' => array( 'type' => 'select', 'title' => __('Tax Category', 'wp-ultimo'), 'desc' => __('Select the product tax category.', 'wp-ultimo'), 'value' => $this->get_object()->get_tax_category(), 'options' => 'wu_get_tax_categories_as_options', 'wrapper_html_attr' => array( 'v-cloak' => '1', 'v-show' => 'require("taxable", true)', ), ), ), ); $sections['allowed_templates'] = array( 'title' => __('Site Templates', 'wp-ultimo'), 'desc' => __('Limit which site templates are available for this particular template.', 'wp-ultimo'), 'icon' => 'dashicons-wu-grid1 wu-align-text-bottom', 'v-show' => "get_state_value('product_type', 'none') !== 'service'", 'state' => array( 'allow_site_templates' => $this->get_object()->get_limitations()->site_templates->is_enabled(), 'site_template_selection_mode' => $this->get_object()->get_limitations()->site_templates->get_mode(), 'pre_selected_template' => $this->get_object()->get_limitations()->site_templates->get_pre_selected_site_template(), ), 'fields' => array( 'modules[site_templates][enabled]' => array( 'type' => 'toggle', 'title' => __('Allow Site Templates', 'wp-ultimo'), 'desc' => __('Toggle this option on to allow this plan to use Site Templates. If this option is disabled, sign-ups on this plan will get a default WordPress site.', 'wp-ultimo'), 'wrapper_html_attr' => array( 'v-cloak' => '1', ), 'html_attr' => array( 'v-model' => 'allow_site_templates', ), ), 'modules[site_templates][mode]' => array( 'type' => 'select', 'title' => __('Site Template Selection Mode', 'wp-ultimo'), 'placeholder' => __('Site Template Selection Mode', 'wp-ultimo'), 'desc' => __('Select the type of limitation you want to apply.', 'wp-ultimo'), 'tooltip' => __('"Default" will follow the settings of the checkout form: if you have a template selection field in there, all the templates selected will show up. If no field is present, then a default WordPress site will be created.

"Assign Site Template" forces new accounts with this plan to use a particular template site (this option removes the template selection field from the signup, if one exists).

Finally, "Choose Available Site Templates", overrides the templates selected on the checkout form with the templates selected here, while also giving you the chance of pre-select a template to be used as default.', 'wp-ultimo'), 'value' => 'default', 'options' => array( 'default' => __('Default', 'wp-ultimo'), 'assign_template' => __('Assign Site Template', 'wp-ultimo'), 'choose_available_templates' => __('Choose Available Site Templates', 'wp-ultimo') ), 'html_attr' => array( 'v-model' => 'site_template_selection_mode', ), 'wrapper_html_attr' => array( 'v-cloak' => '1', 'v-show' => 'allow_site_templates', ), ), 'templates' => array( 'type' => 'html', 'title' => __('Site Templates', 'wp-ultimo'), 'desc' => esc_attr(sprintf('{{ site_template_selection_mode === "assign_template" ? "%s" : "%s" }}', __('Select the Site Template to assign.', 'wp-ultimo'), __('Customize the access level of each Site Template below.', 'wp-ultimo'))), 'wrapper_html_attr' => array( 'v-cloak' => '1', 'v-show' => "allow_site_templates && site_template_selection_mode !== 'default'", ), 'content' => fn() => $this->get_site_template_selection_list($this->get_object()), ), ), ); return apply_filters('wu_product_options_sections', $sections, $this->get_object()); } // end get_product_option_sections; /** * Returns the HTML markup for the plugin selector list. * * @since 2.0.0 * * @param \WP_Ultimo\Models\Product $product The product being edited. * @return string */ public function get_site_template_selection_list($product) { $all_templates = wu_get_site_templates(); return wu_get_template_contents('limitations/site-template-selector', array( 'templates' => $all_templates, 'product' => $product, )); } // end get_site_template_selection_list; /** * Returns the title of the page. * * @since 2.0.0 * @return string Title of the page. */ public function get_title() { return $this->edit ? __('Edit Product', 'wp-ultimo') : __('Add new Product', 'wp-ultimo'); } // end get_title; /** * 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 Product', 'wp-ultimo'); } // end get_menu_title; /** * Returns the action links for that page. * * @since 1.8.2 * @return array */ public function action_links() { $actions = array(); if ($this->get_object()->get_type() === 'plan' && $this->edit) { $shareable_link = $this->get_object()->get_shareable_link(); $actions[] = array( 'url' => '#', 'label' => __('Click to copy Shareable Link', 'wp-ultimo'), 'icon' => 'wu-attachment', 'classes' => 'wu-copy', 'attrs' => 'data-clipboard-text="' . esc_attr($shareable_link) . '"', ); } // end if; return $actions; } // end action_links; /** * Returns the labels to be used on the admin page. * * @since 2.0.0 * @return array */ public function get_labels() { return array( 'edit_label' => __('Edit Product', 'wp-ultimo'), 'add_new_label' => __('Add new Product', 'wp-ultimo'), 'updated_message' => __('Product updated with success!', 'wp-ultimo'), 'title_placeholder' => __('Enter Product Name', 'wp-ultimo'), 'title_description' => __('This name will be used on pricing tables, invoices, and more.', 'wp-ultimo'), 'save_button_label' => __('Save Product', 'wp-ultimo'), 'save_description' => '', 'delete_button_label' => __('Delete Product', 'wp-ultimo'), 'delete_description' => __('Be careful. This action is irreversible.', 'wp-ultimo'), ); } // end get_labels; /** * 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 query_filter($args) { $extra_args = array( 'object_type' => 'product', 'object_id' => absint($this->get_object()->get_id()), ); return array_merge($args, $extra_args); } // end query_filter; /** * Returns the object being edit at the moment. * * @since 2.0.0 * @return \WP_Ultimo\Models\Product */ public function get_object() { if ($this->object !== null) { return $this->object; } // end if; if (isset($_GET['id'])) { $query = new \WP_Ultimo\Database\Products\Product_Query; $item = $query->get_item_by('id', $_GET['id']); if (!$item) { wp_redirect(wu_network_admin_url('wp-ultimo-products')); exit; } // end if; $this->object = $item; return $this->object; } // end if; $this->object = new Product; return $this->object; } // end get_object; /** * Products have titles. * * @since 2.0.0 */ public function has_title(): bool { return true; } // end has_title; /** * Should implement the processes necessary to save the changes made to the object. * * @since 2.0.0 * @return void */ public function handle_save() { /* * Set the recurring value to zero if the toggle is disabled. */ if (!wu_request('recurring')) { $_POST['recurring'] = false; } // end if; if (!wu_request('legacy_options')) { $_POST['legacy_options'] = false; } // end if; if (!wu_request('featured_plan')) { $_POST['featured_plan'] = false; } // end if; /* * Set the setup fee value to zero if the toggle is disabled. */ if (!wu_request('has_setup_fee')) { $_POST['setup_fee'] = 0; } // end if; /* * Disabled Trial */ if (!wu_request('has_trial')) { $_POST['trial_duration'] = 0; } // end if; /* * Set the setup fee value to zero if the toggle is disabled. */ if (!wu_request('price_variations')) { $_POST['price_variations'] = array(); } // end if; /* * Set the taxable value to zero if the toggle is disabled. */ if (!wu_request('taxable')) { $_POST['taxable'] = 0; } // end if; parent::handle_save(); } // end handle_save; } // end class Product_Edit_Admin_Page;