diff --git a/inc/class-ajax.php b/inc/class-ajax.php index 3ee1779fe..a8ef00692 100644 --- a/inc/class-ajax.php +++ b/inc/class-ajax.php @@ -153,6 +153,9 @@ public function search_models(): void { $results = []; + // Merge the caller's query params (search term, etc.) with locally-built params. + $query = array_merge($args['query'], $query ?? []); + if ('user' === $args['model']) { $results = $this->search_wordpress_users($query); } elseif ('page' === $args['model']) { diff --git a/inc/objects/class-limitations.php b/inc/objects/class-limitations.php index 8172e7a5e..9b584c377 100644 --- a/inc/objects/class-limitations.php +++ b/inc/objects/class-limitations.php @@ -317,6 +317,13 @@ protected function merge_recursive(array &$array1, array &$array2, $should_sum = * We use values 0 or '' as unlimited in our limits */ continue; + } elseif ($should_sum && is_null($value) && ! is_null($original_value)) { + /* + * Null values should not overwrite existing values in additive mode. + * A null limit means "no restriction configured" and should not + * remove restrictions from previously merged limitations. + */ + continue; } elseif (isset($array1[ $key ]) && is_numeric($array1[ $key ]) && is_numeric($value) && $should_sum && ! $is_unlimited) { $array1[ $key ] = ((int) $array1[ $key ]) + $value; } elseif ('visibility' === $key && isset($array1[ $key ]) && $should_sum) { @@ -354,8 +361,6 @@ protected function merge_recursive(array &$array1, array &$array2, $should_sum = // Avoid change true values $array1[ $key ] = true !== $original_value || ! $should_sum ? $value : true; - - $array1[ $key ] = true !== $original_value || ! $should_sum ? $value : true; } } } @@ -463,13 +468,13 @@ public static function remove_limitations($slug, $id): void { */ public static function get_empty() { - $limitations = new self(); + $modules_data = []; - foreach (array_keys(self::repository()) as $module_name) { - $limitations->{$module_name}; + foreach (self::repository() as $module_name => $class_name) { + $modules_data[ $module_name ] = $class_name::default_state(); } - return $limitations; + return new self($modules_data); } /** diff --git a/patches/mpdf-mpdf-psr-log-aware-trait-void-return.patch b/patches/mpdf-mpdf-psr-log-aware-trait-void-return.patch new file mode 100644 index 000000000..585dfbc5c --- /dev/null +++ b/patches/mpdf-mpdf-psr-log-aware-trait-void-return.patch @@ -0,0 +1,11 @@ +--- a/src/PsrLogAwareTrait.php ++++ b/src/PsrLogAwareTrait.php +@@ -12,7 +12,7 @@ + */ + protected $logger; + +- public function setLogger(LoggerInterface $logger) ++ public function setLogger(LoggerInterface $logger): void + { + $this->logger = $logger; + } diff --git a/tests/WP_Ultimo/Objects/Limitations_Test.php b/tests/WP_Ultimo/Objects/Limitations_Test.php index 7a7b3f4aa..9fba84a54 100644 --- a/tests/WP_Ultimo/Objects/Limitations_Test.php +++ b/tests/WP_Ultimo/Objects/Limitations_Test.php @@ -783,4 +783,131 @@ public function test_merge_recursive_behavior_priority(): void { $this->assertEquals('force_active', $array1['behavior']); } + + /** + * Test that null limit from addon product does not overwrite plan template list during additive merge. + * + * Regression test: when a plan with configured site templates was merged with an addon product + * that had site_templates.limit = null, the null would overwrite the plan's template list, + * causing "The selected template is not available for this product" validation error. + */ + public function test_merge_null_limit_does_not_overwrite_template_list(): void { + + $plan_limitations = new Limitations([ + 'site_templates' => [ + 'enabled' => true, + 'mode' => 'default', + 'limit' => [ + '2' => ['behavior' => 'available'], + '3' => ['behavior' => 'pre_selected'], + ], + ], + ]); + + $addon_limitations = new Limitations([ + 'site_templates' => [ + 'enabled' => true, + 'mode' => 'default', + 'limit' => null, + ], + ]); + + $merged = $plan_limitations->merge($addon_limitations); + + $available = $merged->site_templates->get_available_site_templates(); + + $this->assertContains(2, $available, 'Template 2 should still be available after merging addon with null limit'); + $this->assertContains(3, $available, 'Template 3 should still be available after merging addon with null limit'); + } + + /** + * Test that null limit does overwrite in override mode. + */ + public function test_merge_null_limit_overwrites_in_override_mode(): void { + + $base = new Limitations([ + 'users' => [ + 'enabled' => true, + 'limit' => 5, + ], + ]); + + $override = new Limitations([ + 'users' => [ + 'enabled' => true, + 'limit' => null, + ], + ]); + + $merged = $base->merge(true, $override); + + $this->assertNull($merged->users->get_limit(), 'Null limit should overwrite in override mode'); + } + + /** + * Test that get_empty returns proper default data in to_array. + */ + public function test_get_empty_to_array_returns_default_states(): void { + + $empty = Limitations::get_empty(); + + $array = $empty->to_array(); + + $this->assertNotEmpty($array, 'get_empty()->to_array() should not be empty'); + $this->assertArrayHasKey('site_templates', $array); + $this->assertArrayHasKey('plugins', $array); + $this->assertArrayHasKey('users', $array); + } + + /** + * Test full checkout scenario: plan templates preserved when merging with addon product. + * + * Simulates the validation rule in class-site-template.php where all product + * limitations are merged together to determine available templates. + */ + public function test_checkout_plan_plus_addon_preserves_templates(): void { + + // Plan with specific templates configured + $plan_data = new Limitations([ + 'site_templates' => [ + 'enabled' => true, + 'mode' => 'choose_available_templates', + 'limit' => [ + '5' => ['behavior' => 'available'], + '7' => ['behavior' => 'available'], + '9' => ['behavior' => 'not_available'], + ], + ], + 'disk_space' => [ + 'enabled' => true, + 'limit' => 500, + ], + ]); + + // Addon product with no template restrictions but some disk space + $addon_data = new Limitations([ + 'site_templates' => [ + 'enabled' => true, + 'mode' => 'default', + 'limit' => null, + ], + 'disk_space' => [ + 'enabled' => true, + 'limit' => 100, + ], + ]); + + // Simulate the validation rule merge + $limits = new Limitations([]); + $limits = $limits->merge($plan_data); + $limits = $limits->merge($addon_data); + + $available = $limits->site_templates->get_available_site_templates(); + + $this->assertContains(5, $available, 'Template 5 should be available'); + $this->assertContains(7, $available, 'Template 7 should be available'); + + // Disk space should be additive + $this->assertEquals(600, $limits->disk_space->get_limit(), 'Disk space should be summed'); + } }