Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions cleantalk.php
Original file line number Diff line number Diff line change
Expand Up @@ -3171,6 +3171,11 @@ function apbctGetContactsEncoder()
$contacts_encoder_params->obfuscation_text = $apbct->settings['data__email_decoder_obfuscation_custom_text'];
$contacts_encoder_params->do_encode_emails = (int)$apbct->settings['data__email_decoder_encode_email_addresses'];
$contacts_encoder_params->do_encode_phones = (int)$apbct->settings['data__email_decoder_encode_phone_numbers'];
$contacts_encoder_params->excluded_strings = \Cleantalk\Common\ContactsEncoder\Exclusions\ExclusionsService::parseExcludedStrings(
isset($apbct->settings['data__email_decoder_excluded_strings'])
? $apbct->settings['data__email_decoder_excluded_strings']
: ''
);
Comment on lines +3174 to +3178

return ContactsEncoder::getInstance($contacts_encoder_params);
}
2 changes: 1 addition & 1 deletion css/cleantalk-admin-settings-page.min.css

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions css/src/cleantalk-admin-settings-page.css
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ i.animate-spin {
font-size: 14px;
vertical-align: top;
}
.apbct_settings-field_title--with-help{
width: auto;
white-space: nowrap;
}
.apbct_settings-field_title--radio{
display: inline-block;
margin: 0;
Expand Down
48 changes: 47 additions & 1 deletion inc/cleantalk-settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -659,7 +659,8 @@ function apbct_settings__set_fields()
'data__email_decoder_obfuscation_mode',
'data__email_decoder_obfuscation_custom_text',
'data__email_decoder_encode_phone_numbers',
'data__email_decoder_encode_email_addresses'
'data__email_decoder_encode_email_addresses',
'data__email_decoder_excluded_strings'
),
'long_description' => true,
),
Expand All @@ -676,6 +677,14 @@ function apbct_settings__set_fields()
'parent' => 'data__email_decoder',
'long_description' => true,
),
'data__email_decoder_excluded_strings' => array(
'type' => 'textarea',
'title' => __('Do not encode these contacts', 'cleantalk-spam-protect'),
'description' => ContactsEncoder::getExcludedStringsDescription(),
'parent' => 'data__email_decoder',
'class' => 'apbct_settings-field_wrapper--sub',
'long_description' => true,
),
'data__email_decoder_obfuscation_mode' => array(
'title' => __('Encoder obfuscation mode', 'cleantalk-spam-protect'),
'description' => ContactsEncoder::getObfuscationModesDescription(),
Expand Down Expand Up @@ -2382,6 +2391,7 @@ function apbct_settings__validate($incoming_settings)
'data__email_decoder_obfuscation_mode',
'data__email_decoder_obfuscation_custom_text',
'data__email_decoder_buffer',
'data__email_decoder_excluded_strings',
);
$incoming_settings = apbct_settings__keep_settings_state_values(
$incoming_settings,
Expand Down Expand Up @@ -2716,6 +2726,12 @@ function apbct_settings__validate($incoming_settings)
$incoming_settings['data__email_decoder_obfuscation_custom_text'] = ContactsEncoder::getDefaultReplacingText();
}

$incoming_settings['data__email_decoder_excluded_strings'] = apbct_settings__sanitize__excluded_contact_strings(
isset($incoming_settings['data__email_decoder_excluded_strings'])
? $incoming_settings['data__email_decoder_excluded_strings']
: ''
);

//sync discussion and plugin settings
if (isset($incoming_settings['cleantalk_allowed_moderation'])) {
update_option('cleantalk_allowed_moderation', TT::toString($incoming_settings['cleantalk_allowed_moderation'], '0'));
Expand Down Expand Up @@ -3264,6 +3280,32 @@ function apbct_settings__sanitize__exclusions($exclusions, $regexp = false, $url
}
}

/**
* Sanitize the Contacts Encoder skip-list: one value per line, max 20 items, 128 chars each.
*
* @param mixed $exclusions
*
* @return string
*/
function apbct_settings__sanitize__excluded_contact_strings($exclusions)
{
if ( ! is_string($exclusions) ) {
return '';
}

$parts = \Cleantalk\Common\ContactsEncoder\Exclusions\ExclusionsService::parseExcludedStrings($exclusions);
$parts = array_slice($parts, 0, 20);
$result = array();
foreach ( $parts as $part ) {
$part = trim(substr($part, 0, 128), " \n\r\t\v\x00");
if ( $part !== '' ) {
$result[] = $part;
}
}

return implode("\n", array_values(array_unique($result)));
}

function apbct_settings__get__long_description()
{
global $apbct;
Expand Down Expand Up @@ -3434,6 +3476,10 @@ function apbct_settings__get_long_descriptions_data()
'title' => __('Contact data encoding: phone numbers', 'cleantalk-spam-protect'),
'desc' => ContactsEncoder::getPhonesEncodingLongDescription(),
),
'data__email_decoder_excluded_strings' => array(
'title' => __('Contact data encoding: do not encode these contacts', 'cleantalk-spam-protect'),
'desc' => ContactsEncoder::getExcludedStringsLongDescription(),
),
'data__email_decoder' => array(
'title' => __('Contact data encoding', 'cleantalk-spam-protect'),
'desc' => ContactsEncoder::getEmailEncoderCommonLongDescription(),
Expand Down
30 changes: 30 additions & 0 deletions lib/Cleantalk/ApbctWP/ContactsEncoder/ContactsEncoder.php
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,36 @@ public static function getEmailsEncodingDescription()
return __('Encode email addresses', 'cleantalk-spam-protect');
}

public static function getExcludedStringsDescription()
{
return __(
'List emails, phone numbers or text fragments that must never be encoded. Put one value per line. Each value is limited to 128 characters, up to 20 values.',
'cleantalk-spam-protect'
);
}

public static function getExcludedStringsLongDescription()
{
$tmp = '
<p>%s</p>
<p>%s</p>
<p class="apbct-icon-right-dir" style="padding-left: 10px">%s</p>
<p class="apbct-icon-right-dir" style="padding-left: 10px">%s</p>
<p class="apbct-icon-right-dir" style="padding-left: 10px">%s</p>
<p>%s</p>
';
$tmp = sprintf(
$tmp,
__('Use this list when you need a contact to stay readable everywhere, including titles, menus and widgets — WordPress does not run shortcodes in those places.', 'cleantalk-spam-protect'),
__('Put one value per line:', 'cleantalk-spam-protect'),
__('an email, e.g. support@example.com', 'cleantalk-spam-protect'),
__('a phone number, e.g. +1 (234) 567-8901 — format differences are ignored', 'cleantalk-spam-protect'),
__('a text fragment, e.g. example.com to skip all emails on that domain', 'cleantalk-spam-protect'),
__('Each value is sliced to 128 characters. The list is limited to 20 values.', 'cleantalk-spam-protect')
);
return $tmp;
}

public static function getPhonesEncodingLongDescription()
{
$tmp = '
Expand Down
6 changes: 5 additions & 1 deletion lib/Cleantalk/ApbctWP/PluginSettingsPage/SettingsField.php
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,11 @@ class="apbct_setting_{{type}} apbct_setting---{{name}}"
*/
private function getInputTextarea()
{
$title_layout = '<h4 class="apbct_settings-field_title apbct_settings-field_title--{{type}}">{{title}} {{popup_description}}</h4>';
$title_class = 'apbct_settings-field_title apbct_settings-field_title--{{type}}';
if ( $this->description_popup !== '' ) {
$title_class .= ' apbct_settings-field_title--with-help';
}
$title_layout = '<h4 class="' . $title_class . '">{{title}} {{popup_description}}</h4>';

$raw_value = empty($this->value) ? TT::getArrayValueAsString($this->params, 'value') : $this->value;
if (is_array($raw_value)) {
Expand Down
1 change: 1 addition & 0 deletions lib/Cleantalk/ApbctWP/State.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ class State extends \Cleantalk\Common\State
'data__email_decoder_obfuscation_custom_text' => '',
'data__email_decoder_encode_phone_numbers' => 0,
'data__email_decoder_encode_email_addresses' => 1,
'data__email_decoder_excluded_strings' => '',
'data__wc_store_blocked_orders' => 0,

// Exclusions
Expand Down
8 changes: 8 additions & 0 deletions lib/Cleantalk/Common/ContactsEncoder/ContactsEncoder.php
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,10 @@ public function modifyGlobalEmails($content)

$position = $this->advanceMatchCursor($matches[0], $match_cursor);

if ( $this->exclusions->isContactExcluded($matches[0]) ) {
return $matches[0];
}

if ( isset($matches[3]) && in_array(strtolower($matches[3]), ['.jpg', '.jpeg', '.png', '.gif', '.svg', '.webp']) ) {
return $matches[0];
}
Expand Down Expand Up @@ -372,6 +376,10 @@ function ($matches) use (&$match_cursor) {

$position = $this->advanceMatchCursor($matches[0], $match_cursor);

if ( $this->exclusions->isContactExcluded($matches[0]) ) {
return $matches[0];
}

if ( $this->helper->isTelTag($matches[0]) ) {
return $this->encodeTelLink($matches[0]);
}
Expand Down
7 changes: 7 additions & 0 deletions lib/Cleantalk/Common/ContactsEncoder/Dto/Params.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,11 @@ class Params
public $obfuscation_text = 'hidden contact data';
public $do_encode_emails = 1;
public $do_encode_phones = 0;

/**
* Emails, phones or text fragments that must never be encoded.
*
* @var string[]
*/
public $excluded_strings = array();
}
114 changes: 114 additions & 0 deletions lib/Cleantalk/Common/ContactsEncoder/Exclusions/ExclusionsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,118 @@ protected function byLoggedIn()
{
return $this->params->is_logged_in;
}

/**
* Split a settings textarea into unique non-empty exclusion strings (one value per line).
*
* @param string $raw
*
* @return string[]
* @psalm-suppress PossiblyUnusedMethod Public API for host apps that store the list as raw text
*/
public static function parseExcludedStrings($raw)
{
if ( ! is_string($raw) || $raw === '' ) {
return array();
}

$parts = preg_split('/\R+/', $raw);
if ( ! is_array($parts) ) {
return array();
}

$result = array();
foreach ( $parts as $part ) {
$part = trim($part, " \n\r\t\v\x00");
if ( $part !== '' ) {
$result[] = $part;
}
}

return array_values(array_unique($result));
}

/**
* Whether a matched email or phone must stay unencoded.
*
* @param string $match
*
* @return bool
*/
public function isContactExcluded($match)
{
if ( ! is_string($match) || $match === '' ) {
return false;
}

if ( empty($this->params->excluded_strings) || ! is_array($this->params->excluded_strings) ) {
return false;
}

$normalized_match = $this->normalizeContactString($match);
$match_digits = $this->extractDigits($match);

foreach ( $this->params->excluded_strings as $exclusion ) {
if ( ! is_string($exclusion) || $exclusion === '' ) {
continue;
}

$normalized_exclusion = $this->normalizeContactString($exclusion);
if ( $normalized_exclusion === '' ) {
continue;
}

if ( $normalized_match === $normalized_exclusion ) {
return true;
}

if ( strpos($normalized_match, $normalized_exclusion) !== false ) {
return true;
}

$exclusion_digits = $this->extractDigits($exclusion);
if (
strlen($exclusion_digits) >= 8
&& strlen($match_digits) >= 8
&& (
strpos($match_digits, $exclusion_digits) !== false
|| strpos($exclusion_digits, $match_digits) !== false
)
) {
return true;
}
}

return false;
}

/**
* @param string $value
*
* @return string
*/
private function normalizeContactString($value)
{
$value = trim($value, " \n\r\t\v\x00");
if ( stripos($value, 'mailto:') === 0 ) {
$value = substr($value, 7);
}
if ( stripos($value, 'tel:') === 0 ) {
$value = substr($value, 4);
}

return strtolower(trim($value, " \n\r\t\v\x00"));
}

/**
* @param string $value
*
* @return string
*/
private function extractDigits($value)
{
$digits = preg_replace('/\D+/', '', $value);

return is_string($digits) ? $digits : '';
}
}
4 changes: 2 additions & 2 deletions lib/Cleantalk/Common/Helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -593,9 +593,9 @@ public static function ipResolve($ip)
* Resolve DNS to IP
*
* @param $host
* @param bool $out
* @param bool|string $out
*
* @return bool
* @return bool|string
* @psalm-suppress PossiblyUnusedMethod
*/
public static function dnsResolve($host, $out = false)
Expand Down
39 changes: 39 additions & 0 deletions tests/ApbctWP/ContactsEncoder/Exclusions/TestExclusionsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,45 @@ public function testByContentSignsIgnoresNonArrayFilterResult()
$this->assertFalse($result);
}

public function testIsContactExcludedMatchesEmailAndPhoneVariants()
{
$params = new Params();
$params->api_key = 'testapikey';
$params->excluded_strings = array('keep@example.com', '+1 800 555-1234', 'company.org');
$service = new ExclusionsService($params);

$this->assertTrue($service->isContactExcluded('keep@example.com'));
$this->assertTrue($service->isContactExcluded('mailto:keep@example.com'));
$this->assertTrue($service->isContactExcluded('office@company.org'));
$this->assertTrue($service->isContactExcluded('(800) 555-1234'));
$this->assertFalse($service->isContactExcluded('public@other.net'));
$this->assertFalse($service->isContactExcluded('(800) 555-9999'));
}

public function testParseExcludedStringsSplitsLines()
{
$parsed = \Cleantalk\Common\ContactsEncoder\Exclusions\ExclusionsService::parseExcludedStrings(
"keep@example.com\n+1 800 555-1234\nexample.com\n"
);

$this->assertSame(
array('keep@example.com', '+1 800 555-1234', 'example.com'),
$parsed
);
}

public function testParseExcludedStringsKeepsCommaInsideALine()
{
$parsed = \Cleantalk\Common\ContactsEncoder\Exclusions\ExclusionsService::parseExcludedStrings(
"keep@example.com, office@example.com\n+1 800 555-1234"
);

$this->assertSame(
array('keep@example.com, office@example.com', '+1 800 555-1234'),
$parsed
);
}

/**
* Helper method to invoke private methods for testing
*
Expand Down
Loading
Loading