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
8 changes: 4 additions & 4 deletions inc/list-tables/class-webhook-list-table.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,14 @@ public function column_name($item): string {
'<a href="%s"><strong>%s</strong></a>
<span data-loading="wu_action_button_loading_%s" id="wu_action_button_loading" class="wu-blinking-animation wu-text-gray-600 wu-my-1 wu-text-2xs wu-uppercase wu-font-semibold hidden" >%s</span>',
wu_network_admin_url('wp-ultimo-edit-webhook', $url_atts),
$item->get_name(),
esc_html($item->get_name()),
$item->get_id(),
__('Sending Test..', 'ultimate-multisite')
);

$actions = [
'edit' => sprintf('<a href="%s">%s</a>', wu_network_admin_url('wp-ultimo-edit-webhook', $url_atts), __('Edit', 'ultimate-multisite')),
'test' => sprintf('<a id="action_button" data-title="' . $item->get_name() . '" data-page="list" data-action="wu_send_test_event" data-event="' . $item->get_event() . '" data-object="' . $item->get_id() . '" data-url="%s" href="">%s</a>', $item->get_webhook_url(), __('Send Test', 'ultimate-multisite')),
'test' => sprintf('<a id="action_button" data-title="' . esc_attr($item->get_name()) . '" data-page="list" data-action="wu_send_test_event" data-event="' . esc_attr($item->get_event()) . '" data-object="' . esc_attr($item->get_id()) . '" data-url="%s" href="">%s</a>', esc_url($item->get_webhook_url()), __('Send Test', 'ultimate-multisite')),
'delete' => sprintf(
'<a title="%s" class="wubox" href="%s">%s</a>',
__('Delete', 'ultimate-multisite'),
Expand All @@ -97,7 +97,7 @@ public function column_name($item): string {
*/
public function column_webhook_url($item) {

$trimmed_url = mb_strimwidth((string) $item->get_webhook_url(), 0, 50, '...');
$trimmed_url = esc_html(mb_strimwidth((string) $item->get_webhook_url(), 0, 50, '...'));

return "<span class='wu-py-1 wu-px-2 wu-bg-gray-200 wu-rounded-sm wu-text-gray-700 wu-text-xs wu-font-mono'>{$trimmed_url}</span>";
}
Expand All @@ -112,7 +112,7 @@ public function column_webhook_url($item) {
*/
public function column_event($item) {

$event = $item->get_event();
$event = esc_html((string) $item->get_event());

return "<span class='wu-py-1 wu-px-2 wu-bg-gray-200 wu-rounded-sm wu-text-gray-700 wu-text-xs wu-font-mono'>{$event}</span>";
}
Expand Down
31 changes: 20 additions & 11 deletions inc/models/class-event.php
Original file line number Diff line number Diff line change
Expand Up @@ -271,23 +271,32 @@ public function interpolate_message($message, $payload): string {

$payload = json_decode(json_encode($payload), true); // phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode

$interpolation_keys = [];

foreach ($payload as $key => &$value) {
$interpolation_keys[] = "{{{$key}}}";

/*
* The message templates (get_default_system_messages) intentionally
* contain HTML (e.g. <strong>{{model}}</strong>) and the rendered result
* is output with Vue v-html in the activity-stream widget. The
* interpolated payload values, however, include attacker-influenced model
* fields (customer display names, site titles, etc.), so every value must
* be HTML-escaped before substitution to prevent stored XSS — while the
* template's own markup is preserved.
*/
$interpolation = [];

foreach ($payload as $key => $value) {
if (is_array($value)) {
$value = implode(' &rarr; ', wu_array_flatten($value));
$value = implode(' &rarr; ', array_map('esc_html', wu_array_flatten($value)));
} else {
$value = esc_html((string) $value);
}
}

$interpolation = array_combine($interpolation_keys, $payload);
$interpolation[ "{{{$key}}}" ] = $value;
}

$interpolation['{{payload}}'] = implode(' - ', wu_array_flatten($payload, true));
$interpolation['{{payload}}'] = implode(' - ', array_map('esc_html', wu_array_flatten($payload, true)));

$interpolation['{{model}}'] = wu_slug_to_name($this->object_type);
$interpolation['{{model}}'] = esc_html(wu_slug_to_name($this->object_type));

$interpolation['{{object_id}}'] = $this->object_id;
$interpolation['{{object_id}}'] = esc_html((string) $this->object_id);

return strtr($message, $interpolation);
}
Expand Down
8 changes: 6 additions & 2 deletions inc/template-library/class-api-client.php
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,12 @@ private function parse_template_data(array $template): array {
'slug' => $template['slug'] ?? '',
'name' => $template['name'] ?? '',
'description' => $template['description'] ?? '',
'short_description' => $template['short_description'] ?? '',
'price_html' => $template['price_html'] ?? '',
// short_description and price_html are rendered with Vue v-html in the
// Template Library grid; the data is fetched over HTTP from the remote
// marketplace, so sanitize the HTML to allowed post markup to neutralize
// any injected script while preserving legitimate formatting.
'short_description' => wp_kses_post($template['short_description'] ?? ''),
'price_html' => wp_kses_post($template['price_html'] ?? ''),
'permalink' => $template['permalink'] ?? '',
'is_free' => empty($template['prices']['price'] ?? 0),
'prices' => $template['prices'] ?? [],
Expand Down
2 changes: 1 addition & 1 deletion views/events/widget-payload.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

<li class="wu-p-4 wu-m-0" v-show="!loading" v-cloak>

<pre id="wu_payload_content" v-html="payload" class="wu-overflow-auto wu-p-4 wu-m-0 wu-mt-2 wu-rounded wu-content-center wu-bg-gray-800 wu-text-white wu-font-mono wu-border wu-border-solid wu-border-gray-300 wu-max-h-screen wu-overflow-y-auto"></pre>
<pre id="wu_payload_content" v-text="payload" class="wu-overflow-auto wu-p-4 wu-m-0 wu-mt-2 wu-rounded wu-content-center wu-bg-gray-800 wu-text-white wu-font-mono wu-border wu-border-solid wu-border-gray-300 wu-max-h-screen wu-overflow-y-auto"></pre>

</li>

Expand Down
Loading