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
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
<?php

namespace Cleantalk\ApbctWP\ContactsEncoder\Integrations;

/**
* Skip Contacts Encoder shortcode handling inside rendered comment lists in buffer mode.
*/
class CEIntegrationCommentList
{
/**
* @var array<string, string>
*/
private $region_placeholders = array();

/**
* @param string $content
*
* @return string
*/
public function protect($content)
{
if ( ! is_string($content) || $content === '' ) {
return $content;
}

if (
stripos($content, 'comment-list') === false
&& stripos($content, 'id="comments"') === false
&& stripos($content, "id='comments'") === false
) {
return $content;
}

$this->region_placeholders = array();

$content = $this->protectElementsById($content, 'comments');
$content = $this->protectElementsByClass($content, 'comment-list', 'ol');
$content = $this->protectElementsByClass($content, 'comment-list', 'ul');

return $content;
}
Comment thread
svfcode marked this conversation as resolved.

/**
* @param string $content
*
* @return string
*/
public function restore($content)
{
if ( ! is_string($content) || $this->region_placeholders === array() ) {
return $content;
}

$content = strtr($content, $this->region_placeholders);
$this->region_placeholders = array();

return $content;
}

/**
* @param string $content
* @param string $id
*
* @return string
*/
private function protectElementsById($content, $id)
{
$pattern = '/<div\b(?=[^>]*\bid=(["\'])' . preg_quote($id, '/') . '\1)/i';

return $this->protectByOpeningTagPattern($content, $pattern, 'div');
}

/**
* @param string $content
* @param string $class
* @param string $tag_name
*
* @return string
*/
private function protectElementsByClass($content, $class, $tag_name)
{
$pattern = '/<' . $tag_name . '\b(?=[^>]*\bclass=(["\'])[^"\']*\b'
. preg_quote($class, '/')
. '\b[^"\']*\1)/i';

return $this->protectByOpeningTagPattern($content, $pattern, $tag_name);
}

/**
* @param string $content
* @param string $open_pattern
* @param string $tag_name
*
* @return string
*/
private function protectByOpeningTagPattern($content, $open_pattern, $tag_name)
{
$offset = 0;

while ( preg_match($open_pattern, $content, $matches, PREG_OFFSET_CAPTURE, $offset) ) {
$start = $matches[0][1] ?? null;

if ( $start === null ) {
break;
}

$element = $this->extractBalancedElement($content, $start, $tag_name);

if ( $element === null ) {
$offset = $start + 1;
continue;
}

$placeholder = '%%APBCT_COMMENT_REGION_' . count($this->region_placeholders) . '%%';
$this->region_placeholders[$placeholder] = $element;

$content = substr($content, 0, $start) . $placeholder . substr($content, $start + strlen($element));
$offset = $start + strlen($placeholder);
}

return $content;
}

/**
* @param string $content
* @param int $start
* @param string $tag_name
*
* @return string|null
*/
private function extractBalancedElement($content, $start, $tag_name)
{
$open_end = strpos($content, '>', $start);

if ( $open_end === false ) {
return null;
}

$depth = 1;
$pos = $open_end + 1;
$length = strlen($content);
$open_needle = '<' . $tag_name;
$close_needle = '</' . $tag_name;

while ( $pos < $length && $depth > 0 ) {
$next_open = stripos($content, $open_needle, $pos);
$next_close = stripos($content, $close_needle, $pos);

if ( $next_close === false ) {
return null;
}

if ( $next_open !== false && $next_open < $next_close && $this->isTagOpenAt($content, $next_open, $tag_name) ) {
$depth++;
$pos = $next_open + 1;
continue;
}

$depth--;
$close_end = strpos($content, '>', $next_close);

if ( $close_end === false ) {
return null;
}

$pos = $close_end + 1;

if ( $depth === 0 ) {
return substr($content, $start, $pos - $start);
}
}

return null;
}

/**
* @param string $content
* @param int $offset
* @param string $tag_name
*
* @return bool
*/
private function isTagOpenAt($content, $offset, $tag_name)
{
return (bool) preg_match('/^<' . preg_quote($tag_name, '/') . '\b/i', substr($content, $offset, strlen($tag_name) + 2));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Cleantalk\ApbctWP\ContactsEncoder\Shortcodes;

use Cleantalk\ApbctWP\ContactsEncoder\Integrations\CEIntegrationCommentList;
use Cleantalk\Common\ContactsEncoder\Dto\Params;

/**
Expand All @@ -15,6 +16,11 @@ class ShortCodesService

public $shortcodes_registered = false;

/**
* @var CEIntegrationCommentList
*/
private $comment_list_integration;

/**
* @return void
*/
Expand Down Expand Up @@ -65,6 +71,7 @@ public function __construct(Params $params)
{
$this->encode = new EncodeContentSC($params);
$this->shortcode_to_exclude = new ExcludedEncodeContentSC();
$this->comment_list_integration = new CEIntegrationCommentList();
}

public function addActionsBeforeModify($hook, $priority = 1)
Expand Down Expand Up @@ -100,6 +107,7 @@ public function modifyBufferBefore($buffer)
{
$this->encode->resetShortcodeReplacements();
$this->shortcode_to_exclude->resetShortcodeReplacements();
$buffer = $this->comment_list_integration->protect($buffer);
$buffer = $this->shortcode_to_exclude->changeContentBeforeEncoderModify($buffer);

return $this->encode->changeContentBeforeEncoderModify($buffer);
Expand All @@ -120,11 +128,14 @@ public function modifyBufferAfter($buffer)

if ( $apbct->settings['data__email_decoder_buffer'] ) {
$buffer = $this->shortcode_to_exclude->finalizeBufferAfterEncoding($buffer);
$buffer = $this->comment_list_integration->restore($buffer);
$apbct->buffer = $buffer;

return $buffer;
}

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

return $this->comment_list_integration->restore($buffer);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

namespace ApbctWP\ContactsEncoder\Integrations;

use Cleantalk\ApbctWP\ContactsEncoder\Integrations\CEIntegrationCommentList;
use PHPUnit\Framework\TestCase;

class TestCEIntegrationCommentList extends TestCase
{
public function testProtectAndRestoreCommentListPreservesSiblingComments()
{
$html = '<div id="comments" class="comments-area">'
. '<ol class="comment-list">'
. '<li class="comment"><div class="comment-content">[apbct_encode_data]</div></li>'
. '<li class="comment"><div class="comment-content">Second comment text</div></li>'
. '<li class="comment"><div class="comment-content">[apbct_encode_data]z[/apbct_encode_data][/apbct_encode_data]</div></li>'
. '</ol>'
. '</div>';

$integration = new CEIntegrationCommentList();
$protected = $integration->protect($html);
$restored = $integration->restore($protected);

$this->assertStringNotContainsString('[apbct_encode_data]', $protected);
$this->assertStringContainsString('%%APBCT_COMMENT_REGION_0%%', $protected);
$this->assertSame($html, $restored);
}

public function testProtectStandaloneCommentListWithoutCommentsWrapper()
{
$html = '<ol class="comment-list">'
. '<li>one</li>'
. '<li>two</li>'
. '</ol>';

$integration = new CEIntegrationCommentList();
$protected = $integration->protect($html);

$this->assertStringContainsString('%%APBCT_COMMENT_REGION_0%%', $protected);
$this->assertSame($html, $integration->restore($protected));
}

public function testProtectLeavesContentWithoutCommentListUntouched()
{
$html = '<article>[apbct_encode_data]post@example.com[/apbct_encode_data]</article>';

$integration = new CEIntegrationCommentList();

$this->assertSame($html, $integration->protect($html));
}

public function testRestoreClearsPlaceholdersForSubsequentCalls()
{
$html = '<ol class="comment-list"><li>one</li></ol>';

$integration = new CEIntegrationCommentList();
$protected = $integration->protect($html);
$restored = $integration->restore($protected);

$this->assertSame($html, $restored);
$this->assertSame($protected, $integration->restore($protected));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace ApbctWP\ContactsEncoder;

use Cleantalk\ApbctWP\ContactsEncoder\Shortcodes\EncodeContentSC;
use Cleantalk\ApbctWP\ContactsEncoder\Shortcodes\ShortCodesService;
use Cleantalk\ApbctWP\Variables\Cookie;
use Cleantalk\Common\ContactsEncoder\Dto\Params;
use PHPUnit\Framework\TestCase;
Expand All @@ -14,6 +15,11 @@ class testEmailEncoderShortCodeEncode extends TestCase
*/
private $shortcode;

/**
* @var ShortCodesService
*/
private $shortcodes_service;

protected function setUp(): void
{
/**
Expand All @@ -27,6 +33,7 @@ protected function setUp(): void
$params = new Params();
$params->api_key = $apbct->api_key;
$this->shortcode = new EncodeContentSC($params);
$this->shortcodes_service = new ShortCodesService($params);
$this->shortcode->register();
$this->clearDecoderPassedCookie();
}
Expand Down Expand Up @@ -345,4 +352,30 @@ public function testAdjacentShortcodesOutsideEncodeDataAreNotExecuted()
}
}

public function testBufferModeDoesNotAbsorbCommentsFromEncodeDataTags()
{
global $apbct;

$previous_buffer_setting = $apbct->settings['data__email_decoder_buffer'];
$apbct->settings['data__email_decoder_buffer'] = true;

$html = '<div id="comments" class="comments-area"><ol class="comment-list">'
. '<li class="comment"><div class="comment-content">[apbct_encode_data]</div></li>'
. '<li class="comment"><div class="comment-content">Second comment text</div></li>'
. '<li class="comment"><div class="comment-content">[apbct_encode_data]z[/apbct_encode_data][/apbct_encode_data]</div></li>'
. '</ol></div>';

try {
$buffer = $this->shortcodes_service->modifyBufferBefore($html);
$buffer = apbctGetContactsEncoder()->modifyContent($buffer);
$buffer = $this->shortcodes_service->modifyBufferAfter($buffer);

$this->assertStringContainsString('Second comment text', $buffer);
$this->assertStringContainsString('[apbct_encode_data]', $buffer);
$this->assertStringNotContainsString('apbct-email-encoder', $buffer);
} finally {
$apbct->settings['data__email_decoder_buffer'] = $previous_buffer_setting;
}
}

}
Loading