Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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 thread
svedge marked this conversation as resolved.

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
23 changes: 22 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,13 @@ function apbct_settings__validate($incoming_settings)
$incoming_settings['data__email_decoder_obfuscation_custom_text'] = ContactsEncoder::getDefaultReplacingText();
}

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

Comment thread
svedge marked this conversation as resolved.
Outdated
//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 @@ -3434,6 +3451,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. Use commas or new lines as separator. 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 or separate them with commas:', '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.
*
* @param string $raw
*
* @return string[]
* @psalm-suppress PossiblyUnusedMethod Public API for host apps that store the list as raw text
*/
Comment thread
svedge marked this conversation as resolved.
public static function parseExcludedStrings($raw)
{
if ( ! is_string($raw) || $raw === '' ) {
return array();
}

$parts = preg_split('/[\r\n,]+/', $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;
}
}
Comment thread
svedge marked this conversation as resolved.
Outdated

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
Comment thread
svedge marked this conversation as resolved.
Outdated
*
* @return bool
* @return bool|string
* @psalm-suppress PossiblyUnusedMethod
Comment thread
svedge marked this conversation as resolved.
Outdated
Comment thread
svedge marked this conversation as resolved.
Outdated
*/
public static function dnsResolve($host, $out = false)
Expand Down
27 changes: 27 additions & 0 deletions tests/ApbctWP/ContactsEncoder/Exclusions/TestExclusionsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,33 @@ 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 testParseExcludedStringsSplitsLinesAndCommas()
{
$parsed = \Cleantalk\Common\ContactsEncoder\Exclusions\ExclusionsService::parseExcludedStrings(
"keep@example.com\n+1 800 555-1234, example.com\n"
);

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

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