Initial Commit

This commit is contained in:
David Stone
2024-11-30 18:24:12 -07:00
commit e8f7955c1c
5432 changed files with 1397750 additions and 0 deletions

View File

@ -0,0 +1,336 @@
<?php
/**
* Adds the Account Summary Element UI to the Admin Panel.
*
* @package WP_Ultimo
* @subpackage UI
* @since 2.0.0
*/
namespace WP_Ultimo\UI;
use \WP_Ultimo\UI\Base_Element;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Adds the Checkout Element UI to the Admin Panel.
*
* @since 2.0.0
*/
class Account_Summary_Element extends Base_Element {
use \WP_Ultimo\Traits\Singleton;
/**
* The id of the element.
*
* Something simple, without prefixes, like 'checkout', or 'pricing-tables'.
*
* This is used to construct shortcodes by prefixing the id with 'wu_'
* e.g. an id checkout becomes the shortcode 'wu_checkout' and
* to generate the Gutenberg block by prefixing it with 'wp-ultimo/'
* e.g. checkout would become the block 'wp-ultimo/checkout'.
*
* @since 2.0.0
* @var string
*/
public $id = 'account-summary';
/**
* Controls if this is a public element to be used in pages/shortcodes by user.
*
* @since 2.0.24
* @var boolean
*/
protected $public = true;
/**
* The current site.
*
* @since 2.2.0
* @var \WP_Ultimo\Models\Site
*/
protected $site;
/**
* The current membership.
*
* @since 2.2.0
* @var \WP_Ultimo\Models\Membership
*/
protected $membership;
/**
* The current product.
*
* @since 2.2.0
* @var \WP_Ultimo\Models\Product
*/
protected $product;
/**
* The attributes to be passed to the template.
*
* @since 2.2.0
* @var array
*/
protected $atts;
/**
* The icon of the UI element.
* e.g. return fa fa-search
*
* @since 2.0.0
* @param string $context One of the values: block, elementor or bb.
* @return string
*/
public function get_icon($context = 'block') {
if ($context === 'elementor') {
return 'eicon-call-to-action';
} // end if;
return 'fa fa-search';
} // end get_icon;
/**
* The title of the UI element.
*
* This is used on the Blocks list of Gutenberg.
* You should return a string with the localized title.
* e.g. return __('My Element', 'wp-ultimo').
*
* @since 2.0.0
* @return string
*/
public function get_title() {
return __('Account Summary', 'wp-ultimo');
} // end get_title;
/**
* The description of the UI element.
*
* This is also used on the Gutenberg block list
* to explain what this block is about.
* You should return a string with the localized title.
* e.g. return __('Adds a checkout form to the page', 'wp-ultimo').
*
* @since 2.0.0
* @return string
*/
public function get_description() {
return __('Adds a account summary block to the page.', 'wp-ultimo');
} // end get_description;
/**
* The list of fields to be added to Gutenberg.
*
* If you plan to add Gutenberg controls to this block,
* you'll need to return an array of fields, following
* our fields interface (@see inc/ui/class-field.php).
*
* You can create new Gutenberg panels by adding fields
* with the type 'header'. See the Checkout Elements for reference.
*
* @see inc/ui/class-checkout-element.php
*
* Return an empty array if you don't have controls to add.
*
* @since 2.0.0
* @return array
*/
public function fields() {
$fields = array();
$fields['header'] = array(
'title' => __('General', 'wp-ultimo'),
'desc' => __('General', 'wp-ultimo'),
'type' => 'header',
);
$fields['title'] = array(
'type' => 'text',
'title' => __('Title', 'wp-ultimo'),
'value' => __('About this Site', 'wp-ultimo'),
'desc' => __('Leave blank to hide the title completely.', 'wp-ultimo'),
'tooltip' => '',
);
return $fields;
} // end fields;
/**
* The list of keywords for this element.
*
* Return an array of strings with keywords describing this
* element. Gutenberg uses this to help customers find blocks.
*
* e.g.:
* return array(
* 'WP Ultimo',
* 'Checkout',
* 'Form',
* 'Cart',
* );
*
* @since 2.0.0
* @return array
*/
public function keywords() {
return array(
'WP Ultimo',
'Account',
'Summary',
);
} // end keywords;
/**
* List of default parameters for the element.
*
* If you are planning to add controls using the fields,
* it might be a good idea to use this method to set defaults
* for the parameters you are expecting.
*
* These defaults will be used inside a 'wp_parse_args' call
* before passing the parameters down to the block render
* function and the shortcode render function.
*
* @since 2.0.0
* @return array
*/
public function defaults() {
return array(
'title' => __('About this Site', 'wp-ultimo'),
);
} // end defaults;
/**
* Runs early on the request lifecycle as soon as we detect the shortcode is present.
*
* @since 2.0.0
* @return void
*/
public function setup() {
$this->site = WP_Ultimo()->currents->get_site();
if (!$this->site || !$this->site->is_customer_allowed()) {
$this->set_display(false);
return;
} // end if;
$this->membership = $this->site->get_membership();
$this->product = $this->membership ? $this->membership->get_plan() : false;
is_multisite() && switch_to_blog($this->site->get_id());
$space_used = get_space_used() * 1024 * 1024;
$space_allowed = get_space_allowed() ? get_space_allowed() * 1024 * 1024 : 1;
$percentage = ceil($space_used / $space_allowed * 100);
$unlimited_space = get_site_option('upload_space_check_disabled');
$message = $unlimited_space ? '%s' : '%s / %s';
is_multisite() && restore_current_blog();
$this->atts = array(
'site_trial' => 0, // @todo: fix this
'space_used' => $space_used,
'space_allowed' => $space_allowed,
'percentage' => $percentage,
'unlimited_space' => $unlimited_space,
'message' => $message,
);
} // end setup;
/**
* Allows the setup in the context of previews.
*
* @since 2.0.0
* @return void
*/
public function setup_preview() {
$this->site = wu_mock_site();
$this->membership = wu_mock_membership();
$this->product = wu_mock_product();
$unlimited_space = get_site_option('upload_space_check_disabled');
$message = $unlimited_space ? '%s' : '%s / %s';
$this->atts = array(
'site_trial' => 30, // @todo: fix this
'space_used' => 120 * MB_IN_BYTES,
'space_allowed' => 1 * GB_IN_BYTES,
'percentage' => 120 * MB_IN_BYTES / 1 * GB_IN_BYTES,
'unlimited_space' => $unlimited_space,
'message' => $message,
);
} // end setup_preview;
/**
* The content to be output on the screen.
*
* Should return HTML markup to be used to display the block.
* This method is shared between the block render method and
* the shortcode implementation.
*
* @since 2.0.0
*
* @param array $atts Parameters of the block/shortcode.
* @param string|null $content The content inside the shortcode.
* @return string
*/
public function output($atts, $content = null) {
$atts = array_merge($atts, $this->atts);
$atts['site'] = $this->site;
$atts['membership'] = $this->membership;
$atts['product'] = $this->product;
return wu_get_template_contents('dashboard-widgets/account-summary', $atts);
} // end output;
/**
* Returns the manage URL for sites, depending on the environment.
*
* @since 2.0.0
*
* @param int $site_id A Site ID.
* @return string
*/
public function get_manage_url($site_id) {
$base_url = \WP_Ultimo\Current::get_manage_url($site_id, 'site');
return is_admin() ? add_query_arg('page', 'account', $base_url . '/admin.php') : $base_url;
} // end get_manage_url;
} // end class Account_Summary_Element;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,423 @@
<?php
/**
* Adds the Billing_Info_Element UI to the Admin Panel.
*
* @package WP_Ultimo
* @subpackage UI
* @since 2.0.0
*/
namespace WP_Ultimo\UI;
use WP_Ultimo\UI\Base_Element;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Adds the Checkout Element UI to the Admin Panel.
*
* @since 2.0.0
*/
class Billing_Info_Element extends Base_Element {
use \WP_Ultimo\Traits\Singleton;
/**
* The id of the element.
*
* Something simple, without prefixes, like 'checkout', or 'pricing-tables'.
*
* This is used to construct shortcodes by prefixing the id with 'wu_'
* e.g. an id checkout becomes the shortcode 'wu_checkout' and
* to generate the Gutenberg block by prefixing it with 'wp-ultimo/'
* e.g. checkout would become the block 'wp-ultimo/checkout'.
*
* @since 2.0.0
* @var string
*/
public $id = 'billing-info';
/**
* Controls if this is a public element to be used in pages/shortcodes by user.
*
* @since 2.0.24
* @var boolean
*/
protected $public = true;
/**
* The membership object.
*
* @since 2.2.0
* @var \WP_Ultimo\Membership
*/
protected $membership;
/**
* The site object.
*
* @since 2.2.0
* @var \WP_Ultimo\Site
*/
protected $site;
/**
* The icon of the UI element.
* e.g. return fa fa-search
*
* @since 2.0.0
* @param string $context One of the values: block, elementor or bb.
*/
public function get_icon($context = 'block'): string {
if ($context === 'elementor') {
return 'eicon-info-circle-o';
} // end if;
return 'fa fa-search';
} // end get_icon;
/**
* Overload the init to add site-related forms.
*
* @since 2.0.0
* @return void
*/
public function init() {
parent::init();
wu_register_form('update_billing_address', array(
'render' => array($this, 'render_update_billing_address'),
'handler' => array($this, 'handle_update_billing_address'),
'capability' => 'exist',
));
} // end init;
/**
* Loads the required scripts.
*
* @since 2.0.0
* @return void
*/
public function register_scripts() {
add_wubox();
} // end register_scripts;
/**
* The title of the UI element.
*
* This is used on the Blocks list of Gutenberg.
* You should return a string with the localized title.
* e.g. return __('My Element', 'wp-ultimo').
*
* @since 2.0.0
* @return string
*/
public function get_title() {
return __('Billing Information', 'wp-ultimo');
} // end get_title;
/**
* The description of the UI element.
*
* This is also used on the Gutenberg block list
* to explain what this block is about.
* You should return a string with the localized title.
* e.g. return __('Adds a checkout form to the page', 'wp-ultimo').
*
* @since 2.0.0
* @return string
*/
public function get_description() {
return __('Adds a checkout form block to the page.', 'wp-ultimo');
} // end get_description;
/**
* The list of fields to be added to Gutenberg.
*
* If you plan to add Gutenberg controls to this block,
* you'll need to return an array of fields, following
* our fields interface (@see inc/ui/class-field.php).
*
* You can create new Gutenberg panels by adding fields
* with the type 'header'. See the Checkout Elements for reference.
*
* @see inc/ui/class-checkout-element.php
*
* Return an empty array if you don't have controls to add.
*
* @since 2.0.0
* @return array
*/
public function fields() {
$fields = array();
$fields['header'] = array(
'title' => __('General', 'wp-ultimo'),
'desc' => __('General', 'wp-ultimo'),
'type' => 'header',
);
$fields['title'] = array(
'type' => 'text',
'title' => __('Title', 'wp-ultimo'),
'value' => __('Billing Address', 'wp-ultimo'),
'desc' => __('Leave blank to hide the title completely.', 'wp-ultimo'),
'tooltip' => '',
);
return $fields;
} // end fields;
/**
* The list of keywords for this element.
*
* Return an array of strings with keywords describing this
* element. Gutenberg uses this to help customers find blocks.
*
* e.g.:
* return array(
* 'WP Ultimo',
* 'Billing Information',
* 'Form',
* 'Cart',
* );
*
* @since 2.0.0
* @return array
*/
public function keywords() {
return array(
'WP Ultimo',
'Billing Information',
'Form',
'Cart',
);
} // end keywords;
/**
* List of default parameters for the element.
*
* If you are planning to add controls using the fields,
* it might be a good idea to use this method to set defaults
* for the parameters you are expecting.
*
* These defaults will be used inside a 'wp_parse_args' call
* before passing the parameters down to the block render
* function and the shortcode render function.
*
* @since 2.0.0
* @return array
*/
public function defaults() {
return array(
'title' => __('Billing Address', 'wp-ultimo'),
);
} // end defaults;
/**
* Runs early on the request lifecycle as soon as we detect the shortcode is present.
*
* @since 2.0.0
* @return void
*/
public function setup() {
$this->membership = WP_Ultimo()->currents->get_membership();
if (!$this->membership) {
$this->set_display(false);
return;
} // end if;
} // end setup;
/**
* Allows the setup in the context of previews.
*
* @since 2.0.0
* @return void
*/
public function setup_preview() {
$this->site = wu_mock_site();
$this->membership = wu_mock_membership();
} // end setup_preview;
/**
* The content to be output on the screen.
*
* Should return HTML markup to be used to display the block.
* This method is shared between the block render method and
* the shortcode implementation.
*
* @since 2.0.0
*
* @param array $atts Parameters of the block/shortcode.
* @param string|null $content The content inside the shortcode.
* @return string
*/
public function output($atts, $content = null) {
$atts['membership'] = $this->membership;
$atts['billing_address'] = $this->membership->get_billing_address();
$atts['update_billing_address_link'] = wu_get_form_url('update_billing_address', array(
'membership' => $this->membership->get_hash(),
'width' => 500,
));
return wu_get_template_contents('dashboard-widgets/billing-info', $atts);
} // end output;
/**
* Apply the placeholders to the fields.
*
* @since 2.0.0
*
* @param array $fields The billing fields.
* @return array
*/
protected function apply_placeholders($fields) {
foreach ($fields as &$field) {
$field['placeholder'] = $field['default_placeholder'];
} // end foreach;
return $fields;
} // end apply_placeholders;
/**
* Renders the update billing address form.
*
* @since 2.0.0
* @return string
*/
public function render_update_billing_address() {
$membership = wu_get_membership_by_hash(wu_request('membership'));
if (!$membership) {
return '';
} // end if;
$billing_address = $membership->get_billing_address();
$fields = array();
$fields['billing-title'] = array(
'type' => 'header',
'order' => 1,
'title' => __('Your Address', 'wp-ultimo'),
'desc' => __('Enter your billing address here. This info will be used on your invoices.', 'wp-ultimo'),
'wrapper_classes' => 'wu-col-span-2',
);
$billing_fields = $this->apply_placeholders($billing_address->get_fields());
$fields = array_merge($fields, $billing_fields);
$fields['submit'] = array(
'type' => 'submit',
'title' => __('Save Changes', 'wp-ultimo'),
'value' => 'save',
'classes' => 'button button-primary wu-w-full',
'wrapper_classes' => 'wu-col-span-2',
);
$fields['membership'] = array(
'type' => 'hidden',
'value' => wu_request('membership'),
);
$form = new \WP_Ultimo\UI\Form('edit_site', $fields, array(
'views' => 'admin-pages/fields',
'classes' => 'wu-modal-form wu-widget-list wu-striped wu-m-0 wu-mt-0 wu-grid-cols-2 wu-grid',
'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 wu-grid-col-span-2',
'html_attr' => array(
'data-wu-app' => 'edit_site',
'data-state' => wu_convert_to_state(),
),
));
$form->render();
} // end render_update_billing_address;
/**
* Handles the password reset form.
*
* @since 2.0.0
* @return void
*/
public function handle_update_billing_address() {
$membership = wu_get_membership_by_hash(wu_request('membership'));
if (!$membership) {
$error = new \WP_Error('membership-dont-exist', __('Something went wrong.', 'wp-ultimo'));
wp_send_json_error($error);
} // end if;
$billing_address = $membership->get_billing_address();
$billing_address->attributes($_POST);
$valid_address = $billing_address->validate();
if (is_wp_error($valid_address)) {
wp_send_json_error($valid_address);
} // end if;
$membership->set_billing_address($billing_address);
$saved = $membership->save();
if (is_wp_error($saved)) {
wp_send_json_error($saved);
} // end if;
wp_send_json_success(array(
'redirect_url' => add_query_arg('updated', (int) $saved, $_SERVER['HTTP_REFERER']),
));
} // end handle_update_billing_address;
} // end class Billing_Info_Element;

View File

@ -0,0 +1,865 @@
<?php
/**
* Adds the Checkout_Element UI to the Admin Panel.
*
* @package WP_Ultimo
* @subpackage UI
* @since 2.0.0
*/
namespace WP_Ultimo\UI;
use \WP_Ultimo\UI\Base_Element;
use \WP_Ultimo\Dependencies\ScssPhp\ScssPhp\Compiler;
use \WP_Ultimo\Database\Memberships\Membership_Status;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Adds the Checkout Element UI to the Admin Panel.
*
* @since 2.0.0
*/
class Checkout_Element extends Base_Element {
use \WP_Ultimo\Traits\Singleton;
/**
* The current signup.
*
* @since 2.2.0
* @var Mocked_Signup
*/
protected $signup;
/**
* The current checkout form steps.
*
* @since 2.2.0
* @var array|false
*/
public $steps;
/**
* The current checkout form step.
*
* @since 2.2.0
* @var array
*/
public $step;
/**
* The current checkout form step name.
*
* @since 2.2.0
* @var string
*/
public $step_name;
/**
* The id of the element.
*
* Something simple, without prefixes, like 'checkout', or 'pricing-tables'.
*
* This is used to construct shortcodes by prefixing the id with 'wu_'
* e.g. an id checkout becomes the shortcode 'wu_checkout' and
* to generate the Gutenberg block by prefixing it with 'wp-ultimo/'
* e.g. checkout would become the block 'wp-ultimo/checkout'.
*
* @since 2.0.0
* @var string
*/
public $id = 'checkout';
/**
* Controls if this is a public element to be used in pages/shortcodes by user.
*
* @since 2.0.24
* @var boolean
*/
protected $public = true;
/**
* The icon of the UI element.
* e.g. return fa fa-search
*
* @since 2.0.0
* @param string $context One of the values: block, elementor or bb.
*/
public function get_icon($context = 'block'): string {
if ($context === 'elementor') {
return 'eicon-cart-medium';
} // end if;
return 'fa fa-search';
} // end get_icon;
/**
* The title of the UI element.
*
* This is used on the Blocks list of Gutenberg.
* You should return a string with the localized title.
* e.g. return __('My Element', 'wp-ultimo').
*
* @since 2.0.0
* @return string
*/
public function get_title() {
return __('Checkout', 'wp-ultimo');
} // end get_title;
/**
* The description of the UI element.
*
* This is also used on the Gutenberg block list
* to explain what this block is about.
* You should return a string with the localized title.
* e.g. return __('Adds a checkout form to the page', 'wp-ultimo').
*
* @since 2.0.0
* @return string
*/
public function get_description() {
return __('Adds a checkout form block to the page.', 'wp-ultimo');
} // end get_description;
/**
* The list of fields to be added to Gutenberg.
*
* If you plan to add Gutenberg controls to this block,
* you'll need to return an array of fields, following
* our fields interface (@see inc/ui/class-field.php).
*
* You can create new Gutenberg panels by adding fields
* with the type 'header'. See the Checkout Elements for reference.
*
* @see inc/ui/class-checkout-element.php
*
* Return an empty array if you don't have controls to add.
*
* @since 2.0.0
* @return array
*/
public function fields() {
$fields = array();
$fields['header'] = array(
'title' => __('General', 'wp-ultimo'),
'desc' => __('General', 'wp-ultimo'),
'type' => 'header',
);
$fields['slug'] = array(
'title' => __('Slug', 'wp-ultimo'),
'desc' => __('The checkout form slug.', 'wp-ultimo'),
'type' => 'text',
);
return $fields;
} // end fields;
/**
* The list of keywords for this element.
*
* Return an array of strings with keywords describing this
* element. Gutenberg uses this to help customers find blocks.
*
* e.g.:
* return array(
* 'WP Ultimo',
* 'Checkout',
* 'Form',
* 'Cart',
* );
*
* @since 2.0.0
* @return array
*/
public function keywords() {
return array(
'WP Ultimo',
'Checkout',
'Form',
'Cart',
);
} // end keywords;
/**
* List of default parameters for the element.
*
* If you are planning to add controls using the fields,
* it might be a good idea to use this method to set defaults
* for the parameters you are expecting.
*
* These defaults will be used inside a 'wp_parse_args' call
* before passing the parameters down to the block render
* function and the shortcode render function.
*
* @since 2.0.0
* @return array
*/
public function defaults() {
return array(
'slug' => 'main-form',
'step' => false,
'display_title' => false,
'membership_limitations' => array(),
);
} // end defaults;
/**
* Checks if we are on a thank you page.
*
* @since 2.0.0
* @return boolean
*/
public function is_thank_you_page() {
return is_user_logged_in() && wu_request('payment') && wu_request('status') === 'done';
} // end is_thank_you_page;
/**
* Triggers the setup event to allow the checkout class to hook in.
*
* @since 2.0.0
* @return void
*/
public function setup() {
if ($this->is_thank_you_page()) {
\WP_Ultimo\UI\Thank_You_Element::get_instance()->setup();
return;
} // end if;
do_action('wu_setup_checkout', $this);
} // end setup;
/**
* Print the Custom CSS added on the checkout.
*
* @since 2.0.0
*
* @param \WP_Ultimo\Models\Checkout_Form $checkout_form The current checkout form.
* @return void
*/
public function print_custom_css($checkout_form) {
$scss = new Compiler();
$slug = $checkout_form->get_slug();
$custom_css = $checkout_form->get_custom_css();
if ($custom_css) {
$custom_css = $scss->compileString(".wu_checkout_form_{$slug} {
{$custom_css}
}")->getCss();
echo sprintf('<style>%s</style>', $custom_css);
} // end if;
} // end print_custom_css;
/**
* Outputs thank you page.
*
* @since 2.0.0
*
* @param array $atts Parameters of the block/shortcode.
* @param string|null $content The content inside the shortcode.
* @return string
*/
public function output_thank_you($atts, $content = null) {
$slug = $atts['slug'];
$checkout_form = wu_get_checkout_form_by_slug($slug);
$atts = $checkout_form->get_meta('wu_thank_you_settings');
$atts['checkout_form'] = $checkout_form;
\WP_Ultimo\UI\Thank_You_Element::get_instance()->register_scripts();
return \WP_Ultimo\UI\Thank_You_Element::get_instance()->output($atts, $content);
} // end output_thank_you;
/**
* Outputs the registration form.
*
* @since 2.0.0
*
* @param array $atts Parameters of the block/shortcode.
* @param string|null $content The content inside the shortcode.
* @return string
*/
public function output_form($atts, $content = null) {
$atts['step'] = wu_request('step', $atts['step']);
$slug = $atts['slug'];
$customer = wu_get_current_customer();
$membership = WP_Ultimo()->currents->get_membership();
/**
* Allow developers bypass the output and set a new one
*
* @param string|boll $bypass If we should bypass the checkout form or a string to return instead of the form.
* @param array $atts Parameters of the checkout block/shortcode.
*/
$bypass = apply_filters('wu_bypass_checkout_form', false, $atts);
if ($bypass) {
return is_string($bypass) ? $bypass : '';
} // end if;
if ($customer && $membership && $slug !== 'wu-finish-checkout') {
$published_sites = $membership->get_published_sites();
$pending_payment = $membership ? $membership->get_last_pending_payment() : false;
if ($pending_payment && !$membership->is_active() && $membership->get_status() !== Membership_Status::TRIALING) {
/**
* We are talking about membership with a pending payment
*/
// Translators: Placeholder receives the customer display name
$message = sprintf(__('Hi %s. You have a pending payment for your membership!', 'wp-ultimo'), $customer->get_display_name());
$payment_url = add_query_arg(array(
'payment' => $pending_payment->get_hash(),
), wu_get_registration_url());
// Translators: The link to registration url with payment hash
$message .= '<br>' . sprintf(__('Click <a href="%s">here</a> to pay.', 'wp-ultimo'), $payment_url);
$message = '<p>' . $message . '</p>';
/**
* Allow developers to change the message if membership have a pending payment
*
* @param string $message The HTML message to print in screen.
* @param WP_Ultimo\Models\Membership $membership The membership in use.
* @param WP_Ultimo\Models\Customer $customer The active customer in use.
*/
return apply_filters('wu_checkout_pending_payment_error_message', $message, $membership, $customer);
} // end if;
$membership_blocked_forms = array(
'wu-add-new-site'
);
if (!$membership->is_active() && $membership->get_status() !== Membership_Status::TRIALING && in_array($atts['slug'], $membership_blocked_forms, true)) {
// Translators: Placeholder receives the customer display name
$message = sprintf(__('Hi %s. You cannot take action on your membership while it is not active!', 'wp-ultimo'), $customer->get_display_name());
if ($membership->get_status() === Membership_Status::PENDING && $customer->get_email_verification() === 'pending') {
/**
* Enqueue thank you page scripts to handle resend email verification link
*/
wp_register_script('wu-thank-you', wu_get_asset('thank-you.js', 'js'), array(), wu_get_version());
wp_localize_script('wu-thank-you', 'wu_thank_you', array(
'ajaxurl' => admin_url('admin-ajax.php'),
'resend_verification_email_nonce' => wp_create_nonce('wu_resend_verification_email_nonce'),
'membership_hash' => $membership->get_hash(),
'i18n' => array(
'resending_verification_email' => __('Resending verification email...', 'wp-ultimo'),
'email_sent' => __('Verification email sent!', 'wp-ultimo'),
),
));
wp_enqueue_script('wu-thank-you');
$message .= '<p>' . __('Check your inbox and verify your email address.', 'wp-ultimo') . '</p>';
$message .= '<span class="wu-styling">';
$message .= sprintf('<a href="#" class="wu-mr-2 wu-resend-verification-email wu-no-underline button button-primary">%s</a>', __('Resend verification email', 'wp-ultimo'));
$message .= '</span>';
} // end if;
/**
* Allow developers to change the message if membership have a pending payment
*
* @param string $message The HTML message to print in screen.
* @param WP_Ultimo\Models\Membership $membership The membership in use.
* @param WP_Ultimo\Models\Customer $customer The active customer in use.
*/
return apply_filters('wu_checkout_membership_status_error_message', $message, $membership, $customer);
} // end if;
if (!wu_multiple_memberships_enabled() && $membership) {
/**
* Allow developers to add new form slugs to bypass this behaviour.
*
* @param array $slugs a list of form slugs to bypass.
*/
$allowed_forms = apply_filters('wu_get_membership_allowed_forms', array(
'wu-checkout',
'wu-add-new-site',
));
if (!in_array($slug, $allowed_forms, true) && !wu_request('payment')) {
$message = sprintf('<p>%s</p>', __('You already have a membership!', 'wp-ultimo'));
if (isset($published_sites[0])) {
$account_link = get_admin_url($published_sites[0]->get_id(), 'admin.php?page=account');
$button_text = __('Go to my account', 'wp-ultimo');
$message .= "<p><a class=\"wu-no-underline button button-primary\" href=\"$account_link\">$button_text</a><p>";
} // end if;
/**
* Allow developers to change the message about the limitation of a single membership for customer.
*
* @param string $message The HTML message to print in screen.
* @param WP_Ultimo\Models\Customer $customer The active customer in use.
*/
return apply_filters('wu_checkout_single_membership_message', $message, $customer);
} // end if;
} // end if;
if ($membership && $membership->get_customer_id() !== $customer->get_id()) {
$message = sprintf('<p>%s</p>', __('You are not allowed to change this membership!', 'wp-ultimo'));
/**
* Allow developers to change the message if customer is not part of the membership
*
* @param string $message The HTML message to print in screen.
* @param WP_Ultimo\Models\Membership $membership The membership in use.
* @param WP_Ultimo\Models\Customer $customer The active customer in use.
*/
return apply_filters('wu_checkout_customer_error_message', $message, $membership, $customer);
} // end if;
/**
* Now we filter the current membership for each membership_limitations
* field in element atts to check if we can show the form, if not we show
* a error message informing the user about and with buttons to allow
* account upgrade and/or to buy a new membership.
*/
if ($membership && !empty($atts['membership_limitations'])) {
$limits = $membership->get_limitations();
foreach ($atts['membership_limitations'] as $limitation) {
if (!method_exists($membership, "get_$limitation")) {
continue;
} // end if;
$current_limit = $limits->{$limitation};
$limit_max = $current_limit->is_enabled() ? $current_limit->get_limit() : PHP_INT_MAX;
$limit_max = !empty($limit_max) ? (int) $limit_max : 1;
$used_limit = $membership->{"get_$limitation"}();
$used_limit = is_array($used_limit) ? count($used_limit) : (int) $used_limit;
if ($used_limit >= $limit_max) {
// Translators: Placeholder receives the limit name
$message = '<p>' . sprintf(__('You reached your membership %s limit!', 'wp-ultimo'), $limitation) . '</p>';
$message .= '<span class="wu-styling">';
if (wu_multiple_memberships_enabled()) {
$register_page = wu_get_registration_url();
$button_text = __('Buy a new membership', 'wp-ultimo');
$message .= "<a class=\"wu-no-underline button button-primary wu-mr-2\" href=\"$register_page\">$button_text</a>";
} // end if;
if ($limitation !== 'sites' || wu_get_setting('enable_multiple_sites')) {
$update_link = '';
$checkout_pages = \WP_Ultimo\Checkout\Checkout_Pages::get_instance();
$update_url = $checkout_pages->get_page_url('update');
if ($update_url) {
$update_link = add_query_arg(array(
'membership' => $membership->get_hash(),
), $update_url);
} elseif (is_admin()) {
$update_link = admin_url('admin.php?page=wu-checkout&membership=' . $membership->get_hash());
} elseif (isset($published_sites[0])) {
$update_link = get_admin_url($published_sites[0]->get_id(), 'admin.php?page=wu-checkout&membership=' . $membership->get_hash());
} // end if;
if (!empty($update_link)) {
$button_text = __('Upgrade your account', 'wp-ultimo');
$message .= "<a class=\"wu-no-underline button button-primary wu-mr-2\" href=\"$update_link\">$button_text</a>";
} // end if;
} // end if;
$message .= '</span>';
/**
* Allow developers to change the message about the membership limit
*
* @param string $message The HTML message to print in screen.
* @param string $limitation The limitation name.
* @param int $limit_max The allowed limit.
* @param int $used_limit The limit used in membership.
* @param WP_Ultimo\Models\Membership $membership The membership in use.
* @param WP_Ultimo\Models\Customer $customer The active customer in use.
*/
return apply_filters('wu_checkout_membership_limit_message', $message, $limitation, $limit_max, $used_limit, $membership, $customer);
} // end if;
} // end foreach;
} // end if;
} elseif (!$customer && $slug === 'wu-finish-checkout') {
if (is_user_logged_in()) {
$message = __('You need to be the account owner to complete this payment.', 'wp-ultimo');
} else {
$message = __('You need to be logged in to complete a payment', 'wp-ultimo');
// Translators: The link to login url with redirect_to url
$message .= '<br>' . sprintf(__('Click <a href="%s">here</a> sign in.', 'wp-ultimo'), wp_login_url(wu_get_current_url()));
} // end if;
$message = '<p>' . $message . '</p>';
/**
* Allow developers to change the message
*
* @param string $message The HTML message to print in screen.
*/
return apply_filters('wu_checkout_payment_login_error_message', $message);
} // end if;
$checkout_form = wu_get_checkout_form_by_slug($slug);
if (!$checkout_form) {
// translators: %s is the id of the form. e.g. main-form
return sprintf(__('Checkout form %s not found.', 'wp-ultimo'), $slug);
} // end if;
if ($checkout_form->get_field_count() === 0) {
// translators: %s is the id of the form. e.g. main-form
return sprintf(__('Checkout form %s contains no fields.', 'wp-ultimo'), $slug);
}
if (!$checkout_form->is_active() || !wu_get_setting('enable_registration')) {
return sprintf('<p>%s</p>', __('Registration is not available at this time.', 'wp-ultimo'));
} // end if;
if ($checkout_form->has_country_lock()) {
$geolocation = \WP_Ultimo\Geolocation::geolocate_ip('', true);
if (!in_array($geolocation['country'], $checkout_form->get_allowed_countries(), true)) {
return sprintf('<p>%s</p>', __('Registration is closed for your location.', 'wp-ultimo'));
} // end if;
} // end if;
$checkout = \WP_Ultimo\Checkout\Checkout::get_instance();
$checkout_form->get_steps_to_show();
$this->steps = $checkout_form->get_steps_to_show();
$step = $checkout_form->get_step($atts['step'], true);
$this->step = $step ? $step : current($this->steps);
$this->step = wp_parse_args($this->step, array(
'classes' => '',
'fields' => array(),
));
$this->step_name = $this->step['id'] ?? '';
/*
* Hack-y way to make signup available on the template.
*/
global $signup;
$signup = new Mocked_Signup($this->step_name, $this->steps); // phpcs:ignore
$this->signup = $signup;
add_action('wp_print_footer_scripts', function() use ($checkout_form) {
$this->print_custom_css($checkout_form);
});
/*
* Load the checkout class with the parameters
* so we can access them inside the layouts.
*/
$checkout->checkout_form = $checkout_form;
$checkout->steps = $this->steps;
$checkout->step = $this->step;
$checkout->step_name = $this->step_name;
$auto_submittable_field = $checkout->contains_auto_submittable_field($this->step['fields']);
$final_fields = wu_create_checkout_fields($this->step['fields']);
/*
* Adds the product fields to keep them.
*/
$final_fields['products[]'] = array(
'type' => 'hidden',
'html_attr' => array(
'v-for' => '(product, index) in unique_products',
'v-model' => 'products[index]',
'v-bind:id' => '"products-" + index',
),
);
$this->inject_inline_auto_submittable_field($auto_submittable_field);
$final_fields = apply_filters('wu_checkout_form_final_fields', $final_fields, $this);
return wu_get_template_contents('checkout/form', array(
'step' => $this->step,
'step_name' => $this->step_name,
'checkout_form_name' => $atts['slug'],
'errors' => $checkout->errors,
'display_title' => $atts['display_title'],
'final_fields' => $final_fields,
));
} // end output_form;
/**
* Injects the auto-submittable field inline snippet.
*
* @since 2.0.11
*
* @param string $auto_submittable_field The auto-submittable field.
* @return void
*/
public function inject_inline_auto_submittable_field($auto_submittable_field) {
$callback = function() use ($auto_submittable_field) {
wp_add_inline_script('wu-checkout', sprintf('
/**
* Set the auto-submittable field, if one exists.
*/
window.wu_auto_submittable_field = %s;
', json_encode($auto_submittable_field)), 'after');
};
if (wu_is_block_theme() && !is_admin()) {
add_action('wu_checkout_scripts', $callback, 100);
} else {
call_user_func($callback);
} // end if;
} // end inject_inline_auto_submittable_field;
/**
* The content to be output on the screen.
*
* Should return HTML markup to be used to display the block.
* This method is shared between the block render method and
* the shortcode implementation.
*
* @since 2.0.0
*
* @param array $atts Parameters of the block/shortcode.
* @param string|null $content The content inside the shortcode.
* @return string
*/
public function output($atts, $content = null) {
if (wu_is_update_page()) {
$atts = array(
'slug' => apply_filters('wu_membership_update_form', 'wu-checkout'),
'step' => false,
'display_title' => false,
);
} // end if;
if (wu_is_new_site_page()) {
$atts = array(
'slug' => apply_filters('wu_membership_new_site_form', 'wu-add-new-site'),
'step' => false,
'display_title' => false,
'membership_limitations' => array('sites'),
);
} // end if;
if ($this->is_thank_you_page()) {
return $this->output_thank_you($atts, $content);
} // end if;
/**
* Allow developers to add new update form slugs.
*
* @param array $slugs a list of form slugs to bypass.
*/
$update_forms = apply_filters('wu_membership_update_forms', array(
'wu-checkout',
));
if (!in_array($atts['slug'], $update_forms, true) && (wu_request('payment') || wu_request('payment_id'))) {
$atts = array(
'slug' => 'wu-finish-checkout',
'step' => false,
'display_title' => false,
);
} // end if;
if (wu_request('wu_form') && in_array(wu_request('wu_form'), $update_forms, true)) {
$atts = array(
'slug' => wu_request('wu_form'),
'step' => false,
'display_title' => false,
);
} // end if;
return $this->output_form($atts, $content);
} // end output;
} // end class Checkout_Element;
/**
* Replacement of the old WU_Signup class for templates.
*
* @since 2.0.0
*/
class Mocked_Signup {
/**
* @var string
*/
public $step;
/**
* @var array
*/
public $steps;
/**
* Constructs the class.
*
* @since 2.0.0
*
* @param string $step Current step.
* @param array $steps List of all steps.
*/
public function __construct($step, $steps)
{
$this->step = $step;
$this->steps = $steps;
} // end __construct;
/**
* Get the value of steps.
*
* @since 2.0.0
* @return mixed
*/
public function get_steps() {
return $this->steps;
} // end get_steps;
/**
* Deprecated: returns the prev step link.
*
* @since 2.0.0
*/
public function get_prev_step_link(): string {
return '';
} // end get_prev_step_link;
} // end class Mocked_Signup;

View File

@ -0,0 +1,648 @@
<?php
/**
* Adds the Current_Membership_Element UI to the Admin Panel.
*
* @package WP_Ultimo
* @subpackage UI
* @since 2.0.0
*/
namespace WP_Ultimo\UI;
use \WP_Ultimo\UI\Base_Element;
use \WP_Ultimo\Checkout\Cart;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Adds the Checkout Element UI to the Admin Panel.
*
* @since 2.0.0
*/
class Current_Membership_Element extends Base_Element {
use \WP_Ultimo\Traits\Singleton;
/**
* The id of the element.
*
* Something simple, without prefixes, like 'checkout', or 'pricing-tables'.
*
* This is used to construct shortcodes by prefixing the id with 'wu_'
* e.g. an id checkout becomes the shortcode 'wu_checkout' and
* to generate the Gutenberg block by prefixing it with 'wp-ultimo/'
* e.g. checkout would become the block 'wp-ultimo/checkout'.
*
* @since 2.0.0
* @var string
*/
public $id = 'current-membership';
/**
* Controls if this is a public element to be used in pages/shortcodes by user.
*
* @since 2.0.24
* @var boolean
*/
protected $public = true;
/**
* The current membership.
*
* @since 2.2.0
* @var \WP_Ultimo\Models\Membership
*/
protected $membership;
/**
* The current plan.
*
* @since 2.2.0
* @var \WP_Ultimo\Models\Product
*/
protected $plan;
/**
* Overload the init to add site-related forms.
*
* @since 2.0.0
* @return void
*/
public function init() {
parent::init();
wu_register_form('see_product_details', array(
'render' => array($this, 'render_product_details'),
'capability' => 'exist',
));
wu_register_form('edit_membership_product_modal', array(
'render' => array($this, 'render_edit_membership_product_modal'),
'handler' => array($this, 'handle_edit_membership_product_modal'),
'capability' => 'exist',
));
} // end init;
/**
* Loads the required scripts.
*
* @since 2.0.0
* @return void
*/
public function register_scripts() {
add_wubox();
} // end register_scripts;
/**
* The icon of the UI element.
* e.g. return fa fa-search
*
* @since 2.0.0
* @param string $context One of the values: block, elementor or bb.
*/
public function get_icon($context = 'block'): string {
if ($context === 'elementor') {
return 'eicon-info-circle-o';
} // end if;
return 'fa fa-search';
} // end get_icon;
/**
* The title of the UI element.
*
* This is used on the Blocks list of Gutenberg.
* You should return a string with the localized title.
* e.g. return __('My Element', 'wp-ultimo').
*
* @since 2.0.0
* @return string
*/
public function get_title() {
return __('Membership', 'wp-ultimo');
} // end get_title;
/**
* The description of the UI element.
*
* This is also used on the Gutenberg block list
* to explain what this block is about.
* You should return a string with the localized title.
* e.g. return __('Adds a checkout form to the page', 'wp-ultimo').
*
* @since 2.0.0
* @return string
*/
public function get_description() {
return __('Adds a checkout form block to the page.', 'wp-ultimo');
} // end get_description;
/**
* The list of fields to be added to Gutenberg.
*
* If you plan to add Gutenberg controls to this block,
* you'll need to return an array of fields, following
* our fields interface (@see inc/ui/class-field.php).
*
* You can create new Gutenberg panels by adding fields
* with the type 'header'. See the Checkout Elements for reference.
*
* @see inc/ui/class-checkout-element.php
*
* Return an empty array if you don't have controls to add.
*
* @since 2.0.0
* @return array
*/
public function fields() {
$fields = array();
$fields['header'] = array(
'title' => __('General', 'wp-ultimo'),
'desc' => __('General', 'wp-ultimo'),
'type' => 'header',
);
$fields['title'] = array(
'type' => 'text',
'title' => __('Title', 'wp-ultimo'),
'value' => __('Your Membership', 'wp-ultimo'),
'desc' => __('Leave blank to hide the title completely.', 'wp-ultimo'),
'tooltip' => '',
);
$fields['display_images'] = array(
'type' => 'toggle',
'title' => __('Display Product Images?', 'wp-ultimo'),
'desc' => __('Toggle to show/hide the product images on the element.', 'wp-ultimo'),
'tooltip' => '',
'value' => 1,
);
$fields['columns'] = array(
'type' => 'number',
'title' => __('Columns', 'wp-ultimo'),
'desc' => __('How many columns to use.', 'wp-ultimo'),
'tooltip' => '',
'value' => 2,
'min' => 1,
'max' => 5,
);
return $fields;
} // end fields;
/**
* The list of keywords for this element.
*
* Return an array of strings with keywords describing this
* element. Gutenberg uses this to help customers find blocks.
*
* e.g.:
* return array(
* 'WP Ultimo',
* 'Membership',
* 'Form',
* 'Cart',
* );
*
* @since 2.0.0
* @return array
*/
public function keywords() {
return array(
'WP Ultimo',
'Membership',
'Form',
'Cart',
);
} // end keywords;
/**
* List of default parameters for the element.
*
* If you are planning to add controls using the fields,
* it might be a good idea to use this method to set defaults
* for the parameters you are expecting.
*
* These defaults will be used inside a 'wp_parse_args' call
* before passing the parameters down to the block render
* function and the shortcode render function.
*
* @since 2.0.0
* @return array
*/
public function defaults() {
return array(
'title' => __('Your Membership', 'wp-ultimo'),
'display_images' => 1,
'columns' => 2,
);
} // end defaults;
/**
* Runs early on the request lifecycle as soon as we detect the shortcode is present.
*
* @since 2.0.0
* @return void
*/
public function setup() {
$this->membership = WP_Ultimo()->currents->get_membership();
if (!$this->membership) {
$this->set_display(false);
return;
} // end if;
$this->plan = $this->membership ? $this->membership->get_plan() : false;
} // end setup;
/**
* Allows the setup in the context of previews.
*
* @since 2.0.0
* @return void
*/
public function setup_preview() {
$this->membership = wu_mock_membership();
$this->plan = wu_mock_product();
} // end setup_preview;
/**
* The content to be output on the screen.
*
* Should return HTML markup to be used to display the block.
* This method is shared between the block render method and
* the shortcode implementation.
*
* @since 2.0.0
*
* @param array $atts Parameters of the block/shortcode.
* @param string|null $content The content inside the shortcode.
* @return string
*/
public function output($atts, $content = null) {
$atts['membership'] = $this->membership;
$atts['plan'] = $this->plan;
$atts['element'] = $this;
$atts['pending_change'] = false;
if ($this->membership) {
$pending_swap_order = $this->membership->get_scheduled_swap();
$atts['pending_products'] = false;
if ($pending_swap_order) {
$atts['pending_change'] = $pending_swap_order->order->get_cart_descriptor();
$atts['pending_change_date'] = wu_date($pending_swap_order->scheduled_date)->format(get_option('date_format'));
$swap_membership = (clone $this->membership)->swap($pending_swap_order->order);
$pending_products = array_map(fn($product) => array(
'id' => $product['product']->get_id(),
'quantity' => $product['quantity'],
), $swap_membership->get_all_products());
// add the id as key
$atts['pending_products'] = array_combine(array_column($pending_products, 'id'), $pending_products);
} // end if;
return wu_get_template_contents('dashboard-widgets/current-membership', $atts);
} // end if;
return '';
} // end output;
/**
* Renders the product details modal window.
*
* @since 2.0.0
* @return void
*/
public function render_product_details() {
$product = wu_get_product_by_slug(wu_request('product'));
if (!$product) {
return;
} // end if;
$atts['product'] = $product;
wu_get_template('dashboard-widgets/current-membership-product-details', $atts);
} // end render_product_details;
/**
* Renders the add/edit line items form.
*
* @since 2.0.0
* @return void
*/
public function render_edit_membership_product_modal() {
$membership = wu_get_membership_by_hash(wu_request('membership'));
$error = '';
if (!$membership) {
$error = __('Membership not selected.', 'wp-ultimo');
} // end if;
$product = wu_get_product_by_slug(wu_request('product'));
if (!$product) {
$error = __('Product not selected.', 'wp-ultimo');
} // end if;
$customer = wu_get_current_customer();
if (empty($error) && !is_super_admin() && (!$customer || $customer->get_id() !== $membership->get_customer_id())) {
$error = __('You are not allowed to do this.', 'wp-ultimo');
} // end if;
if (!empty($error)) {
$error_field = array(
'error_message' => array(
'type' => 'note',
'desc' => $error,
),
);
$form = new \WP_Ultimo\UI\Form('cancel_payment_method', $error_field, array(
'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',
));
$form->render();
return;
} // end if;
/**
* If there is a scheduled swap, we need to swap the membership before
* removing the product to ensure the full change for next billing cycle.
*/
$existing_swap = $membership->get_scheduled_swap();
if ($existing_swap) {
$membership = $membership->swap($existing_swap->order);
} // end if;
$gateway_message = false;
if (!empty($membership->get_gateway())) {
$gateway = wu_get_gateway($membership->get_gateway());
$gateway_message = $gateway ? $gateway->get_amount_update_message(true) : '';
} // end if;
$existing_quantity = array_filter($membership->get_addon_products(), fn($item) => $item['product']->get_id() === $product->get_id())[0]['quantity'];
$fields = array(
'membership' => array(
'type' => 'hidden',
'value' => wu_request('membership'),
),
'product' => array(
'type' => 'hidden',
'value' => wu_request('product'),
),
'quantity' => array(
'type' => 'number',
'title' => __('Quantity to Cancel', 'wp-ultimo'),
'value' => 1,
'placeholder' => 1,
'wrapper_classes' => 'wu-w-1/2',
'html_attr' => array(
'min' => 1,
'max' => $existing_quantity,
'required' => 'required',
),
'wrapper_html_attr' => array(
'v-show' => $existing_quantity > 1 ? 'true' : 'false',
'v-cloak' => '1',
),
),
'confirm' => array(
'type' => 'toggle',
'title' => __('Confirm Product Cancellation', 'wp-ultimo'),
'desc' => __('This action can not be undone.', 'wp-ultimo'),
'html_attr' => array(
'v-model' => 'confirmed',
),
),
'update_note' => array(
'type' => 'note',
'desc' => $gateway_message,
'classes' => 'sm:wu-p-2 wu-bg-red-100 wu-text-red-600 wu-rounded wu-w-full',
),
'submit_button' => array(
'type' => 'submit',
'title' => __('Cancel Product Subscription', 'wp-ultimo'),
'placeholder' => __('Cancel Product Subscription', 'wp-ultimo'),
'value' => 'save',
'classes' => 'wu-w-full button button-primary',
'wrapper_classes' => 'wu-items-end',
'html_attr' => array(
'v-bind:disabled' => '!confirmed',
),
),
);
if (!$gateway_message) {
unset($fields['update_note']);
} // end if;
$form = new \WP_Ultimo\UI\Form('edit_membership_product', $fields, array(
'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' => array(
'data-wu-app' => 'edit_membership_product',
'data-state' => wu_convert_to_state(array(
'confirmed' => false,
)),
),
));
$form->render();
} // end render_edit_membership_product_modal;
/**
* Handles the membership product remove.
*
* @since 2.0.0
* @return void
*/
public function handle_edit_membership_product_modal() {
if (!wu_request('confirm')) {
$error = new \WP_Error('not-confirmed', __('Please confirm the cancellation.', 'wp-ultimo'));
wp_send_json_error($error);
} // end if;
$membership = wu_get_membership_by_hash(wu_request('membership'));
if (!$membership) {
$error = new \WP_Error('membership-not-found', __('Membership not found.', 'wp-ultimo'));
wp_send_json_error($error);
} // end if;
$product = wu_get_product_by_slug(wu_request('product'));
if (!$product) {
$error = new \WP_Error('product-not-found', __('Product not found.', 'wp-ultimo'));
wp_send_json_error($error);
} // end if;
$customer = wu_get_current_customer();
if (!is_super_admin() && (!$customer || $customer->get_id() !== $membership->get_customer_id())) {
$error = __('You are not allowed to do this.', 'wp-ultimo');
wp_send_json_error($error);
} // end if;
// 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'];
$original_quantity = $existing_quantity;
/**
* If there is a scheduled swap, we need to swap the membership before
* removing the product to ensure the full change for next billing cycle.
*/
$existing_swap = $membership->get_scheduled_swap();
if ($existing_swap) {
$membership = $membership->swap($existing_swap->order);
$existing_quantity = array_filter($membership->get_addon_products(), fn($item) => $item['product']->get_id() === $product->get_id())[0]['quantity'];
} // end if;
$quantity = (int) wu_request('quantity', 1);
$quantity = $quantity > $existing_quantity ? $existing_quantity : $quantity;
$membership->remove_product($product->get_id(), $quantity);
$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);
} // end if;
$plan_price = wu_get_membership_product_price($membership, $membership->get_plan()->get_id(), 1);
// do not allow remove more than the plan price
if ($plan_price < $value_to_remove) {
$value_to_remove = $membership->get_amount() - $plan_price;
$value_to_remove = $value_to_remove < 0 ? 0 : $value_to_remove;
} // end if;
$membership->set_amount($membership->get_amount() - $value_to_remove);
$cart = wu_get_membership_new_cart($membership);
$existing_difference = $original_quantity - $existing_quantity;
$removed_quantity = $quantity + $existing_difference;
// translators: %1$s is the quantity removed, %2$s is the product name.
$description = sprintf(__('remove %1$s %2$s from membership', 'wp-ultimo'), $removed_quantity, $product->get_name());
$cart->set_cart_descriptor($description);
$schedule_swap = $membership->schedule_swap($cart);
// Lets schedule this change as the customer already paid for this period.
if (is_wp_error($schedule_swap)) {
wp_send_json_error($schedule_swap);
} // end if;
// Now we trigger the gateway update so the customer is charged for the new amount.
$gateway = wu_get_gateway($membership->get_gateway());
if ($gateway) {
$gateway->process_membership_update($membership, $customer);
} // end if;
wp_send_json_success(array(
'redirect_url' => add_query_arg('updated', 1, $_SERVER['HTTP_REFERER']),
));
} // end handle_edit_membership_product_modal;
} // end class Current_Membership_Element;

View File

@ -0,0 +1,516 @@
<?php
/**
* Adds the Current_Site_Element UI to the Admin Panel.
*
* @package WP_Ultimo
* @subpackage UI
* @since 2.0.0
*/
namespace WP_Ultimo\UI;
use WP_Ultimo\UI\Base_Element;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Adds the Checkout Element UI to the Admin Panel.
*
* @since 2.0.0
*/
class Current_Site_Element extends Base_Element {
use \WP_Ultimo\Traits\Singleton;
/**
* The id of the element.
*
* Something simple, without prefixes, like 'checkout', or 'pricing-tables'.
*
* This is used to construct shortcodes by prefixing the id with 'wu_'
* e.g. an id checkout becomes the shortcode 'wu_checkout' and
* to generate the Gutenberg block by prefixing it with 'wp-ultimo/'
* e.g. checkout would become the block 'wp-ultimo/checkout'.
*
* @since 2.0.0
* @var string
*/
public $id = 'current-site';
/**
* Controls if this is a public element to be used in pages/shortcodes by user.
*
* @since 2.0.24
* @var boolean
*/
protected $public = true;
/**
* The site being managed.
*
* @since 2.0.0
* @var null|\WP_Ultimo\Models\Site
*/
public $site;
/**
* The membership being managed.
*
* @since 2.0.0
* @var null|\WP_Ultimo\Models\Membership
*/
public $membership;
/**
* The icon of the UI element.
* e.g. return fa fa-search
*
* @since 2.0.0
* @param string $context One of the values: block, elementor or bb.
*/
public function get_icon($context = 'block'): string {
if ($context === 'elementor') {
return 'eicon-info-circle-o';
} // end if;
return 'fa fa-search';
} // end get_icon;
/**
* Overload the init to add site-related forms.
*
* @since 2.0.0
* @return void
*/
public function init() {
parent::init();
wu_register_form('edit_site', array(
'render' => array($this, 'render_edit_site'),
'handler' => array($this, 'handle_edit_site'),
'capability' => 'exist',
));
} // end init;
/**
* The title of the UI element.
*
* This is used on the Blocks list of Gutenberg.
* You should return a string with the localized title.
* e.g. return __('My Element', 'wp-ultimo').
*
* @since 2.0.0
* @return string
*/
public function get_title() {
return __('Site', 'wp-ultimo');
} // end get_title;
/**
* The description of the UI element.
*
* This is also used on the Gutenberg block list
* to explain what this block is about.
* You should return a string with the localized title.
* e.g. return __('Adds a checkout form to the page', 'wp-ultimo').
*
* @since 2.0.0
* @return string
*/
public function get_description() {
return __('Adds a block to display the current site being managed.', 'wp-ultimo');
} // end get_description;
/**
* The list of fields to be added to Gutenberg.
*
* If you plan to add Gutenberg controls to this block,
* you'll need to return an array of fields, following
* our fields interface (@see inc/ui/class-field.php).
*
* You can create new Gutenberg panels by adding fields
* with the type 'header'. See the Checkout Elements for reference.
*
* @see inc/ui/class-checkout-element.php
*
* Return an empty array if you don't have controls to add.
*
* @since 2.0.0
* @return array
*/
public function fields() {
$fields = array();
$fields['header'] = array(
'title' => __('General', 'wp-ultimo'),
'desc' => __('General', 'wp-ultimo'),
'type' => 'header',
);
$fields['display_breadcrumbs'] = array(
'type' => 'toggle',
'title' => __('Display Breadcrumbs?', 'wp-ultimo'),
'desc' => __('Toggle to show/hide the breadcrumbs block.', 'wp-ultimo'),
'tooltip' => '',
'value' => 1,
);
$pages = get_pages(array(
'exclude' => array(get_the_ID()),
));
$pages = $pages ? $pages : array();
$pages_list = array(0 => __('Current Page', 'wp-ultimo'));
foreach ($pages as $page) {
$pages_list[$page->ID] = $page->post_title;
} // end foreach;
$fields['breadcrumbs_my_sites_page'] = array(
'type' => 'select',
'title' => __('My Sites Page', 'wp-ultimo'),
'value' => 0,
'desc' => __('The page with the customer sites list.', 'wp-ultimo'),
'options' => $pages_list,
);
$fields['display_description'] = array(
'type' => 'toggle',
'title' => __('Display Site Description?', 'wp-ultimo'),
'desc' => __('Toggle to show/hide the site description on the element.', 'wp-ultimo'),
'tooltip' => '',
'value' => 0,
);
$fields['display_image'] = array(
'type' => 'toggle',
'title' => __('Display Site Screenshot?', 'wp-ultimo'),
'desc' => __('Toggle to show/hide the site screenshots on the element.', 'wp-ultimo'),
'tooltip' => '',
'value' => 1,
);
$fields['screenshot_size'] = array(
'type' => 'number',
'title' => __('Screenshot Size', 'wp-ultimo'),
'desc' => '',
'tooltip' => '',
'value' => 200,
'min' => 100,
'max' => 400,
'required' => array(
'display_image' => 1,
),
);
$fields['screenshot_position'] = array(
'type' => 'select',
'title' => __('Screenshot Position', 'wp-ultimo'),
'options' => array(
'right' => __('Right', 'wp-ultimo'),
'left' => __('Left', 'wp-ultimo'),
),
'desc' => '',
'tooltip' => '',
'value' => 'right',
'required' => array(
'display_image' => 1,
),
);
$fields['show_admin_link'] = array(
'type' => 'toggle',
'title' => __('Show Admin Link?', 'wp-ultimo'),
'desc' => __('Toggle to show/hide the WP admin link on the element.', 'wp-ultimo'),
'tooltip' => '',
'value' => 1,
);
return $fields;
} // end fields;
/**
* The list of keywords for this element.
*
* Return an array of strings with keywords describing this
* element. Gutenberg uses this to help customers find blocks.
*
* e.g.:
* return array(
* 'WP Ultimo',
* 'Site',
* 'Form',
* 'Cart',
* );
*
* @since 2.0.0
* @return array
*/
public function keywords() {
return array(
'WP Ultimo',
'Site',
'Form',
'Cart',
);
} // end keywords;
/**
* List of default parameters for the element.
*
* If you are planning to add controls using the fields,
* it might be a good idea to use this method to set defaults
* for the parameters you are expecting.
*
* These defaults will be used inside a 'wp_parse_args' call
* before passing the parameters down to the block render
* function and the shortcode render function.
*
* @since 2.0.0
* @return array
*/
public function defaults() {
return array(
'display_image' => 1,
'display_breadcrumbs' => 1,
'display_description' => 0,
'screenshot_size' => 200,
'screenshot_position' => 'right',
'breadcrumbs_my_sites_page' => 0,
'show_admin_link' => 1,
);
} // end defaults;
/**
* Runs early on the request lifecycle as soon as we detect the shortcode is present.
*
* @since 2.0.0
* @return void
*/
public function setup() {
$this->site = WP_Ultimo()->currents->get_site();
if (!$this->site || !$this->site->is_customer_allowed()) {
$this->set_display(false);
return;
} // end if;
$this->membership = $this->site->get_membership();
} // end setup;
/**
* Allows the setup in the context of previews.
*
* @since 2.0.0
* @return void
*/
public function setup_preview() {
$this->site = wu_mock_site();
$this->membership = wu_mock_membership();
} // end setup_preview;
/**
* Loads the required scripts.
*
* @since 2.0.0
* @return void
*/
public function register_scripts() {
add_wubox();
} // end register_scripts;
/**
* The content to be output on the screen.
*
* Should return HTML markup to be used to display the block.
* This method is shared between the block render method and
* the shortcode implementation.
*
* @since 2.0.0
*
* @param array $atts Parameters of the block/shortcode.
* @param string|null $content The content inside the shortcode.
* @return string
*/
public function output($atts, $content = null) {
$actions = array(
'visit_site' => array(
'label' => __('Visit Site', 'wp-ultimo'),
'icon_classes' => 'dashicons-wu-browser wu-align-text-bottom',
'classes' => '',
'href' => $this->site->get_active_site_url(),
),
'edit_site' => array(
'label' => __('Edit Site', 'wp-ultimo'),
'icon_classes' => 'dashicons-wu-edit wu-align-text-bottom',
'classes' => 'wubox',
'href' => wu_get_form_url('edit_site', array(
'site' => $this->site->get_hash(),
)),
),
);
if ($atts['show_admin_link']) {
$actions['site_admin'] = array(
'label' => __('Admin Panel', 'wp-ultimo'),
'icon_classes' => 'dashicons-wu-grid wu-align-text-bottom',
'classes' => '',
'href' => get_admin_url($this->site->get_id()),
);
} // end if;
$atts['actions'] = apply_filters('wu_current_site_actions', $actions, $this->site);
$atts['current_site'] = $this->site;
$my_sites_id = $atts['breadcrumbs_my_sites_page'];
$my_sites_url = empty($my_sites_id) ? remove_query_arg('site') : get_page_link($my_sites_id);
$atts['my_sites_url'] = is_admin() ? admin_url('admin.php?page=sites') : $my_sites_url;
return wu_get_template_contents('dashboard-widgets/current-site', $atts);
} // end output;
/**
* Renders the edit site modal.
*
* @since 2.0.0
* @return string
*/
public function render_edit_site() {
$site = wu_get_site_by_hash(wu_request('site'));
if (!$site) {
return '';
} // end if;
$fields = array(
'site_title' => array(
'type' => 'text',
'title' => __('Site Title', 'wp-ultimo'),
'placeholder' => __('e.g. My Awesome Site', 'wp-ultimo'),
'value' => $site->get_title(),
'html_attr' => array(
'v-model' => 'site_title',
),
),
'site_description' => array(
'type' => 'textarea',
'title' => __('Site Description', 'wp-ultimo'),
'placeholder' => __('e.g. My Awesome Site description.', 'wp-ultimo'),
'value' => $site->get_description(),
'html_attr' => array(
'rows' => 5,
),
),
'site' => array(
'type' => 'hidden',
'value' => wu_request('site'),
),
'submit_button' => array(
'type' => 'submit',
'title' => __('Save Changes', 'wp-ultimo'),
'value' => 'save',
'classes' => 'button button-primary wu-w-full',
'wrapper_classes' => 'wu-items-end',
'html_attr' => array(
'v-bind:disabled' => '!site_title.length',
),
),
);
$fields = apply_filters('wu_form_edit_site', $fields, $this);
$form = new \WP_Ultimo\UI\Form('edit_site', $fields, array(
'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' => array(
'data-wu-app' => 'edit_site',
'data-state' => wu_convert_to_state(array(
'site_title' => $site->get_title(),
)),
),
));
$form->render();
} // end render_edit_site;
/**
* Handles the password reset form.
*
* @since 2.0.0
* @return void
*/
public function handle_edit_site() {
$site = wu_get_site_by_hash(wu_request('site'));
if (!$site) {
$error = new \WP_Error('site-dont-exist', __('Something went wrong.', 'wp-ultimo'));
wp_send_json_error($error);
} // end if;
$new_title = wu_request('site_title');
if (!$new_title) {
$error = new \WP_Error('title_empty', __('Site title can not be empty.', 'wp-ultimo'));
wp_send_json_error($error);
} // end if;
$status = update_blog_option($site->get_id(), 'blogname', $new_title);
$status_desc = update_blog_option($site->get_id(), 'blogdescription', wu_request('site_description'));
wp_send_json_success(array(
'redirect_url' => add_query_arg('updated', (int) $status, $_SERVER['HTTP_REFERER']),
));
} // end handle_edit_site;
} // end class Current_Site_Element;

View File

@ -0,0 +1,768 @@
<?php
/**
* Adds the Domain Mapping Element UI to the Admin Panel.
*
* @package WP_Ultimo
* @subpackage UI
* @since 2.0.0
*/
namespace WP_Ultimo\UI;
use \WP_Ultimo\UI\Base_Element;
use \WP_Ultimo\Models\Domain;
use \WP_Ultimo\Database\Domains\Domain_Stage;
use \WP_Ultimo\Models\Site;
use \WP_Ultimo\Models\Membership;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Adds the Checkout Element UI to the Admin Panel.
*
* @since 2.0.0
*/
class Domain_Mapping_Element extends Base_Element {
use \WP_Ultimo\Traits\Singleton;
/**
* The id of the element.
*
* Something simple, without prefixes, like 'checkout', or 'pricing-tables'.
*
* This is used to construct shortcodes by prefixing the id with 'wu_'
* e.g. an id checkout becomes the shortcode 'wu_checkout' and
* to generate the Gutenberg block by prefixing it with 'wp-ultimo/'
* e.g. checkout would become the block 'wp-ultimo/checkout'.
*
* @since 2.0.0
* @var string
*/
public $id = 'domain-mapping';
/**
* Controls if this is a public element to be used in pages/shortcodes by user.
*
* @since 2.0.24
* @var boolean
*/
protected $public = true;
/**
* The current site.
*
* @since 2.2.0
* @var Site
*/
protected $site;
/**
* The current membership.
*
* @since 2.2.0
* @var Membership
*/
protected $membership;
/**
* The icon of the UI element.
* e.g. return fa fa-search
*
* @since 2.0.0
* @param string $context One of the values: block, elementor or bb.
*/
public function get_icon($context = 'block'): string {
if ($context === 'elementor') {
return 'eicon-url';
} // end if;
return 'fa fa-search';
} // end get_icon;
/**
* The title of the UI element.
*
* This is used on the Blocks list of Gutenberg.
* You should return a string with the localized title.
* e.g. return __('My Element', 'wp-ultimo').
*
* @since 2.0.0
* @return string
*/
public function get_title() {
return __('Domains', 'wp-ultimo');
} // end get_title;
/**
* The description of the UI element.
*
* This is also used on the Gutenberg block list
* to explain what this block is about.
* You should return a string with the localized title.
* e.g. return __('Adds a checkout form to the page', 'wp-ultimo').
*
* @since 2.0.0
* @return string
*/
public function get_description() {
return __('Adds the site\'s domains block.', 'wp-ultimo');
} // end get_description;
/**
* The list of fields to be added to Gutenberg.
*
* If you plan to add Gutenberg controls to this block,
* you'll need to return an array of fields, following
* our fields interface (@see inc/ui/class-field.php).
*
* You can create new Gutenberg panels by adding fields
* with the type 'header'. See the Checkout Elements for reference.
*
* @see inc/ui/class-checkout-element.php
*
* Return an empty array if you don't have controls to add.
*
* @since 2.0.0
* @return array
*/
public function fields() {
$fields = array();
$fields['header'] = array(
'title' => __('General', 'wp-ultimo'),
'desc' => __('General', 'wp-ultimo'),
'type' => 'header',
);
$fields['title'] = array(
'type' => 'text',
'title' => __('Title', 'wp-ultimo'),
'value' => __('Domains', 'wp-ultimo'),
'desc' => __('Leave blank to hide the title completely.', 'wp-ultimo'),
'tooltip' => '',
);
return $fields;
} // end fields;
/**
* The list of keywords for this element.
*
* Return an array of strings with keywords describing this
* element. Gutenberg uses this to help customers find blocks.
*
* e.g.:
* return array(
* 'WP Ultimo',
* 'Checkout',
* 'Form',
* 'Cart',
* );
*
* @since 2.0.0
* @return array
*/
public function keywords() {
return array(
'WP Ultimo',
'Domain',
);
} // end keywords;
/**
* List of default parameters for the element.
*
* If you are planning to add controls using the fields,
* it might be a good idea to use this method to set defaults
* for the parameters you are expecting.
*
* These defaults will be used inside a 'wp_parse_args' call
* before passing the parameters down to the block render
* function and the shortcode render function.
*
* @since 2.0.0
* @return array
*/
public function defaults() {
return array(
'title' => __('Domains', 'wp-ultimo'),
);
} // end defaults;
/**
* Initializes the singleton.
*
* @since 2.0.0
* @return void
*/
public function init() {
parent::init();
if ($this->is_preview()) {
$this->site = wu_mock_site();
return;
} // end if;
$this->site = wu_get_current_site();
$maybe_limit_domain_mapping = true;
if ($this->site->has_limitations()) {
$maybe_limit_domain_mapping = $this->site->get_limitations()->domain_mapping->is_enabled();
} // end if;
if (!$this->site || !wu_get_setting('enable_domain_mapping') || !wu_get_setting('custom_domains') || !$maybe_limit_domain_mapping) {
$this->set_display(false);
} // end if;
add_action('plugins_loaded', array($this, 'register_forms'));
} // end init;
/**
* Loads the required scripts.
*
* @since 2.0.0
* @return void
*/
public function register_scripts() {
add_wubox();
} // end register_scripts;
/**
* Register ajax forms used to add a new domain.
*
* @since 2.0.0
* @return void
*/
public function register_forms() {
/*
* Add new Domain
*/
wu_register_form('user_add_new_domain', array(
'render' => array($this, 'render_user_add_new_domain_modal'),
'handler' => array($this, 'handle_user_add_new_domain_modal'),
'capability' => 'exist',
));
wu_register_form('user_make_domain_primary', array(
'render' => array($this, 'render_user_make_domain_primary_modal'),
'handler' => array($this, 'handle_user_make_domain_primary_modal'),
'capability' => 'exist',
));
wu_register_form('user_delete_domain_modal', array(
'render' => array($this, 'render_user_delete_domain_modal'),
'handler' => array($this, 'handle_user_delete_domain_modal'),
'capability' => 'exist',
));
} // end register_forms;
/**
* Renders the add new customer modal.
*
* @since 2.0.0
* @return void
*/
public function render_user_add_new_domain_modal() {
$instructions = \WP_Ultimo\Managers\Domain_Manager::get_instance()->get_domain_mapping_instructions();
$fields = array(
'instructions_note' => array(
'type' => 'note',
'desc' => sprintf('<a href="#" class="wu-no-underline" v-on:click.prevent="ready = false">%s</a>', __('&larr; Back to the Instructions', 'wp-ultimo')),
'wrapper_html_attr' => array(
'v-if' => 'ready',
'v-cloak' => '1',
),
),
'instructions' => array(
'type' => 'text-display',
'copy' => false,
'title' => __('Instructions', 'wp-ultimo'),
'tooltip' => '',
'display_value' => sprintf('<div class="wu--mt-2 wu--mb-2">%s</div>', wpautop($instructions)),
'wrapper_html_attr' => array(
'v-show' => '!ready',
'v-cloak' => 1
),
),
'ready' => array(
'type' => 'submit',
'title' => __('Next Step &rarr;', 'wp-ultimo'),
'value' => 'save',
'classes' => 'button button-primary wu-w-full',
'wrapper_classes' => 'wu-items-end',
'html_attr' => array(
'v-on:click.prevent' => 'ready = true',
),
'wrapper_html_attr' => array(
'v-show' => '!ready',
'v-cloak' => 1
),
),
'current_site' => array(
'type' => 'hidden',
'value' => wu_request('current_site'),
),
'domain' => array(
'type' => 'text',
'title' => __('Domain', 'wp-ultimo'),
'placeholder' => __('mydomain.com', 'wp-ultimo'),
'wrapper_html_attr' => array(
'v-show' => 'ready',
'v-cloak' => 1
),
),
'primary_domain' => array(
'type' => 'toggle',
'title' => __('Primary Domain', 'wp-ultimo'),
'desc' => __('Check to set this domain as the primary', 'wp-ultimo'),
'html_attr' => array(
'v-model' => 'primary_domain',
),
'wrapper_html_attr' => array(
'v-show' => 'ready',
'v-cloak' => 1,
),
),
'primary_note' => array(
'type' => 'note',
'desc' => __('By making this the primary domain, we will convert the previous primary domain for this site, if one exists, into an alias domain.', 'wp-ultimo'),
'wrapper_html_attr' => array(
'v-if' => "require('primary_domain', true) && ready",
'v-cloak' => 1,
),
),
'submit_button_new' => array(
'type' => 'submit',
'title' => __('Add Domain', 'wp-ultimo'),
'value' => 'save',
'classes' => 'button button-primary wu-w-full',
'wrapper_classes' => 'wu-items-end',
'wrapper_html_attr' => array(
'v-show' => 'ready',
'v-cloak' => 1
),
),
);
$form = new \WP_Ultimo\UI\Form('add_new_domain', $fields, array(
'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' => array(
'data-wu-app' => 'add_new_domain',
'data-state' => json_encode(array(
'ready' => 0,
'primary_domain' => false,
)),
),
));
$form->render();
} // end render_user_add_new_domain_modal;
/**
* Handles creation of a new customer.
*
* @since 2.0.0
* @return void
*/
public function handle_user_add_new_domain_modal() {
$current_user_id = get_current_user_id();
$current_site_id = wu_request('current_site');
$current_site = wu_get_site($current_site_id);
if (!is_super_admin() && (!$current_site || $current_user_id !== $current_site->get_customer()->get_user_id())) {
wp_send_json_error(
new \WP_Error('no-permissions', __('You do not have permissions to perform this action.', 'wp-ultimo'))
);
exit;
} // end if;
/*
* Tries to create the domain
*/
$domain = wu_create_domain(array(
'domain' => wu_request('domain'),
'blog_id' => absint($current_site_id),
'primary_domain' => (bool) wu_request('primary_domain'),
));
if (is_wp_error($domain)) {
wp_send_json_error($domain);
} // end if;
if (wu_request('primary_domain')) {
$old_primary_domains = wu_get_domains(array(
'primary_domain' => true,
'blog_id' => $current_site_id,
'id__not_in' => array($domain->get_id()),
'fields' => 'ids',
));
/*
* Trigger async action to update the old primary domains.
*/
do_action_ref_array('wu_async_remove_old_primary_domains', array($old_primary_domains));
} // end if;
wu_enqueue_async_action('wu_async_process_domain_stage', array('domain_id' => $domain->get_id()), 'domain');
/**
* Triggers when a new domain mapping is added.
*/
do_action('wu_domain_created', $domain, $domain->get_site(), $domain->get_site()->get_membership());
wp_send_json_success(array(
'redirect_url' => wu_get_current_url(),
));
exit;
} // end handle_user_add_new_domain_modal;
/**
* Renders the domain delete action.
*
* @since 2.0.0
* @return void
*/
public function render_user_delete_domain_modal() {
$fields = array(
'confirm' => array(
'type' => 'toggle',
'title' => __('Confirm Deletion', 'wp-ultimo'),
'desc' => __('This action can not be undone.', 'wp-ultimo'),
'html_attr' => array(
'v-model' => 'confirmed',
),
),
'domain_id' => array(
'type' => 'hidden',
'value' => wu_request('domain_id'),
),
'submit_button' => array(
'type' => 'submit',
'title' => __('Delete', 'wp-ultimo'),
'placeholder' => __('Delete', 'wp-ultimo'),
'value' => 'save',
'classes' => 'button button-primary wu-w-full',
'wrapper_classes' => 'wu-items-end',
'html_attr' => array(
'v-bind:disabled' => '!confirmed',
),
),
);
$form = new \WP_Ultimo\UI\Form('user_delete_domain_modal', $fields, array(
'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' => array(
'data-wu-app' => 'user_delete_domain_modal',
'data-state' => json_encode(array(
'confirmed' => false,
)),
),
));
$form->render();
} // end render_user_delete_domain_modal;
/**
* Handles deletion of the selected domain
*
* @since 2.0.0
* @return void
*/
public function handle_user_delete_domain_modal() {
if (wu_request('user_id')) {
$customer = wu_get_customer_by_user_id(wu_request('user_id'));
} // end if;
$current_site = wu_request('current_site');
$get_domain = Domain::get_by_id(wu_request('domain_id'));
$domain = new Domain($get_domain);
if ($domain) {
$domain->delete();
} // end if;
wp_send_json_success(array(
'redirect_url' => wu_get_current_url(),
));
} // end handle_user_delete_domain_modal;
/**
* Renders the domain delete action.
*
* @since 2.0.0
* @return void
*/
public function render_user_make_domain_primary_modal() {
$fields = array(
'confirm' => array(
'type' => 'toggle',
'title' => __('Confirm Action', 'wp-ultimo'),
'desc' => __('This action will also convert the previous primary domain (if any) to an alias to prevent unexpected behavior.', 'wp-ultimo'),
'html_attr' => array(
'v-model' => 'confirmed',
),
),
'domain_id' => array(
'type' => 'hidden',
'value' => wu_request('domain_id'),
),
'submit_button' => array(
'type' => 'submit',
'title' => __('Make it Primary', 'wp-ultimo'),
'placeholder' => __('Make it Primary', 'wp-ultimo'),
'value' => 'save',
'classes' => 'button button-primary wu-w-full',
'wrapper_classes' => 'wu-items-end',
'html_attr' => array(
'v-bind:disabled' => '!confirmed',
),
),
);
$form = new \WP_Ultimo\UI\Form('user_delete_domain_modal', $fields, array(
'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' => array(
'data-wu-app' => 'user_delete_domain_modal',
'data-state' => json_encode(array(
'confirmed' => false,
)),
),
));
$form->render();
} // end render_user_make_domain_primary_modal;
/**
* Handles conversion to primary domain.
*
* @since 2.0.0
* @return void
*/
public function handle_user_make_domain_primary_modal() {
$current_site = wu_request('current_site');
$domain_id = wu_request('domain_id');
$domain = wu_get_domain($domain_id);
if ($domain) {
$domain->set_primary_domain(true);
$status = $domain->save();
if (is_wp_error($status)) {
wp_send_json_error($status);
} // end if;
$old_primary_domains = wu_get_domains(array(
'primary_domain' => true,
'blog_id' => $domain->get_blog_id(),
'id__not_in' => array($domain->get_id()),
'fields' => 'ids',
));
/*
* Trigger async action to update the old primary domains.
*/
do_action_ref_array('wu_async_remove_old_primary_domains', array($old_primary_domains));
wp_send_json_success(array(
'redirect_url' => is_main_site() ? wu_get_current_url() : get_admin_url($current_site),
));
} // end if;
wp_send_json_error(new \WP_Error('error', __('Something wrong happenned.', 'wp-ultimo')));
} // end handle_user_make_domain_primary_modal;
/**
* Runs early on the request lifecycle as soon as we detect the shortcode is present.
*
* @since 2.0.0
* @return void
*/
public function setup() {
$this->site = WP_Ultimo()->currents->get_site();
if (!$this->site || !$this->site->is_customer_allowed()) {
$this->set_display(false);
return;
} // end if;
// Ensure admin.php is loaded as we need wu_responsive_table_row function
require_once wu_path('inc/functions/admin.php');
$this->membership = $this->site->get_membership();
} // end setup;
/**
* Allows the setup in the context of previews.
*
* @since 2.0.0
* @return void
*/
public function setup_preview() {
$this->site = wu_mock_site();
$this->membership = wu_mock_membership();
} // end setup_preview;
/**
* The content to be output on the screen.
*
* Should return HTML markup to be used to display the block.
* This method is shared between the block render method and
* the shortcode implementation.
*
* @since 2.0.0
*
* @param array $atts Parameters of the block/shortcode.
* @param string|null $content The content inside the shortcode.
* @return string
*/
public function output($atts, $content = null) {
$current_site = $this->site;
$all_domains = wu_get_domains(array(
'blog_id' => $current_site->get_id(),
'orderby' => 'primary_domain',
'order' => 'DESC',
));
$domains = array();
foreach ($all_domains as $key => $domain) {
$stage = new Domain_Stage($domain->get_stage());
$secure = 'dashicons-wu-lock-open';
$secure_message = __('Domain not secured with HTTPS', 'wp-ultimo');
if ($domain->is_secure()) {
$secure = 'dashicons-wu-lock wu-text-green-500';
$secure_message = __('Domain secured with HTTPS', 'wp-ultimo');
} // end if;
$url_atts = array(
'current_site' => $current_site->get_id(),
'domain_id' => $domain->get_id(),
);
$delete_url = wu_get_form_url('user_delete_domain_modal', $url_atts);
$primary_url = wu_get_form_url('user_make_domain_primary', $url_atts);
$domains[$key] = array(
'id' => $domain->get_id(),
'domain_object' => $domain,
'domain' => $domain->get_domain(),
'stage' => $stage->get_label(),
'primary' => $domain->is_primary_domain(),
'stage_class' => $stage->get_classes(),
'secure_class' => $secure,
'secure_message' => $secure_message,
'delete_link' => $delete_url,
'primary_link' => $primary_url,
);
} // end foreach;
$url_atts = array(
'current_site' => $current_site->get_ID(),
);
$other_atts = array(
'domains' => $domains,
'modal' => array(
'label' => __('Add Domain', 'wp-ultimo'),
'icon' => 'wu-circle-with-plus',
'classes' => 'wubox',
'url' => wu_get_form_url('user_add_new_domain', $url_atts),
),
);
$atts = array_merge($other_atts, $atts);
return wu_get_template_contents('dashboard-widgets/domain-mapping', $atts);
} // end output;
} // end class Domain_Mapping_Element;

503
inc/ui/class-field.php Normal file
View File

@ -0,0 +1,503 @@
<?php
/**
* Describes a field and contains helper functions for sanitization and validation.
*
* @package WP_Ultimo
* @subpackage UI
* @since 2.0.0
*/
namespace WP_Ultimo\UI;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Describes a field and contains helper functions for sanitization and validation.
*
* @since 2.0.0
*/
class Field implements \JsonSerializable {
/**
* Holds the attributes of this field.
*
* @since 2.0.0
* @var array
*/
protected $atts = array();
/**
* Holds the value of the settings represented by this field.
*
* @since 2.0.0
* @var mixed
*/
protected $value = null;
/**
* Set and the attributes passed via the constructor.
*
* @since 2.0.0
*
* @param string $id Field id. This is going to be used to retrieve the value from the database later.
* @param array $atts Field attributes.
*/
public function __construct($id, $atts) {
$this->set_attributes($id, $atts);
} // end __construct;
/**
* Set and the attributes passed via the constructor.
*
* @since 2.0.0
*
* @param string $id Field id. This is going to be used to retrieve the value from the database later.
* @param array $atts Field attributes.
* @return void
*/
public function set_attributes($id, $atts) {
$this->atts = wp_parse_args($atts, array(
'id' => $id,
'type' => 'text',
'icon' => 'dashicons-wu-cog',
'action' => false,
'form' => false,
'title' => false,
'img' => false,
'desc' => false,
'content' => false,
'display_value' => false,
'default_value' => false,
'tooltip' => false,
'args' => false,
'sortable' => false,
'placeholder' => false,
'options' => false,
'options_template' => false,
'require' => false,
'button' => false,
'width' => false,
'rules' => false,
'min' => false,
'max' => false,
'allow_html' => false,
'append' => false,
'order' => false,
'dummy' => false,
'disabled' => false,
'capability' => false,
'edit' => false,
'copy' => false,
'validation' => false,
'meter' => false,
'href' => false,
'raw' => false,
'money' => false,
'stacked' => false, // If the field is inside a restricted container
'columns' => 1,
'classes' => '',
'wrapper_classes' => '',
'html_attr' => array(),
'wrapper_html_attr' => array(),
'sub_fields' => array(),
'prefix' => '',
'suffix' => '',
'prefix_html_attr' => array(),
'suffix_html_attr' => array(),
));
} // end set_attributes;
/**
* Set a particular attribute.
*
* @since 2.0.0
*
* @param string $att The attribute name.
* @param mixed $value The new attribute value.
* @return void
*/
public function set_attribute($att, $value) {
$this->atts[$att] = $value;
} // end set_attribute;
/**
* Returns the list of field attributes.
*
* @since 2.0.0
* @return array
*/
public function get_attributes() {
return $this->atts;
} // end get_attributes;
/**
* Makes sure old fields remain compatible.
*
* We are making some field type name changes in 2.0.
* This method lists an array with aliases in the following format:
*
* - old_type_name => new_type_name.
*
* We throw a deprecation notice to make sure developers update their code appropriately.
*
* @since 2.0.0
* @return string
*/
public function get_compat_template_name() {
$aliases = array(
'heading' => 'header',
'heading_collapsible' => 'header',
'select2' => 'select',
'checkbox' => 'toggle',
);
$deprecated = array(
'heading',
'heading_collapsible',
'select2',
);
if (array_key_exists($this->type, $aliases)) {
$new_type_name = $aliases[$this->type];
if (array_key_exists($this->type, $deprecated)) {
// translators: The %1$s placeholder is the old type name, the second, the new type name.
_doing_it_wrong('wu_add_field', sprintf(__('The field type "%1$s" is no longer supported, use "%2$s" instead.'), $this->type, $new_type_name), '2.0.0');
} // end if;
/*
* Back Compat for Select2 Fields
*/
if ($this->type === 'select2') {
$this->atts['html_attr']['data-selectize'] = 1;
$this->atts['html_attr']['multiple'] = 1;
} // end if;
return $new_type_name;
} // end if;
return false;
} // end get_compat_template_name;
/**
* Returns the template name for a field.
*
* We use this to go to the views folder and fetch the HTML template.
* The return here is not an absolute path, as the folder depends on the view the form is using.
*
* @see \WP_Ultimo\UI\Forms
*
* @since 2.0.0
* @return string
*/
public function get_template_name() {
$compat_name = $this->get_compat_template_name();
$view_name = $compat_name ? $compat_name : $this->type;
return str_replace('_', '-', (string) $view_name);
} // end get_template_name;
/**
* Returns attributes as class properties.
*
* @since 2.0.0
*
* @param string $att Attribute to retrieve.
* @return mixed
*/
public function __get($att) {
$allowed_callable = array(
'title',
'desc',
'content',
'display_value',
'default_value',
'tooltip',
'options',
'require',
'validation',
'value',
'html_attr',
'img',
);
$attr = isset($this->atts[$att]) ? $this->atts[$att] : false;
$allow_callable_prefix = is_string($attr) && strncmp($attr, 'wu_get_', strlen('wu_get_')) === 0 && is_callable($attr);
$allow_callable_method = is_array($attr) && is_callable($attr);
if (in_array($att, $allowed_callable, true) && ($allow_callable_prefix || $allow_callable_method || is_a($attr, \Closure::class))) {
$attr = call_user_func($attr, $this);
} // end if;
if ($att === 'wrapper_classes' && isset($this->atts['wrapper_html_attr']['v-show'])) {
$this->atts['wrapper_classes'] = $this->atts['wrapper_classes'] . ' wu-requires-other';
} // end if;
if ($att === 'type' && $this->atts[$att] === 'submit') {
$this->atts['wrapper_classes'] = $this->atts['wrapper_classes'] . ' wu-submit-field';
} // end if;
if ($att === 'type' && $this->atts[$att] === 'tab-select') {
$this->atts['wrapper_classes'] = $this->atts['wrapper_classes'] . ' wu-tab-field';
} // end if;
if ($att === 'wrapper_classes' && is_a($this->form, '\\WP_Ultimo\\UI\\Form')) {
return $this->form->field_wrapper_classes . ' ' . $this->atts['wrapper_classes'];
} // end if;
if ($att === 'classes' && is_a($this->form, '\\WP_Ultimo\\UI\\Form')) {
return $this->form->field_classes . ' ' . $this->atts['classes'];
} // end if;
if ($att === 'title' && $attr === false && isset($this->atts['name'])) {
$attr = $this->atts['name'];
} // end if;
return $attr;
} // end __get;
/**
* Returns the list of sanitization callbacks for each field type
*
* @since 2.0.0
* @return array
*/
protected function sanitization_rules() {
$rules = array(
'text' => 'sanitize_text_field',
'header' => '__return_null',
'number' => array($this, 'validate_number_field'),
'wp_editor' => array($this, 'validate_textarea_field'),
'textarea' => array($this, 'validate_textarea_field'),
'checkbox' => 'wu_string_to_bool',
'multi_checkbox' => false,
'select2' => false,
'multiselect' => false,
);
return apply_filters('wu_settings_fields_sanitization_rules', $rules);
} // end sanitization_rules;
/**
* Returns the value of the setting represented by this field.
*
* @since 2.0.0
* @return mixed
*/
public function get_value() {
return $this->value;
} // end get_value;
/**
* Sets the value of the settings represented by this field.
*
* This alone won't save the setting to the database. This method also invokes the
* sanitization callback, so we can be sure the data is ready for database insertion.
*
* @since 2.0.0
*
* @param mixed $value Value of the settings being represented by this field.
* @return WP_Ultimo\UI\Field
*/
public function set_value($value) {
$this->value = $value;
if (!$this->raw) {
$this->sanitize();
} // end if;
return $this;
} // end set_value;
/**
* Runs the value of the field through the sanitization callback.
*
* @since 2.0.0
* @return void
*/
public function sanitize() {
$rules = $this->sanitization_rules();
$sanitize_method = isset($rules[$this->type]) ? $rules[$this->type] : $rules['text'];
if ($sanitize_method) {
$this->value = call_user_func($sanitize_method, $this->value);
} // end if;
} // end sanitize;
/**
* Sanitization callback for fields of type number.
*
* Checks if the new value set is between the min and max boundaries.
*
* @since 2.0.0
*
* @param int|float $value Value of the settings being represented by this field.
* @return int|float
*/
protected function validate_number_field($value) {
/**
* Check if the value respects the min/max values.
*/
if ($this->min && $value < $this->min) {
return $this->min;
} // end if;
if ($this->max && $value > $this->max) {
return $this->max;
} // end if;
return $value;
} // end validate_number_field;
/**
* Cleans the value submitted via a textarea or wp_editor field for database insertion.
*
* @since 2.0.0
*
* @param string $value Value of the settings being represented by this field.
* @return string
*/
protected function validate_textarea_field($value) {
if ($this->allow_html) {
return stripslashes(wp_filter_post_kses(addslashes($value)));
} // end if;
return wp_strip_all_tags(stripslashes($value));
} // end validate_textarea_field;
/**
* Return HTML attributes for the field.
*
* @since 2.0.0
* @return string
*/
public function get_html_attributes() {
if (is_callable($this->atts['html_attr'])) {
$this->atts['html_attr'] = call_user_func($this->atts['html_attr']);
} // end if;
$attributes = $this->atts['html_attr'];
unset($this->atts['html_attr']['class']);
if ($this->type === 'number') {
if ($this->min !== false) {
$attributes['min'] = $this->min;
} // end if;
if ($this->max !== false) {
$attributes['max'] = $this->max;
} // end if;
} // end if;
/*
* Adds money formatting and masking
*/
if ($this->money !== false) {
$attributes['v-bind'] = 'money_settings';
} // end if;
return wu_array_to_html_attrs($attributes);
} // end get_html_attributes;
/**
* Return HTML attributes for the field.
*
* @since 2.0.0
* @return string
*/
public function get_wrapper_html_attributes() {
$attributes = $this->atts['wrapper_html_attr'];
unset($this->atts['wrapper_html_attr']['class']);
return wu_array_to_html_attrs($attributes);
} // end get_wrapper_html_attributes;
/**
* Implements our on json_decode version of this object. Useful for use in vue.js
*
* @since 2.0.0
* @return array
*/
#[\ReturnTypeWillChange]
public function jsonSerialize() {
return $this->atts;
} // end jsonSerialize;
} // end class Field;

250
inc/ui/class-form.php Normal file
View File

@ -0,0 +1,250 @@
<?php
/**
* Describes a form that can be used in different contexts, with different view files for each field type.
*
* @package WP_Ultimo
* @subpackage UI
* @since 2.0.0
*/
namespace WP_Ultimo\UI;
use \WP_Ultimo\UI\Field;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Describes a form that can be used in different contexts, with different view files for each field type.
*
* @since 2.0.0
*/
class Form implements \JsonSerializable {
/**
* Holds the attributes of this field.
*
* @since 2.0.0
* @var array
*/
protected $atts = array();
/**
* Holds the fields we want to display using this form.
*
* @since 2.0.0
* @var array
*/
protected $fields = array();
/**
* Set and the attributes passed via the constructor.
*
* @since 2.0.0
*
* @param string $id Form id. This is going to be used to retrieve the value from the database later.
* @param array $fields List of arrays representing the form fields.
* @param array $atts Form attributes.
*/
public function __construct($id, $fields, $atts = array()) {
$this->atts = apply_filters("wu_{$id}_form_atts", wp_parse_args($atts, array(
'id' => $id,
'method' => 'post',
'before' => '',
'after' => '',
'action' => false,
'title' => false,
'wrap_in_form_tag' => false,
'wrap_tag' => 'div',
'classes' => false,
'field_wrapper_classes' => false,
'field_classes' => false,
'views' => 'settings/fields',
'variables' => array(),
'step' => (object) array(
'classes' => '',
'element_id' => '',
),
'html_attr' => array(
'class' => '',
),
)));
$this->set_fields($fields);
} // end __construct;
/**
* Returns attributes as class properties.
*
* @since 2.0.0
*
* @param string $att Attribute to retrieve.
* @return mixed
*/
public function __get($att) {
$allowed_callable = array(
'before',
'after',
);
$attr = isset($this->atts[$att]) ? $this->atts[$att] : false;
if (in_array($att, $allowed_callable, true) && is_callable($attr)) {
$attr = call_user_func($attr, $this);
} // end if;
return $attr;
} // end __get;
/**
* Returns the list of field attributes.
*
* @since 2.0.0
* @return array
*/
public function get_attributes() {
return $this->atts;
} // end get_attributes;
/**
* Returns the list of fields used by the form.
*
* @since 2.0.0
* @return array
*/
public function get_fields() {
return (array) $this->fields;
} // end get_fields;
/**
* Casts fields to \WP_Ultimo\UI\Fields and stores them on private list.
*
* @since 2.0.0
*
* @param array $fields List of fields of the form.
* @return void
*/
public function set_fields($fields) {
$id = $this->id;
/**
* Filters the fields on a form. The form is identified by the ID in the filter name.
*
* @since 2.0.0
*
* @param array $fields List of fields of the form.
*/
$fields = apply_filters("wu_{$id}_form_fields", $fields);
foreach ($fields as $field_slug => $field) {
$field['form'] = $this;
$this->fields[$field_slug] = new Field($field_slug, $field);
} // end foreach;
} // end set_fields;
/**
* Renders the form with its fields.
*
* @since 2.0.0
* @return void
*/
public function render() {
$variables = array_merge($this->variables, array(
'form_slug' => $this->id,
'form' => $this,
'step' => $this->step,
));
ob_start();
foreach ($this->get_fields() as $field_slug => $field) {
$template_name = $field->get_template_name();
if (wu_get_isset($field->wrapper_html_attr, 'id') === false) {
$new_wrapper_attributes = $field->wrapper_html_attr;
$new_wrapper_attributes['id'] = "wrapper-field-$field_slug";
$field->set_attribute('wrapper_html_attr', $new_wrapper_attributes);
} // end if;
wu_get_template("{$this->views}/field-{$template_name}", array(
'field_slug' => $field_slug,
'field' => $field,
), "{$this->views}/field-text");
} // end foreach;
$rendered_fields = ob_get_clean();
$variables['rendered_fields'] = $rendered_fields;
wu_get_template("{$this->views}/form", $variables);
} // end render;
/**
* Return HTML attributes for the field.
*
* @since 2.0.0
* @return string
*/
public function get_html_attributes() {
$attributes = $this->atts['html_attr'];
unset($this->atts['html_attr']['class']);
if ($this->type === 'number') {
if ($this->min !== false) {
$attributes['min'] = $this->min;
} // end if;
if ($this->max !== false) {
$attributes['max'] = $this->max;
} // end if;
} // end if;
return wu_array_to_html_attrs($attributes);
} // end get_html_attributes;
/**
* Implements our on json_decode version of this object. Useful for use in vue.js
*
* @since 2.0.0
* @return array
*/
#[\ReturnTypeWillChange]
public function jsonSerialize() {
return $this->atts;
} // end jsonSerialize;
} // end class Form;

View File

@ -0,0 +1,302 @@
<?php
/**
* Adds the Invoices_Element UI to the Admin Panel.
*
* @package WP_Ultimo
* @subpackage UI
* @since 2.0.0
*/
namespace WP_Ultimo\UI;
use WP_Ultimo\UI\Base_Element;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Adds the Checkout Element UI to the Admin Panel.
*
* @since 2.0.0
*/
class Invoices_Element extends Base_Element {
use \WP_Ultimo\Traits\Singleton;
/**
* The id of the element.
*
* Something simple, without prefixes, like 'checkout', or 'pricing-tables'.
*
* This is used to construct shortcodes by prefixing the id with 'wu_'
* e.g. an id checkout becomes the shortcode 'wu_checkout' and
* to generate the Gutenberg block by prefixing it with 'wp-ultimo/'
* e.g. checkout would become the block 'wp-ultimo/checkout'.
*
* @since 2.0.0
* @var string
*/
public $id = 'invoices';
/**
* Controls if this is a public element to be used in pages/shortcodes by user.
*
* @since 2.0.24
* @var boolean
*/
protected $public = true;
/**
* The current membership.
*
* @since 2.2.0
* @var \WP_Ultimo\Models\Membership
*/
protected $membership;
/**
* The icon of the UI element.
* e.g. return fa fa-search
*
* @since 2.0.0
* @param string $context One of the values: block, elementor or bb.
* @return string
*/
public function get_icon($context = 'block') {
if ($context === 'elementor') {
return 'eicon-price-list';
} // end if;
return 'fa fa-search';
} // end get_icon;
/**
* The title of the UI element.
*
* This is used on the Blocks list of Gutenberg.
* You should return a string with the localized title.
* e.g. return __('My Element', 'wp-ultimo').
*
* @since 2.0.0
* @return string
*/
public function get_title() {
return __('Invoices', 'wp-ultimo');
} // end get_title;
/**
* The description of the UI element.
*
* This is also used on the Gutenberg block list
* to explain what this block is about.
* You should return a string with the localized title.
* e.g. return __('Adds a checkout form to the page', 'wp-ultimo').
*
* @since 2.0.0
* @return string
*/
public function get_description() {
return __('Adds a checkout form block to the page.', 'wp-ultimo');
} // end get_description;
/**
* The list of fields to be added to Gutenberg.
*
* If you plan to add Gutenberg controls to this block,
* you'll need to return an array of fields, following
* our fields interface (@see inc/ui/class-field.php).
*
* You can create new Gutenberg panels by adding fields
* with the type 'header'. See the Checkout Elements for reference.
*
* @see inc/ui/class-checkout-element.php
*
* Return an empty array if you don't have controls to add.
*
* @since 2.0.0
* @return array
*/
public function fields() {
$fields = array();
$fields['header'] = array(
'title' => __('General', 'wp-ultimo'),
'desc' => __('General', 'wp-ultimo'),
'type' => 'header',
);
$fields['title'] = array(
'type' => 'text',
'title' => __('Title', 'wp-ultimo'),
'value' => __('Invoices', 'wp-ultimo'),
'desc' => __('Leave blank to hide the title completely.', 'wp-ultimo'),
'tooltip' => '',
);
$fields['limit'] = array(
'type' => 'int',
'title' => __('Limit', 'wp-ultimo'),
'value' => 10,
'desc' => __('Limit the number of invoices to show.', 'wp-ultimo'),
'tooltip' => '',
);
return $fields;
} // end fields;
/**
* The list of keywords for this element.
*
* Return an array of strings with keywords describing this
* element. Gutenberg uses this to help customers find blocks.
*
* e.g.:
* return array(
* 'WP Ultimo',
* 'Invoices',
* 'Form',
* 'Cart',
* );
*
* @since 2.0.0
* @return array
*/
public function keywords() {
return array(
'WP Ultimo',
'Invoices',
'Form',
'Cart',
);
} // end keywords;
/**
* List of default parameters for the element.
*
* If you are planning to add controls using the fields,
* it might be a good idea to use this method to set defaults
* for the parameters you are expecting.
*
* These defaults will be used inside a 'wp_parse_args' call
* before passing the parameters down to the block render
* function and the shortcode render function.
*
* @since 2.0.0
* @return array
*/
public function defaults() {
return array(
'title' => __('Invoices', 'wp-ultimo'),
'limit' => 0,
);
} // end defaults;
/**
* Loads the required scripts.
*
* @since 2.0.0
* @return void
*/
public function register_scripts() {
wp_enqueue_script('wu-ajax-list-table');
} // end register_scripts;
/**
* Loads dependencies for the render.
*
* @since 2.0.0
* @return void
*/
public function dependencies() {
if (!function_exists('convert_to_screen')) {
require_once ABSPATH . 'wp-admin/includes/template.php';
} // end if;
if (!function_exists('get_column_headers')) {
require_once ABSPATH . 'wp-admin/includes/class-wp-screen.php';
require_once ABSPATH . 'wp-admin/includes/screen.php';
} // end if;
if (!function_exists('wu_responsive_table_row')) {
require wu_path('/inc/functions/admin.php');
} // end if;
} // end dependencies;
/**
* Runs early on the request lifecycle as soon as we detect the shortcode is present.
*
* @since 2.0.0
* @return void
*/
public function setup() {
$this->membership = WP_Ultimo()->currents->get_membership();
if (!$this->membership) {
$this->set_display(false);
return;
} // end if;
} // end setup;
/**
* Allows the setup in the context of previews.
*
* @since 2.0.0
* @return void
*/
public function setup_preview() {
$this->membership = wu_mock_membership();
} // end setup_preview;
/**
* The content to be output on the screen.
*
* Should return HTML markup to be used to display the block.
* This method is shared between the block render method and
* the shortcode implementation.
*
* @since 2.0.0
*
* @param array $atts Parameters of the block/shortcode.
* @param string|null $content The content inside the shortcode.
* @return string
*/
public function output($atts, $content = null) {
$atts['membership'] = $this->membership;
return wu_get_template_contents('dashboard-widgets/invoices', $atts);
} // end output;
} // end class Invoices_Element;

598
inc/ui/class-jumper.php Normal file
View File

@ -0,0 +1,598 @@
<?php
/**
* Adds the Jumper UI to the Admin Panel.
*
* @package WP_Ultimo
* @subpackage UI
* @since 2.0.0
*/
namespace WP_Ultimo\UI;
use WP_Ultimo\Logger;
use WP_Ultimo\UI\Base_Element;
use WP_Ultimo\License;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Adds the Jumper UI to the Admin Panel.
*
* @since 2.0.0
*/
class Jumper {
use \WP_Ultimo\Traits\Singleton;
/**
* GET slug to force the jumper menu reset/fetching.
*
* @since 2.0.0
* @var string
*/
protected $reset_slug = 'wu-rebuild-jumper';
/**
* Key to save the menu list on the transient database.
*
* @since 2.0.0
* @var string
*/
protected $transient_key = 'wu-jumper-menu-list';
/**
* Element construct.
*
* @since 2.0.0
*/
public function __construct() {
add_action('wp_ultimo_load', array($this, 'add_settings'), 20);
add_action('init', array($this, 'load_jumper'));
} // end __construct;
/**
* Checks if we should add the jumper or not.
*
* @since 2.0.0
* @return boolean
*/
protected function is_jumper_enabled() {
return apply_filters('wu_is_jumper_enabled', wu_get_setting('enable_jumper', true) && License::get_instance()->allowed() && current_user_can('manage_network'));
} // end is_jumper_enabled;
/**
* Adds the Jumper trigger to the admin top pages.
*
* @since 2.0.0
*
* @param \WP_Ultimo\Admin_Pages\Base_Admin_Page $page The current page.
* @return void
*/
public function add_jumper_trigger($page) {
wu_get_template('ui/jumper-trigger', array(
'page' => $page,
'jumper' => $this,
));
} // end add_jumper_trigger;
/**
* Loads the necessary elements to display the Jumper.
*
* @since 2.0.0
* @return void
*/
public function load_jumper() {
if ($this->is_jumper_enabled() && is_admin()) {
add_action('wu_header_right', array($this, 'add_jumper_trigger'));
add_action('admin_init', array($this, 'rebuild_menu'));
add_action('admin_enqueue_scripts', array($this, 'enqueue_scripts'));
add_action('admin_enqueue_scripts', array($this, 'enqueue_styles'));
add_action('admin_footer', array($this, 'output'));
add_filter('update_footer', array($this, 'add_jumper_footer_message'), 200);
add_action('wu_after_save_settings', array($this, 'clear_jump_cache_on_save'));
add_filter('wu_link_list', array($this, 'add_wp_ultimo_extra_links'));
add_filter('wu_link_list', array($this, 'add_user_custom_links'));
} // end if;
} // end load_jumper;
/**
* Clear the jumper menu cache on settings save
*
* We need to do this to make sure that we clear the menu when the admin
* adds a new custom menu item.
*
* @since 2.0.0
*
* @param array $settings Settings being saved.
* @return void
*/
public function clear_jump_cache_on_save($settings) {
if (isset($settings['jumper_custom_links'])) {
delete_site_transient($this->transient_key);
} // end if;
} // end clear_jump_cache_on_save;
/**
* Rebuilds the jumper menu via a trigger URL.
*
* @since 2.0.0
* @return void
*/
public function rebuild_menu() {
if (isset($_GET[$this->reset_slug]) && current_user_can('manage_network')) {
delete_site_transient($this->transient_key);
wp_redirect(network_admin_url());
exit;
} // end if;
} // end rebuild_menu;
/**
* Retrieves the custom links added by the super admin
*
* @since 2.0.0
* @return array
*/
public function get_user_custom_links() {
$treated_lines = array();
$saved_links = wu_get_setting('jumper_custom_links');
$lines = explode(PHP_EOL, (string) $saved_links);
foreach ($lines as $line) {
$link_elements = explode(':', $line, 2);
if (count($link_elements) === 2) {
$title = trim($link_elements[1]);
$treated_lines[$title] = trim($link_elements[0]);
} // end if;
} // end foreach;
return $treated_lines;
} // end get_user_custom_links;
/**
* Add the custom links to the Jumper menu
*
* @since 2.0.0
*
* @param array $links Jumper links already saved.
* @return array
*/
public function add_user_custom_links($links) {
$custom_links = $this->get_user_custom_links();
if (!empty($custom_links)) {
$links[__('Custom Links', 'wp-ultimo')] = $custom_links;
} // end if;
return $links;
} // end add_user_custom_links;
/**
* Add WP Ultimo settings links to the Jumper menu.
*
* @since 2.0.0
*
* @param array $links WP Ultimo settings array.
* @return array
*/
public function add_wp_ultimo_extra_links($links) {
if (isset($links['WP Ultimo'])) {
$settings_tabs = array(
'general' => __('General', 'wp-ultimo'),
'network' => __('Network Settings', 'wp-ultimo'),
'gateways' => __('Payment Gateways', 'wp-ultimo'),
'domain_mapping' => __('Domain Mapping & SSL', 'wp-ultimo'),
'emails' => __('Emails', 'wp-ultimo'),
'styling' => __('Styling', 'wp-ultimo'),
'tools' => __('Tools', 'wp-ultimo'),
'advanced' => __('Advanced', 'wp-ultimo'),
'activation' => __('Activation & Support', 'wp-ultimo'),
);
foreach ($settings_tabs as $tab => $tab_label) {
$url = network_admin_url('admin.php?page=wp-ultimo-settings&wu-tab=' . $tab);
// translators: The placeholder represents the title of the Settings tab.
$links['WP Ultimo'][$url] = sprintf(__('Settings: %s', 'wp-ultimo'), $tab_label);
} // end foreach;
$links['WP Ultimo'][ network_admin_url('admin.php?page=wp-ultimo-settings&wu-tab=tools') ] = __('Settings: Webhooks', 'wp-ultimo');
$links['WP Ultimo'][ network_admin_url('admin.php?page=wp-ultimo-system-info&wu-tab=logs') ] = __('System Info: Logs', 'wp-ultimo');
/**
* Adds Main Site Dashboard
*/
if (isset($links[__('Sites')])) {
$main_site_url = get_admin_url(get_current_site()->blog_id);
$links[__('Sites')][$main_site_url] = __('Main Site Dashboard', 'wp-ultimo');
} // end if;
} // end if;
return $links;
} // end add_wp_ultimo_extra_links;
/**
* Get the trigger key defined by the user.
*
* @since 2.0.0
*/
function get_defined_trigger_key(): string {
return substr((string) wu_get_setting('jumper_key', 'g'), 0, 1);
} // end get_defined_trigger_key;
/**
* Get the trigger key combination depending on the OS
*
* - For Win & Linux: ctrl + alt + key defined by user;
* - For Mac: command + option + key defined by user.
*
* @since 2.0.0
*
* @param string $os OS to get the key combination for. Options: win or osx.
* @return array
*/
function get_keys($os = 'win') {
$trigger_key = $this->get_defined_trigger_key();
$keys = array(
'win' => array('ctrl', 'alt', $trigger_key),
'osx' => array('command', 'option', $trigger_key),
);
return isset($keys[$os]) ? $keys[$os] : $keys['win'];
} // end get_keys;
/**
* Changes the helper footer message about the Jumper and its trigger
*
* @since 2.0.0
*
* @param string $text The default WordPress right footer message.
* @return string
*/
public function add_jumper_footer_message($text) {
if (!wu_get_setting('jumper_display_tip', true)) {
return $text;
} // end if;
$os = stristr((string) $_SERVER['HTTP_USER_AGENT'], 'mac') ? 'osx' : 'win';
$keys = $this->get_keys($os);
$html = '';
foreach ($keys as $key) {
$html .= '<span class="wu-keys-key">' . $key . '</span>+';
} // end foreach;
$html = trim($html, '+');
// translators: the %s placeholder is the key combination to trigger the Jumper.
return '<span class="wu-keys">' . sprintf(__('<strong>Quick Tip:</strong> Use %s to jump between pages.', 'wp-ultimo'), $html) . '</span>' . $text;
} // end add_jumper_footer_message;
/**
* Enqueues the JavaScript files necessary to make the jumper work.
*
* @since 2.0.0
* @return void
*/
public function enqueue_scripts() {
wp_register_script('wu-mousetrap', wu_get_asset('mousetrap.js', 'js/lib'), array('jquery'), wu_get_version(), true);
wp_register_script('wu-jumper', wu_get_asset('jumper.js', 'js'), array('jquery', 'wu-selectize', 'wu-mousetrap', 'underscore'), wu_get_version(), true);
wp_localize_script('wu-jumper', 'wu_jumper_vars', array(
'not_found_message' => __('Nothing found for', 'wp-ultimo'),
'trigger_key' => $this->get_defined_trigger_key(),
'network_base_url' => network_admin_url(),
'ajaxurl' => wu_ajax_url(),
'base_url' => get_admin_url(get_current_site()->blog_id),
));
wp_enqueue_script('wu-jumper');
wp_enqueue_style('wu-admin');
} // end enqueue_scripts;
/**
* Enqueues the CSS files necessary to make the jumper work.
*
* @since 2.0.0
* @return void
*/
public function enqueue_styles() {
wp_enqueue_style('wu-jumper', wu_get_asset('jumper.css', 'css'), array(), wu_get_version());
} // end enqueue_styles;
/**
* Outputs the actual HTML markup of the Jumper.
*
* @since 2.0.0
* @return void
*/
public function output() {
wu_get_template('ui/jumper', array(
'menu_groups' => $this->get_link_list(),
));
} // end output;
/**
* Get the full page URL for admin pages.
*
* @since 2.0.0
*
* @param string $url URL of the menu item.
*/
public function get_menu_page_url($url): string {
$final_url = menu_page_url($url, false);
return str_replace(admin_url(), network_admin_url(), $final_url);
} // end get_menu_page_url;
/**
* Returns the URL of a jumper menu item
*
* If the URL is an absolute URL, returns the full-url.
* If the URL is relative, we return the full URL using WordPress url functions.
*
* @since 2.0.0
*
* @param string $url URL of the menu item.
* @return string
*/
public function get_target_url($url) {
if (strpos($url, 'http') !== false) {
return $url;
} // end if;
if (strpos($url, '.php') !== false) {
return network_admin_url($url);
} // end if;
return $this->get_menu_page_url($url);
} // end get_target_url;
/**
* Builds the list of links based on the $menu and $submenu globals.
*
* @since 2.0.0
*
* @return array
*/
public function build_link_list() {
return Logger::track_time('jumper', __('Regenerating Jumper menu items', 'wp-ultimo'), function() {
global $menu, $submenu;
// This variable is going to carry our options
$choices = array();
// Prevent first run bug
if (!is_array($menu) || !is_array($submenu)) {
return array();
} // end if;
// Loop all submenus so que can get our final
foreach ($submenu as $menu_name => $submenu_items) {
$title = $this->search_recursive($menu_name, $menu);
$string = wu_get_isset($title, 0, '');
$title = preg_replace('/[0-9]+/', '', strip_tags($string));
// If parent does not exists, skip
if (!empty($title) && is_array($submenu_items)) {
// We have to loop now each submenu
foreach ($submenu_items as $submenu_item) {
$url = $this->get_target_url($submenu_item[2]);
// Add to our choices the admin urls
$choices[$title][$url] = preg_replace('/[0-9]+/', '', strip_tags((string) $submenu_item[0]));
} // end foreach;
} // end if;
} // end foreach;
$choices = apply_filters('wu_link_list', $choices);
set_site_transient($this->transient_key, $choices, 10 * MINUTE_IN_SECONDS);
return $choices;
});
} // end build_link_list;
/**
* Gets the cached menu list saved.
*
* @since 2.0.0
* @return array
*/
public function get_saved_menu() {
$saved_menu = get_site_transient($this->transient_key);
return $saved_menu ? $saved_menu : array();
} // end get_saved_menu;
/**
* Returns the link list.
*
* @since 2.0.0
* @return array
*/
public function get_link_list() {
$should_rebuild_menu = !get_site_transient($this->transient_key);
return $should_rebuild_menu && is_network_admin() ? $this->build_link_list() : $this->get_saved_menu();
} // end get_link_list;
/**
* Filter the WP Ultimo settings to add Jumper options
*
* @since 2.0.0
*
* @return void
*/
public function add_settings() {
wu_register_settings_section('tools', array(
'title' => __('Tools', 'wp-ultimo'),
'desc' => __('Tools', 'wp-ultimo'),
'icon' => 'dashicons-wu-tools',
));
wu_register_settings_field('tools', 'tools_header', array(
'title' => __('Jumper', 'wp-ultimo'),
'desc' => __('Spotlight-like search bar that allows you to easily access everything on your network.', 'wp-ultimo'),
'type' => 'header',
));
wu_register_settings_field('tools', 'enable_jumper', array(
'title' => __('Enable Jumper', 'wp-ultimo'),
'desc' => __('Turn this option on to make the Jumper available on your network.', 'wp-ultimo'),
'type' => 'toggle',
'default' => 1,
));
wu_register_settings_field('tools', 'jumper_key', array(
'title' => __('Trigger Key', 'wp-ultimo'),
'desc' => __('Change the keyboard key used in conjunction with ctrl + alt (or cmd + option), to trigger the Jumper box.', 'wp-ultimo'),
'type' => 'text',
'default' => 'g',
'require' => array(
'enable_jumper' => 1,
),
));
wu_register_settings_field('tools', 'jumper_custom_links', array(
'title' => __('Custom Links', 'wp-ultimo'),
'desc' => __('Use this textarea to add custom links to the Jumper. Add one per line, with the format "Title : url".', 'wp-ultimo'),
'placeholder' => __('Tile of Custom Link : http://link.com', 'wp-ultimo'),
'type' => 'textarea',
'html_attr' => array(
'rows' => 4,
),
'require' => array(
'enable_jumper' => 1,
),
));
} // end add_settings;
/**
* Helper function to recursively seach an array.
*
* @since 2.0.0
*
* @param string $needle String to seach recursively.
* @param array $haystack Array to search.
* @return mixed
*/
public function search_recursive($needle, $haystack) {
foreach ($haystack as $key => $value) {
$current_key = $key;
if ($needle === $value || (is_array($value) && $this->search_recursive($needle, $value) !== false)) {
return $value;
} // end if;
} // end foreach;
return false;
} // end search_recursive;
} // end class Jumper;

View File

@ -0,0 +1,283 @@
<?php
/**
* Adds the Limnits and Quotas element as BB, Elementor and Widget.
*
* @package WP_Ultimo
* @subpackage UI
* @since 2.0.0
*/
namespace WP_Ultimo\UI;
use \WP_Ultimo\UI\Base_Element;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Adds the Limits and Quotas element as BB, Elementor and Widget.
*
* @since 2.0.0
*/
class Limits_Element extends Base_Element {
use \WP_Ultimo\Traits\Singleton;
/**
* The id of the element.
*
* Something simple, without prefixes, like 'checkout', or 'pricing-tables'.
*
* This is used to construct shortcodes by prefixing the id with 'wu_'
* e.g. an id checkout becomes the shortcode 'wu_checkout' and
* to generate the Gutenberg block by prefixing it with 'wp-ultimo/'
* e.g. checkout would become the block 'wp-ultimo/checkout'.
*
* @since 2.0.0
* @var string
*/
public $id = 'limits';
/**
* Controls if this is a public element to be used in pages/shortcodes by user.
*
* @since 2.0.24
* @var boolean
*/
protected $public = true;
/**
* The current site.
*
* @since 2.2.0
* @var \WP_Ultimo\Models\Site
*/
protected $site;
/**
* The icon of the UI element.
* e.g. return fa fa-search
*
* @since 2.0.0
* @param string $context One of the values: block, elementor or bb.
*/
public function get_icon($context = 'block'): string {
if ($context === 'elementor') {
return 'eicon-skill-bar';
} // end if;
return 'fa fa-search';
} // end get_icon;
/**
* The title of the UI element.
*
* This is used on the Blocks list of Gutenberg.
* You should return a string with the localized title.
* e.g. return __('My Element', 'wp-ultimo').
*
* @since 2.0.0
* @return string
*/
public function get_title() {
return __('Limits & Quotas', 'wp-ultimo');
} // end get_title;
/**
* The description of the UI element.
*
* This is also used on the Gutenberg block list
* to explain what this block is about.
* You should return a string with the localized title.
* e.g. return __('Adds a checkout form to the page', 'wp-ultimo').
*
* @since 2.0.0
* @return string
*/
public function get_description() {
return __('Adds a checkout form block to the page.', 'wp-ultimo');
} // end get_description;
/**
* The list of fields to be added to Gutenberg.
*
* If you plan to add Gutenberg controls to this block,
* you'll need to return an array of fields, following
* our fields interface (@see inc/ui/class-field.php).
*
* You can create new Gutenberg panels by adding fields
* with the type 'header'. See the Checkout Elements for reference.
*
* @see inc/ui/class-checkout-element.php
*
* Return an empty array if you don't have controls to add.
*
* @since 2.0.0
* @return array
*/
public function fields() {
$fields = array();
$fields['header'] = array(
'title' => __('General', 'wp-ultimo'),
'desc' => __('General', 'wp-ultimo'),
'type' => 'header',
);
$fields['title'] = array(
'type' => 'text',
'title' => __('Title', 'wp-ultimo'),
'value' => __('Site Limits', 'wp-ultimo'),
'desc' => __('Leave blank to hide the title completely.', 'wp-ultimo'),
'tooltip' => '',
);
$fields['columns'] = array(
'type' => 'number',
'title' => __('Columns', 'wp-ultimo'),
'desc' => __('How many columns to use.', 'wp-ultimo'),
'tooltip' => '',
'value' => 1,
'min' => 1,
'max' => 10,
);
return $fields;
} // end fields;
/**
* The list of keywords for this element.
*
* Return an array of strings with keywords describing this
* element. Gutenberg uses this to help customers find blocks.
*
* e.g.:
* return array(
* 'WP Ultimo',
* 'Checkout',
* 'Form',
* 'Cart',
* );
*
* @since 2.0.0
* @return array
*/
public function keywords() {
return array(
'WP Ultimo',
'Account',
'Limits',
'Quotas',
);
} // end keywords;
/**
* List of default parameters for the element.
*
* If you are planning to add controls using the fields,
* it might be a good idea to use this method to set defaults
* for the parameters you are expecting.
*
* These defaults will be used inside a 'wp_parse_args' call
* before passing the parameters down to the block render
* function and the shortcode render function.
*
* @since 2.0.0
* @return array
*/
public function defaults() {
return array(
'columns' => 1,
'title' => __('Site Limits', 'wp-ultimo'),
);
} // end defaults;
/**
* Runs early on the request lifecycle as soon as we detect the shortcode is present.
*
* @since 2.0.0
* @return void
*/
public function setup() {
$this->site = WP_Ultimo()->currents->get_site();
if (!$this->site || !$this->site->is_customer_allowed()) {
$this->set_display(false);
} // end if;
} // end setup;
/**
* Allows the setup in the context of previews.
*
* @since 2.0.0
* @return void
*/
public function setup_preview() {
$this->site = wu_mock_site();
} // end setup_preview;
/**
* The content to be output on the screen.
*
* Should return HTML markup to be used to display the block.
* This method is shared between the block render method and
* the shortcode implementation.
*
* @since 2.0.0
*
* @param array $atts Parameters of the block/shortcode.
* @param string|null $content The content inside the shortcode.
* @return string
*/
public function output($atts, $content = null) {
$post_types = get_post_types(array(
'public' => true,
), 'objects');
/*
* Remove post types that where disabled or that are not available for display.
*/
$post_types = array_filter($post_types, fn($post_type_slug) => $this->site->get_limitations()->post_types->{$post_type_slug}->enabled, ARRAY_FILTER_USE_KEY);
/**
* Allow developers to select which post types should be displayed.
*
* @since 2.0.0
* @param array $post_types List of post types.
* @return array New list.
*/
$post_types = apply_filters('wu_get_post_types', $post_types);
$items_to_display = wu_get_setting('limits_and_quotas');
$atts['site'] = $this->site;
$atts['post_types'] = $post_types;
$atts['items_to_display'] = $items_to_display ? array_keys($items_to_display) : false;
$atts['post_type_limits'] = $this->site->get_limitations()->post_types;
return wu_get_template_contents('dashboard-widgets/limits-and-quotas', $atts);
} // end output;
} // end class Limits_Element;

View File

@ -0,0 +1,876 @@
<?php
/**
* Adds the Login Form Element UI to the Admin Panel.
*
* @package WP_Ultimo
* @subpackage UI
* @since 2.0.0
*/
namespace WP_Ultimo\UI;
use \WP_Ultimo\UI\Base_Element;
use \WP_Ultimo\Checkout\Checkout_Pages;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Adds the Checkout Element UI to the Admin Panel.
*
* @since 2.0.0
*/
class Login_Form_Element extends Base_Element {
use \WP_Ultimo\Traits\Singleton;
/**
* The id of the element.
*
* Something simple, without prefixes, like 'checkout', or 'pricing-tables'.
*
* This is used to construct shortcodes by prefixing the id with 'wu_'
* e.g. an id checkout becomes the shortcode 'wu_checkout' and
* to generate the Gutenberg block by prefixing it with 'wp-ultimo/'
* e.g. checkout would become the block 'wp-ultimo/checkout'.
*
* @since 2.0.0
* @var string
*/
public $id = 'login-form';
/**
* Controls if this is a public element to be used in pages/shortcodes by user.
*
* @since 2.0.24
* @var bool
*/
protected $public = true;
/**
* If the current user is logged in.
*
* @since 2.2.0
* @var bool
*/
protected $logged;
/**
* Initializes the singleton.
*
* @since 2.0.11
* @return void
*/
public function init() {
// Handle login redirection
add_filter('login_redirect', array($this, 'handle_redirect'), -1, 3);
parent::init();
} // end init;
/**
* The icon of the UI element.
* e.g. return fa fa-search
*
* @since 2.0.0
* @param string $context One of the values: block, elementor or bb.
*/
public function get_icon($context = 'block'): string {
if ($context === 'elementor') {
return 'eicon-lock-user';
} // end if;
return 'fa fa-search';
} // end get_icon;
/**
* The title of the UI element.
*
* This is used on the Blocks list of Gutenberg.
* You should return a string with the localized title.
* e.g. return __('My Element', 'wp-ultimo').
*
* @since 2.0.0
* @return string
*/
public function get_title() {
return __('Login Form', 'wp-ultimo');
} // end get_title;
/**
* The description of the UI element.
*
* This is also used on the Gutenberg block list
* to explain what this block is about.
* You should return a string with the localized title.
* e.g. return __('Adds a checkout form to the page', 'wp-ultimo').
*
* @since 2.0.0
* @return string
*/
public function get_description() {
return __('Adds a login form to the page.', 'wp-ultimo');
} // end get_description;
/**
* The list of fields to be added to Gutenberg.
*
* If you plan to add Gutenberg controls to this block,
* you'll need to return an array of fields, following
* our fields interface (@see inc/ui/class-field.php).
*
* You can create new Gutenberg panels by adding fields
* with the type 'header'. See the Checkout Elements for reference.
*
* @see inc/ui/class-checkout-element.php
*
* Return an empty array if you don't have controls to add.
*
* @since 2.0.0
* @return array
*/
public function fields() {
$fields = array();
$fields['header'] = array(
'title' => __('General', 'wp-ultimo'),
'desc' => __('General', 'wp-ultimo'),
'type' => 'header',
);
$fields['display_title'] = array(
'type' => 'toggle',
'title' => __('Display Title?', 'wp-ultimo'),
'desc' => __('Toggle to show/hide the title element.', 'wp-ultimo'),
'tooltip' => '',
'value' => 1,
);
$fields['title'] = array(
'type' => 'text',
'title' => __('Title', 'wp-ultimo'),
'value' => __('Login', 'wp-ultimo'),
'desc' => '',
'tooltip' => '',
'required' => array(
'display_title' => 1,
),
);
$fields['redirect_type'] = array(
'type' => 'select',
'title' => __('Redirect Type', 'wp-ultimo'),
'desc' => __('The behavior after login', 'wp-ultimo'),
'tooltip' => '',
'default' => 'default',
'options' => array(
'default' => __('Wordpress Default', 'wp-ultimo'),
'customer_site' => __('Send To Customer Site', 'wp-ultimo'),
'main_site' => __('Send To Main Site', 'wp-ultimo'),
),
);
$fields['customer_redirect_path'] = array(
'type' => 'text',
'title' => __('Customer Redirect Path', 'wp-ultimo'),
'value' => __('/wp-admin', 'wp-ultimo'),
'desc' => __('e.g. /wp-admin', 'wp-ultimo'),
'tooltip' => '',
'required' => array(
'redirect_type' => 'customer_site',
),
);
$fields['main_redirect_path'] = array(
'type' => 'text',
'title' => __('Main Site Redirect Path', 'wp-ultimo'),
'value' => __('/wp-admin', 'wp-ultimo'),
'desc' => __('e.g. /wp-admin', 'wp-ultimo'),
'tooltip' => '',
'required' => array(
'redirect_type' => 'main_site',
),
);
$fields['header_username'] = array(
'title' => __('Username Field', 'wp-ultimo'),
'desc' => __('Username Field', 'wp-ultimo'),
'type' => 'header',
);
$fields['label_username'] = array(
'type' => 'text',
'title' => __('Username Field Label', 'wp-ultimo'),
'value' => __('Username or Email Address', 'wp-ultimo'),
'desc' => __('Leave blank to hide.', 'wp-ultimo'),
'tooltip' => '',
);
$fields['placeholder_username'] = array(
'type' => 'text',
'title' => __('Username Field Placeholder', 'wp-ultimo'),
'desc' => __('e.g. Username Here', 'wp-ultimo'),
'value' => '',
'tooltip' => '',
);
$fields['header_password'] = array(
'title' => __('Password Field', 'wp-ultimo'),
'desc' => __('Password Field', 'wp-ultimo'),
'type' => 'header',
);
$fields['label_password'] = array(
'type' => 'text',
'title' => __('Password Field Label', 'wp-ultimo'),
'value' => __('Password', 'wp-ultimo'),
'desc' => __('Leave blank to hide.', 'wp-ultimo'),
'tooltip' => '',
);
$fields['placeholder_password'] = array(
'type' => 'text',
'title' => __('Password Field Placeholder', 'wp-ultimo'),
'desc' => __('e.g. Your Password', 'wp-ultimo'),
'value' => '',
'tooltip' => '',
);
$fields['header_remember'] = array(
'title' => __('Remember Me', 'wp-ultimo'),
'desc' => __('Remember Me', 'wp-ultimo'),
'type' => 'header',
);
$fields['remember'] = array(
'type' => 'toggle',
'title' => __('Display Remember Toggle?', 'wp-ultimo'),
'desc' => __('Toggle to show/hide the remember me checkbox.', 'wp-ultimo'),
'tooltip' => '',
'value' => 1,
);
$fields['label_remember'] = array(
'type' => 'text',
'title' => __('Remember Me Label', 'wp-ultimo'),
'value' => __('Remember Me'),
'desc' => '',
'tooltip' => '',
'required' => array(
'remember' => 1,
),
);
$fields['desc_remember'] = array(
'type' => 'text',
'title' => __('Remember Me Description', 'wp-ultimo'),
'value' => __('Keep me logged in for two weeks.', 'wp-ultimo'),
'desc' => '',
'tooltip' => '',
'required' => array(
'remember' => 1,
),
);
$fields['header_submit'] = array(
'title' => __('Submit Button', 'wp-ultimo'),
'desc' => __('Submit Button', 'wp-ultimo'),
'type' => 'header',
);
$fields['label_log_in'] = array(
'type' => 'text',
'title' => __('Submit Button Label', 'wp-ultimo'),
'value' => __('Log In', 'wp-ultimo'),
'tooltip' => '',
);
return $fields;
} // end fields;
/**
* Registers scripts and styles necessary to render this.
*
* @since 2.0.0
* @return void
*/
public function register_scripts() {
wp_enqueue_style('wu-admin');
} // end register_scripts;
/**
* The list of keywords for this element.
*
* Return an array of strings with keywords describing this
* element. Gutenberg uses this to help customers find blocks.
*
* e.g.:
* return array(
* 'WP Ultimo',
* 'Billing_Address',
* 'Form',
* 'Cart',
* );
*
* @since 2.0.0
* @return array
*/
public function keywords() {
return array(
'WP Ultimo',
'Login',
'Reset Password',
);
} // end keywords;
/**
* List of default parameters for the element.
*
* If you are planning to add controls using the fields,
* it might be a good idea to use this method to set defaults
* for the parameters you are expecting.
*
* These defaults will be used inside a 'wp_parse_args' call
* before passing the parameters down to the block render
* function and the shortcode render function.
*
* @since 2.0.0
* @return array
*/
public function defaults() {
// Default 'redirect' value takes the user back to the request URI.
$redirect_to = wu_get_current_url();
return array(
'display_title' => 1,
'title' => __('Login', 'wp-ultimo'),
'redirect_type' => 'default',
'customer_redirect_path' => '/wp-admin',
'main_redirect_path' => '/wp-admin',
'redirect' => $redirect_to,
'form_id' => 'loginform',
'label_username' => __('Username or Email Address'),
'placeholder_username' => '',
'label_password' => __('Password'),
'placeholder_password' => '',
'label_remember' => __('Remember Me'),
'desc_remember' => __('Keep me logged in for two weeks.', 'wp-ultimo'),
'label_log_in' => __('Log In'),
'id_username' => 'user_login',
'id_password' => 'user_pass',
'id_remember' => 'rememberme',
'id_submit' => 'wp-submit',
'remember' => true,
'value_username' => '',
'value_remember' => false, // Set 'value_remember' to true to default the "Remember me" checkbox to checked.
);
} // end defaults;
/**
* Runs early on the request lifecycle as soon as we detect the shortcode is present.
*
* @since 2.0.0
* @return void
*/
public function setup() {
$this->logged = is_user_logged_in();
if ($this->is_reset_password_page()) {
$rp_path = '/';
$rp_cookie = 'wp-resetpass-' . COOKIEHASH;
if (isset($_GET['key']) && isset($_GET['login'])) {
$value = sprintf('%s:%s', wp_unslash($_GET['login']), wp_unslash($_GET['key']));
setcookie($rp_cookie, $value, 0, $rp_path, (string) COOKIE_DOMAIN, is_ssl(), true);
wp_safe_redirect(remove_query_arg(array('key', 'login')));
exit;
} // end if;
} // end if;
global $post;
/*
* Handles maintenance mode on Elementor.
*/
if ($post && $post->ID === absint(wu_get_setting('default_login_page', 0))) {
add_filter('elementor/maintenance_mode/is_login_page', '__return_true');
} // end if;
} // end setup;
/**
* Checks if we are in a lost password form page.
*
* @since 2.0.0
* @return boolean
*/
public function is_lost_password_page() {
return wu_request('action') === 'lostpassword';
} // end is_lost_password_page;
/**
* Checks if we are in the email confirm instruction page of a reset password.
*
* @since 2.0.0
* @return boolean
*/
public function is_check_email_confirm() {
return wu_request('checkemail') === 'confirm';
} // end is_check_email_confirm;
/**
* Checks if we are in a reset password page.
*
* @since 2.0.0
* @return boolean
*/
public function is_reset_password_page() {
return wu_request('action') === 'rp' || wu_request('action') === 'resetpass';
} // end is_reset_password_page;
/**
* Checks if we are in the the password rest confirmation page.
*
* @since 2.0.0
* @return boolean
*/
public function is_reset_confirmation_page() {
return wu_request('password-reset') === 'success';
} // end is_reset_confirmation_page;
/**
* Handle custom login redirection
*
* @since 2.0.11
*
* @param string $redirect_to The redirect destination URL.
* @param string $requested_redirect_to The requested redirect destination URL.
* @param /WP_User|/WP_Error $user The URL to redirect user.
* @return string
*/
public function handle_redirect($redirect_to, $requested_redirect_to, $user) {
if (is_wp_error($user)) {
if (wu_request('wu_login_page_url')) {
$redirect_to = wu_request('wu_login_page_url');
$redirect_to = add_query_arg('error', $user->get_error_code(), $redirect_to);
if ($user->get_error_code() === 'invalid_username') {
$redirect_to = add_query_arg('username', wu_request('log'), $redirect_to);
} // end if;
// In this case, WP will not redirect, so we need to do it here
wp_redirect($redirect_to);
exit;
} // end if;
return $redirect_to;
} // end if;
$redirect_type = wu_request('wu_login_form_redirect_type', 'default');
// If some condition match, force user redirection to the URL
if ($redirect_type === 'query_redirect') {
// query_redirect is the default wp behaviour
return $redirect_to;
} elseif ($redirect_type === 'customer_site') {
$user_site = get_active_blog_for_user( $user->ID );
wp_redirect($user_site->siteurl . $requested_redirect_to);
exit;
} elseif ($redirect_type === 'main_site') {
wp_redirect(network_site_url($requested_redirect_to));
exit;
} // end if;
return $redirect_to;
} // end handle_redirect;
/**
* Allows the setup in the context of previews.
*
* @since 2.0.0
* @return void
*/
public function setup_preview() {
$this->logged = false;
} // end setup_preview;
/**
* Returns the logout URL for the "not you bar".
*
* @since 2.0.0
* @return string
*/
public function get_logout_url() {
$redirect_to = wu_get_current_url();
return wp_logout_url($redirect_to);
} // end get_logout_url;
/**
* The content to be output on the screen.
*
* Should return HTML markup to be used to display the block.
* This method is shared between the block render method and
* the shortcode implementation.
*
* @since 2.0.0
*
* @param array $atts Parameters of the block/shortcode.
* @param string|null $content The content inside the shortcode.
* @return string
*/
public function output($atts, $content = null) {
$view = 'dashboard-widgets/login-additional-forms';
/*
* Checks if we are in the confirmation page.
*
* If that's the case, we show a successful message and the
* login URL so the user can re-login with the new password.
*/
if ($this->is_reset_confirmation_page()) {
$fields = array(
'email-activation-instructions' => array(
'type' => 'note',
'desc' => __('Your password has been reset.') . ' <a href="' . esc_url(wp_login_url()) . '">' . __('Log in') . '</a>',
),
);
/*
* Check if are in the email confirmation instructions page.
*
* If that's the case, we show the instructions.
*/
} elseif ($this->is_check_email_confirm()) {
$fields = array(
'email-activation-instructions' => array(
'type' => 'note',
'desc' => sprintf(
/* translators: %s: Link to the login page. */
__('Check your email for the confirmation link, then visit the <a href="%s">login page</a>.'),
wp_login_url()
),
),
);
/*
* Check if we are in the set new password page.
*
* If that's the case, we show the new password fields
* so the user can set a new password.
*/
} elseif ($this->is_reset_password_page()) {
$rp_cookie = 'wp-resetpass-' . COOKIEHASH;
if (isset($_COOKIE[$rp_cookie]) && 0 < strpos((string) $_COOKIE[$rp_cookie], ':')) {
list($rp_login, $rp_key) = explode(':', wp_unslash($_COOKIE[$rp_cookie]), 2);
$user = check_password_reset_key($rp_key, $rp_login);
if (isset($_POST['pass1']) && !hash_equals($rp_key, $_POST['rp_key'])) {
$user = false;
} // end if;
} else {
$user = false;
} // end if;
$redirect_to = add_query_arg('password-reset', 'success', remove_query_arg(array('action', 'error')));
$fields = array(
'pass1' => array(
'type' => 'password',
'title' => __('New password'),
'placeholder' => '',
'value' => '',
'html_attr' => array(
'size' => 24,
'autocapitalize' => 'off',
),
),
'pass2' => array(
'type' => 'password',
'title' => __('Confirm new password'),
'placeholder' => '',
'value' => '',
'html_attr' => array(
'size' => 24,
'autocapitalize' => 'off',
),
),
'lost-password-instructions' => array(
'type' => 'note',
'desc' => wp_get_password_hint(),
'tooltip' => '',
),
'action' => array(
'type' => 'hidden',
'value' => 'resetpass',
),
'rp_key' => array(
'type' => 'hidden',
'value' => $rp_key,
),
'user_login' => array(
'type' => 'hidden',
'value' => $rp_login,
),
'redirect_to' => array(
'type' => 'hidden',
'value' => $redirect_to,
),
'wp-submit' => array(
'type' => 'submit',
'title' => __('Save Password'),
'value' => __('Save Password'),
'classes' => 'button button-primary wu-w-full',
'wrapper_classes' => 'wu-items-end wu-bg-none',
),
);
/*
* Checks if we are in the first reset password page, where the customer requests a reset.
*
* If that's the case, we show the username/email field, so the user can
* get an email with the reset link.
*/
} elseif ($this->is_lost_password_page()) {
$user_login = wu_request('user_login', '');
if ($user_login) {
$user_login = wp_unslash($user_login);
} // end if;
$redirect_to = add_query_arg('checkemail', 'confirm', remove_query_arg(array('action', 'error')));
$fields = array(
'lost-password-instructions' => array(
'type' => 'note',
'desc' => __('Please enter your username or email address. You will receive an email message with instructions on how to reset your password.'),
'tooltip' => '',
),
'user_login' => array(
'type' => 'text',
'title' => __('Username or Email Address'),
'placeholder' => '',
'value' => $user_login,
'html_attr' => array(
'size' => 20,
'autocapitalize' => 'off',
),
),
'action' => array(
'type' => 'hidden',
'value' => 'lostpassword',
),
'redirect_to' => array(
'type' => 'hidden',
'value' => $redirect_to,
),
'wp-submit' => array(
'type' => 'submit',
'title' => __('Get New Password'),
'value' => __('Get New Password'),
'classes' => 'button button-primary wu-w-full',
'wrapper_classes' => 'wu-items-end wu-bg-none',
),
);
} else {
$view = 'dashboard-widgets/login-form';
$fields = array(
'log' => array(
'type' => 'text',
'title' => $atts['label_username'],
'placeholder' => $atts['placeholder_username'],
'tooltip' => '',
),
'pwd' => array(
'type' => 'password',
'title' => $atts['label_password'],
'placeholder' => $atts['placeholder_password'],
'tooltip' => '',
),
);
if ($atts['remember']) {
$fields['rememberme'] = array(
'type' => 'toggle',
'title' => $atts['label_remember'],
'desc' => $atts['desc_remember'],
);
} // end if;
$fields['redirect_to'] = array(
'type' => 'hidden',
'value' => $atts['redirect'],
);
if (isset($_GET['redirect_to'])) {
$atts['redirect_type'] = 'query_redirect';
$fields['redirect_to']['value'] = $_GET['redirect_to'];
} elseif ($atts['redirect_type'] === 'customer_site') {
$fields['redirect_to']['value'] = $atts['customer_redirect_path'];
} elseif ($atts['redirect_type'] === 'main_site') {
$fields['redirect_to']['value'] = $atts['main_redirect_path'];
} // end if;
$fields['wu_login_form_redirect_type'] = array(
'type' => 'hidden',
'value' => $atts['redirect_type'],
);
$fields['wp-submit'] = array(
'type' => 'submit',
'title' => $atts['label_log_in'],
'value' => $atts['label_log_in'],
'classes' => 'button button-primary wu-w-full',
'wrapper_classes' => 'wu-items-end wu-bg-none',
);
$fields['lost-password'] = array(
'type' => 'html',
'content' => sprintf('<a class="wu-text-xs wu-block wu-text-center wu--mt-4" href="%s">%s</a>', esc_url(add_query_arg('action', 'lostpassword')), __('Lost your password?')),
'classes' => '',
'wrapper_classes' => 'wu-items-end wu-bg-none',
);
} // end if;
/*
* Check for error messages
*
* If we have some, we add an additional field
* at the top of the fields array, to display the errors.
*/
if (wu_request('error')) {
$username = wu_request('username', '');
$error_message_field = array(
'error_message' => array(
'type' => 'note',
'desc' => Checkout_Pages::get_instance()->get_error_message(wu_request('error'), $username),
),
);
$fields = array_merge($error_message_field, $fields);
} // end if;
$fields['wu_login_page_url'] = array(
'type' => 'hidden',
'value' => wu_get_current_url(),
);
/**
* Instantiate the form for the order details.
*
* @since 2.0.0
*/
$form = new \WP_Ultimo\UI\Form($this->get_id(), $fields, array(
'action' => esc_url(site_url('wp-login.php', 'login_post')),
'wrap_in_form_tag' => true,
'views' => 'admin-pages/fields',
'classes' => 'wu-p-0 wu-m-0',
'field_wrapper_classes' => 'wu-box-border wu-items-center wu-flex wu-justify-between wu-py-4 wu-m-0',
'html_attr' => array(
'class' => 'wu-w-full',
),
));
$atts['logged'] = $this->logged;
$atts['login_url'] = $this->get_logout_url();
$atts['form'] = $form;
return wu_get_template_contents($view, $atts);
} // end output;
} // end class Login_Form_Element;

View File

@ -0,0 +1,493 @@
<?php
/**
* Adds the My_Sites_Element UI to the Admin Panel.
*
* @package WP_Ultimo
* @subpackage UI
* @since 2.0.0
*/
namespace WP_Ultimo\UI;
use WP_Ultimo\UI\Base_Element;
use WP_Ultimo\Models\Customer;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Adds the Checkout Element UI to the Admin Panel.
*
* @since 2.0.0
*/
class My_Sites_Element extends Base_Element {
use \WP_Ultimo\Traits\Singleton;
/**
* The id of the element.
*
* Something simple, without prefixes, like 'checkout', or 'pricing-tables'.
*
* This is used to construct shortcodes by prefixing the id with 'wu_'
* e.g. an id checkout becomes the shortcode 'wu_checkout' and
* to generate the Gutenberg block by prefixing it with 'wp-ultimo/'
* e.g. checkout would become the block 'wp-ultimo/checkout'.
*
* @since 2.0.0
* @var string
*/
public $id = 'my-sites';
/**
* Controls if this is a public element to be used in pages/shortcodes by user.
*
* @since 2.0.24
* @var boolean
*/
protected $public = true;
/**
* The current customer.
*
* @since 2.2.0
* @var Customer
*/
protected $customer;
/**
* The sites of the current customer.
*
* @since 2.2.0
* @var array
*/
protected $sites;
/**
* The icon of the UI element.
* e.g. return fa fa-search
*
* @since 2.0.0
* @param string $context One of the values: block, elementor or bb.
* @return string
*/
public function get_icon($context = 'block') {
if ($context === 'elementor') {
return 'eicon-info-circle-o';
} // end if;
return 'fa fa-search';
} // end get_icon;
/**
* The title of the UI element.
*
* This is used on the Blocks list of Gutenberg.
* You should return a string with the localized title.
* e.g. return __('My Element', 'wp-ultimo').
*
* @since 2.0.0
* @return string
*/
public function get_title() {
return __('My Sites', 'wp-ultimo');
} // end get_title;
/**
* The description of the UI element.
*
* This is also used on the Gutenberg block list
* to explain what this block is about.
* You should return a string with the localized title.
* e.g. return __('Adds a checkout form to the page', 'wp-ultimo').
*
* @since 2.0.0
* @return string
*/
public function get_description() {
return __('Adds a block to display the sites owned by the current customer.', 'wp-ultimo');
} // end get_description;
/**
* The list of fields to be added to Gutenberg.
*
* If you plan to add Gutenberg controls to this block,
* you'll need to return an array of fields, following
* our fields interface (@see inc/ui/class-field.php).
*
* You can create new Gutenberg panels by adding fields
* with the type 'header'. See the Checkout Elements for reference.
*
* @see inc/ui/class-checkout-element.php
*
* Return an empty array if you don't have controls to add.
*
* @since 2.0.0
* @return array
*/
public function fields() {
$fields = array();
$fields['header'] = array(
'title' => __('General', 'wp-ultimo'),
'desc' => __('General', 'wp-ultimo'),
'type' => 'header',
);
$fields['site_manage_type'] = array(
'type' => 'select',
'title' => __('Site Manage Type', 'wp-ultimo'),
'desc' => __('The page to manage a site.', 'wp-ultimo'),
'tooltip' => '',
'default' => 'default',
'options' => array(
'default' => __('Same Page', 'wp-ultimo'),
'wp_admin' => __('WP Admin', 'wp-ultimo'),
'custom_page' => __('Custom Page', 'wp-ultimo'),
),
);
$fields['site_show'] = array(
'type' => 'select',
'title' => __('Which sites to show?', 'wp-ultimo'),
'desc' => __('Select which sites should be listed for user.', 'wp-ultimo'),
'tooltip' => '',
'default' => 'all',
'options' => array(
'all' => __('All', 'wp-ultimo'),
'owned' => __('Owned', 'wp-ultimo'),
),
);
$pages = get_pages(array(
'exclude' => array(get_the_ID()),
));
$pages = $pages ? $pages : array();
$pages_list = array(0 => __('Current Page', 'wp-ultimo'));
foreach ($pages as $page) {
$pages_list[$page->ID] = $page->post_title;
} // end foreach;
$fields['custom_manage_page'] = array(
'type' => 'select',
'title' => __('Manage Redirect Page', 'wp-ultimo'),
'value' => 0,
'desc' => __('The page to redirect user after select a site.', 'wp-ultimo'),
'tooltip' => '',
'required' => array(
'site_manage_type' => 'custom_page',
),
'options' => $pages_list,
);
$fields['columns'] = array(
'type' => 'number',
'title' => __('Columns', 'wp-ultimo'),
'desc' => __('How many columns to use.', 'wp-ultimo'),
'tooltip' => '',
'value' => 4,
'min' => 1,
'max' => 5,
);
$fields['display_images'] = array(
'type' => 'toggle',
'title' => __('Display Site Screenshot?', 'wp-ultimo'),
'desc' => __('Toggle to show/hide the site screenshots on the element.', 'wp-ultimo'),
'tooltip' => '',
'value' => 1,
);
return $fields;
} // end fields;
/**
* The list of keywords for this element.
*
* Return an array of strings with keywords describing this
* element. Gutenberg uses this to help customers find blocks.
*
* e.g.:
* return array(
* 'WP Ultimo',
* 'Site',
* 'Form',
* 'Cart',
* );
*
* @since 2.0.0
* @return array
*/
public function keywords() {
return array(
'WP Ultimo',
'Site',
'Form',
'Cart',
);
} // end keywords;
/**
* List of default parameters for the element.
*
* If you are planning to add controls using the fields,
* it might be a good idea to use this method to set defaults
* for the parameters you are expecting.
*
* These defaults will be used inside a 'wp_parse_args' call
* before passing the parameters down to the block render
* function and the shortcode render function.
*
* @since 2.0.0
* @return array
*/
public function defaults() {
return array(
'columns' => 4,
'display_images' => 1,
'site_manage_type' => 'default',
'custom_manage_page' => 0,
'site_show' => 'owned',
);
} // end defaults;
/**
* Loads the necessary scripts and styles for this element.
*
* @since 2.0.0
* @return void
*/
public function register_scripts() {
wp_enqueue_style('wu-admin');
} // end register_scripts;
/**
* Runs early on the request lifecycle as soon as we detect the shortcode is present.
*
* @since 2.0.0
* @return void
*/
public function setup() {
global $wpdb;
if (!is_user_logged_in() || WP_Ultimo()->currents->is_site_set_via_request()) {
$this->set_display(false);
return;
} // end if;
$this->customer = WP_Ultimo()->currents->get_customer();
} // end setup;
/**
* Allows the setup in the context of previews.
*
* @since 2.0.0
* @return void
*/
public function setup_preview() {
$this->customer = wu_mock_customer();
$this->sites = array(
wu_mock_site(1),
wu_mock_site(2),
);
} // end setup_preview;
/**
* The content to be output on the screen.
*
* Should return HTML markup to be used to display the block.
* This method is shared between the block render method and
* the shortcode implementation.
*
* @since 2.0.0
*
* @param array $atts Parameters of the block/shortcode.
* @param string|null $content The content inside the shortcode.
* @return string
*/
public function output($atts, $content = null) {
$atts['customer'] = $this->customer;
$atts['sites'] = $this->get_sites(wu_get_isset($atts, 'site_show'));
return wu_get_template_contents('dashboard-widgets/my-sites', $atts);
} // end output;
/**
* Get sites to display on widget
*
* @param string $show The kind of output expected, i.e: all, owned.
* @return array $sites The list of sites do display.
*/
protected function get_sites(?string $show = null): array {
if (!empty($this->sites)) {
return $this->sites;
}
$this->sites = apply_filters('wp_ultimo_pre_my_sites_sites', array(), $show);
if (!empty($this->sites)) {
return $this->sites;
}
if (!empty($this->customer)) {
$pending_sites = \WP_Ultimo\Models\Site::get_all_by_type('pending', array('customer_id' => $this->customer->get_id()));
$customer_sites = array_reduce(
$this->customer->get_sites(),
function ($customer_sites, $site) {
$customer_sites[$site->get_id()] = $site;
return $customer_sites;
}
);
}
if ($show === 'all') {
$wp_user_sites = get_blogs_of_user(get_current_user_id());
$user_sites = array_reduce($wp_user_sites, function($user_sites, $wp_site) use ($customer_sites) {
if (!array_key_exists($wp_site->userblog_id, $customer_sites ?? array()) && $wp_site->userblog_id !== get_main_site_id()) {
$wu_site = wu_get_site($wp_site->userblog_id);
$wu_site->set_membership_id(0);
$user_sites[$wp_site->userblog_id] = $wu_site;
}
return $user_sites;
});
}
$sites = array_merge(
$pending_sites ?? array(),
$customer_sites ?? array(),
$user_sites ?? array(),
);
$this->sites = apply_filters('wp_ultimo_after_my_sites_sites', $sites, $show);
return $this->sites;
} // end get_sites;
/**
* Returns the manage URL for sites, depending on the environment.
*
* @since 2.0.0
*
* @param int $site_id A Site ID.
* @param string $type De redirection type (can be: default, wp_admin or custom_page).
* @param string $custom_page_id The path to redirect ir using custom_page type.
* @return string
*/
public function get_manage_url($site_id, $type = 'default', $custom_page_id = 0) {
if ($type === 'wp_admin') {
return get_admin_url($site_id);
} // end if;
if ($type === 'custom_page') {
$custom_page = get_page_link($custom_page_id);
$url_param = \WP_Ultimo\Current::param_key('site');
$site_hash = \WP_Ultimo\Helpers\Hash::encode($site_id, 'site');
return add_query_arg(array(
$url_param => $site_hash,
), $custom_page);
} // end if;
return \WP_Ultimo\Current::get_manage_url($site_id, 'site');
} // end get_manage_url;
/**
* Returns the new site URL for site creation.
*
* @since 2.0.21
*
* @return string
*/
public function get_new_site_url() {
$membership = WP_Ultimo()->currents->get_membership();
$checkout_pages = \WP_Ultimo\Checkout\Checkout_Pages::get_instance();
$url = $checkout_pages->get_page_url('new_site');
if ($membership) {
if ($url) {
return add_query_arg(array(
'membership' => $membership->get_hash(),
), $url);
} // end if;
if (is_main_site()) {
$sites = $membership->get_sites(false);
if (!empty($sites)) {
return add_query_arg(array(
'page' => 'add-new-site',
), get_admin_url($sites[0]->get_id()));
} // end if;
return '';
} // end if;
} // end if;
return admin_url('admin.php?page=add-new-site');
} // end get_new_site_url;
} // end class My_Sites_Element;

View File

@ -0,0 +1,208 @@
<?php
/**
* Adds the Payment_Methods_Element UI to the Admin Panel.
*
* @package WP_Ultimo
* @subpackage UI
* @since 2.0.0
*/
namespace WP_Ultimo\UI;
use WP_Ultimo\UI\Base_Element;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Adds the Checkout Element UI to the Admin Panel.
*
* @since 2.0.0
*/
class Payment_Methods_Element extends Base_Element {
use \WP_Ultimo\Traits\Singleton;
/**
* The id of the element.
*
* Something simple, without prefixes, like 'checkout', or 'pricing-tables'.
*
* This is used to construct shortcodes by prefixing the id with 'wu_'
* e.g. an id checkout becomes the shortcode 'wu_checkout' and
* to generate the Gutenberg block by prefixing it with 'wp-ultimo/'
* e.g. checkout would become the block 'wp-ultimo/checkout'.
*
* @since 2.0.0
* @var string
*/
public $id = 'payment-methods';
/**
* The icon of the UI element.
* e.g. return fa fa-search
*
* @since 2.0.0
* @param string $context One of the values: block, elementor or bb.
* @return string
*/
public function get_icon($context = 'block') {
if ($context === 'elementor') {
return 'eicon-info-circle-o';
} // end if;
return 'fa fa-search';
} // end get_icon;
/**
* The title of the UI element.
*
* This is used on the Blocks list of Gutenberg.
* You should return a string with the localized title.
* e.g. return __('My Element', 'wp-ultimo').
*
* @since 2.0.0
* @return string
*/
public function get_title() {
return __('Payment Methods', 'wp-ultimo');
} // end get_title;
/**
* The description of the UI element.
*
* This is also used on the Gutenberg block list
* to explain what this block is about.
* You should return a string with the localized title.
* e.g. return __('Adds a checkout form to the page', 'wp-ultimo').
*
* @since 2.0.0
* @return string
*/
public function get_description() {
return __('Adds a checkout form block to the page.', 'wp-ultimo');
} // end get_description;
/**
* The list of fields to be added to Gutenberg.
*
* If you plan to add Gutenberg controls to this block,
* you'll need to return an array of fields, following
* our fields interface (@see inc/ui/class-field.php).
*
* You can create new Gutenberg panels by adding fields
* with the type 'header'. See the Checkout Elements for reference.
*
* @see inc/ui/class-checkout-element.php
*
* Return an empty array if you don't have controls to add.
*
* @since 2.0.0
* @return array
*/
public function fields() {
$fields = array();
$fields['header'] = array(
'title' => __('General', 'wp-ultimo'),
'desc' => __('General', 'wp-ultimo'),
'type' => 'header',
);
$fields['password_strength'] = array(
'type' => 'toggle',
'title' => __('Password Strength Meter', 'wp-ultimo'),
'desc' => __('Set this customer as a VIP.', 'wp-ultimo'),
'tooltip' => '',
'value' => 1,
);
$fields['apply_styles'] = array(
'type' => 'toggle',
'title' => __('Apply Styles', 'wp-ultimo'),
'desc' => __('Set this customer as a VIP.', 'wp-ultimo'),
'tooltip' => '',
'value' => 1,
);
return $fields;
} // end fields;
/**
* The list of keywords for this element.
*
* Return an array of strings with keywords describing this
* element. Gutenberg uses this to help customers find blocks.
*
* e.g.:
* return array(
* 'WP Ultimo',
* 'Payment Methods',
* 'Form',
* 'Cart',
* );
*
* @since 2.0.0
* @return array
*/
public function keywords() {
return array(
'WP Ultimo',
'Payment Methods',
'Form',
'Cart',
);
} // end keywords;
/**
* List of default parameters for the element.
*
* If you are planning to add controls using the fields,
* it might be a good idea to use this method to set defaults
* for the parameters you are expecting.
*
* These defaults will be used inside a 'wp_parse_args' call
* before passing the parameters down to the block render
* function and the shortcode render function.
*
* @since 2.0.0
* @return array
*/
public function defaults() {
return array();
} // end defaults;
/**
* The content to be output on the screen.
*
* Should return HTML markup to be used to display the block.
* This method is shared between the block render method and
* the shortcode implementation.
*
* @since 2.0.0
*
* @param array $atts Parameters of the block/shortcode.
* @param string|null $content The content inside the shortcode.
* @return string
*/
public function output($atts, $content = null) {
return 'lol';
} // end output;
} // end class Payment_Methods_Element;

View File

@ -0,0 +1,234 @@
<?php
/**
* Adds the Simple Text Element UI to the Admin Panel.
*
* @package WP_Ultimo
* @subpackage UI
* @since 2.0.0
*/
namespace WP_Ultimo\UI;
use WP_Ultimo\UI\Base_Element;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Adds the Checkout Element UI to the Admin Panel.
*
* @since 2.0.0
*/
class Simple_Text_Element extends Base_Element {
use \WP_Ultimo\Traits\Singleton;
/**
* The id of the element.
*
* Something simple, without prefixes, like 'checkout', or 'pricing-tables'.
*
* This is used to construct shortcodes by prefixing the id with 'wu_'
* e.g. an id checkout becomes the shortcode 'wu_checkout' and
* to generate the Gutenberg block by prefixing it with 'wp-ultimo/'
* e.g. checkout would become the block 'wp-ultimo/checkout'.
*
* @since 2.0.0
* @var string
*/
public $id = 'simple-text';
/**
* Controls if this is a public element to be used in pages/shortcodes by user.
*
* @since 2.0.24
* @var boolean
*/
protected $public = true;
/**
* Should this element be hidden by default?
*
* @since 2.0.0
* @var boolean
*/
protected $hidden_by_default = true;
/**
* The icon of the UI element.
* e.g. return fa fa-search
*
* @since 2.0.0
* @param string $context One of the values: block, elementor or bb.
* @return string
*/
public function get_icon($context = 'block') {
if ($context === 'elementor') {
return 'eicon-lock-user';
} // end if;
return 'fa fa-search';
} // end get_icon;
/**
* The title of the UI element.
*
* This is used on the Blocks list of Gutenberg.
* You should return a string with the localized title.
* e.g. return __('My Element', 'wp-ultimo').
*
* @since 2.0.0
* @return string
*/
public function get_title() {
return __('Simple Text', 'wp-ultimo');
} // end get_title;
/**
* The description of the UI element.
*
* This is also used on the Gutenberg block list
* to explain what this block is about.
* You should return a string with the localized title.
* e.g. return __('Adds a checkout form to the page', 'wp-ultimo').
*
* @since 2.0.0
* @return string
*/
public function get_description() {
return __('Adds a simple text block to the page.', 'wp-ultimo');
} // end get_description;
/**
* The list of fields to be added to Gutenberg.
*
* If you plan to add Gutenberg controls to this block,
* you'll need to return an array of fields, following
* our fields interface (@see inc/ui/class-field.php).
*
* You can create new Gutenberg panels by adding fields
* with the type 'header'. See the Checkout Elements for reference.
*
* @see inc/ui/class-checkout-element.php
*
* Return an empty array if you don't have controls to add.
*
* @since 2.0.0
* @return array
*/
public function fields() {
$fields = array();
$fields['header'] = array(
'title' => __('General', 'wp-ultimo'),
'desc' => __('General', 'wp-ultimo'),
'type' => 'header',
);
$fields['simple_text'] = array(
'type' => 'textarea',
'title' => __('Content', 'wp-ultimo'),
'placeholder' => __('E.g. Text, HTML or shortcode.', 'wp-ultimo'),
'desc' => __('You can insert plain text, HTML or a shortcode in this block.', 'wp-ultimo'),
'tooltip' => '',
'html_attr' => array(
'rows' => 6,
)
);
return $fields;
} // end fields;
/**
* Registers scripts and styles necessary to render this.
*
* @since 2.0.0
* @return void
*/
public function register_scripts() {
wp_enqueue_style('wu-admin');
} // end register_scripts;
/**
* The list of keywords for this element.
*
* Return an array of strings with keywords describing this
* element. Gutenberg uses this to help customers find blocks.
*
* e.g.:
* return array(
* 'WP Ultimo',
* 'Billing_Address',
* 'Form',
* 'Cart',
* );
*
* @since 2.0.0
* @return array
*/
public function keywords() {
return array(
'WP Ultimo',
'text',
'simple text',
'shortcode',
'textarea'
);
} // end keywords;
/**
* List of default parameters for the element.
*
* If you are planning to add controls using the fields,
* it might be a good idea to use this method to set defaults
* for the parameters you are expecting.
*
* These defaults will be used inside a 'wp_parse_args' call
* before passing the parameters down to the block render
* function and the shortcode render function.
*
* @since 2.0.0
* @return array
*/
public function defaults() {
return array(
'simple_text' => __('Text, HTML or shortcode.', 'wp-ultimo'),
);
} // end defaults;
/**
* The content to be output on the screen.
*
* Should return HTML markup to be used to display the block.
* This method is shared between the block render method and
* the shortcode implementation.
*
* @since 2.0.0
*
* @param array $atts Parameters of the block/shortcode.
* @param string|null $content The content inside the shortcode.
* @return string
*/
public function output($atts, $content = null) {
return wu_get_template_contents('dashboard-widgets/simple-text', $atts);
} // end output;
} // end class Simple_Text_Element;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,311 @@
<?php
/**
* Adds the Site Maintenance Element UI to the Admin Panel.
*
* @package WP_Ultimo
* @subpackage UI
* @since 2.0.0
*/
namespace WP_Ultimo\UI;
use WP_Ultimo\UI\Base_Element;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Adds the Checkout Element UI to the Admin Panel.
*
* @since 2.0.0
*/
class Site_Maintenance_Element extends Base_Element {
use \WP_Ultimo\Traits\Singleton;
/**
* The id of the element.
*
* Something simple, without prefixes, like 'checkout', or 'pricing-tables'.
*
* This is used to construct shortcodes by prefixing the id with 'wu_'
* e.g. an id checkout becomes the shortcode 'wu_checkout' and
* to generate the Gutenberg block by prefixing it with 'wp-ultimo/'
* e.g. checkout would become the block 'wp-ultimo/checkout'.
*
* @since 2.0.0
* @var string
*/
public $id = 'site-maintenance';
/**
* Controls if this is a public element to be used in pages/shortcodes by user.
*
* @since 2.0.24
* @var boolean
*/
protected $public = true;
/**
* Initializes the singleton.
*
* @since 2.0.0
* @return void
*/
public function init() {
if (wu_get_setting('maintenance_mode')) {
parent::init();
} // end if;
} // end init;
/**
* The icon of the UI element.
* e.g. return fa fa-search
*
* @since 2.0.0
* @param string $context One of the values: block, elementor or bb.
* @return string
*/
public function get_icon($context = 'block') {
if ($context === 'elementor') {
return 'eicon-lock-user';
} // end if;
return 'fa fa-search';
} // end get_icon;
/**
* The title of the UI element.
*
* This is used on the Blocks list of Gutenberg.
* You should return a string with the localized title.
* e.g. return __('My Element', 'wp-ultimo').
*
* @since 2.0.0
* @return string
*/
public function get_title() {
return __('Site Maintenance', 'wp-ultimo');
} // end get_title;
/**
* The description of the UI element.
*
* This is also used on the Gutenberg block list
* to explain what this block is about.
* You should return a string with the localized title.
* e.g. return __('Adds a checkout form to the page', 'wp-ultimo').
*
* @since 2.0.0
* @return string
*/
public function get_description() {
return __('Adds the toggle control to turn maintenance mode on.', 'wp-ultimo');
} // end get_description;
/**
* The list of fields to be added to Gutenberg.
*
* If you plan to add Gutenberg controls to this block,
* you'll need to return an array of fields, following
* our fields interface (@see inc/ui/class-field.php).
*
* You can create new Gutenberg panels by adding fields
* with the type 'header'. See the Checkout Elements for reference.
*
* @see inc/ui/class-checkout-element.php
*
* Return an empty array if you don't have controls to add.
*
* @since 2.0.0
* @return array
*/
public function fields() {
$fields = array();
$fields['header'] = array(
'title' => __('General', 'wp-ultimo'),
'desc' => __('General', 'wp-ultimo'),
'type' => 'header',
);
$fields['title'] = array(
'type' => 'text',
'title' => __('Label', 'wp-ultimo'),
'value' => __('Toggle Maintenance Mode', 'wp-ultimo'),
'placeholder' => __('e.g. Toggle Maintenance Mode', 'wp-ultimo'),
'tooltip' => '',
);
$fields['desc'] = array(
'type' => 'textarea',
'title' => __('Description', 'wp-ultimo'),
'value' => __('Put your site on maintenance mode. When activated, the front-end will only be accessible to logged users.', 'wp-ultimo'),
'tooltip' => '',
);
return $fields;
} // end fields;
/**
* The list of keywords for this element.
*
* Return an array of strings with keywords describing this
* element. Gutenberg uses this to help customers find blocks.
*
* e.g.:
* return array(
* 'WP Ultimo',
* 'Billing_Address',
* 'Form',
* 'Cart',
* );
*
* @since 2.0.0
* @return array
*/
public function keywords() {
return array(
'WP Ultimo',
'Login',
'Reset Password',
);
} // end keywords;
/**
* List of default parameters for the element.
*
* If you are planning to add controls using the fields,
* it might be a good idea to use this method to set defaults
* for the parameters you are expecting.
*
* These defaults will be used inside a 'wp_parse_args' call
* before passing the parameters down to the block render
* function and the shortcode render function.
*
* @since 2.0.0
* @return array
*/
public function defaults() {
return array(
'title' => __('Toggle Maintenance Mode', 'wp-ultimo'),
'desc' => __('Put your site on maintenance mode. When activated, the front-end will only be accessible to logged users.', 'wp-ultimo'),
);
} // end defaults;
/**
* Runs early on the request lifecycle as soon as we detect the shortcode is present.
*
* @since 2.0.0
* @return void
*/
public function setup() {
$this->site = WP_Ultimo()->currents->get_site();
if (!$this->site || !$this->site->is_customer_allowed()) {
$this->set_display(false);
return;
} // end if;
} // end setup;
/**
* Allows the setup in the context of previews.
*
* @since 2.0.0
* @return void
*/
public function setup_preview() {
$this->site = wu_mock_site();
} // end setup_preview;
/**
* Registers scripts and styles necessary to render this.
*
* @since 2.0.0
* @return void
*/
public function register_scripts() {
wp_register_script('wu-site-maintenance', wu_get_asset('site-maintenance.js', 'js'), array('jquery', 'wu-functions'), wu_get_version());
wp_localize_script('wu-site-maintenance', 'wu_site_maintenance', array(
'nonce' => wp_create_nonce('wu_toggle_maintenance_mode'),
'ajaxurl' => wu_ajax_url(),
));
wp_enqueue_script('wu-site-maintenance');
} // end register_scripts;
/**
* The content to be output on the screen.
*
* Should return HTML markup to be used to display the block.
* This method is shared between the block render method and
* the shortcode implementation.
*
* @since 2.0.0
*
* @param array $atts Parameters of the block/shortcode.
* @param string|null $content The content inside the shortcode.
* @return string
*/
public function output($atts, $content = null) {
$fields = array(
'maintenance_mode' => array(
'type' => 'toggle',
'title' => $atts['title'],
'desc' => $atts['desc'],
'value' => wu_string_to_bool($this->site->get_meta('wu_maintenance_mode')),
),
'site_hash' => array(
'type' => 'hidden',
'value' => $this->site->get_hash(),
),
);
/**
* Instantiate the form for the order details.
*
* @since 2.0.0
*/
$form = new \WP_Ultimo\UI\Form('maintenance-mode', $fields, array(
'views' => 'admin-pages/fields',
'classes' => 'wu-widget-list wu-striped wu-modal-form wu-widget-list wu-striped wu-m-0 wu-mt-0 wu-list-none wu-p-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' => array(),
));
$atts['form'] = $form;
return wu_get_template_contents('dashboard-widgets/site-maintenance', $atts);
} // end output;
} // end class Site_Maintenance_Element;

View File

@ -0,0 +1,459 @@
<?php
/**
* Adds the Template Previewer code.
*
* @package WP_Ultimo
* @subpackage UI
* @since 2.0.0
*/
namespace WP_Ultimo\UI;
use WP_Ultimo\Database\Sites\Site_Type;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Adds the Template_Previewer UI to the Admin Panel.
*
* @since 2.0.0
*/
class Template_Previewer {
use \WP_Ultimo\Traits\Singleton;
/**
* Keeps a list of the available templates for the products selected.
*
* @since 2.0.11
* @var null|array
*/
protected $available_templates = null;
/**
* Keeps the settings key for the top-bar.
*/
const KEY = 'top_bar_settings';
/**
* Initializes the class.
*
* @since 2.0.0
* @return void
*/
public function init() {
add_action('wp_ultimo_load', array($this, 'hooks'));
} // end init;
/**
* Hooks into WordPress to add the template preview.
*
* @since 2.0.0
* @return void
*/
public function hooks() {
if ($this->is_preview()) {
/*
* Remove admin bar from logged users.
*/
add_filter('show_admin_bar', '__return_false');
add_filter('wu_is_jumper_enabled', '__return_false');
add_filter('wu_is_toolbox_enabled', '__return_false');
add_filter('home_url', array($this, 'append_preview_parameter'), 9999, 4);
add_action('send_headers', array($this, 'send_cross_origin_headers'), 1000);
return;
} // end if;
if ($this->is_template_previewer()) {
add_action('init', array($this, 'template_previewer'));
add_action('wp_enqueue_scripts', array($this, 'register_scripts'));
add_action('wp_print_styles', array($this, 'remove_unnecessary_styles'), 0);
/**
* Runs when inside the template previewer context.
*
* @since 2.0.4
* @param self $template_previewer Instance of the current class.
*/
do_action('wu_template_previewer', $this);
} // end if;
} // end hooks;
/**
* Send the cross origin headers to allow iframes to be loaded.
*
* @since 2.0.9
* @return void
*/
public function send_cross_origin_headers() {
global $current_site;
$_SERVER['HTTP_ORIGIN'] = set_url_scheme("http://{$current_site->domain}");
send_origin_headers();
header_remove('X-Frame-Options');
} // end send_cross_origin_headers;
/**
* Register the necessary scripts.
*
* @since 2.0.0
* @return void
*/
public function register_scripts() {
global $current_site;
$settings = $this->get_settings();
$bg_color = wu_color($settings['bg_color']);
$button_bg_color = wu_color($settings['button_bg_color']);
$button_bg_darker = wu_color($button_bg_color->darken(4));
wp_register_script('wu-template-previewer', wu_get_asset('template-previewer.js', 'js'), array(), wu_get_version());
wp_localize_script('wu-template-previewer', 'wu_template_previewer', array(
'domain' => str_replace('www.', '', (string) $current_site->domain),
'current_template' => wu_request($this->get_preview_parameter(), false),
'current_url' => wu_get_current_url(),
'query_parameter' => $this->get_preview_parameter(),
));
wp_enqueue_script('wu-template-previewer');
wp_enqueue_style('wu-template-previewer', wu_get_asset('template-previewer.css', 'css'), array(), wu_get_version());
wp_add_inline_style('wu-template-previewer', wu_get_template_contents('dynamic-styles/template-previewer', array(
'bg_color' => $bg_color,
'button_bg_color' => $button_bg_color,
)));
wp_enqueue_style('dashicons');
} // end register_scripts;
/**
* Remove the unnecessary styles added by themes and other plugins.
*
* @since 2.0.0
* @return void
*/
public function remove_unnecessary_styles() {
global $wp_styles;
$wp_styles->queue = array(
'wu-admin',
'wu-template-previewer',
'dashicons',
);
} // end remove_unnecessary_styles;
/**
* Append preview parameter.
*
* @since 2.0.0
*
* @param string $url The URL.
* @param string $path Path relative to the home URL. Blank string if no path is specified.
* @param string|null $orig_scheme Scheme to give the home URL context. Accepts 'http', 'https',
* 'relative', 'rest', or null.
* @param int|null $blog_id Site ID, or null for the current site.
* @return string
*/
public function append_preview_parameter($url, $path, $orig_scheme, $blog_id) {
$allowed_schemes = array(
null,
'http',
'https',
);
if (in_array($orig_scheme, $allowed_schemes, true) === false) {
return $url;
} // end if;
if (apply_filters('wu_append_preview_parameter', true, $this) === false) {
return $url;
} // end if;
return add_query_arg('wu-preview', 1, $url);
} // end append_preview_parameter;
/**
* Returns the preview URL for the template previewer.
*
* @since 2.0.0
*
* @param int $site_id The ID of the template site.
* @return string
*/
public function get_preview_url($site_id) {
$args = array(
$this->get_preview_parameter() => $site_id,
);
if (wu_request('open')) {
$args['open'] = 1;
} // end if;
return add_query_arg($args, home_url());
} // end get_preview_url;
/**
* Template Previewer code
*
* @since 1.5.5
* @return void
*/
public function template_previewer() {
global $current_site;
$template_value = wu_request($this->get_preview_parameter(), false);
$selected_template = wu_get_site($template_value);
/**
* Check if this is a site template
*/
if ($selected_template->get_type() !== Site_Type::SITE_TEMPLATE && !wu_request('customizer')) {
wp_die(__('This template is not available', 'wp-ultimo'));
} // end if;
$categories = array();
$settings = $this->get_settings();
$render_parameters = array(
'current_site' => $current_site,
'categories' => $categories,
'selected_template' => $selected_template,
'tp' => $this,
);
$products_ids = isset($_COOKIE['wu_selected_products']) ? explode( ',', (string) $_COOKIE['wu_selected_products']) : array();
$products = array_map('wu_get_product', $products_ids);
// clear array
$products = array_filter($products);
if (!empty($products)) {
$limits = new \WP_Ultimo\Objects\Limitations();
list($plan, $additional_products) = wu_segregate_products($products);
$products = array_merge(array($plan), $additional_products);
foreach ($products as $product) {
$limits = $limits->merge($product->get_limitations());
} // end foreach;
if ($limits->site_templates->get_mode() !== 'default') {
$site_ids = $limits->site_templates->get_available_site_templates();
$render_parameters['templates'] = array_map('wu_get_site', $site_ids);
/**
* Check if the current site is a member of
* the list of available templates
*/
if (!in_array($selected_template->get_id(), $site_ids, true)) {
$redirect_to = wu_get_current_url();
$redirect_to = add_query_arg($this->get_preview_parameter(), current($site_ids), $redirect_to);
wp_redirect($redirect_to);
exit;
} // end if;
} // end if;
} // end if;
if (!isset($render_parameters['templates'])) {
$render_parameters['templates'] = wu_get_site_templates();
} // end if;
$render_parameters['templates'] = array_filter((array) $render_parameters['templates'], fn($site) => $site->is_active());
$render_parameters = array_merge($render_parameters, $settings);
wu_get_template('ui/template-previewer', $render_parameters);
exit;
} // end template_previewer;
/**
* Returns the preview parameter, so admins can change it.
*
* @since 2.0.0
* @return string
*/
public function get_preview_parameter() {
$slug = $this->get_setting('preview_url_parameter', 'template-preview');
return apply_filters('wu_get_template_preview_slug', $slug);
} // end get_preview_parameter;
/**
* Checks if this is a template previewer window.
*
* @since 2.0.0
* @return boolean
*/
public function is_template_previewer() {
$slug = $this->get_preview_parameter();
return wu_request($slug);
} // end is_template_previewer;
/**
* Check if the frame is a preview.
*
* @since 2.0.0
* @return boolean
*/
public function is_preview() {
return !empty(wu_request('wu-preview'));
} // end is_preview;
/**
* Returns the settings.
*
* @since 2.0.0
* @return array
*/
public function get_settings() {
// Fix to issue on wp_get_attachment_url() inside core.
// @todo report it.
$GLOBALS['pagenow'] = '';
$default_settings = array(
'bg_color' => '#f9f9f9',
'button_bg_color' => '#00a1ff',
'logo_url' => wu_get_network_logo(),
'button_text' => __('Use this Template', 'wp-ultimo'),
'preview_url_parameter' => 'template-preview',
'display_responsive_controls' => true,
'use_custom_logo' => false,
'custom_logo' => false,
'enabled' => true,
);
$saved_settings = wu_get_option(Template_Previewer::KEY, array());
$default_settings = array_merge($default_settings, $saved_settings);
$server_request = $_REQUEST;
// Ensure that templates key does not change with request
if (isset($server_request['templates'])) {
unset($server_request['templates']);
} // end if;
$parsed_args = wp_parse_args($server_request, $default_settings);
$parsed_args['display_responsive_controls'] = wu_string_to_bool($parsed_args['display_responsive_controls']);
$parsed_args['use_custom_logo'] = wu_string_to_bool($parsed_args['use_custom_logo']);
return $parsed_args;
} // end get_settings;
/**
* Gets a particular setting.
*
* @since 2.0.0
*
* @param string $setting The setting key.
* @param mixed $default Default value, if it is not found.
* @return mixed
*/
public function get_setting($setting, $default = false) {
return wu_get_isset($this->get_settings(), $setting, $default);
} // end get_setting;
/**
* Save settings.
*
* @since 2.0.0
*
* @param array $settings_to_save List of settings to save.
* @return boolean
*/
public function save_settings($settings_to_save) {
$settings = $this->get_settings();
foreach ($settings as $setting => $value) {
if ($setting === 'logo_url') {
$settings['logo_url'] = wu_get_network_logo();
continue;
} // end if;
$settings[$setting] = wu_get_isset($settings_to_save, $setting, false);
} // end foreach;
return wu_save_option(Template_Previewer::KEY, $settings);
} // end save_settings;
} // end class Template_Previewer;

View File

@ -0,0 +1,464 @@
<?php
/**
* Adds the Template Selection UI to the Admin Panel.
*
* @package WP_Ultimo
* @subpackage UI
* @since 2.0.0
*/
namespace WP_Ultimo\UI;
use \WP_Ultimo\UI\Base_Element;
use \WP_Ultimo\Managers\Field_Templates_Manager;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Adds the Template Selection Element UI to the Admin Panel.
*
* @since 2.0.0
*/
class Template_Switching_Element extends Base_Element {
use \WP_Ultimo\Traits\Singleton;
/**
* The id of the element.
*
* @since 2.0.0
*
* @var string
*/
public $id = 'template-switching';
/**
* The current site element.
*
* @since 2.0.18
*
* @var string
*/
protected $site;
/**
* The membership object.
*
* @since 2.2.0
* @var \WP_Ultimo\Membership
*/
protected $membership;
/**
* The list of products associated with the current membership.
*
* @since 2.2.0
* @var array
*/
protected $products;
/**
* The icon of the UI element.
* e.g. return fa fa-search
*
* @since 2.0.0
* @param string $context One of the values: block, elementor or bb.
*/
public function get_icon($context = 'block'): string {
if ($context === 'elementor') {
return 'eicon-cart-medium';
} // end if;
return 'fa fa-search';
} // end get_icon;
/**
* The title of the UI element.
*
* @since 2.0.0
* @return string
*/
public function get_title() {
return __('Template Switching', 'wp-ultimo');
} // end get_title;
/**
* The description of the UI element.
*
* @since 2.0.0
* @return string
*/
public function get_description() {
return __('Adds the template switching form to this page.', 'wp-ultimo');
} // end get_description;
/**
* Initializes the singleton.
*
* @since 2.0.0
* @return void
*/
public function init() {
add_action('wu_ajax_wu_switch_template', array($this, 'switch_template'));
parent::init();
} // end init;
/**
* Register element scripts.
*
* @since 2.0.4
*
* @return void
*/
public function register_scripts() {
wp_register_script('wu-template-switching', wu_get_asset('template-switching.js', 'js'), array('jquery', 'wu-vue-apps', 'wu-selectizer', 'wp-hooks', 'wu-cookie-helpers'));
wp_localize_script('wu-template-switching', 'wu_template_switching_params', array(
'ajaxurl' => wu_ajax_url(),
));
wp_enqueue_script('wu-template-switching');
} // end register_scripts;
/**
* The list of fields to be added to Gutenberg.
*
* @since 2.0.0
* @return array
*/
public function fields() {
$fields = array();
$fields['header'] = array(
'title' => __('Layout', 'wp-ultimo'),
'desc' => __('Layout', 'wp-ultimo'),
'type' => 'header',
);
$fields['template_selection_template'] = array(
'type' => 'group',
'desc' => Field_Templates_Manager::get_instance()->render_preview_block('template_selection'),
'fields' => array(
'template_selection_template' => array(
'type' => 'select',
'title' => __('Template Selector Layout', 'wp-ultimo'),
'placeholder' => __('Select your Layout', 'wp-ultimo'),
'default' => 'clean',
'options' => array($this, 'get_template_selection_templates'),
'wrapper_classes' => 'wu-flex-grow',
'html_attr' => array(
'v-model' => 'template_selection_template',
),
),
),
);
$fields['_dev_note_develop_your_own_template_1'] = array(
'type' => 'note',
'order' => 99,
'wrapper_classes' => 'sm:wu-p-0 sm:wu-block',
'classes' => '',
'desc' => sprintf('<div class="wu-p-4 wu-bg-blue-100 wu-text-grey-600">%s</div>', __('Want to add customized template selection templates?<br><a target="_blank" class="wu-no-underline" href="https://help.wpultimo.com/article/343-customize-your-checkout-flow-using-field-templates">See how you can do that here</a>.', 'wp-ultimo')),
);
return $fields;
} // end fields;
/**
* The list of keywords for this element.
*
* @since 2.0.0
*
* @return array
*/
public function keywords() {
return array(
'WP Ultimo',
'Template',
'Template Switching',
);
} // end keywords;
/**
* List of default parameters for the element.
*
* @since 2.0.0
* @return array
*/
public function defaults() {
$site_template_ids = wu_get_site_templates(array(
'fields' => 'ids',
));
return array(
'slug' => 'template-switching',
'template_selection_template' => 'clean',
'template_selection_sites' => implode(',', $site_template_ids),
);
} // end defaults;
/**
* Runs early on the request lifecycle as soon as we detect the shortcode is present.
*
* @since 2.0.0
* @return void
*/
public function setup() {
$this->site = wu_get_current_site();
if (!$this->site || !$this->site->is_customer_allowed()) {
$this->set_display(false);
return;
} // end if;
$this->membership = $this->site->get_membership();
$this->products = array();
$all_membership_products = array();
if ($this->membership) {
$all_membership_products = $this->membership->get_all_products();
if (is_array($all_membership_products) && $all_membership_products) {
foreach ($all_membership_products as $product) {
$this->products[] = $product['product']->get_id();
} // end foreach;
} // end if;
} // end if;
} // end setup;
/**
* Runs early on the request lifecycle as soon as we detect the shortcode is present.
*
* @since 2.0.4
*
* @return void
*/
public function setup_preview() {
$this->site = wu_mock_site();
} // end setup_preview;
/**
* Ajax action to change the template for a given site.
*
* @since 2.0.4
*
* @return json|WP_Error Switch template response.
*/
public function switch_template() {
if (!$this->site) {
$this->site = wu_get_current_site();
} // end if;
$template_id = wu_request('template_id', '');
if (!$template_id) {
return new \WP_Error('template_id_required', __('You need to provide a valid template to duplicate.', 'wp-ultimo'));
} // end if;
$switch = \WP_Ultimo\Helpers\Site_Duplicator::override_site($template_id, $this->site->get_id());
/**
* Allow plugin developers to hook functions after a user or super admin switches the site template
*
* @since 1.9.8
* @param integer Site ID
* @return void
*/
do_action('wu_after_switch_template', $this->site->get_id());
if ($switch) {
wp_send_json_success(array(
'redirect_url' => add_query_arg(array(
'updated' => 1,
), $_SERVER['HTTP_REFERER']),
));
} // end if;
} // end switch_template;
/**
* The content to be output on the screen.
*
* Should return HTML markup to be used to display the block.
* This method is shared between the block render method and
* the shortcode implementation.
*
* @since 2.0.0
*
* @param array $atts Parameters of the block/shortcode.
* @param string|null $content The content inside the shortcode.
* @return string
*/
public function output($atts, $content = null) {
if ($this->site) {
$filter_template_limits = new \WP_Ultimo\Limits\Site_Template_Limits();
$atts['products'] = $this->products;
$template_selection_field = $filter_template_limits->maybe_filter_template_selection_options($atts);
if (!isset($template_selection_field['sites'])) {
$template_selection_field['sites'] = array();
} // end if;
$atts['template_selection_sites'] = implode(',', $template_selection_field['sites']);
$site_list = explode(',', $atts['template_selection_sites']);
$sites = array_map('wu_get_site', $site_list);
$sites = array_filter($sites);
$categories = \WP_Ultimo\Models\Site::get_all_categories($sites);
$template_attributes = array(
'sites' => $sites,
'name' => '',
'categories' => $categories,
);
$reducer_class = new \WP_Ultimo\Checkout\Signup_Fields\Signup_Field_Template_Selection();
$template_class = Field_Templates_Manager::get_instance()->get_template_class('template_selection', $atts['template_selection_template']);
$content = $template_class ? $template_class->render_container($template_attributes, $reducer_class) : __('Template does not exist.', 'wp-ultimo');
$checkout_fields['back_to_template_selection'] = array(
'type' => 'note',
'order' => 0,
'desc' => sprintf('<a href="#" class="wu-no-underline wu-mt-1 wu-uppercase wu-text-2xs wu-font-semibold wu-text-gray-600" v-on:click.prevent="template_id = original_template_id; confirm_switch = false">%s</a>', __('&larr; Back to Template Selection', 'wp-ultimo')),
'wrapper_html_attr' => array(
'v-init:original_template_id' => $this->site->get_template_id(),
'v-show' => 'template_id != original_template_id',
'v-cloak' => '1',
),
);
$checkout_fields['template_element'] = array(
'type' => 'note',
'wrapper_classes' => 'wu-w-full',
'classes' => 'wu-w-full',
'desc' => $content,
'wrapper_html_attr' => array(
'v-show' => 'template_id == original_template_id',
'v-cloak' => '1',
),
);
$checkout_fields['confirm_switch'] = array(
'type' => 'toggle',
'title' => __('Confirm template switch?', 'wp-ultimo'),
'desc' => __('Switching your current template completely overwrites the content of your site with the contents of the newly chosen template. All customizations will be lost. This action cannot be undone.', 'wp-ultimo'),
'tooltip' => '',
'wrapper_classes' => '',
'value' => 0,
'html_attr' => array(
'v-model' => 'confirm_switch'
),
'wrapper_html_attr' => array(
'v-show' => 'template_id != 0 && template_id != original_template_id',
'v-cloak' => 1,
),
);
$checkout_fields['submit_switch'] = array(
'type' => 'link',
'display_value' => __('Process Switch', 'wp-ultimo'),
'wrapper_classes' => 'wu-text-right wu-bg-gray-100',
'classes' => 'button button-primary',
'wrapper_html_attr' => array(
'v-cloak' => 1,
'v-show' => 'confirm_switch',
'v-on:click.prevent' => 'ready = true'
),
);
$checkout_fields['template_id'] = array(
'type' => 'hidden',
'html_attr' => array(
'v-model' => 'template_id',
'v-init:template_id' => $this->site->get_template_id(),
),
);
$section_slug = 'wu-template-switching-form';
$form = new Form($section_slug, $checkout_fields, array(
'views' => 'admin-pages/fields',
'classes' => 'wu-striped wu-widget-inset',
'field_wrapper_classes' => 'wu-w-full wu-box-border wu-items-center wu-flex wu-justify-between wu-p-4 wu-py-5 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'
));
ob_start();
$form->render();
return ob_get_clean();
} // end if;
return '';
} // end output;
/**
* Returns the list of available pricing table templates.
*
* @since 2.0.0
* @return array
*/
public function get_template_selection_templates() {
$available_templates = Field_Templates_Manager::get_instance()->get_templates_as_options('template_selection');
return $available_templates;
} // end get_template_selection_templates;
} // end class Template_Switching_Element;

View File

@ -0,0 +1,461 @@
<?php
/**
* Adds the Thank_You_Element UI to the Admin Panel.
*
* @package WP_Ultimo
* @subpackage UI
* @since 2.0.0
*/
namespace WP_Ultimo\UI;
use WP_Ultimo\UI\Base_Element;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Adds the Thank You Element UI to the Admin Panel.
*
* @since 2.0.0
*/
class Thank_You_Element extends Base_Element {
use \WP_Ultimo\Traits\Singleton;
/**
* The id of the element.
*
* Something simple, without prefixes, like 'checkout', or 'pricing-tables'.
*
* This is used to construct shortcodes by prefixing the id with 'wu_'
* e.g. an id checkout becomes the shortcode 'wu_checkout' and
* to generate the Gutenberg block by prefixing it with 'wp-ultimo/'
* e.g. checkout would become the block 'wp-ultimo/checkout'.
*
* @since 2.0.0
* @var string
*/
public $id = 'thank-you';
/**
* The payment object.
*
* @since 2.0.0
* @var \WP_Ultimo\Payment
*/
protected $payment;
/**
* The membership object.
*
* @since 2.0.0
* @var \WP_Ultimo\Membership
*/
protected $membership;
/**
* The customer object.
*
* @since 2.0.0
* @var \WP_Ultimo\Customer
*/
protected $customer;
/**
* The icon of the UI element.
* e.g. return fa fa-search
*
* @since 2.0.0
* @param string $context One of the values: block, elementor or bb.
*/
public function get_icon($context = 'block'): string {
if ($context === 'elementor') {
return 'eicon-info-circle-o';
} // end if;
return 'fa fa-search';
} // end get_icon;
/**
* Overload the init to add site-related forms.
*
* @since 2.0.0
* @return void
*/
public function init() {
parent::init();
} // end init;
/**
* Replace the register page title with the Thank you title.
*
* @since 2.0.0
*
* @param array $title_parts The title parts.
* @return array
*/
public function replace_page_title($title_parts) {
$title_parts['title'] = $this->get_title();
return $title_parts;
} // end replace_page_title;
/**
* Maybe clear the title at the content level.
*
* @since 2.0.0
*
* @param string $title The page title.
* @param int $id The post/page id.
* @return string
*/
public function maybe_replace_page_title($title, $id) {
global $post;
if ($post && $post->ID === $id) {
return '';
} // end if;
return $title;
} // end maybe_replace_page_title;
/**
* Register additional scripts for the thank you page.
*
* @since 2.0.0
* @return void
*/
public function register_scripts() {
$has_pending_site = $this->membership ? (bool) $this->membership->get_pending_site() : false;
$is_publishing = $has_pending_site ? $this->membership->get_pending_site()->is_publishing() : false;
wp_register_script('wu-thank-you', wu_get_asset('thank-you.js', 'js'), array(), wu_get_version());
wp_localize_script('wu-thank-you', 'wu_thank_you', array(
'creating' => $is_publishing,
'has_pending_site' => $has_pending_site,
'next_queue' => wu_get_next_queue_run(),
'ajaxurl' => admin_url('admin-ajax.php'),
'resend_verification_email_nonce' => wp_create_nonce('wu_resend_verification_email_nonce'),
'membership_hash' => $this->membership ? $this->membership->get_hash() : false,
'i18n' => array(
'resending_verification_email' => __('Resending verification email...', 'wp-ultimo'),
'email_sent' => __('Verification email sent!', 'wp-ultimo'),
),
));
wp_enqueue_script('wu-thank-you');
} // end register_scripts;
/**
* The title of the UI element.
*
* This is used on the Blocks list of Gutenberg.
* You should return a string with the localized title.
* e.g. return __('My Element', 'wp-ultimo').
*
* @since 2.0.0
* @return string
*/
public function get_title() {
return __('Thank You', 'wp-ultimo');
} // end get_title;
/**
* The description of the UI element.
*
* This is also used on the Gutenberg block list
* to explain what this block is about.
* You should return a string with the localized title.
* e.g. return __('Adds a checkout form to the page', 'wp-ultimo').
*
* @since 2.0.0
* @return string
*/
public function get_description() {
return __('Adds a checkout form block to the page.', 'wp-ultimo');
} // end get_description;
/**
* The list of fields to be added to Gutenberg.
*
* If you plan to add Gutenberg controls to this block,
* you'll need to return an array of fields, following
* our fields interface (@see inc/ui/class-field.php).
*
* You can create new Gutenberg panels by adding fields
* with the type 'header'. See the Checkout Elements for reference.
*
* @see inc/ui/class-checkout-element.php
*
* Return an empty array if you don't have controls to add.
*
* @since 2.0.0
* @return array
*/
public function fields() {
$fields = array();
$fields['header'] = array(
'title' => __('General', 'wp-ultimo'),
'desc' => __('General', 'wp-ultimo'),
'type' => 'header',
);
$fields['title'] = array(
'type' => 'text',
'title' => __('Title', 'wp-ultimo'),
'value' => __('Thank You', 'wp-ultimo'),
'desc' => __('Leave blank to hide the title completely.', 'wp-ultimo'),
'tooltip' => '',
);
$fields['thank_you_message'] = array(
'type' => 'textarea',
'title' => __('Thank You Message', 'wp-ultimo'),
'desc' => __('Shortcodes are supported.', 'wp-ultimo'),
'value' => __('Thank you for your payment! Your transaction has been completed and a receipt for your purchase has been emailed to you.', 'wp-ultimo'),
'tooltip' => '',
'html_attr' => array(
'rows' => 4,
),
);
$fields['title_pending'] = array(
'type' => 'text',
'title' => __('Title (Pending)', 'wp-ultimo'),
'value' => __('Thank You', 'wp-ultimo'),
'desc' => __('Leave blank to hide the title completely. This title is used when the payment was not yet confirmed.', 'wp-ultimo'),
'tooltip' => '',
);
$fields['thank_you_message_pending'] = array(
'type' => 'textarea',
'title' => __('Thank You Message (Pending)', 'wp-ultimo'),
'desc' => __('This content is used when the payment was not yet confirmed. Shortcodes are supported.', 'wp-ultimo'),
'value' => __('Thank you for your order! We are waiting on the payment processor to confirm your payment, which can take up to 5 minutes. We will notify you via email when your site is ready.', 'wp-ultimo'),
'tooltip' => '',
'html_attr' => array(
'rows' => 4,
),
);
$fields['no_sites_message'] = array(
'type' => 'textarea',
'title' => __('No Sites Message', 'wp-ultimo'),
'desc' => __('A message to show if membership has no sites. Shortcodes are supported.', 'wp-ultimo'),
'value' => __('No sites found', 'wp-ultimo'),
'tooltip' => '',
'html_attr' => array(
'rows' => 4,
),
);
return $fields;
} // end fields;
/**
* The list of keywords for this element.
*
* Return an array of strings with keywords describing this
* element. Gutenberg uses this to help customers find blocks.
*
* e.g.:
* return array(
* 'WP Ultimo',
* 'Billing Information',
* 'Form',
* 'Cart',
* );
*
* @since 2.0.0
* @return array
*/
public function keywords() {
return array(
'WP Ultimo',
'Thank You',
'Form',
'Cart',
);
} // end keywords;
/**
* List of default parameters for the element.
*
* If you are planning to add controls using the fields,
* it might be a good idea to use this method to set defaults
* for the parameters you are expecting.
*
* These defaults will be used inside a 'wp_parse_args' call
* before passing the parameters down to the block render
* function and the shortcode render function.
*
* @since 2.0.0
* @return array
*/
public function defaults() {
return array(
'title' => __('Thank You', 'wp-ultimo'),
'thank_you_message' => __('Thank you for your payment! Your transaction has been completed and a receipt for your purchase has been emailed to you.', 'wp-ultimo'),
'title_pending' => __('Thank You', 'wp-ultimo'),
'thank_you_message_pending' => __('Thank you for your order! We are waiting on the payment processor to confirm your payment, which can take up to 5 minutes. We will notify you via email when your site is ready.', 'wp-ultimo'),
'no_sites_message' => __('No sites found', 'wp-ultimo'),
);
} // end defaults;
/**
* Runs early on the request lifecycle as soon as we detect the shortcode is present.
*
* @since 2.0.0
* @return void
*/
public function setup() {
$this->payment = wu_get_payment_by_hash(wu_request('payment'));
if (!$this->payment) {
$this->set_display(false);
return;
} // end if;
$this->membership = $this->payment->get_membership();
if (!$this->membership || !$this->membership->is_customer_allowed()) {
$this->set_display(false);
return;
} // end if;
$this->customer = $this->membership->get_customer();
add_filter('document_title_parts', array($this, 'replace_page_title'));
add_filter('the_title', array($this, 'maybe_replace_page_title'), 10, 2);
} // end setup;
/**
* Allows the setup in the context of previews.
*
* @since 2.0.0
* @return void
*/
public function setup_preview() {
$this->payment = wu_mock_payment();
$this->membership = wu_mock_membership();
$this->customer = wu_mock_customer();
} // end setup_preview;
/**
* The content to be output on the screen.
*
* Should return HTML markup to be used to display the block.
* This method is shared between the block render method and
* the shortcode implementation.
*
* @since 2.0.0
*
* @param array $atts Parameters of the block/shortcode.
* @param string|null $content The content inside the shortcode.
* @return string
*/
public function output($atts, $content = null) {
$atts['payment'] = $this->payment;
$atts['membership'] = $this->membership;
$atts['customer'] = $this->customer;
$atts = wp_parse_args($atts, $this->defaults());
/*
* Deal with conversion tracking
*/
$conversion_snippets = $atts['checkout_form']->get_conversion_snippets();
if (!empty($conversion_snippets)) {
$product_ids = array();
foreach ($this->payment->get_line_items() as $line_item) {
if ($line_item->get_product_id()) {
$product_ids[] = (string) $line_item->get_product_id();
} // end if;
} // end foreach;
$conversion_placeholders = apply_filters( 'wu_conversion_placeholders', array(
'CUSTOMER_ID' => $this->customer->get_id(),
'CUSTOMER_EMAIL' => $this->customer->get_email_address(),
'MEMBERSHIP_DURATION' => $this->membership->get_recurring_description(),
'MEMBERSHIP_PLAN' => $this->membership->get_plan_id(),
'MEMBERSHIP_AMOUNT' => $this->membership->get_amount(),
'ORDER_ID' => $this->payment->get_hash(),
'ORDER_CURRENCY' => $this->payment->get_currency(),
'ORDER_PRODUCTS' => array_values($product_ids),
'ORDER_AMOUNT' => $this->payment->get_total(),
));
foreach ($conversion_placeholders as $placeholder => $value) {
$conversion_snippets = preg_replace('/\%\%\s?' . $placeholder . '\s?\%\%/', json_encode($value), (string) $conversion_snippets);
} // end foreach;
add_action('wp_print_footer_scripts', function() use ($conversion_snippets) {
echo $conversion_snippets;
});
} // end if;
/*
* Account for the 'className' Gutenberg attribute.
*/
$atts['className'] = trim('wu-' . $this->id . ' ' . wu_get_isset($atts, 'className', ''));
return wu_get_template_contents('dashboard-widgets/thank-you', $atts);
} // end output;
} // end class Thank_You_Element;

113
inc/ui/class-toolbox.php Normal file
View File

@ -0,0 +1,113 @@
<?php
/**
* Adds the Toolbox UI to the Admin Panel.
*
* @package WP_Ultimo
* @subpackage UI
* @since 2.0.0
*/
namespace WP_Ultimo\UI;
use WP_Ultimo\Logger;
use WP_Ultimo\UI\Base_Element;
use WP_Ultimo\License;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Adds the Toolbox UI to the Admin Panel.
*
* @since 2.0.0
*/
class Toolbox {
use \WP_Ultimo\Traits\Singleton;
/**
* Element construct.
*
* @since 2.0.0
*/
public function __construct() {
add_action('init', array($this, 'load_toolbox'));
} // end __construct;
/**
* Checks if we should add the toolbox or not.
*
* @todo fix the check for license activation.
* @since 2.0.0
* @return boolean
*/
protected function is_toolbox_enabled() {
$can_see_toolbox = current_user_can('manage_network');
if (class_exists('\user_switching') && !$can_see_toolbox) {
$old_user = \user_switching::get_old_user();
$can_see_toolbox = user_can($old_user, 'manage_network');
} // end if;
return apply_filters('wu_is_toolbox_enabled', wu_get_setting('enable_jumper', true) && License::get_instance()->allowed() && $can_see_toolbox && !is_network_admin());
} // end is_toolbox_enabled;
/**
* Loads the necessary elements to display the Toolbox.
*
* @since 2.0.0
* @return void
*/
public function load_toolbox() {
if ($this->is_toolbox_enabled()) {
add_action('wp_footer', array($this, 'output'));
add_action('admin_footer', array($this, 'output'));
add_action('wp_enqueue_scripts', array($this, 'enqueue_styles'));
} // end if;
} // end load_toolbox;
/**
* Adds the admin styles to make sure the tooltip renders.
*
* @since 2.0.0
* @return void
*/
public function enqueue_styles() {
wp_enqueue_style('wu-admin');
} // end enqueue_styles;
/**
* Outputs the actual HTML markup of the Toolbox.
*
* @since 2.0.0
* @return void
*/
public function output() {
$current_site = wu_get_current_site();
wu_get_template('ui/toolbox', array(
'toolbox' => $this,
'current_site' => $current_site,
'customer' => $current_site ? $current_site->get_customer() : false,
'membership' => $current_site ? $current_site->get_membership() : false,
));
} // end output;
} // end class Toolbox;

176
inc/ui/class-tours.php Normal file
View File

@ -0,0 +1,176 @@
<?php
/**
* Adds the Tours UI to the Admin Panel.
*
* @package WP_Ultimo
* @subpackage UI
* @since 2.0.0
*/
namespace WP_Ultimo\UI;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Adds the Tours UI to the Admin Panel.
*
* @since 2.0.0
*/
class Tours {
use \WP_Ultimo\Traits\Singleton;
/**
* Registered tours.
*
* @since 2.0.0
* @var array
*/
protected $tours = array();
/**
* Element construct.
*
* @since 2.0.0
*/
public function __construct() {
add_action('wp_ajax_wu_mark_tour_as_finished', array($this, 'mark_as_finished'));
add_action('admin_enqueue_scripts', array($this, 'register_scripts'));
add_action('in_admin_footer', array($this, 'enqueue_scripts'));
} // end __construct;
/**
* Mark the tour as finished for a particular user.
*
* @since 2.0.0
* @return void
*/
public function mark_as_finished() {
check_ajax_referer('wu_tour_finished', 'nonce');
$id = wu_request('tour_id');
if ($id) {
set_user_setting("wu_tour_$id", true);
wp_send_json_success();
} // end if;
wp_send_json_error();
} // end mark_as_finished;
/**
* Register the necessary scripts.
*
* @since 2.0.0
* @return void
*/
public function register_scripts() {
WP_Ultimo()->scripts->register_script('wu-shepherd', wu_get_asset('lib/shepherd.js', 'js'), array());
WP_Ultimo()->scripts->register_script('wu-tours', wu_get_asset('tours.js', 'js'), array('wu-shepherd', 'underscore'));
} // end register_scripts;
/**
* Enqueues the scripts, if we need to.
*
* @since 2.0.0
* @return void
*/
public function enqueue_scripts() {
if ($this->has_tours()) {
wp_localize_script('wu-tours', 'wu_tours', $this->tours);
wp_localize_script('wu-tours', 'wu_tours_vars', array(
'ajaxurl' => wu_ajax_url(),
'nonce' => wp_create_nonce('wu_tour_finished'),
'i18n' => array(
'next' => __('Next', 'wp-ultimo'),
'finish' => __('Close', 'wp-ultimo')
),
));
wp_enqueue_script('wu-tours');
} // end if;
} // end enqueue_scripts;
/**
* Checks if we have registered tours.
*
* @since 2.0.0
* @return boolean
*/
public function has_tours() {
return !empty($this->tours);
} // end has_tours;
/**
* Register a new tour.
*
* @see https://shepherdjs.dev/docs/
*
* @since 2.0.0
*
* @param string $id The id of the tour.
* @param array $steps The tour definition. Check shepherd.js docs.
* @param boolean $once Whether or not we will show this more than once.
* @return void
*/
public function create_tour($id, $steps = array(), $once = true) {
if (did_action('in_admin_header')) {
return;
} // end if;
add_action('in_admin_header', function() use ($id, $steps, $once) {
$force_hide = wu_get_setting('hide_tours', false);
if ($force_hide) {
return;
} // end if;
$finished = (bool) get_user_setting("wu_tour_$id", false);
$finished = apply_filters('wu_tour_finished', $finished, $id, get_current_user_id());
if (!$finished || !$once) {
foreach ($steps as &$step) {
$step['text'] = is_array($step['text']) ? implode('</p><p>', $step['text']) : $step['text'];
$step['text'] = sprintf('<p>%s</p>', $step['text']);
} // end foreach;
$this->tours[$id] = $steps;
} // end if;
});
} // end create_tour;
} // end class Tours;