PluginProbe
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder / V-3.3.0
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder vV-3.3.0
3.3.1 V-3.3.0 3.2.2 3.2.1 3.2.0 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 V3.0.3 V3.0.2 -3.0.1 V_3.0.0 1.1.1 1.1.8 1.2 1.3 1.4 1.4.18 1.5.2 1.9 2.0 2.10.0 2.10.1 All 138 releases
bit-form / includes / Core / Util / Translation / Provider / WpmlProvider.php

WpmlProvider.php in Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder V-3.3.0, at includes/Core/Util/Translation/Provider/WpmlProvider.php

193 lines 5.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace BitCode\BitForm\Core\Util\Translation\Provider;
4
5 use BitCode\BitForm\Core\Util\Translation\Support\AbstractStringRegistrationProvider;
6 use BitCode\BitForm\Core\Util\Translation\TranslationManager;
7 use BitCode\BitForm\Core\Util\Translation\TranslationStringName;
8
9 /**
10 * WPML adapter (String Translation module).
11 *
12 * Frontend/AJAX are lookup-only. Register-at-render is WPML's documented top
13 * cause of icl_strings bloat, so registration happens on form save
14 * (`bitform_form_saved`) and as a backfill when WPML's own screen is opened.
15 */
16 final class WpmlProvider extends AbstractStringRegistrationProvider
17 {
18 public const DOMAIN = 'bit-form';
19
20 /**
21 * Per-form hash of the last registered string set. WPML persists registrations,
22 * so re-pushing an unchanged form is pure write amplification.
23 */
24 public const HASH_OPTION = 'bitforms_wpml_string_hashes';
25
26 /**
27 * Hash state for the current backfill pass, written back once at the end.
28 *
29 * @var array<int,string>|null
30 */
31 private $passHashes = null;
32
33 /** @var bool whether the current pass changed any hash */
34 private $passChanged = false;
35
36 /**
37 * Hash shouldRegisterForm() just computed, reused instead of hashing twice.
38 *
39 * @var array{0:int,1:string}|null
40 */
41 private $pendingHash = null;
42
43 public function registerStrings($formId, array $contextStrings)
44 {
45 foreach ($contextStrings as $context => $string) {
46 do_action('wpml_register_single_string', self::DOMAIN, TranslationStringName::forForm($context, $formId), $string);
47 }
48 }
49
50 public function onAdminInit()
51 {
52 add_action('bitform_form_saved', [$this, 'registerFormStrings']);
53 add_action('admin_init', [$this, 'maybeRegisterAllForms']);
54 }
55
56 public function onAjaxInit($requestLang)
57 {
58 $lang = $this->getCurrentLanguage();
59 if ('' !== $lang) {
60 do_action('wpml_switch_language', $lang);
61 }
62 }
63
64 /**
65 * `bitform_form_saved` callback — registers one form's strings.
66 *
67 * @param mixed $formId
68 */
69 public function registerFormStrings($formId)
70 {
71 $formId = (int) $formId;
72 $strings = RegistrationCollector::collectAllStrings($formId);
73 $this->registerStrings($formId, $strings);
74
75 $hashes = self::hashes();
76 $hashes[$formId] = self::hashStrings($strings);
77 update_option(self::HASH_OPTION, $hashes, false);
78 }
79
80 /**
81 * @param string $page
82 *
83 * @return bool
84 */
85 protected function isTranslationScreen($page)
86 {
87 return '' !== $page && false !== strpos($page, 'wpml-string-translation');
88 }
89
90 /**
91 * Registrations persist and each is a WPML read plus write, so an unchanged
92 * form is skipped. The screen reloads on every filter and page change.
93 *
94 * @param int $formId
95 * @param array<string,string> $strings
96 *
97 * @return bool
98 */
99 protected function shouldRegisterForm($formId, array $strings)
100 {
101 if (null === $this->passHashes) {
102 $this->passHashes = self::hashes();
103 $this->passChanged = false;
104 }
105 $hash = self::hashStrings($strings);
106 $this->pendingHash = [$formId, $hash];
107 return !isset($this->passHashes[$formId]) || $this->passHashes[$formId] !== $hash;
108 }
109
110 /**
111 * @param int $formId
112 * @param array<string,string> $strings
113 */
114 protected function afterFormRegistered($formId, array $strings)
115 {
116 $hash = (is_array($this->pendingHash) && $formId === $this->pendingHash[0])
117 ? $this->pendingHash[1]
118 : self::hashStrings($strings);
119 $this->passHashes[$formId] = $hash;
120 $this->passChanged = true;
121 }
122
123 /**
124 * One option write for the whole pass, not one per form.
125 */
126 protected function afterRegistrationPass()
127 {
128 if ($this->passChanged && is_array($this->passHashes)) {
129 update_option(self::HASH_OPTION, $this->passHashes, false);
130 }
131 $this->passHashes = null;
132 $this->passChanged = false;
133 $this->pendingHash = null;
134 }
135
136 /**
137 * Validated bf_lang, else WPML's current language.
138 *
139 * @return string
140 */
141 protected function detectLanguage()
142 {
143 $requestLang = TranslationManager::getRequestLang();
144 if ('' !== $requestLang) {
145 $active = apply_filters('wpml_active_languages', null);
146 if (is_array($active) && isset($active[$requestLang])) {
147 return $requestLang;
148 }
149 }
150 $current = apply_filters('wpml_current_language', null);
151 return is_string($current) ? $current : '';
152 }
153
154 /**
155 * @param string $string
156 * @param string $context
157 * @param int $formId
158 *
159 * @return mixed
160 */
161 protected function lookup($string, $context, $formId)
162 {
163 $lang = $this->getCurrentLanguage();
164 return apply_filters(
165 'wpml_translate_single_string',
166 $string,
167 self::DOMAIN,
168 TranslationStringName::forForm($context, $formId),
169 '' !== $lang ? $lang : null
170 );
171 }
172
173 /**
174 * @return array<int,string>
175 */
176 private static function hashes()
177 {
178 $hashes = get_option(self::HASH_OPTION, []);
179 return is_array($hashes) ? $hashes : [];
180 }
181
182 /**
183 * @param array<string,string> $strings
184 *
185 * @return string
186 */
187 private static function hashStrings(array $strings)
188 {
189 $encoded = wp_json_encode($strings);
190 return md5(is_string($encoded) ? $encoded : serialize($strings));
191 }
192 }
193