PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.10.01
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.10.01
2.10.0 2.10.01 2.9.1 2.9.0 2.8.1 2.8.0 2.7.7 2.7.5 2.7.0 2.6.01 2.6.0 2.5.0 2.4.01 trunk 1.0.90 1.0.91 1.0.92 1.0.93 1.0.94 1.0.95 1.0.96 1.0.97 1.0.98 1.0.99 1.1.0 All 77 releases
← All changes | app/Services/SmartCodeParser.php +71 -11 2.7.72.10.01 View file →
@@ -143,39 +143,75 @@
143 143
144 144 if ($transformer && is_string($transformer) && $value) {
145 145 switch ($transformer) {
146 146 case 'trim':
147 - return trim($value);
147 + $value = trim($value);
148 + break;
148 149 case 'ucfirst':
149 - return ucfirst($value);
150 + $value = ucfirst($value);
151 + break;
150 152 case 'strtolower':
151 - return strtolower($value);
153 + $value = strtolower($value);
154 + break;
152 155 case 'strtoupper':
153 - return strtoupper($value);
156 + $value = strtoupper($value);
157 + break;
154 158 case 'ucwords':
155 - return ucwords($value);
159 + $value = ucwords($value);
160 + break;
156 161 case 'concat_first': // usage: {{contact.first_name||concat_first|Hi
157 162 if (isset($valueKeys[3])) {
158 163 $value = trim($valueKeys[3] . ' ' . $value);
159 164 }
160 - return $value;
165 + break;
161 166 case 'concat_last': // usage: {{contact.first_name||concat_last|, => FIRST_NAME,
162 167 if (isset($valueKeys[3])) {
163 168 $value = trim($value . '' . $valueKeys[3]);
164 169 }
165 - return $value;
170 + break;
166 171 case 'show_if': // usage {{contact.first_name||show_if|First name exist
167 172 if (isset($valueKeys[3])) {
168 173 $value = $valueKeys[3];
169 174 }
170 - return $value;
171 - default:
172 - return $value;
175 + break;
173 176 }
174 177 }
175 178
176 - return $value;
179 + return $this->escapeValueForContext($value, $dataKey, $valueKey);
180 + }
177 181
182 + /**
183 + * Smartcode values are substituted into lockscreen/lesson/email HTML *after*
184 + * that content has passed through wp_kses / do_blocks, so a resolved scalar
185 + * carrying markup would otherwise bypass sanitisation. Escape ordinary
186 + * values for the HTML context. The few branches that intentionally build a
187 + * trusted fragment (photo_html, name_with_url, section url) already escape
188 + * their own interpolated parts, so they are left untouched, and values from
189 + * third-party group callbacks are the extension's responsibility.
190 + */
191 + protected function escapeValueForContext($value, $dataKey, $valueKey)
192 + {
193 + if (!static::$isHtml || !is_string($value) || $value === '') {
194 + return $value;
195 + }
196 +
197 + $knownGroups = ['site', 'user', 'community', 'section', 'course'];
198 + if (!in_array($dataKey, $knownGroups, true)) {
199 + return $value;
200 + }
201 +
202 + $trustedHtml = [
203 + 'user' => ['photo_html'],
204 + 'community' => ['name_with_url'],
205 + 'section' => ['url'],
206 + ];
207 +
208 + $baseKey = strtok($valueKey, '.'); // "photo_html.50px" -> "photo_html"
209 + if (in_array($baseKey, Arr::get($trustedHtml, $dataKey, []), true)) {
210 + return $value;
211 + }
212 +
213 + return esc_html($value);
178 214 }
179 215
180 216 protected function getWpValue($valueKey, $defaultValue)
181 217 {
@@ -226,8 +262,22 @@
226 262
227 263 $wpUser = $userModel->getWpUser();
228 264 $valueKeys = explode('.', $valueKey);
229 265 if (count($valueKeys) == 1) {
266 + // Smartcodes are resolved against the *viewer* and can be authored by
267 + // space/course/page admins, so only a fixed set of non-sensitive
268 + // profile fields may be read. Never expose user_pass,
269 + // user_activation_key, session tokens or capability meta.
270 + $allowedFields = apply_filters('fluent_community/smartcode/user_fields', [
271 + 'ID', 'first_name', 'last_name', 'nickname', 'display_name',
272 + 'user_email', 'user_login', 'user_nicename', 'user_url',
273 + 'description', 'user_registered'
274 + ]);
275 +
276 + if (!in_array($valueKey, $allowedFields, true)) {
277 + return $defaultValue;
278 + }
279 +
230 280 $value = $wpUser->get($valueKey);
231 281 if (!$value) {
232 282 return $defaultValue;
233 283 }
@@ -251,8 +301,18 @@
251 301 return '<img ' . $style . ' src="' . esc_url($userModel->photo) . '" alt="' . esc_attr($userModel->display_name) . '" class="fcom_user_dynamic_photo" />';
252 302 }
253 303
254 304 if ($customKey == 'meta') {
305 + // No first-party template reads user meta through smartcodes, and the
306 + // viewer's own meta (session_tokens, capability keys, reset keys,
307 + // any _-prefixed value) must never leak into author-controlled
308 + // content. Resolve only meta keys a site has explicitly allowed.
309 + $allowedMetaKeys = apply_filters('fluent_community/smartcode/user_meta_keys', []);
310 +
311 + if (strpos($customProperty, '_') === 0 || !in_array($customProperty, $allowedMetaKeys, true)) {
312 + return $defaultValue;
313 + }
314 +
255 315 $metaValue = get_user_meta($wpUser->ID, $customProperty, true);
256 316 if (!$metaValue) {
257 317 return $defaultValue;
258 318 }