Files
oai-symfony/typo3conf/ext/news/Classes/ViewHelpers/PaginateBodytextViewHelper.php
2026-07-03 17:53:31 +02:00

230 lines
6.9 KiB
PHP

<?php
namespace GeorgRinger\News\ViewHelpers;
use GeorgRinger\News\Domain\Model\News;
/**
* This file is part of the "news" Extension for TYPO3 CMS.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*/
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
/**
* Paginate the bodytext which is very useful for longer texts or to increase
* traffic.
*
* Example
* ----------
* <n:paginateBodytext object="{newsItem}"
* as="bodytext" currentPage="{currentPage}">
* <div class="articleBodyText">
* <f:format.html>{bodytext}</f:format.html>
* </div>
* <f:if condition="{pagination.numberOfPages} > 1">
* <div class="articlePagination">
* <span>Seite</span>
* <ul>
* <f:for each="{pagination.pages}" as="page">
* <f:if condition="{page.isCurrent}">
* <f:then>
* <li class="current">
* {page.number}
* </li>
* </f:then>
* <f:else>
* <li>
* <f:if condition="{page.number} == 1">
* <f:then>
* <f:link.action action="detail" arguments="{news: newsItem}">
* {page.number}
* </f:link.action>
* </f:then>
* <f:else>
* <f:link.action action="detail" arguments="{news: newsItem, currentPage: page.number}">
* {page.number}
* </f:link.action>
* </f:else>
* </f:if>
* </li>
* </f:else>
* </f:if>
* </f:for>
* </ul>
* </div>
* </f:if>
* </n:paginateBodytext>
*/
class PaginateBodytextViewHelper extends AbstractViewHelper
{
/**
* @var bool
*/
protected $escapeOutput = false;
/**
* Initialize arguments
*/
public function initializeArguments()
{
parent::initializeArguments();
$this->registerArgument('object', News::class, 'news item', true);
$this->registerArgument('as', 'string', 'as', true);
$this->registerArgument('currentPage', 'int', 'current page', true);
$this->registerArgument('token', 'string', 'token', '###more###');
}
/**
* Render everything
*
* @return string
*/
public function render(): string
{
$as = $this->arguments['as'];
$currentPage = $this->arguments['currentPage'];
$parts = GeneralUtility::trimExplode($this->arguments['token'], $this->arguments['object']->getBodytext(), true);
$numberOfPages = count($parts);
if ($numberOfPages === 1) {
$result = $parts[0];
} else {
$currentPage = (int)$currentPage;
if ($currentPage < 1) {
$currentPage = 1;
} elseif ($currentPage > $numberOfPages) {
$currentPage = $numberOfPages;
}
$tagsToOpen = [];
$tagsToClose = [];
for ($j = 0; $j < $currentPage; $j++) {
$chunk = $parts[$j];
while (($chunk = mb_strstr($chunk, '<'))) {
$tag = $this->extractTag($chunk);
$tagStrLen = mb_strlen($tag);
if ($this->isOpeningTag($tag)) {
if ($j < $currentPage - 1) {
$tagsToOpen[] = $tag;
}
$tagsToClose[] = $tag;
} elseif ($this->isClosingTag($tag)) {
if ($j < $currentPage - 1) {
array_pop($tagsToOpen);
} elseif (mb_strpos($parts[$j], $chunk) === 0) {
$parts[$j] = mb_substr($parts[$j], $tagStrLen);
array_pop($tagsToOpen);
}
array_pop($tagsToClose);
}
$chunk = mb_substr($chunk, $tagStrLen);
}
}
$result = implode('', $tagsToOpen) . $parts[$currentPage - 1];
while ($tag = array_pop($tagsToClose)) {
$result .= $this->getClosingTagByOpeningTag($tag);
}
}
$pages = [];
for ($i = 1; $i <= $numberOfPages; $i++) {
$pages[] = ['number' => $i, 'isCurrent' => ($i === $currentPage)];
}
$pagination = [
'pages' => $pages,
'numberOfPages' => $numberOfPages,
'current' => $currentPage
];
if ($currentPage < $numberOfPages) {
$pagination['nextPage'] = $currentPage + 1;
}
if ($currentPage > 1) {
$pagination['previousPage'] = $currentPage - 1;
}
$this->templateVariableContainer->add($as, $result);
$this->templateVariableContainer->add('pagination', $pagination);
return $this->renderChildren();
}
/**
* Extracts the first html tag for a given html string
*
* @param string $html
* @return string
*/
protected function extractTag($html): string
{
$tag = '';
$length = mb_strlen($html);
for ($i = 0; $i < $length; $i++) {
$char = mb_substr($html, $i, 1);
$tag .= $char;
if ($char === '>') {
break;
}
}
return $tag;
}
/**
* Checks whether a given tag is self closing
*
* @param string $tag
* @return bool
*/
protected function isSelfClosingTag($tag): bool
{
return mb_substr($tag, -2) === '/>';
}
/**
* Checks whether a given tag is closing tag
*
* @param string $tag
* @return bool
*/
protected function isClosingTag($tag): bool
{
return mb_substr($tag, 0, 2) === '</';
}
/**
* Checks whether a given Tag is a an opening tag
*
* @param string $tag
* @return bool
*/
protected function isOpeningTag($tag): bool
{
return !($this->isSelfClosingTag($tag) || $this->isClosingTag($tag));
}
/**
* Gets a closing tag from a given opening tag
*
* @param string $openingTag
* @return string
*/
protected function getClosingTagByOpeningTag($openingTag): string
{
if (!$tag = mb_strstr(mb_substr($openingTag, 1), ' ', true)) {
$tag = mb_strstr(mb_substr($openingTag, 1), '>', true);
}
return '</' . $tag . '>';
}
}