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

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();
}
121 changes: 121 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,125 @@ 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
*/
Comment thread
svedge marked this conversation as resolved.
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;
}

// Digit compare is for phones only. Emails with long number sequences
// must not match an excluded phone (or the other way around).
if (
strpos($normalized_match, '@') === false
&& strpos($normalized_exclusion, '@') === false
) {
$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 : '';
}
}
16 changes: 10 additions & 6 deletions lib/Cleantalk/Common/Helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -590,18 +590,22 @@ public static function ipResolve($ip)
}

/**
* Resolve DNS to IP
* Resolve DNS to IP.
*
* @param $host
* @param bool $out
* $host must be a full URL that passes FILTER_VALIDATE_URL (scheme required).
* A bare hostname such as HTTP_HOST, or any non-string value, is rejected
* and $out is returned.
*
* @return bool
* @param mixed $host Full URL (FILTER_VALIDATE_URL); other types are rejected
* @param bool|string $out Fallback when $host is invalid or lookup fails
*
* @return bool|string First A-record IPv4 on success, otherwise $out
* @psalm-suppress PossiblyUnusedMethod
*/
public static function dnsResolve($host, $out = false)
{
// Validate/normalize host (accept hostname or IP; URLs and host:port are also supported)
if ( ! $host || ! is_string($host) ) {
// Check if the $url is set and it is an url
if ( ! is_string($host) || $host === '' || ! filter_var($host, FILTER_VALIDATE_URL)) {
return $out;
}

Expand Down
Loading
Loading