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
30 changes: 25 additions & 5 deletions lib/Cleantalk/ApbctWP/ContactsEncoder/ContactsEncoder.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ class ContactsEncoder extends \Cleantalk\Common\ContactsEncoder\ContactsEncoder
*/
private $privacy_policy_hook_handled = false;

/**
* @var bool
*/
private $buffer_modified = false;

/**
* @var string[]
*/
Expand Down Expand Up @@ -84,9 +89,23 @@ public function runEncoding($content = '')
if ($apbct->settings['data__email_decoder_buffer'] && !apbct_is_ajax() && !apbct_is_rest() && !apbct_is_post() && !is_admin()) {
add_action('wp', 'apbct_buffer__start');
add_action('shutdown', 'apbct_buffer__end', 0); // Collect $apbct->buffer
add_action('shutdown', array($this, 'modifyBuffer'), 2); // Modify $apbct->buffer by `ContactsEncoder::modifyBuffer`
$this->shortcodes->addActionsAfterModify('shutdown', 3); // Modify $apbct->buffer by `ShortCodesService::addActionsAfterModify`
add_action('shutdown', array($this, 'modifyBuffer'), 1); // Before apbct_buffer__output (priority 2)
add_action('shutdown', array($this, 'bufferOutput'), 999); // Output $apbct->buffer

foreach ( $hooks_to_encode as $hook ) {
if ( $hook === 'render_block' ) {
// Post content is handled on the_content after do_blocks (priority 9).
continue;
}
if ( $hook === 'the_content' ) {
// Priority 9 runs after do_blocks (9) when registered from init — placeholders keep <p> wrappers.
$this->shortcodes->addActionsBeforeModify($hook, 9);
$this->shortcodes->addActionsAfterModifyEncodeOnly($hook, 999);
continue;
}
$this->shortcodes->addActionsBeforeModify($hook, 9);
$this->shortcodes->addActionsAfterModifyEncodeOnly($hook, 999);
}
} else {
foreach ( $hooks_to_encode as $hook ) {
$this->shortcodes->addActionsBeforeModify($hook, 9);
Expand Down Expand Up @@ -180,12 +199,13 @@ private function handlePrivacyPolicyHook()
public function modifyBuffer()
{
global $apbct;
static $already_output = false;
if ($already_output) {
if ($this->buffer_modified) {
return;
}
$already_output = true;
$this->buffer_modified = true;
$apbct->buffer = $this->shortcodes->modifyBufferBefore($apbct->buffer);
$apbct->buffer = $this->modifyContent($apbct->buffer);
$apbct->buffer = $this->shortcodes->modifyBufferAfter($apbct->buffer);
}

public function bufferOutput()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ class ExclusionsService extends \Cleantalk\Common\ContactsEncoder\Exclusions\Exc

public function doReturnContentBeforeModify($content)
{
if ( $this->byDecoderPassedCookie() ) {
return 'byDecoderPassedCookie';
}

if ( $this->byUrlOnHooks() ) {
return 'byUrlOnHooks';
}
Expand All @@ -54,6 +58,43 @@ public function doReturnContentBeforeModify($content)
return parent::doReturnContentBeforeModify($content);
}

/**
* Exclusions for shortcode placeholder pipeline.
* Unlike doReturnContentBeforeModify(), not blocked when global email/phone encoding is off.
*
* @param string $content
*
* @return string|false
*/
public function doReturnShortcodeContentBeforeModify($content)
{
if ( $this->byUrlOnHooks() ) {
return 'byUrlOnHooks';
}

if ( $this->byContentSigns($content) ) {
return 'byContentSigns';
}

if ( $this->byLoggedIn() ) {
return 'byLoggedIn';
}

if ( empty($content) || ! is_string($content) ) {
return 'byEmptyContent';
}

return false;
}

/**
* @return bool
*/
protected function byLoggedIn()
{
return $this->params->is_logged_in;
}

/**
* @inerhitDoc
*/
Expand Down Expand Up @@ -90,12 +131,6 @@ public function doSkipBeforeModifyingHooksAdded()
*/
private function byServerVars()
{
// Excluded request by alt cookie
$apbct_email_encoder_passed = Cookie::get('apbct_email_encoder_passed');
if ( $apbct_email_encoder_passed === apbct_get_email_encoder_pass_key() ) {
return true;
}

if (
apbct_is_plugin_active('ultimate-member/ultimate-member.php') &&
isset($_POST['um_request']) &&
Expand All @@ -109,6 +144,18 @@ private function byServerVars()
return false;
}

/**
* User passed email encoder check — skip encoding but keep shortcode layout hooks.
*
* @return bool
*/
private function byDecoderPassedCookie()
{
$apbct_email_encoder_passed = Cookie::get('apbct_email_encoder_passed');

return $apbct_email_encoder_passed === apbct_get_email_encoder_pass_key();
}

/**
* @param State $apbct
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use Cleantalk\ApbctWP\Escape;
use Cleantalk\ApbctWP\Variables\Cookie;
use Cleantalk\Common\ContactsEncoder\Dto\Params;
use Cleantalk\Common\ContactsEncoder\Exclusions\ExclusionsService;
use Cleantalk\ApbctWP\ContactsEncoder\Exclusions\ExclusionsService;

/**
* Shortcode to encode any string content.
Expand Down Expand Up @@ -99,7 +99,7 @@ public function callback($_atts, $content, $_tag)
*/
public function changeContentBeforeEncoderModify($content)
{
if ( $this->exclusions->doReturnContentBeforeModify($content) ) {
if ( $this->exclusions->doReturnShortcodeContentBeforeModify($content) ) {
return $content;
}

Expand Down Expand Up @@ -146,6 +146,19 @@ public function changeContentAfterEncoderModify($content)
foreach ($this->shortcode_replacements as $placeholder => $original) {
$content = str_replace($placeholder, $original, $content);
}
return $this->doCallbackAction($content);

$result = $this->doCallbackAction($content);
$this->resetShortcodeReplacements();

return $result;
}

/**
* @return void
*/
public function resetShortcodeReplacements()
{
$this->shortcode_replacements = array();
$this->shortcode_counter = 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,12 @@ public function changeContentAfterEncoderModify($content)
}

if ( $apbct->settings['data__email_decoder_buffer'] ) {
$content = $apbct->buffer;
if ( $this->getCurrentAction() !== 'shutdown' ) {
return $content;
}
if ( $content === '' || $content === null ) {
$content = $apbct->buffer;
}
}

// Skip processing if shortcode is inside an HTML tag to prevent attribute injection
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ public function registerAll()
if (!$this->shortcodes_registered) {
$this->encode->register();
if ( ! $apbct->settings['data__email_decoder_buffer'] ) {
// If buffer is active, Do not run wordpress shortcode replacement - encoder do it itself here `ExcludedEncodeContentSC::changeContentAfterEncoderModify`
// If buffer is active, do not run WordPress shortcode replacement -
// encoder processes apbct_skip_encoding in modifyBufferAfter().
$this->shortcode_to_exclude->register();
}
$this->shortcodes_registered = true;
Expand All @@ -48,4 +49,49 @@ public function addActionsAfterModify($hook, $priority = 999)
add_filter($hook, array($this->encode, 'changeContentAfterEncoderModify'), $priority);
add_filter($hook, array($this->shortcode_to_exclude, 'changeContentAfterEncoderModify'), $priority);
}

public function addActionsAfterModifyEncodeOnly($hook, $priority = 999)
{
add_filter($hook, array($this->encode, 'changeContentAfterEncoderModify'), $priority);
}

/**
* Prepare output buffer content before ContactsEncoder::modifyContent().
*
* @param string $buffer
*
* @return string
*/
public function modifyBufferBefore($buffer)
{
$this->encode->resetShortcodeReplacements();

return $this->encode->changeContentBeforeEncoderModify($buffer);
}

/**
* Finalize output buffer content after ContactsEncoder::modifyContent().
*
* @param string $buffer
*
* @return string
*/
public function modifyBufferAfter($buffer)
{
global $apbct;

$buffer = $this->encode->changeContentAfterEncoderModify($buffer);

if ( $apbct->settings['data__email_decoder_buffer'] ) {
$apbct->buffer = $buffer;
}

$buffer = $this->shortcode_to_exclude->changeContentAfterEncoderModify($buffer);

if ( $apbct->settings['data__email_decoder_buffer'] ) {
return $apbct->buffer;
}

return $buffer;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,32 @@ public function testChangeContentAfterEncoderModifyWithBufferOn(): void
$this->contacts_encoder->dropInstance(); // Need to rebuild the object after the settings changed
$this->contacts_encoder = apbctGetContactsEncoder();

$result = $this->exclude_content_sc->changeContentAfterEncoderModify('');
$result = $this->exclude_content_sc->changeContentAfterEncoderModify($content);

$this->assertEquals($content, $result);
}

/**
* Test changeContentAfterEncoderModify uses apbct->buffer during shutdown when content is empty
*/
public function testChangeContentAfterEncoderModifyWithBufferOnUsesBufferDuringShutdown(): void
{
global $apbct;

$content = 'original buffer content test@example.com';

$apbct->settings['data__email_decoder_buffer'] = true;
$apbct->buffer = $content;
$apbct->saveSettings();
$this->contacts_encoder->dropInstance();
$this->contacts_encoder = apbctGetContactsEncoder();

$shortcodeMock = $this->getMockBuilder(ExcludedEncodeContentSC::class)
->setMethods(['getCurrentAction'])
->getMock();
$shortcodeMock->method('getCurrentAction')->willReturn('shutdown');

$result = $shortcodeMock->changeContentAfterEncoderModify('');

$this->assertEquals($content, $result);
}
Expand Down Expand Up @@ -134,12 +159,15 @@ public function testChangeContentAfterEncoderModifyWithMultipleShortcodes(): voi
$apbct->saveSettings();
$this->contacts_encoder->dropInstance(); // Need to rebuild the object after the settings changed
$this->contacts_encoder = apbctGetContactsEncoder();
$apbct->buffer = 'buffer [apbct_skip_encoding]first[/apbct_skip_encoding] and [apbct_skip_encoding]second[/apbct_skip_encoding]';
$buffer_content = 'buffer [apbct_skip_encoding]first[/apbct_skip_encoding] and [apbct_skip_encoding]second[/apbct_skip_encoding]';
$apbct->buffer = $buffer_content;

$shortcodeMock = $this->getMockBuilder(ExcludedEncodeContentSC::class)
->setMethods(['callback'])
->setMethods(['callback', 'getCurrentAction'])
->getMock();

$shortcodeMock->method('getCurrentAction')->willReturn('shutdown');

// Expect two calls to callback
$matcher = $this->exactly(2);
$shortcodeMock->expects($matcher)
Expand All @@ -156,7 +184,7 @@ public function testChangeContentAfterEncoderModifyWithMultipleShortcodes(): voi
return '';
});

$result = $shortcodeMock->changeContentAfterEncoderModify('input');
$result = $shortcodeMock->changeContentAfterEncoderModify($buffer_content);

$this->assertEquals('buffer decoded first and decoded second', $result);
}
Expand All @@ -172,17 +200,20 @@ public function testChangeContentAfterEncoderModifyWhenCallbackReturnsUnmodified
$apbct->saveSettings();
$this->contacts_encoder->dropInstance(); // Need to rebuild the object after the settings changed
$this->contacts_encoder = apbctGetContactsEncoder();
$apbct->buffer = 'buffer [apbct_skip_encoding]content[/apbct_skip_encoding]';
$buffer_content = 'buffer [apbct_skip_encoding]content[/apbct_skip_encoding]';
$apbct->buffer = $buffer_content;

$shortcodeMock = $this->getMockBuilder(ExcludedEncodeContentSC::class)
->setMethods(['callback'])
->setMethods(['callback', 'getCurrentAction'])
->getMock();

$shortcodeMock->method('getCurrentAction')->willReturn('shutdown');

$shortcodeMock->expects($this->once())
->method('callback')
->willReturn('content'); // Return unmodified content

$result = $shortcodeMock->changeContentAfterEncoderModify('input');
$result = $shortcodeMock->changeContentAfterEncoderModify($buffer_content);

// Expect content not to change, but tags are removed
$this->assertEquals('buffer content', $result);
Expand Down Expand Up @@ -265,7 +296,7 @@ public function testChangeContentAfterEncoderModifySkipsShortcodeInsideHtmlTag()
$this->contacts_encoder->dropInstance();
$this->contacts_encoder = apbctGetContactsEncoder();

$result = $this->exclude_content_sc->changeContentAfterEncoderModify('');
$result = $this->exclude_content_sc->changeContentAfterEncoderModify($content);

// Content should be returned unchanged because shortcode is inside HTML tag
$this->assertEquals($content, $result);
Expand All @@ -289,7 +320,7 @@ public function testMixedShortcodesSafeAndUnsafe(): void
$this->contacts_encoder->dropInstance();
$this->contacts_encoder = apbctGetContactsEncoder();

$result = $this->exclude_content_sc->changeContentAfterEncoderModify('');
$result = $this->exclude_content_sc->changeContentAfterEncoderModify($content);

// Full block should be skipped if ANY shortcode is inside HTML tag
$this->assertEquals($content, $result);
Expand Down
Loading
Loading