Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
php-version: ["7.4", "8.0", "8.1", "8.2", "8.3", "8.4"]
php-version: ["7.4", "8.0", "8.1", "8.2", "8.3", "8.4", "8.5"]
services:
mysql:
image: mysql:5.7
image: mariadb:11.4
env:
MYSQL_ROOT_PASSWORD: root
ports: [3306]
options: >-
--health-cmd="mysqladmin ping --silent"
--health-cmd="healthcheck.sh --connect --innodb_initialized"
--health-interval=10s
--health-timeout=5s
--health-retries=5
Expand Down
12 changes: 7 additions & 5 deletions inc/admin-pages/class-product-edit-admin-page.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
// Exit if accessed directly
defined('ABSPATH') || exit;

use WP_Ultimo\Limitations\Limit_Site_Templates;
use WP_Ultimo\Limits\Site_Template_Limits;
use WP_Ultimo\Models\Product;
use WP_Ultimo\Database\Products\Product_Type;

Expand Down Expand Up @@ -883,11 +885,11 @@ protected function get_product_option_sections() {
'placeholder' => __('Site Template Selection Mode', 'ultimate-multisite'),
'desc' => __('Select the type of limitation you want to apply.', 'ultimate-multisite'),
'tooltip' => __('"Default" will follow the settings of the checkout form: if you have a template selection field in there, all the templates selected will show up. If no field is present, then a default WordPress site will be created. <br><br>"Assign Site Template" forces new accounts with this plan to use a particular template site (this option removes the template selection field from the signup, if one exists). <br><br>Finally, "Choose Available Site Templates", overrides the templates selected on the checkout form with the templates selected here, while also giving you the chance of pre-select a template to be used as default.', 'ultimate-multisite'),
'value' => 'default',
'value' => Limit_Site_Templates::MODE_DEFAULT,
'options' => [
'default' => __('Default - Allow All Site Templates', 'ultimate-multisite'),
'assign_template' => __('Assign Site Template', 'ultimate-multisite'),
'choose_available_templates' => __('Choose Available Site Templates', 'ultimate-multisite'),
Limit_Site_Templates::MODE_DEFAULT => __('Default - Allow All Site Templates', 'ultimate-multisite'),
Limit_Site_Templates::MODE_ASSIGN_TEMPLATE => __('Assign Site Template', 'ultimate-multisite'),
Limit_Site_Templates::MODE_CHOOSE_AVAILABLE_TEMPLATES => __('Choose Available Site Templates', 'ultimate-multisite'),
],
'html_attr' => [
'v-model' => 'site_template_selection_mode',
Expand All @@ -900,7 +902,7 @@ protected function get_product_option_sections() {
'templates' => [
'type' => 'html',
'title' => __('Site Templates', 'ultimate-multisite'),
'desc' => esc_attr(sprintf('{{ site_template_selection_mode === "assign_template" ? "%s" : "%s" }}', __('Select the Site Template to assign.', 'ultimate-multisite'), __('Customize the access level of each Site Template below.', 'ultimate-multisite'))),
'desc' => esc_attr(sprintf('{{ site_template_selection_mode === "' . Limit_Site_Templates::MODE_ASSIGN_TEMPLATE . '" ? "%s" : "%s" }}', __('Select the Site Template to assign.', 'ultimate-multisite'), __('Customize the access level of each Site Template below.', 'ultimate-multisite'))),
'wrapper_html_attr' => [
'v-cloak' => '1',
'v-show' => "allow_site_templates && site_template_selection_mode !== 'default'",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,12 +144,13 @@ public function output(): void {
* Renders the base edit page layout, with the columns and everything else =)
*/
wu_get_template(
'base/centered',
'base/dash',
[
'screen' => get_current_screen(),
'page' => $this,
'content' => '',
'labels' => [
'screen' => get_current_screen(),
'page' => $this,
'has_full_position' => false,
'content' => '',
'labels' => [
'updated_message' => __('Template switched successfully!', 'ultimate-multisite'),
],
]
Expand All @@ -163,7 +164,7 @@ public function output(): void {
* @return void
*/
public function register_widgets(): void {

\WP_Ultimo\UI\Template_Switching_Element::get_instance()->as_metabox(get_current_screen()->id);
\WP_Ultimo\UI\Simple_Text_Element::get_instance()->as_inline_content(get_current_screen()->id, 'wu_dash_before_metaboxes');
\WP_Ultimo\UI\Template_Switching_Element::get_instance()->as_inline_content(get_current_screen()->id, 'wu_dash_before_metaboxes');
Comment on lines 166 to +168
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n inc/admin-pages/customer-panel/class-template-switching-admin-page.php

Repository: Multisite-Ultimate/ultimate-multisite

Length of output: 5057


🏁 Script executed:

find . -name "*Base_Customer_Facing_Admin_Page*" -o -name "*base-customer*" | head -20

Repository: Multisite-Ultimate/ultimate-multisite

Length of output: 140


🏁 Script executed:

cat -n ./inc/admin-pages/class-base-customer-facing-admin-page.php | head -300

Repository: Multisite-Ultimate/ultimate-multisite

Length of output: 9583


🏁 Script executed:

find . -name "*Base_Admin_Page*" -o -name "*base-admin-page*" | head -5

Repository: Multisite-Ultimate/ultimate-multisite

Length of output: 124


🏁 Script executed:

rg -n "register_widgets" ./inc/admin-pages/class-base-admin-page.php -A 5

Repository: Multisite-Ultimate/ultimate-multisite

Length of output: 494


🏁 Script executed:

rg "get_current_screen()" --max-count=20 -B 2

Repository: Multisite-Ultimate/ultimate-multisite

Length of output: 19028


🏁 Script executed:

cat -n inc/ui/class-base-element.php | grep -A 30 "public function as_inline_content"

Repository: Multisite-Ultimate/ultimate-multisite

Length of output: 1060


🏁 Script executed:

rg "register_widgets" -A 10 inc/admin-pages/customer-panel/ | head -100

Repository: Multisite-Ultimate/ultimate-multisite

Length of output: 4904


Add null-check before dereferencing get_current_screen().

Directly calling get_current_screen()->id can cause a fatal error if the screen object is unavailable. While get_current_screen() is typically available at the "load-{hook}" action, adding a defensive null-check is safer:

 public function register_widgets(): void {
-	\WP_Ultimo\UI\Simple_Text_Element::get_instance()->as_inline_content(get_current_screen()->id, 'wu_dash_before_metaboxes');
-	\WP_Ultimo\UI\Template_Switching_Element::get_instance()->as_inline_content(get_current_screen()->id, 'wu_dash_before_metaboxes');
+	$screen = get_current_screen();
+	if ( ! $screen) {
+		return;
+	}
+
+	\WP_Ultimo\UI\Simple_Text_Element::get_instance()->as_inline_content($screen->id, 'wu_dash_before_metaboxes');
+	\WP_Ultimo\UI\Template_Switching_Element::get_instance()->as_inline_content($screen->id, 'wu_dash_before_metaboxes');
 }
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public function register_widgets(): void {
\WP_Ultimo\UI\Template_Switching_Element::get_instance()->as_metabox(get_current_screen()->id);
\WP_Ultimo\UI\Simple_Text_Element::get_instance()->as_inline_content(get_current_screen()->id, 'wu_dash_before_metaboxes');
\WP_Ultimo\UI\Template_Switching_Element::get_instance()->as_inline_content(get_current_screen()->id, 'wu_dash_before_metaboxes');
public function register_widgets(): void {
$screen = get_current_screen();
if ( ! $screen) {
return;
}
\WP_Ultimo\UI\Simple_Text_Element::get_instance()->as_inline_content($screen->id, 'wu_dash_before_metaboxes');
\WP_Ultimo\UI\Template_Switching_Element::get_instance()->as_inline_content($screen->id, 'wu_dash_before_metaboxes');
}
πŸ€– Prompt for AI Agents
In inc/admin-pages/customer-panel/class-template-switching-admin-page.php around
lines 166 to 168, the code dereferences get_current_screen()->id without
checking for null which can cause a fatal error; add a defensive null-check by
calling get_current_screen() into a local variable, verify it's not null (and
optionally that it has an id property), and only call as_inline_content when the
screen object is present; if absent, skip registering the widgets or bail early
with a safe fallback.

}
}
8 changes: 7 additions & 1 deletion inc/class-addon-repository.php
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ public function upgrader_pre_download(bool $reply, $package, \WP_Upgrader $upgra

if (empty($access_token)) {
// translators: %s the url for login.
return new \WP_Error('noauth', sprintf(__('You must <a href="%s" target="_parent">Login</a> first.', 'ultimate-multisite'), $this->get_oauth_url()));
return new \WP_Error('noauth', sprintf(__('You must <a href="%s" target="_parent">Connect to UltimateMultisite.com</a> first.', 'ultimate-multisite'), $this->get_oauth_url()));
}
$this->authorization_header = 'Bearer ' . $access_token;

Expand All @@ -179,6 +179,10 @@ public function upgrader_pre_download(bool $reply, $package, \WP_Upgrader $upgra
return $response;
}

if (403 === absint($code)) {
return new \WP_Error('http_request_failed', esc_html__('403 Access Denied returned from server. Ensure you have an active subscription for this addon.', 'ultimate-multisite'));
}

if (! in_array(absint($code), [200, 302, 301], true)) {
return new \WP_Error('http_request_failed', esc_html__('Failed to connect to the update server. Please try again later.', 'ultimate-multisite'));
}
Expand Down Expand Up @@ -232,6 +236,7 @@ public function save_access_token($code, $redirect_url) {
'dismissible' => true,
]
);
delete_site_transient('wu-addons-list');
} else {
wp_admin_notice(
__('Failed to authenticate with UltimateMultisite.com.', 'ultimate-multisite'),
Expand Down Expand Up @@ -262,5 +267,6 @@ public function set_update_download_headers(array $parsed_args, string $url = ''
public function delete_tokens(): void {
wu_delete_option('wu-refresh-token');
delete_transient('wu-access-token');
delete_site_transient('wu-addons-list');
}
}
6 changes: 3 additions & 3 deletions inc/database/customers/class-customers-table.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ final class Customers_Table extends Table {
* @var array
*/
protected $upgrades = [
'2.0.1-revision.20210508' => 20_210_508,
'2.0.1-revision.20210607' => 20_210_607,
'2.0.1-revision.20230601' => 20_230_601,
'2.0.1-revision.20210508' => 20210508,
'2.0.1-revision.20210607' => 20210607,
'2.0.1-revision.20230601' => 20230601,
];

/**
Expand Down
15 changes: 2 additions & 13 deletions inc/database/domains/class-domains-table.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,22 +54,11 @@ final class Domains_Table extends Table {
'2.0.1-revision.20230601' => 20_230_601,
];

/**
* Domains constructor.
*
* @access public
* @since 2.0.0
* @return void
*/
public function __construct() {

parent::__construct();
}

/**
* Setup the database schema
* Set up the database schema
*
* @access protected
* @acces s protected
Comment on lines +59 to +61
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor

Fix typo in docblock tag.

Line 61 has a typo: @acces s protected should be @access protected (remove the space).

Apply this diff to fix the typo:

-	 * @acces s protected
+	 * @access protected
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
* Set up the database schema
*
* @access protected
* @acces s protected
* Set up the database schema
*
* @access protected
πŸ€– Prompt for AI Agents
In inc/database/domains/class-domains-table.php around lines 59 to 61, the
docblock contains a typo: the tag reads "@acces s protected"; update it to the
correct tag "@access protected" by removing the stray space so the docblock
properly documents the visibility.

* @since 2.0.0
* @return void
*/
Expand Down
23 changes: 13 additions & 10 deletions inc/duplication/data.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@
$schema = DB_NAME;

// Get sources Tables
if (MUCD_PRIMARY_SITE_ID == $from_site_id) {
if ((int) MUCD_PRIMARY_SITE_ID === (int) $from_site_id) {
$from_site_table = self::get_primary_tables($from_site_prefix);
} else {
$sql_query = $wpdb->prepare('SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = %s AND TABLE_NAME LIKE %s', $schema, $from_site_prefix_like . '%');
Expand All @@ -119,6 +119,8 @@

$table_name = $to_site_prefix . $table_base_name;

$wpdb->get_results('SET foreign_key_checks = 0'); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching

// Drop table if exists
self::do_sql_query('DROP TABLE IF EXISTS `' . $table_name . '`');

Expand All @@ -129,8 +131,6 @@

$create_statement_sql = str_replace($from_site_prefix, $to_site_prefix, (string) $create_statement[1]);

$wpdb->get_results('SET foreign_key_checks = 0'); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching

self::do_sql_query($create_statement_sql);

// Populate database with data from source table
Expand Down Expand Up @@ -181,17 +181,19 @@
// Looking for uploads dirs
switch_to_blog($from_site_id);

$dir = wp_upload_dir();
$from_upload_url = str_replace(network_site_url(), get_bloginfo('url') . '/', $dir['baseurl']);
$from_blog_url = get_blog_option($from_site_id, 'siteurl');
$dir = wp_upload_dir();
$from_upload_url_w_network = $dir['baseurl'];
$from_upload_url = str_replace(network_site_url(), get_bloginfo('url') . '/', $dir['baseurl']);
$from_blog_url = get_blog_option($from_site_id, 'siteurl');

restore_current_blog();

switch_to_blog($to_site_id);

$dir = wp_upload_dir();
$to_upload_url = str_replace(network_site_url(), get_bloginfo('url') . '/', $dir['baseurl']);
$to_blog_url = get_blog_option($to_site_id, 'siteurl');
$dir = wp_upload_dir();
$to_upload_url_w_network = $dir['baseurl'];
$to_upload_url = str_replace(network_site_url(), get_bloginfo('url') . '/', $dir['baseurl']);
$to_blog_url = get_blog_option($to_site_id, 'siteurl');

restore_current_blog();

Expand Down Expand Up @@ -229,6 +231,7 @@

$string_to_replace = [
wu_replace_scheme($from_upload_url) => wu_replace_scheme($to_upload_url),
wu_replace_scheme($from_upload_url_w_network) => wu_replace_scheme($to_upload_url_w_network),
wu_replace_scheme($from_blog_url) => wu_replace_scheme($to_blog_url),
$from_site_prefix => $to_site_prefix,
];
Expand Down Expand Up @@ -258,7 +261,7 @@
foreach ( $saved_options as $option_name => $option_value ) {
try {
update_option($option_name, $option_value);
} catch (\Throwable $exception) {
} catch (\Throwable $exception) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
// ...nothing
}
}
Expand Down Expand Up @@ -369,11 +372,11 @@
public static function try_replace($row, $field, $from_string, $to_string) {
if (is_serialized($row[ $field ])) {
$double_serialize = false;
$row[ $field ] = @unserialize($row[ $field ]);

Check warning on line 375 in inc/duplication/data.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

unserialize() found. Serialized data has known vulnerability problems with Object Injection. JSON is generally a better approach for serializing data. See https://www.owasp.org/index.php/PHP_Object_Injection

Check warning on line 375 in inc/duplication/data.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Silencing errors is strongly discouraged. Use proper error checking instead. Found: @unserialize($row[ ...

// FOR SERIALISED OPTIONS, like in wp_carousel plugin
if (is_serialized($row[ $field ])) {
$row[ $field ] = @unserialize($row[ $field ]);

Check warning on line 379 in inc/duplication/data.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

unserialize() found. Serialized data has known vulnerability problems with Object Injection. JSON is generally a better approach for serializing data. See https://www.owasp.org/index.php/PHP_Object_Injection

Check warning on line 379 in inc/duplication/data.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Silencing errors is strongly discouraged. Use proper error checking instead. Found: @unserialize($row[ ...
$double_serialize = true;
}

Expand All @@ -393,11 +396,11 @@
$row[ $field ] = self::replace($row[ $field ], $from_string, $to_string);
}

$row[ $field ] = serialize($row[ $field ]);

Check warning on line 399 in inc/duplication/data.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

serialize() found. Serialized data has known vulnerability problems with Object Injection. JSON is generally a better approach for serializing data. See https://www.owasp.org/index.php/PHP_Object_Injection

// Pour des options comme wp_carousel...
if ($double_serialize) {
$row[ $field ] = serialize($row[ $field ]);

Check warning on line 403 in inc/duplication/data.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

serialize() found. Serialized data has known vulnerability problems with Object Injection. JSON is generally a better approach for serializing data. See https://www.owasp.org/index.php/PHP_Object_Injection
}
} else {
$row[ $field ] = self::replace($row[ $field ], $from_string, $to_string);
Expand Down Expand Up @@ -446,7 +449,7 @@
MUCD_Duplicate::write_log('Result :' . var_export($results, true)); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export
}

if ('' != $wpdb->last_error) {

Check warning on line 452 in inc/duplication/data.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Loose comparisons are not allowed. Expected: "!=="; Found: "!="
self::sql_error($sql_query, $wpdb->last_error);
}

Expand Down
12 changes: 10 additions & 2 deletions inc/helpers/class-site-duplicator.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,16 +95,24 @@ public static function override_site($from_site_id, $to_site_id, $args = []) {

$to_site = wu_get_site($to_site_id);

if (! $to_site) {
wu_log_add('site-duplication', sprintf('Target site %d not found', $to_site_id), LogLevel::ERROR);
return false;
}

$to_site_membership_id = $to_site->get_membership_id();

$to_site_membership = $to_site->get_membership();

$to_site_customer = $to_site_membership->get_customer();
$to_site_customer = $to_site_membership ? $to_site_membership->get_customer() : false;

// Determine email - use customer email if available, otherwise use site admin email
$email = $to_site_customer ? $to_site_customer->get_email_address() : get_blog_option($to_site_id, 'admin_email');

$args = wp_parse_args(
$args,
[
'email' => $to_site_customer->get_email_address(),
'email' => $email,
'title' => $to_site->get_title(),
'path' => $to_site->get_path(),
'from_site_id' => $from_site_id,
Expand Down
9 changes: 5 additions & 4 deletions inc/installers/class-migrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use WP_Error;
use WP_Ultimo\Async_Calls;
use WP_Ultimo\Contracts\Session;
use WP_Ultimo\Limitations\Limit_Site_Templates;
use WP_Ultimo\Traits\Singleton;
use WP_Ultimo\UI\Template_Previewer;
use WP_Ultimo\Models\Checkout_Form;
Expand Down Expand Up @@ -393,7 +394,7 @@ public function get_steps($force_all = false) {
*
* @since 2.0.0
* @param array $steps The list of steps.
* @param \WP_Ultimo\Installers\Migrator $this This class.
* @param \WP_Ultimo\Installers\Migrator $migrator The Migrator class.
*/
$steps = apply_filters('wu_get_migration_steps', $steps, $this);

Expand Down Expand Up @@ -1158,12 +1159,12 @@ protected function _install_products() {
$force_template = get_post_meta($plan->ID, 'wpu_site_template', true);

if ($force_template && wu_get_site($force_template)) {
$site_template_mode = 'assign_template';
$site_template_mode = Limit_Site_Templates::MODE_ASSIGN_TEMPLATE;
}

$has_custom_template_list = false;
} elseif ($has_custom_template_list) {
$site_template_mode = 'choose_available_templates';
$site_template_mode = Limit_Site_Templates::MODE_CHOOSE_AVAILABLE_TEMPLATES;

if (empty($templates)) {
$site_template_enabled = false;
Expand Down Expand Up @@ -2590,4 +2591,4 @@ protected function _install_other() {
}
}
}
// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery
// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery
Loading
Loading