PluginProbe
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder / 51.1.83
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder v51.1.83
51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 51.1.45 51.1.46 51.1.47 51.1.49 All 37 releases
king-addons / includes / helpers / Global / Text_Entities_Migration.php

Text_Entities_Migration.php in King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder 51.1.83, at includes/helpers/Global/Text_Entities_Migration.php

172 lines 4.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * One-shot repair of Cookie Consent and Age Gate texts stored with HTML entities.
4 *
5 * Defaults used to be wrapped in esc_html__(), so quotes and apostrophes were
6 * written into the option as &quot; / &#039;. The banners insert those strings
7 * with textContent, which does not decode entities.
8 *
9 * @package King_Addons
10 */
11
12 namespace King_Addons;
13
14 if (!defined('ABSPATH')) {
15 exit;
16 }
17
18 /**
19 * Walks the two option arrays and undoes the three entities that leak on screen.
20 */
21 final class Text_Entities_Migration
22 {
23 public const FLAG = 'king_addons_migration_text_entities';
24
25 /**
26 * Hook a cheap flag check on admin and front so ZIP/FTP updates still run it.
27 *
28 * @return void
29 */
30 public static function boot(): void
31 {
32 add_action('init', [self::class, 'maybe_run'], 1);
33 add_action('admin_init', [self::class, 'maybe_run'], 1);
34 }
35
36 /**
37 * Decode stored texts once per site, then set the flag.
38 *
39 * @return void
40 */
41 public static function maybe_run(): void
42 {
43 if ('1' === (string) get_option(self::FLAG, '')) {
44 return;
45 }
46
47 self::migrate_option(
48 'king_addons_cookie_consent_options',
49 [
50 ['content', 'title'],
51 ['content', 'message'],
52 ['content', 'privacy_label'],
53 ['content', 'cookie_label'],
54 ['buttons', 'accept'],
55 ['buttons', 'reject'],
56 ['buttons', 'settings'],
57 ['buttons', 'save'],
58 ],
59 true
60 );
61
62 self::migrate_option(
63 'king_addons_age_gate_options',
64 [
65 ['design', 'title'],
66 ['design', 'subtitle'],
67 ['design', 'button_yes'],
68 ['design', 'button_no'],
69 ['behaviour', 'block_message'],
70 ['dob', 'error_invalid'],
71 ['dob', 'error_denied'],
72 ],
73 false
74 );
75
76 update_option(self::FLAG, '1', true);
77 }
78
79 /**
80 * Decode listed string keys in one option. Categories are walked separately
81 * for Cookie Consent.
82 *
83 * @param string $option_name Option name.
84 * @param array<int,array<int,string>> $paths Nested keys to decode.
85 * @param bool $with_categories Also decode category labels.
86 *
87 * @return void
88 */
89 private static function migrate_option(string $option_name, array $paths, bool $with_categories): void
90 {
91 $stored = get_option($option_name, null);
92 if (!is_array($stored)) {
93 return;
94 }
95
96 $changed = false;
97
98 foreach ($paths as $path) {
99 if (self::decode_path($stored, $path)) {
100 $changed = true;
101 }
102 }
103
104 if ($with_categories && isset($stored['categories']) && is_array($stored['categories'])) {
105 foreach ($stored['categories'] as $index => $category) {
106 if (!is_array($category)) {
107 continue;
108 }
109 foreach (['label', 'description'] as $key) {
110 if (self::decode_path($stored, ['categories', (string) $index, $key])) {
111 $changed = true;
112 }
113 }
114 }
115 }
116
117 if ($changed) {
118 update_option($option_name, $stored, false);
119 }
120 }
121
122 /**
123 * Decode one nested string in place.
124 *
125 * @param array<string,mixed> $data Option array (by reference).
126 * @param array<int,string> $path Keys from the root.
127 *
128 * @return bool True when the stored string changed.
129 */
130 private static function decode_path(array &$data, array $path): bool
131 {
132 $ref = &$data;
133 $last = array_pop($path);
134
135 foreach ($path as $key) {
136 if (!is_array($ref) || !array_key_exists($key, $ref)) {
137 return false;
138 }
139 $ref = &$ref[$key];
140 }
141
142 if (!is_array($ref) || !isset($ref[$last]) || !is_string($ref[$last])) {
143 return false;
144 }
145
146 $decoded = self::decode_text($ref[$last]);
147 if ($decoded === $ref[$last]) {
148 return false;
149 }
150
151 $ref[$last] = $decoded;
152
153 return true;
154 }
155
156 /**
157 * Undo the entities esc_html__() writes. Leaves &lt; / &gt; alone.
158 *
159 * @param string $value Stored string.
160 *
161 * @return string
162 */
163 public static function decode_text(string $value): string
164 {
165 return str_replace(
166 ['&quot;', '&#039;', '&#39;', '&amp;'],
167 ['"', "'", "'", '&'],
168 $value
169 );
170 }
171 }
172