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,171 @@
<?php
/**
* Handles limitations to the customer user role.
*
* @todo We need to move posts on downgrade.
* @package WP_Ultimo
* @subpackage Limits
* @since 2.0.10
*/
namespace WP_Ultimo\Limits;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Handles limitations to the customer user role.
*
* @since 2.0.0
*/
class Customer_User_Role_Limits {
use \WP_Ultimo\Traits\Singleton;
/**
* Runs on the first and only instantiation.
*
* @since 2.0.0
* @return void
*/
public function init() {
add_action('in_admin_header', array($this, 'block_new_user_page'));
add_action('wu_async_after_membership_update_products', array($this, 'update_site_user_roles'));
add_filter('editable_roles', array($this, 'filter_editable_roles'));
if (!wu_get_current_site()->has_module_limitation('customer_user_role')) {
return;
} // end if;
} // end init;
/**
* Block new user page if limit has reached.
*
* @since 2.0.20
*/
public function block_new_user_page() {
if (is_super_admin()) {
return;
} // end if;
$screen = get_current_screen();
if (!$screen || $screen->id !== 'user') {
return;
} // end if;
if (!empty(get_editable_roles())) {
return;
} // end if;
$message = __('You reached your membership users limit.', 'wp-ultimo');
/**
* Allow developers to change the message about the membership users limit
*
* @param string $message The message to print in screen.
*/
$message = apply_filters('wu_users_membership_limit_message', $message);
wp_die($message, __('Limit Reached', 'wp-ultimo'), array('back_link' => true));
} // end block_new_user_page;
/**
* Filters editable roles offered as options on limitations.
*
* @since 2.0.10
*
* @param array $roles The list of available roles.
* @return array
*/
public function filter_editable_roles($roles) {
if (!wu_get_current_site()->has_module_limitation('users') || is_super_admin()) {
return $roles;
} // end if;
$users_limitation = wu_get_current_site()->get_limitations()->users;
foreach ($roles as $role => $details) {
$limit = $users_limitation->{$role};
if (property_exists($limit, 'enabled') && $limit->enabled) {
$user_list = get_users(array('role' => $role));
$count = (int) count($user_list);
$limit = (int) wu_get_current_site()->get_limitations()->users->{$role}->number;
if (0 !== $limit && $count >= $limit) {
unset($roles[$role]);
} // end if;
} else {
unset($roles[$role]);
} // end if;
} // end foreach;
return $roles;
} // end filter_editable_roles;
/**
* Updates the site user roles after a up/downgrade.
*
* @since 2.0.10
*
* @param int $membership_id The membership upgraded or downgraded.
* @return void
*/
public function update_site_user_roles($membership_id) {
$membership = wu_get_membership($membership_id);
if ($membership) {
$customer = $membership->get_customer();
if (!$customer) {
return;
} // end if;
$sites = $membership->get_sites(false);
$role = $membership->get_limitations()->customer_user_role->get_limit();
foreach ($sites as $site) {
add_user_to_blog($site->get_id(), $customer->get_user_id(), $role);
} // end foreach;
} // end if;
} // end update_site_user_roles;
} // end class Customer_User_Role_Limits;

View File

@ -0,0 +1,148 @@
<?php
/**
* Handles limitations to disk space
*
* @todo We need to move posts on downgrade.
* @package WP_Ultimo
* @subpackage Limits
* @since 2.0.0
*/
namespace WP_Ultimo\Limits;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Handles limitations to post types, uploads and more.
*
* @since 2.0.0
*/
class Disk_Space_Limits {
use \WP_Ultimo\Traits\Singleton;
/**
* Whether or not the disk space limitations should be loaded.
* This is for performance reasons, so we don't have to run all the hooks if the site doesn't have limitations.
*
* @since 2.1.2
* @var boolean
*/
protected $should_load = false;
/**
* Whether or not the class has started.
*
* @since 2.1.2
* @var boolean
*/
protected $started = false;
/**
* Runs on the first and only instantiation.
*
* @since 2.0.0
* @return void
*/
public function init() {
add_filter('site_option_upload_space_check_disabled', array($this, 'upload_space_check_disabled'));
add_filter('get_space_allowed', array($this, 'apply_disk_space_limitations'));
} // end init;
/**
* Disables the upload space check if the site has limitations.
* This way we can handle our own checks.
*
* @since 2.1.2
*
* @param int $value The current value.
* @return int
*/
public function upload_space_check_disabled($value) {
if (!$this->should_load()) {
return $value;
} // end if;
return 0;
} // end upload_space_check_disabled;
/**
* Checks if the disk space limitations should be loaded.
*
* @since 2.1.2
* @return boolean
*/
protected function should_load() {
if ($this->started) {
return $this->should_load;
} // end if;
$this->started = true;
$this->should_load = true;
/**
* Allow plugin developers to short-circuit the limitations.
*
* You can use this filter to run arbitrary code before any of the limits get initiated.
* If you filter returns any truthy value, the process will move on, if it returns any falsy value,
* the code will return and none of the hooks below will run.
*
* @since 1.7.0
* @return bool
*/
if (!apply_filters('wu_apply_plan_limits', wu_get_current_site()->has_limitations())) {
$this->should_load = false;
} // end if;
if (!wu_get_current_site()->has_module_limitation('disk_space')) {
$this->should_load = false;
} // end if;
return $this->should_load;
} // end should_load;
/**
* Changes the disk_space to the one on the product.
*
* @since 2.0.0
*
* @param string $disk_space The new disk space.
* @return int
*/
public function apply_disk_space_limitations($disk_space) {
if (!$this->should_load()) {
return $disk_space;
} // end if;
$modified_disk_space = wu_get_current_site()->get_limitations()->disk_space->get_limit();
if (is_numeric($modified_disk_space)) {
return $modified_disk_space;
} // end if;
return $disk_space;
} // end apply_disk_space_limitations;
} // end class Disk_Space_Limits;

View File

@ -0,0 +1,400 @@
<?php
/**
* Handles limitations to post types, uploads and more.
*
* @package WP_Ultimo
* @subpackage Limits
* @since 2.0.0
*/
namespace WP_Ultimo\Limits;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Handles limitations to post types, uploads and more.
*
* @since 2.0.0
*/
class Plugin_Limits {
use \WP_Ultimo\Traits\Singleton;
/**
* Site-level plugins cache.
*
* @since 2.0.0
* @var null|array
*/
protected $plugins = null;
/**
* Network plugins cache.
*
* @since 2.0.0
* @var null|array
*/
protected $network_plugins = null;
/**
* Runs on the first and only instantiation.
*
* @since 2.0.0
* @return void
*/
public function init() {
add_action('wu_sunrise_loaded', array($this, 'load_limitations'));
} // end init;
/**
* Apply limitations if they are available.
*
* @since 2.0.0
* @return void
*/
public function load_limitations() {
if (wu_get_current_site()->has_limitations()) {
add_filter('site_option_active_sitewide_plugins', array($this, 'deactivate_network_plugins'));
add_filter('option_active_plugins', array($this, 'deactivate_plugins'));
add_filter('all_plugins', array($this, 'clear_plugin_list'));
add_filter('the_content', array($this, 'clean_unused_shortcodes'), 9999);
add_filter('plugin_action_links', array($this, 'clear_actions'), -2000, 2);
add_filter('show_network_active_plugins', '__return_true');
add_action('load-plugins.php', array($this, 'admin_page_hooks'));
} // end if;
add_action('wu_site_post_save', array($this, 'activate_and_inactive_plugins'), 10, 3);
add_action('wu_checkout_done', array($this, 'maybe_activate_and_inactive_plugins'), 10, 5);
} // end load_limitations;
/**
* Registers scripts onto the plugins page.
*
* @since 2.0.5
* @return void
*/
public function admin_page_hooks() {
add_action('admin_enqueue_scripts', 'add_wubox');
} // end admin_page_hooks;
/**
* Automatically activate and deactivate plugins when the site is created or a upgrade happens.
*
* @since 2.0.0
*
* @param array $data Saved data.
* @param \WP_Ultimo\Models\Site $site_object The site created.
* @param bool $new If this site is a new one.
* @return void
*/
public function activate_and_inactive_plugins($data, $site_object, $new) {
if ($site_object && $new) {
$site_object->sync_plugins();
} // end if;
} // end activate_and_inactive_plugins;
/**
* Activate and Deactivate plugins on upgrades and downgrades.
*
* @since 2.0.5
*
* @param \WP_Ultimo\Models\Payment $payment The payment object.
* @param \WP_Ultimo\Models\Membership $membership The membership object.
* @param \WP_Ultimo\Models\Customer $customer The customer object.
* @param \WP_Ultimo\Checkout\Cart $cart The cart object.
* @param string $type The cart type.
* @return void
*/
public function maybe_activate_and_inactive_plugins($payment, $membership, $customer, $cart, $type) {
if ($type !== 'new' && $membership) {
$membership->sync_plugins();
} // end if;
} // end maybe_activate_and_inactive_plugins;
/**
* Clear the actions of the plugins list table.
*
* @since 2.0.0
*
* @param array $actions The list of plugin actions.
* @param string $plugin_file The plugin path/slug.
* @return array
*/
public function clear_actions($actions, $plugin_file) {
if (!function_exists('wu_generate_upgrade_to_unlock_url')) {
return $actions;
} // end if;
$plugin_limits = wu_get_current_site()->get_limitations()->plugins;
if (isset($actions['network_active'])) {
$actions['network_active'] = sprintf('<span class="wu-styling">
<span class="wu-text-green-600">
<span class="dashicons-wu-flash wu-align-text-bottom"></span>%s
</span>
</span>', __('Always Loaded', 'wp-ultimo'));
} // end if;
if ($plugin_limits->allowed($plugin_file, 'force_active_locked')) {
unset($actions['deactivate']);
} elseif ($plugin_limits->allowed($plugin_file, 'force_inactive_locked')) {
$upgrade = sprintf(
'<a href="%s" class="wu-styling" title="%s"><span class="dashicons-wu-lock1 wu-mr-1"></span>%s</a>',
wu_generate_upgrade_to_unlock_url(array(
'module' => 'plugins',
'type' => $plugin_file,
)),
__('Upgrade to unlock', 'wp-ultimo'),
__('Upgrade to unlock', 'wp-ultimo')
);
$actions['upgrade'] = $upgrade;
unset($actions['activate']);
} // end if;
return $actions;
} // end clear_actions;
/**
* Clears the plugin list.
*
* This method is responsible for controlling
* plugin visibility on the plugins list table.
*
* @since 2.0.0
*
* @param array $plugins The original list of plugins.
* @return array
*/
public function clear_plugin_list($plugins) {
if (is_main_site()) {
return $plugins;
} // end if;
$plugin_limits = wu_get_current_site()->get_limitations()->plugins;
foreach ($plugins as $plugin_slug => $plugin_data) {
if ($plugin_data['Network']) {
unset($plugins[$plugin_slug]);
} // end if;
if (strncmp($plugin_slug, 'wp-ultimo', strlen('wp-ultimo')) === 0) {
unset($plugins[$plugin_slug]);
} // end if;
if ($plugin_limits->allowed($plugin_slug, 'hidden')) {
unset($plugins[$plugin_slug]);
} // end if;
} // end foreach;
return $plugins;
} // end clear_plugin_list;
/**
* Deactivates the network plugins that people are not allowed to use.
*
* We need different methods because keys are different network wide and on the sub-site level.
*
* @since 2.0.0
*
* @param array $plugins Array with the plugins activated.
* @return array
*/
public function deactivate_network_plugins($plugins) {
/*
* Bail on network admin =)
*/
if (is_network_admin() || is_main_site()) {
return $plugins;
} // end if;
/*
* Get the network plugins cache, if they're set.
*/
if (is_array($this->network_plugins)) {
return $this->network_plugins;
} // end if;
$plugin_limits = wu_get_current_site()->get_limitations()->plugins;
foreach ($plugins as $plugin_slug => $timestamp) {
if (strpos($plugin_slug, 'wp-ultimo') !== false) {
continue;
} // end if;
if ($plugin_limits->allowed($plugin_slug, 'force_inactive_locked')) {
unset($plugins[$plugin_slug]);
} // end if;
} // end foreach;
// Ensure get_plugins function is loaded.
if (!function_exists('get_plugins')) {
include ABSPATH . '/wp-admin/includes/plugin.php';
} // end if;
$other_plugins = get_plugins();
foreach ($other_plugins as $plugin_path => $other_plugin) {
if (!wu_get_isset($plugins, $plugin_path) && $plugin_limits->allowed($plugin_path, 'force_active_locked')) {
$plugins[$plugin_path] = true;
} // end if;
} // end foreach;
$this->network_plugins = $plugins;
return $plugins;
} // end deactivate_network_plugins;
/**
* Deactivates the plugins that people are not allowed to use.
*
* @since 2.0.0
*
* @param array $plugins Array with the plugins activated.
* @return array
*/
public function deactivate_plugins($plugins) {
/*
* Bail on network admin =)
*/
if (is_network_admin() || is_main_site()) {
return $plugins;
} // end if;
/*
* Get the site-level plugins cache, if they're set.
*/
if (is_array($this->plugins)) {
return $this->plugins;
} // end if;
$plugin_limits = wu_get_current_site()->get_limitations()->plugins;
foreach ($plugins as $plugin_slug) {
if (strpos((string) $plugin_slug, 'wp-ultimo') !== false) {
continue;
} // end if;
if ($plugin_limits->allowed($plugin_slug, 'force_inactive_locked')) {
$index = array_search($plugin_slug, $plugins, true);
unset($plugins[$index]);
} // end if;
} // end foreach;
// Ensure get_plugins function is loaded.
if (!function_exists('get_plugins')) {
include ABSPATH . '/wp-admin/includes/plugin.php';
} // end if;
$other_plugins = get_plugins();
foreach ($other_plugins as $plugin_path => $other_plugin) {
if (in_array($plugin_path, $plugins, true) && $plugin_limits->allowed($plugin_path, 'force_active_locked')) {
$plugins[] = $plugin_path;
} // end if;
} // end foreach;
$this->plugins = $plugins;
return $plugins;
} // end deactivate_plugins;
/**
* Remove the unused shortcodes after we disable plugins.
*
* @since 2.0.0
*
* @param string $content The post content.
* @return string
*/
public function clean_unused_shortcodes($content) {
$content = preg_replace('/\[.+\].+\[\/.+\]/', '', $content);
return $content;
} // end clean_unused_shortcodes;
} // end class Plugin_Limits;

View File

@ -0,0 +1,299 @@
<?php
/**
* Handles limitations to post types, uploads and more.
*
* @todo We need to move posts on downgrade.
* @package WP_Ultimo
* @subpackage Limits
* @since 2.0.0
*/
namespace WP_Ultimo\Limits;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Handles limitations to post types, uploads and more.
*
* @since 2.0.0
*/
class Post_Type_Limits {
use \WP_Ultimo\Traits\Singleton;
/**
* Runs on the first and only instantiation.
*
* @since 2.0.0
* @return void
*/
public function init() {
/**
* Emulated post types.
*
* @since 2.0.6
*/
if (is_main_site() && is_network_admin()) {
add_action('init', array($this, 'register_emulated_post_types'), 999);
} // end if;
/**
* Allow plugin developers to short-circuit the limitations.
*
* You can use this filter to run arbitrary code before any of the limits get initiated.
* If you filter returns any truthy value, the process will move on, if it returns any falsy value,
* the code will return and none of the hooks below will run.
*
* @since 1.7.0
* @return bool
*/
if (!apply_filters('wu_apply_plan_limits', wu_get_current_site()->has_limitations())) {
return;
} // end if;
if (!wu_get_current_site()->has_module_limitation('post_types')) {
return;
} // end if;
add_action('load-post-new.php', array($this, 'limit_posts'));
add_filter('wp_handle_upload', array($this, 'limit_media'));
add_filter('media_upload_tabs', array($this, 'limit_tabs'));
add_action('current_screen', array($this, 'limit_restoring'), 10);
add_filter('wp_insert_post_data', array($this, 'limit_draft_publishing'), 10, 2);
} // end init;
/**
* Emulates post types to avoid having to have plugins active on the main site.
*
* @since 2.0.6
* @return void
*/
public function register_emulated_post_types() {
$emulated_post_types = wu_get_setting('emulated_post_types', array());
if (is_array($emulated_post_types) && !empty($emulated_post_types)) {
foreach ($emulated_post_types as $pt) {
$pt = (object) $pt;
$existing_pt = get_post_type_object($pt->post_type);
if ($existing_pt) {
continue;
} // end if;
register_post_type($pt->post_type, array(
'label' => $pt->label,
'exclude_from_search' => true,
'public' => true,
'show_in_menu' => false,
'has_archive' => false,
'can_export' => false,
'delete_with_user' => false,
));
} // end foreach;
} // end if;
} // end register_emulated_post_types;
/**
* Prevents users from trashing posts and restoring them later to bypass the limitation.
*
* @since 2.0.0
* @return void
*/
public function limit_restoring() {
if (isset($_REQUEST['action']) && $_REQUEST['action'] === 'untrash') {
$this->limit_posts();
} // end if;
} // end limit_restoring;
/**
* Limit the posts after the user reach his plan limits
*
* @since 1.0.0
* @since 1.5.4 Checks for blocked post types
*/
public function limit_posts() {
if (is_main_site()) {
return;
} // end if;
$screen = get_current_screen();
if (!wu_get_current_site()->get_limitations()->post_types->{$screen->post_type}->enabled) {
$upgrade_message = __('Your plan does not support this post type.', 'wp-ultimo');
// translators: %s is the URL.
wp_die($upgrade_message, __('Limit Reached', 'wp-ultimo'), array('back_link' => true));
} // end if;
// Check if that is more than our limit
if (wu_get_current_site()->get_limitations()->post_types->is_post_above_limit($screen->post_type)) {
$upgrade_message = __('You reached your plan\'s post limit.', 'wp-ultimo');
// translators: %s is the URL
wp_die($upgrade_message, __('Limit Reached', 'wp-ultimo'), array('back_link' => true));
} // end if;
} // end limit_posts;
/**
* Checks if the user is trying to publish a draft post.
*
* If that's the case, only allow him to do it if the post count is not above the quota.
*
* @since 1.7.0
* @param array $data Info being saved on posts.
* @param array $modified_data Data that is changing. We are interested in publish.
* @return array
*/
public function limit_draft_publishing($data, $modified_data) {
global $current_screen;
if (empty($current_screen)) {
return $data;
} // end if;
if (get_post_status($modified_data['ID']) === 'publish') {
return $data; // If the post is already published, no need to make changes
} // end if;
if (isset($data['post_status']) && $data['post_status'] !== 'publish') {
return $data;
} // end if;
$post_type = isset($data['post_type']) ? $data['post_type'] : 'post';
$post_type_limits = wu_get_current_site()->get_limitations()->post_types;
if (!$post_type_limits->{$current_screen->post_type}->enabled || $post_type_limits->is_post_above_limit($post_type)) {
$data['post_status'] = 'draft';
} // end if;
return $data;
} // end limit_draft_publishing;
/**
* Limits uploads of items to the media library.
*
* @since 2.0.0
*
* @param array $file $_FILE array being passed.
* @return mixed
*/
public function limit_media($file) {
if (!wu_get_current_site()->get_limitations()->post_types->attachment->enabled) {
$file['error'] = __('Your plan does not support media upload.', 'wp-ultimo');
return $file;
} // end if;
$post_count = wp_count_posts('attachment');
$post_count = $post_count->inherit;
$quota = wu_get_current_site()->get_limitations()->post_types->attachment->number;
// This bit is for the flash uploader
if ($file['type'] === 'application/octet-stream' && isset($file['tmp_name'])) {
$file_size = getimagesize($file['tmp_name']);
if (isset($file_size['error']) && $file_size['error'] !== 0) {
$file['error'] = "Unexpected Error: {$file_size['error']}";
return $file;
} else {
$file['type'] = $file_size['mime'];
} // end if;
} // end if;
if ($quota > 0 && $post_count >= $quota) {
// translators: %d is the number of images allowed.
$file['error'] = sprintf(__('You reached your media upload limit of %d images. Upgrade your account to unlock more media uploads.', 'wp-ultimo'), $quota, '#');
} // end if;
return $file;
} // end limit_media;
/**
* Remove the upload tabs if the quota is over.
*
* @since 2.0.0
*
* @param array $tabs Tabs of the media gallery upload modal.
* @return array
*/
public function limit_tabs($tabs) {
$post_count = wp_count_posts('attachment');
$post_count = $post_count->inherit;
$quota = wu_get_current_site()->get_limitations()->post_types->attachment->number;
if ($quota > 0 && $post_count > $quota) {
unset($tabs['type']);
unset($tabs['type_url']);
} // end if;
return $tabs;
} // end limit_tabs;
} // end class Post_Type_Limits;

View File

@ -0,0 +1,217 @@
<?php
/**
* Handles limitations to disk space
*
* @todo We need to move posts on downgrade.
* @package WP_Ultimo
* @subpackage Limits
* @since 2.0.0
*/
namespace WP_Ultimo\Limits;
use WP_Ultimo\Checkout\Checkout;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Handles limitations to post types, uploads and more.
*
* @since 2.0.0
*/
class Site_Template_Limits {
use \WP_Ultimo\Traits\Singleton;
/**
* Runs on the first and only instantiation.
*
* @since 2.0.0
* @return void
*/
public function init() {
add_action('plugins_loaded', array($this, 'setup'));
} // end init;
/**
* Sets up the hooks and checks.
*
* @since 2.0.0
* @return void
*/
public function setup() {
add_filter('wu_template_selection_render_attributes', array($this, 'maybe_filter_template_selection_options'));
add_filter('wu_checkout_template_id', array($this, 'maybe_force_template_selection'), 10, 2);
add_filter('wu_cart_get_extra_params', array($this, 'maybe_force_template_selection_on_cart'), 10, 2);
} // end setup;
/**
* Maybe filter the template selection options on the template selection field.
*
* @since 2.0.0
*
* @param array $attributes The template rendering attributes.
* @return array
*/
public function maybe_filter_template_selection_options($attributes) {
$attributes['should_display'] = true;
$products = array_map('wu_get_product', wu_get_isset($attributes, 'products', 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') {
$attributes['sites'] = wu_get_isset($attributes, 'sites', explode(',', ($attributes['template_selection_sites'] ?? '')));
return $attributes;
} elseif ($limits->site_templates->get_mode() === 'assign_template') {
$attributes['should_display'] = false;
} else {
$site_list = wu_get_isset($attributes, 'sites', explode(',', ($attributes['template_selection_sites'] ?? '')));
$available_templates = $limits->site_templates->get_available_site_templates();
$attributes['sites'] = array_intersect($site_list, $available_templates);
} // end if;
} // end if;
return $attributes;
} // end maybe_filter_template_selection_options;
/**
* Decides if we need to force the selection of a given template during the site creation.
*
* @since 2.0.0
*
* @param int $template_id The current template id.
* @param \WP_Ultimo\Models\Membership $membership The membership object.
* @return int
*/
public function maybe_force_template_selection($template_id, $membership) {
if ($membership && $membership->get_limitations()->site_templates->get_mode() === 'assign_template') {
$template_id = $membership->get_limitations()->site_templates->get_pre_selected_site_template();
} // end if;
return $template_id;
} // end maybe_force_template_selection;
/**
* Pre-selects a given template on the checkout screen depending on permissions.
*
* @since 2.0.0
*
* @param array $extra List if extra elements.
* @param \WP_Ultimo\Checkout\Cart $cart The cart object.
* @return array
*/
public function maybe_force_template_selection_on_cart($extra, $cart) {
$limits = new \WP_Ultimo\Objects\Limitations();
$products = $cart->get_all_products();
list($plan, $additional_products) = wu_segregate_products($products);
$products = array_merge(array($plan), $additional_products);
$products = array_filter($products);
foreach ($products as $product) {
$limits = $limits->merge($product->get_limitations());
} // end foreach;
if ($limits->site_templates->get_mode() === 'assign_template') {
$extra['template_id'] = $limits->site_templates->get_pre_selected_site_template();
} elseif ($limits->site_templates->get_mode() === 'choose_available_templates') {
$template_id = Checkout::get_instance()->request_or_session('template_id');
$extra['template_id'] = $this->is_template_available($products, $template_id) ? $template_id : false;
} // end if;
return $extra;
} // end maybe_force_template_selection_on_cart;
/**
* Check if site template is available in current limits
*
* @param array $products the list of products to check for limit.
* @param int $template_id the site template id.
* @return boolean
*/
protected function is_template_available($products, $template_id) {
$template_id = (int) $template_id;
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() === 'assign_template') {
return $limits->site_templates->get_pre_selected_site_template() === $template_id;
} else {
$available_templates = $limits->site_templates->get_available_site_templates();
return in_array($template_id, $available_templates, true);
} // end if;
} // end if;
return true;
} // end is_template_available;
} // end class Site_Template_Limits;

View File

@ -0,0 +1,400 @@
<?php
/**
* Handles limitations to post types, uploads and more.
*
* @package WP_Ultimo
* @subpackage Limits
* @since 2.0.0
*/
namespace WP_Ultimo\Limits;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Handles limitations to post types, uploads and more.
*
* @since 2.0.0
*/
class Theme_Limits {
use \WP_Ultimo\Traits\Singleton;
/**
* List of themes that are not available.
*
* @since 2.0.0
* @var array
*/
protected $themes_not_available = array();
/**
* Keep a cache of the results as the check is costly.
*
* @since 2.1.2
* @var null|false|string
*/
protected $forced_theme_stylesheet = null;
/**
* Keep a cache of the template results as the check is costly.
*
* @since 2.1.2
* @var null|false|string
*/
protected $forced_theme_template = null;
/**
* Runs on the first and only instantiation.
*
* @since 2.0.0
* @return void
*/
public function init() {
/**
* We need to bail if we're inside the WP CLI context and the
* `skip-plugins` flag is present.
*
* This is due to the fact that without WP Ultimo being loaded,
* the functions and classes we'll need to perform any kind of proper
* checks won't be available. To validate if we're being loaded or not,
* we check for the function `wu_get_product`.
*
* @since 2.1.0
*/
if (wu_cli_is_plugin_skipped('wp-ultimo')) {
return;
}
add_action('wu_sunrise_loaded', array($this, 'load_limitations'));
} // end init;
/**
* Apply limitations if they are available.
*
* @since 2.0.0
* @return void
*/
public function load_limitations() {
if (wu_get_current_site()->has_limitations()) {
add_filter('stylesheet', array($this, 'force_active_theme_stylesheet'));
add_filter('template', array($this, 'force_active_theme_template'));
add_filter('allowed_themes', array($this, 'add_extra_available_themes'));
add_filter('site_allowed_themes', array($this, 'add_extra_available_themes'));
add_filter('wp_prepare_themes_for_js', array($this, 'maybe_remove_activate_button'));
add_action('admin_enqueue_scripts', array($this, 'hacky_remove_activate_button'));
add_action('admin_footer-themes.php', array($this, 'modify_backbone_template'));
add_action('customize_changeset_save_data', array($this, 'prevent_theme_activation_on_customizer'), 99, 2);
} // end if;
} // end load_limitations;
/**
* Prevents sub-site admins from switching to locked themes inside the customizer.
*
* @since 2.0.10
*
* @param array $data The changeset array being saved.
* @param array $context The context array with tons of info about the current customizer session.
* @return array|void
*/
public function prevent_theme_activation_on_customizer($data, $context) {
if (wu_get_current_site()->has_limitations() === false) {
return $data;
} // end if;
$pending_theme_switch = $context['manager']->is_theme_active() === false;
if ($pending_theme_switch === false) {
return $data;
} // end if;
$new_theme = $context['manager']->theme()->stylesheet;
$theme_limitations = wu_get_current_site()->get_limitations()->themes;
if ($theme_limitations->allowed($new_theme, 'not_available')) {
$response = array(
'code' => 'not-available',
'message' => __('This theme is not available on your current plan.', 'wp-ultimo'),
);
wp_send_json($response, 'not-available');
exit;
} // end if;
return $data;
} // end prevent_theme_activation_on_customizer;
/**
* Removes the activate button from not available themes.
*
* This uses a very hack-y approach due to a bug on WordPress
* Core. The problem is that the WP code assumes that no one
* with the capability of activating themes would be unable
* to activate a theme (in cases of external factors for example).
*
* @todo Send patch to WordPress core.
* @since 2.0.0
* @return void
*/
public function hacky_remove_activate_button() {
global $pagenow;
if (!function_exists('wu_generate_upgrade_to_unlock_url')) {
return;
} // end if;
if ($pagenow !== 'themes.php') {
return;
} // end if;
$membership = wu_get_current_site()->get_membership();
if (!$membership) {
return;
} // end if;
$upgrade_button = wu_generate_upgrade_to_unlock_button(__('Upgrade to unlock', 'wp-ultimo'), array(
'module' => 'themes',
'type' => 'EXTENSION',
'classes' => 'button',
));
wp_localize_script('theme', 'wu_theme_settings', array(
'themes_not_available' => $this->themes_not_available,
'replacement_message' => $upgrade_button,
));
} // end hacky_remove_activate_button;
/**
* Modifies the default WordPress theme page template.
*
* @since 2.0.0
* @return void
*/
public function modify_backbone_template() { // phpcs:disable ?>
<script type="text/javascript">
if (typeof wu_theme_settings !== 'undefined') {
let content = document.getElementById("tmpl-theme").innerHTML;
content = content.replace(new RegExp('(<a class="button activate".*<\/a>)', 'g'), '<# if ( !wu_theme_settings.themes_not_available.includes(data.id) ) { #>$1<# } else { #> {{{ wu_theme_settings.replacement_message.replace("EXTENSION", data.id) }}} <# } #>');
document.getElementById("tmpl-theme").innerHTML = content;
} // end if;
</script>
<?php // phpcs:enable
} // end modify_backbone_template;
/**
* Checks if a theme needs to have the activate button removed.
*
* @since 2.0.0
*
* @param array $themes The list of themes available.
* @return array
*/
public function maybe_remove_activate_button($themes) {
if (is_main_site()) {
return $themes;
} // end if;
$theme_limitations = wu_get_current_site()->get_limitations()->themes;
foreach ($themes as $stylesheet => &$data) {
$data['notAvailable'] = false;
if ($theme_limitations->allowed($stylesheet, 'not_available')) {
$data['actions']['activate'] = '';
/*
* Hack solution due to core WP
* not allowing us to filter out the button.
*/
$data['notAvailable'] = true;
/*
* Adds to the not available list
* for our hack-y solution.
*/
$this->themes_not_available[] = $stylesheet;
} // end if;
} // end foreach;
return $themes;
} // end maybe_remove_activate_button;
/**
* Force the activation of one particularly selected theme.
*
* @since 2.0.0
*
* @param string $stylesheet The default theme being used.
* @return string
*/
public function force_active_theme_stylesheet($stylesheet) {
if (is_main_site()) {
return $stylesheet;
} // end if;
$forced_stylesheet = $this->get_forced_theme_stylesheet();
return $forced_stylesheet ? $forced_stylesheet : $stylesheet;
} // end force_active_theme_stylesheet;
/**
* Force the activation of one particularly selected theme.
*
* @since 2.1.2
*
* @param string $template The default theme being used.
* @return string
*/
public function force_active_theme_template($template) {
if (is_main_site()) {
return $template;
} // end if;
$forced_template = $this->get_forced_theme_template();
return $forced_template ? $forced_template : $template;
} // end force_active_theme_template;
/**
* Deactivates the plugins that people are not allowed to use.
*
* @since 2.0.0
*
* @param array $themes Array with the plugins activated.
* @return array
*/
public function add_extra_available_themes($themes) {
/*
* Bail on network admin =)
*/
if (is_network_admin()) {
return $themes;
} // end if;
$theme_limitations = wu_get_current_site()->get_limitations()->themes;
$_themes = $theme_limitations->get_all_themes();
foreach ($_themes as $theme_stylesheet) {
$should_appear = $theme_limitations->allowed($theme_stylesheet, 'visible');
if (!$should_appear && isset($themes[$theme_stylesheet])) {
unset($themes[$theme_stylesheet]);
} elseif ($should_appear && !isset($themes[$theme_stylesheet])) {
$themes[] = $theme_stylesheet;
} // end if;
} // end foreach;
return $themes;
} // end add_extra_available_themes;
/**
* Get the stylesheet of the theme that is forced to be active.
*
* @since 2.1.2
*
* @return string|bool The stylesheet of the theme that is forced to be active or false.
*/
protected function get_forced_theme_stylesheet() {
if ($this->forced_theme_stylesheet === null) {
$this->forced_theme_stylesheet = wu_get_current_site()->get_limitations()->themes->get_forced_active_theme();
} // end if;
return $this->forced_theme_stylesheet;
} // end get_forced_theme_stylesheet;
/**
* Get the template of the theme that is forced to be active.
*
* @since 2.1.2
*
* @return string|bool The template of the theme that is forced to be active or false.
*/
protected function get_forced_theme_template() {
if ($this->forced_theme_template === null) {
$stylesheet = $this->get_forced_theme_stylesheet();
$this->forced_theme_template = $stylesheet ? wp_get_theme($stylesheet)->get_template() : false;
} // end if;
return $this->forced_theme_template;
} // end get_forced_theme_template;
} // end class Theme_Limits;

View File

@ -0,0 +1,44 @@
<?php
/**
* Handles trial limitations.
*
* @package WP_Ultimo
* @subpackage Limits
* @since 2.0.0
*/
namespace WP_Ultimo\Limits;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Handles trial limitations.
*
* @since 2.0.0
*/
class Trial_Limits {
use \WP_Ultimo\Traits\Singleton;
/**
* Runs on the first and only instantiation.
*
* @since 2.0.0
* @return void
*/
public function init() {
} // end init;
/**
* Apply limitations if they are available.
*
* @since 2.0.0
* @return void
*/
public function load_limitations() {
} // end load_limitations;
} // end class Trial_Limits;