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(
- ' new_meta_fields.splice(index, 1)">',
- __('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(' price_variations.splice(index, 1)">', 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(' period_options.splice(index, 1)">', __('Remove', 'ultimate-multisite')),
+ 'desc' => function () {
+ printf(' period_options.splice(index, 1)">', 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(' options.splice(index, 1)">', __('Remove', 'ultimate-multisite')),
+ 'desc' => function () {
+ printf(' options.splice(index, 1)">', 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(' emulated_post_types.splice(index, 1)">', __('Remove', 'ultimate-multisite')),
+ 'desc' => function () {
+ printf(' emulated_post_types.splice(index, 1)">', 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 @@
-
+
|