PluginProbe
Extendify / trunk
Extendify vtrunk
3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 0.6.0 All 127 releases
← All changes | app/PartnerData.php +82 -4 3.1.6 → trunk View file →
@@ -98,8 +98,9 @@
98 98 'useAutoUpdate' => false,
99 99 'showLaunchUpdate' => false,
100 100 'activeTests' => [],
101 101 'showExtendifyCode' => false,
102 + 'useComingSoon' => false,
102 103 'extendifyCodeData' => [
103 104 'link' => '',
104 105 'title' => '',
105 106 'message' => '',
@@ -104,8 +105,10 @@
104 105 'title' => '',
105 106 'message' => '',
106 107 'cta-primary' => '',
107 108 ],
109 + 'customDesign' => null,
110 + 'strings' => [],
108 111 ];
109 112
110 113 // phpcs:disable Generic.Metrics.CyclomaticComplexity.MaxExceeded
111 114 /**
@@ -187,9 +190,12 @@
187 190 self::$config['useAutoUpdate'] = ($data['useAutoUpdate'] ?? self::$config['useAutoUpdate']);
188 191 self::$config['showLaunchUpdate'] = ($data['showLaunchUpdate'] ?? self::$config['showLaunchUpdate']);
189 192 self::$config['activeTests'] = ($data['activeTests'] ?? self::$config['activeTests']);
190 193 self::$config['showExtendifyCode'] = ($data['showExtendifyCode'] ?? self::$config['showExtendifyCode']);
194 + self::$config['useComingSoon'] = ($data['useComingSoon'] ?? self::$config['useComingSoon']);
191 195 self::$config['extendifyCodeData'] = ($data['extendifyCodeData'] ?? self::$config['extendifyCodeData']);
196 + self::$config['customDesign'] = ($data['customDesign'] ?? self::$config['customDesign']);
197 + self::$config['strings'] = ($data['strings'] ?? self::$config['strings']);
192 198
193 199 // Add the job hook to fetch the partner data.
194 200 \add_action('extendify_fetch_partner_data', [self::class, 'fetchPartnerData']);
195 201 }
@@ -273,12 +279,16 @@
273 279 }
274 280
275 281 $sanitizedData = array_merge(
276 282 Sanitizer::sanitizeUnknown($result['data']),
277 - ['consentTermsCustom' => \sanitize_text_field(htmlentities(
278 - ($result['data']['consentTermsCustom'] ?? ''),
279 - ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401
280 - ))]
283 + [
284 + 'consentTermsCustom' => \sanitize_text_field(htmlentities(
285 + ($result['data']['consentTermsCustom'] ?? ''),
286 + ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401
287 + )),
288 + 'customDesign' => self::sanitizeDesign($result['data']['customDesign'] ?? null),
289 + 'strings' => self::sanitizeStrings($result['data']['strings'] ?? null),
290 + ]
281 291 );
282 292
283 293 // Merge before persisting as this data is accessed directly elsewhere.
284 294 $mergedData = array_merge(self::$config, $sanitizedData);
@@ -284,8 +294,76 @@
284 294 $mergedData = array_merge(self::$config, $sanitizedData);
285 295 \update_option('extendify_partner_data_v2', $mergedData);
286 296
287 297 return $mergedData;
298 + }
299 +
300 + /**
301 + * Partner copy overriding the shipped strings.
302 + *
303 + * Which keys exist is the flow's to know, so only shape and text are checked here.
304 + *
305 + * @param mixed $strings The map as the partner-data response carried it.
306 + * @return array
307 + */
308 + public static function sanitizeStrings($strings)
309 + {
310 + return array_map(
311 + 'sanitize_text_field',
312 + self::designEntries($strings, '/^[a-zA-Z][a-zA-Z0-9]*$/', 'is_string')
313 + );
314 + }
315 +
316 + /**
317 + * A design carries GLSL, which the text sanitizers break, so only its shape is checked.
318 + *
319 + * Empty members are left out rather than kept: json_encode writes an empty
320 + * PHP array as [], and the page reads the design as an object.
321 + *
322 + * @param mixed $design The design as the partner-data response carried it.
323 + * @return array|null
324 + */
325 + private static function sanitizeDesign($design)
326 + {
327 + if (!is_array($design)) {
328 + return null;
329 + }
330 +
331 + $shader = ($design['shader'] ?? null);
332 + $logo = ($design['logo'] ?? null);
333 + $kept = array_filter([
334 + 'vars' => self::designEntries(
335 + ($design['vars'] ?? null),
336 + '/^--ext-(ui|tpl)-[a-z0-9-]+$/',
337 + function ($value) {
338 + // A CSS value is a string, but 0.88 is a natural way to write one.
339 + return is_string($value) || is_int($value) || is_float($value);
340 + }
341 + ),
342 + 'templates' => self::designEntries(($design['templates'] ?? null), '/^[A-Za-z0-9_-]+$/', 'is_string'),
343 + 'shader' => is_string($shader) ? \wp_check_invalid_utf8($shader) : '',
344 + 'logo' => is_string($logo) ? \esc_url_raw($logo) : '',
345 + ]);
346 +
347 + return $kept ?: null;
348 + }
349 +
350 + private static function designEntries($entries, $keyPattern, callable $accepts)
351 + {
352 + if (!is_array($entries)) {
353 + return [];
354 + }
355 +
356 + $kept = [];
357 + foreach ($entries as $key => $value) {
358 + if (!is_string($key) || !preg_match($keyPattern, $key) || !$accepts($value)) {
359 + continue;
360 + }
361 +
362 + $kept[$key] = is_string($value) ? \wp_check_invalid_utf8($value) : $value;
363 + }
364 +
365 + return $kept;
288 366 }
289 367
290 368 /**
291 369 * Return colors mapped as css variables