`) and edited in Zadex > Settings > "Integrations". * * Secret fields are never printed back: the admin form shows an empty password input with a "saved" hint; * an empty input keeps the saved value and the "Remove" checkbox clears it (the same rules as the * reCAPTCHA secret). Only keys present in the submitted form are returned by sanitize(), so partial saves * never wipe anything. */ final class Credentials { public const PREFIX = 'integration_'; /** @return array */ public static function fields(): array { // Labels stay untranslated here; render() translates them at output time (admin screen, after `init`). return [ 'mailchimp_api_key' => ['provider' => 'Mailchimp', 'label' => 'API key', 'secret' => true, 'kind' => 'key'], 'activecampaign_url' => ['provider' => 'ActiveCampaign', 'label' => 'API URL', 'secret' => false, 'kind' => 'url'], 'activecampaign_api_key' => ['provider' => 'ActiveCampaign', 'label' => 'API key', 'secret' => true, 'kind' => 'key'], 'getresponse_api_key' => ['provider' => 'GetResponse', 'label' => 'API key', 'secret' => true, 'kind' => 'key'], 'convertkit_api_key' => ['provider' => 'ConvertKit (Kit)', 'label' => 'API key', 'secret' => true, 'kind' => 'key'], 'mailerlite_api_key' => ['provider' => 'MailerLite', 'label' => 'API token', 'secret' => true, 'kind' => 'key'], 'drip_account_id' => ['provider' => 'Drip', 'label' => 'Account ID', 'secret' => false, 'kind' => 'digits'], 'drip_api_token' => ['provider' => 'Drip', 'label' => 'API token', 'secret' => true, 'kind' => 'key'], ]; } /** Saved value of one credential ('' when unset). */ public static function get(string $name): string { $all = get_option(Settings::OPTION, []); $value = is_array($all) ? ($all[self::PREFIX . $name] ?? '') : ''; return is_string($value) ? trim($value) : ''; } public static function has(string $name): bool { return self::get($name) !== ''; } /** * Sanitizes the "Integrations" part of the settings form (`integrations[]`, * `integrations[_clear]`) into flat `integration_` settings keys. * * @return array */ public static function sanitize(array $input): array { $in = isset($input['integrations']) && is_array($input['integrations']) ? $input['integrations'] : []; $out = []; foreach (self::fields() as $name => $field) { $key = self::PREFIX . $name; if ($field['secret']) { if (!empty($in[$name . '_clear'])) { $out[$key] = ''; } elseif (array_key_exists($name, $in) && ($clean = self::clean($in[$name], $field['kind'])) !== '') { $out[$key] = $clean; } continue; } if (array_key_exists($name, $in)) { $out[$key] = self::clean($in[$name], $field['kind']); } } return $out; } /** @param mixed $value */ public static function clean($value, string $kind): string { $value = is_scalar($value) ? trim((string) $value) : ''; switch ($kind) { case 'url': $value = esc_url_raw($value); return preg_match('#^https://[^\s/]+#i', $value) ? substr(rtrim($value, '/'), 0, 200) : ''; case 'digits': return substr(preg_replace('/\D/', '', $value) ?? '', 0, 20); default: // API keys / tokens: printable ASCII without spaces. return substr(preg_replace('/[^\x21-\x7E]/', '', $value) ?? '', 0, 200); } } /** "Integrations" section of the Zadex > Settings form (inside the existing
). */ public static function render(): void { $fields = self::fields(); $labels = ['API key' => __('API key', 'zadex'), 'API URL' => __('API URL', 'zadex'), 'API token' => __('API token', 'zadex'), 'Account ID' => __('Account ID', 'zadex')]; echo '

' . esc_html__('Integrations (Form widget)', 'zadex') . '

'; echo '

' . esc_html__('API keys used by the Form widget\'s email marketing actions. Keys are only used on the server and are never shown again after saving.', 'zadex') . '

'; echo ''; $provider = ''; foreach ($fields as $name => $field) { if ($field['provider'] !== $provider) { $provider = $field['provider']; echo ''; } $id = 'zx-int-' . str_replace('_', '-', $name); $saved = self::get($name); echo ''; } echo ''; } }