PluginProbe
404 Solution / 4.2.0
404 Solution v4.2.0
4.3.5 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.2.0 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.1.7 4.1.6 4.1.5 4.1.4 4.1.3 trunk 2.30.0 All 109 releases
404-solution / includes / SupportRequestButton.php

SupportRequestButton.php in 404 Solution 4.2.0, at includes/SupportRequestButton.php

73 lines 3.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('ABSPATH')) {
4 exit;
5 }
6
7 /**
8 * HTML mount point for the reusable "Send debug log to developer" button.
9 *
10 * Pure rendering helper. No DAO calls, no singleton, no globals. Returns
11 * a single empty <div> carrying the data attributes the JS component
12 * (includes/js/support-request-button.js) needs to bootstrap itself. The
13 * component reads `data-triggered-from` (allowlisted server-side by
14 * Ajax_SupportRequest::ALLOWED_TRIGGER_SOURCES) and the optional
15 * `data-context-summary` (a one-line free-text hint shown to the admin in
16 * the confirmation modal so they remember which screen they're sending
17 * from).
18 *
19 * Caller responsibilities:
20 * 1. wp_enqueue_script() the abj404-support-request-button bundle and
21 * its dependency abj404-support-request-client (handled by
22 * WordPress_Connector::registerSupportRequestAssets()).
23 * 2. Pass a triggered_from slug that exists in the AJAX allowlist.
24 * Drift is caught at the server side (400 response) but the lint in
25 * tests/SupportRequestButtonRenderTest enforces the matching set.
26 */
27 final class ABJ_404_Solution_SupportRequestButton {
28
29 /**
30 * Render an empty mount-point div for the support-request button. The
31 * JS component (window.ABJ404.SupportRequestButton.mount) discovers
32 * the div via its CSS class and bootstraps the button, modal, and
33 * AJAX wiring.
34 *
35 * Both data attributes are escaped with esc_attr() so the caller may
36 * pass arbitrary user-derived strings (e.g. an error message excerpt
37 * as the context_summary) without opening an XSS hole.
38 *
39 * @param string $triggeredFrom Required. One of
40 * Ajax_SupportRequest::ALLOWED_TRIGGER_SOURCES.
41 * @param string|null $contextSummary Optional one-line summary shown
42 * inside the modal so the user knows which screen the report is
43 * anchored to. Pass null or '' to omit.
44 * @return string HTML safe to print directly into an admin page.
45 */
46 public static function render(string $triggeredFrom, ?string $contextSummary = null): string {
47 $triggeredFromAttr = self::escAttr($triggeredFrom);
48 $html = '<div class="abj404-support-request-mount"'
49 . ' data-triggered-from="' . $triggeredFromAttr . '"';
50 if ($contextSummary !== null && $contextSummary !== '') {
51 $html .= ' data-context-summary="' . self::escAttr($contextSummary) . '"';
52 }
53 $html .= '></div>';
54 return $html;
55 }
56
57 /**
58 * Wrapper around esc_attr() that survives running outside of a real
59 * WordPress request (PHPStan-bench, structural tests). When WP is
60 * loaded the real esc_attr() handles the escaping; otherwise a
61 * minimal htmlspecialchars() shim keeps the output safe.
62 *
63 * @param string $value
64 * @return string
65 */
66 private static function escAttr(string $value): string {
67 if (function_exists('esc_attr')) {
68 return (string)esc_attr($value);
69 }
70 return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
71 }
72 }
73