Skip to content
Merged
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 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 @@ -481,7 +481,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 : '';
}
}
20 changes: 12 additions & 8 deletions lib/Cleantalk/Common/Helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -590,19 +590,23 @@ public static function ipResolve($ip)
}

/**
* Resolve DNS to IP
* Resolve DNS to IP.
*
* @param $host
* $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 false|string
* @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)
public static function dnsResolve($host, $out = false)
{
$out = false;
// Validate/normalize host (accept hostname or IP; URLs and host:port are also supported)
if ( ! $host || ! is_string($host) ) {
return false;
// Check if the $url is set and it is an url
if ( ! is_string($host) || $host === '' || ! filter_var($host, FILTER_VALIDATE_URL)) {
return $out;
}

if ( strpos($host, '://') !== false ) {
Expand Down
Loading
Loading