diff --git a/inc/admin-pages/class-broadcast-list-admin-page.php b/inc/admin-pages/class-broadcast-list-admin-page.php index 50428ae17..42e8b3bfd 100644 --- a/inc/admin-pages/class-broadcast-list-admin-page.php +++ b/inc/admin-pages/class-broadcast-list-admin-page.php @@ -294,7 +294,9 @@ public function render_add_new_broadcast_modal(): void { ], 'step_note' => [ 'type' => 'note', - 'desc' => sprintf('%s', __('← Back to Type Selection', 'ultimate-multisite')), + 'desc' => function () { + printf('%s', esc_html__('← Back to Type Selection', 'ultimate-multisite')); + }, 'wrapper_html_attr' => [ 'v-show' => 'step === 2', ], @@ -352,7 +354,9 @@ public function render_add_new_broadcast_modal(): void { ], 'step_note_2' => [ 'type' => 'note', - 'desc' => sprintf('%s', __('← Back to Target Selection', 'ultimate-multisite')), + 'desc' => function () { + printf('%s', esc_html__('← Back to Target Selection', 'ultimate-multisite')); + }, 'wrapper_html_attr' => [ 'v-show' => 'step === 3', ], diff --git a/inc/admin-pages/class-checkout-form-edit-admin-page.php b/inc/admin-pages/class-checkout-form-edit-admin-page.php index fcb5e1e15..c0e78827b 100644 --- a/inc/admin-pages/class-checkout-form-edit-admin-page.php +++ b/inc/admin-pages/class-checkout-form-edit-admin-page.php @@ -408,7 +408,9 @@ public function get_create_field_fields($attributes = []) { 'type_note' => [ 'type' => 'note', 'order' => 0, - 'desc' => sprintf('%s', __('← Back to Field Type Selection', 'ultimate-multisite')), + 'desc' => function () { + printf('%s', esc_html__('← Back to Field Type Selection', 'ultimate-multisite')); + }, 'wrapper_html_attr' => [ 'v-show' => 'type && (!saved && !name)', 'v-cloak' => '1', diff --git a/inc/admin-pages/class-customer-edit-admin-page.php b/inc/admin-pages/class-customer-edit-admin-page.php index b7455cbd7..44b1a9ffb 100644 --- a/inc/admin-pages/class-customer-edit-admin-page.php +++ b/inc/admin-pages/class-customer-edit-admin-page.php @@ -107,11 +107,11 @@ public function page_loaded() { // Handle delete meta field action if (isset($_GET['delete_meta_key']) && isset($_GET['_wpnonce'])) { $meta_key = sanitize_key($_GET['delete_meta_key']); - $nonce = sanitize_text_field($_GET['_wpnonce']); + $nonce = sanitize_text_field(wp_unslash($_GET['_wpnonce'])); // Verify nonce for security if ( ! wp_verify_nonce($nonce, 'delete_customer_meta_' . $meta_key)) { - wp_die(__('Security check failed. Please try again.', 'ultimate-multisite')); + wp_die(esc_html__('Security check failed. Please try again.', 'ultimate-multisite')); } $customer = $this->get_object(); @@ -586,10 +586,12 @@ public function generate_customer_meta_fields() { 'fields' => [ 'new_meta_remove' => [ 'type' => 'note', - 'desc' => sprintf( - '', - __('Remove', 'ultimate-multisite') - ), + 'desc' => function () { + printf( + '', + esc_html__('Remove', 'ultimate-multisite') + ); + }, 'wrapper_classes' => 'wu-absolute wu-top-0 wu-right-0', ], 'new_meta_slug' => [ diff --git a/inc/admin-pages/class-product-edit-admin-page.php b/inc/admin-pages/class-product-edit-admin-page.php index dea4a7f21..de4dfec77 100644 --- a/inc/admin-pages/class-product-edit-admin-page.php +++ b/inc/admin-pages/class-product-edit-admin-page.php @@ -754,7 +754,14 @@ protected function get_product_option_sections() { 'fields' => [ 'price_variations_remove' => [ 'type' => 'note', - 'desc' => sprintf('', esc_html__('Remove', 'ultimate-multisite')), + 'desc' => function () { + printf( + ' + + ', + esc_html__('Remove', 'ultimate-multisite') + ); + }, 'wrapper_classes' => 'wu-absolute wu-top-0 wu-right-0', ], 'price_variations_duration' => [ @@ -921,13 +928,13 @@ protected function get_product_option_sections() { * @since 2.0.0 * * @param \WP_Ultimo\Models\Product $product The product being edited. - * @return string + * @return void */ public function get_site_template_selection_list($product) { $all_templates = wu_get_site_templates(); - return wu_get_template_contents( + wu_get_template( 'limitations/site-template-selector', [ 'templates' => $all_templates, diff --git a/inc/admin-pages/class-settings-admin-page.php b/inc/admin-pages/class-settings-admin-page.php index e00b55d75..7b2d678ff 100644 --- a/inc/admin-pages/class-settings-admin-page.php +++ b/inc/admin-pages/class-settings-admin-page.php @@ -538,7 +538,7 @@ public function default_handler(): void { if (isset($_POST[ $field ])) { // phpcs:ignore WordPress.Security.NonceVerification $value = wp_unslash($_POST[ $field ]); // phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized if (is_array($value)) { - $filtered_data[ $field ] = array_map('sanitize_text_field', $value); + $filtered_data[ $field ] = wu_clean($value); } elseif ( ! empty($field_data['allow_html'])) { $filtered_data[ $field ] = sanitize_post_field('post_content', $value, $this->get_id(), 'db'); } else { diff --git a/inc/checkout/signup-fields/class-signup-field-period-selection.php b/inc/checkout/signup-fields/class-signup-field-period-selection.php index 88386baab..30e84f13e 100644 --- a/inc/checkout/signup-fields/class-signup-field-period-selection.php +++ b/inc/checkout/signup-fields/class-signup-field-period-selection.php @@ -212,7 +212,9 @@ public function get_fields() { 'fields' => [ 'period_options_remove' => [ 'type' => 'note', - 'desc' => sprintf('', __('Remove', 'ultimate-multisite')), + 'desc' => function () { + printf('', esc_html__('Remove', 'ultimate-multisite')); + }, 'wrapper_classes' => 'wu-absolute wu-top-0 wu-right-0', ], 'period_options_duration' => [ diff --git a/inc/checkout/signup-fields/class-signup-field-select.php b/inc/checkout/signup-fields/class-signup-field-select.php index 6ad033718..a94cdd351 100644 --- a/inc/checkout/signup-fields/class-signup-field-select.php +++ b/inc/checkout/signup-fields/class-signup-field-select.php @@ -175,7 +175,9 @@ public function get_fields() { 'fields' => [ 'options_remove' => [ 'type' => 'note', - 'desc' => sprintf('', __('Remove', 'ultimate-multisite')), + 'desc' => function () { + printf('', esc_html__('Remove', 'ultimate-multisite')); + }, 'wrapper_classes' => 'wu-absolute wu-top-0 wu-right-0', ], 'options_key' => [ diff --git a/inc/class-api.php b/inc/class-api.php index d4e9d7d7c..e29993035 100644 --- a/inc/class-api.php +++ b/inc/class-api.php @@ -229,7 +229,7 @@ public function add_settings(): void { 'api', 'api_note', [ - 'desc' => __('This is your API Key. You cannot change it directly. To reset the API key and secret, use the button "Refresh API credentials" below.', 'ultimate-multisite'), + 'desc' => fn() => esc_html__('This is your API Key. You cannot change it directly. To reset the API key and secret, use the button "Refresh API credentials" below.', 'ultimate-multisite'), 'type' => 'note', 'classes' => 'wu-text-gray-700 wu-text-xs', 'wrapper_classes' => 'wu-bg-white sm:wu-border-t-0 sm:wu-mt-0 sm:wu-pt-0', diff --git a/inc/class-orphaned-tables-manager.php b/inc/class-orphaned-tables-manager.php index f85164dca..3d8298d9a 100644 --- a/inc/class-orphaned-tables-manager.php +++ b/inc/class-orphaned-tables-manager.php @@ -112,34 +112,36 @@ public function render_orphaned_tables_delete_modal(): void { return; } - $table_list = '
'; - foreach ($orphaned_tables as $table) { - $table_list .= '
' . esc_html($table) . '
'; - } - $table_list .= '
'; - $fields = [ 'confirmation' => [ 'type' => 'note', - 'desc' => sprintf( - '
+ 'desc' => function () use ($orphaned_tables, $table_count) { + printf( + '

%s

-

%s

- %s -

- %s %s -

-
', - sprintf( +

%s

', + sprintf( /* translators: %d: number of orphaned tables */ - esc_html(_n('Confirm Deletion of %d Orphaned Table', 'Confirm Deletion of %d Orphaned Tables', $table_count, 'ultimate-multisite')), - $table_count - ), - esc_html__('You are about to permanently delete the following database tables:', 'ultimate-multisite'), - $table_list, - esc_html__('Warning:', 'ultimate-multisite'), - esc_html__('This action cannot be undone. Please ensure you have a database backup before proceeding.', 'ultimate-multisite') - ), + esc_html(_n('Confirm Deletion of %d Orphaned Table', 'Confirm Deletion of %d Orphaned Tables', $table_count, 'ultimate-multisite')), + esc_html($table_count) + ), + esc_html__('You are about to permanently delete the following database tables:', 'ultimate-multisite'), + ); + + echo '
'; + foreach ($orphaned_tables as $table) { + echo '
' . esc_html($table) . '
'; + } + echo '
'; + printf( + '

+ %s %s +

', + esc_html__('Warning:', 'ultimate-multisite'), + esc_html__('This action cannot be undone. Please ensure you have a database backup before proceeding.', 'ultimate-multisite') + ); + echo '
'; + }, 'wrapper_classes' => 'wu-w-full', ], 'submit' => [ @@ -185,9 +187,7 @@ public function handle_orphaned_tables_delete_modal(): void { wp_die(esc_html__('You do not have the required permissions.', 'ultimate-multisite')); } - if (empty($orphaned_tables) || ! is_array($orphaned_tables)) { - $orphaned_tables = $this->find_orphaned_tables(); - } + $orphaned_tables = $this->find_orphaned_tables(); $deleted_count = $this->delete_orphaned_tables($orphaned_tables); diff --git a/inc/class-settings.php b/inc/class-settings.php index 663f2dd8e..26d63eb7d 100644 --- a/inc/class-settings.php +++ b/inc/class-settings.php @@ -1107,7 +1107,9 @@ public function default_sections(): void { 'fields' => [ 'emulated_post_types_remove' => [ 'type' => 'note', - 'desc' => sprintf('', __('Remove', 'ultimate-multisite')), + 'desc' => function () { + printf('', esc_html__('Remove', 'ultimate-multisite')); + }, 'wrapper_classes' => 'wu-absolute wu-top-0 wu-right-0', ], 'emulated_post_types_slug' => [ diff --git a/inc/database/engine/class-query.php b/inc/database/engine/class-query.php index 4114eaeea..acc6f0eda 100644 --- a/inc/database/engine/class-query.php +++ b/inc/database/engine/class-query.php @@ -96,4 +96,24 @@ public function get_plural_name() { return $this->item_name_plural; } + + /** + * Get columns from an array of arguments. + * Copy of the parent method of public access. + * + * @param array $args Arguments to filter columns by. + * @param string $operator Optional. The logical operation to perform. + * @param string $field Optional. A field from the object to place + * instead of the entire object. Default false. + * @return array Array of column. + */ + public function get_columns($args = array(), $operator = 'and', $field = false) { + // Filter columns. + $filter = wp_filter_object_list($this->columns, $args, $operator, $field); + + // Return column or false. + return ! empty($filter) + ? array_values($filter) + : array(); + } } diff --git a/inc/functions/email.php b/inc/functions/email.php index 8c8ad9aa9..25f7b81f0 100644 --- a/inc/functions/email.php +++ b/inc/functions/email.php @@ -100,7 +100,7 @@ function wu_get_default_system_emails($slug = '') { * @since 2.0.0 * * @param string $slug Default system email slug to be create. - * @return array + * @return bool|Email */ function wu_create_default_system_email($slug) { diff --git a/inc/functions/helper.php b/inc/functions/helper.php index f5b6cfcc8..ae1729c0f 100644 --- a/inc/functions/helper.php +++ b/inc/functions/helper.php @@ -362,6 +362,15 @@ function wu_kses_allowed_html(): array { 'v-model' => true, 'v-bind' => true, 'v-bind:class' => true, + 'v-bind:href' => true, + 'v-bind:value' => true, + 'v-bind:checked' => true, + 'v-bind:disabled' => true, + 'v-bind:src' => true, + 'v-bind:max' => true, + 'v-bind:min' => true, + 'v-bind:id' => true, + 'v-bind:readonly' => true, 'v-on' => true, 'v-cloak' => true, 'v-pre' => true, @@ -370,12 +379,30 @@ function wu_kses_allowed_html(): array { // Vue.js shorthand attributes ':class' => true, ':style' => true, + ':name' => true, + ':id' => true, + ':value' => true, + ':key' => true, + ':data-slug' => true, + ':src' => true, + ':alt' => true, + ':href' => true, + ':data-title' => true, + ':aria-label' => true, + ':colspan' => true, + ':list' => true, + ':tag' => true, + ':headers' => true, + ':step-name' => true, + ':disabled' => true, + ':element' => true, + ':template' => true, 'v-on:click' => true, + 'v-on:click.prevent' => true, 'v-on:input' => true, 'v-on:change' => true, '@click' => true, '@click.prevent' => true, - 'v-on:click.prevent' => true, '@submit' => true, '@change' => true, // Common data attributes diff --git a/inc/limits/class-site-template-limits.php b/inc/limits/class-site-template-limits.php index c04224d14..cb6115dc3 100644 --- a/inc/limits/class-site-template-limits.php +++ b/inc/limits/class-site-template-limits.php @@ -106,7 +106,7 @@ public function maybe_filter_template_selection_options($attributes) { */ public function maybe_force_template_selection($template_id, $membership) { - if ($membership && $membership->get_limitations()->site_templates->get_mode() === Limit_Site_Templates::MODE_ASSIGN_TEMPLATE) { + if ($membership && Limit_Site_Templates::MODE_ASSIGN_TEMPLATE === $membership->get_limitations()->site_templates->get_mode()) { $template_id = $membership->get_limitations()->site_templates->get_pre_selected_site_template(); } diff --git a/inc/list-tables/class-base-list-table.php b/inc/list-tables/class-base-list-table.php index 86f86ee8f..4ffb71716 100644 --- a/inc/list-tables/class-base-list-table.php +++ b/inc/list-tables/class-base-list-table.php @@ -9,6 +9,7 @@ namespace WP_Ultimo\List_Tables; +use Closure; use WP_Ultimo\Helpers\Hash; // Exit if accessed directly @@ -1262,10 +1263,6 @@ public function get_default_date_filter_options() { /** * Returns the columns from the BerlinDB Schema. * - * Schema columns are protected on BerlinDB, which makes it hard to reference them out context. - * This is the reason for the reflection funkiness going on in here. - * Maybe there's a better way to do it, but it works for now. - * * @since 2.0.0 * * @param array $args Key => Value pair to search the return columns. e.g. array('searchable' => true). @@ -1274,16 +1271,7 @@ public function get_default_date_filter_options() { * @return array. */ protected function get_schema_columns($args = [], $operator = 'and', $field = false) { - - $query_class = new $this->query_class(); - - $reflector = new \ReflectionObject($query_class); - - $method = $reflector->getMethod('get_columns'); - - $method->setAccessible(true); - - return $method->invoke($query_class, $args, $operator, $field); + return (new $this->query_class())->get_columns($args, $operator, $field); } /** diff --git a/inc/managers/class-email-manager.php b/inc/managers/class-email-manager.php index 2507c0952..857d7d8f1 100644 --- a/inc/managers/class-email-manager.php +++ b/inc/managers/class-email-manager.php @@ -12,6 +12,7 @@ namespace WP_Ultimo\Managers; use Psr\Log\LogLevel; +use WP_Error; use WP_Ultimo\Managers\Base_Manager; use WP_Ultimo\Models\Email; use WP_Ultimo\Helpers\Sender; @@ -40,7 +41,7 @@ class Email_Manager extends Base_Manager { protected $slug = 'email'; /** - * The model class associated to this manager. + * The model class associated with this manager. * * @since 2.0.0 * @var string @@ -53,7 +54,7 @@ class Email_Manager extends Base_Manager { * @since 2.0.0 * @var array */ - protected $registered_default_system_emails; + protected $registered_default_system_emails = []; /** * Instantiate the necessary hooks. @@ -67,13 +68,6 @@ public function init(): void { $this->enable_wp_cli(); - add_action( - 'init', - function () { - $this->register_all_default_system_emails(); - } - ); - /* * Adds the Email fields */ @@ -114,6 +108,25 @@ public function send_system_email($slug, $payload): void { 'email' => wu_get_setting('from_email'), ]; + if (empty($all_emails)) { + $every_email = wu_get_emails(); + if (empty($every_email)) { + // No system emails registered, probably they weren't created during setup. + // Let's create them now + $this->create_all_system_emails(); + $all_emails = wu_get_emails( + [ + 'event' => $slug, + ] + ); + } + } + + if (empty($all_emails)) { + // translators: %s: event slug. + wu_log_add('mailer', sprintf(__('No emails found for event %s.', 'ultimate-multisite'), $slug)); + } + /* * Loop through all the emails registered. */ @@ -325,13 +338,19 @@ public function register_default_system_email($args): void { * * @since 2.0.0 * - * @param array $args with the system email details to register. - * @return bool + * @param array|null $args with the system email details to register. + * @return Email|null|WP_Error Returns Email object if created, null if already exists or invalid args, or WP_Error on failure. */ public function create_system_email($args) { + // Validate that args is an array and has required fields + if (! is_array($args) || empty($args['slug'])) { + return null; + } + + // Check if email already exists if ($this->is_created($args['slug'])) { - return; + return null; // Email already exists, no need to create } $email_args = wp_parse_args( @@ -367,7 +386,6 @@ public function create_system_email($args) { * @return void */ public function create_all_system_emails(): void { - $system_emails = wu_get_default_system_emails(); foreach ($system_emails as $email_key => $email_value) { @@ -383,9 +401,6 @@ public function create_all_system_emails(): void { * @return void */ public function register_all_default_system_emails(): void { - - // TODO: Don't render every email until they are used. - /* * Payment Successful - Admin */ @@ -489,6 +504,9 @@ public function register_all_default_system_emails(): void { * @return array All default system emails. */ public function get_default_system_emails($slug = '') { + if (empty($this->registered_default_system_emails)) { + $this->register_all_default_system_emails(); + } if ($slug && isset($this->registered_default_system_emails[ $slug ])) { return $this->registered_default_system_emails[ $slug ]; @@ -501,11 +519,11 @@ public function get_default_system_emails($slug = '') { * Check if the system email already exists. * * @param mixed $slug Email slug to use as reference. - * @return bool Return email object or false. + * @return bool True if email exists, false otherwise. */ public function is_created($slug): bool { - return (bool) wu_get_email_by('slug', $slug); + return wu_get_email_by('slug', $slug) !== false; } /** @@ -514,9 +532,9 @@ public function is_created($slug): bool { * @since 2.0.0 * * @param string $slug With the event slug. - * @return array With the email template. + * @return void */ - public function get_event_placeholders($slug = '') { + public function get_event_placeholders($slug = ''): void { $placeholders = []; @@ -541,8 +559,6 @@ public function get_event_placeholders($slug = '') { if (wu_request('email_event')) { wp_send_json($placeholders); - } else { - return $placeholders; } } @@ -554,11 +570,11 @@ public function get_event_placeholders($slug = '') { * @param array $to Email targets. * @param string $subject Email subject. * @param string $template Email content. - * @return mixed + * @return void */ - public function send_schedule_system_email($to, $subject, $template) { + public function send_schedule_system_email($to, $subject, $template): void { - return Sender::send_mail($to, $subject, $template); + Sender::send_mail($to, $subject, $template); } /** diff --git a/inc/models/class-base-model.php b/inc/models/class-base-model.php index b8f6150e9..f0a09e9b5 100644 --- a/inc/models/class-base-model.php +++ b/inc/models/class-base-model.php @@ -374,7 +374,7 @@ public static function get_by_hash($item_hash) { * * @param string $column The name of the column to query for. * @param string $value Value to search for. - * @return Base_Model|false + * @return static|false */ public static function get_by($column, $value) { diff --git a/inc/models/class-post-base-model.php b/inc/models/class-post-base-model.php index f37a34b0e..6e98fceee 100644 --- a/inc/models/class-post-base-model.php +++ b/inc/models/class-post-base-model.php @@ -298,7 +298,7 @@ public function set_status($status): void { * setting creation and modification dates first. * * @since 2.0.0 - * @return bool + * @return bool|\WP_Error */ public function save() { diff --git a/inc/ui/class-domain-mapping-element.php b/inc/ui/class-domain-mapping-element.php index 0280cde49..3713d654d 100644 --- a/inc/ui/class-domain-mapping-element.php +++ b/inc/ui/class-domain-mapping-element.php @@ -290,7 +290,9 @@ public function render_user_add_new_domain_modal(): void { $fields = [ 'instructions_note' => [ 'type' => 'note', - 'desc' => sprintf('%s', __('← Back to the Instructions', 'ultimate-multisite')), + 'desc' => function () { + printf('%s', esc_html__('← Back to the Instructions', 'ultimate-multisite')); + }, 'wrapper_html_attr' => [ 'v-if' => 'ready', 'v-cloak' => '1', diff --git a/inc/ui/class-template-switching-element.php b/inc/ui/class-template-switching-element.php index 266b7cf3e..be235061b 100644 --- a/inc/ui/class-template-switching-element.php +++ b/inc/ui/class-template-switching-element.php @@ -381,7 +381,9 @@ public function output($atts, $content = null): void { 'back_to_template_selection' => [ 'type' => 'note', 'order' => 0, - 'desc' => sprintf('%s', __('← Back to Template Selection', 'ultimate-multisite')), + 'desc' => function () { + printf('%s', esc_html__('← Back to Template Selection', 'ultimate-multisite')); + }, 'wrapper_html_attr' => [ 'v-init:original_template_id' => $this->site->get_template_id(), 'v-show' => 'template_id != original_template_id', diff --git a/tests/WP_Ultimo/Managers/Email_Manager_Test.php b/tests/WP_Ultimo/Managers/Email_Manager_Test.php new file mode 100644 index 000000000..58d596904 --- /dev/null +++ b/tests/WP_Ultimo/Managers/Email_Manager_Test.php @@ -0,0 +1,157 @@ +manager = Email_Manager::get_instance(); + } + + /** + * Test that create_all_system_emails registers emails before creating them. + * + * This tests the fix for the setup wizard issue where system emails + * weren't being created because registered_default_system_emails was empty. + */ + public function test_create_all_system_emails_registers_before_creating(): void { + // Use reflection to access the protected property + $reflection = new \ReflectionClass($this->manager); + $property = $reflection->getProperty('registered_default_system_emails'); + $property->setAccessible(true); + + // Reset the property to null to simulate the initial state + $property->setValue($this->manager, null); + + // Delete any existing system emails to ensure a clean test state + $existing_emails = wu_get_all_system_emails(); + foreach ($existing_emails as $email) { + $email->delete(); + } + + // Get count of existing emails before (should be 0 now) + $emails_before = wu_get_all_system_emails(); + $count_before = count($emails_before); + + // Call create_all_system_emails + $this->manager->create_all_system_emails(); + + // After calling create_all_system_emails, the property should be populated + $registered_emails = $property->getValue($this->manager); + $this->assertIsArray($registered_emails, 'registered_default_system_emails should be an array'); + $this->assertNotEmpty($registered_emails, 'registered_default_system_emails should not be empty'); + + // Verify emails were actually created + $emails_after = wu_get_all_system_emails(); + $count_after = count($emails_after); + + $this->assertGreaterThan($count_before, $count_after, 'System emails should have been created'); + } + + /** + * Test that register_all_default_system_emails populates the registry. + */ + public function test_register_all_default_system_emails_populates_registry(): void { + // Use reflection to access the protected property + $reflection = new \ReflectionClass($this->manager); + $property = $reflection->getProperty('registered_default_system_emails'); + $property->setAccessible(true); + + // Reset the property to null + $property->setValue($this->manager, null); + + // Call register_all_default_system_emails + $this->manager->register_all_default_system_emails(); + + // Verify the property is now populated + $registered_emails = $property->getValue($this->manager); + $this->assertIsArray($registered_emails, 'registered_default_system_emails should be an array'); + $this->assertNotEmpty($registered_emails, 'registered_default_system_emails should contain email definitions'); + + // Verify some expected default emails are registered + $this->assertArrayHasKey('payment_received_admin', $registered_emails); + $this->assertArrayHasKey('payment_received_customer', $registered_emails); + $this->assertArrayHasKey('site_published_admin', $registered_emails); + $this->assertArrayHasKey('site_published_customer', $registered_emails); + } + + /** + * Test that is_created correctly identifies existing emails. + */ + public function test_is_created_identifies_existing_emails(): void { + // Create a test email + $email_data = [ + 'slug' => 'test_email_unique_' . time(), + 'title' => 'Test Email', + 'content' => 'Test content', + 'event' => 'test_event', + 'target' => 'admin', + 'type' => 'system_email', + 'status' => 'publish', + ]; + + $email = wu_create_email($email_data); + $this->assertNotWPError($email, 'Email should be created successfully'); + + // Test is_created returns true for the existing email + $is_created = $this->manager->is_created($email_data['slug']); + $this->assertTrue($is_created, 'is_created should return true for existing email'); + + // Test is_created returns false for non-existent email + $is_not_created = $this->manager->is_created('non_existent_email_slug_' . time()); + $this->assertFalse($is_not_created, 'is_created should return false for non-existent email'); + } + + /** + * Test that create_system_email doesn't create duplicates. + */ + public function test_create_system_email_prevents_duplicates(): void { + // Register default system emails first + $this->manager->register_all_default_system_emails(); + + // Get count before + $emails_before = wu_get_all_system_emails(); + $count_before = count($emails_before); + + // Try to create the same system email twice + $email_data = [ + 'slug' => 'payment_received_admin', + 'title' => 'Test Payment Email', + 'content' => 'Test content', + 'event' => 'payment_received', + 'target' => 'admin', + ]; + + $result1 = $this->manager->create_system_email($email_data); + $result2 = $this->manager->create_system_email($email_data); + + // The second call should return early without creating a duplicate + $this->assertNull($result2, 'Second create_system_email call should return null for duplicate'); + + // Verify count didn't increase by more than 1 + $emails_after = wu_get_all_system_emails(); + $count_after = count($emails_after); + + $this->assertLessThanOrEqual($count_before + 1, $count_after, 'Should not create duplicate emails'); + } +} diff --git a/views/admin-pages/fields/field-html.php b/views/admin-pages/fields/field-html.php index 92847d1ab..6e776a0fd 100644 --- a/views/admin-pages/fields/field-html.php +++ b/views/admin-pages/fields/field-html.php @@ -41,9 +41,13 @@ ?>
- - content, wu_kses_allowed_html()); ?> - + content; + if ($content) { + echo wp_kses($content, wu_kses_allowed_html()); + } + ?>
diff --git a/views/admin-pages/fields/field-note.php b/views/admin-pages/fields/field-note.php index 5afbfa916..05e501b51 100644 --- a/views/admin-pages/fields/field-note.php +++ b/views/admin-pages/fields/field-note.php @@ -27,9 +27,12 @@ ?>
- - desc ?? '', wu_kses_allowed_html()); ?> - + desc; + if ($desc) { + echo wp_kses($desc, wu_kses_allowed_html()); + } + ?>
diff --git a/views/broadcast/emails/base.php b/views/broadcast/emails/base.php index 74d64e143..c3c600fe9 100644 --- a/views/broadcast/emails/base.php +++ b/views/broadcast/emails/base.php @@ -92,7 +92,10 @@


- +